Knowledge Base

Preserving for the future: Shell scripts, AoC, and more

Latest way to get certificate in FreeIPA

Copy pasta

openssl genpkey -algorithm RSA -out https-app1.ipa.internal.com.key
openssl req -new -key https-app1.ipa.internal.com.key -subj "/O=IPA.INTERNAL.COM/CN=app1.ipa.internal.com" -addext "subjectAltName = DNS:webapp.ipa.internal.com,DNS:app.ipa.internal.com" -out https-app1.ipa.internal.com.csr
ipa host-add --force webapp.ipa.internal.com
ipa host-add --force app.ipa.internal.com
ipa service-add --force HTTP/app1.ipa.internal.com
ipa service-add --force HTTP/webapp.ipa.internal.com
ipa service-add --force HTTP/app.ipa.internal.com
ipa cert-request --chain --principal=HTTP/app1.ipa.internal.com https-app1.ipa.internal.com.csr --certificate-out=https-app1.ipa.internal.com.pem

Extra, in case you forget to add "--chain" to the above command. It is not necessary for a 2-deep cert chain, that is, if you don't have an intermediate certificate.

sn="$( ipa cert-find --raw --services=HTTP/"$( hostname -f )" | awk '/serial_number:/{print $NF}' )"
ipa cert-show --chain "${sn}" --certificate-out=https-app1.ipa.internal.com.chain.pem

Explanation

I learned you can use genpkey from the (openssl) genrsa man page. This simplifies the command a little. And now, with the later versions of openssl, you can pass SAN extensions and even the subject on the command line! I remember reading about that years ago but this is the first time my server environment has a new enough version of openssl to take advantage of that.

References

  1. Generate certificate with SubjectAltName attributes in FreeIPA
  2. openssl-genpkey(1ossl)

Save whole hard drive image

I have some old hard drives (IDE connectors) I wish to backup. In my current solution, these old Windows XP-era-sized hard drives are a piece of cake to store. But how should I store them? They aren't compact discs, so genisoimage wouldn't cut it.

Here's how I chose to preserve them.

make disk image

time sudo qemu-img convert -c -O qcow2 /dev/sdd1 DOWNSTAIRS_PC.qcow2

mount image

sudo modprobe nbd max_part=8
sudo qemu-nbd --connect=/dev/nbd0 ~/DOWNSTAIRS_PC.qcow2
sudo fdisk /dev/nbd0 -l
sudo mount /dev/nbd0 /mnt/foo

when done

sudo umount /mnt/foo
sudo qemu-nbd --disconnect /dev/nbd0
sudo modprobe -r nbd

References

  1. https://unix.stackexchange.com/questions/522839/how-to-clone-a-single-partition-from-a-hd-to-a-qcow2-disk-image-for-use-in-qemu
  2. https://unix.stackexchange.com/questions/268460/how-to-mount-qcow2-image

I build d2x-rebirth for myself

Devuan/Debian has package d2x-rebirth which is the rewritten engine that can run Descent, the classic "6 Degrees of Freedom" game.

However, the upstream project has its code on Github which has had major improvements since the last time Debian grabbed the latest version (2013).

So I bothered to build d2x-rebirth myself, from Debian sources and the latest upstream code. All this so I can disable the thief bot in Descent 2.

You're welcome, and I'm sorry for all the time you're going to waste shooting rebellious robots.

PSA: Check your backups

Public service announcement

This is not a treatise on ensuring your backups work. That is a different topic for a different day.

You should make sure you backup what matters! Here are some things you probably want to exclude from your backups:

  • ~/.cache (temporary cache of anything on the OS for your user)
  • /mnt/public/Video/temp (where in-progress video conversion files live)
  • /var/lib/radicale/collections/*/*/*/.Radicale.cache
  • **/.~*.??? (to exclude usually LibreOffice lock files)
  • **/.*.swp (vim swap files)

Every so often you should run a reverse-sync in dry-run mode to see what you've changed/moved/removed/renamed since the last cleanup. Your Important document 5.odt might not be necessary, if you renamed it to Important-document-6.odt in the meantime.

For some reason I also found better reverse-sync logs when I omitted some of the famous -rlptgoDEA flags and just used:

time sudo rsync -nv -rltD ----exclude=/var/lib/radicale/collections/*/*/*/.Radicale.cache --exclude=**/.~*.??? --exclude=**/.*.swp /mnt/temp-mount/shares/ /var/server3/shares

With the ending slashes exactly as shown here. So just -rltD.

Installing Windows 7 on Alienware M17x-R4

I used to use a Windows-based process to copy the Windows 7 installer to a new hard disk which would then be installed in a new system, and then I would install the OS to that very drive from itself. It worked great. This was before 2015, the Year of Linux on the Desktop.

Now that I use all GNU/Linux, but I needed to install Windows 7, I had a hard time getting this prepared. Here is how I did it.

AskUbuntu indicated to use WoeUSB, so i used the provided instructions.

Deploying WoeUSB to a usb caused the win 7 installer to not be able to choose a disk at all (a failure of the windows installer, not WoeUSB), so I used WoeUSB to copy my win7x64 pro install iso to the SSD that I then placed in the system.

time sudo bash ./woeusb-5.2.4.bash --target-filesystem NTFS --device /mnt/public/Support/SetupsBig/Windows/en_windows_7_professional_x64_dvd_x15-65805.iso /dev/sdZ

Why I did it

The graphics card in a (Dell) Alienware M17x-R4 graphics card is an Nvidia 660M. I was unable to get the debian-packaged drivers and the nvidia.com drivers to work on modern kernel (6.4.0.3). I concluded it was useful for retro gaming, if you call the Windows 7 era retro gaming.

I could have probably used an older release of Devuan GNU+Linux, but I use exclusively Devuan unstable, and I don't want to start using an older release now.

References

  1. https://askubuntu.com/questions/289559/how-can-i-create-a-windows-bootable-usb-stick-using-ubuntu
  2. https://github.com/WoeUSB/WoeUSB
  3. https://superuser.com/questions/320581/couldnt-find-boot-disk-error-while-installing-windows-7-using-usb

Auxiliary but not needed in the path I took

  1. https://forums.tomshardware.com/threads/ssd-detected-but-cannot-install-windows-7-on-it.2759746/
  2. https://superuser.com/questions/919747/error-installing-windows-setup-was-unable-to-create-a-new-system-partition-or

House rules for Between Two Castles board game

In a departure from my ordinary content, here's something interesting my gaming group assembled over this past weekend.

The board game Between Two Castles of Mad King Ludwig is a great board game. It's a mashup of Between Two Cities and The Castles of Mad King Ludwig. For those of you who have played it enough to understand it well and want to try something ridiculously awesome and time-consuming, here's my set of house rules:

MegaTower rules

  1. Play phases of equal numbers of rooms per player until you run out of rooms to do this equally. The phases start with 9 rooms each like normal. Obviously you will get the most phases and turns if you play with only 3 players.
  2. At the beginning of the game, the players as a group may agree on up to 2 room types that can be built below grade in addition to normal. Suggestions: yellow, orange.
  3. You may have up to two outside (blue border) rooms on top of a column.
  4. Bonuses for number of same room type happen at 3, 6, and 8 rooms: you get the regular room bonus at 3 and 6, and the choose-shield-bonus at 8 (normally 5). If you happen to miss accumulating a bonus during a turn, then you forfeit that bonus.
  5. You can choose to delay any bonus until the end of the phase (running out of room in the hand). If you forget to execute a bonus at the end of a phase, you forfeit that bonus. You may play the bonus at any point before the end of the phase; that is, you do not have to wait until the end of the phase.
  6. At the end of each phase numbered N, you may rearrange up to N+1 rooms, while following all the normal rules of placement. Rooms can only be transposed; you cannot take up a new slot or leave a gap where there was a room before. This can occur before or after rule 5.

Color commentary

  1. If you have an even number of rooms in a phase, the players may decide to either make everyone play the last 2 rooms or discard the last 2 rooms. But who would choose to skip?!
  2. Yellow (food) rooms and orange (utility) rooms are the most on-theme for being below ground, and also seemed the most dependent on being near downstairs rooms. But the players may agree on up to any two additional room types.
  3. This facilitates lots of gardens stacked on top of each other. Also, double tower turrets on top of pretty much everything drains the entire stack of bonus rooms but feels so good points-wise!
  4. straightforward
  5. Forgetting a bonus is really annoying! Don't do it. The way delaying a bonus works is: if you accumulate a yellow bonus (draw 5 rooms and place one), go ahead and draw the 5 rooms now and set them aside until when you plan on playing that bonus. If you forget about them though, and the phase ends, you have to put all five face-down rooms back! For an orange bonus, draw the 3 bonus cards and leave them face-down until you wish to play them.
  6. Don't allow yourself to get stuck in analysis paralysis. Your group might decide to implement a time limit for this rearrangement operation.

Disclaimers

I am in no way related to whoever publishes this board game. I was not paid to make these statements. If somebody wishes to pay me for talking about this game and stretching it to its limits, you can find my email address.

Newspipe support for reverse proxy

I submitted this patch upstream.

I added a new option in the config, named prefix. If you set this, all the internal urls will add it. We use a ReverseProxy trick to add the prefix. This solves the generated urls, but for the hardcoded urls built throughout the jinja2 templates, I had to just manually update those. We use a neat trick to add a custom function to the template-parsing context which we can use to get the prefix (which safely returns the empty string if we had not set it, because we initialize the variable as empty).

Most of the lines of this patch are just adjusting all the templates to add the function call to {{ prefix() }}

diff --git a/instance/config.py b/instance/config.py
index 42e624c..af7617a 100644
--- a/instance/config.py
+++ b/instance/config.py
@@ -10,6 +10,9 @@ PORT = 5000
 DEBUG = True
 API_ROOT = "/api/v2.0"

+# Optional, and useful if you are using a reverse proxy with this virtual path prefix
+#PREFIX = "/newspipe"
+
 CSRF_ENABLED = True
 SECRET_KEY = "LCx3BchmHRxFzkEv4BqQJyeXRLXenf"
 SECURITY_PASSWORD_SALT = "L8gTsyrpRQEF8jNWQPyvRfv7U5kJkD"
diff --git a/instance/sqlite.py b/instance/sqlite.py
index 1f8d620..c6eaa46 100644
--- a/instance/sqlite.py
+++ b/instance/sqlite.py
@@ -10,6 +10,9 @@ PORT = 5000
 DEBUG = True
 API_ROOT = "/api/v2.0"

+# Optional, and useful if you are using a reverse proxy with this virtual path prefix
+#PREFIX = "/newspipe"
+
 CSRF_ENABLED = True
 SECRET_KEY = "LCx3BchmHRxFzkEv4BqQJyeXRLXenf"
 SECURITY_PASSWORD_SALT = "L8gTsyrpRQEF8jNWQPyvRfv7U5kJkD"
diff --git a/newspipe/bootstrap.py b/newspipe/bootstrap.py
index 1204491..1c5c967 100644
--- a/newspipe/bootstrap.py
+++ b/newspipe/bootstrap.py
@@ -44,6 +44,14 @@ def set_logging(
             handler.setLevel(log_level)
         logger.setLevel(log_level)

+class ReverseProxied(object):
+    def __init__(self, app, script_name):
+        self.app = app
+        self.script_name = script_name
+
+    def __call__(self, environ, start_response):
+        environ['SCRIPT_NAME'] = self.script_name
+        return self.app(environ, start_response)

 # Create Flask application
 application = Flask(__name__, instance_relative_config=True)
@@ -63,6 +71,11 @@ else:

 set_logging(application.config["LOG_PATH"])

+_prefix = ""
+if "PREFIX" in application.config:
+    application.wsgi_app = ReverseProxied(application.wsgi_app, script_name=application.config["PREFIX"])
+    _prefix = application.config["PREFIX"]
+
 db = SQLAlchemy(application)

 migrate = Migrate(application, db)
@@ -100,3 +113,9 @@ application.jinja_env.filters["datetime"] = format_datetime
 application.jinja_env.filters["datetimeformat"] = datetimeformat
 # inject application in Jinja env
 application.jinja_env.globals["application"] = application
+
+@application.context_processor
+def utility_processor():
+    def prefix():
+        return _prefix.rstrip("/")
+    return dict(prefix=prefix)
diff --git a/newspipe/controllers/user.py b/newspipe/controllers/user.py
index 00ffb96..d2c5bc0 100644
--- a/newspipe/controllers/user.py
+++ b/newspipe/controllers/user.py
@@ -138,7 +138,7 @@ class LdapuserController:
         namelist = []
         try:
             query = dns.resolver.query(f"_ldap._tcp.{domain}", "SRV")
-        except dns.resolver.NXDOMAIN:
+        except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
             # no records exist that match the request, so we were probably
             # given a specific hostname, and an empty query will trigger
             # the logic below that will add the original domain to the list.
diff --git a/newspipe/static/js/articles.js b/newspipe/static/js/articles.js
index f1cee6e..29ebac2 100644
--- a/newspipe/static/js/articles.js
+++ b/newspipe/static/js/articles.js
@@ -86,7 +86,7 @@ Array.prototype.map.call(nodes, function(node) {
       }

       // sends the updates to the server
-      fetch(API_ROOT + "article/" + article_id, {
+      fetch(prefix + API_ROOT + "article/" + article_id, {
         method: "PUT",
         headers: {
           'Content-Type': 'application/json',
@@ -157,7 +157,7 @@ Array.prototype.map.call(nodes, function(node) {
       }

       // sends the updates to the server
-      fetch(API_ROOT + "article/" + article_id, {
+      fetch(prefix + API_ROOT + "article/" + article_id, {
         method: "PUT",
         headers: {
           'Content-Type': 'application/json',
@@ -189,7 +189,7 @@ Array.prototype.map.call(nodes, function(node) {
         data = JSON.stringify(data);

         // sends the updates to the server
-        fetch(API_ROOT + "articles", {
+        fetch(prefix + API_ROOT + "articles", {
           method: "DELETE",
           headers: {
             'Content-Type': 'application/json',
diff --git a/newspipe/static/js/config.js b/newspipe/static/js/config.js
new file mode 100644
index 0000000..7fad438
--- /dev/null
+++ b/newspipe/static/js/config.js
@@ -0,0 +1,7 @@
+/*
+ * Set variables here for the javascript used by newspipe.
+ */
+var prefix = "/newspipe";
+
+/* Set exactly one trailing slash on prefix. */
+prefix = prefix.replace(/\/+$/,"/");
diff --git a/newspipe/templates/article.html b/newspipe/templates/article.html
index c62e3f0..d9cf9a6 100644
--- a/newspipe/templates/article.html
+++ b/newspipe/templates/article.html
@@ -4,7 +4,7 @@
     <div class="row" data-article="{{ article.id }}" id="filters" data-filter="{{ filter_ }}">
         <div class="col">
             <h2><a href="{{ article.link  }}" target="_blank">{{ article.title|safe }}</a></h2>
-            <h3>{{ _('from') }} <a href="/feed/{{ article.source.id }}">{{ article.source.title }}</a></h3>
+            <h3>{{ _('from') }} <a href="{{ prefix() }}/feed/{{ article.source.id }}">{{ article.source.title }}</a></h3>
             <a href="{{ url_for("article.delete", article_id=article.id) }}"><i class="fa fa-times delete" aria-hidden="true" title="{{ _('Delete this article') }}"></i></a>
             {% if article.like %}
                 <a href="#"><i class="fa fa-star like" aria-hidden="true" title="{{ _('One of your favorites') }}"></i></a>
diff --git a/newspipe/templates/duplicates.html b/newspipe/templates/duplicates.html
index 38dc52b..d138226 100644
--- a/newspipe/templates/duplicates.html
+++ b/newspipe/templates/duplicates.html
@@ -1,7 +1,7 @@
 {% extends "layout.html" %}
 {% block content %}
 <div class="container">
-    <p><h1>{{ _('Duplicates in the feed') }} <a href="/feed/{{ feed.id }}">{{ feed.title }}</a>.</h1><p>
+    <p><h1>{{ _('Duplicates in the feed') }} <a href="{{ prefix() }}/feed/{{ feed.id }}">{{ feed.title }}</a>.</h1><p>
     <div class="table-responsive">
         <table class="table table-striped">
             <thead>
@@ -19,8 +19,8 @@
             {% for pair in duplicates %}
                 <tr>
                     <td>{{ loop.index }}</td>
-                    <td id="{{ pair[0].id }}"><a href="{{ url_for("article.delete", article_id=pair[0].id) }}"><i class="fa fa-times" aria-hidden="true" title="{{ _('Delete this article') }}"></i></a>&nbsp;<a href="/article/{{ pair[0].id }}">{{ pair[0].title }}</a> ({{ pair[0].retrieved_date }})</td>
-                    <td id="{{ pair[1].id }}"><a href="{{ url_for("article.delete", article_id=pair[1].id) }}"><i class="fa fa-times" aria-hidden="true" title="{{ _('Delete this article') }}"></i></a>&nbsp;<a href="/article/{{ pair[1].id }}">{{ pair[1].title }}</a> ({{ pair[1].retrieved_date }})</td>
+                    <td id="{{ pair[0].id }}"><a href="{{ url_for("article.delete", article_id=pair[0].id) }}"><i class="fa fa-times" aria-hidden="true" title="{{ _('Delete this article') }}"></i></a>&nbsp;<a href="{{ prefix() }}/article/{{ pair[0].id }}">{{ pair[0].title }}</a> ({{ pair[0].retrieved_date }})</td>
+                    <td id="{{ pair[1].id }}"><a href="{{ url_for("article.delete", article_id=pair[1].id) }}"><i class="fa fa-times" aria-hidden="true" title="{{ _('Delete this article') }}"></i></a>&nbsp;<a href="{{ prefix() }}/article/{{ pair[1].id }}">{{ pair[1].title }}</a> ({{ pair[1].retrieved_date }})</td>
                 </tr>
             {% endfor %}
             </tbody>
diff --git a/newspipe/templates/history.html b/newspipe/templates/history.html
index 153c2f1..00e22ef 100644
--- a/newspipe/templates/history.html
+++ b/newspipe/templates/history.html
@@ -43,7 +43,7 @@
       <ul class="list-group">
         {% for date in articles_counter | sort(reverse = True) %}
         {% for article in articles %}
-        <li class="list-group-item">{{ article.date | datetime }} - <a href="/article/{{ article.id }}">{{ article.title | safe }}</a></li>
+        <li class="list-group-item">{{ article.date | datetime }} - <a href="{{ prefix() }}/article/{{ article.id }}">{{ article.title | safe }}</a></li>
         {% endfor %}
         {% endfor %}
       </ul>
diff --git a/newspipe/templates/home.html b/newspipe/templates/home.html
index 631b769..5feb18d 100644
--- a/newspipe/templates/home.html
+++ b/newspipe/templates/home.html
@@ -42,7 +42,7 @@
                             {% if feed_id == feed.id %}</b>{% endif %}
                         </a></li>
                         <li class="nav-item feed-commands {% if in_error.get(fid, 0) > 0 %}d-none{% endif %}" data-bs-feed="{{ feed.id }}"><span class="nav-link">
-                            <a href="/feed/{{ feed.id }}"><i class="fa fa-info" aria-hidden="true" title="{{ _('Details') }}"></i></a>
+                            <a href="{{ prefix() }}/feed/{{ feed.id }}"><i class="fa fa-info" aria-hidden="true" title="{{ _('Details') }}"></i></a>
                             <a href="{{ url_for('feed.form', feed_id=feed.id) }}"><i class="fa fa-pencil-square-o" aria-hidden="true" title="{{ _('Edit this feed') }}"></i></a>
                             <a href="{{ url_for('article.mark_as', new_value='unread', feed_id=feed.id) }}"><i class="fa fa-square-o" aria-hidden="true" title="{{ _('Mark this feed as unread') }}"></i></a>
                             <a href="{{ url_for('article.mark_as', new_value='read', feed_id=feed.id) }}"><i class="fa fa-check-square-o" aria-hidden="true" title="{{ _('Mark this feed as read') }}"></i></a>
@@ -73,7 +73,7 @@
                 {% if feed_id == fid %}</b>{% endif %}
             </a></li>
             <li class="nav-item feed-commands {% if in_error.get(fid, 0) > 0 %}d-none{% endif %}" data-bs-feed="{{ fid }}"><span class="nav-link">
-                <a href="/feed/{{ fid }}"><i class="fa fa-info" aria-hidden="true" title="{{ _('Details') }}"></i></a>
+                <a href="{{ prefix() }}/feed/{{ fid }}"><i class="fa fa-info" aria-hidden="true" title="{{ _('Details') }}"></i></a>
                 <a href="{{ url_for('feed.form', feed_id=fid) }}"><i class="fa fa-pencil-square-o" aria-hidden="true" title="{{ _('Edit this feed') }}"></i></a>
                 <a href="{{ url_for('article.mark_as', new_value='unread', feed_id=fid) }}"><i class="fa fa-square-o" aria-hidden="true" title="{{ _('Mark this feed as unread') }}"></i></a>
                 <a href="{{ url_for('article.mark_as', new_value='read', feed_id=fid) }}"><i class="fa fa-check-square-o" aria-hidden="true" title="{{ _('Mark this feed as read') }}"></i></a>
@@ -102,7 +102,7 @@
                 {% if feed_id == fid %}</b>{% endif %}
             </a></li>
             <li class="nav-item feed-commands {% if in_error.get(fid, 0) > 0 %}d-none{% endif %}" data-bs-feed="{{ fid }}"><span class="nav-link">
-                <a href="/feed/{{ fid }}"><i class="fa fa-info" aria-hidden="true" title="{{ _('Details') }}"></i></a>
+                <a href="{{ prefix() }}/feed/{{ fid }}"><i class="fa fa-info" aria-hidden="true" title="{{ _('Details') }}"></i></a>
                 <a href="{{ url_for('feed.form', feed_id=fid) }}"><i class="fa fa-pencil-square-o" aria-hidden="true" title="{{ _('Edit this feed') }}"></i></a>
                 <a href="{{ url_for('article.mark_as', new_value='unread', feed_id=fid) }}"><i class="fa fa-square-o" aria-hidden="true" title="{{ _('Mark this feed as unread') }}"></i></a>
                 <a href="{{ url_for('article.mark_as', new_value='read', feed_id=fid) }}"><i class="fa fa-check-square-o" aria-hidden="true" title="{{ _('Mark this feed as read') }}"></i></a>
@@ -174,11 +174,11 @@
                             {% if not feed_id %}
                                 <td class="d-none d-md-block">
                                     <img src="{{ url_for('icon.icon', url=feeds[article.source.id].icon_url) }}" width="16px">
-                                    <a href="/article/redirect/{{ article.id}}" target="_blank">{{ article.source.title | safe }}</a>
+                                    <a href="{{ prefix() }}/article/redirect/{{ article.id}}" target="_blank">{{ article.source.title | safe }}</a>
                                 </td>
                             {% endif %}
                             <td {%if filter_ == 'all' and article.readed == False %}style='font-weight:bold'{% endif %}>
-                                <a href="/article/{{ article.id }}" title="{{ article.title }}">{{ article.title | truncate(100, False, '...') }}</a>
+                                <a href="{{ prefix() }}/article/{{ article.id }}" title="{{ article.title }}">{{ article.title | truncate(100, False, '...') }}</a>
                             </td>
                             <td class="date d-none d-lg-block">{{ article.date | datetime(format='short') }}</td>
                         </tr>
diff --git a/newspipe/templates/layout.html b/newspipe/templates/layout.html
index 2464040..466ff4e 100644
--- a/newspipe/templates/layout.html
+++ b/newspipe/templates/layout.html
@@ -21,7 +21,7 @@
     {% block menu %}
     <nav class="navbar navbar-expand-lg navbar-dark bg-newspipe-blue">
        <div class="container-fluid">
-        <a class="navbar-brand" href="/">Newspipe</a>
+        <a class="navbar-brand" href="{{ prefix() }}/">Newspipe</a>
         <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
             <span class="navbar-toggler-icon"></span>
         </button>
@@ -150,6 +150,7 @@
     {% block content %}{% endblock %}

     <!-- Placed at the end of the document so the pages load faster -->
+    <script type="text/javascript" src="{{ url_for('static', filename = 'js/config.js') }}"></script>
     <script type="text/javascript" src="{{ url_for('static', filename = 'js/articles.js') }}"></script>
     <script type="text/javascript" src="{{ url_for('static', filename = 'js/feed.js') }}"></script>
   </body>
diff --git a/newspipe/templates/login.html b/newspipe/templates/login.html
index b995230..78af3c4 100644
--- a/newspipe/templates/login.html
+++ b/newspipe/templates/login.html
@@ -19,7 +19,7 @@
                     <div class="alert alert-warning" role="alert">{{ message }}</div>
                 {% endfor %}
                 {{ form.submit(class_="btn btn-primary") }}
-                {% if self_registration %}<a href="/signup" class="btn btn-info">{{ _('Sign up') }}</a>{% endif %}
+                {% if self_registration %}<a href="{{ prefix() }}/signup" class="btn btn-info">{{ _('Sign up') }}</a>{% endif %}
             </form>
         </div>
     </div>
diff --git a/newspipe/templates/management.html b/newspipe/templates/management.html
index 4e977f8..19dbded 100644
--- a/newspipe/templates/management.html
+++ b/newspipe/templates/management.html
@@ -6,7 +6,7 @@
             <div class="row">
                 <div class="col">
                     <h2>{{ _('Your subscriptions') }}</h2>
-                    <p>{{ _('You are subscribed to') }} {{ nb_feeds }} <a href="/feeds">{{ _('feeds') }}</a>. <a href="{{ url_for("feed.form") }}">{{ _('Add') }}</a> {{ _('a feed') }}.</p>
+                    <p>{{ _('You are subscribed to') }} {{ nb_feeds }} <a href="{{ prefix() }}/feeds">{{ _('feeds') }}</a>. <a href="{{ url_for("feed.form") }}">{{ _('Add') }}</a> {{ _('a feed') }}.</p>
                     <p>{{ nb_articles }} {{ _('articles are stored in the database with') }} {{ nb_unread_articles }} {{ _('unread articles') }}.</p>
                     <p>{{ _('You have') }} {{ nb_categories }} <a href="{{ url_for("categories.list_")}}">{{ _('categories') }}</a>.</p>
                     <a href="{{ url_for("articles.expire", weeks=10) }}" class="btn btn-primary" onclick="return confirm('{{ _('You are going to delete old articles.') }}');">{{ _('Delete articles older than 10 weeks') }}</a>

Coming soon to newspipe: reverse proxy support

I have taken it upon myself to add reverse-proxy with prefix support to my docker image for newspipe.

So far I have written some basic parts such as a config option for specifying the expected prefix. See the commits in both the newspipe and newspipe-docker repos.

What I haven't done yet is update the url_for function to also add the prefix to the generated urls for most links in the app. Once I do that, I'll send a pull request to upstream.

References

Weblinks

  1. Flask application behind a reverse proxy - Wolfblog
  2. How to mount a Flask app under a URL prefix (or really, any WSGI app) - Little Umbrellas
  3. Radicale v3 Documentation

My previous related work

  1. automatically detect X-Forwarded-Prefix
  2. Set the configured prefix with a custom endpoint

New image helper

Because I love reinventing the wheel, here is my way-too-simple photo gallery web page generator.

#!/bin/sh
# Startdate: 2023-07-13-5 16:48
# Purpose: make simple html gallery of images and directories
# Reference: tapestry-helper.sh
# Usage:
#    Ensure no spaces in the filenames.
# Improve:
#    add ".." links to any child directories.
INDIR=/mnt/public/www/gallery/2013-01-picasa-partial-dump
HTML_HEAD="${INDIR}/.header.html"
HTML_FOOT="${INDIR}/.footer.html"
DIRECTORY_ICON="/gallery/2013-01-picasa-partial-dump/folder.svg"
THUMBNAIL_SIZE=300
USE_THUMBNAILS=1
if test -n "${USE_THUMBNAILS}" && test "${USE_THUMBNAILS}" = 1 ;
then
   mkdir -p "${INDIR}/.thumbnails"
fi
# use MAKE_THUMBNAILS=1 to actually run convert, which slows the process down. You disable MAKE_THUMBNAILS when just regenerating the html
test -z "${MAKE_THUMBNAILS}" && MAKE_THUMBNAILS=0
cd "${INDIR}"
# now that there are no spaces we can just do this
# begin dir loop
for word in $( find "${INDIR}" -mindepth 0 -type d ! -name '.thumbnails' ) ;
do
   (
   cd "${word}"
   shortword="$( basename "${word}" )"
   mkdir -p ./.thumbnails
   rm -f ./index.html
   exec 3>&1
   exec 1>> ./index.html
   eval "cat <<EOF
$( cat "${HTML_HEAD}" )
EOF
"
   # make a .. link if not the parent directory
   if ! test "${word}" = "${INDIR}" ; then
      echo "<div class=\"item\"><a href=\"..\"><img src=\"${DIRECTORY_ICON}\" class=\"thumb\">..</a></div>"
   fi
   tds="$( find . -mindepth 1 -maxdepth 1 -type d ! -name '.thumbnails' ! -name '.*' )"
   for td in ${tds} ; do
      td_basename="$( basename "${td}" )"
      #echo "<a href=\"${td}\"><img src=\"${DIRECTORY_ICON}\" class=\"thumb\">${td}</a>"
      echo "<div class=\"item\"><a href=\"${td}\"><img src=\"${DIRECTORY_ICON}\" class=\"thumb\">${td_basename}</a></div>"
   done
   tfs="$( find . -mindepth 1 -maxdepth 1 ! -type d ! -name '*.ini' ! -name '.*.ini' ! -name '.*.swp' ! -name '*.sh' ! -name '*.html' ! -name '*.db' ! -name 'folder.svg' ! -name '.*' ! -name '*.css' -printf '%T@ %P\n' | sort -n | awk '{print $NF}' )"
   for tf in ${tfs} ;
   do
      tf_in="${word}/${tf}"
      tf_thumb_web="${tf}"
      if test -n "${USE_THUMBNAILS}" && test "${USE_THUMBNAILS}" = 1 ;
      then
         tf_thumb="${word%%/}/.thumbnails/${tf%%.???}.jpg"
         tf_thumb_web=".thumbnails/${tf%%.???}.jpg"
         test "${MAKE_THUMBNAILS}" = "1" && convert -filter Lanczos -resize "${THUMBNAIL_SIZE}x${THUMBNAIL_SIZE}" "${word}/${tf}" "${tf_thumb}"
      fi
      echo "<a href=\"${tf}\"><img src=\"${tf_thumb_web}\" class=\"thumb\"></a>"
   done
   cat "${HTML_FOOT}"
   exec 1>&3
   )
done # end dir loop

My .header.html and .footer.html files are very basic.

<html>
<!-- started 2023-07-13 -->
<link rel="stylesheet" href="/gallery/2013-01-picasa-partial-dump/simple.css">
<head>
<title>${shortword}</title>
</head>
<body>

And footer just closes the html tags.

</body>
</html>

And the css.

.thumb {
   max-width: 300px;
   max-height: 300px;
}

div.item {
    vertical-align: top;
    display: inline-block;
    text-align: center;
    width: 120px;
}
/*
img {
    width: 100px;
    height: 100px;
    background-color: grey;
}
*/
div.item img {
   max-width: 220px;
   max-height: 220px;
}
.caption {
    display: block;
}

I just grabbed a random folder.svg from one of the GNOME Icon themes.