{"version":"https://jsonfeed.org/version/1","title":"Jan's Stuff","home_page_url":"https://jan.alphadev.net/","feed_url":"https://jan.alphadev.net/feed.json","items":[{"id":"https://jan.alphadev.net/blog/2026/10000","url":"https://jan.alphadev.net/blog/2026/10000/","title":"10.000","content_html":"<article class=\"post-content\">\n            <p><img src=\"https://jan.alphadev.net/assets/2026/IMG_5134.jpeg\" alt=\"Ten thousand kilometers on the odometer\" /></p>\n            <p>After having driven the Zero S for 10.000 Kilometers, <a href=\"https://jan.alphadev.net/blog/2025/motorcycle-post-mortem/\">one crash</a> and <a href=\"https://jan.alphadev.net/category/trip-report\">lots of trips</a> in cold, hot and rainy weather, here’s a quick résumé.</p>\n            <h1 id=\"reach-anxiety-doesnt-exist-in-practice\">Reach anxiety doesn’t exist in practice</h1>\n            <p>When riding solo for several hours, it is a welcome change to make a quick charging stop. Both to rest mentally and physically.</p>\n            <p>And when riding with others, the required speeds and acceleration decrease the bigger the group gets. I have never gotten better mileages than when riding with larger groups.</p>\n            <p>Naturally this assumes that you’re smart about your route planning.</p>\n            <p>I have not kept exact records, but the consumption is so low that the wear on the tires ends up being more expensive than the electricity itself.</p>\n            <h1 id=\"is-it-a-motorcycle\">Is it a motorcycle?</h1>\n            <p>Several of my trips were along bigger motorcycles (650cc, 800cc, 1000cc) and I’ve never had a problem keeping up. The acceleration and the higher top speed never betray that it is a 125cc(-equivalent) bike.</p>\n            <p>The only place where it <em>actually is</em> too underpowered is at sustained highway speeds.</p>\n            <h1 id=\"noise\">Noise</h1>\n            <p>The bike itself doesn’t come with an artificial noise generator.</p>\n            <p>All the noises it does make, are an effect of it driving.</p>\n            <p>Biggest noise contributor is the belt drive. It is near silent except for certain load situations (when the throttle is slightly above cruising and slowly accelerating), it begins to wail.</p>\n            <p>The wailing gets louder and more high pitched the faster you go. On the Autobahn it often cuts in and out abruptly at certain speeds and loads.</p>\n            <h1 id=\"canyon-mode\">Canyon Mode</h1>\n            <p>Every time I go for a trip to the Black Forest or Swabian Alps I set the bike to Canyon Mode. This enables both full torque and max regen breaking. While it is slightly more annoying to keep speed, it makes it more than up in total savings and ride comfort. Especially speeding up steep hills or cruising down a long windy serpentine road.</p>\n            <h1 id=\"longevity\">Longevity</h1>\n            <p>Apart from the tires the bike needs almost no maintenance. The only fluid in the entire bike is the breaking fluid.</p>\n            <p>This is the 3rd year and I have not encountered any change in the max range. It holds its charge like on the first day.</p>\n            <h2 id=\"hibernation\">Hibernation</h2>\n            <p>You simply charge it to 80% before the winter and park it in a dry area. It will automatically enter a hibernation mode after some time. After the winter you charge it back up and it is ready to go.</p>\n            <h1 id=\"outlook\">Outlook</h1>\n            <p>I plan to acquire the big motorcycle license sometime next year, but will keep riding this bike.</p>\n            <p>I am more than happy with what I’ve got. And would go for a Zero bike again that is if they are still selling then. But my next bike will <em>need</em> to have a CCS plug and ideally a better, more modern battery chemistry.</p>\n            <p><strong>On to the next milestone!</strong></p>\n          </article>","date_published":"2026-07-13T00:00:00+00:00","date_modified":"2026-07-14T00:00:00+00:00","tags":["motorcycle","zero s 2024"],"author":{"name":"Jan Seeger"}},{"id":"https://jan.alphadev.net/blog/2026/missing-ios-features.md","url":"https://jan.alphadev.net/blog/2026/missing-ios-features.md/","title":"Missing iOS features","content_html":"<article class=\"post-content\">\n            <p>Lately there was an <a href=\"https://weblog.rogueamoeba.com/2026/06/26/free-the-icons/\">uproar of people against the new overly simplified new macOS App Icons</a>.</p>\n            <p>I do find the new icons less creative and less legible in a sea of same-y icons.</p>\n            <p>I’m glad somebody is taking matters into their hands, but there’s things that bother me far more (than <a href=\"https://unsung.aresluna.org/something-that-probably-bothered-us-more-than-anyone/\">rounded corner radii</a>):</p>\n            <h1 id=\"exclude-from-spotlight-indexing\">Exclude from Spotlight Indexing</h1>\n            <p>Apple has announced a new and improved Siri and in preparation for that, they have beefed up their Spotlight indexing.</p>\n            <p>On macOS you could always just drop an empty <code class=\"language-plaintext highlighter-rouge\">.noindex</code> file into any directory and it will henceforth be ignored by Spotlight indexing.</p>\n            <h2 id=\"no-dot-files-in-filesapp\">No Dot-files in Files.app</h2>\n            <p>Not on iOS, where the Files.app won’t let you create Dot-files in the first place.</p>\n            <p>Even workarounds like the “Save File” Shortcut will silently ignore them (and when creating a file without shortcuts append .txt<sup id=\"fnref:1\"><a href=\"#fn:1\" class=\"footnote\" rel=\"footnote\" role=\"doc-noteref\">1</a></sup>)</p>\n            <h2 id=\"siri-settings\">Siri Settings</h2>\n            <p>Given that there is no power user reachable lever, I’d have expected to find excluded directions in the Settings.</p>\n            <p>Alas, no luck.</p>\n            <h1 id=\"custom-search-providers\">Custom Search Providers</h1>\n            <p>Google search sucks (not as much as others, but still), has sucked for years and isn’t going to get better anytime soon.</p>\n            <p>Safari’s Search Engine setting doesn’t provide a custom option where a URL pattern like <code class=\"language-plaintext highlighter-rouge\">https://kagi.com/search?q=%s</code> can be set.</p>\n            <p>This has been table stakes for decades in all other Browsers out there.</p>\n            <div class=\"footnotes\" role=\"doc-endnotes\">\n              <ol>\n                <li id=\"fn:1\">\n                  <p>I thought UTI was supposed to allow determining file types without having to rely on the extension. <a href=\"#fnref:1\" class=\"reversefootnote\" role=\"doc-backlink\">&#8617;</a></p>\n                </li>\n              </ol>\n            </div>\n          </article>","date_published":"2026-07-04T00:00:00+00:00","date_modified":"2026-07-14T00:00:00+00:00","tags":["iOS","apple"],"author":{"name":"Jan Seeger"}},{"id":"https://jan.alphadev.net/blog/2026/apple-container","url":"https://jan.alphadev.net/blog/2026/apple-container/","title":"Apple Container as Docker replacement","content_html":"<article class=\"post-content\">\n            <p>With macOS 26 Tahoe, support for running <a href=\"https://github.com/opencontainers/image-spec\">OCI-style containers</a> is directly built into the OS. Similar to <a href=\"https://support.microsoft.com/en-us/windows/enable-virtualization-on-windows-c5578302-6e43-4b4b-a449-8ced115f58e1\">Microsoft’s Virtual Machine Platform</a>, which is a subset of their Hyper-V offering.</p>\n            <p>Docker for macOS is free for personal use, but their constant nag-screens, forgetting of credentials, ceaseless telemetry collecting and them pushing users hard to try their AI functionality, made me dread each new version for the past few months. It also doesn’t help that most of the newly added features are designed with their corporate customers in mind.</p>\n            <p>There’s other options that run entirely in Userland, like <a href=\"https://github.com/abiosoft/colima\">Colima</a> and <a href=\"https://podman-desktop.io\">Podman</a>, but I wanted to give the Apple variant a try.</p>\n            <h1 id=\"installation\">Installation</h1>\n            <p>Tahoe comes with the underlying Virtualisation Framework. All that is needed is the Userland client, called <a href=\"https://github.com/apple/container\">Container</a> to operate it, as the rest will be downloaded on first use.</p>\n            <p>The client can either be obtained via <a href=\"https://formulae.brew.sh/formula/container\">homebrew</a> (<code class=\"language-plaintext highlighter-rouge\">brew install container</code>) or via the standalone package from <a href=\"https://github.com/apple/container/releases/\">GitHub Releases</a>.</p>\n            <h1 id=\"getting-started\">Getting Started</h1>\n            <p>To be able to use it the Virtual Machine with the Linux Container has to be started:</p>\n            <div class=\"language-sh highlighter-rouge\">\n              <div class=\"highlight\">\n                <pre class=\"highlight\"><code><span class=\"nv\">$ </span>container system start\n</code></pre>\n              </div>\n            </div>\n            <p>It will automatically download everything it needs, like the Linux VM the Containers run and build in.</p>\n            <h2 id=\"start-with-system\">Start with System</h2>\n            <p>Unfortunately the VM has to be manually started after each reboot. While a <a href=\"https://github.com/apple/container/issues/158\">solution is currently underway</a>, in the meantime we can define our own LaunchAgent:</p>\n            <div class=\"language-sh highlighter-rouge\">\n              <div class=\"highlight\">\n                <pre class=\"highlight\"><code><span class=\"nv\">$ </span><span class=\"nb\">cat</span> <span class=\"o\">&gt;</span> ~/Library/LaunchAgents/com.user.container.autostart.plist <span class=\"o\">&lt;&lt;</span> <span class=\"sh\">'</span><span class=\"no\">EOF</span><span class=\"sh\">'\n&lt;?xml version=\"1.0\" encoding=\"UTF-8\"?&gt;\n&lt;!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\"&gt;\n&lt;plist version=\"1.0\"&gt;\n&lt;dict&gt;\n    &lt;key&gt;Label&lt;/key&gt;\n    &lt;string&gt;com.user.container.autostart&lt;/string&gt;\n    &lt;key&gt;ProgramArguments&lt;/key&gt;\n    &lt;array&gt;\n        &lt;string&gt;/usr/local/bin/container&lt;/string&gt;\n        &lt;string&gt;system&lt;/string&gt;\n        &lt;string&gt;start&lt;/string&gt;\n    &lt;/array&gt;\n    &lt;key&gt;RunAtLoad&lt;/key&gt;\n    &lt;true/&gt;\n    &lt;key&gt;StandardOutPath&lt;/key&gt;\n    &lt;string&gt;/tmp/container-autostart.log&lt;/string&gt;\n    &lt;key&gt;StandardErrorPath&lt;/key&gt;\n    &lt;string&gt;/tmp/container-autostart.log&lt;/string&gt;\n&lt;/dict&gt;\n&lt;/plist&gt;\n</span><span class=\"no\">EOF\n</span></code></pre>\n              </div>\n            </div>\n            <h1 id=\"builder-without-rosetta\">Builder without Rosetta</h1>\n            <p>I don’t have Rosetta installed and don’t want to install it. <a href=\"https://github.com/apple/container/issues/103#issuecomment-3003675213\">But since Apple can’t know what platform a container is targeting until after it is brought up, they always enable Rosetta support</a>, which fails the build if it isn’t installed.</p>\n            <p>This behaviour can be configured out by disabling Rosetta globally for all Containers:</p>\n            <div class=\"language-sh highlighter-rouge\">\n              <div class=\"highlight\">\n                <pre class=\"highlight\"><code><span class=\"nv\">$ </span><span class=\"nb\">mkdir</span> <span class=\"nt\">-p</span> ~/.config/container\n<span class=\"nv\">$ </span><span class=\"nb\">echo</span> <span class=\"s2\">\"[build]</span><span class=\"se\">\\n</span><span class=\"s2\">rosetta = false\"</span> <span class=\"o\">&gt;&gt;</span> ~/.config/container/config.toml\n</code></pre>\n              </div>\n            </div>\n            <h1 id=\"uses\">Uses</h1>\n            <p>Once it is up and running it acts as a drop-in replacement for Docker. In command invocations, <code class=\"language-plaintext highlighter-rouge\">docker</code> can be replaced with <code class=\"language-plaintext highlighter-rouge\">container</code>, except for a few fringe use-cases, like inotify and IPv6 forwards.</p>\n            <p>This post was written using a Jekyll image continuously rendering previews for changes to this document running in the Container Framework. Works perfectly fine.</p>\n            <p>But unfortunately there’s one specific use-case that currently prevents me from uninstalling Docker.app from my machine: Permanent Gitea Action Runner in the background.</p>\n          </article>","date_published":"2026-06-20T00:00:00+00:00","date_modified":"2026-07-14T00:00:00+00:00","tags":["docker","container"],"author":{"name":"Jan Seeger"}},{"id":"https://jan.alphadev.net/blog/2026/chinese-domain-scam","url":"https://jan.alphadev.net/blog/2026/chinese-domain-scam/","title":"Chinese Domain Scam","content_html":"<article class=\"post-content\">\n            <p>About once a year I receive an email from a Chinese (Domain) Registry that claims to supposedly inquire for a company that is about to register the <em>alphadev.cn</em>, <em>alphadev.com.cn</em>, <em>alphadev.net.cn</em> and <em>alphadev.org.cn</em> domains.</p>\n            <p>It starts by setting a false urgency:</p>\n            <blockquote>\n              <p>If you are not the person who is in charge of this, please forward this to your CEO, because this is urgent. Thanks!</p>\n            </blockquote>\n            <p>Then they ask whether I have any business with said entity:</p>\n            <blockquote>\n              <p>But after checking it, we find this name conflict with your company name or trademark. In order to deal with this matter better, it’s necessary to send email to you and confirm whether your company have affiliation with this CN company or not?</p>\n            </blockquote>\n            <p>If I don’t answer, another follow-up broadly repeating the initial message is sent.</p>\n            <p>But regardless of me responding, or whether I “permit” or “deny” the “company” to go through with the registration, a third email from a supposed representative of the registering company is sent that claims that they intend to register the domains in any case:</p>\n            <blockquote>\n              <p>We are waiting for Mr. $name_of_registry_official approval and think these CN domains and internet keyword are very important for our business. Even though Mr. $name_of_registry_official advises us to change another name, we will persist in this name.</p>\n            </blockquote>\n            <p>After that last message there is silence for about a year and neither of the domains are ever being registered.</p>\n            <p>I assume this is a high-pressure sales tactic to have people register .cn-<acronym title=\"Top-Level Domain\">TLD</acronym>s. I haven’t checked their <acronym title=\"Terms of Service\">ToS</acronym> and I don’t even know what registering a Chinese domain entails or costs.</p>\n            <p>But what I <em>am</em> certain about is that I don’t want to register these domains and should I ever “expand my business” to China, I’m sure I will be able to find a suitable domain myself, and so will you if you made your way here by searching for strings from such an email.</p>\n          </article>","date_published":"2026-06-15T00:00:00+00:00","date_modified":"2026-07-14T00:00:00+00:00","tags":[],"author":{"name":"Jan Seeger"}},{"id":"https://jan.alphadev.net/blog/2026/git-on-synology-2","url":"https://jan.alphadev.net/blog/2026/git-on-synology-2/","title":"Running modern Git on ancient Synology kernels - revisited","content_html":"<article class=\"post-content\">\n            <p>In the <a href=\"https://jan.alphadev.net/blog/2026/git-on-synology/\">original post</a> I had made small shim that redirects calls from libc getrandom to the Kernel device.</p>\n            <p>Since I had used alpine images, linking against libmusl was a reasonable choice, but that meant this hack would be limited to libmusl-based images.</p>\n            <p>I have since found a way to generalise this to work with any flavor of libc:</p>\n            <div class=\"language-c highlighter-rouge\">\n              <div class=\"highlight\">\n                <pre class=\"highlight\"><code><span class=\"c1\">// getrandom_shim.c  — no libc dependency</span>\n<span class=\"cp\">#include</span> <span class=\"cpf\">&lt;sys/syscall.h&gt;</span><span class=\"cp\">\n#include</span> <span class=\"cpf\">&lt;stddef.h&gt;</span><span class=\"cp\">\n</span>\n<span class=\"k\">static</span> <span class=\"kt\">long</span> <span class=\"nf\">sys</span><span class=\"p\">(</span><span class=\"kt\">long</span> <span class=\"n\">n</span><span class=\"p\">,</span> <span class=\"kt\">long</span> <span class=\"n\">a</span><span class=\"p\">,</span> <span class=\"kt\">long</span> <span class=\"n\">b</span><span class=\"p\">,</span> <span class=\"kt\">long</span> <span class=\"n\">c</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n    <span class=\"kt\">long</span> <span class=\"n\">r</span><span class=\"p\">;</span>\n<span class=\"cp\">#if defined(__x86_64__)\n</span>    <span class=\"k\">register</span> <span class=\"kt\">long</span> <span class=\"n\">r10</span> <span class=\"n\">asm</span><span class=\"p\">(</span><span class=\"s\">\"r10\"</span><span class=\"p\">);</span> <span class=\"c1\">// unused here</span>\n    <span class=\"n\">asm</span> <span class=\"k\">volatile</span><span class=\"p\">(</span><span class=\"s\">\"syscall\"</span> <span class=\"o\">:</span> <span class=\"s\">\"=a\"</span><span class=\"p\">(</span><span class=\"n\">r</span><span class=\"p\">)</span> <span class=\"o\">:</span> <span class=\"s\">\"a\"</span><span class=\"p\">(</span><span class=\"n\">n</span><span class=\"p\">),</span><span class=\"s\">\"D\"</span><span class=\"p\">(</span><span class=\"n\">a</span><span class=\"p\">),</span><span class=\"s\">\"S\"</span><span class=\"p\">(</span><span class=\"n\">b</span><span class=\"p\">),</span><span class=\"s\">\"d\"</span><span class=\"p\">(</span><span class=\"n\">c</span><span class=\"p\">)</span>\n                 <span class=\"o\">:</span> <span class=\"s\">\"rcx\"</span><span class=\"p\">,</span><span class=\"s\">\"r11\"</span><span class=\"p\">,</span><span class=\"s\">\"memory\"</span><span class=\"p\">);</span>\n<span class=\"cp\">#elif defined(__aarch64__)\n</span>    <span class=\"k\">register</span> <span class=\"kt\">long</span> <span class=\"n\">x8</span> <span class=\"n\">asm</span><span class=\"p\">(</span><span class=\"s\">\"x8\"</span><span class=\"p\">)</span><span class=\"o\">=</span><span class=\"n\">n</span><span class=\"p\">,</span> <span class=\"n\">x0</span> <span class=\"n\">asm</span><span class=\"p\">(</span><span class=\"s\">\"x0\"</span><span class=\"p\">)</span><span class=\"o\">=</span><span class=\"n\">a</span><span class=\"p\">,</span> <span class=\"n\">x1</span> <span class=\"n\">asm</span><span class=\"p\">(</span><span class=\"s\">\"x1\"</span><span class=\"p\">)</span><span class=\"o\">=</span><span class=\"n\">b</span><span class=\"p\">,</span> <span class=\"n\">x2</span> <span class=\"n\">asm</span><span class=\"p\">(</span><span class=\"s\">\"x2\"</span><span class=\"p\">)</span><span class=\"o\">=</span><span class=\"n\">c</span><span class=\"p\">;</span>\n    <span class=\"n\">asm</span> <span class=\"k\">volatile</span><span class=\"p\">(</span><span class=\"s\">\"svc 0\"</span> <span class=\"o\">:</span> <span class=\"s\">\"+r\"</span><span class=\"p\">(</span><span class=\"n\">x0</span><span class=\"p\">)</span> <span class=\"o\">:</span> <span class=\"s\">\"r\"</span><span class=\"p\">(</span><span class=\"n\">x8</span><span class=\"p\">),</span><span class=\"s\">\"r\"</span><span class=\"p\">(</span><span class=\"n\">x1</span><span class=\"p\">),</span><span class=\"s\">\"r\"</span><span class=\"p\">(</span><span class=\"n\">x2</span><span class=\"p\">)</span> <span class=\"o\">:</span> <span class=\"s\">\"memory\"</span><span class=\"p\">);</span>\n    <span class=\"n\">r</span> <span class=\"o\">=</span> <span class=\"n\">x0</span><span class=\"p\">;</span>\n<span class=\"cp\">#endif\n</span>    <span class=\"k\">return</span> <span class=\"n\">r</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n\n<span class=\"kt\">long</span> <span class=\"nf\">getrandom</span><span class=\"p\">(</span><span class=\"kt\">void</span> <span class=\"o\">*</span><span class=\"n\">buf</span><span class=\"p\">,</span> <span class=\"kt\">size_t</span> <span class=\"n\">len</span><span class=\"p\">,</span> <span class=\"kt\">unsigned</span> <span class=\"kt\">int</span> <span class=\"n\">flags</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n    <span class=\"p\">(</span><span class=\"kt\">void</span><span class=\"p\">)</span><span class=\"n\">flags</span><span class=\"p\">;</span>\n    <span class=\"kt\">long</span> <span class=\"n\">fd</span> <span class=\"o\">=</span> <span class=\"n\">sys</span><span class=\"p\">(</span><span class=\"n\">SYS_open</span><span class=\"p\">,</span> <span class=\"p\">(</span><span class=\"kt\">long</span><span class=\"p\">)</span><span class=\"s\">\"/dev/urandom\"</span><span class=\"p\">,</span> <span class=\"mi\">0</span> <span class=\"cm\">/*O_RDONLY*/</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">);</span>\n    <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">fd</span> <span class=\"o\">&lt;</span> <span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"k\">return</span> <span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">;</span>\n    <span class=\"kt\">long</span> <span class=\"n\">n</span> <span class=\"o\">=</span> <span class=\"n\">sys</span><span class=\"p\">(</span><span class=\"n\">SYS_read</span><span class=\"p\">,</span> <span class=\"n\">fd</span><span class=\"p\">,</span> <span class=\"p\">(</span><span class=\"kt\">long</span><span class=\"p\">)</span><span class=\"n\">buf</span><span class=\"p\">,</span> <span class=\"p\">(</span><span class=\"kt\">long</span><span class=\"p\">)</span><span class=\"n\">len</span><span class=\"p\">);</span>\n    <span class=\"n\">sys</span><span class=\"p\">(</span><span class=\"n\">SYS_close</span><span class=\"p\">,</span> <span class=\"n\">fd</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">,</span> <span class=\"mi\">0</span><span class=\"p\">);</span>\n    <span class=\"k\">return</span> <span class=\"n\">n</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n</code></pre>\n              </div>\n            </div>\n            <p>We now have to add <code class=\"language-plaintext highlighter-rouge\">-nostdlib</code> to the compilation command, but <code class=\"language-plaintext highlighter-rouge\">musl-dev</code> is still needed for the syscall headers:</p>\n            <div class=\"language-sh highlighter-rouge\">\n              <div class=\"highlight\">\n                <pre class=\"highlight\"><code>docker run <span class=\"nt\">--rm</span> <span class=\"nt\">-v</span> <span class=\"s2\">\"</span><span class=\"nv\">$PWD</span><span class=\"s2\">\"</span>:/src <span class=\"nt\">-w</span> /src alpine:3.19 sh <span class=\"nt\">-c</span> <span class=\"se\">\\</span>\n  <span class=\"s2\">\"apk add --no-cache gcc musl-dev &amp;&amp; gcc -shared -fPIC -nostdlib -O2 -o getrandom_shim.so getrandom_shim.c\"</span>\n<span class=\"nb\">chmod </span>644 getrandom_shim.so\n</code></pre>\n              </div>\n            </div>\n            <p>And now we can use the <code class=\"language-plaintext highlighter-rouge\">getrandom_shim.so</code> for any images, regardless of what style of libc they use.</p>\n            <p>On that note, this follow-up post answers whether this hack is still in place.</p>\n          </article>","date_published":"2026-06-05T00:00:00+00:00","date_modified":"2026-07-14T00:00:00+00:00","tags":["synology","git"],"author":{"name":"Jan Seeger"}},{"id":"https://jan.alphadev.net/blog/2026/trip-report-munich","url":"https://jan.alphadev.net/blog/2026/trip-report-munich/","title":"Trip Report: Munich","content_html":"<article class=\"post-content\">\n            <p><em>This post is written in July 2026, but was dated back to when the trip originally happened.</em></p>\n            <p>I attended Kotlin Conf in Munich, and a wedding in Ulm the following weekend. It was supposed to be nice weather and I decided to combine the two and travel by motorcycle.</p>\n            <p><img src=\"https://jan.alphadev.net/assets/2026/IMG_5135.jpeg\" alt=\"Map showing the Route\" /></p>\n            <h1 id=\"day-1-journey-to-munich\">Day 1: Journey to Munich</h1>\n            <p>The plan was to travel along cities to stretch out the range and delay stopping for a charge. Which I did in Ulm where a charging station was conveniently placed right next to a bakery. Time for lunch.</p>\n            <p>The trip had started with the best weather. But during my last charging stop in Augsburg it started to pour down heavy rain. I set the bike to rain mode and pressed on. Somewhere between Ismaning and Aschheim the streets got dry again but at that point I was already soaked through.</p>\n            <p>3 KMs before my destination my legs started to cramp and I had to decide whether to stop and rest or press on and be done. I decided to go on.</p>\n            <p>Immediately after arriving at the apartment, I began to dry my clothes and hang them in the bathroom with the light on so the dehumidifier could do it’s thing.</p>\n            <h1 id=\"day-2-conference\">Day 2: Conference</h1>\n            <p>There was a charger at the main entrance of the conference. But it was always occupied by other people. The ones at street level were either all occupied, and one free I finally found didn’t want to start the charging process.</p>\n            <p>There was an underground parking garage which supposedly had about 30 charges, but motorcycles and underground garages normally don’t go along well. But lacking any other options I had to try.</p>\n            <p>It didn’t say no motorcycles allowed anywhere, so I approached the gate. It was one of those license plate reader ones. But when I was about to turn away, it opened. I didn’t check whether it had captured my license plate.</p>\n            <p>In the parking garage there wasn’t just one spot for electric vehicles but lots of small islands with 2 chargers each. They all were pointed to by different and partly contradictory  signs, which made it extra confusing. I finally found one and it worked<sup id=\"fnref:1\"><a href=\"#fn:1\" class=\"footnote\" rel=\"footnote\" role=\"doc-noteref\">1</a></sup>.</p>\n            <p>After the charging I went to the payment machine and entered my license plate, but it couldn’t be found. Pressed the “call for help” button, which connected me to two people at the same time. They could hear each other, and I could hear them both, but they couldn’t hear me. It took them a while to find out that both are garage employees and after they hung up I pressed it again. This time just one clerk and we could both hear each other. I explained the situation and he said I don’t have to pay just drive out.</p>\n            <p>Hopped on the bike, followed the signage to the nearest exit and then I noticed that the gap between the barriers was big enough for me to drive through, so I didn’t wait in front of it to open and went out.</p>\n            <p>My clothes still were damp to the touch. I spent the evening drying off clothes in the bathroom with the hair dryer.</p>\n            <h1 id=\"day-3-journey-to-ulm\">Day 3: Journey to Ulm</h1>\n            <p>I had to check out of the apartment but the conference would go on for another day. I packed everything to be ready for the road, dressed “conference casual” and took my motorcycle clothes with me to the conference and checked it into the garderobe there.</p>\n            <p>In the afternoon I then got it back, disappeared into a bathroom stall and switched to the motorcycle clothes, which unfortunately still were a little bit damp on the inside. After half an hour riding in the hot sun that was taken care of.</p>\n            <p>I deliberately took the long way round back through small villages and rural towns. Ate late lunch at a bakery next to a parking lot with chargers <sup id=\"fnref:2\"><a href=\"#fn:2\" class=\"footnote\" rel=\"footnote\" role=\"doc-noteref\">2</a></sup>.</p>\n            <p>And then arrived timely in Schelklingen. Originally my plan was to leave the bike there, attend the wedding and the next day drive towards Ulm to charge. But the hotel had their own dedicated customer charging spot, which they put on the hotel bill at the end.</p>\n            <h1 id=\"day-5-home\">Day: 5: Home</h1>\n            <p>The drive home was uneventful and beautiful. And since it was a sunny Sunday morning I encountered lots of other bikers.</p>\n            <h1 id=\"résumé\">Résumé</h1>\n            <p>Weather was nice, except for the downpour on my way there. Charging was annoying but never an actual issue<sup id=\"fnref:3\"><a href=\"#fn:3\" class=\"footnote\" rel=\"footnote\" role=\"doc-noteref\">3</a></sup>. Conference was nice. Wedding was nice. Would do it again, but this time I’d pack differently.</p>\n            <div class=\"footnotes\" role=\"doc-endnotes\">\n              <ol>\n                <li id=\"fn:1\">\n                  <p>Had to clear the plug and the socket of the rainwater that had collected there with my shirt because I didn’t have any tissues with me. <a href=\"#fnref:1\" class=\"reversefootnote\" role=\"doc-backlink\">&#8617;</a></p>\n                </li>\n                <li id=\"fn:2\">\n                  <p>Shops, Malls, Bakeries, and parks near charging infrastructure is becoming a theme here. <a href=\"#fnref:2\" class=\"reversefootnote\" role=\"doc-backlink\">&#8617;</a></p>\n                </li>\n                <li id=\"fn:3\">\n                  <p>It is always annoying to arrive in a new unfamiliar city and do the public charger roulette. E-Mobility at home is just way more comfortable. <a href=\"#fnref:3\" class=\"reversefootnote\" role=\"doc-backlink\">&#8617;</a></p>\n                </li>\n              </ol>\n            </div>\n          </article>","date_published":"2026-05-23T00:00:00+00:00","date_modified":"2026-07-14T00:00:00+00:00","tags":["motorcycle","zero s 2024"],"author":{"name":"Jan Seeger"}},{"id":"https://jan.alphadev.net/blog/2026/gear-replacements","url":"https://jan.alphadev.net/blog/2026/gear-replacements/","title":"Gear Replacements","content_html":"<article class=\"post-content\">\n            <h1 id=\"peak-design-everyday-backpack-35l-v1\">Peak Design Everyday Backpack 35L v1</h1>\n            <p>After half a decade the zipper on my trusty backpack has failed. No surprise there, I had been travelling for 10s of thousands of KMs with the thing by Airplane, Train, Buses, Car, Motorcycles and on Foot and have used (and abused) it to hell and back.</p>\n            <p>The options were having it repaired, but that would have either involved sending the thing halfway across the globe (and getting a replacement v2 variant in the end) or repairing it here locally, I had even started to look into possible replacement parts and their specs. But that would have set me back more than half of the cost of a brand-new backpack.</p>\n            <p>Instead I then took this as an opportunity to upgrade to the <a href=\"https://www.peakdesign.com/eu/products/travel-backpack?Size=45L&amp;Color=Black\">Peak Design 45L Travel Backpack</a>, which solves several issues I had with the old one, like the straps, the luggage pass-through and the small volume. On the other hand this also means I now have to optimise my gear and packing procedure again from scratch (which isn’t necessarily a bad thing, but additional effort)</p>\n            <h1 id=\"motorcycle-carplay-display\">Motorcycle CarPlay Display</h1>\n            <p>After two seasons of riding with the <a href=\"https://jan.alphadev.net/blog/2024/zero-modifications-carplay/#carplay-display\">cheap CarPlay display from China</a> (that was supposed to be weather-proof) the pixels in the display got stuck. This was fine but sometimes the backlight cut out, making it impossible to read directions.</p>\n            <p>By recommendation of a friend of mine, I replaced it with an <a href=\"elebest\">Elebest C650</a>, which is solves several of the issues I had with the first device:</p>\n            <ul>\n              <li>\n                <p>The screen is high-res enough that you can’t make out any pixels by eye and way brighter</p>\n              </li>\n              <li>\n                <p>Media Playback does only resume on connect if there is an app actively in memory and has an open media session</p>\n              </li>\n              <li>\n                <p>The display is mounted in a cradle and can be removed</p>\n                <p>This one is also supposed to be weather-proof, but instead of verifying this I can remove the display over the winter.</p>\n              </li>\n            </ul>\n            <p><img src=\"https://jan.alphadev.net/assets/2026/0e732692-f283-4381-963b-c4cdc63f46b1.jpg\" alt=\"Elebest C650 mounted to the handlebar\" /></p>\n            <h1 id=\"gloves\">Gloves</h1>\n            <p>During the <a href=\"https://jan.alphadev.net/blog/2026/trip-report-wiesbaden/\">Wiesbaden Trip</a> I got cold and bought warm <a href=\"https://www.polo-motorrad.com/de-de/reusch-driftice-gore-tex-leder-textilhandschuh-lang-schwarz-8/3115131006001934/pdp\">Reusch Driftice gloves</a>, I’ll keep my <a href=\"https://www.motorun.de/RUKKA-Virium-glove-waterproof-black-8\">Rukka Virium</a>s but then for warmer weather.</p>\n          </article>","date_published":"2026-05-08T00:00:00+00:00","date_modified":"2026-07-14T00:00:00+00:00","tags":[],"author":{"name":"Jan Seeger"}},{"id":"https://jan.alphadev.net/blog/2026/fake-clerk","url":"https://jan.alphadev.net/blog/2026/fake-clerk/","title":"Fake Clerk OAuth in Coolify","content_html":"<article class=\"post-content\">\n            <p>After I <a href=\"https://jan.alphadev.net/blog/2026/coolify/\">fell in love</a> with the way <a href=\"https://coolify.io/\">Coolify</a> makes deployments stupidly simple, it was bound to stay. But I wanted to use OAuth to authenticate users.</p>\n            <p><a href=\"https://coolify.io/docs/knowledge-base/oauth\">Officially Coolify supports Azure, BitBucket, GitLab and Google OAuth providers</a>. While not explicitly mentioned in the docs, it supports a few more (Authentik, Clerk, Discord, Infomaniak and Citadel).</p>\n            <p>But mine (<a href=\"https://pocket-id.org\">PocketID</a>) unfortunately isn’t on the list and all the providers differ in their implementations in minute details, like data structures or URL schemas.</p>\n            <h1 id=\"fake-clerk-proxy\">Fake Clerk Proxy</h1>\n            <p>An hour of back and forth with Claude later, Clerk was made out to be the closest match to PocketID. All that is needed is a small proxy that rewrites a few urls:</p>\n            <pre><code class=\"language-Caddyfile\">:80 {\n    log {\n        output file /var/log/caddy/access.log\n        format json\n    }\n\n    @authorize path /oauth/authorize\n    handle @authorize {\n        uri query scope openid+email+profile\n        redir {$POCKETID_URL}/authorize?{query} 302\n    }\n\n    rewrite /oauth/token    /api/oidc/token\n    rewrite /oauth/userinfo /api/oidc/userinfo\n\n    reverse_proxy {$POCKETID_URL}\n}\n</code></pre>\n            <p>Combined with a small Dockerfile:</p>\n            <div class=\"language-Dockerfile highlighter-rouge\">\n              <div class=\"highlight\">\n                <pre class=\"highlight\"><code><span class=\"k\">FROM</span><span class=\"s\"> caddy:2-alpine</span>\n<span class=\"k\">ENV</span><span class=\"s\"> POCKETID_URL=\"\"</span>\n<span class=\"k\">COPY</span><span class=\"s\"> Caddyfile /etc/caddy/Caddyfile</span>\n<span class=\"k\">EXPOSE</span><span class=\"s\"> 80</span>\n<span class=\"k\">HEALTHCHECK</span><span class=\"s\"> --interval=30s --timeout=3s \\</span>\n  CMD wget -q --spider http://localhost/ || exit 1\n</code></pre>\n              </div>\n            </div>\n            <h1 id=\"custom-claims\">Custom Claims</h1>\n            <p>Coolify expects additional Claims from “Clerk” that PocketID does not provide by default, using a custom User Group that injects these hardcoded values we can fake it.</p>\n            <div class=\"language-plaintext highlighter-rouge\">\n              <div class=\"highlight\">\n                <pre class=\"highlight\"><code>email_verified = true\nuser_id = pocketid-user\n</code></pre>\n              </div>\n            </div>\n            <p>The actual values don’t matter, Coolify merely expects them to be set and then ignores it. Users are matched by their email address.</p>\n            <h1 id=\"configure-coolify-oauth\">Configure Coolify OAuth</h1>\n            <p>Once deployed and running, the “Clerk” OAuth can be configured as usual and the Base URL/Authentication Endpoint pointing to the “Clerk” Proxy.</p>\n            <p>PocketID being Passkeys-only is considered safe, but the old Username/Password login is vulnerable to brute-force attacks.\n              To prevent this we add a redirect in the Server Configuration -&gt; Proxy -&gt; Dynamic Configurations we add a file called login-redirect.yaml (Don’t forget to replace <code class=\"language-plaintext highlighter-rouge\">{coolify-host}</code> with your domain):</p>\n            <div class=\"language-yaml highlighter-rouge\">\n              <div class=\"highlight\">\n                <pre class=\"highlight\"><code><span class=\"na\">http</span><span class=\"pi\">:</span>\n  <span class=\"na\">middlewares</span><span class=\"pi\">:</span>\n    <span class=\"na\">redirect-to-clerk</span><span class=\"pi\">:</span>\n      <span class=\"na\">redirectRegex</span><span class=\"pi\">:</span>\n        <span class=\"na\">regex</span><span class=\"pi\">:</span> <span class=\"s2\">\"</span><span class=\"s\">^https://{coolify-host}/login$\"</span>\n        <span class=\"na\">replacement</span><span class=\"pi\">:</span> <span class=\"s2\">\"</span><span class=\"s\">https://{coolify-host}/auth/clerk/redirect\"</span>\n        <span class=\"na\">permanent</span><span class=\"pi\">:</span> <span class=\"kc\">false</span>\n\n  <span class=\"na\">routers</span><span class=\"pi\">:</span>\n    <span class=\"na\">coolify-login-redirect</span><span class=\"pi\">:</span>\n      <span class=\"na\">rule</span><span class=\"pi\">:</span> <span class=\"s2\">\"</span><span class=\"s\">Host(`{coolify-host}`)</span><span class=\"nv\"> </span><span class=\"s\">&amp;&amp;</span><span class=\"nv\"> </span><span class=\"s\">Path(`/login`)\"</span>\n      <span class=\"na\">middlewares</span><span class=\"pi\">:</span>\n        <span class=\"pi\">-</span> <span class=\"s\">redirect-to-clerk@file</span>\n      <span class=\"na\">service</span><span class=\"pi\">:</span> <span class=\"s\">coolify</span>\n      <span class=\"na\">entryPoints</span><span class=\"pi\">:</span>\n        <span class=\"pi\">-</span> <span class=\"s\">https</span>\n      <span class=\"na\">tls</span><span class=\"pi\">:</span>\n        <span class=\"na\">certresolver</span><span class=\"pi\">:</span> <span class=\"s\">letsencrypt</span>\n</code></pre>\n              </div>\n            </div>\n            <p>Coolify’s Traefik should automatically pick up dynamic configs and the next login should immediately redirect you to your OAuth Endpoint.</p>\n            <h1 id=\"further-considerations\">Further considerations</h1>\n            <p>This will break authentication if the “Clerk” Proxy container or PocketID is ever not up and reachable.</p>\n            <p>Should either one of those go down we can place this shell script on the host as an easy way to gain emergency access:</p>\n            <div class=\"language-sh highlighter-rouge\">\n              <div class=\"highlight\">\n                <pre class=\"highlight\"><code><span class=\"c\">#!/bin/sh</span>\n\n<span class=\"nv\">FILE</span><span class=\"o\">=</span><span class=\"s2\">\"/data/coolify/proxy/dynamic/login-redirect.yaml\"</span>\n\n<span class=\"k\">if</span> <span class=\"o\">[</span> <span class=\"nt\">-f</span> <span class=\"s2\">\"</span><span class=\"nv\">$FILE</span><span class=\"s2\">\"</span> <span class=\"o\">]</span><span class=\"p\">;</span> <span class=\"k\">then\n    </span><span class=\"nb\">rm</span> <span class=\"s2\">\"</span><span class=\"nv\">$FILE</span><span class=\"s2\">\"</span>\n    <span class=\"nb\">echo</span> <span class=\"s2\">\"Login redirect disabled — password login restored\"</span>\n<span class=\"k\">else\n    </span><span class=\"nb\">cat</span> <span class=\"o\">&gt;</span> <span class=\"s2\">\"</span><span class=\"nv\">$FILE</span><span class=\"s2\">\"</span> <span class=\"o\">&lt;&lt;</span> <span class=\"sh\">'</span><span class=\"no\">EOF</span><span class=\"sh\">'\nhttp:\n  middlewares:\n    redirect-to-clerk:\n      redirectRegex:\n        regex: \"^https://{coolify-host}/login</span><span class=\"nv\">$\"</span><span class=\"sh\">\n        replacement: \"https://{coolify-host}/auth/clerk/redirect\"\n        permanent: false\n\n  routers:\n    coolify-login-redirect:\n      rule: \"Host(`{coolify-host}`) &amp;&amp; Path(`/login`)\"\n      middlewares:\n        - redirect-to-clerk@file\n      service: coolify\n      entryPoints:\n        - https\n      tls:\n        certresolver: letsencrypt\n</span><span class=\"no\">EOF\n</span>    <span class=\"nb\">echo</span> <span class=\"s2\">\"Login redirect enabled — passkey login enforced\"</span>\n<span class=\"k\">fi</span>\n</code></pre>\n              </div>\n            </div>\n          </article>","date_published":"2026-05-06T00:00:00+00:00","date_modified":"2026-07-14T00:00:00+00:00","tags":["oauth","coolify","pocketid"],"author":{"name":"Jan Seeger"}},{"id":"https://jan.alphadev.net/blog/2026/git-on-synology","url":"https://jan.alphadev.net/blog/2026/git-on-synology/","title":"Running modern Git on ancient Synology kernels","content_html":"<article class=\"post-content\">\n            <p><strong>UPDATE</strong>: There is a <a href=\"https://jan.alphadev.net/blog/2026/git-on-synology-2/\">follow-up with better variant of the hack described by this post</a>.</p>\n            <p>Git push to my Gitea failed with the following message:</p>\n            <div class=\"language-plaintext highlighter-rouge\">\n              <div class=\"highlight\">\n                <pre class=\"highlight\"><code>remote: error: unable to get random bytes for temporary file: Function not implemented\nremote: error: unable to create temporary file: Function not implemented\n</code></pre>\n              </div>\n            </div>\n            <p>Instead of generating random bits from <code class=\"language-plaintext highlighter-rouge\">/dev/urandom</code>, modern Git now calls the <code class=\"language-plaintext highlighter-rouge\">getrandom</code> syscall via libc, which has been available from Kernel 3.17 and up. Unfortunately for me Synology does not update to newer Kernel versions, instead they backport patches to the old version that came with the device, which is good for stability, but bad if you need to run a more modern Git version.</p>\n            <h1 id=\"ld_preload-shim\">LD_PRELOAD shim</h1>\n            <p>Claude suggested to replace the NAS, second best option was to build this small shim as SO file against libmusl and mount it into Gitea’s container:</p>\n            <div class=\"language-c highlighter-rouge\">\n              <div class=\"highlight\">\n                <pre class=\"highlight\"><code><span class=\"c1\">// file: getrandom_shim.c</span>\n<span class=\"cp\">#define _GNU_SOURCE\n#include</span> <span class=\"cpf\">&lt;sys/types.h&gt;</span><span class=\"cp\">\n#include</span> <span class=\"cpf\">&lt;unistd.h&gt;</span><span class=\"cp\">\n#include</span> <span class=\"cpf\">&lt;fcntl.h&gt;</span><span class=\"cp\">\n#include</span> <span class=\"cpf\">&lt;errno.h&gt;</span><span class=\"cp\">\n</span>\n<span class=\"kt\">ssize_t</span> <span class=\"nf\">getrandom</span><span class=\"p\">(</span><span class=\"kt\">void</span> <span class=\"o\">*</span><span class=\"n\">buf</span><span class=\"p\">,</span> <span class=\"kt\">size_t</span> <span class=\"n\">buflen</span><span class=\"p\">,</span> <span class=\"kt\">unsigned</span> <span class=\"kt\">int</span> <span class=\"n\">flags</span><span class=\"p\">)</span> <span class=\"p\">{</span>\n    <span class=\"p\">(</span><span class=\"kt\">void</span><span class=\"p\">)</span><span class=\"n\">flags</span><span class=\"p\">;</span>\n    <span class=\"kt\">int</span> <span class=\"n\">fd</span> <span class=\"o\">=</span> <span class=\"n\">open</span><span class=\"p\">(</span><span class=\"s\">\"/dev/urandom\"</span><span class=\"p\">,</span> <span class=\"n\">O_RDONLY</span><span class=\"p\">);</span>\n    <span class=\"k\">if</span> <span class=\"p\">(</span><span class=\"n\">fd</span> <span class=\"o\">&lt;</span> <span class=\"mi\">0</span><span class=\"p\">)</span> <span class=\"p\">{</span> <span class=\"n\">errno</span> <span class=\"o\">=</span> <span class=\"n\">EIO</span><span class=\"p\">;</span> <span class=\"k\">return</span> <span class=\"o\">-</span><span class=\"mi\">1</span><span class=\"p\">;</span> <span class=\"p\">}</span>\n    <span class=\"kt\">ssize_t</span> <span class=\"n\">n</span> <span class=\"o\">=</span> <span class=\"n\">read</span><span class=\"p\">(</span><span class=\"n\">fd</span><span class=\"p\">,</span> <span class=\"n\">buf</span><span class=\"p\">,</span> <span class=\"n\">buflen</span><span class=\"p\">);</span>\n    <span class=\"n\">close</span><span class=\"p\">(</span><span class=\"n\">fd</span><span class=\"p\">);</span>\n    <span class=\"k\">return</span> <span class=\"n\">n</span><span class=\"p\">;</span>\n<span class=\"p\">}</span>\n</code></pre>\n              </div>\n            </div>\n            <p>We take this small shim that redirects the syscall to the old <code class=\"language-plaintext highlighter-rouge\">/dev/urandom</code> device and compile it against alpine, which uses musl:</p>\n            <div class=\"language-sh highlighter-rouge\">\n              <div class=\"highlight\">\n                <pre class=\"highlight\"><code>/volume1/docker/shim# docker run <span class=\"nt\">--rm</span> <span class=\"nt\">-v</span> <span class=\"s2\">\"</span><span class=\"nv\">$PWD</span><span class=\"s2\">\"</span>:/src <span class=\"nt\">-w</span> /src alpine:3.19 sh <span class=\"nt\">-c</span> <span class=\"se\">\\</span>\n  <span class=\"s2\">\"apk add --no-cache gcc musl-dev &amp;&amp; gcc -shared -fPIC -O2 -o getrandom_shim.so getrandom_shim.c\"</span>\n/volume1/docker/shim# <span class=\"nb\">chmod </span>644 getrandom_shim.so\n</code></pre>\n              </div>\n            </div>\n            <p>Once the shim is built, we can now add these two lines the Gitea docker-compose.yaml to inject it into the dynamic linker before all the other symbols are resolved and redeploy:</p>\n            <div class=\"language-plaintext highlighter-rouge\">\n              <div class=\"highlight\">\n                <pre class=\"highlight\"><code>environment:\n      - LD_PRELOAD=/usr/local/lib/getrandom_shim.so\n…\nvolumes:\n      - /volume1/docker/shim/getrandom_shim.so:/usr/local/lib/getrandom_shim.so:ro\n</code></pre>\n              </div>\n            </div>\n            <p>Git is now working properly again. This post is put under the <a href=\"https://jan.alphadev.net/category/playground\">playground category</a> deliberately, because I don’t know yet whether I want to keep it like this or not.</p>\n          </article>","date_published":"2026-04-21T00:00:00+00:00","date_modified":"2026-07-14T00:00:00+00:00","tags":["synology","git"],"author":{"name":"Jan Seeger"}},{"id":"https://jan.alphadev.net/blog/2026/trip-report-wiesbaden","url":"https://jan.alphadev.net/blog/2026/trip-report-wiesbaden/","title":"Trip Report: Wiesbaden","content_html":"<article class=\"post-content\">\n            <p>First big motorcycle trip of the year. Fresh set of tires (approximately 150 KMs of use). Friday through Sunday, 700 KMs</p>\n            <p><img src=\"https://jan.alphadev.net/assets/2026/2C0E5E90-A6DF-4EE0-A631-DFE05C588960.png\" alt=\"Map showing the Tour\" /></p>\n            <h1 id=\"day-1-baden\">Day 1: Baden</h1>\n            <p>Started trip in light drizzle and 12 degrees C.</p>\n            <p>As the tour went on the rain stopped but I started to feel cold and replaced my light gloves with warmer ones I picked up along the way in Pforzheim.</p>\n            <p><img src=\"https://jan.alphadev.net/assets/2026/35B10592-0413-47CC-9648-0C74F914DAAE.jpg\" alt=\"Photo taken at our first stop in Pforzheim\" /></p>\n            <p>With a small delay we made it to Heidelberg where (at least in the old town area) publicly accessible chargers were sparse. A friendly parking garage manager did help me by blocking a free spot that had just opened up.</p>\n            <p>After a quick bite (including a quick dessert to go from <a href=\"https://zeitfuerbrot.com/baeckereien\">Zeit für Brot</a>) we went on to Mainz way delayed.</p>\n            <p>To not have additional delays we decided to use the faster route via the Autobahn, but were held up majorly when a construction site merged three lanes into one and traffic stopped to a crawl.</p>\n            <p>We made it to Mainz right after the sun went down. But the charger near our hotel I had previously planned on using was in a parking garage with license plate recognition and they refused to let me in to charge.</p>\n            <p><img src=\"https://jan.alphadev.net/assets/2026/27C4F344-5344-477A-A43D-AE3321F7F4F2.jpg\" alt=\"Electric Bike parked on the sidewalk next to the Charger\" /></p>\n            <p>I then found one in the old town area that showed as free, but when I arrived one was blocked by an electric car that had the cable in but wasn’t even charging and the other one by a rundown combustion Opel, so I had to drive up onto the sidewalk next to the charger.</p>\n            <p>I first tried the EnBW app and it appeared to unlock it but wouldn’t start charging. Two attempts via their website didn’t work. I then proceeded to download their app, where you had to unlock the charger first and then plug the vehicle in after it prompts you, it worked. Left the 80% charge target in because we wanted to spend time in Wiesbaden the next day.</p>\n            <h1 id=\"day-2-franken\">Day 2: Franken</h1>\n            <p>We stayed at the Ibis city, rooms and hotel bar were nice but when I woke up I was greeted by bed bugs.</p>\n            <p><img src=\"https://jan.alphadev.net/assets/2026/72AE1443-2346-4E8D-B0FA-059AABF54CFC.jpg\" alt=\"Bed Bug from the Hotel\" /></p>\n            <p>During checkout the front desk wasn’t occupied, but since we stayed only for the one night I didn’t bother waiting around to complain.</p>\n            <p>We went on to meet up for breakfast with friends. Naturally, the public charger on the map was on fenced off company grounds and the gate guard didn’t even know about a charger for electric vehicles existing.</p>\n            <p>I then found one nearby that wasn’t on any map by ESWE unfortunately neither the Roaming, nor their app worked.</p>\n            <p>Weather was nice and sunny, lots of other motorcycle riders out.</p>\n            <p>We continued on to Aschaffenburg. Found a charger next to some industrial zone. Plugged it in, and due to the learnings of the days prior, went straight to the AppStore and downloaded the app. While charging we went to a nearby Biergarten for some coffee and cake.</p>\n            <p>The journey onwards to Würzburg was uneventful and nice. We switched to lighter clothes for the day.</p>\n            <p>After checking into the hotel at the border of the city we went looking for a charger nearby. All the ones we found, either didn’t work or had the wrong plugs. Finally when we wanted to go grab some lunch, we came by a company parking lot with lots of free chargers and a poster saying for public use, naturally not showing up on the map.</p>\n            <h1 id=\"day-3-hohenlohe\">Day 3: Hohenlohe</h1>\n            <p>The next day I grabbed a quick coffee double espresso from the hotel before getting my bike back from the charger, then got back and ate real breakfast.</p>\n            <p>The weather already didn’t look promising with light rain.</p>\n            <p>We drove on to Schwäbisch Hall, where after a long, time reach anxiety kicked in again. Perhaps it was a combo of using the rain mode (which reduces the power recovered from braking), being soaking wet and cold and wanting to escape to a bakery with a warm coffee and watching the battery gauge slowly go down: Plugged in for 15 mins, couldn’t find an open bakery and drove on for the final (about 35) KMs. Didn’t need the 15 mins of charging in the end.</p>\n            <p>Wind, overcast sky and pouring rain for hours make for a terrible riding experience.</p>\n            <p>In Schwäbisch Hall we found the parking lot of the DMV that had chargers. There was a sign for no motorcycle parking, which I conveniently ignored and pulled a ticket. The chargers are activated using your entry ticket and you pay for both charging and parking before leaving.</p>\n            <p>We went to a restaurant at the top of the Einkorn and met up with friends and family there for lunch. Pealed off several layers of soaked clothing in the hopes of it drying at least somewhat.</p>\n            <p>Picked up the bike on the way back and was surprised that on Sundays parking (and charging) is free!</p>\n            <p>It was still raining and we were cold all the way to Esslingen where we went into a gas station to top up and warm up for a few minutes.</p>\n            <p>Slight navigational mishap lead us onto the A8 for a short stint. After that we rode the B27 back home with the option to stop in Tübingen if we get colder or the range gets any lower.</p>\n            <p>In the end we made it right before the sun went down. Freezing cold and shaking. I plugged the bike back in at home with 17 KMs of battery left.</p>\n            <p><img src=\"https://jan.alphadev.net/assets/2026/4570404C-011C-4A88-85AA-DA17321220FC.jpg\" alt=\"Picture of the bike, completely dirty\" /></p>\n            <h1 id=\"summary\">Summary</h1>\n            <p>In good conditions the bike has way more reach than what our backs can endure on those seats.</p>\n            <p>Riding for hundreds of kilometers in wind and rain is miserable. I am now considering what adjustments I’m going to make to my gear for future trips.</p>\n            <p>I now have three more charging apps on my phone. Price-wise (the free charging not included) it averages about 60 cents/kWh. The best experiences were with chargers that somebody put there, but weren’t on any map. I’ll add them to Chargemap once I’m done writing this.</p>\n          </article>","date_published":"2026-04-14T00:00:00+00:00","date_modified":"2026-07-14T00:00:00+00:00","tags":["motorcycle","zero s 2024"],"author":{"name":"Jan Seeger"}},{"id":"https://jan.alphadev.net/blog/2026/gitea-ssh-forwarding","url":"https://jan.alphadev.net/blog/2026/gitea-ssh-forwarding/","title":"Forwarding SSH to Gitea","content_html":"<article class=\"post-content\">\n            <p>For years my Gitea instance (in Docker) did have proper HTTPS clone URLs using a Reverse Proxy that handles domain and SSL termination, <strong>but I could never get SSH to work properly and had to expose the Gitea built-in SSH server on a different port</strong>.</p>\n            <p>This post describes my setup step by step in case you (or I) need to recreate it.</p>\n            <h1 id=\"prerequisites\">Prerequisites</h1>\n            <ul>\n              <li>a working Gitea instance</li>\n              <li>with its User Accounts set up</li>\n              <li>at least one user with a Public Key set up</li>\n              <li>your Synology has its SSH service properly configured, secured, exposed through the firewall</li>\n            </ul>\n            <h1 id=\"ssh-fundamentals\">SSH fundamentals</h1>\n            <p>But first a few things to understand, which I fully grasped way too late into the process:</p>\n            <ul>\n              <li>\n                <p>There will be just one SSH server running (on the host)</p>\n              </li>\n              <li>\n                <p>During authentication the SSH protocol only allows authentication. And exactly once.</p>\n                <p>That means you cannot during the authentication forward the connection to another server, only after authentication. But  any authentication required by the second server would fail because your (git) client thinks auth has already completed (which in a way it has)</p>\n              </li>\n              <li>\n                <p>Agent-Forwarding might work in theory, but we want regular ssh clones without any host config modifications required, that matters doubly on CI builds.</p>\n              </li>\n            </ul>\n            <h1 id=\"the-setup\">The setup</h1>\n            <p>Gitea comes batteries-included with little helpers for that. But the tricky part is getting this to work reliably and securely. We’ll mount the git’s .ssh dir read-write into the Gitea container and Gitea will then keep it updated with all its valid users public keys and an additional <em>tagged</em> command that lets Gitea to detect the user and allows further processing.</p>\n            <h1 id=\"setting-up-the-git-user\">Setting up the git user</h1>\n            <p>We need a system user that all git clones then are funnelled through (The git@ part in git@$domain.tld).</p>\n            <p>Synology’s Web Interface will block the creation of a git user for <em>security reasons</em>. Terminal it then is:</p>\n            <div class=\"language-sh highlighter-rouge\">\n              <div class=\"highlight\">\n                <pre class=\"highlight\"><code><span class=\"nb\">sudo </span>synouser <span class=\"nt\">--add</span> git <span class=\"s1\">''</span> <span class=\"s2\">\"\"</span> 0 <span class=\"s2\">\"\"</span> 0\n</code></pre>\n              </div>\n            </div>\n            <p>Then set an invalid password for the git user to prevent password authentication:</p>\n            <div class=\"language-sh highlighter-rouge\">\n              <div class=\"highlight\">\n                <pre class=\"highlight\"><code><span class=\"nb\">sudo </span>synouser <span class=\"nt\">--setpw</span> git <span class=\"s1\">'*'</span>\n</code></pre>\n              </div>\n            </div>\n            <p>While you’re at it, go to the Web Interface and tick the “Prevent user from changing password” checkbox.</p>\n            <h1 id=\"setting-up-the-git-users-ssh-directory\">Setting up the git user’s SSH directory</h1>\n            <div class=\"language-sh highlighter-rouge\">\n              <div class=\"highlight\">\n                <pre class=\"highlight\"><code><span class=\"nb\">sudo mkdir</span> <span class=\"nt\">-p</span> /var/services/homes/git/.ssh\n<span class=\"nb\">sudo touch</span> /var/services/homes/git/.ssh/authorized_keys\n<span class=\"nb\">sudo chown</span> <span class=\"nt\">-R</span> 1000 /var/services/homes/git/.ssh\n<span class=\"nb\">sudo chmod </span>700 /var/services/homes/git/.ssh\n<span class=\"nb\">sudo chmod </span>600 /var/services/homes/git/.ssh/authorized_keys\n<span class=\"nb\">sudo chmod </span>755 /var/services/homes/git\n</code></pre>\n              </div>\n            </div>\n            <p>Next we remove Synology’s extended ACLs from the git’s home directory, as sshd’s StrictModes rejects directories with ACL entries:</p>\n            <div class=\"language-sh highlighter-rouge\">\n              <div class=\"highlight\">\n                <pre class=\"highlight\"><code><span class=\"nb\">sudo</span> /usr/syno/bin/synoacltool <span class=\"nt\">-del</span> /var/services/homes/git\n</code></pre>\n              </div>\n            </div>\n            <h2 id=\"preparing-the-git-user-account-settings\">Preparing the git user account settings</h2>\n            <p>The user in my Gitea Docker container has the user id 1000 and because SSH PubKey auth is set to be picky when it comes to file permissions of its key files, I had to match the uid on the host.</p>\n            <p>First let’s check if the user id 1000 is free:</p>\n            <div class=\"language-sh highlighter-rouge\">\n              <div class=\"highlight\">\n                <pre class=\"highlight\"><code><span class=\"nb\">id </span>1000\n</code></pre>\n              </div>\n            </div>\n            <p>In my case it said “no such user”, which is exactly what we need.</p>\n            <p>Open the passwd file and change the git user id entry to 1000.</p>\n            <p><strong>Please take utmost care when editing this file, as it potentially could break your Synology install and lock you out of it!</strong></p>\n            <div class=\"language-sh highlighter-rouge\">\n              <div class=\"highlight\">\n                <pre class=\"highlight\"><code><span class=\"nb\">sudo </span>vi /etc/passwd\n</code></pre>\n              </div>\n            </div>\n            <p>Find the <code class=\"language-plaintext highlighter-rouge\">git</code> line and change it to:</p>\n            <div class=\"language-plaintext highlighter-rouge\">\n              <div class=\"highlight\">\n                <pre class=\"highlight\"><code>git:x:1000:100::/var/services/homes/git:/bin/sh\n</code></pre>\n              </div>\n            </div>\n            <p>This a changes the git user id to 1000 and the login shell to a valid value.</p>\n            <p>Now is a good time to restart the ssh service using</p>\n            <div class=\"language-sh highlighter-rouge\">\n              <div class=\"highlight\">\n                <pre class=\"highlight\"><code><span class=\"nb\">sudo </span>synoservicectl <span class=\"nt\">--restart</span> sshd\n</code></pre>\n              </div>\n            </div>\n            <h1 id=\"forwarding-to-gitea\">Forwarding to Gitea</h1>\n            <p>This is the tricky part I didn’t understand at first: An entry in the authorized_keys file can have additional parameters, like restricting the user to certain commands after auth.</p>\n            <p>We’ll create our own wrapper to forward to Gitea and restrict the git user to only be able to execute this one command that connects through to Gitea as <code class=\"language-plaintext highlighter-rouge\">/usr/local/bin/gitea-serv</code>:</p>\n            <div class=\"language-sh highlighter-rouge\">\n              <div class=\"highlight\">\n                <pre class=\"highlight\"><code><span class=\"nb\">sudo </span>vi /usr/local/bin/gitea-serv\n</code></pre>\n              </div>\n            </div>\n            <p>And put the following content there:</p>\n            <div class=\"language-sh highlighter-rouge\">\n              <div class=\"highlight\">\n                <pre class=\"highlight\"><code><span class=\"c\">#!/bin/sh</span>\n<span class=\"nb\">exec</span> /volume1/@appstore/ContainerManager/usr/bin/docker <span class=\"nb\">exec</span> <span class=\"nt\">-i</span> <span class=\"nt\">-u</span> git <span class=\"nt\">-e</span> <span class=\"nv\">SSH_ORIGINAL_COMMAND</span><span class=\"o\">=</span><span class=\"s2\">\"</span><span class=\"nv\">$SSH_ORIGINAL_COMMAND</span><span class=\"s2\">\"</span> gitea /app/gitea/gitea serv <span class=\"s2\">\"</span><span class=\"nv\">$@</span><span class=\"s2\">\"</span> <span class=\"nt\">--config</span> /data/gitea/conf/app.ini\n</code></pre>\n              </div>\n            </div>\n            <p>Make it executable:</p>\n            <div class=\"language-sh highlighter-rouge\">\n              <div class=\"highlight\">\n                <pre class=\"highlight\"><code><span class=\"nb\">sudo chmod </span>755 /usr/local/bin/gitea-serv\n</code></pre>\n              </div>\n            </div>\n            <h1 id=\"allow-gitea-serv-in-sudoers\">Allow <em>gitea-serv</em> in sudoers</h1>\n            <p>By default no user can access Gitea (or any other Docker container for that matter). We could add the git user to the docker group, but that would give it full access to any Container. We don’t want that. But we can give it sudo access without password check to the <em>gitea-serv</em> command:</p>\n            <div class=\"language-sh highlighter-rouge\">\n              <div class=\"highlight\">\n                <pre class=\"highlight\"><code><span class=\"nb\">sudo </span>sh <span class=\"nt\">-c</span> <span class=\"s1\">'cat &gt; /etc/sudoers.d/git-gitea &lt;&lt; EOF\ngit ALL=(root) NOPASSWD: /usr/local/bin/gitea-serv\nDefaults&gt;root env_keep+=SSH_ORIGINAL_COMMAND\nEOF'</span>\n</code></pre>\n              </div>\n            </div>\n            <p>Verify it works by switching to the git user and testing:</p>\n            <div class=\"language-sh highlighter-rouge\">\n              <div class=\"highlight\">\n                <pre class=\"highlight\"><code><span class=\"nb\">sudo </span>su <span class=\"nt\">-s</span> /bin/sh git <span class=\"nt\">-c</span> <span class=\"s1\">'sudo /usr/local/bin/gitea-serv serv key-1'</span>\n</code></pre>\n              </div>\n            </div>\n            <p>If it works the expected output should be something like <code class=\"language-plaintext highlighter-rouge\">Hi there, &lt;username&gt;! You've successfully authenticated...</code>. The actual name doesn’t matter, as <em>key-1</em> is just the first key that Gitea has stored.</p>\n            <p>If it didn’t work yet, it’ll probably sort itself out with the next step.</p>\n            <h1 id=\"mount-the-git-users-ssh-directory-into-the-gitea-container\">Mount the git users’ .ssh directory into the Gitea container</h1>\n            <p>I use docker-compose for the Gitea install, so I had to add this in the volumes section:</p>\n            <div class=\"language-yaml highlighter-rouge\">\n              <div class=\"highlight\">\n                <pre class=\"highlight\"><code><span class=\"na\">volumes</span><span class=\"pi\">:</span>\n  <span class=\"pi\">-</span> <span class=\"s\">other volumes</span>\n  <span class=\"pi\">-</span> <span class=\"s\">/var/services/homes/git/.ssh:/data/git/.ssh:rw</span>\n</code></pre>\n              </div>\n            </div>\n            <p>But if you use plain old Docker, just add the directory in there.</p>\n            <p>Please note: You have to mount the entire .ssh dir, as with file-based mounts Gitea has issues writing the keys.</p>\n            <h1 id=\"update-giteas-configuration\">Update Gitea’s configuration</h1>\n            <p>As already mentioned earlier, we have to tell Gitea to write the correct command (our gitea-serv) to the authorized_keys file, and we have to disable the built-in ssh server.</p>\n            <p>In my case I updated the docker-compose.yaml again:</p>\n            <div class=\"language-plaintext highlighter-rouge\">\n              <div class=\"highlight\">\n                <pre class=\"highlight\"><code>environment:\n  - SSH_DOMAIN=${ssh domain}\n  - SSH_PORT=22 # change this to whatever your Synology SSH daemon listens to\n  - START_SSH_SERVER=false # Gitea's own SSH server not needed\n</code></pre>\n              </div>\n            </div>\n            <p>The first two env variables do not have any effect other than to show up in the UI as clone URL. The second disables the built-in SSH server.</p>\n            <p>The other part of the Gitea config lives in the <em>app.ini</em> file:</p>\n            <div class=\"language-ini highlighter-rouge\">\n              <div class=\"highlight\">\n                <pre class=\"highlight\"><code><span class=\"nn\">[server]</span><span class=\"w\">\n</span><span class=\"py\">SSH_CREATE_AUTHORIZED_KEYS_FILE</span><span class=\"w\"> </span><span class=\"p\">=</span><span class=\"w\"> </span><span class=\"s\">true</span>\n<span class=\"py\">SSH_AUTHORIZED_KEYS_COMMAND_TEMPLATE</span><span class=\"w\"> </span><span class=\"p\">=</span><span class=\"w\"> </span><span class=\"s\">sudo /usr/local/bin/gitea-serv key-{{.Key.ID}}</span>\n</code></pre>\n              </div>\n            </div>\n            <p>Now it is time to restart (or in case of docker-compose redeploy) Gitea:</p>\n            <div class=\"language-sh highlighter-rouge\">\n              <div class=\"highlight\">\n                <pre class=\"highlight\"><code><span class=\"nb\">cd</span> <span class=\"o\">{</span>location of your docker-compose.yaml<span class=\"o\">}</span> <span class=\"o\">&amp;&amp;</span> docker compose restart\n</code></pre>\n              </div>\n            </div>\n            <h1 id=\"populate-authorized_keys\">Populate authorized_keys</h1>\n            <p>Whenever a key is removed or added in the Gitea webinterface the authorized_keys will be regenerated. But the regeneration can also be invoked from the CLI:</p>\n            <div class=\"language-sh highlighter-rouge\">\n              <div class=\"highlight\">\n                <pre class=\"highlight\"><code>docker <span class=\"nb\">exec</span> <span class=\"nt\">-u</span> git gitea /app/gitea/gitea admin regenerate keys <span class=\"nt\">--config</span> /data/gitea/conf/app.ini\n</code></pre>\n              </div>\n            </div>\n            <p>Verify the file was written correctly:</p>\n            <div class=\"language-bash highlighter-rouge\">\n              <div class=\"highlight\">\n                <pre class=\"highlight\"><code>docker <span class=\"nb\">exec </span>gitea <span class=\"nb\">cat</span> /data/git/.ssh/authorized_keys | <span class=\"nb\">head</span> <span class=\"nt\">-3</span>\n</code></pre>\n              </div>\n            </div>\n            <p>Expected format should be:</p>\n            <div class=\"language-plaintext highlighter-rouge\">\n              <div class=\"highlight\">\n                <pre class=\"highlight\"><code># gitea public key\ncommand=\"sudo /usr/local/bin/gitea-serv key-1\",no-port-forwarding,...,restrict ssh-ed25519 AAAA... user-1\n</code></pre>\n              </div>\n            </div>\n            <h1 id=\"test-the-connection\">Test the connection</h1>\n            <div class=\"language-sh highlighter-rouge\">\n              <div class=\"highlight\">\n                <pre class=\"highlight\"><code>ssh <span class=\"nt\">-T</span> git@<span class=\"k\">${</span><span class=\"nv\">your</span><span class=\"p\">-server</span><span class=\"k\">}</span>\n</code></pre>\n              </div>\n            </div>\n            <p>Expected output:</p>\n            <div class=\"language-plaintext highlighter-rouge\">\n              <div class=\"highlight\">\n                <pre class=\"highlight\"><code>Hi there, jan! You've successfully authenticated with the key named ${your-key-name},\nbut Gitea does not provide shell access.\n</code></pre>\n              </div>\n            </div>\n            <p>And your regular users should also still be able to connect:</p>\n            <div class=\"language-sh highlighter-rouge\">\n              <div class=\"highlight\">\n                <pre class=\"highlight\"><code>ssh <span class=\"o\">{</span>non-git-user<span class=\"o\">}</span>@<span class=\"o\">{</span>your-server<span class=\"o\">}</span>\n</code></pre>\n              </div>\n            </div>\n            <p>I am so happy works! I now can ssh into the machine using any other user and the HTTPS/SSH clone URLs finally work as expected. Didn’t have to do weird redirects, the git user and the sudo part is restricted to this one functionality. And Gitea is now also part of Synology’s fail2ban mechanism.</p>\n            <h1 id=\"troubleshooting\">Troubleshooting</h1>\n            <p>These are the steps I took to troubleshoot issues with the setup.</p>\n            <h2 id=\"permission-denied-on-connect\">Permission denied on connect</h2>\n            <p>Check what the sshd logs say:</p>\n            <div class=\"language-bash highlighter-rouge\">\n              <div class=\"highlight\">\n                <pre class=\"highlight\"><code><span class=\"nb\">sudo </span>journalctl <span class=\"nt\">-u</span> sshd.service <span class=\"nt\">--since</span> <span class=\"s2\">\"5 minutes ago\"</span>\n</code></pre>\n              </div>\n            </div>\n            <h2 id=\"bad-acl-permission\">bad ACL permission</h2>\n            <p>Try removing the Extended ACLs again: <code class=\"language-plaintext highlighter-rouge\">synoacltool -del /var/services/homes/git</code></p>\n            <h2 id=\"could-not-open-authorized-keys\">Could not open authorized keys”</h2>\n            <p>Check ownership: <code class=\"language-plaintext highlighter-rouge\">sudo chown -R 1000 /var/services/homes/git/.ssh</code></p>\n            <h2 id=\"key-not-found\">Key not found</h2>\n            <p>Regenerate keys: <code class=\"language-plaintext highlighter-rouge\">docker exec -u git gitea /app/gitea/gitea admin regenerate keys --config /data/gitea/conf/app.ini</code></p>\n            <h2 id=\"git-pushpull-shows-gitea-welcome-message-instead-of-working\">Git push/pull shows Gitea welcome message instead of working</h2>\n            <p><code class=\"language-plaintext highlighter-rouge\">SSH_ORIGINAL_COMMAND</code> is not being passed. Verify sudoers contains:</p>\n            <div class=\"language-plaintext highlighter-rouge\">\n              <div class=\"highlight\">\n                <pre class=\"highlight\"><code>Defaults&gt;root env_keep+=SSH_ORIGINAL_COMMAND\n</code></pre>\n              </div>\n            </div>\n            <h2 id=\"setup-broken-after-dsm-update\">Setup broken after DSM update</h2>\n            <p>Re-do the steps in <a href=\"#preparing-the-git-user-account-settings\">Preparing the git user account settings</a></p>\n            <h2 id=\"wrapper-script-docker-not-found\">Wrapper script: docker not found</h2>\n            <p>Verify the Docker binary path:</p>\n            <div class=\"language-bash highlighter-rouge\">\n              <div class=\"highlight\">\n                <pre class=\"highlight\"><code><span class=\"nb\">readlink</span> <span class=\"nt\">-f</span> /usr/local/bin/docker\n</code></pre>\n              </div>\n            </div>\n            <p>Update the path in <code class=\"language-plaintext highlighter-rouge\">/usr/local/bin/gitea-serv</code> accordingly. On Synology with Container Manager it is typically:</p>\n            <div class=\"language-plaintext highlighter-rouge\">\n              <div class=\"highlight\">\n                <pre class=\"highlight\"><code>/volume1/@appstore/ContainerManager/usr/bin/docker\n</code></pre>\n              </div>\n            </div>\n          </article>","date_published":"2026-03-22T00:00:00+00:00","date_modified":"2026-07-14T00:00:00+00:00","tags":["ssh","gitea","git"],"author":{"name":"Jan Seeger"}},{"id":"https://jan.alphadev.net/blog/2026/migrating-wasabi-synology-hyperbackup","url":"https://jan.alphadev.net/blog/2026/migrating-wasabi-synology-hyperbackup/","title":"Migrating Synology from Wasabi to HyperBackup Vault","content_html":"<article class=\"post-content\">\n            <p>New record: 3rd <a href=\"https://jan.alphadev.net/category/syslog\">syslog Post</a> in 3 days.</p>\n            <p><a href=\"https://wasabi.com/\">Wasabi</a> used to have a nice offer 4$/TB/month, S3 interface and a few years ago they even started to offer their services in European data centers. And because Synology HyperBackup natively supports S3 with custom endpoints it was a breeze to set up.</p>\n            <p>But over the years they have increased their prices, while at the same time their service got worse year over year.</p>\n            <p>When I got my second Synology, the plan was to allocate a small portion of storage on each machine and receive the backups of the other. And since both Synologys are hundreds of kilometers apart, it would even count as proper off-site backup.</p>\n            <p>Reddit is full of both reports that it works reliably, and that it always refuses connect from one machine to the other. However all agree that for it to work ports 5001 <em>(HTTPS Webinterface)</em> and 6281 <em>(HyperBackup Vault)</em> have to be reachable.</p>\n            <p>In my case, the port 5001 isn’t reachable from the internet, but a reverse proxy terminates the traffic on a subdomain using  <acronym title=\"Server Name Indication\">SNI</acronym>.</p>\n            <p>Eventually I gave up, since Wasabi did mostly work, and a working but increasingly expensive backup is better than a free one that isn’t reliable. But then the E-Mails about failing backup jobs slowly started to pile up, until I was fed up and had another go.</p>\n            <h1 id=\"tailnet\">Tailnet</h1>\n            <p>I had <a href=\"https://tailscale.com/\">Tailscale</a> installed on both machines. Normally used as Exit Node when using unencrypted WiFis or when I’m abroad and need an IP that is located in Germany. But due to the way Synology has set up the networking on their devices, I could connect from the Tailnet into the NAS but not from the NAS out to the Tailnet.</p>\n            <p>The <a href=\"https://tailscale.com/docs/integrations/synology#enable-outbound-connections\">Tailscale Docs describe a workaround</a> which involves creating the needed tun device on each boot. Now that they can see each other, we can use the Tailnet link-local IPv6 to set up the backup. Since the SSL cert for the login frame (and in my case even worse: PassKey auth) is broken, you’ll have to use the “Password Login” option.</p>\n            <p><img src=\"https://jan.alphadev.net/assets/2026/a28e49cb-1556-4cde-97d7-440d29939d62.jpg\" alt=\"HyperBackup\" /></p>\n          </article>","date_published":"2026-03-13T00:00:00+00:00","date_modified":"2026-07-14T00:00:00+00:00","tags":["synology"],"author":{"name":"Jan Seeger"}},{"id":"https://jan.alphadev.net/blog/2026/power-outage","url":"https://jan.alphadev.net/blog/2026/power-outage/","title":"The Power Woes continue","content_html":"<article class=\"post-content\">\n            <p>Around mid-day the power went out <a href=\"https://jan.alphadev.net/blog/2025/power-woes/\">again</a>. I searched the internet for a cause, or any mention of an outage or power grid issue. Came up empty-handed.</p>\n            <p><img src=\"https://jan.alphadev.net/assets/2026/95433e78-e87c-4640-bf66-4f8d5c03ecc8.jpeg\" alt=\"Syslog: Server ran off battery power between 12:49:41 and 12:49:36 on the 12th of March 2026\" /></p>\n            <p>The power went out during my lunch break. One of the rare occasions where I noticed it before the <acronym title=\"Uninterruptible Power Supply\">UPS</acronym> informed me.</p>\n          </article>","date_published":"2026-03-12T00:00:00+00:00","date_modified":"2026-07-14T00:00:00+00:00","tags":["ups"],"author":{"name":"Jan Seeger"}},{"id":"https://jan.alphadev.net/blog/2026/coolify","url":"https://jan.alphadev.net/blog/2026/coolify/","title":"Kotlin + Ktor + Hetzner + Coolify = ❤️","content_html":"<article class=\"post-content\">\n            <h1 id=\"web-apps\">Web Apps</h1>\n            <p>For simplicity sake my default way of publishing was with static sites on either GitLab Pages or GitHub Pages. But for deploying web apps I just didn’t have a nice way.</p>\n            <p>There were unsuccessful attempts with serverless Cloud providers, that charge by the CPU second, but that means I don’t get to have any application state, because the instances are short-lived. And the deployment itself was always fiddly and couldn’t be relied on.</p>\n            <p>I had stumbled on <a href=\"https://coolify.io\">Coolify</a> earlier before and had wanted to give it a try. But when I found out you can have Hetzner pre-install one when configuring a shared server with them, there was just no reason not to try.</p>\n            <p>Setup took a few minutes, and now all I have to do is to add a tiny Dockerfile to a Kotlin repo, the usual: pull temurin, build the app, run the jar, expose port 8080 and set up a new App in Coolify. Add a domain name and optionally a Webhook that triggers a rebuild when the code changes, and off we go.</p>\n            <p>Coolify makes this feel outright casual and effortless. Need a database side-car along with it? Coolify has you covered. Custom config for the reverse proxy? Easily done.</p>\n            <h1 id=\"static-site-hosting\">Static Site Hosting</h1>\n            <p>After having migrated from <a href=\"https://jan.alphadev.net/blog/2024/good-bye-neocities/\">Neocities to GitLab Pages</a>, I had (somewhat jokingly) mused on self-hosting again. And since it worked so well with the custom web apps, I had to try it with a static site.</p>\n            <p>And with some help from Claude this now runs on my own host. <del>The SSL score is atrocious</del><sup id=\"fnref:1\"><a href=\"#fn:1\" class=\"footnote\" rel=\"footnote\" role=\"doc-noteref\">1</a></sup> and I am not fully happy with the setup, but at least it is mine. Instead of using the static site deploy type, I built a bespoke Docker container with nginx.</p>\n            <p>The convoluted GitLab Pipeline process with multiple containers and interwoven includes is now handled by a small multi-step Dockerfile that contains a total of 19 lines of code for building, deploying and running everything!</p>\n            <h1 id=\"batteries-included-apps\">Batteries-included Apps</h1>\n            <p>It even comes with pre-configured apps. You can spin up a complete <a href=\"https://pocket-id.org\">PocketID</a> instance in a few seconds, ready to go.</p>\n            <p>I always wanted to try <a href=\"https://appwrite.io\">AppWrite</a> or <a href=\"https://supabase.com\">Supabase</a> for small personal projects. Now I can deploy one with a few clicks, play around and tear it down with as few clicks again.</p>\n            <h1 id=\"downsides\">Downsides</h1>\n            <p>Coolify is written in PHP.  I’d much prefer something written in a more robust language, but it works.</p>\n            <p>It does work with arbitrary Git Servers, like Gitea, but takes some coercion and finagling to get it working, while it is clearly designed to be used with the GitHub integration that comes with it.</p>\n            <p>Note: Gitea doesn’t (yet) have a dedicated Coolify target for WebHooks but will work when selecting “Gitea” as target.</p>\n            <p>ALL the different http apps are in use. Coolify uses Caddy as frontend, Traefik as backend and the Docker Images usually have their own Webserver or Reverse Proxy in it. It works but that is more moving pieces than I would have liked my setup to have. If any one of those has a security vulnerability the entire stack is open to attack.</p>\n            <div class=\"footnotes\" role=\"doc-endnotes\">\n              <ol>\n                <li id=\"fn:1\">\n                  <p><a href=\"https://www.ssllabs.com/ssltest/analyze.html?d=jan.alphadev.net&amp;latest\">Has been fixed since</a>, using a custom Reverse Proxy middleware config. <a href=\"#fnref:1\" class=\"reversefootnote\" role=\"doc-backlink\">&#8617;</a></p>\n                </li>\n              </ol>\n            </div>\n          </article>","date_published":"2026-03-11T00:00:00+00:00","date_modified":"2026-07-14T00:00:00+00:00","tags":["kotlin","ktor","hetzner","coolify"],"author":{"name":"Jan Seeger"}},{"id":"https://jan.alphadev.net/blog/2026/first-motorcycle-trip","url":"https://jan.alphadev.net/blog/2026/first-motorcycle-trip/","title":"First Motorcycle ride of the year","content_html":"<article class=\"post-content\">\n            <p>The first somewhat ridable days were weeks ago, but I was either gone or didn’t have the time.</p>\n            <p>Today everything serendipitously fell into place: yesterday was warm enough, no frost over night, nice weather on a lazy Saturday afternoon and a buddy of mine was also planning on a short ride.</p>\n            <p>Started the ride at 80% battery, and rode 55 kilometers in total, down to about 50%. We took it slowly on backroads and gradually increased the riding difficulty. We had all kinds of terrain, flat portions and up and down the hills, easy curves, turnpikes. By the end the sun was about to set and the temperatures started to drop from the 17 degrees we had set out at, down to 13 degrees.</p>\n            <p>Compared to my (also electric) winter car, it is noticeable how much better the motor and the BMC performs. Not only in output power but in estimating, recouping and saving power.</p>\n            <p>It was fun every minute of the ride and I’m looking forward to the warmer months, when the conditions are even better and the sun is up for longer.</p>\n          </article>","date_published":"2026-03-07T00:00:00+00:00","date_modified":"2026-07-14T00:00:00+00:00","tags":["motorcycle","zero s 2024"],"author":{"name":"Jan Seeger"}},{"id":"https://jan.alphadev.net/blog/2026/xteink-4","url":"https://jan.alphadev.net/blog/2026/xteink-4/","title":"New e-Reader","content_html":"<article class=\"post-content\">\n            <p>In 2010 I imported a <a href=\"https://en.wikipedia.org/wiki/Barnes_%26_Noble_Nook_1st_Edition\">Barnes &amp; Noble Nook</a> from the U.S. and had devoured tons of books (including Stephen King’s Dark entire <a href=\"https://en.wikipedia.org/wiki/The_Dark_Tower_(series)\">Tower Series</a>) on it. Until the screen broke by accident on a vacation two years later.</p>\n            <p>Since then I had fallen back to reading on Android phones (<a href=\"https://en.wikipedia.org/wiki/Aldiko\">Aldiko Reader</a>) and iBooks on the iPad mini, then the iPad Pro and then my iPhone after that.</p>\n            <p>iBooks had automatic syncing, so I could double click a book on the Mac and it would automatically show up on all the other devices. And Screen Fatigue aside, the iPhone’s high-res, self illuminated display is quite nice at night.</p>\n            <p>Ever since my Nook broke, I had missed the feeling of eInk. But a Kindle device was never an option with their closed-up ecosystem. In the meantime Kobo had released some nice e-Readers (<a href=\"https://gl.kobobooks.com/products/kobo-clara-colour\">even with color displays</a>) and there even were other <a href=\"https://shop.boox.com/products/palma2\">portable options</a>.</p>\n            <p>A full size e-Reader takes up space in your jacket pocket<sup id=\"fnref:1\"><a href=\"#fn:1\" class=\"footnote\" rel=\"footnote\" role=\"doc-noteref\">1</a></sup>. I opted to go for a small, cheap and unilluminated one by <a href=\"https://www.xteink.com/products/xteink-x4\">Xteink</a> with a black &amp; white E-Ink display.</p>\n            <h1 id=\"xteink-x4\">Xteink X4</h1>\n            <h2 id=\"hardware\">Hardware</h2>\n            <p>There’s no Android and thus no support for arbitrary Apps to be installed. For me that is a plus, as it removes unnecessary complexity and saves battery.</p>\n            <p>Speaking of which, the battery holds up well for me, I have to charge it every couple of weeks. And the ubiquitous USB-C makes charging easy, when needed.</p>\n            <p>The device is computationally underwhelming, but given the battery size that is also a good thing. Once a book has been “indexed”<sup id=\"fnref:2\"><a href=\"#fn:2\" class=\"footnote\" rel=\"footnote\" role=\"doc-noteref\">2</a></sup>, page turns themselves are quick and snappy. But navigating the menu and settings will still take a long second.</p>\n            <p>My device came with a FAT32 formatted 32 GB Micro SD card in the box, once I took that out and formatted it as ExFAT the entire interface was loading noticeably quicker.</p>\n            <p><img src=\"https://jan.alphadev.net/assets/2026/xteink-marketing.jpg\" alt=\"Xteink X4 Marketing Material showing it attached to an iPhone (Pro because of the big camera array)\" /></p>\n            <p>I’m still a bit confused, their marketing material shows the device attached to an iPhone via MagSafe. Realistically it only fits Pro Max iPhones and according to the <a href=\"https://www.reddit.com/r/xteinkereader/\">Subreddit</a> only certain Models.</p>\n            <p><img src=\"https://jan.alphadev.net/assets/2026/DSC09388.jpeg\" alt=\"Xteink X4 laying on an iPhone 15 Pro with the camera bump getting in the way\" /></p>\n            <p>Therefore if you are, like me, in the non-Max club, this is going to be a standalone device for you.</p>\n            <h2 id=\"software\">Software</h2>\n            <h3 id=\"stock-firmware\">Stock Firmware</h3>\n            <p>The default fonts are perfectly legible. But if the defaults aren’t your cup of tea, there’s tons of <a href=\"https://www.readme.club\">great resources</a> out there to customize everything to your liking.</p>\n            <p>It only supports ePub, TXT and BMP files, no Markdown, no PDF and no Comic Book formats. Which is fine, but should be known before buying.</p>\n            <p>But even the formats it does support are often broken, For instance there’s stray HTML divs rendered throughout the text. I have yet to try cleaning up the ePub files with a Calibre Plugin to see whether it improves the experience. But coming from the Nook and iBooks I’m used to just copy the Publisher’s ePub files over as-is.</p>\n            <p>Some book covers and images are missing because modern ones come with SVG files that cannot be rendered, which is a bummer but understandable.</p>\n            <h4 id=\"webinterface\">Webinterface</h4>\n            <p>On the plus side, no Internet connection is needed, as there is a Hotspot built-in, that once you connect your phone to it, you can navigate to a barebones web interface and upload ePub files directly there.</p>\n            <p><img src=\"https://jan.alphadev.net/assets/2026/0a976f6d-466e-41f5-bf67-d74333bcccf6.png\" alt=\"Screenshot of the Webinterface\" /></p>\n            <p>But in reality the web interface is janky and you have to acknowledge several error messages, upload the file, close some other error message saying that the upload failed (even though it worked) and then disconnect. File management, as in creating folders, moving or deleting files is present but outright doesn’t work.</p>\n            <h2 id=\"custom-firmware\">Custom Firmware</h2>\n            <p>There is an active community developing tools and mods for it. Including an open source <a href=\"https://github.com/crosspoint-reader/crosspoint-reader\">alternative firmware</a> that can be flashed. CrossPoint Reader’s interface is more barebones, and has less settings. But the text rendering and the reading experience is way better.</p>\n            <p>Plus at the moment there is so much work being done, that a new version with new features is dropped literally every few days.</p>\n            <p>There’s no Hotspot functionality, but once connected to a network<sup id=\"fnref:3\"><a href=\"#fn:3\" class=\"footnote\" rel=\"footnote\" role=\"doc-noteref\">3</a></sup> you can even directly browse Calibre OPDS catalogs without taking out the Storage Card.</p>\n            <p>Another thing of note: The manufacturer doesn’t seem to like the custom Firmware at all, which is why the devs have been forced to move to a <a href=\"https://www.reddit.com/r/xteinkHax/\">Subreddit of their own</a>.</p>\n            <h2 id=\"should-you-get-one\">Should you get one?</h2>\n            <p>I’ve paid 65,48€ including postage and shipping and now have read several books with the Stock Firmware as well as the alternative one and am happy with it.</p>\n            <p>This is still not a purchase recommendation though, but if you think it has a place in your life, even after I described its flaws here, go ahead and buy it.</p>\n            <p>The hardware is cheap and lightweight. Do not get it for the Software. If the MagSafe issue doesn’t bother you, get the Chinese X4 from Ali Express and flash the alternative Firmware:</p>\n            <p>The upcoming X3 will supposedly fix the MagSafe issue, but does have proprietary Pogo Pins instead of USB‑C and it is currently unclear whether it will allow flashing <acronym title=\"Custom Firmware\">CFW</acronym>.</p>\n            <p>Don’t bother with the warranty of the international version — It isn’t worth it for such a cheap device.</p>\n            <p>You do not have to be able to read a single word of Chinese:</p>\n            <ul>\n              <li>Power it up</li>\n              <li>Connect the USB Cable</li>\n              <li>Navigate to the <a href=\"https://xteink.dve.al\">flashing website</a></li>\n              <li>Press the flash button</li>\n              <li>Push the reset button on the side</li>\n              <li>Done</li>\n            </ul>\n            <div class=\"footnotes\" role=\"doc-endnotes\">\n              <ol>\n                <li id=\"fn:1\">\n                  <p>And you have to have it handy, otherwise you’ll end up using it only at home <a href=\"#fnref:1\" class=\"reversefootnote\" role=\"doc-backlink\">&#8617;</a></p>\n                </li>\n                <li id=\"fn:2\">\n                  <p>Indexing is what it says, in reality it converts the ePub into its internal binary format that is computationally better suited for the Hardware. <a href=\"#fnref:2\" class=\"reversefootnote\" role=\"doc-backlink\">&#8617;</a></p>\n                </li>\n                <li id=\"fn:3\">\n                  <p>Yes, Mobile Hotspot also work. <a href=\"#fnref:3\" class=\"reversefootnote\" role=\"doc-backlink\">&#8617;</a></p>\n                </li>\n              </ol>\n            </div>\n          </article>","date_published":"2026-01-22T00:00:00+00:00","date_modified":"2026-07-14T00:00:00+00:00","tags":["reading","eink"],"author":{"name":"Jan Seeger"}},{"id":"https://jan.alphadev.net/blog/2026/ocean-view","url":"https://jan.alphadev.net/blog/2026/ocean-view/","title":"Ocean View","content_html":"<article class=\"post-content\">\n            <center>\n              <figure>\n                <img src=\"https://jan.alphadev.net/assets/2025/429FDC00-127E-4FAC-98BB-8AA9DE360052.jpg\">\n                <figcaption>Ocean View</figcaption>\n              </figure>\n            </center>\n          </article>","date_published":"2026-01-03T00:00:00+00:00","date_modified":"2026-07-14T00:00:00+00:00","tags":["travels","tenerife"],"author":{"name":"Jan Seeger"}},{"id":"https://jan.alphadev.net/blog/2026/40-questions","url":"https://jan.alphadev.net/blog/2026/40-questions/","title":"40 Questions","content_html":"<article class=\"post-content\">\n            <p>For some years now I fill out Steph Ango’s <a href=\"https://stephango.com/40-questions\">40 Questions</a> to reflect over the past year and don’t share it with anybody but store it for future reference for myself.</p>\n            <p>The questions cover a broad enough base, serving as a good starting point to  drill into the year past.</p>\n            <p>Haven’t tried the <a href=\"https://stephango.com/40-questions-decade\">decade one</a> yet, but will also do at some point.</p>\n          </article>","date_published":"2026-01-01T00:00:00+00:00","date_modified":"2026-07-14T00:00:00+00:00","tags":[],"author":{"name":"Jan Seeger"}},{"id":"https://jan.alphadev.net/blog/2025/media-report","url":"https://jan.alphadev.net/blog/2025/media-report/","title":"Media Report 4/2025","content_html":"<article class=\"post-content\">\n            <p>As is <a href=\"https://jan.alphadev.net/blog/2024/media-report-01-2024/\">customary</a>, I’ve been reading a lot during my vacation:</p>\n            <ul>\n              <li>\n                <p><a href=\"https://pragprog.com/titles/kotlinbt/kotlin-brain-teasers/\">Kotlin Brain Teasers</a></p>\n                <p>Found it via <a href=\"https://pragprog.com/\">Lukas Mathis’ recommendation</a> and thoroughly enjoyed it, did even learn something new.<sup id=\"fnref:1\"><a href=\"#fn:1\" class=\"footnote\" rel=\"footnote\" role=\"doc-noteref\">1</a></sup></p>\n                <p>Big fan of <a href=\"https://pragprog.com/\">PragProg</a> as a publisher in general.</p>\n              </li>\n              <li>\n                <p><a href=\"https://qntm.org/ra\">Ra</a></p>\n                <p>qntm’s work is always an interesting read. Started this book <a href=\"https://jan.alphadev.net/blog/2024/media-report-01-2024/\">two years ago</a> and only now found the time to finish it.</p>\n                <p>At the beginning it is weird to read through the setup, as it tries to connect “magic” and “science”, especially with various jumps in times and storylines. But once you are past the “suspension of disbelief” threshold, it gets easier and all these loose threads are masterfully connected to a coherent whole.</p>\n              </li>\n              <li>\n                <p><a href=\"https://www.goodreads.com/book/show/32076670-ball-lightning\">Ball Lightning</a></p>\n                <p>After having read Cixin Liu’s famous masterpiece <a href=\"https://www.goodreads.com/book/show/20518872-the-three-body-problem\">Three Body Problem</a> trilogy way back, I had set out to read some of his other works and had read Ball Lightning. But since the Three Body Problem still occupied my mind at the time, I did read this book but didn’t fully grasp it back then.</p>\n                <p>When I scrolled past it in my library and it said “read” but I couldn’t remember a single thing, I re-read it and it was worth it.</p>\n              </li>\n              <li>\n                <p><a href=\"https://www.goodreads.com/book/show/35018901-head-on\">Head On</a></p>\n                <p>Being a huge fan of Scalzi’s <a href=\"https://jan.alphadev.net/blog/2013/old-mans-war/\">Old Man’s War</a> series, naturally I had to read his other works.</p>\n                <p>I devoured this entire book within a day and a half, attesting to its ability to capture the reader. The story involves a lot of characters and their interactions, but each are distinct enough that they are not confusing.<sup id=\"fnref:2\"><a href=\"#fn:2\" class=\"footnote\" rel=\"footnote\" role=\"doc-noteref\">2</a></sup> And in the end it all resolves neatly.</p>\n              </li>\n              <li>\n                <p><a href=\"https://qntm.org/structure\">Fine Structure</a></p>\n                <p>Classic QNTM, you have to keep at it to be able to keep up with the plot, memetics naturally present. Overall I did enjoy the book, but the end feels a bit rushed for an otherwise good book. It all happens on a few pages, and does not resolve some of the plot strings.</p>\n              </li>\n              <li>\n                <p><a href=\"https://pragprog.com/titles/hwrust/hands-on-rust/\">Hands-on Rust</a></p>\n                <p>Another one I picked up at the PragProg Black Friday sale. Didn’t yet finish, but learning a new language isn’t as easy as finishing a book front to back, so 🤷</p>\n              </li>\n            </ul>\n            <h1 id=\"39c3-talks\">39C3 Talks</h1>\n            <p>Apart from reading I watched a lot of interesting talks from the <a href=\"https://media.ccc.de/c/39c3\">congress</a>. Interesting to me as a potential buyer of a Steam Frame, was the <a href=\"https://media.ccc.de/v/39c3-breaking-architecture-barriers-running-x86-games-and-apps-on-arm\">Fex-Talk</a>. But my pick for the best one so far goes to the <a href=\"https://media.ccc.de/v/39c3-all-my-deutschlandtickets-gone-fraud-at-an-industrial-scale\">Deutschland Ticket</a> one.</p>\n            <h1 id=\"read-before-my-vacation\">Read before my vacation</h1>\n            <p>These I had read before my vacation, during summer or fall but still want to list them somewhere for completeness’ sake:</p>\n            <ul>\n              <li>\n                <p><a href=\"https://www.diewithzerobook.com/\">Die with Zero</a></p>\n                <p>Bill Perkins makes the case from various angles that one should re-evaluate how wealth and posessions are used in ones lifetime. He makes the case using various parables and sheds light from various angles. Worthwhile cause, even if the target group seems to be US citizens. Interesting read nonetheless, though it repeats very often recounting the same concepts.</p>\n              </li>\n              <li>\n                <p><a href=\"https://craphound.com/category/redteamblues/\">Read Team Blues</a></p>\n                <p>Cory Doctorow writes less outlandish SciFi more grounded in the real world, thinking ahead of technalogy that already exists and takes it to new places, diving into the consequences they have for us by telling a compelling story around it.</p>\n                <p>This book is no exception where it delves into Cryptocurrencies and the Silicon Valley. Nice and cozy read.</p>\n              </li>\n            </ul>\n            <div class=\"footnotes\" role=\"doc-endnotes\">\n              <ol>\n                <li id=\"fn:1\">\n                  <p>Most surprisingly: The value of a previously committed return statement can be overridden using a finally statement! <a href=\"#fnref:1\" class=\"reversefootnote\" role=\"doc-backlink\">&#8617;</a></p>\n                </li>\n                <li id=\"fn:2\">\n                  <p>Spoiler: It also helps a bit that many of them won’t live through to the end. <a href=\"#fnref:2\" class=\"reversefootnote\" role=\"doc-backlink\">&#8617;</a></p>\n                </li>\n              </ol>\n            </div>\n          </article>","date_published":"2025-12-29T00:00:00+00:00","date_modified":"2026-07-14T00:00:00+00:00","tags":["reading"],"author":{"name":"Jan Seeger"}}],"description":"Der alltägliche Wahnsinn","author":{"name":"Jan Seeger"}}