All posts by #!/bin/blog

Wo kommt eigentlich das Wasser her?

Tja. Weiß man doch. Oder?

“Fällt bei Stromausfall eigentlich auch das Wasser aus?” fragte jemand im Kontext des Stromausfall in Spanien und Portugal im April 2025.

Soviel wusste ich schonmal: Das Trinkwasser wird aktiv in die bekannten Hochbehälter gepumpt, und spätestens sobald die leer sind, fällt es aus. Vermutlich ist das auch gut so, denn die Kläranlage funktioniert bei Stromausfall schließlich ebenfalls nicht.

Ich habe das Thema zunächst vergessen und als ich dann mal wieder am Hochbehälter spazierenging, war die Frage erneut da: Wo kommt eigentlich das Wasser her?


Die wenigen Fakten

An den aktiven und historischen Hochbehältern hier in Pohlheim stehen Schautafeln, die aber in erster Linie die (gar nicht mal uninteressante) Historie beleuchten und nicht so wirklich auf die aktuellen Gegebenheiten eingehen. Ein paar Eckpunkte lassen sich hier aber ablesen:

  • Das Wasser kommt seit den 1960er Jahren nicht mehr aus Quellen vor Ort, sondern aus dem 50 km entfernten Wasserwerk Stadtallendorf.
  • Die Leitung in meine Stadt zweigt an einem bekannten Industriebetrieb mitten im Wald von der Fernleitung ab.

Das sind erste Kristallisationspunkte, an denen man andocken kann.

Umfangreiches Klicken brachte einen Netzplan des Zweckverbands Mittelhessischer Wasserwerke zutage, von dem ich lernte, dass Wasserleitungen Nummern zur Identifikation haben, und dass es Wasserleitungen gibt, die vom Zweckverband betrieben werden und welche, die von der Stadt betrieben werden. Mich interessierten vor allem die vom Zweckverband betriebenen Fernleitungen, die jeweils an der Stadgrenze enden, ab wo es mit Hausanschlüssen und Hydranten weitergeht.

Auf dem Netzplan tauchten auch zum ersten mal die von mir in ihrer Existenz lediglich vermuteten Pumpwerke, im Fachjargon Druckerhöhungsanlagen, auf. Eine kleine, klar zum älteren aber noch aktiven Hochbehälter gehörende, die das Wasser in den höher gelegenen neuen Hochbehälter pumpt, und eine größere irgendwo im Süden von Gießen.


Zeichen lesen

Weiß man erst einmal, dass die Leitungen Nummern haben, erkennt man auf den kleinen blauen Wasserschildern, mit denen alle Armaturen oberirdisch beschriftet sind, leicht eine organisatorische Nummer, die immer die Nummer der jeweiligen Leitung beinhaltet. Mit dem eher abstrakten Netzplan bewaffnet, konnte ich dann relativ gut ausschließen, was links und was rechts der Leitung liegt, Annahmen über den weiteren Verlauf der Leitung treffen und immer mehr unterirdische Standorte identifizieren, die Rückschluss auf den Leitungsverlauf erlauben. Mit etwas numerologischem Gefühl kann man anhand zweier Nummern auch bewerten, ob man dazwischen noch einen Standort übersehen hat.

Die Kürzel der Wasserschilder hinsichtlich des Typs der jeweiligen Armatur sind mir leider bis zuletzt ein Rätsel geblieben, denn es existiert hier vor Ort *keine* Schnittmenge (also wirklich Null) mit dem, was jemand auf Wikipedia dazu aufgeschrieben hat.

KürzelBedeutungOverpass Turbo Suche
SSchieberhttps://overpass-turbo.eu/s/25bu
SSSchieberschacht(?) – Es ist hier immer ein Einstieg vorhanden.https://overpass-turbo.eu/s/25bm
BEBelüftung (und Entlüftung?)https://overpass-turbo.eu/s/25bn
ENUnbekannthttps://overpass-turbo.eu/s/25bo
MSUnbekannthttps://overpass-turbo.eu/s/25bp
VBUnbekannt, ausschließlich an großen Abzweigen der Hauptleitung 1.1 gesehen.https://overpass-turbo.eu/s/25bq
AKAbsperrklappe(?)https://overpass-turbo.eu/s/25br
Düker unter der Lahn auf der Hauptleitung 2.7https://overpass-turbo.eu/s/25bs

Exkursionen

Nachdem die Umgebung des Hochbehälters zu Fuß erkundet war, gab es keine Ausreden mehr, und es musste mit dem Fahrrad querfeldein gehen.

Bei dieser Spurensuche hat sich eine ganz wichtige Grundregel entwickelt: Die Leute vom Wasserwerk wollen und müssen mit ihrem blauen Werkstattwagen überall direkt ran. Jeder einzelne Standort, den ich identifiziert habe, ist (wenn auch nicht öffentlich) mit einem nicht geländegängigen Auto anfahrbar. Auf einem kaum passierbaren Trail mitten in der Wildnis wird man niemals Wasserarmaturen finden.


Verdatung

Jeder hat auf Openstreetmap schon die eingetragenen Hochspannungstrassen gesehen. Wasserleitungen sind dagegen absolut exotisch. Hydranten sind vielleicht kartiert, Schieber eher nicht, Leitungsverläufe schon gar nicht. Eine Ausnahme davon ist Florstadt in der Wetterau, wo die Feuerwehr die innerörtlichen Leitungsverläufe kartiert hat.

Ich habe also angefangen, mir ein paar der dünn gesäten Beispiele zu suchen, das ganze mit dem OSM-Wiki abgeglichen und angefangen, wie folgt zu taggen:

Armaturen

diameter=x
operator=ZMW
pipeline=valve
ref=x.y
substance=water
note=x (Kürzel und Durchmesserangabe des Schilds)

Leitungen

name=ZMW x.y
operator=ZMW
ref=x.y
waterway=pressurized
man_made=pipeline
substance=water
location=underground
layer=-1

Die Gesamtheit aus Teilstücken und Armaturen als Relation

layer=-1
man_made=pipeline
ref=z.y
name=ZMW x.y
substance=water
type=waterway
waterway=pressurised

Hydranten habe ich nur in Ausnahmefällen ergänzt:

  • Ganz fehlende Hydranten auf Hauptleitungen aufgenommen
  • Gegebenenfalls note=Blauer Rand für Hydranten, die nicht ohne weiteres von der Feuerwehr zur Wasserentnahme benutzt werden dürfen.
  • Falschzuordnung Unter-/Überflurhydrant berichtigt
  • Wenn ergänzt wurde, dann auch Durchmesser und Referenznummer dazu.

Hier die von mir kartierten Relationen:

  • ZMW 1.1, die aus Richtung Wetzlar kommende und östlich von Gießen verlaufende Fernleitung nach Stadtallendorf
  • ZMW 2.7, die von ZMW 1.1 abzweigende westlich von Gießen verlaufende Fernleitung nach Stadtallendorf
  • ZMW 5.9, die von ZMW 1.1 abzweigende Hauptleitung zu meinem Wohnort
  • ZMW 3.14, eine zu einer südlich gelegenen Kommune weiterführende Leitung, die auch die beiden Hochbehälter verbindet
  • ZMW 3.15, eine redundante Leitung zwischen den Hochbehältern
  • ZMW 4.15, eine zu den östlichen Stadtteilen führende Leitung
  • ZMW 4.16, eine zu den südlichen Stadtteilen führende Leitung
  • ZMW 4.18, eine zu den nördlichen Stadtteilen führende Leitung, von der ich vermute, dass sie auch die Anbindung der sogenannten “Hochdruckzone” der Stadt ist.
  • ZMW 1.5 in Richtung Lich hat mich nur interessiert, weil ihre Druckerhöhungsanlage so prominent im Wald zu sehen ist

Des weiteren:

Leitungsverläufe wurden grundsätzlich nur als direkte Verbindungen zwischen überirdisch gefundenen Armaturen kartiert, außer es war eine zweifelsfreie Aussage über den Verlauf möglich, wie etwa bei ZMW 1.5 auf der auffälligen Schneise im Stadtwald, sowie der SWG-Querverbindung entlang der Bahngleise und entlang des Aulweg, an dessen Kreuzung mit dem Wartweg zum Zeitpunkt meiner Erkundungen genau diese 600er Leitung der Querverbindung gebrochen war.


Druckerhöhungsanlagen

Die Schnittstelle zwischen ZMW 1.1, ihrer Querverbindung, und ZMW 2.7 hat mir ein paar Tage lang ziemliche Rätsel aufgegeben, weil sie sich an einem komplett unübersichtlichen gordischen Knoten zwischen Bahnlinien und mehreren autobahnänlichen Bundesstraßen befinden musste. Ich konnte das sehr unauffällige Gebäude schließlich zweifelsfrei identifizieren, habe es aber in der Karte nicht getaggt.

Die Druckerhöhungsanlage am Abzweig der ZMW 1.5 von der ZMW 1.1 steht prominent im Wald und ist vor Ort als solche beschriftet.


Wie weiter

Ein spannender Einblick in Infrastruktur, von der man kaum etwas weiß und sieht, geht zu Ende. Unauffälligen Grüngürteln in Neubaugebieten und vegetationsfreien Schneisen im Wald messe ich plötzlich Bedeutung als potenziellem und vermutlichem Trassenverlauf zu.

Falls mich jemand auf Insiderbasis in die Geheimnisse der Schilderkürzel einweihen und zu einer Besichtigungstour in Wasserwerk und Hochbehälter einladen möchte: Kontaktmöglichkeiten finden sich im Impressum. 🙂

Too Good To #016

Today: IPv6 with EUI64, kernel panic evasion, Seahorse CLI interaction.


IPv6 with EUI64 (the thing with the hardware address) in NetworkManager

# nmcli connection modify "Wired connection 1" ipv6.ip6-privacy 0
# nmcli connection modify "Wired connection 1" ipv6.addr-gen-mode eui64

Auto-prevent crashy service from starting

We have a commercial piece of software here that can cause an irrecoverable kernel panic when starting, by loading a proprietary kernel module. Detect whether the system was previously shut down cleanly and fail the service, if not:

# /etc/systemd/system/fouled-up.service.d/prevent.conf

[Service]
ExecStartPre=echo 'Analyzing journal for clean shutdown on previous boot.'
ExecStartPre=journalctl -b -1 -g 'Shutting down.' _COMM=systemd
  • ExecStartPre – Fail the unit if this command fails.
  • journalctl -b -1 – Look at entries from previous boot.
  • -g 'Shutting down.' – Grep for entries containing this string. Returns non-zero if not found.
  • _COMM=systemd – Match entries from this command only.

Gnome session keyring / Seahorse CLI interaction

Nobody seems to really know how this works. I do however have an application password that I want to keep in the Gnome session keyring.

The documentation for secret-tool references attributes/keys and values, but there seem to be no predefined names or convention for this.

How to save and retrieve a password in the Gnome session keyring:

# apt-get install libsecret-tools
$ printf 'xxsecretxx' |
  secret-tool store --label='my application secret' application theapplication
$ secret-tool lookup application theapplication
xxsecretxx

Benchmarking ratarmount

Ratarmount is an excellent tool for mounting archives as filesystems, and I use it a lot. Mostly for union-mounting tar.xz telemetry bundles created by sos report. The ratarmount README suggests to prefer indexed tar.xz archives created using pixz for performance, so let’s see what’s the best compression to use.


TL;DRs

  • On huge archives, tar.gz is always fast and fastest, no optimization required.
  • Best to recompress tar.xz to tar.gz for best performance. Recompressing with pixz yields an improvement, but not as much as gzip.
  • On tiny archives close to the host’s RAM size, performance is hard to predict and may put gzip behind.

The “Backup” use-case

For my test to have a bit of a sizable workload, I pick a reasonably-sized tar.gz, a remnant historical backup of a long-gone server:

-rw-r----- 1 root root 1.7G Aug  6 10:16 example.tar.gz

This is stored on a RAID-1 of 7200 rpm hard drives, which should amplify all seek performance issues. 8 GB RAM, 6 physical CPU cores, 12 threads.

I prepare a list of 1000 random files from the archive that I’ll be reading from the mounted archive.

tar ztf example.tar.gz | egrep -v '(/$|(sys|proc|dev|run))' | shuf | head -1000 > example.list

Now, I mount the tar.gz for my baseline measurement.

umount ./mnt; ratarmount example.tar.gz ./mnt
time xargs -I{} md5sum ./mnt/{} < example.list
...
real    0m13.974s
user    0m1.178s
sys     0m0.984s

I recompress the tar from gzip to pixz and measure again:

gzip -dc example.tar.gz | pixz > example.tar.pxz
umount ./mnt; ratarmount example.tar.pxz ./mnt
time xargs -I{} md5sum ./mnt/{} < example.list
...
real    0m57.408s
user    0m1.216s
sys     0m0.904s

Multiple times slower! Back to ratarmount‘s README: “In contrast to bzip2 and gzip compressed files, true seeking on XZ and ZStandard files is only possible at block or frame boundaries.”Are you telling me gzip is not the issue and only naively compressed xz is? A quick recompress using vanilla xz and a comparison of that to the pixz compressed archive:

gzip -dc example.tar.gz | xz --threads=$(nproc) > example.tar.xz
umount ./mnt; ratarmount example.tar.xz ./mnt
time xargs -I{} md5sum ./mnt/{} < example.list
...
real    1m33.549s
user    0m1.109s
sys     0m0.838s

Indeed a noticable, although not huge, penalty compared to pixz. Now that I’m here and wasted this much time, a final measurement using bzip2:

gzip -dc example.tar.gz | bzip2 > example.tar.bz2
umount ./mnt; ratarmount example.tar.bz2 ./mnt
time xargs -I{} md5sum ./mnt/{} < example.list
...
real    0m44.410s
user    0m1.301s
sys     0m1.164s

So ratarmount handles bzip2 around the same speed as xz created by pixz.

Gzip is always fastest, even without any special treatment, and I assume this is because multi-threaded rapidgzip literally is ratarmount’s sister project.


The “Telemetry” use-case

Back to my tiny sosreport files in tar.xz format, still on the 7200-rpm HDD system. For consistency, I’ll use the same md5sum benchmark on 1000 archive members as above.

ls -lh sosreport.tar.xz
-rw------- 1 root root 9.3M Aug 14 16:07 sosreport.tar.xz
tar Jtf sosreport.tar.xz | egrep -v '(/$|(sys|proc|dev|run))' | shuf | head -1000 > sosreport.list
umount ./mnt; ratarmount sosreport.tar.xz ./mnt
time xargs -I{} md5sum ./mnt/{} < sosreport.list
...
real    0m4.979s
user    0m0.940s
sys     0m0.625s

A conversion to pixz:

xz -dc sosreport.tar.xz | pixz > sosreport.tar.pxz
umount ./mnt; ratarmount sosreport.tar.pxz ./mnt
time xargs -I{} md5sum ./mnt/{} < sosreport.list
...
real    0m6.847s
user    0m0.829s
sys     0m0.553s

And a conversion to tar.gz:

xz -dc sosreport.tar.xz | gzip > sosreport.tar.gz
umount ./mnt; ratarmount sosreport.tar.gz ./mnt
time xargs -I{} md5sum ./mnt/{} < sosreport.list
...
real    0m13.202s
user    0m0.918s
sys     0m0.598s

gzip is suddenly slower here, and I believe it’s because the file turned out more than 50% larger than the xz versions, both of which are close to the hosts’s RAM size of 8 GB:

-rw-r--r-- 1 root root  14M Aug 14 16:17 sosreport.tar.gz
-rw-r--r-- 1 root root 7.8M Aug 14 16:16 sosreport.tar.pxz
-rw------- 1 root root 9.3M Aug 14 16:07 sosreport.tar.xz

My initial notes on how to install ratarmount in a python virtualenv are documented in Too good to #0013.

Too Good To #015

The terrible benchmarks edition.

A number of ad-hoc benchmarks I collected over time. They are terrible.

Use at your own risk. Not only your hardware may be in danger, but most of all, your reputation.


CPU (create high load)

Note this is GNU parallel (apt-get install parallel), not the 100% incompatible moreutils parallel.

See parallel_alternatives(7); it’s quite hilarious.

parallel --line-buffer --tagstring='{#}' --max-args=0 openssl speed ::: $(seq $(nproc))

CPU (compare speed)

sysbench --num-threads=$(nproc) --test=cpu run

Newer invocation style:

sysbench --threads=$(nproc) cpu run

CPU (compare speed, single-core shitbox edition)

Cobbled this up for putting a 1993 vintage RS/6000 into perspective. Counts how many rand() / (rand() + 1) divisions the system will do in 10 second intervals.

perl -e '$o=time();$s=$o;while(10>$o-$s)
{rand()/(rand()+1);$i++;$n=time();if($n!=$o)
{printf"%i\n",$i;$i=0};$o=$n}'

Storage (throughput)

  • -u $LOGNAME: Specify username explicitly (relevant when running as root)
  • -f without argument: Skip character i/o tests
  • -n 0: Skip file creation tests
/sbin/bonnie++ -u $LOGNAME -d . -f -n 0

Storage (filesystem)

  • -u $LOGNAME: Specify username explicitly (relevant when running as root)
  • -s 0: Skip the throughput test
  • -n 128: Number of files times 1024, increase until the +++-outputs disappear.
/sbin/bonnie++ -u $LOGNAME -d . -s 0 -n 128

Der Untergang der Morning Midas

Die Morning Midas war ein Frachtschiff beladen mit Autos, das ab dem 26. Mai 2025 auf dem Weg von China nach Mexiko war. Am 3. Juni 2025 geriet es im Nordpazifik in Brand und die Crew konnte nach erfolglosen Löschversuchen von einem vorbeikommenden Frachtschiff aufgenommen werden. Es gab keine Toten oder Verletzten.

Am 16. Juni meldete die US Coast Guard, das Feuer auf dem führerlosen Schiff sei erloschen.

Am 24. Juni wurde dann schließlich der Untergang der Morning Midas am Tag zuvor gemeldet.


Autos an Bord

Die Ladung der Morning Midas setzte sich nach Meldungen der US Coast Guard wie folgt zusammen:

  • 3048 Fahrzeuge insgesamt
  • 70 batterieelektrische Fahrzeuge
  • 681 Fahrzeuge mit Hybridantrieb
  • Daraus abgeleitet also: 2297 reine Verbrennerfahrzeuge und 2978 Fahrzeuge mit Verbrennungsmotor.

In einer bisher so nicht beobachteten Auslegung wurde daraus in den meisten Teilen der Presse sofort ein Frachter “mit mehr als 700 Elektroautos an Bord”: PCTC Morning Midas Ablaze in Pacific with 700+ EVs Onboard, Crew Safely Evacuated

Zum Tankinhalt der 2978 Fahrzeuge mit Verbrennungsmotor kamen 1880 Tonnen(!) fossiler Betriebsmittel für das Schiff selbst, die beim Untergang – noch – nicht in die Umwelt ausgetreten waren.


Brandwahrscheinlichkeit pro Antriebskonzept

Die Hybriden sollen für eine ganz einfach gehaltene Betrachtung doppelt, sowohl als Verbrenner als auch als Elektroauto gezählt werden:

  • 2978 konventionelle Antriebe mit Verbrennungsmotor
  • 751 Elektroantriebe

Es gilt als erwiesen, dass das Brandrisiko bei Elektroautos nicht höher ist als bei Verbrennern. Für die Berechnung soll angenommen werden, dass es gleich hoch sei.

Die Wahrscheinlichkeit, dass der Brand von einem Elektroantrieb ausgegangen ist, liegt somit bei 751 ÷ 3048, also ziemlich genau 1:4 oder 25%.

Die Wahrscheinlichkeit, dass der Brand durch einen Verbrennungsmotor entstanden ist, liegt dagegen bei 2978 ÷ 3048, also 98%.


Das Elektroauto-Deck

Gar nicht so einfach, eine sinnvolle Quelle zu finden, wonach die Besatzung den Brand auf dem Deck mit den Elektroautos beobachtet hat. Dieser etwas erratisch aufgebaute Artikel bei CNN aus den ersten Tagen berichtet: A large plume of smoke was initially seen at the ship’s stern coming from the deck loaded with electric vehicles

Es liegen keine öffentlich verfügbaren Informationen der Reederei vor; offenbar wurde ausschließlich direkt mit Presse und Coast Guard kommuniziert.

Das einzige, was die These mit dem Elektroauto-Deck stützen könnte, wäre, dass das Schiff gezielt auf Lücke beladen worden sein könnte, um die Elektroautos zu isolieren. Ich würde tatsächlich erwarten, dass von unten nach oben beladen wird, die schweren Elektroautos zuunterst, und dann auch keine Lücken gelassen werden, um den Schwerpunkt niedrig zu halten.

Will man der neuen Entwicklung folgen, dass Hybride als Elektroautos zählen, waren auf den entsprechenden Decks (nicht nur auf einem) hunderte Hybride mit Verbrennungsmotoren verladen und die These vom Elektroauto-Deck ist in sich nicht schlüssig.


Was bleibt?

Fakten schonmal keine, außer der Erkenntnis, dass 2978 Verbrennerantriebe und 751 Elektroantriebe an Bord waren. In jedem einzelnen Auto noch dazu explosives Kältemittel, das im Hinblick auf Brandrisiko und -bekämpfung nicht immer unumstritten war.

Ein Defekt am Schiff, oder Fahrlässigkeit, sind genauso denkbar wie ein Defekt an einem der Autos.

Anders als im Fall der Fremantle Highway, wo die am Brand schuldigen Elektroautos alle unbeschädigt von Bord gekommen sind, liegen alle Beweisstücke diesmal in kaum erreichbaren 5000 Metern Tiefe am Grund des Pazifik.



Chronologie der US Coast Guard


Über den Autor

Martin Schmitt ist in den 1990er Jahren nach Ausbildung und Berufstätigkeit als gelernter Speditionskaufmann in die IT gewechselt, hält sich also selbstbewusst für einen Experten in Logistikfragen.

Dieses Posting wurde – wie alle anderen auf diesem Blog – ohne die Unterstützung eines großen Sprachmodells (im Volksmund “künstliche Intelligenz”) verfasst.

jq argfile/slurpfile migration

I’ve been using jq --argfile for a few years, despite the manpage up to jq 1.6 advising against it. Let me create a demonstration JSON that resembles my telemetry use case:

$ jq -n '.metric1=1234|.metric2=5678' | tee /tmp/test.json
{
  "metric1": 1234,
  "metric2": 5678
}

This data used to be read via --argfile, bound to a variable and hooked into a bigger JSON object:

$ jq -n --argfile m /tmp/test.json '.metrics=$m'
{
  "metrics": {
    "metric1": 1234,
    "metric2": 5678
  }
}

The Version of jq on Debian 13/Trixie does not support the --argfile option anymore:

jq: Unknown option --argfile

TL;DR: I had to replace --argfile with --slurpfile and use the first list index from the bound variable:

$ jq -n --slurpfile m /tmp/test.json '.metrics=$m[0]'
{
  "metrics": {
    "metric1": 1234,
    "metric2": 5678
  }
}

Why though?

--argfile behaves differently depending on the number of JSON objects in the file. If it the file contains 1 JSON object, it is returned as a JSON object. If I create a JSON file that contains 2 JSON objects, they are converted to a list of objects:

$ jq -n '.metric1=1234,.metric2=5678' | tee /tmp/test2.json 
{
  "metric1": 1234
}
{
  "metric2": 5678
}
$ jq -n --argfile m /tmp/test.json '.metrics=$m'
{
  "metrics": {
    "metric1": 1234,
    "metric2": 5678
  }
}
$ jq -n --argfile m /tmp/test2.json '.metrics=$m'
{
  "metrics": [
    {
      "metric1": 1234
    },
    {
      "metric2": 5678
    }
  ]
}

--slurpfile treats all JSON objects the same and if the file contains a single JSON object, returns a list of JSON objects containing a single entry. Hence the added [0] from the TL;DR section.

One might argue that the removal of --argfile could have come following a clear deprecation notice instead of semi-permanently (first appearance in Debian 9/Stretch) advising against its use and then removing it all of a sudden. However, as far as I am concerned, the issue was detected in earliest Debian 13 compatibility testing within days after the freeze, I immediately knew what was cooking, the migration to --slurpfile was trivial, and it can also be deployed retroactively on all older systems.

Too Good To #014

In today’s installment:

  • dmesg from before the machine crashed
  • Kill process ID from pidfile
  • Let systemd retry a task
  • Set MTU on OpenVPN connections in Networkmanager

dmesg from before the machine crashed

Super helpful and complete after most kernel panics and Magic SysRq resets:

journalctl --dmesg --boot -1

Kill process ID from pidfile

pkill --pidfile foo.pid

I use this in this wacky systemd user unit:

[Unit]
Description=nginx restreamer

[Service]
ExecStart=/usr/sbin/nginx \
        -c %h/.local/nginx/nginx.conf \
        -g "pid %h/.local/nginx/nginx.pid;"
ExecReload=pkill -HUP --pidfile %h/.local/nginx/nginx.pid

[Install]
WantedBy=default.target

Let systemd retry a task

Not the first appearance of systemd-run in this category, and probably not the last. Here’s how to hand a task to systemd-run in order to be retried a number of times.

My bro math for interval calculation goes like this:

  • RestartSec = Time to wait between end of a failed attempt and start of the next attempt
  • RuntimeMaxSec = Allowed time for each attempt
  • StartLimitBurst = Number of attempts to make
  • StartLimitIntervalSec = (RestartSec + RuntimeMaxSec) x StartLimitBurst x 10

So for a service that shall retry every 5 minutes (RestartSec), 12 times (StartLimitBurst), and shall be timed out after 30 seconds (RuntimeMaxSec):

systemd-run \
    --user \
    --property=Restart=on-failure\
    --property=RestartSec=300 \
    --property=RuntimeMaxSec=30
    --property=StartLimitBurst=12 \
    --property=StartLimitIntervalSec=39600 \
    sh -c 'test -e /tmp/1 && touch /tmp/1'

Set MTU on OpenVPN connections in Networkmanager

Is this really still required?

sudo install -m 0755 /dev/stdin /etc/NetworkManager/dispatcher.d/vpn-up << "Here"
#!/bin/sh

if [ "$2" = "vpn-up" ]; then
        ip link set dev "$1" mtu 1392
fi
Here

Headless OBS on Debian

What I tinkered with in recent days was a headless Linux desktop for OBS, running on a server, in my case to gateway an Icecast stream to Twitch.

A number of things need to be considered here:

  • Identify what dependencies need to be installed.
  • Run a pseudo X11 display server and session.
  • Auto-start OBS, auto-start the stream.
  • Run a VNC frontend to the X11 session.
  • Backups.

OBS from Flatpak

Pretty unusual on a server, but should work without issues:

sudo apt-get install flatpak
sudo flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
sudo flatpak install flathub com.obsproject.Studio

Caveat: There is no OBS Browser source on ARM aarch64, so OBS will not be reasonably usable on Oracle’s “Always Free Tier” VPSes. Believe me. I tried.


X11 dependencies

I run the fvwm3 window manager in the virtual display server, because it’s not completely on the hostile end of the window manager spectrum.

sudo apt-get install x11vnc xvfb fvwm3 menu menu-xdg python3-xdg xbase-clients xterm xauth xinit

X11 startup

For X11 startup, I use a systemd user-unit along with a traditional ~/.xinitrc, a file I hadn’t worked with in ages.

# ~/.config/systemd/user/xinit.service
[Unit]
Description=X virtual framebuffer

[Service]
ExecStart=xinit -- /usr/bin/Xvfb :20 -nolisten tcp -screen 0 1280x720x24

[Install]
WantedBy=default.target

Caveat: The preview pane inside OBS requires 24 bit color depth.

The ~/.xinitrc sets a language environment, which I use for influencing the date format inside the OBS browser source. The OBS invocation also starts the stream automatically, and ignores uncontrolled shutdowns of OBS, so the stream will autostart after a reboot.

# ~/.xinitrc
LANG=en_GB.utf-8 flatpak run com.obsproject.Studio --startstreaming --disable-shutdown-check &
exec fvwm3

VNC

The VNC service will be bound to localhost only, but adding a password won’t hurt and will hardly be noticeable once it’s setup and saved in the VNC client.

x11vnc -storepasswd

Another systemd user-unit takes care of starting x11vnc:

# ~/.config/systemd/user/x11vnc.service
[Unit]
Description=x11vnc
Requires=xinit.service

[Service]
ExecStart=x11vnc -forever -usepw -listen localhost -display :20

[Install]
WantedBy=default.target

Backups

OBS’s data is saved in: ~/.var/app/com.obsproject.Studio


Access

  • ssh <theserver> -L 5900:localhost:5900
  • Connect VNC viewer to localhost
Screenshot of the running VNC session with fvwm3 and OBS.

Too good to #0013

In this installment:

  • Pipewire Easyeffects with RNNoise on Debian 12 / Bookworm
  • Gnome Night Light from Sunrise to Sunset, without Location Services
  • Revisiting ratarmount

Pipewire Easyeffects with RNNoise on Debian 12 / Bookworm

RNNoise for removing background sound on your microphone is not included in Debian 12, due to licensing issues around its training data. The least painful way to work around this is to install the Easyeffects Flatpak instead of the packaged easyeffects:

sudo apt-get install flatpak gnome-software-plugin-flatpak
sudo apt-get remove easyeffects
flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
flatpak install flathub com.github.wwmm.easyeffects

Gnome Night Light from Sunrise to Sunset, without Location Services

Set latitude and longitude manually (coordinates shown are for Frankfurt, Germany: 50° 6′ 38″ N, 8° 40′ 56″ E):

gsettings set \
  org.gnome.settings-daemon.plugins.color \
  night-light-last-coordinates '(50.0, 9.0)'

Revisiting ratarmount

Since my previous look at ratarmount, it went from 0.6.3 to 1.0.0, had the most interesting developments and now mounts archives via HTTP:

virtualenv ~/.local/ratarmount
~/.local/ratarmount/bin/pip3 install -U ratarmount[fsspec]
~/.local/ratarmount/bin/ratarmount
install -D ~/.local/ratarmount/bin/ratarmount ~/bin/

ratarmount -f https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.13.4.tar.xz ~/mnt

systemd delayed service restart

Ich habe einige Monate damit rumgemacht, dass das Konglomerat Telegraf/Prometheus/Grafana von ziemlich starken Abhängigkeiten bei der Startreihenfolge der beteiligten Systeme geplagt ist. Meine Server rebooten alle einmal pro Woche automatisch innerhalb eines gewissen Zeitfensters und seit Grafana musste ich so alle 2-3 Wochen manuell eingreifen und Services neustarten, da insbesondere Telegraf davon ausgeht, dass Schnittstellen, auf die es selbst zugreift, beim eigenen Start erreichbar sein müssen. (Siehe auch: Fallacies of distributed computing.)

Die Frage war also, wie sieht die einfachste Lösung aus, um einen gegebenen Service 60 Minuten nach seinem Start noch einmal automatisch durchzustarten (mit sporadischen Lücken in den Daten kann ich am frühen Sonntagmorgen leben).

Timer und dedizierte Service-Units zu verteilen schien mir zu aufwändig, und ich habe unangenehm lange gegrübelt, wie die simpelste Lösung aussehen könnte, die einfach per systemctl edit foo.service (oder per anderweitig verteiltem systemd drop-in, ymmv) in die betreffende Unit eingebracht werden kann.

Angekommen bin ich schließlich bei dieser Bandwurmzeile:

# /etc/systemd/system/telegraf.service.d/override.conf
[Service]
ExecStartPost=-+sh -c 'systemctl --quiet is-active %N-restart || systemd-run --unit=%N-restart --on-active=1h systemctl restart %N'

Von rechts nach links:

  • systemd-run instanziiert einen transienten timer und service, der in einer Stunde systemctl restart ausführen wird. %N wird dabei durch den Namen der übergeordneten Unit (hier also: telegraf) ersetzt.
  • Der Name des transienten timer und service wird über die Option --unit vorgegeben um im vorherigen Schritt (eins weiter links) vorhersagbar zu sein.
  • systemd-run wird nur dann ausgeführt, wenn nicht bereits eine Unit aktiv ist, die den vorgegebenen Namen hat, den die transiente Unit bekommen würde, wenn sie noch auszuführen wäre.
  • Das ganze ist als Shell-Aufruf gewrappt, um den || – Operator zu haben.
  • ExecStartPost=+ führt als root aus, obwohl die übergeordnete Unit ihre Befehle unprivilegiert ausführt.
  • ExecStartPost=- ignoriert den Fehler aus dem Shell-Einzeiler, wenn die Unit bereits aktiv war. Es könnte ersatzweise auch ; true ans Ende des Einzeilers geschrieben werden.