<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>foosel.net</title><link>https://foosel.net/</link><description>Recent content on foosel.net</description><generator>Hugo</generator><language>en-us</language><copyright>Gina Häußge (foosel)</copyright><lastBuildDate>Wed, 17 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://foosel.net/feed.xml" rel="self" type="application/rss+xml"/><item><title>Meta: Some late spring cleaning</title><link>https://foosel.net/blog/2026-06-17-some-late-spring-cleaning/</link><pubDate>Wed, 17 Jun 2026 00:00:00 +0000</pubDate><guid>https://foosel.net/blog/2026-06-17-some-late-spring-cleaning/</guid><description>&lt;p&gt;It was annoying me for a while now that my chosen theme &lt;a href="https://github.com/adityatelange/hugo-PaperMod/"&gt;PaperMod&lt;/a&gt; didn&amp;rsquo;t also support some kind of all-in-one RSS feed with all main sections. And also that the feeds were called &lt;code&gt;index.xml&lt;/code&gt; instead of &lt;code&gt;feed.xml&lt;/code&gt;, confusing me every time I wanted to check something on them manually. Also, the feed link title just being &lt;code&gt;rss&lt;/code&gt; didn&amp;rsquo;t exactly help in a feed reader either.&lt;/p&gt;
&lt;p&gt;So just now I&amp;rsquo;ve done some long overdue spring cleaning on this page:&lt;/p&gt;</description><content:encoded><![CDATA[<p>It was annoying me for a while now that my chosen theme <a href="https://github.com/adityatelange/hugo-PaperMod/">PaperMod</a> didn&rsquo;t also support some kind of all-in-one RSS feed with all main sections. And also that the feeds were called <code>index.xml</code> instead of <code>feed.xml</code>, confusing me every time I wanted to check something on them manually. Also, the feed link title just being <code>rss</code> didn&rsquo;t exactly help in a feed reader either.</p>
<p>So just now I&rsquo;ve done some long overdue spring cleaning on this page:</p>
<ol>
<li>I updated PaperMod<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> &amp; adjusted my overwrites as necessary</li>
<li>I implemented my custom rss feed template with some more logic to render it with all main sections on home</li>
<li>I disabled the automatic inclusion of the section feeds and created my own with proper <code>title</code> attributes (&quot;(All|Blog|TIL) posts on foosel.net&quot;), put into the head of all pages</li>
<li>I overrode the default name of the generated feeds to be <code>feed.xml</code> &amp; added the necessary redirects (hopefully&hellip;)</li>
<li>I also added links to the main RSS feed at two prominent places: in the social icons on the home page as well as in the main navigation next to the search button. While the feeds were always discoverable through the related <code>link</code> tags in the HTML page itself, and there were also specific links on the section main pages, I still got some questions here and there on whether there was a feed, so I hope this makes things easier to discover now.</li>
</ol>
<p>If you want a look behind the scenes on how I did that, <a href="https://github.com/foosel/foosel.github.io">this page&rsquo;s source is on a public repo</a>, so feel free to take a look 😊</p>
<p>All that&rsquo;s left to say is: I&rsquo;m sorry should I have broken something there or caused your RSS reader to push all old posts into your face 😬</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>&hellip; and discovered that it&rsquo;s now apparently also a victim of vibe coding 😕&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>How to fix the Chromium WebHID connection for a Keychron G5 under Linux</title><link>https://foosel.net/til/how-to-fix-the-webhid-connection-for-a-keychron-g5-under-linux/</link><pubDate>Tue, 28 Apr 2026 00:00:00 +0000</pubDate><guid>https://foosel.net/til/how-to-fix-the-webhid-connection-for-a-keychron-g5-under-linux/</guid><description>&lt;p&gt;Today my new mouse arrived, a &lt;a href="https://www.keychron.com/products/keychron-g5-ultra-light-wireless-mouse"&gt;Keychron G5&lt;/a&gt;. I had read that it was supposedly configurable through (Chromium based) browsers via &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/WebHID_API"&gt;WebHID&lt;/a&gt;, OS agnostic, so no need to install any weird (bloated) drivers or keep a Windows VM ready. However, when I went to &lt;code&gt;https://launcher.keychron.com&lt;/code&gt; in my ungoogled Chromium running under Fedora Linux, I had to realize that while it could see the mouse, it would not actually connect to it.&lt;/p&gt;
&lt;p&gt;A quick web search later I fell over &lt;a href="https://github.com/StefanMarAntonsson/keychron-launcher-arch-guide"&gt;this repository&lt;/a&gt; with a guide on how to get Chromium&amp;rsquo;s WebHID connection to work to a Keychron Q5 HE under ArchLinux, and I figured that was close enough (and the steps outlined therein logical enough) to adapt. And just so I can easily find this again, I&amp;rsquo;m replicating the crucial bits here&amp;hellip;&lt;/p&gt;</description><content:encoded><![CDATA[<p>Today my new mouse arrived, a <a href="https://www.keychron.com/products/keychron-g5-ultra-light-wireless-mouse">Keychron G5</a>. I had read that it was supposedly configurable through (Chromium based) browsers via <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebHID_API">WebHID</a>, OS agnostic, so no need to install any weird (bloated) drivers or keep a Windows VM ready. However, when I went to <code>https://launcher.keychron.com</code> in my ungoogled Chromium running under Fedora Linux, I had to realize that while it could see the mouse, it would not actually connect to it.</p>
<p>A quick web search later I fell over <a href="https://github.com/StefanMarAntonsson/keychron-launcher-arch-guide">this repository</a> with a guide on how to get Chromium&rsquo;s WebHID connection to work to a Keychron Q5 HE under ArchLinux, and I figured that was close enough (and the steps outlined therein logical enough) to adapt. And just so I can easily find this again, I&rsquo;m replicating the crucial bits here&hellip;</p>
<p>Apparently all I needed were some additional <code>udev</code> rules for both the mouse itself (when connected via the USB-C cable) as well as the 2.4GHz dongle (when connected wirelessly). So, first I figured out the relevant vendor and product IDs:</p>
<pre tabindex="0"><code>$ lsusb | grep -i keychron
Bus 005 Device 042: ID 3434:d028 Keychron Keychron Ultra-Link 8K
Bus 005 Device 062: ID 3434:d06f Keychron Keychron G5
</code></pre><p>Those turned out to be vendor <code>0x3434</code> and products <code>0xd06f</code> (wired mouse) and <code>0xd028</code> (dongle). So based on the repo linked above I created <code>/etc/udev/rules.d/50-keychron-g5.rules</code> as follows:</p>
<pre tabindex="0"><code># Keychron G5 (WebHID)
KERNEL==&#34;hidraw*&#34;, SUBSYSTEM==&#34;hidraw&#34;, ATTRS{idVendor}==&#34;3434&#34;, ATTRS{idProduct}==&#34;d06f&#34;, MODE=&#34;0666&#34;, TAG+=&#34;uaccess&#34;, TAG+=&#34;udev-acl&#34;
KERNEL==&#34;hidraw*&#34;, SUBSYSTEM==&#34;hidraw&#34;, ATTRS{idVendor}==&#34;3434&#34;, ATTRS{idProduct}==&#34;d028&#34;, MODE=&#34;0666&#34;, TAG+=&#34;uaccess&#34;, TAG+=&#34;udev-acl&#34;
</code></pre><p>then refreshed <code>udev</code>:</p>
<pre tabindex="0"><code>sudo udevadm control --reload-rules
sudo udevadm trigger
</code></pre><p>and replugged the mouse and restarted Chromium.</p>
<p>After that, the launcher page could finally connect! 🥳</p>
]]></content:encoded></item><item><title>How to automatically build a human.json file in Hugo</title><link>https://foosel.net/til/how-to-automatically-build-a-humanjson-file-in-hugo/</link><pubDate>Fri, 20 Mar 2026 00:00:00 +0000</pubDate><guid>https://foosel.net/til/how-to-automatically-build-a-humanjson-file-in-hugo/</guid><description>&lt;p&gt;As I mentioned the other day, I recently added a &lt;a href="https://foosel.net/til/humanjson/"&gt;human.json file&lt;/a&gt; to this site.&lt;/p&gt;
&lt;p&gt;However, it became a bit annoying adding new vouches to the json manually.&lt;/p&gt;
&lt;p&gt;So today I sat down and made it get created automatically during my page build powered by &lt;a href="https://gohugo.io"&gt;Hugo&lt;/a&gt;, based on a list of vouches stored in a &lt;code&gt;vouches.yaml&lt;/code&gt; data file&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;For this I first created said data file in &lt;code&gt;data/vouches.yaml&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- &lt;span style="color:#f92672"&gt;url&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;https://food.foosel.net&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;date&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;2026-03-14&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- &lt;span style="color:#f92672"&gt;url&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;https://chaos.social/@foosel&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;date&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;2026-03-14&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- &lt;span style="color:#f92672"&gt;url&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;https://octoprint.org&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;date&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;2026-03-14&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then I created a new output format &lt;code&gt;humanjson&lt;/code&gt; in my &lt;code&gt;config.yaml&lt;/code&gt;, making sure to also add it to the home page:&lt;/p&gt;</description><content:encoded><![CDATA[<p>As I mentioned the other day, I recently added a <a href="/til/humanjson/">human.json file</a> to this site.</p>
<p>However, it became a bit annoying adding new vouches to the json manually.</p>
<p>So today I sat down and made it get created automatically during my page build powered by <a href="https://gohugo.io">Hugo</a>, based on a list of vouches stored in a <code>vouches.yaml</code> data file<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>.</p>
<p>For this I first created said data file in <code>data/vouches.yaml</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>- <span style="color:#f92672">url</span>: <span style="color:#ae81ff">https://food.foosel.net</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">date</span>: <span style="color:#e6db74">&#34;2026-03-14&#34;</span>
</span></span><span style="display:flex;"><span>- <span style="color:#f92672">url</span>: <span style="color:#ae81ff">https://chaos.social/@foosel</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">date</span>: <span style="color:#e6db74">&#34;2026-03-14&#34;</span>
</span></span><span style="display:flex;"><span>- <span style="color:#f92672">url</span>: <span style="color:#ae81ff">https://octoprint.org</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">date</span>: <span style="color:#e6db74">&#34;2026-03-14&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># ...</span>
</span></span></code></pre></div><p>Then I created a new output format <code>humanjson</code> in my <code>config.yaml</code>, making sure to also add it to the home page:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">outputs</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">home</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#ae81ff">HTML</span>
</span></span><span style="display:flex;"><span>    - <span style="color:#ae81ff">JSON</span>
</span></span><span style="display:flex;"><span>    - <span style="color:#ae81ff">humanjson</span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e"># ...</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">outputFormats</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#75715e"># ...</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">humanjson</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">mediaType</span>: <span style="color:#ae81ff">application/json</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">baseName</span>: <span style="color:#ae81ff">human</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">isPlainText</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">notAlternative</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e"># ...</span>
</span></span></code></pre></div><p>Finally I created a template for that in  <code>_layout/home.humanjson.json</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go-text-template" data-lang="go-text-template"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    &#34;version&#34;: &#34;0.1.1&#34;,
</span></span><span style="display:flex;"><span>    &#34;url&#34;: <span style="color:#75715e">{{</span> <span style="color:#a6e22e">.Site.BaseURL</span> <span style="color:#f92672">|</span> <span style="color:#a6e22e">jsonify</span> <span style="color:#75715e">}}</span>,
</span></span><span style="display:flex;"><span>    &#34;vouches&#34;: [
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">{{-</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">$index</span><span style="color:#f92672">,</span> <span style="color:#a6e22e">$vouch</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">hugo</span><span style="color:#a6e22e">.Data.vouches</span> <span style="color:#75715e">}}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">{{-</span> <span style="color:#66d9ef">with</span> <span style="color:#a6e22e">$vouch</span> <span style="color:#75715e">}}</span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">{{-</span> <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">$index</span> <span style="color:#75715e">}}</span>,<span style="color:#75715e">{{</span><span style="color:#66d9ef">end</span><span style="color:#75715e">}}</span>
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>            &#34;url&#34;: <span style="color:#75715e">{{</span> <span style="color:#a6e22e">.url</span> <span style="color:#f92672">|</span> <span style="color:#a6e22e">jsonify</span> <span style="color:#75715e">}}</span>,
</span></span><span style="display:flex;"><span>            &#34;vouched_at&#34;: <span style="color:#75715e">{{</span> <span style="color:#a6e22e">.date</span> <span style="color:#f92672">|</span> <span style="color:#a6e22e">jsonify</span> <span style="color:#75715e">}}</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">{{-</span> <span style="color:#66d9ef">end</span> <span style="color:#75715e">}}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">{{-</span> <span style="color:#66d9ef">end</span> <span style="color:#75715e">}}</span>
</span></span><span style="display:flex;"><span>    ]
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>I kept the required <code>&lt;link rel=&quot;/human.json&quot;&gt;</code> in my <code>layouts/partials/extend_head.html</code> file that gets included on <em>all</em> pages by my theme (contrary to how the linking of output formats work, which is why I went with <code>notAlternative: true</code> for that).</p>
<p>And to make it even easier to quickly add a new vouch, I also added a new task to my <a href="https://taskfile.dev">Taskfile</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span>  <span style="color:#f92672">add-vouch</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">desc</span>: <span style="color:#ae81ff">Adds a new vouch to human.json.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">vars</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">PAGE</span>: <span style="color:#e6db74">&#39;{{.CLI_ARGS}}&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">FILE</span>: <span style="color:#e6db74">&#39;data/vouches.yaml&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">TODAY</span>: <span style="color:#e6db74">&#39;{{now | date &#34;2006-01-02&#34;}}&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">cmds</span>:
</span></span><span style="display:flex;"><span>      - |<span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        cat &gt;&gt; {{.FILE}} &lt;&lt;EOF
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        - url: {{.PAGE}}
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">          date: &#34;{{.TODAY}}&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">        EOF</span>
</span></span></code></pre></div><p>Now, vouching for a page<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> is as easy as</p>
<pre tabindex="0"><code>task add-vouch -- https://example.com
</code></pre><div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Targeting Hugo 0.156.0+&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Even from my phone thanks to <a href="https://termux.dev/">Termux</a>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>human.json</title><link>https://foosel.net/til/humanjson/</link><pubDate>Sun, 15 Mar 2026 00:00:00 +0000</pubDate><guid>https://foosel.net/til/humanjson/</guid><description>&lt;p&gt;Thanks to &lt;a href="https://mastodon.social/@sethmlarson/116229292847984092"&gt;a toot by Seth Larson&lt;/a&gt; yesterday I learned about the &lt;a href="https://codeberg.org/robida/human.json"&gt;still in draft &lt;code&gt;human.json&lt;/code&gt; spec&lt;/a&gt; by Beto Dealmeida:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;human.json&lt;/code&gt; is a lightweight protocol for humans to assert authorship of their site content and vouch for the humanity of others. It uses URL ownership as identity, and trust propagates through a crawlable web of vouches between sites.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Basically, a web of trust to confirm that pages are written by actual human beings vs just LLM generated slop.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Thanks to <a href="https://mastodon.social/@sethmlarson/116229292847984092">a toot by Seth Larson</a> yesterday I learned about the <a href="https://codeberg.org/robida/human.json">still in draft <code>human.json</code> spec</a> by Beto Dealmeida:</p>
<blockquote>
<p><code>human.json</code> is a lightweight protocol for humans to assert authorship of their site content and vouch for the humanity of others. It uses URL ownership as identity, and trust propagates through a crawlable web of vouches between sites.</p>
</blockquote>
<p>Basically, a web of trust to confirm that pages are written by actual human beings vs just LLM generated slop.</p>
<p>That idea certainly clicked with me and so I quickly threw together a basic <a href="/human.json"><code>human.json</code></a> in this page&rsquo;s <code>static</code> folder and linked to it from the <code>layouts/partials/extend_head.html</code> override file. That will get filled more over time 😊</p>
<p>And as reading through the spec of <code>human.json</code> also made me learn about the <a href="/ai"><code>/ai</code> slashpage</a> I added that as well and linked it from the footer.</p>
<p>Those <a href="https://slashpages.net/">slashpages</a> are a fun idea I&rsquo;ll have to investigate further too!</p>
]]></content:encoded></item><item><title>My TODO list lives in my pocket</title><link>https://foosel.net/blog/2026-03-06-my-todo-list-lives-in-my-pocket/</link><pubDate>Fri, 06 Mar 2026 00:00:00 +0000</pubDate><guid>https://foosel.net/blog/2026-03-06-my-todo-list-lives-in-my-pocket/</guid><description>Combining leather working, paper crafting and handwriting to manage my life</description><content:encoded><![CDATA[<p>For several months now I&rsquo;ve been meaning to write about my notebook journey. Today the stars finally aligned and I just sat down to tell you about how I&rsquo;ve been managing my TODOs for almost a year now.</p>
<p>It all started when I created myself a little leather cover for my A5 sized solo RPG journal:</p>















        
        

        
            
            
        

        
            <blockquote class="toot-blockquote" cite="https://chaos.social@foosel/status/114191126595846444">
                <div class="toot-header">
                    <a class="toot-profile" href="https://chaos.social/@foosel" rel="noopener">
                        <img
                            src="https://assets.chaos.social/accounts/avatars/000/235/099/original/a2e381e9aab4a693.png"
                            alt="Mastodon avatar for @foosel@chaos.social"
                            loading="lazy"
                        />
                    </a>
                    <div class="toot-author">
                        <a class="toot-author-name" href="https://chaos.social/@foosel" rel="noopener">Gina Häußge</a>
                        <a class="toot-author-handle" href="https://chaos.social/@foosel" rel="noopener">@foosel@chaos.social</a>
                    </div>
                </div>
                <p>Currently learning how to play <a href="https://chaos.social/tags/Starforged" class="mention hashtag" rel="tag">#<span>Starforged</span></a> and as I apparently enjoy the journaling part of it I just built myself a traveler&#39;s notebook 😊 Apart from two notebook inserts, this also has an insert for holding my character sheet and my assets, plus a move cheat sheet.</p><p><a href="https://chaos.social/tags/making" class="mention hashtag" rel="tag">#<span>making</span></a> <a href="https://chaos.social/tags/leatherwork" class="mention hashtag" rel="tag">#<span>leatherwork</span></a> <a href="https://chaos.social/tags/solo_rpg" class="mention hashtag" rel="tag">#<span>solo_rpg</span></a> <a href="https://chaos.social/tags/ttrpg" class="mention hashtag" rel="tag">#<span>ttrpg</span></a></p>
                
                    
                        
                            
                        
                    
                        
                            
                        
                    
                        
                            
                        
                    
                        
                            
                        
                    
                    <div class="toot-img-grid-4">
                    
                        
                            
                            <style>
                                .img-a9ae72f4ab1bfe0fc8ab99a68ccc106d {
                                    aspect-ratio: 3325 / 2494;
                                }
                            </style>
                            <img
                                src="https://assets.chaos.social/media_attachments/files/114/191/114/661/115/151/original/d6e0e0b6103216dc.jpg"
                                alt="A traveler&#39;s notebook made from brown leather"
                                class="toot-media-img img-a9ae72f4ab1bfe0fc8ab99a68ccc106d"
                                loading="lazy"
                            />
                        
                    
                        
                            
                            <style>
                                .img-a1d389bab9ae628c8b8db5618e153491 {
                                    aspect-ratio: 3325 / 2494;
                                }
                            </style>
                            <img
                                src="https://assets.chaos.social/media_attachments/files/114/191/115/578/435/489/original/9df115952591023d.jpg"
                                alt="Craft paper insert with a big pocket for some asset cards."
                                class="toot-media-img img-a1d389bab9ae628c8b8db5618e153491"
                                loading="lazy"
                            />
                        
                    
                        
                            
                            <style>
                                .img-85174fd57622e1a23341dbf1af6d0b1d {
                                    aspect-ratio: 3325 / 2494;
                                }
                            </style>
                            <img
                                src="https://assets.chaos.social/media_attachments/files/114/191/115/961/179/899/original/244bc59a564175c5.jpg"
                                alt="Notebook insert with a part of my current run"
                                class="toot-media-img img-85174fd57622e1a23341dbf1af6d0b1d"
                                loading="lazy"
                            />
                        
                    
                        
                            
                            <style>
                                .img-16ed1d771035e4691da4bdcd6e0b9ca4 {
                                    aspect-ratio: 3325 / 2494;
                                }
                            </style>
                            <img
                                src="https://assets.chaos.social/media_attachments/files/114/191/116/130/826/300/original/41c4f4587a06a332.jpg"
                                alt="Craft paper insert holding my folded character sheet."
                                class="toot-media-img img-16ed1d771035e4691da4bdcd6e0b9ca4"
                                loading="lazy"
                            />
                        
                    
                    </div>
                    
                    
                        
                        
                    
                        
                        
                    
                        
                        
                    
                        
                        
                    
                
                
                
                <div class="toot-footer">
                    <a href="https://chaos.social/@foosel/114191126595846444" class="toot-date" rel="noopener">March 19, 2025, 21:05</a>&nbsp;<span class="pokey">(UTC)</span>
                </div>
            </blockquote>
        
    
<p>I quickly realized that I quite liked writing down things by hand on the 100gsm dot grid paper of the notebook I got, and that I also liked the look and functionality of what I had created there.</p>
<p>That lead to an idea. At this point I was still managing my TODOs in <a href="https://todoist.com">Todoist</a>, but also often ended up writing down the weekly tasks on the whiteboard in my office as I had learned that checking them off physically with an actual pen gave me way more dopamine than clicking a checkbox in an app. And the whiteboard approach quickly fell apart when I was not at my desk - I often ended up taking a picture of my whiteboard before having to work from somewhere else, or putting things into Todoist that felt a bit like they didn&rsquo;t belong there (small chores).</p>
<p>So I thought that maybe I should just try to go fully analogue with my tasks, in a portable form factor. The A5 size I had used for the RPG journal didn&rsquo;t fit that portability requirement fully, but A6 would be something that I could easily fit into my cargo pockets. I thus ordered a stack of A6 notebooks<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> and built another leather cover for that size.</p>















        
        

        
            
            
        

        
            <blockquote class="toot-blockquote" cite="https://chaos.social@foosel/status/114241410760797146">
                <div class="toot-header">
                    <a class="toot-profile" href="https://chaos.social/@foosel" rel="noopener">
                        <img
                            src="https://assets.chaos.social/accounts/avatars/000/235/099/original/a2e381e9aab4a693.png"
                            alt="Mastodon avatar for @foosel@chaos.social"
                            loading="lazy"
                        />
                    </a>
                    <div class="toot-author">
                        <a class="toot-author-name" href="https://chaos.social/@foosel" rel="noopener">Gina Häußge</a>
                        <a class="toot-author-handle" href="https://chaos.social/@foosel" rel="noopener">@foosel@chaos.social</a>
                    </div>
                </div>
                <p>After really enjoying using the traveler&#39;s notebook I built for <a href="https://chaos.social/tags/solorpg" class="mention hashtag" rel="tag">#<span>solorpg</span></a> use recently*, I&#39;ve just now built a smaller version for regular note taking. While I&#39;m a huge fan of <span class="h-card" translate="no"><a href="https://mas.to/@obsidian" class="u-url mention">@<span>obsidian</span></a></span>, not everything needs to be digital and I want to try some analog brainstorming and possibly journaling again.</p><p>* see <a href="https://chaos.social/@foosel/114191126595846444" target="_blank" rel="nofollow noopener" translate="no"><span class="invisible">https://</span><span class="ellipsis">chaos.social/@foosel/114191126</span><span class="invisible">595846444</span></a></p><p><a href="https://chaos.social/tags/leatherworking" class="mention hashtag" rel="tag">#<span>leatherworking</span></a></p>
                
                    
                        
                            
                        
                    
                    <div class="toot-img-grid-1">
                    
                        
                            
                            <style>
                                .img-e21e1a72443dbcd0ed66d2c4d3f9fb29 {
                                    aspect-ratio: 3325 / 2494;
                                }
                            </style>
                            <img
                                src="https://assets.chaos.social/media_attachments/files/114/241/401/327/898/403/original/8570c4aa2b82fefb.jpg"
                                alt="A passport/A6 sized traveler&#39;s notebook sitting on a table"
                                class="toot-media-img img-e21e1a72443dbcd0ed66d2c4d3f9fb29"
                                loading="lazy"
                            />
                        
                    
                    </div>
                    
                    
                        
                        
                    
                
                
                
                <div class="toot-footer">
                    <a href="https://chaos.social/@foosel/114241410760797146" class="toot-date" rel="noopener">March 28, 2025, 18:13</a>&nbsp;<span class="pokey">(UTC)</span>
                </div>
            </blockquote>
        
    
<p>Once more I went with a <a href="https://www.instructables.com/DIY-Midori-Style-Travelers-Notebook/">Midori style &ldquo;Traveler&rsquo;s Notebook&rdquo; approach</a> - a simple leather cover with a bungie cord in the middle, acting as both a place where to hang in one notebook from its spine, and a loop on the outside to keep the journal closed. To hang more than one book, additional bungie cord loops can be used to attach two or more books together.</p>
<p>Just like with my RPG journal, I also built myself an insert out of craft cardboard, to hold some of my cards and a slim tracker module, allow storing flat things on the front and back (receipts, tickets, my x-ray passport &amp; dentist bonus log&hellip;) and attach a pen loop and some bookmark ribbons to as well.</p>
<p><img alt="The outside front of the insert, with two card holders. The top one holds some white cards, the bottom one a black card sized tracker." loading="lazy" src="/blog/2026-03-06-my-todo-list-lives-in-my-pocket/front_inlay.resized.jpg"></p>
<p><img alt="Inside the insert&rsquo;s front there&rsquo;s a pocket to store flat stuff." loading="lazy" src="/blog/2026-03-06-my-todo-list-lives-in-my-pocket/front_inlay_2.resized.jpg"></p>
<p><img alt="The insert&rsquo;s back also has a pocket inside." loading="lazy" src="/blog/2026-03-06-my-todo-list-lives-in-my-pocket/back_inlay.resized.jpg"></p>
<p>Then I sat down and thought a lot on how to best go about organising things. I of course had heard about the <a href="https://bulletjournal.com/">Bullet Journal</a> method, so I looked up details on that. I quickly learned two things: 1) contrary to what images I found online and what I saw my mom doing made me believe, at its core bullet journalling is way less about fancy hand lettering and page designs but rather about rapidly logging down stuff and b) it&rsquo;s not actually about TODO lists but rather an approach for helping with self reflection and reaching your goals.</p>
<p>What I got from a) actually helped me a lot, but b) didn&rsquo;t work for me at all. I do call my little notebook my Bullet Journal, but it is in fact mostly a TODO list combined with some personal tracking, and of course note taking.</p>
<p>I did adopt the symbols however: Tasks that are planned but not yet started get a <code>•</code> in front of them. I can cross that off with an <code>X</code> to turn it into &ldquo;done&rdquo;. If a task takes longer or spans across multiple days, I can cross it off halfway <code>/</code> to mark it as started or in progress. With <code>&lt;</code> or <code>&gt;</code> I can signify that a task was rescheduled (forwards or backwards). All of these symbols can just be drawn over the initial <code>•</code> and I find that an amazing way to keep track of more than two possible task states.</p>
<p>Additionally I also use <code>°</code> for events, <code>-</code> to mark notes, <code>=</code> for mental and physical health related things and <code>»</code> for quotes.</p>
<p>So, how do I use all of this to organise my life now?</p>
<p>My journal contains two 30 sheet/60 page A5 books.</p>
<p>The first one is focused on the current month. I use it to keep track of my running tasks, logging my days, monitoring my <a href="https://foosel.net/blog/2025-09-15-about-my-chronic-vertigo/">vertigo symptoms</a> and mood. It helps me a ton  each day to see that I actually did get stuff done (even if it often doesn&rsquo;t feel like it).</p>
<p>Each month I create an overview of tasks I should get done sometime this month in my <em>Monthly Log</em>, and an overview of the next month and anything later than that in my <em>Future Log</em>.</p>
<p><img alt="Spread in my notebook showing the tasks for this month, and the future tasks for next month and later dates." loading="lazy" src="/blog/2026-03-06-my-todo-list-lives-in-my-pocket/this_and_next_month.resized.webp"></p>
<p>My <em>Daily Log</em> is combined with my mood tracker. I jot down one <strong>positive</strong> thing about each day (to combat the constant doom and gloom caused by living in this reality - it helps reading through what I write here at the end of each month). Even if it is just about taking an amazing nap that day, it counts.</p>
<p><img alt="Spread in my notebook showing a daily log, one line per day for the whole month, and a mood graph mapping each day&rsquo;s average mood in five levels." loading="lazy" src="/blog/2026-03-06-my-todo-list-lives-in-my-pocket/daily_log.resized.webp"></p>
<p>Then follows a spread with my <em>Vertigo Log</em>. It&rsquo;s a monthly log again with columns for the various symptoms and possible triggers I&rsquo;m tracking, with some space for daily notes as well. Sorry, but I&rsquo;m not including a picture here as all of that is a bit too personal to share publicly.</p>
<p>The rest of the notebook is organised by week. Each week I start with an overview of my repeating <em>chores</em> in the shape of an <a href="https://alastairjohnston.com/projects-the-alastair-method/">Alastair matrix</a>. I have columns for all days of the week. Each task gets its own line, with a <code>•</code> in the column(s) of the day(s) where I need or want to take care of it, and another <code>•</code> right in front of the task that I get to cross off when it&rsquo;s done.</p>
<p><img alt="Spread with a week&rsquo;s chore list on the left and a list of tasks for Next Week on the right." loading="lazy" src="/blog/2026-03-06-my-todo-list-lives-in-my-pocket/chores_and_next_week.resized.webp"></p>
<p>On the second page of the spread I have my running list of things I need to take care of <em>next week</em>.</p>
<p>The next spread is my running <em>tasks</em> list for the week. I put everything here as it comes up. Like the chores list it&rsquo;s a matrix allowing me to schedule each task for one or more specific days.</p>
<p><img alt="Spread with a running task list of a week." loading="lazy" src="/blog/2026-03-06-my-todo-list-lives-in-my-pocket/running_tasklist.resized.webp"></p>
<p>As you can see, I also color code tasks with markers, which allows me to easily filter the list at a glance. Green is work related, light blue are household chores, dark blue are health related tasks and pink are event related things.</p>
<p>I pre-fill the chores and tasks list based on the things I scheduled for this week over the course of last week, the monthly log &amp; the weekly repeated chores.</p>
<p>On the next pages then follows my running log of the week. Each morning I jot down the current date and weekday, take note of the weather (and also keep that updated throughout the day - it&rsquo;s a vertigo trigger) and then copy over all tasks scheduled for the day from the list of chores or tasks. Whatever comes up during the day gets added too, and also put on the running task list (I want that as a full overview of the week). If something comes up that doesn&rsquo;t need to get done this week, I throw it on the list for next week, or next month or later, whatever fits.</p>
<p><img alt="Spread of a running weekly log in my notebook. There are headers for March 2nd through March 4th, little symbols next to them, and several notes below." loading="lazy" src="/blog/2026-03-06-my-todo-list-lives-in-my-pocket/running_week.resized.webp"></p>
<p>What I also log into each day&rsquo;s header are various symbols for habit tracking, and an overall mood indicator - I use that for the mood graph in the daily log.</p>
<p>That&rsquo;s basically the day to day with the notebook: Jot down tasks as they come, get an overview each day, get sweet sweet dopamine every time I cross something off.</p>
<p>But what about the second book? That is for longer term tracking.</p>
<p>I have an overview of (recurring) tasks throughout the year here that I need to take care of. I use that at the start of each month to prefill the monthly log.</p>
<p><img alt="Spread in my notebook, titled &ldquo;Monthly Task List 2026&rdquo;. It&rsquo;s a matrix with columns for each month and a whole bunch of tasks scheduled across them." loading="lazy" src="/blog/2026-03-06-my-todo-list-lives-in-my-pocket/monthly_tasks_2026.resized.webp"></p>
<p>I also use the second notebook for long term work stuff, e.g. here is how I keep track of what I needed to take care of for each bugfix release for OctoPrint 1.11.x that wasn&rsquo;t already logged in the shape of public issues, PRs or commits, e.g. things I need to mention in the changelog, or not yet published security advisories.</p>
<p><img alt="Spread in my notebook, titled &ldquo;OctoPrint 1.11.x&rdquo;. There are entries for 1.11.1 to 1.11.8. All entries up to and including 1.11.7 have notes below them, a checkmark next to the header and the release date." loading="lazy" src="/blog/2026-03-06-my-todo-list-lives-in-my-pocket/octoprint_1.11.x.resized.webp"></p>
<p>What else goes in there are ideas for various projects, what things I have borrowed to or from friends (and when and whether it was returned yet), what books I read, vacation plans, quick notes, notes about talks I&rsquo;ve seen &hellip; In short, basically everything else. I have a ton of lists (the Bullet Journal Method calls those &ldquo;collections&rdquo;), some of which are getting constantly updated, some of which only act as reference.</p>
<p>Some things from the second book get copied over to my personal knowledge base that I keep in <a href="https://obsidian.md">Obsidian</a> (synced to my own NextCloud). As an example, I used the notebook last year during two PyCon Italia and EuroPython to take notes during the talks I attended, and those have now gone into my knowledge base (or turned into tasks long taken care of).</p>
<p>Really long term tasks - e.g. future vaccinations, check-ups and such - I still keep digitally in <a href="https://tasks.org">tasks.org</a> (also synced to my own NextCloud). At some point they&rsquo;ll trigger a reminder on my phone, and then they&rsquo;ll get schedule in my regular task list.</p>
<p>At this point I can say that basically everything gets managed with the help of this tiny self built journal for almost a year now and that works amazingly well for me.</p>
<p><img alt="My closed journal on my desk, next to it a couple of pastelle colored marker pens." loading="lazy" src="/blog/2026-03-06-my-todo-list-lives-in-my-pocket/bujo_and_pens.resized.jpg"></p>
<p>Needless to say I&rsquo;ve cancelled my Todoist subscription. 😉 <a href="https://di.day">#iDIDit</a>.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>I got <a href="https://www.amazon.de/dp/B0D3YTV4GZ">these</a> but I&rsquo;m sure you can find something similar elsewhere too.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>How to work around tailscale breaking IPv6 on the host</title><link>https://foosel.net/til/how-to-work-around-tailscale-breaking-ipv6-on-the-host/</link><pubDate>Fri, 23 Jan 2026 00:00:00 +0000</pubDate><guid>https://foosel.net/til/how-to-work-around-tailscale-breaking-ipv6-on-the-host/</guid><description>&lt;p&gt;Yesterday I ran into an incredibly weird issue. I installed the &lt;a href="https://tailscale.com/"&gt;Tailscale&lt;/a&gt; client on two of my virtual servers hosted in the Hetzner Cloud (running Ubuntu) and suddenly the websites they offered stopped working. I suspected Tailscale and indeed, &lt;code&gt;tailscale down&lt;/code&gt; immediately restored functionality.&lt;/p&gt;
&lt;p&gt;The websites in question are actually on GitHub Pages and my servers are just acting as reverse proxy to resolve domain and TLS, and a look into the web server&amp;rsquo;s &lt;code&gt;error.log&lt;/code&gt; showed that the issue in serving was that the server could no longer reach its upstream at &lt;code&gt;github.io&lt;/code&gt; when Tailscale was active. It wasn&amp;rsquo;t a general loss of external connectivity though - IPv4 addresses still worked great, the webserver however was trying to connect to the upstream via IPv6 and this is where things failed.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Yesterday I ran into an incredibly weird issue. I installed the <a href="https://tailscale.com/">Tailscale</a> client on two of my virtual servers hosted in the Hetzner Cloud (running Ubuntu) and suddenly the websites they offered stopped working. I suspected Tailscale and indeed, <code>tailscale down</code> immediately restored functionality.</p>
<p>The websites in question are actually on GitHub Pages and my servers are just acting as reverse proxy to resolve domain and TLS, and a look into the web server&rsquo;s <code>error.log</code> showed that the issue in serving was that the server could no longer reach its upstream at <code>github.io</code> when Tailscale was active. It wasn&rsquo;t a general loss of external connectivity though - IPv4 addresses still worked great, the webserver however was trying to connect to the upstream via IPv6 and this is where things failed.</p>
<p>I did some quick tests, pinging Google&rsquo;s DNS on both IPv4 (<code>8.8.8.8</code>) and IPv6 (<code>2001:4860:4860::8888</code>) with Tailscale running, and that showed that while Tailscale was running, IPv6 connectivity just broke down completely while IPv4 continued to work:</p>
<pre tabindex="0"><code>$ sudo tailscale up &amp;&amp; ping -c 3 8.8.8.8 &amp;&amp; sudo tailscale down
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=117 time=3.98 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=117 time=3.69 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=117 time=3.63 ms

--- 8.8.8.8 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2002ms
rtt min/avg/max/mdev = 3.631/3.771/3.986/0.162 ms

$ sudo tailscale up &amp;&amp; ping -c 3 2001:4860:4860::8888 &amp;&amp; sudo tailscale down
PING 2001:4860:4860::8888(2001:4860:4860::8888) 56 data bytes

--- 2001:4860:4860::8888 ping statistics ---
3 packets transmitted, 0 received, 100% packet loss, time 2051ms
</code></pre><p>As a next step, I checked the route that would be taken for resolving <code>2001:4860:4860::8888</code> with and without Tailscale:</p>
<pre tabindex="0"><code>$ ip route get 2001:4860:4860::8888
2001:4860:4860::8888 from :: via fe80::1 dev eth0 src aaaa:bbbb:cccc:dddd::1 metric 1024 pref medium

$ sudo tailscale up &amp;&amp; ip route get 2001:4860:4860::8888 &amp;&amp; sudo tailscale down
2001:4860:4860::8888 from :: via fe80::1 dev eth0 src fd7a:115c:a1e0::aaaa:bbbb metric 1024 pref medium
</code></pre><p>So, the issue was that for some reason, while Tailscale was running the system decided to use Tailscale&rsquo;s internal IPv6 <code>fd7a:115c:a1e0::aaaa:bbbb</code> set on <code>tailscale0</code> as the source IP but send the packet through the default route and <code>eth0</code>, and that didn&rsquo;t work. It basically hijacked the IPv6 traffic, even when the Tailnet wasn&rsquo;t even involved.</p>
<p>The routes looked fine to me and some buddies I asked also didn&rsquo;t spot anything amiss:</p>
<pre tabindex="0"><code>$ ip -6 rule show
0:      from all lookup local
5210:   from all fwmark 0x80000/0xff0000 lookup main
5230:   from all fwmark 0x80000/0xff0000 lookup default
5250:   from all fwmark 0x80000/0xff0000 unreachable
5270:   from all lookup 52
32766:  from all lookup main

$ ip -6 route show table local
local ::1 dev lo proto kernel metric 0 pref medium
local aaaa:bbbb:cccc:dddd::1 dev eth0 proto kernel metric 0 pref medium
local fd7a:115c:a1e0::aaaa:bbbb dev tailscale0 proto kernel metric 0 pref medium
local fe80::5fdc:58a7:1a93:da5a dev tailscale0 proto kernel metric 0 pref medium
local fe80::9400:ff:fe0d:61a1 dev eth0 proto kernel metric 0 pref medium
multicast ff00::/8 dev eth0 proto kernel metric 256 pref medium
multicast ff00::/8 dev tailscale0 proto kernel metric 256 pref medium

$ ip -6 route show table 52
fd7a:115c:a1e0::53 dev tailscale0 metric 1024 pref medium
fd7a:115c:a1e0::/48 dev tailscale0 metric 1024 pref medium

$ ip -6 route show table main
aaaa:bbbb:cccc:dddd::/64 dev eth0 proto kernel metric 256 pref medium
fd7a:115c:a1e0::aaaa:bbbb dev tailscale0 proto kernel metric 256 pref medium
fe80::/64 dev eth0 proto kernel metric 256 pref medium
default via fe80::1 dev eth0 metric 1024 pref medium
</code></pre><p>From what I could see, firing up Tailscale would add the following new rules to the routing table:</p>
<pre tabindex="0"><code>$ ip route show table all &gt; ts-off.txt
$ sudo tailscale up &amp;&amp; ip route show table all &gt; ts-on.txt &amp;&amp; sudo tailscale down
$ diff ts-off.txt ts-on.txt
0a1,2
&gt; 100.a.b.c dev tailscale0 table 52
&gt; 100.100.100.100 dev tailscale0 table 52
3a6
&gt; local 100.x.y.z dev tailscale0 table local proto kernel scope host src 100.x.y.z
12a16,17
&gt; fd7a:115c:a1e0::53 dev tailscale0 table 52 metric 1024 pref medium
&gt; fd7a:115c:a1e0::/48 dev tailscale0 table 52 metric 1024 pref medium
13a19
&gt; fd7a:115c:a1e0::aaaa:bbbb dev tailscale0 proto kernel metric 256 pref medium
17a24
&gt; local fd7a:115c:a1e0::aaaa:bbbb dev tailscale0 table local proto kernel metric 0 pref medium
</code></pre><p>I still have not figured out what is actually going on there, and a reproduction on a fresh server so far also wasn&rsquo;t successful. The problem is that packets are being sent with the wrong source IPv6, but that&rsquo;s just a symptom of the underlying cause.</p>
<p>Thankfully, my buddy Jub came up with the workaround to change the default route to use a fixed IPv6 source address - the correct one - and that solved the issue (by fixing the symptom):</p>
<pre tabindex="0"><code>ip -6 route replace default via fe80::1 dev eth0 src aaaa:bbbb:cccc:dddd::1
</code></pre><p>I put that on a new <code>post-up</code> line into the network setup in <code>/etc/network/interface.d/50-cloud-init.cfg</code></p>
<pre tabindex="0"><code>auto eth0:0
iface eth0:0 inet6 static
    address aaaa:bbbb:cccc:dddd::/64
    gateway fe80::1
    post-up route add -net :: netmask 0 gw fe80::1%eth0 || true
    post-up ip -6 route replace default via fe80::1 dev eth0 src aaaa:bbbb:cccc:dddd::1 || true
    pre-down route del -net :: netmask 0 gw fe80::1%eth0 || true
</code></pre><p>A reboot confirmed that this works as a <strong>workaround</strong>.</p>
<p>But as I mentioned, I still can&rsquo;t make any sense of the underlying issue. I found <a href="https://github.com/tailscale/tailscale/issues/17936">one open bug report in Tailscale&rsquo;s bug tracker</a> that sounded familiar, but it didn&rsquo;t fully match my situation. I also have to admit that my administration skills kinda get a bit fuzzy when it comes to full blown route analysis &amp; debugging - so should you have any ideas at all what is actually causing this behaviour here, please get in touch <a href="https://chaos.social/@foosel">on Mastodon</a> - I&rsquo;d love to see this mystery solved, but am out of my depth here 😅</p>
]]></content:encoded></item><item><title>What cool stuff did I do in 2025?</title><link>https://foosel.net/blog/2025-12-30-2025-retrospective/</link><pubDate>Tue, 30 Dec 2025 00:00:00 +0000</pubDate><guid>https://foosel.net/blog/2025-12-30-2025-retrospective/</guid><description>Highlighting some of my personal achievements of the past year to combat the negative vibes of this year</description><content:encoded><![CDATA[<p>Just like <a href="https://foosel.net/blog/2024-12-30-2024-retrospective/">last year</a> I&rsquo;ve decided to sit down today and write my very own retrospective. The year has been difficult in several ways, but whenever I took another look at last year&rsquo;s post it put a smile on my face, remembering the good things about life in general.</p>
<p>In the hope that this will continue to work, here we go with the same look at cool stuff that I did or experienced in 2025&hellip;</p>
<ol>
<li>
<p>I started the year with my by now traditional deep clean project, this time taking care of my hallway and the built-in closet. That also included recycling my first ever 3d printer with which everything OctoPrint started - a step I had to do for space reasons, but which still felt weird.</p>

   
   
   
   
   
   
   
   
   
   
   
   
   
   
           
           
   
           
               
               
           
   
           
               <blockquote class="toot-blockquote" cite="https://chaos.social@foosel/status/113760557884199400">
                   <div class="toot-header">
                       <a class="toot-profile" href="https://chaos.social/@foosel" rel="noopener">
                           <img
                               src="https://assets.chaos.social/accounts/avatars/000/235/099/original/a2e381e9aab4a693.png"
                               alt="Mastodon avatar for @foosel@chaos.social"
                               loading="lazy"
                           />
                       </a>
                       <div class="toot-author">
                           <a class="toot-author-name" href="https://chaos.social/@foosel" rel="noopener">Gina Häußge</a>
                           <a class="toot-author-handle" href="https://chaos.social/@foosel" rel="noopener">@foosel@chaos.social</a>
                       </div>
                   </div>
                   <p>Continuing my start-of-the-year clean-up project tradition (for last year&#39;s project see <a href="https://chaos.social/@foosel/111687404140972445" target="_blank" rel="nofollow noopener" translate="no"><span class="invisible">https://</span><span class="ellipsis">chaos.social/@foosel/111687404</span><span class="invisible">140972445</span></a>).</p><p>Today I tackled the hallway shelf and the built-in closet. Filled up several garbage bags once more. Ouch, my back 😅</p><p>I also decided to throw out my first 3d printer that was still stored in the closet, as much as it saddens me. It&#39;s no longer working &amp; it&#39;s taking up too much space for that. But I&#39;ll keep the front plate &amp; Pi 😊</p><p>No before pictures for you bw - too embarrassing 😬</p>
                   
                       
                           
                               
                           
                       
                           
                               
                           
                       
                           
                               
                           
                       
                       <div class="toot-img-grid-3">
                       
                           
                               
                               <style>
                                   .img-2140162f994f8b39e9b299983aa5a15a {
                                       aspect-ratio: 2494 / 3325;
                                   }
                               </style>
                               <img
                                   src="https://assets.chaos.social/media_attachments/files/113/760/522/317/649/813/original/ecbbde4c30707d16.jpg"
                                   alt="Hallway shelf with some games, bottles, COVID/health related stuff and storage for empties. To the left two stacked up chairs from the dinner table."
                                   class="toot-media-img img-2140162f994f8b39e9b299983aa5a15a"
                                   loading="lazy"
                               />
                           
                       
                           
                               
                               <style>
                                   .img-1f2ccf7901347e88915635318ef6e39f {
                                       aspect-ratio: 2494 / 3325;
                                   }
                               </style>
                               <img
                                   src="https://assets.chaos.social/media_attachments/files/113/760/522/696/338/037/original/b447ffca799bec41.jpg"
                                   alt="Built-in closet. Kitchen stuff, household and DIY things, some hung up jackets and bags, tools, airbrush stuff, foldable trolley"
                                   class="toot-media-img img-1f2ccf7901347e88915635318ef6e39f"
                                   loading="lazy"
                               />
                           
                       
                           
                               
                               <style>
                                   .img-a0a279b3a9a45d38c64cd3643ee9bd35 {
                                       aspect-ratio: 2494 / 3325;
                                   }
                               </style>
                               <img
                                   src="https://assets.chaos.social/media_attachments/files/113/760/523/545/501/778/original/4d91d23ecf22866e.jpg"
                                   alt="A slightly modded original Ultimaker built from laser cut wood"
                                   class="toot-media-img img-a0a279b3a9a45d38c64cd3643ee9bd35"
                                   loading="lazy"
                               />
                           
                       
                       </div>
                       
                       
                           
                           
                       
                           
                           
                       
                           
                           
                       
                   
                   
                   
                   <div class="toot-footer">
                       <a href="https://chaos.social/@foosel/113760557884199400" class="toot-date" rel="noopener">January 2, 2025, 20:05</a>&nbsp;<span class="pokey">(UTC)</span>
                   </div>
               </blockquote>
           
       
</li>
<li>
<p>I painted all of the (core) miniatures of my copy of <a href="https://boardgamegeek.com/boardgame/204472/sub-terra">&ldquo;Sub Terra&rdquo;</a>, making sure to also use a bit of UV colors for some special effects under black light.</p>
<p>
   
   
   
   
   
   
   
   
   
   
   
   
   
   
           
           
   
           
               
               
           
   
           
               <blockquote class="toot-blockquote" cite="https://chaos.social@foosel/status/113776631910946170">
                   <div class="toot-header">
                       <a class="toot-profile" href="https://chaos.social/@foosel" rel="noopener">
                           <img
                               src="https://assets.chaos.social/accounts/avatars/000/235/099/original/a2e381e9aab4a693.png"
                               alt="Mastodon avatar for @foosel@chaos.social"
                               loading="lazy"
                           />
                       </a>
                       <div class="toot-author">
                           <a class="toot-author-name" href="https://chaos.social/@foosel" rel="noopener">Gina Häußge</a>
                           <a class="toot-author-handle" href="https://chaos.social/@foosel" rel="noopener">@foosel@chaos.social</a>
                       </div>
                   </div>
                   <p>It&#39;s been years since I last painted some <a href="https://chaos.social/tags/miniatures" class="mention hashtag" rel="tag">#<span>miniatures</span></a>, and since then several <a href="https://chaos.social/tags/boardgames" class="mention hashtag" rel="tag">#<span>boardgames</span></a> have moved in that come with some. </p><p>As I find painting quite relaxing (it allows me to focus on something I&#39;m doing with my hands while making my brain shut up), I recently decided it was time to get back into it. I also wanted to try something new and got myself a set of Army Painter Speedpaints.</p><p>I started with the core game of Sub Terra yesterday and am quite happy with how things are looking now 😊</p>
                   
                       
                           
                               
                           
                       
                       <div class="toot-img-grid-1">
                       
                           
                               
                               <style>
                                   .img-ab02a3b002de4906f50098b1b4c9c9d3 {
                                       aspect-ratio: 3325 / 2494;
                                   }
                               </style>
                               <img
                                   src="https://assets.chaos.social/media_attachments/files/113/776/622/498/110/728/original/2591f48b6fa2d0a9.jpg"
                                   alt="Miniatures of the core game of Sub Terra, mounted on corks and painted."
                                   class="toot-media-img img-ab02a3b002de4906f50098b1b4c9c9d3"
                                   loading="lazy"
                               />
                           
                       
                       </div>
                       
                       
                           
                           
                       
                   
                   
                   
                   <div class="toot-footer">
                       <a href="https://chaos.social/@foosel/113776631910946170" class="toot-date" rel="noopener">January 5, 2025, 16:13</a>&nbsp;<span class="pokey">(UTC)</span>
                   </div>
               </blockquote>
           
       

   
   
   
   
   
   
   
   
   
   
   
   
   
   
           
           
   
           
               
               
           
   
           
               <blockquote class="toot-blockquote" cite="https://chaos.social@foosel/status/113776640997773679">
                   <div class="toot-header">
                       <a class="toot-profile" href="https://chaos.social/@foosel" rel="noopener">
                           <img
                               src="https://assets.chaos.social/accounts/avatars/000/235/099/original/a2e381e9aab4a693.png"
                               alt="Mastodon avatar for @foosel@chaos.social"
                               loading="lazy"
                           />
                       </a>
                       <div class="toot-author">
                           <a class="toot-author-name" href="https://chaos.social/@foosel" rel="noopener">Gina Häußge</a>
                           <a class="toot-author-handle" href="https://chaos.social/@foosel" rel="noopener">@foosel@chaos.social</a>
                       </div>
                   </div>
                   <p>And since this is a game that can be played in the darkness under UV light, I decided to add a little extra too...</p>
                   
                       
                           
                               
                           
                       
                       <div class="toot-img-grid-1">
                       
                           
                               
                               <style>
                                   .img-fd2de912d4fda6037db7d69986a94185 {
                                       aspect-ratio: 3325 / 2494;
                                   }
                               </style>
                               <img
                                   src="https://assets.chaos.social/media_attachments/files/113/776/635/697/100/256/original/5f3935e93f0e97d9.jpg"
                                   alt="The same shot under UV light, showing the monsters glowing green and the headlamps on the characters glowing white."
                                   class="toot-media-img img-fd2de912d4fda6037db7d69986a94185"
                                   loading="lazy"
                               />
                           
                       
                       </div>
                       
                       
                           
                           
                       
                   
                   
                   
                   <div class="toot-footer">
                       <a href="https://chaos.social/@foosel/113776640997773679" class="toot-date" rel="noopener">January 5, 2025, 16:16</a>&nbsp;<span class="pokey">(UTC)</span>
                   </div>
               </blockquote>
           
       </p>
</li>
<li>
<p>That also prompted me to finally kit up my painting stuff on a small cart that fits into the freshly cleaned up built-in closet.</p>
<p><img loading="lazy" src="/blog/2025-12-30-2025-retrospective/painting-cart.jpg"></p>
</li>
<li>
<p>As it is also becoming traditional by now, I spent some wonderfully calm days with my partner in the southern Black Forest again. We spent the days taking long walks/hikes through the snowy landscape, and the evenings playing through <a href="https://store.steampowered.com/app/2716400/The_Rise_of_the_Golden_Idol/">&ldquo;Rise of the Golden Idol&rdquo;</a> - quality time! 😊</p>

   
   
   
   
   
   
   
   
   
   
   
   
   
   
           
           
   
           
               
               
           
   
           
               <blockquote class="toot-blockquote" cite="https://chaos.social@foosel/status/113856741272685499">
                   <div class="toot-header">
                       <a class="toot-profile" href="https://chaos.social/@foosel" rel="noopener">
                           <img
                               src="https://assets.chaos.social/accounts/avatars/000/235/099/original/a2e381e9aab4a693.png"
                               alt="Mastodon avatar for @foosel@chaos.social"
                               loading="lazy"
                           />
                       </a>
                       <div class="toot-author">
                           <a class="toot-author-name" href="https://chaos.social/@foosel" rel="noopener">Gina Häußge</a>
                           <a class="toot-author-handle" href="https://chaos.social/@foosel" rel="noopener">@foosel@chaos.social</a>
                       </div>
                   </div>
                   <p>Spent a beautiful week with my partner of now five years in the southern Black Forest, to celebrate our anniversary by taking a short time out in nature 😊</p><p>While I&#39;m admittedly not too thrilled to return to work again tomorrow, this was the kind of reset I needed and it did a lot in topping up my energy levels 🥰</p><p>Apart from hiking and a wellness day in a spa, we also played through Rise of the Golden Idol in the evenings 😊 <a href="https://chaos.social/tags/relationshipGoals" class="mention hashtag" rel="tag">#<span>relationshipGoals</span></a></p>
                   
                       
                           
                               
                           
                       
                           
                               
                           
                       
                       <div class="toot-img-grid-2">
                       
                           
                               
                               <style>
                                   .img-e8a9968ce200f6d87eedf9332e6eb1dd {
                                       aspect-ratio: 3840 / 2160;
                                   }
                               </style>
                               <img
                                   src="https://assets.chaos.social/media_attachments/files/113/856/712/202/153/005/original/c3fb2bbd389c48f3.jpg"
                                   alt="Me posing next to the summit cross of the Belchen mountain. Behind me there&#39;s an ocean of clouds against a blue sky. The outlines of the Alps are visible in the far off distance."
                                   class="toot-media-img img-e8a9968ce200f6d87eedf9332e6eb1dd"
                                   loading="lazy"
                               />
                           
                       
                           
                               
                               <style>
                                   .img-658ca92d2db5d71b4cff2e65111ec25a {
                                       aspect-ratio: 3136 / 2356;
                                   }
                               </style>
                               <img
                                   src="https://assets.chaos.social/media_attachments/files/113/856/759/148/403/930/original/2ae0fa8e719e7b3b.jpg"
                                   alt="Selfie of me and my partner, both of us wearing sun glasses against the glaring sun and hats and winter clothes against the biting cold, smiling into the camera."
                                   class="toot-media-img img-658ca92d2db5d71b4cff2e65111ec25a"
                                   loading="lazy"
                               />
                           
                       
                       </div>
                       
                       
                           
                           
                       
                           
                           
                       
                   
                   
                   
                   <div class="toot-footer">
                       <a href="https://chaos.social/@foosel/113856741272685499" class="toot-date" rel="noopener">January 19, 2025, 19:46</a>&nbsp;<span class="pokey">(UTC)</span>
                   </div>
               </blockquote>
           
       
</li>
<li>
<p>Back from that, I finally found the time to frame the bits of my first 3d printer that I kept and hung them in my office - that way it&rsquo;s not fully gone but taking up way less space than it did before.</p>
<p><img loading="lazy" src="/blog/2025-12-30-2025-retrospective/ultimaker-framed.jpg"></p>
</li>
<li>
<p>At the end of January, I gave my first talk in front of a live audience since the start of the pandemic, at a local Women in Tech meetup. To say that I was incredibly nervous about this would be an understatement, but everything went smoothly once I got started 😊</p>

   
   
   
   
   
   
   
   
   
   
   
   
   
   
           
           
   
           
               
               
           
   
           
               <blockquote class="toot-blockquote" cite="https://chaos.social@foosel/status/113916461942021342">
                   <div class="toot-header">
                       <a class="toot-profile" href="https://chaos.social/@foosel" rel="noopener">
                           <img
                               src="https://assets.chaos.social/accounts/avatars/000/235/099/original/a2e381e9aab4a693.png"
                               alt="Mastodon avatar for @foosel@chaos.social"
                               loading="lazy"
                           />
                       </a>
                       <div class="toot-author">
                           <a class="toot-author-name" href="https://chaos.social/@foosel" rel="noopener">Gina Häußge</a>
                           <a class="toot-author-handle" href="https://chaos.social/@foosel" rel="noopener">@foosel@chaos.social</a>
                       </div>
                   </div>
                   <p>Another one for the collection to remind me that I in fact now CAN give talks without turning bright red thanks to crippling stagefright 💪 It was a long process ^^</p><p>Slides are now also up on foosel.net!</p><p>Thank you Christian for the picture!</p>
                   
                       
                           
                               
                           
                       
                       <div class="toot-img-grid-1">
                       
                           
                               
                               <style>
                                   .img-9ee7f9543b59d6a9e2f9d515b77ae905 {
                                       aspect-ratio: 1280 / 960;
                                   }
                               </style>
                               <img
                                   src="https://assets.chaos.social/media_attachments/files/113/916/453/436/582/228/original/3c52bc42e4fca356.jpeg"
                                   alt="Me standing on stage, giving a talk. I&#39;m wearing an OctoPrint shirt and a mask. Behind me there&#39;s a slide about me, with my avatar on the right and a short bio on the left."
                                   class="toot-media-img img-9ee7f9543b59d6a9e2f9d515b77ae905"
                                   loading="lazy"
                               />
                           
                       
                       </div>
                       
                       
                           
                           
                       
                   
                   
                   
                   <div class="toot-footer">
                       <a href="https://chaos.social/@foosel/113916461942021342" class="toot-date" rel="noopener">January 30, 2025, 08:54</a>&nbsp;<span class="pokey">(UTC)</span>
                   </div>
               </blockquote>
           
       
</li>
<li>
<p>Using my vinyl cutter I created some custom Mass Effect merch! Happy to say that the design indeed held up through several washes now - this thing has become my favourite hoodie and I&rsquo;m actually wearing it as I&rsquo;m typing these lines.</p>

   
   
   
   
   
   
   
   
   
   
   
   
   
   
           
           
   
           
               
               
           
   
           
               <blockquote class="toot-blockquote" cite="https://chaos.social@foosel/status/113934395775722840">
                   <div class="toot-header">
                       <a class="toot-profile" href="https://chaos.social/@foosel" rel="noopener">
                           <img
                               src="https://assets.chaos.social/accounts/avatars/000/235/099/original/a2e381e9aab4a693.png"
                               alt="Mastodon avatar for @foosel@chaos.social"
                               loading="lazy"
                           />
                       </a>
                       <div class="toot-author">
                           <a class="toot-author-name" href="https://chaos.social/@foosel" rel="noopener">Gina Häußge</a>
                           <a class="toot-author-handle" href="https://chaos.social/@foosel" rel="noopener">@foosel@chaos.social</a>
                       </div>
                   </div>
                   <p>Took care of a <a href="https://chaos.social/tags/project" class="mention hashtag" rel="tag">#<span>project</span></a> I had planned since before Christmas and turned a hoodie off mine into something a tad nerdier. This is now my favourite hoodie on the citadel!¹</p><p>¹ I just hope it survives the laundry, still my first steps with HTV 🙈</p><p><a href="https://chaos.social/tags/MassEffect" class="mention hashtag" rel="tag">#<span>MassEffect</span></a></p>
                   
                       
                           
                               
                           
                       
                           
                               
                           
                       
                       <div class="toot-img-grid-2">
                       
                           
                               
                               <style>
                                   .img-4e3ce2fb448135d84ce111396cf7f6cb {
                                       aspect-ratio: 2464 / 3280;
                                   }
                               </style>
                               <img
                                   src="https://assets.chaos.social/media_attachments/files/113/934/384/143/985/215/original/27840b314850673d.jpg"
                                   alt="Me wearing a dark grey hoodie jacket. On the breast of the jacket there&#39;s a print with a prominent &#34;N7&#34; logo from Mass Effect."
                                   class="toot-media-img img-4e3ce2fb448135d84ce111396cf7f6cb"
                                   loading="lazy"
                               />
                           
                       
                           
                               
                               <style>
                                   .img-6110cac4a5cb4aa7946aa7ff1508af95 {
                                       aspect-ratio: 2464 / 3280;
                                   }
                               </style>
                               <img
                                   src="https://assets.chaos.social/media_attachments/files/113/934/384/374/978/518/original/46d1900a8c8bee66.jpg"
                                   alt="Close-up of the print. It says &#34;Alliance Special Forces Training Program&#34; around a circle, and there&#39;s the alliance logo in the middle with the N7 logo underneath."
                                   class="toot-media-img img-6110cac4a5cb4aa7946aa7ff1508af95"
                                   loading="lazy"
                               />
                           
                       
                       </div>
                       
                       
                           
                           
                       
                           
                           
                       
                   
                   
                   
                   <div class="toot-footer">
                       <a href="https://chaos.social/@foosel/113934395775722840" class="toot-date" rel="noopener">February 2, 2025, 12:55</a>&nbsp;<span class="pokey">(UTC)</span>
                   </div>
               </blockquote>
           
       
<p>I also created two more shirts, and another hoodie for my partner:</p>

   
   
   
   
   
   
   
   
   
   
   
   
   
   
           
           
   
           
               
               
           
   
           
               <blockquote class="toot-blockquote" cite="https://chaos.social@foosel/status/114060040625283604">
                   <div class="toot-header">
                       <a class="toot-profile" href="https://chaos.social/@foosel" rel="noopener">
                           <img
                               src="https://assets.chaos.social/accounts/avatars/000/235/099/original/a2e381e9aab4a693.png"
                               alt="Mastodon avatar for @foosel@chaos.social"
                               loading="lazy"
                           />
                       </a>
                       <div class="toot-author">
                           <a class="toot-author-name" href="https://chaos.social/@foosel" rel="noopener">Gina Häußge</a>
                           <a class="toot-author-handle" href="https://chaos.social/@foosel" rel="noopener">@foosel@chaos.social</a>
                       </div>
                   </div>
                   <p>When the world becomes too much, I tend to make things...</p><p>Paragon shirt and Cerberus Normandy SR2 hoodie for my partner, Renegade shirt for me 😊</p>
                   
                       
                           
                               
                           
                       
                       <div class="toot-img-grid-1">
                       
                           
                               
                               <style>
                                   .img-e0819dd3b1a6ac77c7a36b9be3767d6d {
                                       aspect-ratio: 3325 / 2494;
                                   }
                               </style>
                               <img
                                   src="https://assets.chaos.social/media_attachments/files/114/060/028/822/084/093/original/f4ba7db7a17c4769.jpg"
                                   alt="Three freshly created Mass Effect textiles next to each other, a shirt with the Paragon logo in blue, a short with the Renegade logo in red, and a zipper hoodie with the Cerberus logo and &#34;Normandy SR2&#34; in white and orange."
                                   class="toot-media-img img-e0819dd3b1a6ac77c7a36b9be3767d6d"
                                   loading="lazy"
                               />
                           
                       
                       </div>
                       
                       
                           
                           
                       
                   
                   
                   
                   <div class="toot-footer">
                       <a href="https://chaos.social/@foosel/114060040625283604" class="toot-date" rel="noopener">February 24, 2025, 17:28</a>&nbsp;<span class="pokey">(UTC)</span>
                   </div>
               </blockquote>
           
       
</li>
<li>
<p>I finally played through <a href="https://store.steampowered.com/app/1562430/DREDGE/">&ldquo;Dredge&rdquo;</a> and had a blast doing so!</p>
</li>
<li>
<p>I bought a new vacuum and immediately fell into love with it, so much so that I printed a whole bunch of holders for its adapters 😂 Peak adulting ^^</p>

   
   
   
   
   
   
   
   
   
   
   
   
   
   
           
           
   
           
               
               
           
   
           
               <blockquote class="toot-blockquote" cite="https://chaos.social@foosel/status/114054064123727495">
                   <div class="toot-header">
                       <a class="toot-profile" href="https://chaos.social/@foosel" rel="noopener">
                           <img
                               src="https://assets.chaos.social/accounts/avatars/000/235/099/original/a2e381e9aab4a693.png"
                               alt="Mastodon avatar for @foosel@chaos.social"
                               loading="lazy"
                           />
                       </a>
                       <div class="toot-author">
                           <a class="toot-author-name" href="https://chaos.social/@foosel" rel="noopener">Gina Häußge</a>
                           <a class="toot-author-handle" href="https://chaos.social/@foosel" rel="noopener">@foosel@chaos.social</a>
                       </div>
                   </div>
                   <p>For the record, my new cordless vacuum arrived early last week and I&#39;m really in love with that thing 😅</p><p>And after printing some holders yesterday and today, I now have it hanging in the built-in closet with all the adapters right next to it, ready to go every second 👍😀</p>
                   
                       
                           
                               
                           
                       
                       <div class="toot-img-grid-1">
                       
                           
                               
                               <style>
                                   .img-df0731a8116ac8ea41f79cc101acf36f {
                                       aspect-ratio: 2494 / 3325;
                                   }
                               </style>
                               <img
                                   src="https://assets.chaos.social/media_attachments/files/114/054/053/943/862/467/original/f1ccf15c4469271e.jpg"
                                   alt="Picture of the vacuum and its adapters in the built-in closet. The vacuum is mounted right behind the door. On the inside of the door there are some 3d printed holders holding all the adapters and a hose."
                                   class="toot-media-img img-df0731a8116ac8ea41f79cc101acf36f"
                                   loading="lazy"
                               />
                           
                       
                       </div>
                       
                       
                           
                           
                       
                   
                   
                   
                   <div class="toot-footer">
                       <a href="https://chaos.social/@foosel/114054064123727495" class="toot-date" rel="noopener">February 23, 2025, 16:08</a>&nbsp;<span class="pokey">(UTC)</span>
                   </div>
               </blockquote>
           
       
</li>
<li>
<p>I started on my journey to reduce my footprint on US clouds:</p>
<ul>
<li>I switched from Google Drive to a NextCloud instance hosted at Hetzner in a german data center</li>
<li>After adding some more storage to my NAS I migrated all my pictures from Google Photos to a self-hosted <a href="https://immich.app/">immich</a> server</li>
<li>I switched my homelab&rsquo;s backup approach from Synology&rsquo;s proprietary stuff to <a href="https://www.borgbackup.org/">Borg</a>/<a href="https://torsion.org/borgmatic/">borgmatic</a></li>
</ul>
</li>
<li>
<p>I started playing <a href="https://tomkinpress.com/pages/ironsworn-starforged">&ldquo;Starforged&rdquo;</a> and for journaling my adventures built myself a traveler&rsquo;s notebook.</p>

    
    
    
    
    
    
    
    
    
    
    
    
    
    
            
            
    
            
                
                
            
    
            
                <blockquote class="toot-blockquote" cite="https://chaos.social@foosel/status/114191126595846444">
                    <div class="toot-header">
                        <a class="toot-profile" href="https://chaos.social/@foosel" rel="noopener">
                            <img
                                src="https://assets.chaos.social/accounts/avatars/000/235/099/original/a2e381e9aab4a693.png"
                                alt="Mastodon avatar for @foosel@chaos.social"
                                loading="lazy"
                            />
                        </a>
                        <div class="toot-author">
                            <a class="toot-author-name" href="https://chaos.social/@foosel" rel="noopener">Gina Häußge</a>
                            <a class="toot-author-handle" href="https://chaos.social/@foosel" rel="noopener">@foosel@chaos.social</a>
                        </div>
                    </div>
                    <p>Currently learning how to play <a href="https://chaos.social/tags/Starforged" class="mention hashtag" rel="tag">#<span>Starforged</span></a> and as I apparently enjoy the journaling part of it I just built myself a traveler&#39;s notebook 😊 Apart from two notebook inserts, this also has an insert for holding my character sheet and my assets, plus a move cheat sheet.</p><p><a href="https://chaos.social/tags/making" class="mention hashtag" rel="tag">#<span>making</span></a> <a href="https://chaos.social/tags/leatherwork" class="mention hashtag" rel="tag">#<span>leatherwork</span></a> <a href="https://chaos.social/tags/solo_rpg" class="mention hashtag" rel="tag">#<span>solo_rpg</span></a> <a href="https://chaos.social/tags/ttrpg" class="mention hashtag" rel="tag">#<span>ttrpg</span></a></p>
                    
                        
                            
                                
                            
                        
                            
                                
                            
                        
                            
                                
                            
                        
                            
                                
                            
                        
                        <div class="toot-img-grid-4">
                        
                            
                                
                                <style>
                                    .img-a9ae72f4ab1bfe0fc8ab99a68ccc106d {
                                        aspect-ratio: 3325 / 2494;
                                    }
                                </style>
                                <img
                                    src="https://assets.chaos.social/media_attachments/files/114/191/114/661/115/151/original/d6e0e0b6103216dc.jpg"
                                    alt="A traveler&#39;s notebook made from brown leather"
                                    class="toot-media-img img-a9ae72f4ab1bfe0fc8ab99a68ccc106d"
                                    loading="lazy"
                                />
                            
                        
                            
                                
                                <style>
                                    .img-a1d389bab9ae628c8b8db5618e153491 {
                                        aspect-ratio: 3325 / 2494;
                                    }
                                </style>
                                <img
                                    src="https://assets.chaos.social/media_attachments/files/114/191/115/578/435/489/original/9df115952591023d.jpg"
                                    alt="Craft paper insert with a big pocket for some asset cards."
                                    class="toot-media-img img-a1d389bab9ae628c8b8db5618e153491"
                                    loading="lazy"
                                />
                            
                        
                            
                                
                                <style>
                                    .img-85174fd57622e1a23341dbf1af6d0b1d {
                                        aspect-ratio: 3325 / 2494;
                                    }
                                </style>
                                <img
                                    src="https://assets.chaos.social/media_attachments/files/114/191/115/961/179/899/original/244bc59a564175c5.jpg"
                                    alt="Notebook insert with a part of my current run"
                                    class="toot-media-img img-85174fd57622e1a23341dbf1af6d0b1d"
                                    loading="lazy"
                                />
                            
                        
                            
                                
                                <style>
                                    .img-16ed1d771035e4691da4bdcd6e0b9ca4 {
                                        aspect-ratio: 3325 / 2494;
                                    }
                                </style>
                                <img
                                    src="https://assets.chaos.social/media_attachments/files/114/191/116/130/826/300/original/41c4f4587a06a332.jpg"
                                    alt="Craft paper insert holding my folded character sheet."
                                    class="toot-media-img img-16ed1d771035e4691da4bdcd6e0b9ca4"
                                    loading="lazy"
                                />
                            
                        
                        </div>
                        
                        
                            
                            
                        
                            
                            
                        
                            
                            
                        
                            
                            
                        
                    
                    
                    
                    <div class="toot-footer">
                        <a href="https://chaos.social/@foosel/114191126595846444" class="toot-date" rel="noopener">March 19, 2025, 21:05</a>&nbsp;<span class="pokey">(UTC)</span>
                    </div>
                </blockquote>
            
        
<p>This in turn led to me starting a Bullet Journal in an A6 sized self crafted traveler&rsquo;s notebook, and that&rsquo;s been used daily since. I&rsquo;ve been meaning to write a post about that, I really need to do this asap&hellip;</p>

    
    
    
    
    
    
    
    
    
    
    
    
    
    
            
            
    
            
                
                
            
    
            
                <blockquote class="toot-blockquote" cite="https://chaos.social@foosel/status/114241410760797146">
                    <div class="toot-header">
                        <a class="toot-profile" href="https://chaos.social/@foosel" rel="noopener">
                            <img
                                src="https://assets.chaos.social/accounts/avatars/000/235/099/original/a2e381e9aab4a693.png"
                                alt="Mastodon avatar for @foosel@chaos.social"
                                loading="lazy"
                            />
                        </a>
                        <div class="toot-author">
                            <a class="toot-author-name" href="https://chaos.social/@foosel" rel="noopener">Gina Häußge</a>
                            <a class="toot-author-handle" href="https://chaos.social/@foosel" rel="noopener">@foosel@chaos.social</a>
                        </div>
                    </div>
                    <p>After really enjoying using the traveler&#39;s notebook I built for <a href="https://chaos.social/tags/solorpg" class="mention hashtag" rel="tag">#<span>solorpg</span></a> use recently*, I&#39;ve just now built a smaller version for regular note taking. While I&#39;m a huge fan of <span class="h-card" translate="no"><a href="https://mas.to/@obsidian" class="u-url mention">@<span>obsidian</span></a></span>, not everything needs to be digital and I want to try some analog brainstorming and possibly journaling again.</p><p>* see <a href="https://chaos.social/@foosel/114191126595846444" target="_blank" rel="nofollow noopener" translate="no"><span class="invisible">https://</span><span class="ellipsis">chaos.social/@foosel/114191126</span><span class="invisible">595846444</span></a></p><p><a href="https://chaos.social/tags/leatherworking" class="mention hashtag" rel="tag">#<span>leatherworking</span></a></p>
                    
                        
                            
                                
                            
                        
                        <div class="toot-img-grid-1">
                        
                            
                                
                                <style>
                                    .img-e21e1a72443dbcd0ed66d2c4d3f9fb29 {
                                        aspect-ratio: 3325 / 2494;
                                    }
                                </style>
                                <img
                                    src="https://assets.chaos.social/media_attachments/files/114/241/401/327/898/403/original/8570c4aa2b82fefb.jpg"
                                    alt="A passport/A6 sized traveler&#39;s notebook sitting on a table"
                                    class="toot-media-img img-e21e1a72443dbcd0ed66d2c4d3f9fb29"
                                    loading="lazy"
                                />
                            
                        
                        </div>
                        
                        
                            
                            
                        
                    
                    
                    
                    <div class="toot-footer">
                        <a href="https://chaos.social/@foosel/114241410760797146" class="toot-date" rel="noopener">March 28, 2025, 18:13</a>&nbsp;<span class="pokey">(UTC)</span>
                    </div>
                </blockquote>
            
        
</li>
<li>
<p>On Easter my partner surprised me with a self-designed Lego Brickheads kit of us, which I had a blast building together with him.</p>
<p><img alt="Custom Brickheads of &ldquo;Gina&rdquo; and &ldquo;Dennis&rdquo; built and standing in front of the custom made box" loading="lazy" src="/blog/2025-12-30-2025-retrospective/brickheads.jpg"></p>
</li>
<li>
<p>I finally bound a book again, creating my first ever paperback!</p>

    
    
    
    
    
    
    
    
    
    
    
    
    
    
            
            
    
            
                
                
            
    
            
                <blockquote class="toot-blockquote" cite="https://chaos.social@foosel/status/114405026360844463">
                    <div class="toot-header">
                        <a class="toot-profile" href="https://chaos.social/@foosel" rel="noopener">
                            <img
                                src="https://assets.chaos.social/accounts/avatars/000/235/099/original/a2e381e9aab4a693.png"
                                alt="Mastodon avatar for @foosel@chaos.social"
                                loading="lazy"
                            />
                        </a>
                        <div class="toot-author">
                            <a class="toot-author-name" href="https://chaos.social/@foosel" rel="noopener">Gina Häußge</a>
                            <a class="toot-author-handle" href="https://chaos.social/@foosel" rel="noopener">@foosel@chaos.social</a>
                        </div>
                    </div>
                    <p>I haven&#39;t bound a book in a while but wanted to turn my copy of The Perilous Void into something physical for use on my <a href="https://chaos.social/tags/Starforged" class="mention hashtag" rel="tag">#<span>Starforged</span></a> adventure. Originally I planned to go with ring binding, but the printout didn&#39;t leave enough space for that so instead I went for a double fan bound paperback. Not perfect, but I&#39;m still quite proud of it 😊</p><p><a href="https://chaos.social/tags/solorpg" class="mention hashtag" rel="tag">#<span>solorpg</span></a> <a href="https://chaos.social/tags/ttrpg" class="mention hashtag" rel="tag">#<span>ttrpg</span></a> <a href="https://chaos.social/tags/bookbinding" class="mention hashtag" rel="tag">#<span>bookbinding</span></a></p>
                    
                        
                            
                                
                            
                        
                            
                                
                            
                        
                            
                                
                            
                        
                            
                                
                            
                        
                        <div class="toot-img-grid-4">
                        
                            
                                
                                <style>
                                    .img-1014eb72eadb421cca69e3e3e3da7d85 {
                                        aspect-ratio: 3325 / 2494;
                                    }
                                </style>
                                <img
                                    src="https://assets.chaos.social/media_attachments/files/114/405/018/114/577/140/original/8b51b1fd7255b7e5.jpg"
                                    alt="The finished paperback lying on a table."
                                    class="toot-media-img img-1014eb72eadb421cca69e3e3e3da7d85"
                                    loading="lazy"
                                />
                            
                        
                            
                                
                                <style>
                                    .img-7ab41050e723ad9a91b2535ca1cb696f {
                                        aspect-ratio: 3325 / 2494;
                                    }
                                </style>
                                <img
                                    src="https://assets.chaos.social/media_attachments/files/114/405/018/350/375/775/original/d7e586379f0a1814.jpg"
                                    alt="Closer look at the spine of the finished paperback."
                                    class="toot-media-img img-7ab41050e723ad9a91b2535ca1cb696f"
                                    loading="lazy"
                                />
                            
                        
                            
                                
                                <style>
                                    .img-9c593f2bb8314323fa2a6587c4a49d67 {
                                        aspect-ratio: 3325 / 2494;
                                    }
                                </style>
                                <img
                                    src="https://assets.chaos.social/media_attachments/files/114/405/018/610/572/487/original/f48dfcc927685233.jpg"
                                    alt="The book held open by my hand."
                                    class="toot-media-img img-9c593f2bb8314323fa2a6587c4a49d67"
                                    loading="lazy"
                                />
                            
                        
                            
                                
                                <style>
                                    .img-e238c0602e576a229f35666035911fb0 {
                                        aspect-ratio: 3325 / 2494;
                                    }
                                </style>
                                <img
                                    src="https://assets.chaos.social/media_attachments/files/114/405/018/778/405/114/original/5eb69439ac71f3cb.jpg"
                                    alt="Close-up of the bound spine while held open."
                                    class="toot-media-img img-e238c0602e576a229f35666035911fb0"
                                    loading="lazy"
                                />
                            
                        
                        </div>
                        
                        
                            
                            
                        
                            
                            
                        
                            
                            
                        
                            
                            
                        
                    
                    
                    
                    <div class="toot-footer">
                        <a href="https://chaos.social/@foosel/114405026360844463" class="toot-date" rel="noopener">April 26, 2025, 15:42</a>&nbsp;<span class="pokey">(UTC)</span>
                    </div>
                </blockquote>
            
        
</li>
<li>
<p>I upgraded my Framework 13 Intel 11th gen to a new AMD Ryzen AI 300 mainboard. It&rsquo;s my daily driver that I use for basically everything, and so I also put all the RAM in there that it supported: 96GB, which back then cost me 180€. Given the RAM prices now (the same kit now is getting sold for 900€), I am incredibly happy about this foresight 😬</p>
<p><img alt="Screenshot of the System Information of the new laptop. Device name: mariner; OS: Fedora Linux 42 (Workstation Edition); Hardware Model: Framework Laptop 13 AMD Ryzen AI 300 Series; Processor: AMD Ryzen HX370 w/ Radeon 890M x 24; Memory: 96GiB; Disk Capacity: 2.0 TB" loading="lazy" src="/blog/2025-12-30-2025-retrospective/framework-upgrade.png"></p>
</li>
<li>
<p>During an evening walk through my partner&rsquo;s home town with him I made an incredible find in a &ldquo;book closet&rdquo;: A book about home computers from the mid 1980s.</p>

    
    
    
    
    
    
    
    
    
    
    
    
    
    
            
            
    
            
                
                
            
    
            
                <blockquote class="toot-blockquote" cite="https://chaos.social@foosel/status/114507595287597953">
                    <div class="toot-header">
                        <a class="toot-profile" href="https://chaos.social/@foosel" rel="noopener">
                            <img
                                src="https://assets.chaos.social/accounts/avatars/000/235/099/original/a2e381e9aab4a693.png"
                                alt="Mastodon avatar for @foosel@chaos.social"
                                loading="lazy"
                            />
                        </a>
                        <div class="toot-author">
                            <a class="toot-author-name" href="https://chaos.social/@foosel" rel="noopener">Gina Häußge</a>
                            <a class="toot-author-handle" href="https://chaos.social/@foosel" rel="noopener">@foosel@chaos.social</a>
                        </div>
                    </div>
                    <p>Auf dem Abendspaziergang gerade dieses Kleinod in einem Bücherschrank gefunden und ich bin hin und weg. Das hätte ich gerne als Kind gehabt! Geht richtig tief in den Erklärungen und ist finde ich sehr schön aufbereitet. Ich bin gerade total glücklich über diesen Schatz und werde es in Ehren halten!</p><p>Und ich glaub ich gehe Mal auf die Jagd nach den anderen Teilen 😅</p><p>Ich hab noch keine Jahreszahl gefunden, aber die PLZ des Verlags ist noch vierstellig, also wohl vor 1993.</p><p>*edit* Wohl ~1985 😀</p>
                    
                        
                            
                                
                            
                        
                            
                                
                            
                        
                            
                                
                            
                        
                            
                                
                            
                        
                        <div class="toot-img-grid-4">
                        
                            
                                
                                <style>
                                    .img-0fb2a69fb3c86875c64098675b71e900 {
                                        aspect-ratio: 2494 / 3325;
                                    }
                                </style>
                                <img
                                    src="https://assets.chaos.social/media_attachments/files/114/507/569/798/910/952/original/078291aed7edddef.jpg"
                                    alt="Cover von &#34;Schreibers Schlüssel zur Computerwelt: Heimcomputer&#34;. Das Cover zeigt eine grafische Darstellung eines alten Mikrocomputers."
                                    class="toot-media-img img-0fb2a69fb3c86875c64098675b71e900"
                                    loading="lazy"
                                />
                            
                        
                            
                                
                                <style>
                                    .img-29a77f36704a5917e699b324a425af9d {
                                        aspect-ratio: 3325 / 2494;
                                    }
                                </style>
                                <img
                                    src="https://assets.chaos.social/media_attachments/files/114/507/571/809/218/163/original/721bada242237073.jpg"
                                    alt="Doppelseite des Buches, &#34;Was ist das - ein Computer?&#34;.
    
    Diverse Textboxen und Zeichnungen zeigen und beschreiben verschiedene Bestandteile eines Mikrocomputers."
                                    class="toot-media-img img-29a77f36704a5917e699b324a425af9d"
                                    loading="lazy"
                                />
                            
                        
                            
                                
                                <style>
                                    .img-073a4f9df27857a95fee220e7b125786 {
                                        aspect-ratio: 3325 / 2494;
                                    }
                                </style>
                                <img
                                    src="https://assets.chaos.social/media_attachments/files/114/507/573/794/942/030/original/3bf504eb19bb6b5f.jpg"
                                    alt="Doppelseite im Buch, &#34;Die Sache mit den Bits&#34;. In verschiedenen Textboxen aufgelockert mit Grafiken werden Binärkodierung und -operationen erläutert."
                                    class="toot-media-img img-073a4f9df27857a95fee220e7b125786"
                                    loading="lazy"
                                />
                            
                        
                            
                                
                                <style>
                                    .img-2b780bf2329c0d129e2da27cf75c0aed {
                                        aspect-ratio: 2494 / 3325;
                                    }
                                </style>
                                <img
                                    src="https://assets.chaos.social/media_attachments/files/114/507/574/048/329/660/original/81dcc314d4a2d0fa.jpg"
                                    alt="Die Rückseite des Buches zeigt die Teile der Reihe: 
    
    - Heimcomputer 
    - BTX für jedermann
    - BASIC
    - Taschenrechner ganz einfach
    - Daten im Haushalt
    - Heimcomputer Lexikon
    "
                                    class="toot-media-img img-2b780bf2329c0d129e2da27cf75c0aed"
                                    loading="lazy"
                                />
                            
                        
                        </div>
                        
                        
                            
                            
                        
                            
                            
                        
                            
                            
                        
                            
                            
                        
                    
                    
                    
                    <div class="toot-footer">
                        <a href="https://chaos.social/@foosel/114507595287597953" class="toot-date" rel="noopener">May 14, 2025, 18:27</a>&nbsp;<span class="pokey">(UTC)</span>
                    </div>
                </blockquote>
            
        
<p>The rest of the evening was spent reading through it quite excited, and hunting down some other ones from the series on ebay 😂</p>
</li>
<li>
<p>I went to PyCon Italia - my first conference since the start of the pandemic - and not only went climbing with some fellow Pythonistas but also met Jacopo, who&rsquo;s been helping me a lot with OctoPrint&rsquo;s security!</p>

    
    
    
    
    
    
    
    
    
    
    
    
    
    
            
            
    
            
                
                
            
    
            
                <blockquote class="toot-blockquote" cite="https://chaos.social@foosel/status/114602174901579648">
                    <div class="toot-header">
                        <a class="toot-profile" href="https://chaos.social/@foosel" rel="noopener">
                            <img
                                src="https://assets.chaos.social/accounts/avatars/000/235/099/original/a2e381e9aab4a693.png"
                                alt="Mastodon avatar for @foosel@chaos.social"
                                loading="lazy"
                            />
                        </a>
                        <div class="toot-author">
                            <a class="toot-author-name" href="https://chaos.social/@foosel" rel="noopener">Gina Häußge</a>
                            <a class="toot-author-handle" href="https://chaos.social/@foosel" rel="noopener">@foosel@chaos.social</a>
                        </div>
                    </div>
                    <p>For the past few days at <span class="h-card" translate="no"><a href="https://social.python.it/@pycon" class="u-url mention">@<span>pycon</span></a></span> I got to hang out with Jacopo Tediosi, who has helped me a lot in improving OctoPrint&#39;s security over the past year. It&#39;s always great when something like this happens!</p><p><a href="https://chaos.social/tags/pyconit2025" class="mention hashtag" rel="tag">#<span>pyconit2025</span></a></p>
                    
                        
                            
                                
                            
                        
                        <div class="toot-img-grid-1">
                        
                            
                                
                                <style>
                                    .img-be2790a1b56cb8f4c0dbb5ad92362df8 {
                                        aspect-ratio: 3325 / 2494;
                                    }
                                </style>
                                <img
                                    src="https://assets.chaos.social/media_attachments/files/114/602/167/262/664/725/original/91ff2e253d45bda4.jpg"
                                    alt="Me and Jacopo are posing in front of the photo wall at PyCon. We are both smiling."
                                    class="toot-media-img img-be2790a1b56cb8f4c0dbb5ad92362df8"
                                    loading="lazy"
                                />
                            
                        
                        </div>
                        
                        
                            
                            
                        
                    
                    
                    
                    <div class="toot-footer">
                        <a href="https://chaos.social/@foosel/114602174901579648" class="toot-date" rel="noopener">May 31, 2025, 11:20</a>&nbsp;<span class="pokey">(UTC)</span>
                    </div>
                </blockquote>
            
        
<p>I&rsquo;ve already got my ticket for PyCon Italia 2026 😊</p>
</li>
<li>
<p>The refactoring work on OctoPrint since late 2024 finally paid out and I was able to print from the in progress Moonraker Connector Plugin for the first time.</p>

    
    
    
    
    
    
    
    
    
    
    
    
    
    
            
            
    
            
                
                
            
    
            
                <blockquote class="toot-blockquote" cite="https://chaos.social@foosel/status/114788854495540415">
                    <div class="toot-header">
                        <a class="toot-profile" href="https://chaos.social/@foosel" rel="noopener">
                            <img
                                src="https://assets.chaos.social/accounts/avatars/000/235/099/original/a2e381e9aab4a693.png"
                                alt="Mastodon avatar for @foosel@chaos.social"
                                loading="lazy"
                            />
                        </a>
                        <div class="toot-author">
                            <a class="toot-author-name" href="https://chaos.social/@foosel" rel="noopener">Gina Häußge</a>
                            <a class="toot-author-handle" href="https://chaos.social/@foosel" rel="noopener">@foosel@chaos.social</a>
                        </div>
                    </div>
                    <p>Have been spending the week so far working on a plugin for <a href="https://chaos.social/tags/OctoPrint" class="mention hashtag" rel="tag">#<span>OctoPrint</span></a>&#39;s new comm layer that will be part of 1.12.0 &amp; as of earlier today I&#39;m able to print on Moonraker enabled Klipper based printers😁</p><p>This went faster than expected thanks to all that refactoring that I started shortly before x-mas, and I&#39;m incredibly happy that the architecture approach I had worked so well &amp; actually makes adding support for a different connection type this easy!</p><p>Plenty of bugs to fix ofc, but it&#39;s ALIVE!</p>
                    
                        
                            
                                
                            
                        
                        <div class="toot-img-grid-1">
                        
                            
                                
                                <style>
                                    .img-1554a4a14eb0da099d02431efc1dbec7 {
                                        aspect-ratio: 462 / 670;
                                    }
                                </style>
                                <img
                                    src="https://assets.chaos.social/media_attachments/files/114/788/824/233/589/733/original/3cf9d23d25c0c92a.png"
                                    alt="Screenshot of OctoPrint&#39;s state panel, showing a print from &#34;SD&#34; in progress. The progress bar sits at 34%."
                                    class="toot-media-img img-1554a4a14eb0da099d02431efc1dbec7"
                                    loading="lazy"
                                />
                            
                        
                        </div>
                        
                        
                            
                            
                        
                    
                    
                    
                    <div class="toot-footer">
                        <a href="https://chaos.social/@foosel/114788854495540415" class="toot-date" rel="noopener">July 3, 2025, 10:35</a>&nbsp;<span class="pokey">(UTC)</span>
                    </div>
                </blockquote>
            
        
</li>
<li>
<p>I went to EuroPython in Prague again, and this time even gave a talk! Of course, support plushy Timothy was there too 😊</p>
<p><img alt="Me giving a talk at EuroPython 2025. Support plush donkey Timothy is sitting at the front of the podium." loading="lazy" src="/blog/2025-12-30-2025-retrospective/europython-talk.jpg"></p>
</li>
<li>
<p>In August I went to <a href="">WHY2025</a> - my first camp in the Netherlands since 2009. We took the <a href="https://datagnome.de">Datenzwerge</a> with us of course and deployed all 10 of them across the camp grounds. It was some very intense but awesome days!</p>

    
    
    
    
    
    
    
    
    
    
    
    
    
    
            
            
    
            
                
                
            
    
            
                <blockquote class="toot-blockquote" cite="https://chaos.social@foosel/status/115038186227796448">
                    <div class="toot-header">
                        <a class="toot-profile" href="https://chaos.social/@foosel" rel="noopener">
                            <img
                                src="https://assets.chaos.social/accounts/avatars/000/235/099/original/a2e381e9aab4a693.png"
                                alt="Mastodon avatar for @foosel@chaos.social"
                                loading="lazy"
                            />
                        </a>
                        <div class="toot-author">
                            <a class="toot-author-name" href="https://chaos.social/@foosel" rel="noopener">Gina Häußge</a>
                            <a class="toot-author-handle" href="https://chaos.social/@foosel" rel="noopener">@foosel@chaos.social</a>
                        </div>
                    </div>
                    <p>Some pictures from <a href="https://chaos.social/tags/why2025" class="mention hashtag" rel="tag">#<span>why2025</span></a> ...</p>
                    
                        
                            
                                
                            
                        
                            
                                
                            
                        
                            
                                
                            
                        
                            
                                
                            
                        
                        <div class="toot-img-grid-4">
                        
                            
                                
                                <style>
                                    .img-10367abd34aea8c2f4f76c3757a5b55a {
                                        aspect-ratio: 3324 / 2496;
                                    }
                                </style>
                                <img
                                    src="https://assets.chaos.social/media_attachments/files/115/038/180/213/881/581/original/94a0f7c4db4049fd.jpg"
                                    alt="Led lights on the dark"
                                    class="toot-media-img img-10367abd34aea8c2f4f76c3757a5b55a"
                                    loading="lazy"
                                />
                            
                        
                            
                                
                                <style>
                                    .img-a9b6c0c3755ab94dede082f9de5d8b41 {
                                        aspect-ratio: 2496 / 3324;
                                    }
                                </style>
                                <img
                                    src="https://assets.chaos.social/media_attachments/files/115/038/180/537/550/259/original/4a17cfb63731a11c.jpg"
                                    alt="A led curtain"
                                    class="toot-media-img img-a9b6c0c3755ab94dede082f9de5d8b41"
                                    loading="lazy"
                                />
                            
                        
                            
                                
                                <style>
                                    .img-1028791c8ef368750a13a987f7bd84d4 {
                                        aspect-ratio: 3324 / 2496;
                                    }
                                </style>
                                <img
                                    src="https://assets.chaos.social/media_attachments/files/115/038/180/728/189/732/original/bbd60899a71a89b6.jpg"
                                    alt="Colourful led bulbs on the side of a tent, in the dark"
                                    class="toot-media-img img-1028791c8ef368750a13a987f7bd84d4"
                                    loading="lazy"
                                />
                            
                        
                            
                                
                                <style>
                                    .img-214cc7c6ba9eb7b9d299e487a44587f0 {
                                        aspect-ratio: 3324 / 2496;
                                    }
                                </style>
                                <img
                                    src="https://assets.chaos.social/media_attachments/files/115/038/180/955/275/234/original/693e2ec562190989.jpg"
                                    alt="Some high grass in the dark, lighted in deep green. In front the shadow of a bicycle"
                                    class="toot-media-img img-214cc7c6ba9eb7b9d299e487a44587f0"
                                    loading="lazy"
                                />
                            
                        
                        </div>
                        
                        
                            
                            
                        
                            
                            
                        
                            
                            
                        
                            
                            
                        
                    
                    
                    
                    <div class="toot-footer">
                        <a href="https://chaos.social/@foosel/115038186227796448" class="toot-date" rel="noopener">August 16, 2025, 11:23</a>&nbsp;<span class="pokey">(UTC)</span>
                    </div>
                </blockquote>
            
        
</li>
<li>
<p>While at WHY I met <a href="https://eheidi.dev/">Erika Heidi</a> whom I so far only had known virtually through GitHub Stars. We nerded out SO MUCH about tech, bouldering and 3d printing, it was a ton of fun :D</p>
<p><img alt="Me and Erika taking a selfie at WHY" loading="lazy" src="/blog/2025-12-30-2025-retrospective/why-erika.jpg"></p>
</li>
<li>
<p>For my partner&rsquo;s birthday, I created yet another custom shirt utilizing the vinyl cutter and some HTV, this time with a logo from Dead Space. The design was based on in-game graphics, and weeding it was a bit of a nightmare 😂</p>
<p><img alt="Custom logo of the &ldquo;Ishimura Planetside Team&rdquo; from Dead Space" loading="lazy" src="/blog/2025-12-30-2025-retrospective/vinyl-dead-space.jpg"></p>
</li>
<li>
<p>I also got my partner an OLED Steamdeck for his birthday, and therefore also had to create <a href="https://foosel.net/blog/2023-01-19-custom-steamdeck-buttons/">new custom buttons</a>. While at that, I also finally created some green ones for myself, and also created a whole new mold for the digi pad. Now my Steamdeck is also nicely customized 😊</p>

    
    
    
    
    
    
    
    
    
    
    
    
    
    
            
            
    
            
                
                
            
    
            
                <blockquote class="toot-blockquote" cite="https://chaos.social@foosel/status/115119707999875452">
                    <div class="toot-header">
                        <a class="toot-profile" href="https://chaos.social/@foosel" rel="noopener">
                            <img
                                src="https://assets.chaos.social/accounts/avatars/000/235/099/original/a2e381e9aab4a693.png"
                                alt="Mastodon avatar for @foosel@chaos.social"
                                loading="lazy"
                            />
                        </a>
                        <div class="toot-author">
                            <a class="toot-author-name" href="https://chaos.social/@foosel" rel="noopener">Gina Häußge</a>
                            <a class="toot-author-handle" href="https://chaos.social/@foosel" rel="noopener">@foosel@chaos.social</a>
                        </div>
                    </div>
                    <p>Finished my custom buttons and d-pad and put them into my <a href="https://chaos.social/tags/SteamDeck" class="mention hashtag" rel="tag">#<span>SteamDeck</span></a> 😊</p><p>Had to sand the d-pad on the center spike as otherwise it didn&#39;t work well, but I think now things are fine. Almost ran out on the rub-on letters too, my last X is now on there 😅 I&#39;m thinking about getting a custom decal sheet made.</p>
                    
                        
                            
                                
                            
                        
                            
                                
                            
                        
                            
                                
                            
                        
                        <div class="toot-img-grid-3">
                        
                            
                                
                                <style>
                                    .img-9c1761852e5a08043714197a6e8c753e {
                                        aspect-ratio: 3325 / 2494;
                                    }
                                </style>
                                <img
                                    src="https://assets.chaos.social/media_attachments/files/115/119/674/590/688/121/original/6fe2af457433863e.jpg"
                                    alt="Close-up of custom made green action buttons in a Steamdeck"
                                    class="toot-media-img img-9c1761852e5a08043714197a6e8c753e"
                                    loading="lazy"
                                />
                            
                        
                            
                                
                                <style>
                                    .img-6244361b50bbd64a8d8846c6e6c366fc {
                                        aspect-ratio: 3325 / 2494;
                                    }
                                </style>
                                <img
                                    src="https://assets.chaos.social/media_attachments/files/115/119/674/826/278/540/original/1fc6274f9ea0bfe4.jpg"
                                    alt="Close-up of a custom made green d-pad in a Steamdeck"
                                    class="toot-media-img img-6244361b50bbd64a8d8846c6e6c366fc"
                                    loading="lazy"
                                />
                            
                        
                            
                                
                                <style>
                                    .img-ac822a4d94ed5804f9ee415f1ea4b5dd {
                                        aspect-ratio: 3325 / 2494;
                                    }
                                </style>
                                <img
                                    src="https://assets.chaos.social/media_attachments/files/115/119/675/034/662/845/original/476eeab72e1072fb.jpg"
                                    alt="Steamdeck OLED with custom made green d-pad and action buttons."
                                    class="toot-media-img img-ac822a4d94ed5804f9ee415f1ea4b5dd"
                                    loading="lazy"
                                />
                            
                        
                        </div>
                        
                        
                            
                            
                        
                            
                            
                        
                            
                            
                        
                    
                    
                    
                    <div class="toot-footer">
                        <a href="https://chaos.social/@foosel/115119707999875452" class="toot-date" rel="noopener">August 30, 2025, 20:55</a>&nbsp;<span class="pokey">(UTC)</span>
                    </div>
                </blockquote>
            
        
</li>
<li>
<p>During this time, I also played through my personal Game of the Year, <a href="https://store.steampowered.com/app/1170570/The_Drifter/">&ldquo;The Drifter&rdquo;</a>. That also spawned a whole Point-and-Click-Adventure phase for me again 😂</p>
</li>
<li>
<p>Over the course of October I took part in another month long daily art challenge, &ldquo;Cosytober&rdquo;. I once again painted 31 pixelart pictures, this time limiting myself to 64x64px and the ENDESGA77 palette.</p>

    
    
    
    
    
    
    
    
    
    
    
    
    
    
            
            
    
            
                
                
            
    
            
                <blockquote class="toot-blockquote" cite="https://chaos.social@foosel/status/115468514303275578">
                    <div class="toot-header">
                        <a class="toot-profile" href="https://chaos.social/@foosel" rel="noopener">
                            <img
                                src="https://assets.chaos.social/accounts/avatars/000/235/099/original/a2e381e9aab4a693.png"
                                alt="Mastodon avatar for @foosel@chaos.social"
                                loading="lazy"
                            />
                        </a>
                        <div class="toot-author">
                            <a class="toot-author-name" href="https://chaos.social/@foosel" rel="noopener">Gina Häußge</a>
                            <a class="toot-author-handle" href="https://chaos.social/@foosel" rel="noopener">@foosel@chaos.social</a>
                        </div>
                    </div>
                    <p>And that&#39;s a wrap on <a href="https://chaos.social/tags/cosytober2025" class="mention hashtag" rel="tag">#<span>cosytober2025</span></a> for me! </p><p>I&#39;m very proud that despite everything I managed to do one every single day throughout this month! And I&#39;m also very happy with the drawings, I think I improved a lot, and am really enjoying doing pixelart!</p><p>Like last year, I compiled all of the drawings of the month into a wallpaper and you can find that below.</p><p>Do you have any personal favourites? I&#39;d love to know!</p><p>cc <span class="h-card" translate="no"><a href="https://mastodon.rosiesosocial.com/@RosieSoCosy" class="u-url mention">@<span>RosieSoCosy</span></a></span></p>
                    
                        
                            
                                
                            
                        
                        <div class="toot-img-grid-1">
                        
                            
                                
                                <style>
                                    .img-4c3aed289b4b16d1602b26b5c15460c3 {
                                        aspect-ratio: 1920 / 1080;
                                    }
                                </style>
                                <img
                                    src="https://assets.chaos.social/media_attachments/files/115/468/507/468/004/612/original/770a0dc9b690b71b.png"
                                    alt="Wallpaper of all 31 pixelart drawings of &#34;foosel&#39;s Cosytober&#34;"
                                    class="toot-media-img img-4c3aed289b4b16d1602b26b5c15460c3"
                                    loading="lazy"
                                />
                            
                        
                        </div>
                        
                        
                            
                            
                        
                    
                    
                    
                    <div class="toot-footer">
                        <a href="https://chaos.social/@foosel/115468514303275578" class="toot-date" rel="noopener">October 31, 2025, 11:21</a>&nbsp;<span class="pokey">(UTC)</span>
                    </div>
                </blockquote>
            
        
<p>I also started taking part in the <a href="https://mastodon.art/@pixel_dailies">Pixelart Dailies</a> during October and kept that going well into November, when I ran out of energy.</p>
</li>
<li>
<p>During Cosytober I also got myself a used Surface Go 2 tablet off of ebay and turned it into a pen enabled Linux based tablet, perfect for working on pixelart from the couch 😁</p>

    
    
    
    
    
    
    
    
    
    
    
    
    
    
            
            
    
            
                
                
            
    
            
                <blockquote class="toot-blockquote" cite="https://chaos.social@foosel/status/115379554054628521">
                    <div class="toot-header">
                        <a class="toot-profile" href="https://chaos.social/@foosel" rel="noopener">
                            <img
                                src="https://assets.chaos.social/accounts/avatars/000/235/099/original/a2e381e9aab4a693.png"
                                alt="Mastodon avatar for @foosel@chaos.social"
                                loading="lazy"
                            />
                        </a>
                        <div class="toot-author">
                            <a class="toot-author-name" href="https://chaos.social/@foosel" rel="noopener">Gina Häußge</a>
                            <a class="toot-author-handle" href="https://chaos.social/@foosel" rel="noopener">@foosel@chaos.social</a>
                        </div>
                    </div>
                    <p>I&#39;ve been back to Linux on the desktop for several years now. For the longest time I was wondering how Linux on a tablet would work. So say Hi to this Surface Go 2 that I bought used on eBay for cheap and liberated from the Windows 10 that it came with. 😬 <a href="https://chaos.social/tags/EndOf10" class="mention hashtag" rel="tag">#<span>EndOf10</span></a></p><p>It now runs <a href="https://chaos.social/tags/Bluefin" class="mention hashtag" rel="tag">#<span>Bluefin</span></a>, and pretty much everything worked out of the box. The only a tad annoying thing is entering my password via the touchscreen. Amazing tablet for <a href="https://chaos.social/tags/Aseprite" class="mention hashtag" rel="tag">#<span>Aseprite</span></a>! 😊</p><p>More detailed blog post when I&#39;m back on my feet 🤧</p>
                    
                        
                            
                                
                            
                        
                            
                                
                            
                        
                        <div class="toot-img-grid-2">
                        
                            
                                
                                <style>
                                    .img-7243cce210826a2a8b7d5ebd19c04d72 {
                                        aspect-ratio: 3325 / 2494;
                                    }
                                </style>
                                <img
                                    src="https://assets.chaos.social/media_attachments/files/115/379/537/657/332/407/original/df72bb4af9500e22.jpg"
                                    alt="Picture of the Surface Go 2 running Linux. It shows the system info. The system&#39;s name is &#34;Tendi&#34;"
                                    class="toot-media-img img-7243cce210826a2a8b7d5ebd19c04d72"
                                    loading="lazy"
                                />
                            
                        
                            
                                
                                <style>
                                    .img-9737e49d03ccfd516120432aecb574cf {
                                        aspect-ratio: 3325 / 2494;
                                    }
                                </style>
                                <img
                                    src="https://assets.chaos.social/media_attachments/files/115/379/538/647/494/758/original/9bebf61b1437e836.jpg"
                                    alt="Picture of the tablet running Aseprite with a pixelart drawing of a squirrel currently open."
                                    class="toot-media-img img-9737e49d03ccfd516120432aecb574cf"
                                    loading="lazy"
                                />
                            
                        
                        </div>
                        
                        
                            
                            
                        
                            
                            
                        
                    
                    
                    
                    <div class="toot-footer">
                        <a href="https://chaos.social/@foosel/115379554054628521" class="toot-date" rel="noopener">October 15, 2025, 18:17</a>&nbsp;<span class="pokey">(UTC)</span>
                    </div>
                </blockquote>
            
        
<p>And yes, I know, I still owe this blog post 🙈</p>
</li>
<li>
<p>In early December this year&rsquo;s hotsauce finally got finished and labeled. The chillies were planted in February and the fruits harvested in early October. As always, it was a project that was in progress throughout most of the year!</p>

    
    
    
    
    
    
    
    
    
    
    
    
    
    
            
            
    
            
                
                
            
    
            
                <blockquote class="toot-blockquote" cite="https://chaos.social@foosel/status/115651620739382562">
                    <div class="toot-header">
                        <a class="toot-profile" href="https://chaos.social/@foosel" rel="noopener">
                            <img
                                src="https://assets.chaos.social/accounts/avatars/000/235/099/original/a2e381e9aab4a693.png"
                                alt="Mastodon avatar for @foosel@chaos.social"
                                loading="lazy"
                            />
                        </a>
                        <div class="toot-author">
                            <a class="toot-author-name" href="https://chaos.social/@foosel" rel="noopener">Gina Häußge</a>
                            <a class="toot-author-handle" href="https://chaos.social/@foosel" rel="noopener">@foosel@chaos.social</a>
                        </div>
                    </div>
                    <p>May I present? The result of our <a href="https://chaos.social/tags/ProjectHotsauce2025" class="mention hashtag" rel="tag">#<span>ProjectHotsauce2025</span></a>: Firestarter 😁</p><p>We continue our run of naming the yearly sauce after a song 😉</p>
                    
                        
                            
                                
                            
                        
                        <div class="toot-img-grid-1">
                        
                            
                                
                                <style>
                                    .img-4a2949d88f011560e793d157dd853849 {
                                        aspect-ratio: 1600 / 1201;
                                    }
                                </style>
                                <img
                                    src="https://assets.chaos.social/media_attachments/files/115/651/610/737/507/233/original/4c8d537de2554466.jpg"
                                    alt="Several hot sauce bottles on a table, now labeled.
    
    The label says
    
    Firestarter
    Hot Sauce 2025
    Made By Gina & Dennis"
                                    class="toot-media-img img-4a2949d88f011560e793d157dd853849"
                                    loading="lazy"
                                />
                            
                        
                        </div>
                        
                        
                            
                            
                        
                    
                    
                    
                    <div class="toot-footer">
                        <a href="https://chaos.social/@foosel/115651620739382562" class="toot-date" rel="noopener">December 2, 2025, 19:28</a>&nbsp;<span class="pokey">(UTC)</span>
                    </div>
                </blockquote>
            
        
</li>
<li>
<p>Also in early December, I fell completely in love with the game <a href="https://store.steampowered.com/app/1532200/Mars_First_Logistics/">&ldquo;Mars First Logistics&rdquo;</a>, which I can only recommend if you are into Lego Technic.</p>

    
    
    
    
    
    
    
    
    
    
    
    
    
    
            
            
    
            
                
                
            
    
            
                <blockquote class="toot-blockquote" cite="https://chaos.social@foosel/status/115736693422407285">
                    <div class="toot-header">
                        <a class="toot-profile" href="https://chaos.social/@foosel" rel="noopener">
                            <img
                                src="https://assets.chaos.social/accounts/avatars/000/235/099/original/a2e381e9aab4a693.png"
                                alt="Mastodon avatar for @foosel@chaos.social"
                                loading="lazy"
                            />
                        </a>
                        <div class="toot-author">
                            <a class="toot-author-name" href="https://chaos.social/@foosel" rel="noopener">Gina Häußge</a>
                            <a class="toot-author-handle" href="https://chaos.social/@foosel" rel="noopener">@foosel@chaos.social</a>
                        </div>
                    </div>
                    <p>Choooo choooooo!</p><p><a href="https://chaos.social/tags/MarsFirstLogistics" class="mention hashtag" rel="tag">#<span>MarsFirstLogistics</span></a> <a href="https://chaos.social/tags/gaming" class="mention hashtag" rel="tag">#<span>gaming</span></a></p>
                    
                        
                            
                                
                            
                        
                        <div class="toot-img-grid-1">
                        
                            
                                
                                <style>
                                    .img-8bddb17ce14320cf1fc6ea7bfcc3d286 {
                                        aspect-ratio: 1920 / 1080;
                                    }
                                </style>
                                <img
                                    src="https://assets.chaos.social/media_attachments/files/115/736/682/450/842/644/original/edcf331157162e2d.png"
                                    alt="Screenshot from Mars First Logistics, showing my transporter being carried by a Monorail running through the reddish landscape."
                                    class="toot-media-img img-8bddb17ce14320cf1fc6ea7bfcc3d286"
                                    loading="lazy"
                                />
                            
                        
                        </div>
                        
                        
                            
                            
                        
                    
                    
                    
                    <div class="toot-footer">
                        <a href="https://chaos.social/@foosel/115736693422407285" class="toot-date" rel="noopener">December 17, 2025, 20:03</a>&nbsp;<span class="pokey">(UTC)</span>
                    </div>
                </blockquote>
            
        
</li>
<li>
<p>And finally, shortly before Christmas I tried my hand at making Vanillekipferl for the first time, and greatly succeeded!</p>

    
    
    
    
    
    
    
    
    
    
    
    
    
    
            
            
    
            
                
                
            
    
            
                <blockquote class="toot-blockquote" cite="https://chaos.social@foosel/status/115753009964627497">
                    <div class="toot-header">
                        <a class="toot-profile" href="https://chaos.social/@foosel" rel="noopener">
                            <img
                                src="https://assets.chaos.social/accounts/avatars/000/235/099/original/a2e381e9aab4a693.png"
                                alt="Mastodon avatar for @foosel@chaos.social"
                                loading="lazy"
                            />
                        </a>
                        <div class="toot-author">
                            <a class="toot-author-name" href="https://chaos.social/@foosel" rel="noopener">Gina Häußge</a>
                            <a class="toot-author-handle" href="https://chaos.social/@foosel" rel="noopener">@foosel@chaos.social</a>
                        </div>
                    </div>
                    <p>I also tried my hands at Vanille Kipferl for the first time in my life!</p>
                    
                        
                            
                                
                            
                        
                        <div class="toot-img-grid-1">
                        
                            
                                
                                <style>
                                    .img-827ce46e5608d7941fca37b4bd2f7570 {
                                        aspect-ratio: 3325 / 2494;
                                    }
                                </style>
                                <img
                                    src="https://assets.chaos.social/media_attachments/files/115/753/008/121/740/156/original/e511af17f6187501.jpg"
                                    alt="Several freshly baked Kipferl in a tin."
                                    class="toot-media-img img-827ce46e5608d7941fca37b4bd2f7570"
                                    loading="lazy"
                                />
                            
                        
                        </div>
                        
                        
                            
                            
                        
                    
                    
                    
                    <div class="toot-footer">
                        <a href="https://chaos.social/@foosel/115753009964627497" class="toot-date" rel="noopener">December 20, 2025, 17:12</a>&nbsp;<span class="pokey">(UTC)</span>
                    </div>
                </blockquote>
            
        
</li>
</ol>
<p>And that&rsquo;s a wrap on 2025 I think! I probably once more missed some fun stuff or awesome stuff I did or experienced, but reading through all of that written down here it feels like less of a crappy year already 😬</p>
<p>Like last year, this list was compiled with the help of my <a href="https://chaos.social/@foosel">Mastodon account</a> and my picture gallery 😄 I&rsquo;ll see that I do one of these again in a year&rsquo;s time. For now I wish you a happy and safe 2026!</p>
]]></content:encoded></item><item><title>About my chronic vertigo</title><link>https://foosel.net/blog/2025-09-15-about-my-chronic-vertigo/</link><pubDate>Mon, 15 Sep 2025 00:00:00 +0000</pubDate><guid>https://foosel.net/blog/2025-09-15-about-my-chronic-vertigo/</guid><description>&lt;p&gt;&lt;em&gt;I finally decided to write the following post as every time I have an acute episode of my chronic vertigo
and mention it to people, I have to explain my situation and symptoms again. So I decided to write it
down here to just be able to link people to it instead 😉. This is &lt;strong&gt;not&lt;/strong&gt; an attempt to get attention or pity,
so please spare me that! 😅&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I have had a chronic vertigo since fall of 2000 now. It&amp;rsquo;s not a case of issues with low blood pressure (&amp;ldquo;blackness in front of the eyes&amp;rdquo;) or some
light-headedness or something like that, but rather a case of
the same sensoric input being generated somewhere between my &lt;a href="https://en.wikipedia.org/wiki/Vestibular_system"&gt;vestibular system&lt;/a&gt; and my brain as if I was on a roller coaster or sitting on
a merry-go-around. During acute episodes which last hours to days (and sometimes even weeks) this leads to unstable walking, a very insecure feeling when standing and sometimes also nausea
and &lt;a href="https://en.wikipedia.org/wiki/Nystagmus"&gt;nystagmus&lt;/a&gt;, not to mention exhaustion while trying to compensate for all of that. Thankfully, it&amp;rsquo;s &amp;ldquo;just&amp;rdquo; vertigo, my hearing is perfectly fine.&lt;/p&gt;</description><content:encoded><![CDATA[<p><em>I finally decided to write the following post as every time I have an acute episode of my chronic vertigo
and mention it to people, I have to explain my situation and symptoms again. So I decided to write it
down here to just be able to link people to it instead 😉. This is <strong>not</strong> an attempt to get attention or pity,
so please spare me that! 😅</em></p>
<p>I have had a chronic vertigo since fall of 2000 now. It&rsquo;s not a case of issues with low blood pressure (&ldquo;blackness in front of the eyes&rdquo;) or some
light-headedness or something like that, but rather a case of
the same sensoric input being generated somewhere between my <a href="https://en.wikipedia.org/wiki/Vestibular_system">vestibular system</a> and my brain as if I was on a roller coaster or sitting on
a merry-go-around. During acute episodes which last hours to days (and sometimes even weeks) this leads to unstable walking, a very insecure feeling when standing and sometimes also nausea
and <a href="https://en.wikipedia.org/wiki/Nystagmus">nystagmus</a>, not to mention exhaustion while trying to compensate for all of that. Thankfully, it&rsquo;s &ldquo;just&rdquo; vertigo, my hearing is perfectly fine.</p>
<p>This crap started with a really bad respiratory infection that gave me a high fever and put me into bed
for almost two weeks. After the fever went down I had a constant vertigo. This was right during my last two years at
school and meant that I missed almost two months of classes and then had to get brought to and fetched
from school by my dad for a month while I was learning to cope with this during my day-to-day and it turning less
constant<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>.</p>
<p>The initial diagnosis was <a href="https://en.wikipedia.org/wiki/Labyrinthitis">labyrinthitis</a>. When that took too long without the symptoms going away despite
treatment, I spent a week getting a full check-through at a hospital in summer of 2001 after which the diagnosis became
<a href="https://en.wikipedia.org/wiki/Benign_paroxysmal_positional_vertigo">BPPV</a> and I was taught the usual treatment for that. With that diagnosis I went on with life, doing my prescribed exercises,
hoping it would just disappear one day. But when that didn&rsquo;t happen and was still going on in summer of 2019 I decided to get yet another full check-through and the result of
that was basically &ldquo;It&rsquo;s not BPPV, but we don&rsquo;t know what it is, everything works like it should as far as we can see&rdquo;. Not a hardware issue, no wiring problems nor
firmware bugs to see 🤷</p>
<p>So this kinda sucks. Most days I thankfully don&rsquo;t notice it, and when it hits I can still handle my day-to-day
fine in most cases thanks to a sheer ton of training 😬. It certainly helps that I can do my job sitting down 😅</p>
<p>The bad episodes seem to get triggered by sudden weather changes<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>, stress and illness<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>. I so far haven&rsquo;t
seen <em>any</em> connection to my food intake (thankfully). As I have a history of weather and stress
induced migraines, and <a href="https://en.wikipedia.org/wiki/Migraine-associated_vertigo">vestibular migraines</a> are a thing as
I learned after yet another lengthy research session, I&rsquo;m now tracking my symptoms daily
(vertigo, headache, tinnitus, nausea, plus stress &amp; weather) in preparation for yet <em>another</em> round of
trying to get a proper diagnosis and ideally working treatment for this shit from a neurologist.</p>
<p><strong>tldr:</strong> It&rsquo;s not low blood pressure, it&rsquo;s not just &ldquo;a bit dizzy&rdquo;, it&rsquo;s not BPPV (so Epley etc don&rsquo;t help), no hearing loss is involved.
It might be a weird version of migraines, I&rsquo;m currently tracking things in preparation for another attempt at getting to the bottom of this.
I won&rsquo;t die from it and have well working coping strategies but on some days it just plain sucks.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>That&rsquo;s when I learned that the brain is absolutely <em>amazing</em> at compensating for broken sensoric inputs!&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Or this constant rain-sun-rain-sun-rain-sun shit that&rsquo;s going on during seasonal changes now it seems 😒&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>Especially respiratory infections are my kryptonite - five days of the sniffles, ten or more of everything spinning.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>How to set the internal schedule on a Roomba 960 using rest980</title><link>https://foosel.net/til/how-to-set-the-internal-schedule-on-a-roomba-960-using-rest980/</link><pubDate>Wed, 09 Jul 2025 00:00:00 +0000</pubDate><guid>https://foosel.net/til/how-to-set-the-internal-schedule-on-a-roomba-960-using-rest980/</guid><description>&lt;p&gt;For some reason my Roomba 960 decided to fall off the cloud, or at least the official app refuses to see it.&lt;/p&gt;
&lt;p&gt;I thankfully already have an instance of &lt;a href="https://github.com/koalazak/rest980"&gt;rest980&lt;/a&gt; running in my homelab anyhow, and it is still happily chatting with
the bot. And tbh, I might just block cloud access again as having everything local is better anyhow.&lt;/p&gt;
&lt;p&gt;In any case, I wanted to disable the schedule currently set on it internally to switch to scheduling stuff from my home automation,
but without the app working I wasn&amp;rsquo;t sure on how. So I went hunting through rest980&amp;rsquo;s source - as the README didn&amp;rsquo;t tell me what
I was looking for - and found that I could program the weekly schedule with some easy &lt;code&gt;curl&lt;/code&gt; magic via the &lt;code&gt;/api/local/config/week&lt;/code&gt;
endpoint.&lt;/p&gt;</description><content:encoded><![CDATA[<p>For some reason my Roomba 960 decided to fall off the cloud, or at least the official app refuses to see it.</p>
<p>I thankfully already have an instance of <a href="https://github.com/koalazak/rest980">rest980</a> running in my homelab anyhow, and it is still happily chatting with
the bot. And tbh, I might just block cloud access again as having everything local is better anyhow.</p>
<p>In any case, I wanted to disable the schedule currently set on it internally to switch to scheduling stuff from my home automation,
but without the app working I wasn&rsquo;t sure on how. So I went hunting through rest980&rsquo;s source - as the README didn&rsquo;t tell me what
I was looking for - and found that I could program the weekly schedule with some easy <code>curl</code> magic via the <code>/api/local/config/week</code>
endpoint.</p>
<p>Firing off a <code>GET</code> against that, this is the data structure I received:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;cycle&#34;</span>: [
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;none&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;start&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;start&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;start&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;start&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;start&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;none&#34;</span>
</span></span><span style="display:flex;"><span>  ],
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;h&#34;</span>: [
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">9</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">15</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">15</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">15</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">15</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">15</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">9</span>
</span></span><span style="display:flex;"><span>  ],
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;m&#34;</span>: [
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span>  ]
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><code>cycle</code> seems to be the on/off button from Sunday at index 0 to Saturday on index 6. <code>start</code> schedules a cleaning run, <code>none</code> disables it.
<code>h</code> is the hours on which to start each day, and <code>m</code> the minute.</p>
<p>What I wanted to do was to set all of the days to off, and this I achieved with this combined <code>GET</code>/<code>POST</code> call with some <code>jq</code> manipulation in the middle:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>curl $REST980_URL/api/local/config/week | <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>    jq <span style="color:#e6db74">&#39;.cycle[] = &#34;none&#34;&#39;</span> | <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>    curl --json @- $REST980_URL/api/local/config/week
</span></span></code></pre></div><p>Another problem - hopefully - solved! I&rsquo;ll see tomorrow if this <em>really</em> disabled the schedule 😅 but I&rsquo;m optimistic!</p>
]]></content:encoded></item><item><title>How to automatically sync screenshots from the Steamdeck to Immich</title><link>https://foosel.net/til/how-to-automatically-sync-screenshots-from-the-steamdeck-to-immich/</link><pubDate>Tue, 25 Mar 2025 00:00:00 +0000</pubDate><guid>https://foosel.net/til/how-to-automatically-sync-screenshots-from-the-steamdeck-to-immich/</guid><description>&lt;p&gt;As part of &lt;a href="https://chaos.social/@foosel/114105591362840338"&gt;my ongoing effort to reduce my dependency on US services&lt;/a&gt;, I just moved my photos
from Google Photos to a self-hosted &lt;a href="https://immich.app/"&gt;immich&lt;/a&gt; instance (which I btw can only recommend so far).&lt;/p&gt;
&lt;p&gt;You might remember from &lt;a href="https://foosel.net/til/how-to-automatically-sync-screenshots-from-the-steamdeck-to-google-photos/"&gt;a previous TIL&lt;/a&gt;
that I had my Steamdeck configured to push my screenshots into a custom album on Google Photos. Obviously I had to change that now as well,
but sadly couldn&amp;rsquo;t use the existing &lt;a href="https://rclone.org/"&gt;rclone&lt;/a&gt;-based setup for it.&lt;/p&gt;</description><content:encoded><![CDATA[<p>As part of <a href="https://chaos.social/@foosel/114105591362840338">my ongoing effort to reduce my dependency on US services</a>, I just moved my photos
from Google Photos to a self-hosted <a href="https://immich.app/">immich</a> instance (which I btw can only recommend so far).</p>
<p>You might remember from <a href="/til/how-to-automatically-sync-screenshots-from-the-steamdeck-to-google-photos/">a previous TIL</a>
that I had my Steamdeck configured to push my screenshots into a custom album on Google Photos. Obviously I had to change that now as well,
but sadly couldn&rsquo;t use the existing <a href="https://rclone.org/">rclone</a>-based setup for it.</p>
<p>My first idea was to utilize <a href="https://github.com/simulot/immich-go">immich-go</a>, as I have just successfully used that for the
three day long import of over 50000 pictures from my Google Photos takeout into immich. But that turned out to not be the right tool here: in order to not even try to
upload already existing files it will fetch an asset list from immich first, and while that really improves performance for large batch imports,
it takes way too long for uploading a single new screenshot.</p>
<p>So instead I went with something self-built which utilizes <a href="https://immich.app/docs/api/">immich&rsquo;s API</a>.</p>
<h2 id="a-custom-upload-script">A custom upload script</h2>
<p>The first part is this little bash script that will take a file as input, upload it to a pre-configured immich instance and also add it to a
pre-defined album (which already has to exist). This lives in <code>~/.local/bin/immich-upload.sh</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e">#!/bin/bash
</span></span></span><span style="display:flex;"><span>set -e
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>IMMICH_SERVER<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;https://immich.example.com&#34;</span>
</span></span><span style="display:flex;"><span>IMMICH_KEY<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;your api key goes here&#34;</span>
</span></span><span style="display:flex;"><span>IMMICH_ALBUM<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;your album id goes here&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>INPUT<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;</span>$1<span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#f92672">[</span> <span style="color:#e6db74">&#34;</span>$INPUT<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;&#34;</span> <span style="color:#f92672">]</span>; <span style="color:#66d9ef">then</span>
</span></span><span style="display:flex;"><span>    echo <span style="color:#e6db74">&#34;immich-upload.sh &lt;file&gt;&#34;</span>
</span></span><span style="display:flex;"><span>    exit <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">fi</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>file_modified<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>stat -c %Y <span style="color:#e6db74">&#34;</span>$INPUT<span style="color:#e6db74">&#34;</span> | date --iso-8601<span style="color:#f92672">=</span>seconds<span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>name<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>basename <span style="color:#e6db74">&#34;</span>$INPUT<span style="color:#e6db74">&#34;</span><span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># ---</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>echo <span style="color:#e6db74">&#34;Uploading </span>$INPUT<span style="color:#e6db74"> to immich...&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>upload<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>curl -sL --request POST <span style="color:#e6db74">&#34;</span>$IMMICH_SERVER<span style="color:#e6db74">/api/assets&#34;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>    -H <span style="color:#e6db74">&#34;Content-Type: multipart/form-data&#34;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>    -H <span style="color:#e6db74">&#34;Accept: application/json&#34;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>    -H <span style="color:#e6db74">&#34;X-API-Key: </span>$IMMICH_KEY<span style="color:#e6db74">&#34;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>    -F <span style="color:#e6db74">&#34;deviceId=\&#34;curl/steamdeck\&#34;&#34;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>    -F <span style="color:#e6db74">&#34;deviceAssetId=\&#34;</span>$name<span style="color:#e6db74">-</span>$file_modified<span style="color:#e6db74">\&#34;&#34;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>    -F <span style="color:#e6db74">&#34;fileCreatedAt=\&#34;</span>$file_modified<span style="color:#e6db74">\&#34;&#34;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>    -F <span style="color:#e6db74">&#34;fileModifiedAt=\&#34;</span>$file_modified<span style="color:#e6db74">\&#34;&#34;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>    -F <span style="color:#e6db74">&#34;assetData=@\&#34;</span>$INPUT<span style="color:#e6db74">\&#34;&#34;</span><span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>id<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>echo <span style="color:#e6db74">&#34;</span>$upload<span style="color:#e6db74">&#34;</span> | jq -r .id<span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>echo <span style="color:#e6db74">&#34;Uploaded file, asset id is </span>$id<span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># ---</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>echo <span style="color:#e6db74">&#34;Adding file to album </span>$IMMICH_ALBUM<span style="color:#e6db74">...&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>payload<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>jq -n --arg id $id <span style="color:#e6db74">&#39;{ids:[$ARGS.named.id]}&#39;</span><span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>album<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>curl -sL --request PUT <span style="color:#e6db74">&#34;</span>$IMMICH_SERVER<span style="color:#e6db74">/api/albums/</span>$IMMICH_ALBUM<span style="color:#e6db74">/assets&#34;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>    -H <span style="color:#e6db74">&#34;Content-Type: application/json&#34;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>    -H <span style="color:#e6db74">&#34;Accept: application/json&#34;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>    -H <span style="color:#e6db74">&#34;X-API-Key: </span>$IMMICH_KEY<span style="color:#e6db74">&#34;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>    -d <span style="color:#e6db74">&#34;</span>$payload<span style="color:#e6db74">&#34;</span><span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>echo <span style="color:#e6db74">&#34;... done&#34;</span>
</span></span></code></pre></div><h2 id="reacting-to-new-screenshots">Reacting to new screenshots</h2>
<p>I use <a href="https://github.com/watchexec/watchexec">watchexec</a> to listen for changes in my custom screenshot folder
(<a href="/til/how-to-automatically-sync-screenshots-from-the-steamdeck-to-google-photos/">see this TIL post on how to set that up</a>)
and calling the upload script with the correct file name. I downloaded a release build of <code>watchexec</code> and threw it into <code>~/.local/bin</code>, then created another
script <code>~/.local/bin/sync-screenshots</code> that takes care of setting all of the correct parameters<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e">#!/usr/bin/env bash
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>WATCHEXEC<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>HOME<span style="color:#e6db74">}</span><span style="color:#e6db74">/.local/bin/watchexec&#34;</span>
</span></span><span style="display:flex;"><span>FOLDER<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>HOME<span style="color:#e6db74">}</span><span style="color:#e6db74">/.steam_screenshots&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#e6db74">${</span>WATCHEXEC<span style="color:#e6db74">}</span> --exts jpg,png,mp4 --fs-events create --emit-events-to environment -w $FOLDER -o queue -p -v -- <span style="color:#e6db74">&#39;/home/deck/.local/bin/immich-upload.sh &#34;$WATCHEXEC_COMMON_PATH/$WATCHEXEC_CREATED_PATH&#34;&#39;</span>
</span></span></code></pre></div><h2 id="putting-it-all-together">Putting it all together</h2>
<p>Finally, a new systemd unit in <code>~/.config/systemd/user/sync-screenshots.service</code> takes care of starting this bash script and keeping it running:</p>
<pre tabindex="0"><code>[Unit]
Description=Sync Steam Screenshots

[Service]
ExecStart=%h/.local/bin/sync-screenshots
Restart=on-failure
RestartSec=5

[Install]
WantedBy=default.target
</code></pre><p>I enabled and started that:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>systemctl --user enable sync-screenshots
</span></span><span style="display:flex;"><span>systemctl --user start sync-screenshots
</span></span></code></pre></div><p>Then I took a screenshot and confirmed that the script had run:</p>
<pre tabindex="0"><code>Mar 25 15:17:03 steamdeck sync_screenshots[77135]: [Running: /home/deck/.local/bin/immich-upload.sh &#34;$WATCHEXEC_COMMON_PATH/$WATCHEXEC_CREATED_PATH&#34;]
Mar 25 15:17:03 steamdeck sync_screenshots[77188]: Uploading /home/deck/.steam_screenshots/7_20250325151703_1.png to immich...
Mar 25 15:17:04 steamdeck sync_screenshots[77188]: Uploaded file, asset id is 141dc605-edef-48f1-83b5-00bd9d72b13e
Mar 25 15:17:04 steamdeck sync_screenshots[77188]: Adding file to album 0ce35e68-a564-4e26-921e-c486cd9e4725...
Mar 25 15:17:05 steamdeck sync_screenshots[77188]: ... done
Mar 25 15:17:05 steamdeck sync_screenshots[77135]: [Command was successful]
</code></pre><p>And indeed, upon checking my immich instance, I was also looking at the freshly uploaded screenshot. Mission accomplished!</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>It&rsquo;s currently reacting to newly added <code>jpg</code>, <code>png</code> or <code>mp4</code> files. The latter is in preparation of hopefully another toolchain to automatically convert clips from
<a href="https://store.steampowered.com/gamerecording">Steam&rsquo;s game recorder</a> that will automatically push its results into the screenshot folder as well, but that&rsquo;s only an idea for now.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>How to fix VirtualBox on Fedora 40 with Kernel 6.12+</title><link>https://foosel.net/til/how-to-fix-virtualbox-on-fedora-40-with-kernel-612/</link><pubDate>Thu, 27 Feb 2025 00:00:00 +0000</pubDate><guid>https://foosel.net/til/how-to-fix-virtualbox-on-fedora-40-with-kernel-612/</guid><description>&lt;p&gt;I (accidentally&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;) did a software update on my laptop last night, and this morning when I needed my Win10 VM for something, VirtualBox threw an error like this at me:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;VirtualBox can&amp;rsquo;t operate in VMX root mode. Please disable the KVM kernel extension, recompile your kernel and reboot (VERR_VMX_IN_VMX_ROOT_MODE).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A quick web search for &amp;ldquo;fedora update virtualbox vboxisomaker&amp;rdquo; gave me &lt;a href="https://discussion.fedoraproject.org/t/139896"&gt;this forum post&lt;/a&gt; and consequently
&lt;a href="https://www.virtualbox.org/ticket/22248"&gt;this bug report&lt;/a&gt;, in which I found the solution: I just had to add the kernel parameter &lt;code&gt;kvm.enable_virt_at_load=0&lt;/code&gt; to disable KVM -
which comes enabled by default since Kernel 6.12. I accomplished that with grubby:&lt;/p&gt;</description><content:encoded><![CDATA[<p>I (accidentally<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>) did a software update on my laptop last night, and this morning when I needed my Win10 VM for something, VirtualBox threw an error like this at me:</p>
<blockquote>
<p>VirtualBox can&rsquo;t operate in VMX root mode. Please disable the KVM kernel extension, recompile your kernel and reboot (VERR_VMX_IN_VMX_ROOT_MODE).</p>
</blockquote>
<p>A quick web search for &ldquo;fedora update virtualbox vboxisomaker&rdquo; gave me <a href="https://discussion.fedoraproject.org/t/139896">this forum post</a> and consequently
<a href="https://www.virtualbox.org/ticket/22248">this bug report</a>, in which I found the solution: I just had to add the kernel parameter <code>kvm.enable_virt_at_load=0</code> to disable KVM -
which comes enabled by default since Kernel 6.12. I accomplished that with grubby:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo grubby --update-kernel<span style="color:#f92672">=</span>ALL --args<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;kvm.enable_virt_at_load=0&#34;</span>
</span></span></code></pre></div><p>After a reboot, VirtualBox started again.</p>
<p>Given how often VirtualBox breaks for me on updates, long term I think I really need to find a different solution&hellip; And yes, I also really need to upgrade to Fedora 41, I know 😉</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>I needed to a quick reboot before an online call and promptly forgot to uncheck &ldquo;install updates&rdquo; on the reboot dialog. Which made me be late on the call. Meh.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>Five of my favourite lifehacks</title><link>https://foosel.net/blog/2025-02-05-five-of-my-favourite-lifehacks/</link><pubDate>Wed, 05 Feb 2025 00:00:00 +0000</pubDate><guid>https://foosel.net/blog/2025-02-05-five-of-my-favourite-lifehacks/</guid><description>&lt;p&gt;For a couple of years now I&amp;rsquo;ve had the sneaking suspicion that I&amp;rsquo;m neurodivergent. And while I still don&amp;rsquo;t have a diagnosis, there are several things
I use to cope with my brain constantly going &lt;em&gt;BRRRRRR&lt;/em&gt;, thinking (and worrying) about everything while forgetting important things, and I thought some of those
might also be helpful to others.&lt;/p&gt;
&lt;p&gt;Thus, here are five of my favourite lifehacks that I&amp;rsquo;ve been using to cope with the weirdness that is my brain. Some of them I&amp;rsquo;ve used for years before I ever suspected
something, some of them I only discovered after reading up on neurodiversity. Maybe you find something interesting for yourself!&lt;/p&gt;</description><content:encoded><![CDATA[<p>For a couple of years now I&rsquo;ve had the sneaking suspicion that I&rsquo;m neurodivergent. And while I still don&rsquo;t have a diagnosis, there are several things
I use to cope with my brain constantly going <em>BRRRRRR</em>, thinking (and worrying) about everything while forgetting important things, and I thought some of those
might also be helpful to others.</p>
<p>Thus, here are five of my favourite lifehacks that I&rsquo;ve been using to cope with the weirdness that is my brain. Some of them I&rsquo;ve used for years before I ever suspected
something, some of them I only discovered after reading up on neurodiversity. Maybe you find something interesting for yourself!</p>
<h2 id="waterproof-post-its-in-the-shower">Waterproof post-its in the shower</h2>
<p>My daily morning shower is a time I really do enjoy. It&rsquo;s also a time where I seem to get quite creative from time to time, getting great ideas on how to solve
existing problems in my life, or just remembering stuff I need to take care of. Of course, after getting out of the shower, once I&rsquo;ve toweled off, most
of that stuff is forgotten again.</p>
<p>A solution that has worked amazingly well for me here are waterproof post-its and a pencil that I just have stored high up in my shower! So when I get an idea or remember
something important, I now just note it down on a post-it and immediately glue that post-it to the shower door. That last step is very important, because that
way I have it basically right in my face while toweling myself off, and don&rsquo;t forget to take it with me when I&rsquo;m done with my morning routine to take care of it or
note it down somewhere else (either my <a href="https://obsidian.md/">Obsidian</a> knowledge base for ideas, or <a href="https://www.todoist.com/">Todoist</a> for TODOs).</p>
<p><img alt="A stack of water proof post-its put on my shower wall, with a pencil put on top on a hook." loading="lazy" src="/blog/2025-02-05-five-of-my-favourite-lifehacks/postits.jpg"></p>
<p>That approach has been working well for me now for quite some years! Before I came across the waterproof post-its though, I tried the same approach with a diver writing panel.
But with that I had the problem that I then either forgot that I had written something on it (as it hung inside the shower, not immediately visible while toweling off), or I
forgot to clear it again (as I didn&rsquo;t have an eraser right on hand) and so found it already full with notes when I needed to write something new on it. The post-it approach
solves both points, as the notes are way more mobile that way, and taking one off the stack immediately gives me a fresh one.</p>
<p>Something that doesn&rsquo;t get a post-it though is when my shower gel runs out - in that case I just throw the empty bottle out of the shower right away so I will stumble
over it when I get out and remember to put it on the grocery list 😆</p>
<h2 id="visual-timers">Visual timers</h2>
<p>I have a problem with timers (e.g. for the laundry) going off when I&rsquo;m right in the middle of something at work, not immediately able to react to it, and then
promptly forgetting about it. And I have a problem with noticing time run by in general when I&rsquo;m in the flow.</p>
<p>For the longest time this was causing me a ton of stress and cases of &ldquo;Oh no, not again&rdquo;, but these days I have that pretty well under control after putting a
visual timer on my work desk and one in the living room. And that&rsquo;s a trick I only discovered after reading up on neurodiversity and learning about
<a href="https://www.psychologytoday.com/intl/basics/time-blindness">time blindness</a>.</p>
<p><img alt="A visual timer set to 15min." loading="lazy" src="/blog/2025-02-05-five-of-my-favourite-lifehacks/timer.jpg"></p>
<p>Laundry timer just went off but I&rsquo;m deep in some code, hunting a bug? My hand now automatically just reaches to the visual timer and sets up something like 30min on it.
I only want to work on this work item for an hour (time box, follow-up appointment, it&rsquo;s already 5pm and I really should call it a day soon, &hellip;)? Hand out, timer on 60min.
I have to wait 10min to check my COVID test? Hand out, timer on 10min.</p>
<p>All of that would of course also work with a timer on my phone, but this works better for me: There&rsquo;s a physical timer right in front of me, with a physical wheel I can turn
to set a time, and I don&rsquo;t have to parse the numbers on the front either, the amount of time is clearly visible by how much red I see.</p>
<p>Where my phone <em>does</em> come in though is when I know I have an appointment sometime in the day. This causes me a ton of stress for the whole day until the appointment because
I know there&rsquo;s something coming up that I must not miss, and then I&rsquo;m scared of forgetting all about time again and missing it, causing me to not be able to concentrate on
anything else very well. What has helped me here is putting one to several alarms on my phone before the actual appointment, giving me an early warning that the time is coming
close, and allowing me to relax until that happens.</p>
<h2 id="physical-callbacks">Physical callbacks</h2>
<p>Another problem I often encounter is having to remember to do something at some point in the near future. My forgetfulness is becoming a bit of a pattern here I guess 😅</p>
<p>Some examples:</p>
<ul>
<li>I have a bunch of envelopes I need to bring to the next mailbox the next time I&rsquo;m out. (Hello <a href="https://www.patreon.com/foosel">OctoPrint Patreons</a> at $10+ 👋)</li>
<li>We just had a BBQ on the balcony and now the grill has to cool down before I can close and cover it again.</li>
<li>There are dinner left-overs that need to go into the fridge or freezer, but currently they are still too warm for that.</li>
<li>I need to remember to do something come monday when I&rsquo;m back at my desk, which I can only do at my desk, e.g. ordering a new prescription.</li>
</ul>
<p>For things like that I usually use a quick physical callback.</p>
<p><img alt="A note stuck into my keyboard saying &ldquo;Fix Build on staging/maintenance&rdquo;." loading="lazy" src="/blog/2025-02-05-five-of-my-favourite-lifehacks/callback.jpg"></p>
<p>Something I still have to do before going to sleep, e.g. take care of the grill or the left-overs? I put something
in the hallway that doesn&rsquo;t belong there. Usually that&rsquo;s my rolled up yoga mat, as that&rsquo;s a quick and easy thing to do given its usual place. I have to remember to take care
of something while out? I put it on top of my shoes or throw it into a bag I hang from the apartment door&rsquo;s handle. Something I need to take care off at my desk? A note stuck
into my keyboard or the empty prescription thrown on my desk work wonders.</p>
<h2 id="large-water-bottle-at-my-desk">Large water bottle at my desk</h2>
<p>I tend to not drink enough when I don&rsquo;t have water immediately in reach. So now I always keep a large refillable water bottle on my desk that I can drink from directly and that can
stay open without a big risk of spilling. Currently that&rsquo;s a 1l or 1.5l Nalgene wide mouth with an OTF cap.</p>
<p><img alt="My open water bottle sitting on my desk." loading="lazy" src="/blog/2025-02-05-five-of-my-favourite-lifehacks/bottle.jpg"></p>
<p>Bottle + glass never worked well for me - constantly having to refill the glass is something that annoys me greatly, plus I tend to forget it (of course&hellip;). Smaller bottle also
doesn&rsquo;t work, constantly empty. And a larger bottle than 1.5l poses the issue that I have to take it to the kitchen to refill from the tap, vs just being able to take care of that
during the regular bathroom break (which is WAY faster).</p>
<p>The goal here is to make it as easy as possible for me to - often absentmindedly - drink water, and that&rsquo;s achieved with that approach, allowing me to reach my daily 2-3l of fluid
intake.</p>
<h2 id="sleep-headphones--audio-books">Sleep headphones &amp; audio books</h2>
<p>My brain doesn&rsquo;t shut up fscking <em>ever</em>, and it interferes with my nightly rest. Imagine trying to fall asleep when your brain is constantly thinking about the latest bug in your code,
code architecture, what groceries you need to buy, upcoming elections, that discussion in that issue you had this morning, that email you still have to write tomorrow, &hellip;</p>
<p>It&rsquo;s only when I&rsquo;m completely and utterly exhausted that I can fall asleep right away, otherwise it&rsquo;s a long struggle of trying to ignore my brain and coming down from the day before
it works.</p>
<p>Thankfully, I discovered sleep headphones for myself a long time ago, so headphones in the shape of a headband with some really flat speakers inside and a bluetooth module or wired connection
to your phone or mp3 player.</p>
<p><img alt="My sleep headphones lying on my pillow." loading="lazy" src="/blog/2025-02-05-five-of-my-favourite-lifehacks/sleepphones.jpg"></p>
<p>Those allow me to listen to things without disturbing my partner. And those things for me usually are audio versions of books I already happen to know. The latter
is extremely important, as with books I don&rsquo;t know yet, my curiousity into how the story continues might keep me from falling asleep. I have the same issue I with most podcasts. But
audio books of books I&rsquo;ve already read work amazingly well. I set the sleep timer in my audio player to something like 30min and then just close my eyes and listen. My brain shuts up
as I&rsquo;m keeping it busy with listening and imagining what&rsquo;s going on in the book, and I often manage to drift off in less than 10min.</p>
<p>So there you are, five lifehacks that make my daily life easier for me!</p>
]]></content:encoded></item><item><title>What cool stuff did I do in 2024?</title><link>https://foosel.net/blog/2024-12-30-2024-retrospective/</link><pubDate>Mon, 30 Dec 2024 00:00:00 +0000</pubDate><guid>https://foosel.net/blog/2024-12-30-2024-retrospective/</guid><description>An attempt at highlighting some personal achievements of the past year, to combat the constant negative vibes</description><content:encoded><![CDATA[<p>I decided to try my hand at my very own 2024 retrospective to give myself a chance to see more of the good things I managed to get done or that I experienced. And as the news continue to be all doom and gloom I figured why not do this in the shape of a blog post so that a) it&rsquo;s more easy for myself to revisit that again in the future whenever I get dragged down and b) y&rsquo;all maybe also get some positive vibes.</p>
<p>So here we go&hellip;</p>
<ol>
<li>
<p>I got myself a Silhouette Portrait 3 vinyl cutter (that i can fully operate with open source tooling based on an Inkscape plugin, fsck proprietary cloud sh*t!) and immediately gave it a face. Technically that still happened in 2023, but on December 30th, so I&rsquo;ll let it count ;)

   
   
   
   
   
   
   
   
   
   
   
   
   
   
           
           
   
           
               
               
           
   
           
               <blockquote class="toot-blockquote" cite="https://chaos.social@foosel/status/111669542081808946">
                   <div class="toot-header">
                       <a class="toot-profile" href="https://chaos.social/@foosel" rel="noopener">
                           <img
                               src="https://assets.chaos.social/accounts/avatars/000/235/099/original/a2e381e9aab4a693.png"
                               alt="Mastodon avatar for @foosel@chaos.social"
                               loading="lazy"
                           />
                       </a>
                       <div class="toot-author">
                           <a class="toot-author-name" href="https://chaos.social/@foosel" rel="noopener">Gina Häußge</a>
                           <a class="toot-author-handle" href="https://chaos.social/@foosel" rel="noopener">@foosel@chaos.social</a>
                       </div>
                   </div>
                   <p>New tool in the house! After circling one for years now, I finally pulled the trigger on a vinyl cutter/plotter!</p><p>It&#39;s a Silhouette Portrait 3, only a small one, but the important part is that I can use it with Open Source tooling &amp; local only*, thanks to the combo of <span class="h-card" translate="no"><a href="https://mastodon.art/@inkscape" class="u-url mention">@<span>inkscape</span></a></span> &amp; the inkscape-silhouette** extension!</p><p>Lots of ideas swirling around in my head about stickers, shirts and such, but for now I&#39;ve given it a face! 😄</p><p>* Fsck this cloud only subscription crap!</p><p>** <a href="https://github.com/fablabnbg/inkscape-silhouette" target="_blank" rel="nofollow noopener" translate="no"><span class="invisible">https://</span><span class="ellipsis">github.com/fablabnbg/inkscape-</span><span class="invisible">silhouette</span></a></p>
                   
                       
                           
                               
                           
                       
                       <div class="toot-img-grid-1">
                       
                           
                               
                               <style>
                                   .img-32f672dd5cacfcb7b0249d76fa91a93d {
                                       aspect-ratio: 3325 / 2494;
                                   }
                               </style>
                               <img
                                   src="https://assets.chaos.social/media_attachments/files/111/669/525/211/912/358/original/6e66def4b545b67c.jpg"
                                   alt="A Silhouette Portrait 3 vinyl cutter with two eye stickers. It looks like it is doing this face: o_o"
                                   class="toot-media-img img-32f672dd5cacfcb7b0249d76fa91a93d"
                                   loading="lazy"
                               />
                           
                       
                       </div>
                       
                       
                           
                           
                       
                   
                   
                   
                   <div class="toot-footer">
                       <a href="https://chaos.social/@foosel/111669542081808946" class="toot-date" rel="noopener">December 30, 2023, 13:13</a>&nbsp;<span class="pokey">(UTC)</span>
                   </div>
               </blockquote>
           
       </p>
</li>
<li>
<p>I did a deep clean and reorganization of my office, and I&rsquo;m happy to report that this has mostly held up!

   
   
   
   
   
   
   
   
   
   
   
   
   
   
           
           
   
           
               
               
           
   
           
               <blockquote class="toot-blockquote" cite="https://chaos.social@foosel/status/111704357097141495">
                   <div class="toot-header">
                       <a class="toot-profile" href="https://chaos.social/@foosel" rel="noopener">
                           <img
                               src="https://assets.chaos.social/accounts/avatars/000/235/099/original/a2e381e9aab4a693.png"
                               alt="Mastodon avatar for @foosel@chaos.social"
                               loading="lazy"
                           />
                       </a>
                       <div class="toot-author">
                           <a class="toot-author-name" href="https://chaos.social/@foosel" rel="noopener">Gina Häußge</a>
                           <a class="toot-author-handle" href="https://chaos.social/@foosel" rel="noopener">@foosel@chaos.social</a>
                       </div>
                   </div>
                   <p>Day 4: Desk, electronics area, paper printer (a bit). I still need to take care of the paper waste in a few (we have some containers for that down the street), the e-waste (run to the place for that some time next week) and some bulky waste ASAP, and I didn&#39;t touch the lassr material storage after all, but for now I&#39;m calling this done.</p><p>Feels great walking into the office again, which is the best thing considering I&#39;ll be back at work come Monday.</p>
                   
                       
                           
                               
                           
                       
                           
                               
                           
                       
                           
                               
                           
                       
                       <div class="toot-img-grid-3">
                       
                           
                               
                               <style>
                                   .img-dd4ac4d7d92f8bc3ba112965d12026c2 {
                                       aspect-ratio: 3325 / 2494;
                                   }
                               </style>
                               <img
                                   src="https://assets.chaos.social/media_attachments/files/111/704/339/338/814/327/original/470e84c0eb575788.jpg"
                                   alt="View towards the back of the office. Desk to the left, a big whiteboard and some shelves filled with electronics directly forward and a paper printer and a table with a laser cutter to the right."
                                   class="toot-media-img img-dd4ac4d7d92f8bc3ba112965d12026c2"
                                   loading="lazy"
                               />
                           
                       
                           
                               
                               <style>
                                   .img-abb3a1a23b1a892902b4023ae88cbec8 {
                                       aspect-ratio: 3325 / 2494;
                                   }
                               </style>
                               <img
                                   src="https://assets.chaos.social/media_attachments/files/111/704/340/108/739/770/original/6382d94d1b9c5963.jpg"
                                   alt="View towards the desk from being the chair. Three screens, Framework laptop vertical to the left, a bunch of steaming lights."
                                   class="toot-media-img img-abb3a1a23b1a892902b4023ae88cbec8"
                                   loading="lazy"
                               />
                           
                       
                           
                               
                               <style>
                                   .img-c4ae238bbbaeea155cb1e1b06062f058 {
                                       aspect-ratio: 3325 / 2494;
                                   }
                               </style>
                               <img
                                   src="https://assets.chaos.social/media_attachments/files/111/704/340/513/213/985/original/91b247e489578ac3.jpg"
                                   alt="Once more a view towards the front of the office with the Expedit directly forward. To the left the still untouched laser table, to the right the heavy duty shelves."
                                   class="toot-media-img img-c4ae238bbbaeea155cb1e1b06062f058"
                                   loading="lazy"
                               />
                           
                       
                       </div>
                       
                       
                           
                           
                       
                           
                           
                       
                           
                           
                       
                   
                   
                   
                   <div class="toot-footer">
                       <a href="https://chaos.social/@foosel/111704357097141495" class="toot-date" rel="noopener">January 5, 2024, 16:47</a>&nbsp;<span class="pokey">(UTC)</span>
                   </div>
               </blockquote>
           
       </p>
</li>
<li>
<p>I got a new label printer (Brother QL-820NWB) and <a href="https://foosel.net/til/how-to-print-deutsche-post-stamps-via-the-command-line-on-a-brother-ql-label-printer/">created scripts to easily print out Deutsche Post stamps with that</a>.</p>
</li>
<li>
<p>I spent some beautiful calm days with my partner in the southern black forest - plenty of snow and walks and games!</p>
<p><img alt="A tree on the side of a hill in the snow. The sky is blue, the sun is shining." loading="lazy" src="/blog/2024-12-30-2024-retrospective/schauinsland.jpg"></p>
</li>
<li>
<p>With the help of the vinyl cutter and the laser cutter I gave our <a href="https://datagnome.de">Datenzwerge</a> some stylish travel cases!</p>

   
   
   
   
   
   
   
   
   
   
   
   
   
   
           
           
   
           
               
               
           
   
           
               <blockquote class="toot-blockquote" cite="https://chaos.social@foosel/status/111914246770176122">
                   <div class="toot-header">
                       <a class="toot-profile" href="https://chaos.social/@foosel" rel="noopener">
                           <img
                               src="https://assets.chaos.social/accounts/avatars/000/235/099/original/a2e381e9aab4a693.png"
                               alt="Mastodon avatar for @foosel@chaos.social"
                               loading="lazy"
                           />
                       </a>
                       <div class="toot-author">
                           <a class="toot-author-name" href="https://chaos.social/@foosel" rel="noopener">Gina Häußge</a>
                           <a class="toot-author-handle" href="https://chaos.social/@foosel" rel="noopener">@foosel@chaos.social</a>
                       </div>
                   </div>
                   <p>Our datagnomes will travel in style from now on!</p><p>We got two L-Boxes for them now which fit 5 gnomes each. A few weeks ago I lasercut some custom foam inlays for them, and just today finally got around to also put some custom vinyl stickers on them, after my partner turned the logo he created black and white for us 😊</p><p>I&#39;m very happy with the result, and I hope <span class="h-card" translate="no"><a href="https://chaos.social/@Romses" class="u-url mention">@<span>Romses</span></a></span> will be as well once he gets his box into his hands soon 😄</p><p><a href="https://chaos.social/tags/DatenzwergLeaks" class="mention hashtag" rel="tag">#<span>DatenzwergLeaks</span></a></p>
                   
                       
                           
                               
                           
                       
                           
                               
                           
                       
                           
                               
                           
                       
                       <div class="toot-img-grid-3">
                       
                           
                               
                               <style>
                                   .img-ea5b2473afefe82c7f0be34c64db84e2 {
                                       aspect-ratio: 3325 / 2494;
                                   }
                               </style>
                               <img
                                   src="https://assets.chaos.social/media_attachments/files/111/914/230/716/722/817/original/5149fce9b55a4c59.jpg"
                                   alt="A black L-Box 136 standing on a chair. On its lid there&#39;s a black and white datagnome logo with the text datagnome.de next to it."
                                   class="toot-media-img img-ea5b2473afefe82c7f0be34c64db84e2"
                                   loading="lazy"
                               />
                           
                       
                           
                               
                               <style>
                                   .img-6e5e731b3c70d93182635ff219db6452 {
                                       aspect-ratio: 2494 / 3325;
                                   }
                               </style>
                               <img
                                   src="https://assets.chaos.social/media_attachments/files/111/914/231/026/614/297/original/f007293d4894e45e.jpg"
                                   alt="The open box on a chair. Five datagnomes are inside, each in its own compartment in a custom foam insert. There&#39;s also two smaller compartments holding extra gnome feet."
                                   class="toot-media-img img-6e5e731b3c70d93182635ff219db6452"
                                   loading="lazy"
                               />
                           
                       
                           
                               
                               <style>
                                   .img-bb77a1bb69645e91343cef7b8353c1b2 {
                                       aspect-ratio: 3325 / 2494;
                                   }
                               </style>
                               <img
                                   src="https://assets.chaos.social/media_attachments/files/111/914/231/372/118/263/original/327ad0b9e4c03345.jpg"
                                   alt="Both of the boxes, clopped together so they can be lifted both as one unit."
                                   class="toot-media-img img-bb77a1bb69645e91343cef7b8353c1b2"
                                   loading="lazy"
                               />
                           
                       
                       </div>
                       
                       
                           
                           
                       
                           
                           
                       
                           
                           
                       
                   
                   
                   
                   <div class="toot-footer">
                       <a href="https://chaos.social/@foosel/111914246770176122" class="toot-date" rel="noopener">February 11, 2024, 18:24</a>&nbsp;<span class="pokey">(UTC)</span>
                   </div>
               </blockquote>
           
       
</li>
<li>
<p>I also did a deep clean and reorganization of my wardrobe, and that&rsquo;s also still holding up! No more dreading of sorting the laundry into the wardrobe!</p>

   
   
   
   
   
   
   
   
   
   
   
   
   
   
           
           
   
           
               
               
           
   
           
               <blockquote class="toot-blockquote" cite="https://chaos.social@foosel/status/111942405312732513">
                   <div class="toot-header">
                       <a class="toot-profile" href="https://chaos.social/@foosel" rel="noopener">
                           <img
                               src="https://assets.chaos.social/accounts/avatars/000/235/099/original/a2e381e9aab4a693.png"
                               alt="Mastodon avatar for @foosel@chaos.social"
                               loading="lazy"
                           />
                       </a>
                       <div class="toot-author">
                           <a class="toot-author-name" href="https://chaos.social/@foosel" rel="noopener">Gina Häußge</a>
                           <a class="toot-author-handle" href="https://chaos.social/@foosel" rel="noopener">@foosel@chaos.social</a>
                       </div>
                   </div>
                   <p>Phewwww. Six hours of wardrobe cleaning today, and after doing the same last Friday I can now say the wardrobe is in a shape I really enjoy opening it again. </p><p>Today&#39;s focus was the &quot;general household laundry stuff&quot; part of it. The plushies and the sewing machine have their own shelves now 😄</p><p>I&#39;ve already brought the 3 large garbage bags full of old clothes to the recycling container, &amp; put the 6 actual trash bags into the garbage container. </p><p>And now excuse me while I fall down on my couch.</p>
                   
                       
                           
                               
                           
                       
                       <div class="toot-img-grid-1">
                       
                           
                               
                               <style>
                                   .img-b7468f4f203311e985ebe37cf72d71b1 {
                                       aspect-ratio: 2494 / 3325;
                                   }
                               </style>
                               <img
                                   src="https://assets.chaos.social/media_attachments/files/111/942/393/803/119/716/original/6ab4eda7e4155c16.jpg"
                                   alt="Picture of a freshly cleaned wardrobe."
                                   class="toot-media-img img-b7468f4f203311e985ebe37cf72d71b1"
                                   loading="lazy"
                               />
                           
                       
                       </div>
                       
                       
                           
                           
                       
                   
                   
                   
                   <div class="toot-footer">
                       <a href="https://chaos.social/@foosel/111942405312732513" class="toot-date" rel="noopener">February 16, 2024, 17:45</a>&nbsp;<span class="pokey">(UTC)</span>
                   </div>
               </blockquote>
           
       
</li>
<li>
<p>I learned book binding and got into solo RPGs!</p>

   
   
   
   
   
   
   
   
   
   
   
   
   
   
           
           
   
           
               
               
           
   
           
               <blockquote class="toot-blockquote" cite="https://chaos.social@foosel/status/112349936201913060">
                   <div class="toot-header">
                       <a class="toot-profile" href="https://chaos.social/@foosel" rel="noopener">
                           <img
                               src="https://assets.chaos.social/accounts/avatars/000/235/099/original/a2e381e9aab4a693.png"
                               alt="Mastodon avatar for @foosel@chaos.social"
                               loading="lazy"
                           />
                       </a>
                       <div class="toot-author">
                           <a class="toot-author-name" href="https://chaos.social/@foosel" rel="noopener">Gina Häußge</a>
                           <a class="toot-author-handle" href="https://chaos.social/@foosel" rel="noopener">@foosel@chaos.social</a>
                       </div>
                   </div>
                   <p>Inspired by <span class="h-card" translate="no"><a href="https://mastodon.art/@stiftnuersel" class="u-url mention">@<span>stiftnuersel</span></a></span> I decided to finally try to add book binding to my skill set and started with binding both books of 2D6 Dungeon (also a tip by <span class="h-card" translate="no"><a href="https://mastodon.art/@stiftnuersel" class="u-url mention">@<span>stiftnuersel</span></a></span>).</p><p>Especially sewing the paper stacks was incredibly relaxing. I put an audio book on (going through The Stormlight Archive again) and just went for it. </p><p>Decided to go with a soft cover. The cardboard came from some deliveries and the fabric from the left overs of an old sewing project. </p><p>Quite happy with the result 😊</p>
                   
                       
                           
                               
                           
                       
                           
                               
                           
                       
                           
                               
                           
                       
                           
                               
                           
                       
                       <div class="toot-img-grid-4">
                       
                           
                               
                               <style>
                                   .img-ceae5b1dba98e70df73196f78a8a8c35 {
                                       aspect-ratio: 3325 / 2494;
                                   }
                               </style>
                               <img
                                   src="https://assets.chaos.social/media_attachments/files/112/349/907/794/968/695/original/5b78cec985b9fd51.jpg"
                                   alt="Two freshly bound text blocks."
                                   class="toot-media-img img-ceae5b1dba98e70df73196f78a8a8c35"
                                   loading="lazy"
                               />
                           
                       
                           
                               
                               <style>
                                   .img-cf3ab7d62e2c8b4416ef6dc2885c9e27 {
                                       aspect-ratio: 3325 / 2494;
                                   }
                               </style>
                               <img
                                   src="https://assets.chaos.social/media_attachments/files/112/349/909/240/954/139/original/4c203266e3ee348f.jpg"
                                   alt="Template used for creating the sewing holes."
                                   class="toot-media-img img-cf3ab7d62e2c8b4416ef6dc2885c9e27"
                                   loading="lazy"
                               />
                           
                       
                           
                               
                               <style>
                                   .img-8fba713e43f66ea8103a6b9da61c6835 {
                                       aspect-ratio: 3325 / 2494;
                                   }
                               </style>
                               <img
                                   src="https://assets.chaos.social/media_attachments/files/112/349/910/904/288/741/original/7337019487524df7.jpg"
                                   alt="Finished book covered with a black structured fabric."
                                   class="toot-media-img img-8fba713e43f66ea8103a6b9da61c6835"
                                   loading="lazy"
                               />
                           
                       
                           
                               
                               <style>
                                   .img-9a45671a1970cd6ac2371bf1d98773fe {
                                       aspect-ratio: 1080 / 1440;
                                   }
                               </style>
                               <img
                                   src="https://assets.chaos.social/media_attachments/files/112/349/911/315/645/559/original/df0d5e57734ce146.jpg"
                                   alt="Leafing through the book."
                                   class="toot-media-img img-9a45671a1970cd6ac2371bf1d98773fe"
                                   loading="lazy"
                               />
                           
                       
                       </div>
                       
                       
                           
                           
                       
                           
                           
                       
                           
                           
                       
                           
                           
                       
                   
                   
                   
                   <div class="toot-footer">
                       <a href="https://chaos.social/@foosel/112349936201913060" class="toot-date" rel="noopener">April 28, 2024, 17:06</a>&nbsp;<span class="pokey">(UTC)</span>
                   </div>
               </blockquote>
           
       
</li>
<li>
<p>I also got into crafting print-and-play games!</p>

   
   
   
   
   
   
   
   
   
   
   
   
   
   
           
           
   
           
               
               
           
   
           
               <blockquote class="toot-blockquote" cite="https://chaos.social@foosel/status/112401363792302351">
                   <div class="toot-header">
                       <a class="toot-profile" href="https://chaos.social/@foosel" rel="noopener">
                           <img
                               src="https://assets.chaos.social/accounts/avatars/000/235/099/original/a2e381e9aab4a693.png"
                               alt="Mastodon avatar for @foosel@chaos.social"
                               loading="lazy"
                           />
                       </a>
                       <div class="toot-author">
                           <a class="toot-author-name" href="https://chaos.social/@foosel" rel="noopener">Gina Häußge</a>
                           <a class="toot-author-handle" href="https://chaos.social/@foosel" rel="noopener">@foosel@chaos.social</a>
                       </div>
                   </div>
                   <p>I&#39;ve been in a crafting mood for a few days now. One of the results of that were my recent adventures in book binding <a href="https://chaos.social/tags/2d6Dungeon" class="mention hashtag" rel="tag">#<span>2d6Dungeon</span></a>, the other was finally figuring out a good way to create custom playing cards for print&#39;n&#39;play games. </p><p>I stumbled across <a href="https://chaos.social/tags/EscapeFromStation52" class="mention hashtag" rel="tag">#<span>EscapeFromStation52</span></a> in one of my itch.io bundles and that was the trigger: Cards were printed one sided on a piece of 185gsm linen stock, sealed with fixative, folded and spray glued together, then cut. A bit thick but feel &amp; shuffle great!</p>
                   
                       
                           
                               
                           
                       
                           
                               
                           
                       
                       <div class="toot-img-grid-2">
                       
                           
                               
                               <style>
                                   .img-457dd3c3ae9ae01dda59989bdf8aff9d {
                                       aspect-ratio: 3325 / 2494;
                                   }
                               </style>
                               <img
                                   src="https://assets.chaos.social/media_attachments/files/112/401/353/116/099/380/original/cdc45fb258fcabea.jpg"
                                   alt="The stack of cards for the game"
                                   class="toot-media-img img-457dd3c3ae9ae01dda59989bdf8aff9d"
                                   loading="lazy"
                               />
                           
                       
                           
                               
                               <style>
                                   .img-3b91ac412867bca468089259145a61c7 {
                                       aspect-ratio: 3325 / 2494;
                                   }
                               </style>
                               <img
                                   src="https://assets.chaos.social/media_attachments/files/112/401/356/673/063/182/original/cea29bf1cfab489c.jpg"
                                   alt="Front and backs of the cards"
                                   class="toot-media-img img-3b91ac412867bca468089259145a61c7"
                                   loading="lazy"
                               />
                           
                       
                       </div>
                       
                       
                           
                           
                       
                           
                           
                       
                   
                   
                   
                   <div class="toot-footer">
                       <a href="https://chaos.social/@foosel/112401363792302351" class="toot-date" rel="noopener">May 7, 2024, 19:05</a>&nbsp;<span class="pokey">(UTC)</span>
                   </div>
               </blockquote>
           
       
</li>
<li>
<p>As a result of that I also built a little tool (running 100% in the browser!) to convert the usual PNP card PDFs into ready to print gutterfold PDFs for easier alignment of the card backs and fronts: <a href="https://foosel.github.io/cardfoldr/">CardFoldr</a>!</p>
</li>
<li>
<p>I missed the probably biggest Aurora event of the past decade in Germany, but managed to still get some shots a few days later.</p>

    
    
    
    
    
    
    
    
    
    
    
    
    
    
            
            
    
            
                
                
            
    
            
                <blockquote class="toot-blockquote" cite="https://chaos.social@foosel/status/112428209096634181">
                    <div class="toot-header">
                        <a class="toot-profile" href="https://chaos.social/@foosel" rel="noopener">
                            <img
                                src="https://assets.chaos.social/accounts/avatars/000/235/099/original/a2e381e9aab4a693.png"
                                alt="Mastodon avatar for @foosel@chaos.social"
                                loading="lazy"
                            />
                        </a>
                        <div class="toot-author">
                            <a class="toot-author-name" href="https://chaos.social/@foosel" rel="noopener">Gina Häußge</a>
                            <a class="toot-author-handle" href="https://chaos.social/@foosel" rel="noopener">@foosel@chaos.social</a>
                        </div>
                    </div>
                    <p>I&#39;m still unhappy that I completely missed the aurora from Friday to Saturday (only heard about it the next day and cursed heavily) but at least got some pictures last night. It was only minimally visible by eye, but the camera was able to see more.</p><p>And we also ended up chatting with some really nice strangers that were also out to look at the sky, so while I just kept clicking the record button on the camera while getting colder and colder, the company completely made up for it.</p>
                    
                        
                            
                                
                            
                        
                            
                                
                            
                        
                        <div class="toot-img-grid-2">
                        
                            
                                
                                <style>
                                    .img-c3f2da579b423f3262243f146851d30c {
                                        aspect-ratio: 3324 / 2496;
                                    }
                                </style>
                                <img
                                    src="https://assets.chaos.social/media_attachments/files/112/428/179/434/598/316/original/2e9df8e36e777334.jpg"
                                    alt="Night shot over a field with a reddish aurora in the sky. Some lights on the ground."
                                    class="toot-media-img img-c3f2da579b423f3262243f146851d30c"
                                    loading="lazy"
                                />
                            
                        
                            
                                
                                <style>
                                    .img-fe5f4baa4e3cc84dcc58c75648aeef3a {
                                        aspect-ratio: 3324 / 2496;
                                    }
                                </style>
                                <img
                                    src="https://assets.chaos.social/media_attachments/files/112/428/186/439/563/751/original/f76ff73f81139912.jpg"
                                    alt="Night shot over a field. Stars in the sky and some reddish and slightly greenish light over it, the reddish looks like an aurora."
                                    class="toot-media-img img-fe5f4baa4e3cc84dcc58c75648aeef3a"
                                    loading="lazy"
                                />
                            
                        
                        </div>
                        
                        
                            
                            
                        
                            
                            
                        
                    
                    
                    
                    <div class="toot-footer">
                        <a href="https://chaos.social/@foosel/112428209096634181" class="toot-date" rel="noopener">May 12, 2024, 12:52</a>&nbsp;<span class="pokey">(UTC)</span>
                    </div>
                </blockquote>
            
        
</li>
<li>
<p>I did some minor leather crafting to build myself a big roll-up dice tray for my solo RPG sessions. It has since also come in handy during the one or other board game session.</p>

    
    
    
    
    
    
    
    
    
    
    
    
    
    
            
            
    
            
                
                
            
    
            
                <blockquote class="toot-blockquote" cite="https://chaos.social@foosel/status/112457157352018051">
                    <div class="toot-header">
                        <a class="toot-profile" href="https://chaos.social/@foosel" rel="noopener">
                            <img
                                src="https://assets.chaos.social/accounts/avatars/000/235/099/original/a2e381e9aab4a693.png"
                                alt="Mastodon avatar for @foosel@chaos.social"
                                loading="lazy"
                            />
                        </a>
                        <div class="toot-author">
                            <a class="toot-author-name" href="https://chaos.social/@foosel" rel="noopener">Gina Häußge</a>
                            <a class="toot-author-handle" href="https://chaos.social/@foosel" rel="noopener">@foosel@chaos.social</a>
                        </div>
                    </div>
                    <p>I wanted a bigger dice tray. Had some leather and snap buttons on hand. I&#39;m not sure if this qualifies as <a href="https://chaos.social/tags/LeatherWorking" class="mention hashtag" rel="tag">#<span>LeatherWorking</span></a> but it definitely qualifies as gaming related <a href="https://chaos.social/tags/crafting" class="mention hashtag" rel="tag">#<span>crafting</span></a>, so even while I&#39;m under the weather (hence no climbing pictures today) I got something made 😊</p><p>Edit: I should add that this approach to a dice tray is not my idea and there are plenty of these out there to buy. I just didn&#39;t want to buy something I had everything on hand to make myself as I enjoy making.</p>
                    
                        
                            
                                
                            
                        
                            
                                
                            
                        
                        <div class="toot-img-grid-2">
                        
                            
                                
                                <style>
                                    .img-3098551b5a490b33b03c86d578988279 {
                                        aspect-ratio: 3325 / 2494;
                                    }
                                </style>
                                <img
                                    src="https://assets.chaos.social/media_attachments/files/112/457/147/236/319/119/original/51022acc7dc2ab2d.jpg"
                                    alt="A rolled up square of black leather with some snap buttons visible on the edges, and a leather strap wrapped and buttoned up around it."
                                    class="toot-media-img img-3098551b5a490b33b03c86d578988279"
                                    loading="lazy"
                                />
                            
                        
                            
                                
                                <style>
                                    .img-f7a5cf955d9e25ebdedf645773bcb85e {
                                        aspect-ratio: 3325 / 2494;
                                    }
                                </style>
                                <img
                                    src="https://assets.chaos.social/media_attachments/files/112/457/147/447/891/099/original/63d5024e70b5ed71.jpg"
                                    alt="The leather square unrolled and the buttons snapped together. It&#39;s now usable as a dice tray."
                                    class="toot-media-img img-f7a5cf955d9e25ebdedf645773bcb85e"
                                    loading="lazy"
                                />
                            
                        
                        </div>
                        
                        
                            
                            
                        
                            
                            
                        
                    
                    
                    
                    <div class="toot-footer">
                        <a href="https://chaos.social/@foosel/112457157352018051" class="toot-date" rel="noopener">May 17, 2024, 15:34</a>&nbsp;<span class="pokey">(UTC)</span>
                    </div>
                </blockquote>
            
        
</li>
<li>
<p>I went to <a href="https://entropia.de/GPN22">GPN22</a> with my buddy Romses and five Datenzwerge. We had a great time there chatting with old and new friends, and getting more grey hair due to WiFi issues with the gnomes and DECT issues 😂</p>
<p><img alt="A picnic blanket with an opened up DECT phone, soldering tooling, a partial Datenzwerg, and some more electronics. Also - someone&rsquo;s leg and someone else&rsquo;s hands holding a portable oscilloscope" loading="lazy" src="/blog/2024-12-30-2024-retrospective/symbolbild-gpn.jpg"></p>
</li>
<li>
<p>My book binding experiments escalated and I bound a 400 page book 😬</p>

    
    
    
    
    
    
    
    
    
    
    
    
    
    
            
            
    
            
                
                
            
    
            
                <blockquote class="toot-blockquote" cite="https://chaos.social@foosel/status/112665867404914833">
                    <div class="toot-header">
                        <a class="toot-profile" href="https://chaos.social/@foosel" rel="noopener">
                            <img
                                src="https://assets.chaos.social/accounts/avatars/000/235/099/original/a2e381e9aab4a693.png"
                                alt="Mastodon avatar for @foosel@chaos.social"
                                loading="lazy"
                            />
                        </a>
                        <div class="toot-author">
                            <a class="toot-author-name" href="https://chaos.social/@foosel" rel="noopener">Gina Häußge</a>
                            <a class="toot-author-handle" href="https://chaos.social/@foosel" rel="noopener">@foosel@chaos.social</a>
                        </div>
                    </div>
                    <p>My custom hardcover edition of Across a Thousand Dead Worlds <a href="https://chaos.social/tags/atdw" class="mention hashtag" rel="tag">#<span>atdw</span></a> is finished! 🎉</p><p>Created the case earlier today, then glued in the textblock. While that was in the press I fetched the vinyl cutter and created a basic cover and spine print, using the same fonts that are also used in the book. </p><p>It&#39;s not fully square, and trying to back it yesterday also caused some steps in the rounding, but I&#39;m still very happy with the result! 😊</p><p>Will probably still add a dust jacket, but for now it&#39;s done!</p>
                    
                        
                            
                                
                            
                        
                            
                                
                            
                        
                            
                                
                            
                        
                            
                                
                            
                        
                        <div class="toot-img-grid-4">
                        
                            
                                
                                <style>
                                    .img-e7f4605b56535fb755c2d27c0eea0c97 {
                                        aspect-ratio: 3325 / 2494;
                                    }
                                </style>
                                <img
                                    src="https://assets.chaos.social/media_attachments/files/112/665/849/000/119/512/original/bea1ec7b4b931135.jpg"
                                    alt="The book sitting in a press made from two boards and some weights on top."
                                    class="toot-media-img img-e7f4605b56535fb755c2d27c0eea0c97"
                                    loading="lazy"
                                />
                            
                        
                            
                                
                                <style>
                                    .img-f01d4244a3c60f59245d18a4758eb0d9 {
                                        aspect-ratio: 3325 / 2494;
                                    }
                                </style>
                                <img
                                    src="https://assets.chaos.social/media_attachments/files/112/665/849/359/888/565/original/b96cb6c51110db74.jpg"
                                    alt="The finished book lying on a dark table. It&#39;s covered in black cloth. On the front are the letters &#34;ADTW&#34; in a two by two letter square, on the spine it says &#34;Across a Thousand Dead Worlds&#34; in two lines."
                                    class="toot-media-img img-f01d4244a3c60f59245d18a4758eb0d9"
                                    loading="lazy"
                                />
                            
                        
                            
                                
                                <style>
                                    .img-29a386cb164ee3cd6207760865773507 {
                                        aspect-ratio: 3325 / 2494;
                                    }
                                </style>
                                <img
                                    src="https://assets.chaos.social/media_attachments/files/112/665/849/561/244/742/original/1d79a853fc4ff136.jpg"
                                    alt="Close-up of the tail of the opened book. The spine works great."
                                    class="toot-media-img img-29a386cb164ee3cd6207760865773507"
                                    loading="lazy"
                                />
                            
                        
                            
                                
                                <style>
                                    .img-5e79b61efd16094df197e7674e46e50d {
                                        aspect-ratio: 2494 / 3325;
                                    }
                                </style>
                                <img
                                    src="https://assets.chaos.social/media_attachments/files/112/665/849/747/292/662/original/200228041f23b19d.jpg"
                                    alt="The book sitting on a book shelf and looking great!"
                                    class="toot-media-img img-5e79b61efd16094df197e7674e46e50d"
                                    loading="lazy"
                                />
                            
                        
                        </div>
                        
                        
                            
                            
                        
                            
                            
                        
                            
                            
                        
                            
                            
                        
                    
                    
                    
                    <div class="toot-footer">
                        <a href="https://chaos.social/@foosel/112665867404914833" class="toot-date" rel="noopener">June 23, 2024, 12:11</a>&nbsp;<span class="pokey">(UTC)</span>
                    </div>
                </blockquote>
            
        
</li>
<li>
<p>I went to EuroPython to Prague, had some amazing chats with people there, enjoyed a bunch of very tasty non-alcoholic cocktails, had a lovely time at the social event, and also experienced my first flight-cancellation and rebooking on my return trip 😅</p>

    
    
    
    
    
    
    
    
    
    
    
    
    
    
            
            
    
            
                
                
            
    
            
                <blockquote class="toot-blockquote" cite="https://chaos.social@foosel/status/112766493783478034">
                    <div class="toot-header">
                        <a class="toot-profile" href="https://chaos.social/@foosel" rel="noopener">
                            <img
                                src="https://assets.chaos.social/accounts/avatars/000/235/099/original/a2e381e9aab4a693.png"
                                alt="Mastodon avatar for @foosel@chaos.social"
                                loading="lazy"
                            />
                        </a>
                        <div class="toot-author">
                            <a class="toot-author-name" href="https://chaos.social/@foosel" rel="noopener">Gina Häußge</a>
                            <a class="toot-author-handle" href="https://chaos.social/@foosel" rel="noopener">@foosel@chaos.social</a>
                        </div>
                    </div>
                    <p>Good morning <a href="https://chaos.social/tags/EuroPython" class="mention hashtag" rel="tag">#<span>EuroPython</span></a>! The sleep was good, the self test was negative, the breakfast was delicious, now <span class="h-card" translate="no"><a href="https://plushies.social/@timothy" class="u-url mention">@<span>timothy</span></a></span> and I are ready for day two of talks and chats!</p>
                    
                        
                            
                                
                            
                        
                        <div class="toot-img-grid-1">
                        
                            
                                
                                <style>
                                    .img-732f123532c27f0fdc9262a6e1237689 {
                                        aspect-ratio: 2464 / 3280;
                                    }
                                </style>
                                <img
                                    src="https://assets.chaos.social/media_attachments/files/112/766/489/101/694/244/original/18544abb04ffb64a.jpg"
                                    alt="Me in the audience of Forum Hall at EuroPython, masked with my back to the front and my plush donkey on my head, pointing to the stage"
                                    class="toot-media-img img-732f123532c27f0fdc9262a6e1237689"
                                    loading="lazy"
                                />
                            
                        
                        </div>
                        
                        
                            
                            
                        
                    
                    
                    
                    <div class="toot-footer">
                        <a href="https://chaos.social/@foosel/112766493783478034" class="toot-date" rel="noopener">July 11, 2024, 06:42</a>&nbsp;<span class="pokey">(UTC)</span>
                    </div>
                </blockquote>
            
        
</li>
<li>
<p>After a <strong>very</strong> stressful time at work thanks to <a href="https://octoprint.org/blog/2024/06/28/stats-manipulation/">some stats manipulation</a> by <a href="https://octoprint.org/blog/2024/07/04/more-stats-manipulation/">some bad players</a> (that btw still has me get very angry whenever I think about it) I had a very relaxing and resetting two week vacation with my partner at the north sea, filled with e-bike rides, walks through the wadden sea, our very first stint at a &ldquo;Hochseilgarten&rdquo; (climbing garden) and also playing through Fort Solis, Little Hope and House of Ashes together during the evenings 😄 I also treated myself to a new camera body for this occasion, a Lumix G91, allowing the G70 to stay in webcam mode for good now.</p>
<p><img alt="A tiny starfish in the sand, under water" loading="lazy" src="/blog/2024-12-30-2024-retrospective/starfish.jpg"></p>
</li>
<li>
<p>While there I also blogged about my <a href="https://foosel.net/blog/2024-08-08-edc-2024/">EDC 2024</a>. For which I then promptly sewed a new custom pouch once I was back at home.</p>

    
    
    
    
    
    
    
    
    
    
    
    
    
    
            
            
    
            
                
                
            
    
            
                <blockquote class="toot-blockquote" cite="https://chaos.social@foosel/status/112996329622482193">
                    <div class="toot-header">
                        <a class="toot-profile" href="https://chaos.social/@foosel" rel="noopener">
                            <img
                                src="https://assets.chaos.social/accounts/avatars/000/235/099/original/a2e381e9aab4a693.png"
                                alt="Mastodon avatar for @foosel@chaos.social"
                                loading="lazy"
                            />
                        </a>
                        <div class="toot-author">
                            <a class="toot-author-name" href="https://chaos.social/@foosel" rel="noopener">Gina Häußge</a>
                            <a class="toot-author-handle" href="https://chaos.social/@foosel" rel="noopener">@foosel@chaos.social</a>
                        </div>
                    </div>
                    <p>Went down the <a href="https://chaos.social/tags/myog" class="mention hashtag" rel="tag">#<span>myog</span></a> rabbit hole recently as my bought <a href="https://chaos.social/tags/edc" class="mention hashtag" rel="tag">#<span>edc</span></a> pouch was annoying me.</p><p>Even though my chronic vertigo is currently making me nauseous, I decided to try to distract myself from that and sewed a first version of a pouch.</p><p>Outside is some black 600D cordura, inside red ripstop nylon for contrast, and two stripes of 50mm stretch band turned into two pockets on one side and three on the other to help with organisation. The zipper is a red ykk aqua guard.</p>
                    
                        
                            
                                
                            
                        
                            
                                
                            
                        
                        <div class="toot-img-grid-2">
                        
                            
                                
                                <style>
                                    .img-7e620a3a0b269e4f949d6e4cc183deb2 {
                                        aspect-ratio: 3325 / 2494;
                                    }
                                </style>
                                <img
                                    src="https://assets.chaos.social/media_attachments/files/112/996/317/114/839/063/original/8b4a294d7e50618c.jpg"
                                    alt="A black pouch with a red zipper."
                                    class="toot-media-img img-7e620a3a0b269e4f949d6e4cc183deb2"
                                    loading="lazy"
                                />
                            
                        
                            
                                
                                <style>
                                    .img-b72abbc6d00dff5921476929f863cf42 {
                                        aspect-ratio: 3325 / 2494;
                                    }
                                </style>
                                <img
                                    src="https://assets.chaos.social/media_attachments/files/112/996/317/737/836/855/original/fcee57ee2ee1d8cb.jpg"
                                    alt="The pouch zipped and pushed open. The inside shows some small pockets created by black fabric stripes on top of the red inside."
                                    class="toot-media-img img-b72abbc6d00dff5921476929f863cf42"
                                    loading="lazy"
                                />
                            
                        
                        </div>
                        
                        
                            
                            
                        
                            
                            
                        
                    
                    
                    
                    <div class="toot-footer">
                        <a href="https://chaos.social/@foosel/112996329622482193" class="toot-date" rel="noopener">August 20, 2024, 20:52</a>&nbsp;<span class="pokey">(UTC)</span>
                    </div>
                </blockquote>
            
        
</li>
<li>
<p>During September I participated in FediJam and created <a href="https://foosel.itch.io/cloud-shepherd">Cloud Shepherd</a>, a cozy puzzle game in which you herd clouds over fields to make it rain. I did all the programming, graphics and sound effects (yes, also the dog and the sheep) myself - and <strong>I won the gamejam</strong>! And as this was my first time doing pixelart, it gave me some courage&hellip;</p>
<p><img alt="The title screen of my game Cloud Shepherd, showing a rural landscape from above, with some clouds, sheep, a herding dog and a field. All of that is done in brightly colored pixel art" loading="lazy" src="/blog/2024-12-30-2024-retrospective/cloud-shepherd.png"></p>
</li>
<li>
<p>&hellip; so throughout October <a href="https://chaos.social/@foosel/113233763230193057">I partook in the #hartoween art challenge</a> and did a pixelart painting for every day - albeit admittedly some of them a few days later due to other committments and illness, but all 31 of them in October!</p>
<p><img alt="A collage of all 31 pixelart paintings with their associated prompts" loading="lazy" src="/blog/2024-12-30-2024-retrospective/hartoween-wallpaper.png"></p>
</li>
<li>
<p>For that I also figured out how to <a href="https://foosel.net/til/how-to-make-transparent-gifs-easier-shareable-by-adding-a-checkerboard-background/">make transparent GIFs more easily shareable on social media</a> as I also was creating timelapses of my drawings and wanted to share them with a proper checkerboard background.</p>
</li>
<li>
<p>Also in October, I went to <a href="https://2024.mrmcd.net/de/">MRMCD24</a> together with my partner and my buddy Romses, and of course a whole army of Datenzwerge. We deployed 8 of them this time and it was a huge success.</p>
</li>
<li>
<p>During that time I also played through my personal Game of the Year 2024, Animal Well!</p>

    
    
    
    
    
    
    
    
    
    
    
    
    
    
            
            
    
            
                
                
            
    
            
                <blockquote class="toot-blockquote" cite="https://chaos.social@foosel/status/113290415749654937">
                    <div class="toot-header">
                        <a class="toot-profile" href="https://chaos.social/@foosel" rel="noopener">
                            <img
                                src="https://assets.chaos.social/accounts/avatars/000/235/099/original/a2e381e9aab4a693.png"
                                alt="Mastodon avatar for @foosel@chaos.social"
                                loading="lazy"
                            />
                        </a>
                        <div class="toot-author">
                            <a class="toot-author-name" href="https://chaos.social/@foosel" rel="noopener">Gina Häußge</a>
                            <a class="toot-author-handle" href="https://chaos.social/@foosel" rel="noopener">@foosel@chaos.social</a>
                        </div>
                    </div>
                    <p>Can&#39;t help myself, I&#39;m absolutely in awe at the art style of <a href="https://chaos.social/tags/AnimalWell" class="mention hashtag" rel="tag">#<span>AnimalWell</span></a>. </p><p>That mix of highlights, scan lines and glow, plus all these details... So much to discover on every single screen, and yet the pallette still *feels* subtle.</p><p>I have to admit, I got drawn to that game by its graphics first and foremost, but gameplay is a ton of fun too. A puzzle platformer with a beautiful atmosphere. Can recommend!</p>
                    
                        
                            
                                
                            
                        
                        <div class="toot-img-grid-1">
                        
                            
                                
                                <style>
                                    .img-bda04d798f739c346cbf2cfed7ffb96d {
                                        aspect-ratio: 1280 / 800;
                                    }
                                </style>
                                <img
                                    src="https://assets.chaos.social/media_attachments/files/113/290/402/788/933/185/original/d9ffe67d41d2ef09.png"
                                    alt="Screenshot of Animal Well, showing a cave with some capybaras, plants hanging from the ceiling, and small lights. The grass on the ground looks like it glows."
                                    class="toot-media-img img-bda04d798f739c346cbf2cfed7ffb96d"
                                    loading="lazy"
                                />
                            
                        
                        </div>
                        
                        
                            
                            
                        
                    
                    
                    
                    <div class="toot-footer">
                        <a href="https://chaos.social/@foosel/113290415749654937" class="toot-date" rel="noopener">October 11, 2024, 19:22</a>&nbsp;<span class="pokey">(UTC)</span>
                    </div>
                </blockquote>
            
        
</li>
<li>
<p>In November the 2024 edition of our hotsauce got finished. As every year we planted the chillies around February and then took care of them throughout most of the year. Sadly, the yield this time around wasn&rsquo;t as great and we had to add some store bought chillies into the mix. I think the sauce still came out great though after five weeks of fermentation. Alas, I can&rsquo;t taste it myself - no heat tolerance 😅</p>

    
    
    
    
    
    
    
    
    
    
    
    
    
    
            
            
    
            
                
                
            
    
            
                <blockquote class="toot-blockquote" cite="https://chaos.social@foosel/status/113419292806716881">
                    <div class="toot-header">
                        <a class="toot-profile" href="https://chaos.social/@foosel" rel="noopener">
                            <img
                                src="https://assets.chaos.social/accounts/avatars/000/235/099/original/a2e381e9aab4a693.png"
                                alt="Mastodon avatar for @foosel@chaos.social"
                                loading="lazy"
                            />
                        </a>
                        <div class="toot-author">
                            <a class="toot-author-name" href="https://chaos.social/@foosel" rel="noopener">Gina Häußge</a>
                            <a class="toot-author-handle" href="https://chaos.social/@foosel" rel="noopener">@foosel@chaos.social</a>
                        </div>
                    </div>
                    <p>And now they are labelled 😊</p>
                    
                        
                            
                                
                            
                        
                        <div class="toot-img-grid-1">
                        
                            
                                
                                <style>
                                    .img-959d8163343345e6dcbb101b3eed1a5f {
                                        aspect-ratio: 3325 / 2494;
                                    }
                                </style>
                                <img
                                    src="https://assets.chaos.social/media_attachments/files/113/419/287/506/851/826/original/af88f81979b8a92e.jpg"
                                    alt="A bunch of small hot sauce bottles on a table. The bottles are labelled. The labels say 
    
    Hellrider
    Hot sauce by Gina & Dennis
    2024"
                                    class="toot-media-img img-959d8163343345e6dcbb101b3eed1a5f"
                                    loading="lazy"
                                />
                            
                        
                        </div>
                        
                        
                            
                            
                        
                    
                    
                    
                    <div class="toot-footer">
                        <a href="https://chaos.social/@foosel/113419292806716881" class="toot-date" rel="noopener">November 3, 2024, 13:37</a>&nbsp;<span class="pokey">(UTC)</span>
                    </div>
                </blockquote>
            
        
</li>
<li>
<p>After one too many quality issues with my Blue Yeti, I upgraded my office audio setup and got myself a Shure MV7+ and a Vocaster Two audio interface. And since then I&rsquo;m finally <em>happy</em> when hearing my own voice in a recording 😄</p>
</li>
<li>
<p>As my final work project of the year, I did some - successful! - refactoring experiments on OctoPrint that unlocked some things I&rsquo;ll work on in early 2025, and I&rsquo;m really looking forward to that 😊</p>
</li>
</ol>
<p>And there you have it. My personal list of good stuff that I did or experienced in 2024! Probably missing plenty of things, but that&rsquo;s all that I could think of now off the top of my head and with the help of my <a href="https://chaos.social/@foosel">Mastodon account</a> and my picture gallery 😄 Let&rsquo;s see what cool stuff I&rsquo;ll manage to list for 2025!</p>
]]></content:encoded></item><item><title>How to fetch additional data for a flux query from a json file</title><link>https://foosel.net/til/how-to-fetch-additional-data-for-a-flux-query-from-a-json-file/</link><pubDate>Thu, 26 Dec 2024 00:00:00 +0000</pubDate><guid>https://foosel.net/til/how-to-fetch-additional-data-for-a-flux-query-from-a-json-file/</guid><description>&lt;p&gt;My buddy Romses is currently taking care of the &lt;a href="https://datagnome.de"&gt;Datenzwerg deployment&lt;/a&gt; at 38c3, and like at every event where we deploy them I&amp;rsquo;m updating our page and &lt;a href="https://grafana.datagnome.de"&gt;Grafana dashboard&lt;/a&gt; with the locations of the gnomes.&lt;/p&gt;
&lt;p&gt;So far the latter was always quite annoying: We have only the names of the gnomes in our influx data, and adding the location/deployment status to the graph thus meant having something like this for every single graph:&lt;/p&gt;</description><content:encoded><![CDATA[<p>My buddy Romses is currently taking care of the <a href="https://datagnome.de">Datenzwerg deployment</a> at 38c3, and like at every event where we deploy them I&rsquo;m updating our page and <a href="https://grafana.datagnome.de">Grafana dashboard</a> with the locations of the gnomes.</p>
<p>So far the latter was always quite annoying: We have only the names of the gnomes in our influx data, and adding the location/deployment status to the graph thus meant having something like this for every single graph:</p>
<pre tabindex="0"><code class="language-flux" data-lang="flux">import &#34;strings&#34;
import &#34;dict&#34;

locations = [
    &#34;Bashful&#34;: &#34;Uptime Bar&#34;,
    &#34;Dopey&#34;: &#34;c3cat&#34;,
    &#34;Grumpy&#34;: &#34;Späti&#34;,
    &#34;Happy&#34;: &#34;Kidspace&#34;,
    &#34;Hefty&#34;: &#34;HASS Assembly&#34;,
    &#34;Moopsy&#34;: &#34;Chaospost&#34;,
    &#34;Kinky&#34;: &#34;Eventphone&#34;,
    &#34;Nerdy&#34;: &#34;House of Tea&#34;,
    &#34;Sleepy&#34;: &#34;DDOS Bar&#34;,
    &#34;Sneezy&#34;: &#34;Wohnzimmer&#34;
]

from(bucket: &#34;datagnome&#34;)
  [...]
  |&gt; map(fn: (r) =&gt; ({r with device: r.device + &#34; (&#34; + dict.get(dict: locations, key: r.device, default: &#34;?&#34;) + &#34;)&#34;}))
</code></pre><p>Which of course means that I had to update this <code>locations</code> dict for every single panel, on every single deployment, at least twice (start and end of the event).</p>
<p>I finally decided I had to solve this differently and just now figured out how to keep the deployment info in <a href="https://github.com/romses/Datenzwerg/blob/main/docs/deployment.json">a JSON file on our git repo</a> and then querying <em>that</em> from the graphs, instead of manually keeping the lookup data updated in more than one place:</p>
<pre tabindex="0"><code class="language-flux" data-lang="flux">import &#34;strings&#34;
import &#34;dict&#34;
import &#34;http/requests&#34;
import &#34;experimental/json&#34;

response = requests.get(url: &#34;https://raw.githubusercontent.com/romses/Datenzwerg/refs/heads/main/docs/deployment.json&#34;)
data = json.parse(data: response.body)
locations = dict.fromList(pairs: data)

from(bucket: &#34;datagnome&#34;)
  [...]
  |&gt; map(fn: (r) =&gt; ({r with device: r.device + &#34; (&#34; + dict.get(dict: locations, key: r.device, default: &#34;?&#34;) + &#34;)&#34;}))
</code></pre><p>So far seems to work well, and I&rsquo;m very happy to be able to do this faster now (and also more easily from my phone, should I need to).</p>
<p>Next step: Figuring out how to use that JSON file to also keep the event box on the home page updated. But that&rsquo;s for another day :)</p>
]]></content:encoded></item><item><title>How to make transparent GIFs more easily sharable by adding a checkerboard background</title><link>https://foosel.net/til/how-to-make-transparent-gifs-easier-shareable-by-adding-a-checkerboard-background/</link><pubDate>Tue, 15 Oct 2024 00:00:00 +0000</pubDate><guid>https://foosel.net/til/how-to-make-transparent-gifs-easier-shareable-by-adding-a-checkerboard-background/</guid><description>&lt;p&gt;I&amp;rsquo;m currently taking part in the &lt;a href="https://social.horrorhub.club/@stina_marie/113220760493893634"&gt;&amp;quot;#hARToween&amp;quot; daily art challenge&lt;/a&gt;, as I want to
work on my pixelart skills and drawing a 128x128px pixel drawing each day&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt; for a month seemed
like a good idea. You can follow my posts &lt;a href="https://chaos.social/@foosel/113233763230193057"&gt;in this thread&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m using &lt;a href="https://aseprite.org/"&gt;Aseprite&lt;/a&gt;, and recently came across &lt;a href="https://sprngr.itch.io/aseprite-record"&gt;the &amp;ldquo;record for aseprite&amp;rdquo; script for it&lt;/a&gt;
that allows taking regular snapshots of what I&amp;rsquo;m currently drawing so a timelapse can be created from
that. And that works nicely, but I had to realize that the timelapse would come with transparency until
I came to the background during my drawing, which looked really weird when sharing the resulting GIF.&lt;/p&gt;</description><content:encoded><![CDATA[<p>I&rsquo;m currently taking part in the <a href="https://social.horrorhub.club/@stina_marie/113220760493893634">&quot;#hARToween&quot; daily art challenge</a>, as I want to
work on my pixelart skills and drawing a 128x128px pixel drawing each day<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> for a month seemed
like a good idea. You can follow my posts <a href="https://chaos.social/@foosel/113233763230193057">in this thread</a>.</p>
<p>I&rsquo;m using <a href="https://aseprite.org/">Aseprite</a>, and recently came across <a href="https://sprngr.itch.io/aseprite-record">the &ldquo;record for aseprite&rdquo; script for it</a>
that allows taking regular snapshots of what I&rsquo;m currently drawing so a timelapse can be created from
that. And that works nicely, but I had to realize that the timelapse would come with transparency until
I came to the background during my drawing, which looked really weird when sharing the resulting GIF.</p>
<p>So I looked into adding the usual transparency checkerboard background to the GIF with a quick script,
and of course, <a href="https://imagemagick.org/">ImageMagick</a> once more to the rescue. Alas, the resulting GIF was quite large and ImageMagick&rsquo;s
optimization options caused glitches in the GIF. So I looked for another option to optimize the GIF and
came across <a href="https://www.lcdf.org/gifsicle/">gifsicle</a>.</p>
<p>The result is this bash script which will take a GIF and an optional background image to set,
add the background (or a freshly generated checkerboard pattern of the right size) to the GIF, then compress
the GIF:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e">#!/bin/bash
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>GIF<span style="color:#f92672">=</span>$1
</span></span><span style="display:flex;"><span>BG<span style="color:#f92672">=</span>$2
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>BASE<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>basename <span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>GIF%.*<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span><span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>OUTPUT<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;</span>$BASE<span style="color:#e6db74">.bg.gif&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#f92672">[</span> -f <span style="color:#e6db74">&#34;</span>$BG<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">]</span>; <span style="color:#66d9ef">then</span>
</span></span><span style="display:flex;"><span>    echo <span style="color:#e6db74">&#34;Adding background image </span>$BG<span style="color:#e6db74"> to all frames of </span>$GIF<span style="color:#e6db74">...&#34;</span>
</span></span><span style="display:flex;"><span>    magick <span style="color:#e6db74">&#34;</span>$GIF<span style="color:#e6db74">&#34;</span> -coalesce null: <span style="color:#e6db74">&#34;</span>$BG<span style="color:#e6db74">&#34;</span> -compose dstOver -layers composite <span style="color:#e6db74">&#34;</span>$OUTPUT<span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">else</span>
</span></span><span style="display:flex;"><span>    size<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>gifsicle --sinfo <span style="color:#e6db74">&#34;</span>$GIF<span style="color:#e6db74">&#34;</span> | grep <span style="color:#e6db74">&#34;logical screen&#34;</span> | xargs echo -n | cut -d<span style="color:#e6db74">&#34; &#34;</span> -f 3<span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    echo <span style="color:#e6db74">&#34;Generating a </span>$size<span style="color:#e6db74"> checkerboard pattern and adding it as background to all frames of </span>$GIF<span style="color:#e6db74">...&#34;</span>
</span></span><span style="display:flex;"><span>    magick <span style="color:#e6db74">&#34;</span>$GIF<span style="color:#e6db74">&#34;</span> -coalesce null: <span style="color:#ae81ff">\(</span> -size $size tile:pattern:checkerboard <span style="color:#ae81ff">\)</span> -compose dstOver -layers composite <span style="color:#e6db74">&#34;</span>$OUTPUT<span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">fi</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>echo <span style="color:#e6db74">&#34;Optimizing </span>$OUTPUT<span style="color:#e6db74">...&#34;</span>
</span></span><span style="display:flex;"><span>gifsicle --batch -O3 --lossy<span style="color:#f92672">=</span><span style="color:#ae81ff">35</span> <span style="color:#e6db74">&#34;</span>$OUTPUT<span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>echo <span style="color:#e6db74">&#34;...done!&#34;</span>
</span></span></code></pre></div><p>Example call:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-plain" data-lang="plain"><span style="display:flex;"><span>$ gif_bg 14.gif 
</span></span><span style="display:flex;"><span>Generating a 256x256 checkerboard pattern and adding it as background to all frames of 14.gif...
</span></span><span style="display:flex;"><span>Optimizing 14.bg.gif...
</span></span><span style="display:flex;"><span>...done!
</span></span></code></pre></div><p>I&rsquo;m quite happy with the result:</p>















        
        

        
            
            
        

        
            <blockquote class="toot-blockquote" cite="https://chaos.social@foosel/status/113306303596486838">
                <div class="toot-header">
                    <a class="toot-profile" href="https://chaos.social/@foosel" rel="noopener">
                        <img
                            src="https://assets.chaos.social/accounts/avatars/000/235/099/original/a2e381e9aab4a693.png"
                            alt="Mastodon avatar for @foosel@chaos.social"
                            loading="lazy"
                        />
                    </a>
                    <div class="toot-author">
                        <a class="toot-author-name" href="https://chaos.social/@foosel" rel="noopener">Gina Häußge</a>
                        <a class="toot-author-handle" href="https://chaos.social/@foosel" rel="noopener">@foosel@chaos.social</a>
                    </div>
                </div>
                <p>Now also with a creation timelapse (after I finally managed to get a proper workflow going for that)</p>
                
                    
                        
                    
                    <div class="toot-img-grid-0">
                    
                        
                    
                    </div>
                    
                    
                        
                        
                            
                            <style>
                                .img-f64237a660efa590d99cd4bd48022b22 {
                                    aspect-ratio: 256 / 256;
                                }
                            </style>
                            <div class="ctr toot-video-wrapper">
                                <video loop autoplay muted playsinline controls controlslist="nofullscreen" class="ctr toot-media-img img-f64237a660efa590d99cd4bd48022b22">
                                    <source src="https://assets.chaos.social/media_attachments/files/113/306/301/353/935/058/original/891b82c9cf56ffd5.mp4">
                                    <p class="legal ctr">(Your browser doesn&rsquo;t support the <code>video</code> tag.)</p>
                                </video></div>
                        
                    
                
                
                
                <div class="toot-footer">
                    <a href="https://chaos.social/@foosel/113306303596486838" class="toot-date" rel="noopener">October 14, 2024, 14:43</a>&nbsp;<span class="pokey">(UTC)</span>
                </div>
            </blockquote>
        
    
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Well, almost, I was at MRMCD and then a bit under the weather after and thus missed some days that I&rsquo;m now trying to catch up on again.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>How I (hopefully) fixed the flickering of my UHKv2's left half</title><link>https://foosel.net/til/how-i-hopefully-fixed-the-flickering-of-my-uhkv2s-left-half/</link><pubDate>Mon, 19 Aug 2024 00:00:00 +0000</pubDate><guid>https://foosel.net/til/how-i-hopefully-fixed-the-flickering-of-my-uhkv2s-left-half/</guid><description>&lt;p&gt;I&amp;rsquo;ve been a long time user of the &lt;a href="https://ultimatehackingkeyboard.com/"&gt;Ultimate Hacking Keyboard (UHK)&lt;/a&gt;. It started with acquiring version 1 in 2020 and upgrading to version 2 in 2022. The UHK is a split keyboard with two halves connected by a bridge cable. While the first version was rock solid for me, with the second one I sadly started to experience intermittent flickering of the left half, during which the left side became unresponsive. Sometimes the half would then even outright disconnect completely. Unplugging and replugging it would usually fix the issue, but was annoying.&lt;/p&gt;</description><content:encoded><![CDATA[<p>I&rsquo;ve been a long time user of the <a href="https://ultimatehackingkeyboard.com/">Ultimate Hacking Keyboard (UHK)</a>. It started with acquiring version 1 in 2020 and upgrading to version 2 in 2022. The UHK is a split keyboard with two halves connected by a bridge cable. While the first version was rock solid for me, with the second one I sadly started to experience intermittent flickering of the left half, during which the left side became unresponsive. Sometimes the half would then even outright disconnect completely. Unplugging and replugging it would usually fix the issue, but was annoying.</p>
<p>I reached out to UHK support, they confirmed they had gotten reports about this from other customers as well and were still investigating. They even created <a href="https://ultimatehackingkeyboard.com/repair/fixing-misbehaving-keys">an FAQ page</a>. In the end, after much troubleshooting I got a replacement, and for a while it looked like things were fixed. But then the flickering returned, just less frequent. For the longest time I&rsquo;ve now just lived with having to occasionally replug my keyboard. But on Friday I found myself on speaker phone with my mom, helping her debug an issue with her PC through screen sharing, with my mobile located between the two halves of the keyboard, and the flickering started with a vengeance! I had to replug the keyboard three or four times during the call, which was super annoying.</p>
<p>So I decided investigate once more, to see if maybe some new information had come up. And indeed it had, I found <a href="https://forum.ultimatehackingkeyboard.com/t/left-module-flickering-with-mobile-phone/152">this thread on the UHK forums</a>:</p>
<blockquote>
<p>Thought I’d start a topic about an issue which I’m aware at least a few people have experienced. Which is that the left module’s connection can flicker on/off if your mobile phone is placed to the left of it.</p>
<p>I realised this initially when I tried to capture a video of it happening and couldn’t do it, realising that the act of picking up my phone resolved it. These days I keep the phone away from that side of desk. It’s not a particular problem for me, but I’m sure it’ll be a confusing issue for people when they first encounter it.</p>
<p>My suspicion is that the I2C connection between the two halves has less protection against interference than USB. But it might be interesting to hear from actual engineers about this. And also if people have found a way to mitigate it.</p>
</blockquote>
<p>And that got immediately confirmed by UHK:</p>
<blockquote>
<p>Admittedly, the UHK is rather sensitive to electromagnetic interference, and we send <a href="https://ultimatehackingkeyboard.com/repair/fixing-misbehaving-keys">this troubleshooting guide</a> to our customers when they encounter such issues. We couldn’t catch this issue in the design phase, but fortunately, people can almost always work around it.</p>
<p>I2C is a likely reason; the bus is too long and has too many ICs on it. Future UHK versions will use UART between the halves. Another possible cause is insufficient ground pour on the PCBs, which will be much increased in future versions as well.</p>
</blockquote>
<p>and matches my experiences perfectly! When it got really bad on Friday, the phone was actively being used and right next to the left half. And I usually keep my phone in my right pocket, and only occassionally put it on the desk, which could explain why the flickering is so intermittent for me. I checked the (adjusted) FAQ entry and found this advice at the end of it:</p>
<blockquote>
<p>If [the issue persists], no matter what, then you can reduce the communication speed of the internal I2C bus of the UHK, making it more stable. The default value is 100000. You can half the communication speed by running the following smart macro commands, preferably in the <code>$onInit</code> macro:</p>
<pre tabindex="0"><code>set macroEngine.extendedCommands 1
set i2cBaudRate 500000
</code></pre><p>which should make communication more stable, but the smaller the value, the less responsive your UHK will get, which you will notice below a certain value</p>
</blockquote>
<p>I&rsquo;ve now applied that through the UHK Agent software, and so far (after a test call to my phone located in the same position as on Friday), things seem stable. Time will tell if this is a permanent fix, but I&rsquo;m hopeful. And I&rsquo;m glad I found this information, as it explains the issue and gives me a way to mitigate it (I might add some aluminum foil to the bottom of the left half too, as suggested).</p>
<p>I&rsquo;m also glad that UHK is aware of the issue and working on a fix for future versions. I&rsquo;m looking forward to that, as I really like the UHK otherwise.</p>
]]></content:encoded></item><item><title>EDC 2024</title><link>https://foosel.net/blog/2024-08-08-edc-2024/</link><pubDate>Thu, 08 Aug 2024 00:00:00 +0000</pubDate><guid>https://foosel.net/blog/2024-08-08-edc-2024/</guid><description>My everyday carry of 2024</description><content:encoded><![CDATA[<p>The other day I was taking care of a fresh mosquito bite with the heat-it that&rsquo;s part of my EDC (&ldquo;everyday carry&rdquo;), and that reminded me that I&rsquo;d been meaning to do a quick write-up about my EDC for ages.</p>
<p>On top of my keys (with some meds and my second factor tokens) and my phone (currently a Pixel 6a) I usually carry a little pouch with me as pictured above.</p>
<p>Unpacking it fully, it looks like this:</p>
<p><img alt="Picture of the now empty pouch and its contents as described below" loading="lazy" src="/blog/2024-08-08-edc-2024/edc2024_annotated.jpg"></p>
<p>So what is all this stuff? Here we go:</p>
<ol>
<li>a full miniature RPG dice set (you never know when you need to roll on something)</li>
<li>64GB USB stick (mostly empty for impromptu file transfers, but also something to watch in case I have to entertain myself in a hotel or something)</li>
<li>Troika Liliput pen (ballpoint pen, ruler, stylus, screwdriver)</li>
<li>small pill tube with painkillers (I tend to get weather related headaches)</li>
<li>Leatherman Squirt PS4</li>
<li>small rechargeable LED flashlight (white light, red light &amp; UV, plus emergency flashing)</li>
<li>small lighter (I don&rsquo;t smoke, I just prefer to have a fire and emergency light source on me)</li>
<li>3d printed emergency whistle</li>
<li>small refreshment towel (in case I need to wash my hands but can&rsquo;t)</li>
<li>hygiene baggy &amp; tampon (in case I misjudge my cycle or need to help someone else who misjudged theirs)</li>
<li>two glucose sweets (always good to have something on hand for myself or others)</li>
<li>meds against motion sickness (which I sadly suffer from greatly, especially in cars I&rsquo;m not driving myself)</li>
<li>short USB-C to USB-A cable for charging my phone, plus a SIM tool</li>
<li>USB-C heat-it (little gadget I can plug into my phone and control from an app that helps with mosquito bites through heat - consider it a really portable form of the popular bite-away wand)</li>
<li>(not pictured) a key for a safe</li>
</ol>
<p>And that&rsquo;s it with regards to my EDC! I&rsquo;m currently thinking about sewing a custom pouch, as the store bought one I currently use could really use better zippers and a slightly different internal organisation, but that&rsquo;s a future project.</p>
<p>I&rsquo;ll do my best to also add a post about my portable solder kit soon 😁 In the meantime, feel free to get in touch <a href="https://chaos.social/@foosel">on Mastodon</a> for questions or comments!</p>
<p><em>edit</em> I forgot to label the USB stick earlier, this has now been fixed.</p>
]]></content:encoded></item><item><title>On the importance of time off</title><link>https://foosel.net/blog/2024-07-25-on-the-importance-of-time-off/</link><pubDate>Thu, 25 Jul 2024 00:00:00 +0000</pubDate><guid>https://foosel.net/blog/2024-07-25-on-the-importance-of-time-off/</guid><description>Why work related mentions on my personal accounts annoy me</description><content:encoded><![CDATA[<p>I&rsquo;m on day 4 of my desperately needed summer vacation<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>, and twice already I got pinged on my personal social media accounts about OctoPrint related stuff. This happens on every vacation that I take, and during shorter breaks like weekends or just after hours as well, so much so that I&rsquo;ve pinned a post about it to my Mastodon account (that now also cross references this very blog post):</p>















        
        

        
            
            
        

        
            <blockquote class="toot-blockquote" cite="https://chaos.social@foosel/status/109245619011351909">
                <div class="toot-header">
                    <a class="toot-profile" href="https://chaos.social/@foosel" rel="noopener">
                        <img
                            src="https://assets.chaos.social/accounts/avatars/000/235/099/original/a2e381e9aab4a693.png"
                            alt="Mastodon avatar for @foosel@chaos.social"
                            loading="lazy"
                        />
                    </a>
                    <div class="toot-author">
                        <a class="toot-author-name" href="https://chaos.social/@foosel" rel="noopener">Gina Häußge</a>
                        <a class="toot-author-handle" href="https://chaos.social/@foosel" rel="noopener">@foosel@chaos.social</a>
                    </div>
                </div>
                <p>Friendly reminder: This is my private Mastodon account which I also want to be able to read after work, during the weekends &amp; other time off without getting dragged back into work 😉 </p><p>See also: <a href="https://foosel.net/blog/2024-07-25-on-the-importance-of-time-off/" target="_blank" rel="nofollow noopener" translate="no"><span class="invisible">https://</span><span class="ellipsis">foosel.net/blog/2024-07-25-on-</span><span class="invisible">the-importance-of-time-off/</span></a></p><p>Thus, if you have any OctoPrint 🐙 related mentions, please direct them at <span class="h-card" translate="no"><a href="https://fosstodon.org/@octoprint" class="u-url mention">@<span>octoprint</span></a></span>. </p><p>For support, get in touch on the forum or check out the Discord server, see <a href="https://help.octoprint.org" target="_blank" rel="nofollow noopener" translate="no"><span class="invisible">https://</span><span class="">help.octoprint.org</span><span class="invisible"></span></a></p><p>Respect my right to have a life beyond OctoPrint, thank you 😊</p>
                
                
                
                <div class="toot-footer">
                    <a href="https://chaos.social/@foosel/109245619011351909" class="toot-date" rel="noopener">October 28, 2022, 11:17</a>&nbsp;<span class="pokey">(UTC)</span>
                </div>
            </blockquote>
        
    
<p>Contrary to what many people seem to think 😉 I indeed have a life beyond OctoPrint, and actually a lot of stuff I&rsquo;m interested in beyond 3d printing (shocking, I know!). Yet a lot of people out there think that as an Open Source maintainer I&rsquo;m available for anything concerning my project(s) 24/7. Even if OctoPrint wasn&rsquo;t the <em>incredibly</em> stressful and intense, albeit amazing job it is, this wouldn&rsquo;t be the case for me.</p>
<p><strong>Even if we love what we do, we need rest.</strong> Our brains need a chance to get out of the usual daily routine and we need to get some dopamine boosts from a change of rhythm. Stuff that got learned, stuff that caused harm, stuff that needed a lot of thinking, all of that needs time to get processed. That&rsquo;s why we (shouldn&rsquo;t) work the whole day, that&rsquo;s why we (should) have weekends, and that&rsquo;s why we also (should) have vacations where we do something else than what we do day to day for a living.</p>
<p>What I learned from several close calls with full blown burn-out over the past ~12 years of maintaining OctoPrint is that taking regular restful breaks is probably one of the most important things, maybe even <strong>the</strong> most important thing, I can do for the long term health of both myself and my projects. That&rsquo;s why not getting dragged back into work in general and OctoPrint specifically during my off times is a boundary that I will always defend hard<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>.</p>
<p>And that&rsquo;s what I can only recommend to anyone - maintainer or not. You have a right to restful breaks. And you cannot be at the peak of your performance if you are close to or already burnt out. Stand up for your time off! Fuck hustle culture!</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>It&rsquo;s disturbing how much energy this whole <a href="https://octoprint.org/blog/2024/06/28/stats-manipulation/">stats manipulation</a> crap sucked - <a href="https://octoprint.org/blog/2024/07/04/more-stats-manipulation/">twice</a>!&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>And let&rsquo;s face it, no one else will.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>How to check for cloud IPs in nginx</title><link>https://foosel.net/til/how-to-check-for-cloud-ips-in-nginx/</link><pubDate>Mon, 01 Jul 2024 00:00:00 +0000</pubDate><guid>https://foosel.net/til/how-to-check-for-cloud-ips-in-nginx/</guid><description>&lt;p&gt;I&amp;rsquo;m currently busy mitigating a &lt;a href="https://octoprint.org/blog/2024/06/28/stats-manipulation/"&gt;stats manipulation on OctoPrint&lt;/a&gt;, and one of the steps I&amp;rsquo;m taking is blocking off several cloud options from accessing the tracking endpoint - and &lt;em&gt;only&lt;/em&gt; that.&lt;/p&gt;
&lt;p&gt;Since we are talking about several thousand of IPs here in at least 1.5k of CIDR ranges, I was looking for the best way to do that that wouldn&amp;rsquo;t cause a lot of performance impact - the tracking server needs to be fast.&lt;/p&gt;</description><content:encoded><![CDATA[<p>I&rsquo;m currently busy mitigating a <a href="https://octoprint.org/blog/2024/06/28/stats-manipulation/">stats manipulation on OctoPrint</a>, and one of the steps I&rsquo;m taking is blocking off several cloud options from accessing the tracking endpoint - and <em>only</em> that.</p>
<p>Since we are talking about several thousand of IPs here in at least 1.5k of CIDR ranges, I was looking for the best way to do that that wouldn&rsquo;t cause a lot of performance impact - the tracking server needs to be fast.</p>
<p>A list of all CIDR ranges with <code>deny</code> turned out to not work thanks to my endpoint definition in nginx using <code>return</code> statements, and those are apparently evaluated before <code>allow</code> and <code>deny</code> statements. But then I got the hint to look at the <a href="https://nginx.org/en/docs/http/ngx_http_geo_module.html"><code>geo</code> module</a> and with that it was easy to build a map of IP ranges that should be matched and just combining that with an <code>if</code>.</p>
<p>For a first test I created a converter for <a href="https://github.com/PodderApps/ipcat">this list of IP ranges</a>, filtering for the really big players:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e">#!/bin/bash
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>URL<span style="color:#f92672">=</span>https://raw.githubusercontent.com/PodderApps/ipcat/main/datacenters.csv
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#f92672">[</span> $1 !<span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;&#34;</span> <span style="color:#f92672">]</span>; <span style="color:#66d9ef">then</span>
</span></span><span style="display:flex;"><span>	DATA<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>curl -s $URL | grep -E <span style="color:#e6db74">&#34;</span>$1<span style="color:#e6db74">&#34;</span><span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">else</span>
</span></span><span style="display:flex;"><span>	DATA<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>curl -s $URL<span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">fi</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>echo <span style="color:#e6db74">&#39;geo $is_cloud {&#39;</span>
</span></span><span style="display:flex;"><span>echo <span style="color:#e6db74">&#39;    default 0;&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">while</span> IFS<span style="color:#f92672">=</span> read -r line; <span style="color:#66d9ef">do</span>
</span></span><span style="display:flex;"><span>	start_ip<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>echo $line | cut -d, -f1<span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>	end_ip<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>echo $line | cut -d, -f2<span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>	comment<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>echo $line | cut -d, -f3<span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	script<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;import ipaddress; start_ip=ipaddress.IPv4Address(\&#34;</span>$start_ip<span style="color:#e6db74">\&#34;); end_ip=ipaddress.IPv4Address(\&#34;</span>$end_ip<span style="color:#e6db74">\&#34;); print(next(ipaddress.summarize_address_range(start_ip, end_ip)))&#34;</span>
</span></span><span style="display:flex;"><span>	cidr<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>python3 -c <span style="color:#e6db74">&#34;</span>$script<span style="color:#e6db74">&#34;</span><span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	echo <span style="color:#e6db74">&#34;    </span>$cidr<span style="color:#e6db74"> 1; # </span>$comment<span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">done</span> <span style="color:#f92672">&lt;&lt;&lt;</span> <span style="color:#e6db74">&#34;</span>$DATA<span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>echo <span style="color:#e6db74">&#39;}&#39;</span>
</span></span></code></pre></div><p>Calling this like this then created a <code>conf</code> file:</p>
<pre tabindex="0"><code class="language-prompt" data-lang="prompt">$ sudo ./generate_is_cloud_map &#34;AWS|DigitalOcean|Google&#34; &gt; /etc/nginx/snippets/is-cloud.conf
$ cat /etc/nginx/snippets/ip-cloud.conf
geo $is_cloud {
    default 0;
    3.0.0.0/15; # Amazon AWS
    # ...
}
</code></pre><p>which I then could use in my nginx <code>location</code> config through an include and an <code>if</code> statement:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-nginx" data-lang="nginx"><span style="display:flex;"><span><span style="color:#66d9ef">include</span> snippets/is-cloud.conf;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># ...
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">server</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">location</span> <span style="color:#e6db74">/mylocation</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">if</span> <span style="color:#e6db74">(</span>$is_cloud = <span style="color:#ae81ff">1</span><span style="color:#e6db74">)</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">return</span> <span style="color:#ae81ff">403</span>;
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># ...
</span></span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>For now this seems to work. I&rsquo;m going to give this a day or so and then look into further IP sources and also blocking off the IPv6 ranges.</p>
]]></content:encoded></item><item><title>How to quickly create a header modifying reverse proxy with mitmproxy</title><link>https://foosel.net/til/how-to-quickly-create-a-header-modifying-reverse-proxy-with-mitmproxy/</link><pubDate>Tue, 12 Mar 2024 00:00:00 +0000</pubDate><guid>https://foosel.net/til/how-to-quickly-create-a-header-modifying-reverse-proxy-with-mitmproxy/</guid><description>&lt;p&gt;I&amp;rsquo;m currently in the process of testing some changes on OctoPrint involving its automatic user login via request headers, and
for that needed to quickly set up a reverse proxy that would modify the headers of the requests going to the development server
for some quick testing.&lt;/p&gt;
&lt;p&gt;Specifically, I wanted a quick CLI tool that would allow me to set up a reverse proxy listening on port 5555, forwarding to
&lt;code&gt;http://localhost:5000&lt;/code&gt; while also setting the headers &lt;code&gt;X-Remote-User&lt;/code&gt; to &lt;code&gt;remote&lt;/code&gt; and &lt;code&gt;X-Remote-Host&lt;/code&gt; to &lt;code&gt;localhost:5555&lt;/code&gt;.&lt;/p&gt;</description><content:encoded><![CDATA[<p>I&rsquo;m currently in the process of testing some changes on OctoPrint involving its automatic user login via request headers, and
for that needed to quickly set up a reverse proxy that would modify the headers of the requests going to the development server
for some quick testing.</p>
<p>Specifically, I wanted a quick CLI tool that would allow me to set up a reverse proxy listening on port 5555, forwarding to
<code>http://localhost:5000</code> while also setting the headers <code>X-Remote-User</code> to <code>remote</code> and <code>X-Remote-Host</code> to <code>localhost:5555</code>.</p>
<p>Enter <a href="https://mitmproxy.org/"><code>mitmproxy</code></a>, or more specifically its <code>mitmdump</code> tool, which turned out to be a great tool for this job.</p>
<p>All I needed was to run the following command:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>mitmdump --mode reverse:http://localhost:5000@5555 --modify-headers <span style="color:#e6db74">&#34;/X-Remote-User/remote&#34;</span> --modify-headers <span style="color:#e6db74">&#34;/X-Forwarded-Host/localhost:5555&#34;</span>
</span></span></code></pre></div><p>This does the following:</p>
<ul>
<li><code>--mode reverse:http://localhost:5000@5555</code> sets up a reverse proxy listening on port 5555, forwarding to <code>http://localhost:5000</code></li>
<li><code>--modify-headers &quot;/X-Remote-User/remote&quot;</code> sets the <code>X-Remote-User</code> header to <code>remote</code></li>
<li><code>--modify-headers &quot;/X-Forwarded-Host/localhost:5555&quot;</code> sets the <code>X-Forwarded-Host</code> header to <code>localhost:5555</code></li>
</ul>
<p>With that the <a href="https://community.octoprint.org/t/reverse-proxy-configuration-examples/1107">reverse proxy test page in OctoPrint</a>
turned all green and I could test my changes without having to set up an actual reverse proxy in front of the development server.</p>
]]></content:encoded></item><item><title>How to print Deutsche Post stamps via the command line on a Brother QL label printer</title><link>https://foosel.net/til/how-to-print-deutsche-post-stamps-via-the-command-line-on-a-brother-ql-label-printer/</link><pubDate>Thu, 11 Jan 2024 00:00:00 +0000</pubDate><guid>https://foosel.net/til/how-to-print-deutsche-post-stamps-via-the-command-line-on-a-brother-ql-label-printer/</guid><description>&lt;p&gt;&lt;em&gt;Update from 2024-01-12: I&amp;rsquo;ve updated the scripts to support both 50mm and 62mm wide labels, and added some more whitespace trimming to the basic stamp. The post has been adjusted accordingly.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I recently acquired a &lt;a href="https://www.brother-usa.com/products/QL820NWB"&gt;Brother QL-820NWB label printer&lt;/a&gt; to be able to quickly create labels for boxes and such, and ideally also print out Deutsche Post&amp;rsquo;s &amp;ldquo;print yourself&amp;rdquo; stamps with it. The Deutsche Post stamp shop allows me to download PDFs targeting the 62mm wide endless labels for that printer, for the two types of stamps I&amp;rsquo;m interested in (stamp, and address label with stamp). But my attempts in printing those directly to the printer through Gnome&amp;rsquo;s printer integration weren&amp;rsquo;t successful, things were too small, the cutter didn&amp;rsquo;t work etc.&lt;/p&gt;</description><content:encoded><![CDATA[<p><em>Update from 2024-01-12: I&rsquo;ve updated the scripts to support both 50mm and 62mm wide labels, and added some more whitespace trimming to the basic stamp. The post has been adjusted accordingly.</em></p>
<p>I recently acquired a <a href="https://www.brother-usa.com/products/QL820NWB">Brother QL-820NWB label printer</a> to be able to quickly create labels for boxes and such, and ideally also print out Deutsche Post&rsquo;s &ldquo;print yourself&rdquo; stamps with it. The Deutsche Post stamp shop allows me to download PDFs targeting the 62mm wide endless labels for that printer, for the two types of stamps I&rsquo;m interested in (stamp, and address label with stamp). But my attempts in printing those directly to the printer through Gnome&rsquo;s printer integration weren&rsquo;t successful, things were too small, the cutter didn&rsquo;t work etc.</p>
<p>I knew that printing to the printer via my local instance of <a href="https://github.com/pklaus/brother_ql_web">brother_ql_web</a> works flawlessly, and that the library this is based on, <a href="https://github.com/pklaus/brother_ql">brother_ql</a>, has a command line interface. So I thought, why not just convert the PDFs to individual PNGs, and then print those?</p>
<p>Through the magic of some shell scripting, I&rsquo;m now able to do just that, right from the command line.</p>
<p>I installed the <code>brother_ql</code> Python package via pip:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>pip install --user brother_ql
</span></span></code></pre></div><p>I had to do a little manual patch to make it work with the latest versions of the required Pillow dependency, by editing <code>brother_ql/conversion.py</code> and changing <code>Image.ANTIALIAS</code> to <code>Image.LANCZOS</code>.</p>
<p>I also made sure my <code>.bash_profile</code> contains the address and model of my printer:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>export BROTHER_QL_PRINTER<span style="color:#f92672">=</span>tcp://192.168.x.x
</span></span><span style="display:flex;"><span>export BROTHER_QL_MODEL<span style="color:#f92672">=</span>QL-820NWB
</span></span></code></pre></div><p>Then I created two shell scripts, one for printing stamps and one for printing labels.</p>
<p>The first one, <code>porto_print</code>, takes care of printing the stamps. It resizes, trims, adds a new border and then extends to the native width for the selected label size (50mm by default, or 62mm if requested) while keeping a right alignment:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e">#!/bin/bash
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Usage:</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#   porto_print &lt;pdf&gt; [50|62]</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#!/bin/bash</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>tmpdir<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>mktemp -d<span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>cleanup<span style="color:#f92672">()</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>  rm -rf <span style="color:#e6db74">&#34;</span>$tmpdir<span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">}</span>
</span></span><span style="display:flex;"><span>trap cleanup EXIT
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>PDF<span style="color:#f92672">=</span>$1
</span></span><span style="display:flex;"><span>LABEL<span style="color:#f92672">=</span><span style="color:#e6db74">${</span>2<span style="color:#66d9ef">:-</span>50<span style="color:#e6db74">}</span>
</span></span><span style="display:flex;"><span>PNG<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>basename <span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>PDF%.*<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span><span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">case</span> $LABEL in
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;50&#34;</span><span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>    WIDTH<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;554&#34;</span>
</span></span><span style="display:flex;"><span>    ;;
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;62&#34;</span><span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>    WIDTH<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;696&#34;</span>
</span></span><span style="display:flex;"><span>    ;;
</span></span><span style="display:flex;"><span>  *<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>    echo <span style="color:#e6db74">&#34;Unsupported label size: </span>$LABEL<span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>    exit -1
</span></span><span style="display:flex;"><span>    ;;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">esac</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>echo <span style="color:#e6db74">&#34;Converting PDF to individual PNGs...&#34;</span>
</span></span><span style="display:flex;"><span>pdftoppm <span style="color:#e6db74">&#34;</span>$PDF<span style="color:#e6db74">&#34;</span> <span style="color:#e6db74">&#34;</span>$tmpdir<span style="color:#e6db74">/</span>$PNG<span style="color:#e6db74">&#34;</span> -png -r <span style="color:#ae81ff">600</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> file in <span style="color:#66d9ef">$(</span>ls $tmpdir/*.png<span style="color:#66d9ef">)</span>; <span style="color:#66d9ef">do</span>
</span></span><span style="display:flex;"><span>  echo <span style="color:#e6db74">&#34;Printing </span>$file<span style="color:#e6db74">...&#34;</span>
</span></span><span style="display:flex;"><span>  mogrify -background white -bordercolor white -resize 696x -trim -border 25x25 -gravity east -extent <span style="color:#e6db74">${</span>WIDTH<span style="color:#e6db74">}</span>x284 <span style="color:#e6db74">&#34;</span>$file<span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>  brother_ql print -l $LABEL <span style="color:#e6db74">&#34;</span>$file<span style="color:#e6db74">&#34;</span> 
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">done</span>
</span></span></code></pre></div><p>The second one, <code>porto_address_print</code>, does basically the same, just with slightly different parameters and left alignment:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e">#!/bin/bash
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Usage:</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">#   porto_address_print &lt;pdf&gt;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>tmpdir<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>mktemp -d<span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>cleanup<span style="color:#f92672">()</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>  rm -rf <span style="color:#e6db74">&#34;</span>$tmpdir<span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">}</span>
</span></span><span style="display:flex;"><span>trap cleanup EXIT
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>PDF<span style="color:#f92672">=</span>$1
</span></span><span style="display:flex;"><span>LABEL<span style="color:#f92672">=</span><span style="color:#e6db74">${</span>2<span style="color:#66d9ef">:-</span>50<span style="color:#e6db74">}</span>
</span></span><span style="display:flex;"><span>PNG<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>basename <span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>PDF%.*<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span><span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">case</span> $LABEL in
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;50&#34;</span><span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>    WIDTH<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;554&#34;</span>
</span></span><span style="display:flex;"><span>    ;;
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;62&#34;</span><span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>    WIDTH<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;696&#34;</span>
</span></span><span style="display:flex;"><span>    ;;
</span></span><span style="display:flex;"><span>  *<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>    echo <span style="color:#e6db74">&#34;Unsupported label size: </span>$LABEL<span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>    exit -1
</span></span><span style="display:flex;"><span>    ;;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">esac</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>echo <span style="color:#e6db74">&#34;Converting PDF to individual PNGs...&#34;</span>
</span></span><span style="display:flex;"><span>pdftoppm <span style="color:#e6db74">&#34;</span>$PDF<span style="color:#e6db74">&#34;</span> <span style="color:#e6db74">&#34;</span>$tmpdir<span style="color:#e6db74">/</span>$PNG<span style="color:#e6db74">&#34;</span> -png -r <span style="color:#ae81ff">600</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">for</span> file in <span style="color:#66d9ef">$(</span>ls $tmpdir/*.png<span style="color:#66d9ef">)</span>; <span style="color:#66d9ef">do</span>
</span></span><span style="display:flex;"><span>  echo <span style="color:#e6db74">&#34;Printing </span>$file<span style="color:#e6db74">...&#34;</span>
</span></span><span style="display:flex;"><span>  mogrify -bordercolor white -background white -resize 696x -trim -border 25x25 -gravity West -extent <span style="color:#e6db74">${</span>WIDTH<span style="color:#e6db74">}</span>x839 <span style="color:#e6db74">&#34;</span>$file<span style="color:#e6db74">&#34;</span> 
</span></span><span style="display:flex;"><span>  brother_ql print -l $LABEL <span style="color:#e6db74">&#34;</span>$file<span style="color:#e6db74">&#34;</span> 
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">done</span>
</span></span></code></pre></div><p>Both of these were placed under <code>~/.local/bin</code> and made executable. I can now call them both from anywhere on the command line, just passing the path to the PDF to print.</p>
<p>The result is one or more nicely printed stamps or address labels, ready to be stuck to an envelope:</p>
<p><img alt="Printed stamp and printed stamp with address label, freshly printed from example files through the two scripts" loading="lazy" src="/til/how-to-print-deutsche-post-stamps-via-the-command-line-on-a-brother-ql-label-printer/result.jpg"></p>
<p>Now, this should hopefully make it easier for me to print all those address labels for OctoPrint sticker shipments in the future ;) Next step: Automated QR code labels for the various boxes on my shelves ^^</p>
]]></content:encoded></item><item><title>How to force paperless-ngx to consume signed PDFs</title><link>https://foosel.net/til/how-to-force-paperless-to-consume-signed-pdfs/</link><pubDate>Wed, 06 Dec 2023 00:00:00 +0000</pubDate><guid>https://foosel.net/til/how-to-force-paperless-to-consume-signed-pdfs/</guid><description>&lt;p&gt;I use &lt;a href=""&gt;paperless-ngx&lt;/a&gt; to manage my documents, together with some rules that automaticaly
ingest PDFs from my mail boxes. However, I noticed that a recently received invoice from AWS
was not ingested as expected. Looking at the logs I found this error message for it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;invoice.pdf: Error occurred while consuming document invoice.pdf: DigitalSignatureError: Input PDF has a digital signature. OCR would alter the document,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;invalidating the signature.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I don&amp;rsquo;t know if a software update brought this refusal to run OCR on signed PDFs, or if AWS
simply so long didn&amp;rsquo;t send me signed PDFs, but I needed to find a way to force paperless to
ingest signed things as well as having all of that stuff stored in paperless is a vital part
of my accounting workflow.&lt;/p&gt;</description><content:encoded><![CDATA[<p>I use <a href="">paperless-ngx</a> to manage my documents, together with some rules that automaticaly
ingest PDFs from my mail boxes. However, I noticed that a recently received invoice from AWS
was not ingested as expected. Looking at the logs I found this error message for it:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>invoice.pdf: Error occurred while consuming document invoice.pdf: DigitalSignatureError: Input PDF has a digital signature. OCR would alter the document,
</span></span><span style="display:flex;"><span>invalidating the signature.
</span></span></code></pre></div><p>I don&rsquo;t know if a software update brought this refusal to run OCR on signed PDFs, or if AWS
simply so long didn&rsquo;t send me signed PDFs, but I needed to find a way to force paperless to
ingest signed things as well as having all of that stuff stored in paperless is a vital part
of my accounting workflow.</p>
<p>A quick search for the error message brought me to
<a href="https://github.com/paperless-ngx/paperless-ngx/discussions/4047">this discussion on the paperless-ngx GitHub repository</a>
and therein I also found the <a href="https://github.com/paperless-ngx/paperless-ngx/discussions/4047#discussioncomment-7019544">solution</a>,
which is to set the <code>PAPERLESS_OCR_USER_ARGS</code> config option to
<code>{&quot;invalidate_digital_signatures&quot;: true}</code>.</p>
<p>As I run paperless via Docker I needed to add the following to the <code>environment</code> section in my paperless
<code>docker-compose.yml</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">services</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#75715e"># ...</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">paperless</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># ...</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">environment</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#75715e"># ...</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">PAPERLESS_OCR_USER_ARGS</span>: <span style="color:#e6db74">&#39;{&#34;invalidate_digital_signatures&#34;: true}&#39;</span>
</span></span><span style="display:flex;"><span>      <span style="color:#75715e"># ...</span>
</span></span></code></pre></div><p>And after adding this and a quick <code>docker compose up -d</code> things seem to now work as expected
again. Yay!</p>
]]></content:encoded></item><item><title>How to fix GRUB after a SteamOS update</title><link>https://foosel.net/til/how-to-fix-grub-after-steamos-update/</link><pubDate>Sat, 25 Nov 2023 00:00:00 +0000</pubDate><guid>https://foosel.net/til/how-to-fix-grub-after-steamos-update/</guid><description>&lt;p&gt;My partner just ran into an issue after updating his SteamDeck to the latest SteamOS version (3.4.x to 3.5.7).&lt;/p&gt;
&lt;p&gt;He has a dual boot setup running using &lt;a href="https://github.com/jlobue10/SteamDeck_rEFInd"&gt;rEFInd&lt;/a&gt;, and while that survived the OS update just fine, when he wanted to return to SteamOS after a quick stint in Windows today, he was greeted by a GRUB boot menu.&lt;/p&gt;
&lt;p&gt;Detective foosel to the rescue.&lt;/p&gt;
&lt;p&gt;Attempting to boot the SteamOS entry in grub resulted in an error like this (with another device UUID):&lt;/p&gt;</description><content:encoded><![CDATA[<p>My partner just ran into an issue after updating his SteamDeck to the latest SteamOS version (3.4.x to 3.5.7).</p>
<p>He has a dual boot setup running using <a href="https://github.com/jlobue10/SteamDeck_rEFInd">rEFInd</a>, and while that survived the OS update just fine, when he wanted to return to SteamOS after a quick stint in Windows today, he was greeted by a GRUB boot menu.</p>
<p>Detective foosel to the rescue.</p>
<p>Attempting to boot the SteamOS entry in grub resulted in an error like this (with another device UUID):</p>
<pre tabindex="0"><code>error: no such device: aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee
error: file `/boot/vmlinuz-linux-neptune` not found.
error: you need to load the kernel first.

Press any key to continue...
</code></pre><p>So it couldn&rsquo;t find it&rsquo;s boot device and due to that also not the kernel stored thereon.</p>
<p>Entering the Deck&rsquo;s boot manager and manually booting <code>\efi\steamos\steamoscl.efi</code> also led to the same situation.</p>
<p>I was able to still boot into SteamOS via the fallback entry however (which also had a different boot device UUID).</p>
<p>And it took me way too long<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> to simply just get the idea to let Linux update its GRUB entries:</p>
<pre tabindex="0"><code>sudo update-grub
</code></pre><p>That fixed it.</p>
<p>No idea if the dual boot setup played a roll in this mess or if it was just some random hiccup, my Deck&rsquo;s update went without a hitch 🤷‍♀️ But if it happens again I now have this entry to check 😁</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>I reinstalled rEFInd, rebuilt the EFI entries (<code>sudo efibootmgr -c -d /dev/nvme0n1 -p 1 -L &quot;SteamOS&quot; -l \\efi\\steamos\\steamcl.efi</code>) and the initramfs files (<code>mkinitcpio -P</code>) before getting the idea to maybe start at the bottom instead of the top.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>How to fix VirtualBox on Fedora 38 with Kernel 6.4.10+ by adding a missing include</title><link>https://foosel.net/til/how-to-fix-virtualbox-on-fedora-38-with-kernel-6410/</link><pubDate>Thu, 07 Sep 2023 00:00:00 +0000</pubDate><guid>https://foosel.net/til/how-to-fix-virtualbox-on-fedora-38-with-kernel-6410/</guid><description>&lt;p&gt;I recently did a software update on my laptop running Fedora 38, and that also brought in a kernel update. Starting my Win10 VirtualBox VM afterwards no longer worked as it needed the kernel module to be recompiled. However, that failed:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ sudo /sbin/vboxconfig
[sudo] password for gina:
vboxdrv.sh: Stopping VirtualBox services.
depmod: WARNING: could not open modules.order at /lib/modules/6.3.8-200.fc38.x86_64: No such file or directory
depmod: WARNING: could not open modules.builtin at /lib/modules/6.3.8-200.fc38.x86_64: No such file or directory
depmod: WARNING: could not open modules.builtin.modinfo at /lib/modules/6.3.8-200.fc38.x86_64: No such file or directory
vboxdrv.sh: Starting VirtualBox services.
vboxdrv.sh: Building VirtualBox kernel modules.
egrep: warning: egrep is obsolescent; using grep -E
vboxdrv.sh: failed: Look at /var/log/vbox-setup.log to find out what went wrong.
There were problems setting up VirtualBox. To re-start the set-up process, run
/sbin/vboxconfig
as root. If your system is using EFI Secure Boot you may need to sign the
kernel modules (vboxdrv, vboxnetflt, vboxnetadp, vboxpci) before you can load
them. Please see your Linux system&amp;#39;s documentation for more information.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;A look into &lt;code&gt;/var/log/vbox-setup.log&lt;/code&gt; revealed an error along the lines of this one&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;</description><content:encoded><![CDATA[<p>I recently did a software update on my laptop running Fedora 38, and that also brought in a kernel update. Starting my Win10 VirtualBox VM afterwards no longer worked as it needed the kernel module to be recompiled. However, that failed:</p>
<pre tabindex="0"><code>$ sudo /sbin/vboxconfig 
[sudo] password for gina: 
vboxdrv.sh: Stopping VirtualBox services.
depmod: WARNING: could not open modules.order at /lib/modules/6.3.8-200.fc38.x86_64: No such file or directory
depmod: WARNING: could not open modules.builtin at /lib/modules/6.3.8-200.fc38.x86_64: No such file or directory
depmod: WARNING: could not open modules.builtin.modinfo at /lib/modules/6.3.8-200.fc38.x86_64: No such file or directory
vboxdrv.sh: Starting VirtualBox services.
vboxdrv.sh: Building VirtualBox kernel modules.
egrep: warning: egrep is obsolescent; using grep -E
vboxdrv.sh: failed: Look at /var/log/vbox-setup.log to find out what went wrong.

There were problems setting up VirtualBox.  To re-start the set-up process, run
  /sbin/vboxconfig
as root.  If your system is using EFI Secure Boot you may need to sign the
kernel modules (vboxdrv, vboxnetflt, vboxnetadp, vboxpci) before you can load
them. Please see your Linux system&#39;s documentation for more information.
</code></pre><p>A look into <code>/var/log/vbox-setup.log</code> revealed an error along the lines of this one<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>:</p>
<pre tabindex="0"><code>/tmp/akmodsbuild.bPlgZsDR/BUILD/VirtualBox-kmod-7.0.10/_kmod_build_6.4.10-200.fc38.x86_64/vboxnetflt/linux/VBoxNetFlt-linux.c: In function &#39;vboxNetFltLinuxForwardToIntNetInner&#39;:
/tmp/akmodsbuild.bPlgZsDR/BUILD/VirtualBox-kmod-7.0.10/_kmod_build_6.4.10-200.fc38.x86_64/vboxnetflt/linux/VBoxNetFlt-linux.c:1570:40: error: implicit declaration of function &#39;skb_gso_segment&#39;; did you mean &#39;skb_gso_reset&#39;? [-Werror=implicit-function-declaration]
 1570 |             struct sk_buff *pSegment = skb_gso_segment(pBuf, 0 /*supported features*/);
      |                                        ^~~~~~~~~~~~~~~
      |                                        skb_gso_reset
/tmp/akmodsbuild.bPlgZsDR/BUILD/VirtualBox-kmod-7.0.10/_kmod_build_6.4.10-200.fc38.x86_64/vboxnetflt/linux/VBoxNetFlt-linux.c:1570:40: warning: initialization of &#39;struct sk_buff *&#39; from &#39;int&#39; makes pointer from integer without a cast [-Wint-conversion]
cc1: some warnings being treated as errors
</code></pre><p>I did some web searching and came across <a href="https://discussion.fedoraproject.org/t/87492">this post on the Fedora forums</a> with someone having the exact same issue, and found <a href="https://discussion.fedoraproject.org/t/6-4-10-200-fc38-x86-64-created-problems-with-virtual-box/87492/12">a solution in the comments courtesy of Peter Francis</a>:</p>
<blockquote>
<p>Adding</p>
<pre><code>#include &lt;net/gso.h&gt;
</code></pre>
<p>below the line</p>
<pre><code>#include &lt;linux/inetdevice.h&gt;
</code></pre>
<p>in <code>/usr/share/virtualbox/src/vboxhost/vboxnetflt/linux/VBoxNetFlt-linux.c</code> fixed it for me.</p>
<p>Hopefully when the file gets overwritten on next VirtualBox update the fix will be already added by VirtualBox’s programmers.</p>
</blockquote>
<p>And what can I say, it also fixed it for me! And should this not get fixed in the next update, now I&rsquo;ll know where to find the solution again - my own TIL post 😉</p>
<p>PS: Something tells me this won&rsquo;t be the last VirtualBox related TIL post I&rsquo;ll write&hellip;</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>This is copy-pasted from someone else, as my log file got overwritten by the successful compile later. However it looked very much like this error, apart from my Kernel already being at 6.4.13.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>How to monitor network traffic on my USG via Wireshark</title><link>https://foosel.net/til/how-to-monitor-network-traffic-on-my-usg-via-wireshark/</link><pubDate>Mon, 28 Aug 2023 00:00:00 +0000</pubDate><guid>https://foosel.net/til/how-to-monitor-network-traffic-on-my-usg-via-wireshark/</guid><description>&lt;p&gt;I&amp;rsquo;m currently trying to figure out some internal network issues&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt; and for that need to monitor the traffic of a specific device on my network. I&amp;rsquo;m using a Unifi USG as my router (behind the ISP&amp;rsquo;s Fritzbox that I consider hostile since it&amp;rsquo;s not mine). I found &lt;a href="https://www.reddit.com/r/Ubiquiti/comments/ar444z/what_is_the_best_way_to_monitor_traffic_of_a/egkv91p/"&gt;this post on reddit&lt;/a&gt; that explains how to capture traffic on the USG via &lt;code&gt;tcpdump&lt;/code&gt; and send it through the SSH session to Wireshark on my laptop:&lt;/p&gt;</description><content:encoded><![CDATA[<p>I&rsquo;m currently trying to figure out some internal network issues<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> and for that need to monitor the traffic of a specific device on my network. I&rsquo;m using a Unifi USG as my router (behind the ISP&rsquo;s Fritzbox that I consider hostile since it&rsquo;s not mine). I found <a href="https://www.reddit.com/r/Ubiquiti/comments/ar444z/what_is_the_best_way_to_monitor_traffic_of_a/egkv91p/">this post on reddit</a> that explains how to capture traffic on the USG via <code>tcpdump</code> and send it through the SSH session to Wireshark on my laptop:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>ssh admin@192.168.1.1 <span style="color:#e6db74">&#39;sudo tcpdump -f -i eth1 -w - src 192.168.1.12&#39;</span> | wireshark -k -i - 
</span></span></code></pre></div><p>I could confirm that this works and created a small script to make it easier to use by throwing this into <code>~/.local/bin/gatedump</code><sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e">#!/bin/bash
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>ARGS<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;</span>$@<span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>TCPDUMP<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;sudo tcpdump -f -w - </span>$ARGS<span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>ssh usg <span style="color:#e6db74">&#34;</span>$TCPDUMP<span style="color:#e6db74">&#34;</span> | wireshark -k -i -
</span></span></code></pre></div><p>This now allows me to easily run <code>tcpdump</code> remotely with custom arguments, e.g. <code>gatedump -i eth1 host 192.168.1.123</code>, and have it fire up Wireshark automatically. Wish me luck I&rsquo;ll now be able to figure out what&rsquo;s going on on my network, because it&rsquo;s driving me up the wall.</p>
<p><em>Update from 2023-12-06</em>: In case you are wondering how this story ended, the issue resolved itself with the next OS update of my partner&rsquo;s iPhone. So whatever caused it, it&rsquo;s gone now, and I hope for good.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>The iPhone of my partner seems to do something that makes my ISP&rsquo;s router freak out and drop packets every couple of minutes. No issue when he&rsquo;s not here or doesn&rsquo;t have it connected to the WiFi, immediate packet loss when it&rsquo;s on the WiFi. It started at the start of this month and we are both currently out of explanations.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p><code>ssh usg</code> does automatically use the correct host, port and user thanks to an entry in <code>~/.ssh/config</code>.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>How to make MkDocs support site_url relative URLs</title><link>https://foosel.net/til/how-to-make-mkdocs-support-siteurl-relative-urls/</link><pubDate>Thu, 27 Jul 2023 00:00:00 +0000</pubDate><guid>https://foosel.net/til/how-to-make-mkdocs-support-siteurl-relative-urls/</guid><description>&lt;p&gt;I&amp;rsquo;m currently &lt;em&gt;finally&lt;/em&gt; back on converting the &lt;a href="https://octoprint.org"&gt;OctoPrint&lt;/a&gt; docs to using Markdown and &lt;a href="https://mkdocs.org"&gt;MkDocs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Since I have some images in the docs that I want to be able to reference without having to use relative URLs (&lt;code&gt;../../../../images/&lt;/code&gt;),
especially since that would tie things in OctoPrint&amp;rsquo;s source tree structure too close to things in its documentation tree structure that
might or might not end up being in a different repository in the future, I needed a way to use absolute URLs here (&lt;code&gt;/images/&lt;/code&gt;). But
since the docs will most likely also end up being hosted on a version specific subpath of &lt;code&gt;docs.octoprint.org&lt;/code&gt;, just using
(host) absolute URLs would not work either and break.&lt;/p&gt;</description><content:encoded><![CDATA[<p>I&rsquo;m currently <em>finally</em> back on converting the <a href="https://octoprint.org">OctoPrint</a> docs to using Markdown and <a href="https://mkdocs.org">MkDocs</a>.</p>
<p>Since I have some images in the docs that I want to be able to reference without having to use relative URLs (<code>../../../../images/</code>),
especially since that would tie things in OctoPrint&rsquo;s source tree structure too close to things in its documentation tree structure that
might or might not end up being in a different repository in the future, I needed a way to use absolute URLs here (<code>/images/</code>). But
since the docs will most likely also end up being hosted on a version specific subpath of <code>docs.octoprint.org</code>, just using
(host) absolute URLs would not work either and break.</p>
<p>There&rsquo;s <a href="https://github.com/mkdocs/mkdocs/issues/1592">several</a> <a href="https://github.com/mkdocs/mkdocs/issues/192">issues</a> on the MkDocs issue
tracker about workflow problems caused by this, but the suggested workarounds like using <a href="https://mkdocs-macros-plugin.readthedocs.io/">macros</a>
to prefix a variable&rsquo;s contents to related URLs didn&rsquo;t work for me due to me also heavily relying on <a href="mkdocstrings.github.io/">mkdocstrings</a>,
and anything contained in docstrings is not processed by macros<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>.</p>
<p>So I got the idea to implement a minimal MkDocs plugin that would just turn all URLs contained in <code>href</code> and <code>src</code> attributes
that are prefixed with a custom schema <code>site:</code> schema into <em>site relative</em> URLs, with this effect. Example:</p>
<table>
  <thead>
      <tr>
          <th>URL</th>
          <th>site_url</th>
          <th>resulting URL</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>site:images/foo.png</code></td>
          <td><code>https://docs.octoprint.org/</code></td>
          <td><code>/images/foo.png</code></td>
      </tr>
      <tr>
          <td><code>site:images/foo.png</code></td>
          <td><code>https://docs.octoprint.org/1.9.x/</code></td>
          <td><code>/1.9.x/images/foo.png</code></td>
      </tr>
  </tbody>
</table>
<p>Using a <a href="https://www.mkdocs.org/user-guide/configuration/#hooks">hook</a> I could register a callback for the
<a href="https://www.mkdocs.org/dev-guide/plugins/#on_page_content"><code>on_page_content</code></a> event that would then replace all URLs as needed
in the generated page HTML.</p>
<p>And this is the resulting <code>site_urls.py</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">import</span> logging
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> urllib.parse
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> re
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> mkdocs.plugins
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>log <span style="color:#f92672">=</span> logging<span style="color:#f92672">.</span>getLogger(<span style="color:#e6db74">&#34;mkdocs&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>SITE_URLS_REGEX <span style="color:#f92672">=</span> re<span style="color:#f92672">.</span>compile(<span style="color:#e6db74">r</span><span style="color:#e6db74">&#39;(href|src)=&#34;site:([^&#34;]+)&#34;&#39;</span>, re<span style="color:#f92672">.</span>IGNORECASE)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">@mkdocs.plugins.event_priority</span>(<span style="color:#ae81ff">50</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">on_page_content</span>(html, page, config, files):
</span></span><span style="display:flex;"><span>    site_url <span style="color:#f92672">=</span> config[<span style="color:#e6db74">&#34;site_url&#34;</span>]
</span></span><span style="display:flex;"><span>    path <span style="color:#f92672">=</span> urllib<span style="color:#f92672">.</span>parse<span style="color:#f92672">.</span>urlparse(site_url)<span style="color:#f92672">.</span>path
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#f92672">not</span> path:
</span></span><span style="display:flex;"><span>        path <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;/&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#f92672">not</span> path<span style="color:#f92672">.</span>endswith(<span style="color:#e6db74">&#34;/&#34;</span>):
</span></span><span style="display:flex;"><span>        path <span style="color:#f92672">+=</span> <span style="color:#e6db74">&#34;/&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">_replace</span>(<span style="color:#66d9ef">match</span>):
</span></span><span style="display:flex;"><span>        param <span style="color:#f92672">=</span> <span style="color:#66d9ef">match</span><span style="color:#f92672">.</span>group(<span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>        url <span style="color:#f92672">=</span> <span style="color:#66d9ef">match</span><span style="color:#f92672">.</span>group(<span style="color:#ae81ff">2</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> url<span style="color:#f92672">.</span>startswith(<span style="color:#e6db74">&#34;/&#34;</span>):
</span></span><span style="display:flex;"><span>            url <span style="color:#f92672">=</span> url[<span style="color:#ae81ff">1</span>:]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        log<span style="color:#f92672">.</span>info(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;Replacing site:</span><span style="color:#e6db74">{</span>match<span style="color:#f92672">.</span>group(<span style="color:#ae81ff">2</span>)<span style="color:#e6db74">}</span><span style="color:#e6db74"> with </span><span style="color:#e6db74">{</span>path<span style="color:#e6db74">}{</span>url<span style="color:#e6db74">}</span><span style="color:#e6db74">...&#34;</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#e6db74">f</span><span style="color:#e6db74">&#39;</span><span style="color:#e6db74">{</span>param<span style="color:#e6db74">}</span><span style="color:#e6db74">=&#34;</span><span style="color:#e6db74">{</span>path<span style="color:#e6db74">}{</span>url<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> SITE_URLS_REGEX<span style="color:#f92672">.</span>sub(_replace, html)
</span></span></code></pre></div><p>that I&rsquo;ve registered as a hook in my <code>mkdocs.yaml</code> like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">hooks</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">site_urls.py</span>
</span></span></code></pre></div><p>Seems to work just fine, both for images and links! 😄</p>
<p><strong>Update 2023-07-28</strong>: I&rsquo;ve now published this as a proper plugin on PyPI, see <a href="https://pypi.org/project/mkdocs-site-urls/">mkdocs-site-urls</a>.
With that, all you need to do - given you are already on MkDocs 1.5 or newer - is installing the plugin via <code>pip install mkdocs-site-urls</code> and
then adding this to your <code>mkdocs.yaml</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">plugins</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#ae81ff">site-urls</span>
</span></span></code></pre></div><div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>As I found out when I wanted to add a <code>version_added</code> macro, which simply didn&rsquo;t render.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>Why ESPHome might fail compiling a custom component with 'fatal error: string: No such file or directory'</title><link>https://foosel.net/til/why-esphome-might-fail-compiling-a-custom-component/</link><pubDate>Fri, 14 Jul 2023 00:00:00 +0000</pubDate><guid>https://foosel.net/til/why-esphome-might-fail-compiling-a-custom-component/</guid><description>&lt;p&gt;I just spent several hours trying to figure out why ESPHome refused to compile a custom component I was working on. The error message I got was&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Compiling .pioenvs/datenzwerg-sleepy/src/esphome/components/sound_pressure/sound_pressure_sensor.c.o
In file included from src/esphome/components/sound_pressure/sound_pressure_sensor.h:3,
from src/esphome/components/sound_pressure/sound_pressure_sensor.c:1:
src/esphome/core/component.h:3:10: fatal error: string: No such file or directory
3 | #include &amp;lt;string&amp;gt;
| ^~~~~~~~
compilation terminated.
Compiling .pioenvs/datenzwerg-sleepy/src/main.cpp.o
*** [.pioenvs/datenzwerg-sleepy/src/esphome/components/sound_pressure/sound_pressure_sensor.c.o] Error 1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Other external and internal components compiled just fine, so that was quite a head scratcher, until I just &lt;em&gt;finally&lt;/em&gt; noticed something in my source tree: my custom component&amp;rsquo;s source file had the file ending &lt;code&gt;.c&lt;/code&gt; instead of &lt;code&gt;.cpp&lt;/code&gt;. And that caused all of this, a quick rename from &lt;code&gt;sound_pressure_sensor.c&lt;/code&gt; to &lt;code&gt;sound_pressure_sensor.cpp&lt;/code&gt; resolved the compilation error 🤦‍♀️&lt;/p&gt;</description><content:encoded><![CDATA[<p>I just spent several hours trying to figure out why ESPHome refused to compile a custom component I was working on. The error message I got was</p>
<pre tabindex="0"><code>Compiling .pioenvs/datenzwerg-sleepy/src/esphome/components/sound_pressure/sound_pressure_sensor.c.o
In file included from src/esphome/components/sound_pressure/sound_pressure_sensor.h:3,
                 from src/esphome/components/sound_pressure/sound_pressure_sensor.c:1:
src/esphome/core/component.h:3:10: fatal error: string: No such file or directory
    3 | #include &lt;string&gt;
      |          ^~~~~~~~
compilation terminated.
Compiling .pioenvs/datenzwerg-sleepy/src/main.cpp.o
*** [.pioenvs/datenzwerg-sleepy/src/esphome/components/sound_pressure/sound_pressure_sensor.c.o] Error 1
</code></pre><p>Other external and internal components compiled just fine, so that was quite a head scratcher, until I just <em>finally</em> noticed something in my source tree: my custom component&rsquo;s source file had the file ending <code>.c</code> instead of <code>.cpp</code>. And that caused all of this, a quick rename from <code>sound_pressure_sensor.c</code> to <code>sound_pressure_sensor.cpp</code> resolved the compilation error 🤦‍♀️</p>
<p>And since it was utterly impossible for me to find anything about this particular problem online, I decided to share my mishap with all of you here, so that in the future someone else doing an internet search for this will have more luck than me today and not waste hours on this 😅</p>
]]></content:encoded></item><item><title>How to fix VirtualBox on Fedora 38 with Kernel 6.3.5 by disabling IBT</title><link>https://foosel.net/til/how-to-fix-virtualbox-on-fedora-38-with-kernel-635/</link><pubDate>Tue, 13 Jun 2023 00:00:00 +0000</pubDate><guid>https://foosel.net/til/how-to-fix-virtualbox-on-fedora-38-with-kernel-635/</guid><description>&lt;p&gt;For accounting and some windows only software (👋 Affinity Designer) I have a Windows 10 VM running in VirtualBox on my Framework running Fedora 38. Apparently I got a kernel update recently and as of this morning the VM refused to start. It just hung, and a look into &lt;code&gt;journalctl&lt;/code&gt; showed something like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Jun 13 10:23:50 draper kernel: traps: Missing ENDBR: 0xffff9b688c308f30
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;After some searching I came across &lt;a href="https://forums.virtualbox.org/viewtopic.php?p=536761#p536761"&gt;this thread on the VirtualBox forums&lt;/a&gt; which explained the issue and also includes the solution. Apparently the VirtualBox kernel driver &lt;a href="https://www.virtualbox.org/ticket/21435"&gt;triggers Intel&amp;rsquo;s IBT (indirect branch tracking)&lt;/a&gt;. The solution is to disable that&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt; by passing &lt;code&gt;ibt=off&lt;/code&gt; as a kernel parameter:&lt;/p&gt;</description><content:encoded><![CDATA[<p>For accounting and some windows only software (👋 Affinity Designer) I have a Windows 10 VM running in VirtualBox on my Framework running Fedora 38. Apparently I got a kernel update recently and as of this morning the VM refused to start. It just hung, and a look into <code>journalctl</code> showed something like this:</p>
<pre tabindex="0"><code>Jun 13 10:23:50 draper kernel: traps: Missing ENDBR: 0xffff9b688c308f30
</code></pre><p>After some searching I came across <a href="https://forums.virtualbox.org/viewtopic.php?p=536761#p536761">this thread on the VirtualBox forums</a> which explained the issue and also includes the solution. Apparently the VirtualBox kernel driver <a href="https://www.virtualbox.org/ticket/21435">triggers Intel&rsquo;s IBT (indirect branch tracking)</a>. The solution is to disable that<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> by passing <code>ibt=off</code> as a kernel parameter:</p>
<pre tabindex="0"><code>sudo grubby --update-kernel=ALL --args=&#34;ibt=off&#34;
</code></pre><p>After a reboot I could rebuild the vbox kernel driver via <code>/sbin/vboxconfig</code>, which ran through without issues, and after that the VM started up just fine.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Honestly, I&rsquo;d prefer to keep <a href="https://lwn.net/Articles/889475/">IBT</a> enabled for security reasons, but I need the VM to work. Let&rsquo;s hope VirtualBox fixes this soon, though given how long this seems to have gone on I&rsquo;m a bit skeptical.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>Developing "Pew Pew Pew: Danger Zone!"</title><link>https://foosel.net/blog/2023-05-30-developing-pew-pew-pew-danger-zone/</link><pubDate>Tue, 30 May 2023 00:00:00 +0000</pubDate><guid>https://foosel.net/blog/2023-05-30-developing-pew-pew-pew-danger-zone/</guid><description>A bit of a devlog for my Go Godot Jam 4 entry</description><content:encoded><![CDATA[<p>As hinted at to be my plan in <a href="/blog/2023-04-16-the-path-to-super-bunny-hoppers/">my post about my first proper gamedev experience back in April</a>, I recently took part in my first
ever gamejam, <a href="https://itch.io/jam/go-godot-jam-4">Go Godot Jam 4</a>, and during that built a game called <a href="https://foosel.itch.io/pew-pew-pew-danger-zone">&ldquo;Pew Pew Pew: Danger Zone!&rdquo;</a>. The jam required all submissions to be built in <a href="https://godotengine.org">Godot Engine</a>, which was one of the reasons I chose it - I just recently got my feet wet in game development with Godot 4 and wanted to get some more practice in. Since my partner was otherwise committed, I entered as a solo dev.</p>
<p>In this post I want to share a bit of a devlog and some insights into that experience, to add more background to <a href="https://chaos.social/@foosel/110321340611941751">the Mastodon thread I maintained throughout</a>.</p>
<p>Apologies in advance, this is going to be a long one, but there are also a ton of pictures and videos to break up the text a bit 😉</p>
<h2 id="development">Development</h2>
<p>The submission phase (so the time I had for developing and submitting a game fitting the theme) was 9 days long, from Friday, May 5th at 22:00 UTC to Sunday, May 14th at 22:00 UTC. Since the start wasn&rsquo;t until midnight in my timezone and after yet another intense work week I was quite exhausted on that Friday evening, I didn&rsquo;t stay up for the theme announcement. So it wasn&rsquo;t before the morning of Satuday May 6th for me to learn about the theme <strong>LESS IS MORE</strong>, right after waking up.</p>
<h3 id="saturday-may-6th-lets-go">Saturday, May 6th: Let&rsquo;s go!</h3>
<p>During my morning routine I got an idea on what game to create<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>: A little shoot-em-up (shmup) with minimalistic graphics, slightly bullet hell-ish, where the only way to upgrade your weapon and shoot more bullets was to lose health. The challenge would be balancing health vs damage output, possibly even dodging health pickups to stay at a higher bullet output with the risk of dying.</p>
<p>After breakfast I sat down to sketch out this idea a bit further.</p>
<p><img alt="My handwritten and handrawn notes of the game idea" loading="lazy" src="/blog/2023-05-30-developing-pew-pew-pew-danger-zone/ggj4-notes.webp"></p>
<p>The originally plan was to have the player be a triangle pointing upwards, and several enemy types also based on geometric shapes. The player would have three hit points and thus three different bullet emitter configurations. I jotted down some ideas for collectable power-ups (a shield, health, little shooting satellites, homing missiles), did some initial UI sketches and also laid out a rough plan for the interactive tutorial I wanted to include, by already leaving myself a plan B in case I wouldn&rsquo;t have enough time to implement it. Not written down in my notes but already quite clear in my head was the plan to have the game have 2-3 levels and a final boss to defeat, which felt like a good scope for the time I had available.</p>
<p>The plan was to spend most of the time until maybe Wednesday implementing controls, enemies, bullets and the basic game loop, and then spending the rest of the time on the tutorial, levels and boss, plus of course testing and publishing on <code>itch.io</code>.</p>















        
        

        
            
            
        

        
            <blockquote class="toot-blockquote" cite="https://chaos.social@foosel/status/110321340611941751">
                <div class="toot-header">
                    <a class="toot-profile" href="https://chaos.social/@foosel" rel="noopener">
                        <img
                            src="https://assets.chaos.social/accounts/avatars/000/235/099/original/a2e381e9aab4a693.png"
                            alt="Mastodon avatar for @foosel@chaos.social"
                            loading="lazy"
                        />
                    </a>
                    <div class="toot-author">
                        <a class="toot-author-name" href="https://chaos.social/@foosel" rel="noopener">Gina Häußge</a>
                        <a class="toot-author-handle" href="https://chaos.social/@foosel" rel="noopener">@foosel@chaos.social</a>
                    </div>
                </div>
                <p><a href="https://chaos.social/tags/GoGodotJam4" class="mention hashtag" rel="tag">#<span>GoGodotJam4</span></a> started at midnight. The theme is &quot;Less is more&quot; and I intend to participate! Got an idea that should work I hope, so let the coding commence!</p>
                
                
                
                <div class="toot-footer">
                    <a href="https://chaos.social/@foosel/110321340611941751" class="toot-date" rel="noopener">May 6, 2023, 10:47</a>&nbsp;<span class="pokey">(UTC)</span>
                </div>
            </blockquote>
        
    
<p>Then I sat down and got to work.</p>
<p>My first task was getting a player scene created and basic 2d movement controls implemented. After that I quickly created a player asset, two enemy assets and some bullets in Inkscape, basing my color choices on the <a href="https://lospec.com/palette-list/endesga-64">ENDESGA 64 color palette</a>, and started looking into automatic bullet emitters and bullet patterns for the enemies, and straight shooting for the player. I also implemented the logic for the player to lose health when colliding with the enemies&rsquo; bullets and consequently shooting more bullets, and for the enemies to lose health and dying when colliding with the player&rsquo;s bullets.</p>
<p>All of that took me the better part of Saturday, but by the time evening got around I had a very basic prototype with bullet emitters and patterns on the (still static) enemies and some basic shooting logic on the player. There was no sound yet, no game juice, nothing like that, but it was a start!</p>
<video controls preload="auto" width="100%"  autoplay loop playsinline class="html-video">
    <source src="/blog/2023-05-30-developing-pew-pew-pew-danger-zone/ggj4-day1.webm" type="video/webm">
  <span></span>
</video>
<h3 id="sunday-may-7th-lets-add-some-game-juice">Sunday, May 7th: Let&rsquo;s add some game juice!</h3>
<p>Sunday began with a trip to a doc to get my sixth COVID shot<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>. After that I sat down again in front of the laptop to implement as much as I could before the by now customary post-vaccination-headache would start haunting me.</p>
<p>It turned out to be a quite busy day, as I implemented:</p>
<ul>
<li>screen scrolling and movement limits for the player to always stay on screen</li>
<li>player death and respawning at the bottom of the screen, with a short period of invincibility right after respawning indicated by some fading in and out</li>
<li>screenshake and an explosion particle effect when destroying an enemy or the player</li>
<li>some basic sound effects for shooting and the explosions, whipped up in <a href="https://github.com/timothyqiu/gdfxr">gdfxr</a></li>
<li>a simple shadow effect for the player and enemies to simulate height (really just the same sprite with a modulate color applied and offset by a few pixels)</li>
</ul>
<video controls preload="auto" width="100%"  playsinline class="html-video">
    <source src="/blog/2023-05-30-developing-pew-pew-pew-danger-zone/ggj4-day2.webm" type="video/webm">
  <span></span>
</video>
<p>I also did some research on what music to put in and came across some amazing tracks by <a href="https://davidkbd.itch.io/">David KBD</a>. I was not yet fully set on which tracks to use precisely, but I was sure I had found a match (and was right!).</p>
<p>I was quite happy with that kind of progress and went to sleep with a good feeling, despite dreading the next day a bit due to the expected side effects of the vaccination.</p>
<h3 id="monday-may-8th-its-got-a-name">Monday, May 8th: It&rsquo;s got a name!</h3>
<p>But when I woke up on Monday morning, I happily realized that this time I had gotten away with just a bit of a headache and some tiredness, but nothing more. Alas, that still didn&rsquo;t allow me to continue to work on the game - it was a regular work day after all! And so I had to wait until the evening to continue. I was able to implement some pluggable enemy behaviour (for now only following the player at an offset, but with an underlying code structure that would allow to quickly implement other behaviours as well) and also a first version of the HUD with health bar, damage output bar, life counter and score display. Also, I whipped up a background graphic in Inkscape and put that in as well. But most importantly I came up with a name for my creation: <strong>Pew Pew Pew: Danger Zone!</strong></p>
<video controls preload="auto" width="100%"  playsinline class="html-video">
    <source src="/blog/2023-05-30-developing-pew-pew-pew-danger-zone/ggj4-day3.webm" type="video/webm">
  <span></span>
</video>
<h3 id="tuesday-may-9th-pickups">Tuesday, May 9th: Pickups!</h3>
<p>Just as Monday, Tuesday would also be a slower day with regards to progress on the game - working on OctoPrint all day didn&rsquo;t leave much time and energy in the evenings to make huge steps forward<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>, but I got some more stuff done nevertheless and was able to add various pickups to the game:</p>
<ul>
<li>a health pickup that would recover one hit point for the player (which of course would reduce the damage output),</li>
<li>a shield pickup that would protect the player from bullet damage for a few seconds,</li>
<li>point pickups and</li>
<li>a pickup that would add a little drone circling around the player which for now did nothing but in the future I wanted to have shoot homing missiles at the closest enemy</li>
</ul>
<p>Almost all of that would of course see further fine tuning during the next few days, but it was a good start on the pickups.</p>
<video controls preload="auto" width="100%"  playsinline class="html-video">
    <source src="/blog/2023-05-30-developing-pew-pew-pew-danger-zone/ggj4-day4.webm" type="video/webm">
  <span></span>
</video>
<h3 id="wednesday-may-10th-refactoring--doubting-myself">Wednesday, May 10th: Refactoring &amp; doubting myself!</h3>
<p>Wednesday was the worse day of the jam for me. Work was tough, I was really out of energy in the evening, and the time that I did have after work was spent on refactoring the bullet emitters on the enemies so that I could also use them for the player and setting up some more bullet patterns, during which I also managed to introduce a hilarious bug that doubled all of the emitted bullets, making the game incredibly hard.</p>
<p><img alt="A screenshot from PPP:DZ. Most of the screen is filled with bullets thanks to the duplication bug." loading="lazy" src="/blog/2023-05-30-developing-pew-pew-pew-danger-zone/ggj4-day5.png"></p>
<p>That was also the day I spent a lot of time on trying to get the drone to work and failing miserably - the homing missiles just didn&rsquo;t feel right, a circular pattern I tried as an alternative felt even worse, and all in all it simply was not coming together. I knew I still didn&rsquo;t have a single level, the drone idea was failing and I was thinking I&rsquo;d have to pull it, and things just still didn&rsquo;t feel very fun. All in all, I was starting to seriously doubt whether I&rsquo;d be able to submit something to the jam that I could be proud of. I went to bed quite frustrated and in doubt with myself.</p>
<h3 id="thursday-may-11th-menus-bombs-game-over">Thursday, May 11th: Menus, bombs, game over!</h3>
<p>Thursday was a way better day however! I woke up very early, and when I realized I could no longer sleep got up and went through my morning routine. I was still thinking a lot about the drone and how to fix it, when suddenly a shower thought struck. I&rsquo;d implement another pickup, a little bomb that you&rsquo;d be able to trigger with a button press and which would then damage all enemies in its radius for a large amount of points and eradicate all bullets and pickups.</p>
<p>I finished my routine, sat down in front of the computer and started working on that - even before work! It was around 7am and I figured I should be able to get that implemented in the 2-2.5h before I had to start working on OctoPrint, and it turns out I indeed managed to pull that off! I created a small effect to indicate the damage radius of the bomb that would get attached to the player when the bomb was picked up, and a small animation that would make this light up and vanish when the bomb was set off via the <code>Shift</code> key, together with a new sound effect. And doing that was fun! I was starting to feel optimistic again and started on my work day.</p>
<p>I finished that a bit earlier than usual because I had accumulated some overtime the past few weeks and got back into working on PPP:DZ. I fixed the homing missiles on the drone - they were not perfect, but they were finally homing! I also created a title graphic, the game&rsquo;s menu, a pause menu, a game over and a winning screen, some pickup indicators for the HUD, made level switching work, and changed some things about the pickups - the drone would now time out, the shield would track damage and be able to protect from 10 damage points. I think this was also the day I had the idea to give the player a bit of gravity for pulling in pickups, and that also made things finally feel way nicer!</p>
<video controls preload="auto" width="100%"  playsinline class="html-video">
    <source src="/blog/2023-05-30-developing-pew-pew-pew-danger-zone/ggj4-day6.webm" type="video/webm">
  <span></span>
</video>
<p>It was an incredibly productive day, and I knew I&rsquo;d be able to fully concentrate on the game for all of Friday, Saturday and Sunday. I set myself the goal to finish a first level by the end of the next day, add two more levels and a boss fight on Saturday, and do nothing but testing and bug fixing for all of Sunday.</p>
<p>I went to bed feeling like I might actually be able to pull this off after all!</p>
<h3 id="friday-may-12th-weve-got-a-first-level">Friday, May 12th: We&rsquo;ve got a first level!</h3>
<p>After starting the day with a climbing session at the bouldering gym and some chatting with my buddies, I returned home, jumped into the shower and immediately got another idea for the game<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup>: Modulating the bullet speed based on health, increasing their speed the less health you still have, leading to a more frantic feeling and also even more damage output.</p>
<p>As soon as I was done with the morning routine I sat back down in front of the laptop and looked into implementing that. It turned out to be surprisingly easy to do, and when I fired up the game for the first time and took some damage on purpose I actually loudly shouted &ldquo;F*CK YEAH!&rdquo; because it made the game feel so much better!</p>
<p>I spent the rest of the day implementing the first level - creating a bunch of enemies and made them follow the player, move in a pattern across the screen or just stay statically in place and shoot away, fine tuning placement and playing through it again and again and again until I was happy with the feeling of things. In the process, I also found and fixed a sheer ton of bugs and minor or major annoyances, including a couple of game breaking race conditions 😬</p>
<p>Somewhere during that day, <a href="https://foosel.itch.io/pew-pew-pew-danger-zone">I also set up the game on itch.io</a>, wrote a little description, created some preview images and made sure the web export was running fine. At this point the page was still set to private, but thanks to the shareable secret link I was already able to send it to a few friends for testing, which is what I did.</p>
<p>I think around this time I must also have added the highscore mechanism, incl. persistence, which to my happy surprise also worked without any issues in the web export.</p>
<p>By the end of the day, I was exhausted, but happy with the progress, right on time, and looking forward to the next day!</p>















        
        

        
            
            
        

        
            <blockquote class="toot-blockquote" cite="https://chaos.social@foosel/status/110357894701554845">
                <div class="toot-header">
                    <a class="toot-profile" href="https://chaos.social/@foosel" rel="noopener">
                        <img
                            src="https://assets.chaos.social/accounts/avatars/000/235/099/original/a2e381e9aab4a693.png"
                            alt="Mastodon avatar for @foosel@chaos.social"
                            loading="lazy"
                        />
                    </a>
                    <div class="toot-author">
                        <a class="toot-author-name" href="https://chaos.social/@foosel" rel="noopener">Gina Häußge</a>
                        <a class="toot-author-handle" href="https://chaos.social/@foosel" rel="noopener">@foosel@chaos.social</a>
                    </div>
                </div>
                <p>Current state: exhausted, but level 1 designed through and implemented :D Only needs some more fine tuning! Also fixed a ton of issues I found during play testing, even some game breaking race conditions 😲</p><p>Fun addition: firing interval now inversely scales with health too - the closer to the death, the faster you fire. &quot;Less health, more bullets&quot; indeed.</p><p>Quite happy with the progress once again! Nothing planned the next two days but working on and finishing this 💪</p>
                
                    
                        
                    
                    <div class="toot-img-grid-0">
                    
                        
                    
                    </div>
                    
                    
                        
                            
                            <style>
                                .img-36a639af097046ce2f167f127b49bccd {
                                    aspect-ratio: 1280 / 720;
                                }
                            </style>
                            <div class="ctr toot-video-wrapper">
                                <video muted playsinline controls class="ctr toot-media-img img-36a639af097046ce2f167f127b49bccd">
                                    <source src="https://assets.chaos.social/media_attachments/files/110/357/880/597/878/877/original/e03754c6fe10d442.mp4">
                                    <p class="legal ctr">(Your browser doesn&rsquo;t support the <code>video</code> tag.)</p>
                                </video>
                            </div>
                        
                        
                    
                
                
                
                <div class="toot-footer">
                    <a href="https://chaos.social/@foosel/110357894701554845" class="toot-date" rel="noopener">May 12, 2023, 21:43</a>&nbsp;<span class="pokey">(UTC)</span>
                </div>
            </blockquote>
        
    
<h3 id="saturday-may-13th-content-done">Saturday, May 13th: Content done!</h3>
<p>I woke up to some amazing feedback from a buddy and implemented that right away:</p>
<ul>
<li>the pickup gravity was a bit too aggressive and constantly yeeted things out of the screen, so I toned that down a bit and that help a lot</li>
<li>the drone was feeling too weak, so I increased its shot frequency</li>
<li>there was still an issue that the level end screen would not stop you from continuing to move around and shoot behind it - that required a bit more work, but was on the TODO list already anyhow</li>
</ul>
<p>Next task on the plan for the day was implementing two more levels and a boss! Since the boss definitely was the higher risk and higher impact, I tackled that one first, and created a big circle enemy chock full of hit points and with a shield powered by satellites rotating around it that would also shoot at the player. Admittedly that the shield was powered by these satellites was a bit of a hidden feature - shooting one down would temporarily remove the shield, only for it to return, until all satellites were taken out. It seems not many players might have caught on to that though 😅</p>
<p>I threw it into its dedicated level and added a boss health bar to the HUD.</p>
<p>Around afternoon I posted a sneak peak of the boss on Mastodon:</p>















        
        

        
            
            
        

        
            <blockquote class="toot-blockquote" cite="https://chaos.social@foosel/status/110362364305310898">
                <div class="toot-header">
                    <a class="toot-profile" href="https://chaos.social/@foosel" rel="noopener">
                        <img
                            src="https://assets.chaos.social/accounts/avatars/000/235/099/original/a2e381e9aab4a693.png"
                            alt="Mastodon avatar for @foosel@chaos.social"
                            loading="lazy"
                        />
                    </a>
                    <div class="toot-author">
                        <a class="toot-author-name" href="https://chaos.social/@foosel" rel="noopener">Gina Häußge</a>
                        <a class="toot-author-handle" href="https://chaos.social/@foosel" rel="noopener">@foosel@chaos.social</a>
                    </div>
                </div>
                <p>👀</p>
                
                    
                        
                            
                        
                    
                    <div class="toot-img-grid-1">
                    
                        
                            
                            <style>
                                .img-a1495e23254a6b3d9ad23184fa599034 {
                                    aspect-ratio: 1280 / 720;
                                }
                            </style>
                            <img
                                src="https://assets.chaos.social/media_attachments/files/110/362/360/528/771/954/original/08beffe37c501cda.png"
                                alt="Screenshot of the boss in my game Pew Pew Pew: Danger Zone!

A big circular enemy, with five triangle shaped enemies orbiting it. Tons of bullets on the screen."
                                class="toot-media-img img-a1495e23254a6b3d9ad23184fa599034"
                                loading="lazy"
                            />
                        
                    
                    </div>
                    
                    
                        
                        
                    
                
                
                
                <div class="toot-footer">
                    <a href="https://chaos.social/@foosel/110362364305310898" class="toot-date" rel="noopener">May 13, 2023, 16:40</a>&nbsp;<span class="pokey">(UTC)</span>
                </div>
            </blockquote>
        
    
<p>I then went on to create two more levels, which turned out to be surprisingly fast - I was able to reuse some of the enemy movement patterns and also could quickly implement some more ideas I had, thanks to the pluggable behaviours and Godot&rsquo;s animations.</p>
<p>The music selection was finalized as well: &ldquo;Screams in the Distance&rdquo; (as loop) from <a href="https://davidkbd.itch.io/eternity-metal-scfi-music-pack">DavidKBD&rsquo;s Eternity Pack</a> for the title screen, &ldquo;Spiral of Plasma&rdquo; for the levels and &ldquo;Synth Kobra&rdquo; for the boss fight, both from <a href="https://davidkbd.itch.io/interstellar-edm-metal-music-pack">DavidKBD&rsquo;s Interstellar Pack</a>.<sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup></p>
<p>Doing test play after test play while developing I came to the conclusion that the boss encounter still needed a certain &ldquo;oompf!&rdquo; and added a warning animation with an alert sound, which together with the music choice imho really helped to set the right tone for a boss fight.</p>
<video controls preload="auto" width="100%"  playsinline class="html-video">
    <source src="/blog/2023-05-30-developing-pew-pew-pew-danger-zone/boss-fight.mp4" type="video/mp4">
  <span></span>
</video>
<p>After some more testing and fine tuning I decided to go public with the game, in the hopes to get some more feedback from a wider audience before the final day, <a href="https://foosel.itch.io/pew-pew-pew-danger-zone">published the itch.io page</a> and posted a link to it on Mastodon:</p>















        
        

        
            
            
        

        
            <blockquote class="toot-blockquote" cite="https://chaos.social@foosel/status/110362764232694746">
                <div class="toot-header">
                    <a class="toot-profile" href="https://chaos.social/@foosel" rel="noopener">
                        <img
                            src="https://assets.chaos.social/accounts/avatars/000/235/099/original/a2e381e9aab4a693.png"
                            alt="Mastodon avatar for @foosel@chaos.social"
                            loading="lazy"
                        />
                    </a>
                    <div class="toot-author">
                        <a class="toot-author-name" href="https://chaos.social/@foosel" rel="noopener">Gina Häußge</a>
                        <a class="toot-author-handle" href="https://chaos.social/@foosel" rel="noopener">@foosel@chaos.social</a>
                    </div>
                </div>
                <p>Okay! As of now all the content I wanted to create for &quot;Pew Pew Pew: Danger Zone!&quot; has been created 🥰</p><p>For the rest of today and all of tomorrow the plan is testing, testing, testing, improving some things a bit and ironing out any kinks I find.</p><p>I&#39;m beyond proud that I so far actually did manage to pull this off the way I planned it, for my first jam ever. Feels amazing. 😄</p><p><a href="https://foosel.itch.io/pew-pew-pew-danger-zone" target="_blank" rel="nofollow noopener" translate="no"><span class="invisible">https://</span><span class="ellipsis">foosel.itch.io/pew-pew-pew-dan</span><span class="invisible">ger-zone</span></a></p><p><a href="https://chaos.social/tags/GoGodotJam4" class="mention hashtag" rel="tag">#<span>GoGodotJam4</span></a> <a href="https://chaos.social/tags/GoGodotJam" class="mention hashtag" rel="tag">#<span>GoGodotJam</span></a> <a href="https://chaos.social/tags/gamedev" class="mention hashtag" rel="tag">#<span>gamedev</span></a> <a href="https://chaos.social/tags/gamejam" class="mention hashtag" rel="tag">#<span>gamejam</span></a></p>
                
                
                
                <div class="toot-footer">
                    <a href="https://chaos.social/@foosel/110362764232694746" class="toot-date" rel="noopener">May 13, 2023, 18:22</a>&nbsp;<span class="pokey">(UTC)</span>
                </div>
            </blockquote>
        
    
<p>At this point I also submitted it to the jam, knowing full well that it was not yet done, but also that I&rsquo;d still be able to upload new builds and update the page itself until the deadline. I figured I&rsquo;d rather have an old build submitted than miss the deadline altogether due to some unforeseen circumstances.</p>
<p>Then I continued testing and fixing stuff until I was too tired to do go on and went to bed.</p>
<h3 id="sunday-may-14th-day-of-the-deadline">Sunday, May 14th: Day of the Deadline!</h3>
<p>I woke up to some more feedback, one point of which would turn out to be the most common one during the rating phase as well: the enemy bullets were too hard to see. I tried changing their color to red like the enemies themselves, and that helped, but made the game look a bit too monochrome. So I decided to see if switching to a darker background would help, and it did indeed.</p>
<p><img alt="Screenshot of the boss fight in front of a darker background. The bullets have more contrast now." loading="lazy" src="/blog/2023-05-30-developing-pew-pew-pew-danger-zone/boss-fight-darker.png"></p>
<p>Things were still not perfect, but it was the final day, I still had to implement a tutorial screen, do some more testing, fine tune some sound effects and finally do several more testing runs - I really didn&rsquo;t have time for big experiments anymore to make the bullets more visible. And to be honest, I didn&rsquo;t consider trying to completely avoid the enemy bullets the goal of the game anyhow, given getting hit was the only way to improve your damage output and there was some short period of invincibility after taking a hit, so sudden death scenarios shouldn&rsquo;t be that big of a concern either.</p>
<p>Thus, I put &ldquo;further research how to increase visibility of enemy bullets&rdquo; on the &ldquo;post jam&rdquo; task list and after redoing all the screenshots and going over all game items to make sure they still were visible too (the shield pickup needed some changes here), I tackled the tutorial. I had said goodbye to the idea of a tutorial level a long time ago already, and almost scratched the tutorial screen as well, but figured it would be better to have some hints in game. I created some key graphics to visualize the controls and put together a basic &ldquo;how to play&rdquo; one-pager that could be selected in the main menu. I also added a very basic keyboard control summary to the bottom of the screen right at the start of the first level, that would slowly fade out.</p>
<p><img alt="Screenshot of the final menu, with the new &ldquo;how to play&rdquo; option" loading="lazy" src="/blog/2023-05-30-developing-pew-pew-pew-danger-zone/menu.png"></p>
<p><img alt="Screenshot of the tutorial screen" loading="lazy" src="/blog/2023-05-30-developing-pew-pew-pew-danger-zone/tutorial-screen.png"></p>
<p><img alt="Screenshot of the control summary at the start of the first level" loading="lazy" src="/blog/2023-05-30-developing-pew-pew-pew-danger-zone/control-summary.png"></p>
<p>What then followed were a couple more hours of final testing, and once I could no longer find anything crucial that needed to be fixed, I decided to call it done, did a final upload and announced on Mastodon that I was done:</p>















        
        

        
            
            
        

        
            <blockquote class="toot-blockquote" cite="https://chaos.social@foosel/status/110367608340282718">
                <div class="toot-header">
                    <a class="toot-profile" href="https://chaos.social/@foosel" rel="noopener">
                        <img
                            src="https://assets.chaos.social/accounts/avatars/000/235/099/original/a2e381e9aab4a693.png"
                            alt="Mastodon avatar for @foosel@chaos.social"
                            loading="lazy"
                        />
                    </a>
                    <div class="toot-author">
                        <a class="toot-author-name" href="https://chaos.social/@foosel" rel="noopener">Gina Häußge</a>
                        <a class="toot-author-handle" href="https://chaos.social/@foosel" rel="noopener">@foosel@chaos.social</a>
                    </div>
                </div>
                <p>With the latest update that adds some final polishing (and after making another run through to make sure everything looks ok), I&#39;m calling a wrap on &quot;Pew Pew Pew: Danger Zone!&quot; and my first <a href="https://chaos.social/tags/gamejam" class="mention hashtag" rel="tag">#<span>gamejam</span></a> </p><p>Wow, what an experience. Woke up last Saturday to the theme, thought about it during my morning routine, came up with an idea &amp; put down a plan that felt doable in the available time. And - to my absolute surprise - this plan worked out! Beyond proud! 😊</p><p><a href="https://foosel.itch.io/pew-pew-pew-danger-zone" target="_blank" rel="nofollow noopener" translate="no"><span class="invisible">https://</span><span class="ellipsis">foosel.itch.io/pew-pew-pew-dan</span><span class="invisible">ger-zone</span></a></p>
                
                
                
                <div class="toot-footer">
                    <a href="https://chaos.social/@foosel/110367608340282718" class="toot-date" rel="noopener">May 14, 2023, 14:54</a>&nbsp;<span class="pokey">(UTC)</span>
                </div>
            </blockquote>
        
    
<p>And if you want to see what the game looked like at the time of submission, here&rsquo;s a playthrough of the first level:</p>
<video controls preload="auto" width="100%"  playsinline class="html-video">
    <source src="/blog/2023-05-30-developing-pew-pew-pew-danger-zone/pewpewpew-level1.mp4" type="video/mp4">
  <span></span>
</video>
<p>and another one of the boss fight:</p>
<video controls preload="auto" width="100%"  playsinline class="html-video">
    <source src="/blog/2023-05-30-developing-pew-pew-pew-danger-zone/pewpewpew-boss.mp4" type="video/mp4">
  <span></span>
</video>
<h2 id="feedback--lessons-learned">Feedback &amp; lessons learned</h2>
<p>After the submission phase ended, what came next was the rating phase. Everyone who had submitted a game during the jam and of course the organizers themselves could now rate the submissions in five categories. Quoting from the jam page:</p>
<blockquote>
<ol>
<li><strong>Technical</strong> - Does the game work? Does the game show off the engine? Does it do anything particularly impressive? Is it feature rich?</li>
<li><strong>Artistic</strong> - Is it pretty? Are the sound and visuals effective and fitting? Is the style coherent to the gameplay? Are the text boxes legible?</li>
<li><strong>Design</strong> - Does the game teach you how to play it? Do the mechanics make sense?</li>
<li><strong>Gameplay</strong> - Is it fun? Does the difficulty scale? Are there accessibility options? Have I played the same game before or is it innovative?</li>
<li><strong>Theme</strong> - Does the game fit the theme? Is it surface level or is the theme at the core of the game?</li>
</ol>
</blockquote>
<p>It took me a while to wrap my head around these categories and get a good feeling for how to rate games in them, but after a couple games played and rated I got into a good groove. Meanwhile I started receiving comments and feedback from other participants and a recurring theme there was: &ldquo;the enemy bullets are hard to see&rdquo; 😅</p>
<p>Lesson learned: I should at least have given myself a timebox on that last day, to try out some more things to make the bullets more visible. I&rsquo;m still not sure if I would have found a solution that would have worked well, but some more timeboxed experimenting might have helped. But then, it was the final day of my first ever jam, and I was a bit anxious to get everything done in time, so I guess I can forgive myself for not having done that.</p>
<p>Other than that the feedback was very positive, and when <a href="https://youtu.be/fp_1nU3fvwA?t=4185">it even got streamed by one of the judges</a> I was happy to see that they seemed to really enjoy it and gave me some great ratings as well! Many people said it was fun, enjoyed the intensity, and gave amazing suggestions on how to improve it further.</p>
<p>I also got into some nice discussions about gamedev in general with other participants (and made at least one new friend 👋😊).</p>
<p>So, while horribly anxiety inducing whenever I saw a new notification pop up on itch.io, the rating phase was also a lot of fun, I learned a lot from it and got a ton of helpful feedback!</p>
<p>And I hope I did my part in making it a similarily positive experience for the others as well, by giving them constructive feedback on their games too. I tried to play and rate as many games as possible, and while I didn&rsquo;t manage to play all of them (there were almost 200 submissions!), I did play and rate 42 games in the end, which was almost double of the ratings that I received. Next time I&rsquo;ll try to do more!</p>
<h2 id="final-results--the-future-of-pppdz">Final results &amp; the future of PPP:DZ</h2>
<p>Rating ended late on May 25th, but the results would not be announced until a final event stream that got scheduled for late on May 27th.</p>
<p>In the end, I made 21st place overall in the jam, and even #9 in gameplay, which was an outcome that I absolutely did not expect on my first ever gamejame participation and only my second gamedev experience! When I saw the results, I was absolutely floored! Based on what I had seen from others during the jam, I was hoping I&rsquo;d made it in the Top 100 (out of almost 200 submissions), and would have already been happy with a placement in the Top 50. That I almost made it into the Top 20 and even managed to secure a place in the Top 10 of the Gameplay category was beyond my wildest dreams!</p>
<p><img alt="Screenshot of my final score, showing the overall placement at 21st place and Gameplay at 9th" loading="lazy" src="/blog/2023-05-30-developing-pew-pew-pew-danger-zone/result.png"></p>
<p>But even before this amazing outcome I had already decided that I wanted to continue working on the game. I found myself playing it again and again on my Steamdeck, trying to improve on my highscore, and that made me think that I had indeed something fun here that I could build on.</p>















        
        

        
            
            
        

        
            <blockquote class="toot-blockquote" cite="https://chaos.social@foosel/status/110369145946028083">
                <div class="toot-header">
                    <a class="toot-profile" href="https://chaos.social/@foosel" rel="noopener">
                        <img
                            src="https://assets.chaos.social/accounts/avatars/000/235/099/original/a2e381e9aab4a693.png"
                            alt="Mastodon avatar for @foosel@chaos.social"
                            loading="lazy"
                        />
                    </a>
                    <div class="toot-author">
                        <a class="toot-author-name" href="https://chaos.social/@foosel" rel="noopener">Gina Häußge</a>
                        <a class="toot-author-handle" href="https://chaos.social/@foosel" rel="noopener">@foosel@chaos.social</a>
                    </div>
                </div>
                <p>Y&#39;all, that is still just such an amazing feeling...</p><p>And I really enjoy playing this game that I built, so much so that I&#39;m thinking to build up on this concept further and ideas are starting to float around in my head 🤔 </p><p>We&#39;ll see 😁</p><p>Btw, I&#39;ve put the Linux export up on the itch.io page, so if you want to play it on the deck too, download, in desktop mode add as non-steam game, return to game mode, enjoy ^^ Left analog stick to move, A to shoot, X to trigger the bomb.</p>
                
                    
                        
                            
                        
                    
                    <div class="toot-img-grid-1">
                    
                        
                            
                            <style>
                                .img-920c0a88c11c58fef8bf59f5ba745990 {
                                    aspect-ratio: 1663 / 1247;
                                }
                            </style>
                            <img
                                src="https://assets.chaos.social/media_attachments/files/110/369/136/717/327/637/original/f2c5a715125cd903.jpg"
                                alt="My game running on my Steamdeck "
                                class="toot-media-img img-920c0a88c11c58fef8bf59f5ba745990"
                                loading="lazy"
                            />
                        
                    
                    </div>
                    
                    
                        
                        
                    
                
                
                
                <div class="toot-footer">
                    <a href="https://chaos.social/@foosel/110369145946028083" class="toot-date" rel="noopener">May 14, 2023, 21:25</a>&nbsp;<span class="pokey">(UTC)</span>
                </div>
            </blockquote>
        
    
<p>So - I won&rsquo;t let myself get nailed down on &ldquo;when&rdquo; here (day job, balance, etc), but my plan is to take this concept and develop it further into a full game. I already have a couple of ideas on how to do that, and I&rsquo;m looking forward to working on them.</p>
<p>And what about future gamejam participations? I&rsquo;m hooked now and plan to participate next in the <a href="https://itch.io/jam/gmtk-2023">GMTK Game Jam 2023</a> in July. That will be probably be a solo effort again, and way more intense given that I&rsquo;ll have to take a game from idea to finished product in 48 hours, but I&rsquo;m looking forward to the challenge!</p>
<h2 id="play-the-game">Play the game!</h2>
<p>If you want to play the game, you can find it on itch.io here:</p>



<iframe style="margin: 1em auto" src="https://itch.io/embed/2063488?dark=true" width="552" height="167" frameborder="0"></iframe>

<p>And I&rsquo;ve also thrown up the sources on GitHub: <a href="https://github.com/foosel/pew-pew-pew-danger-zone">foosel/pew-pew-pew-danger-zone</a><sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup>. Just a word of warning on those: I&rsquo;m by no means an expert in Godot Engine, and a gamedev newbiew, and I was developing under heavy time constraints. I might have done some horrible things, please don&rsquo;t hold them against me 😅</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Yes, indeed, it was an actual shower thought.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>I plan to attend <a href="https://entropia.de/GPN21">GPN21</a> next week and wanted to get another booster shot before that just in case - as much as everyone seems to pretend it is, no, COVID is not over, and the last thing that I need in my extremely stressful and busy life is to get sick, possibly even long term.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>Admittedly, getting up really early to get an hour in at the bouldering gym before work like every Tuesday didn&rsquo;t help either 😬&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>I you see a pattern emerging here that involves me getting great ideas in the shower, you are not wrong. OctoPrint might also have been one, but ten years later I frankly can&rsquo;t remember for sure. At this point however I keep a stack of waterproof post-its and a pencil in the shower, in case something good pop ups in my head.&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p>If you are looking for some metal riffs for your game, take a look at <a href="https://davidkbd.itch.io">David&rsquo;s work</a>, it&rsquo;s really good!&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:6">
<p>You are welcome to dig around in the code and also of course to send in PRs, but I&rsquo;m currently still debating with myself if I want to merge anything from others, so please don&rsquo;t get offended if any PRs stay open and also uncommented for now 😅&#160;<a href="#fnref:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>How to export a Godot 4 game to run on the web on itch.io</title><link>https://foosel.net/til/how-to-export-a-godot-4-game-to-run-on-the-web-on-itchio/</link><pubDate>Sun, 14 May 2023 00:00:00 +0000</pubDate><guid>https://foosel.net/til/how-to-export-a-godot-4-game-to-run-on-the-web-on-itchio/</guid><description>&lt;p&gt;On the &lt;a href="https://itch.io/jam/go-godot-jam-4"&gt;Go Godot Jam 4&lt;/a&gt; Discord I just saw some people having issues with how to get HTML5 exports from Godot 4 to work on itch.io, and since I just had to do this for &lt;a href="https://foosel.itch.io/pew-pew-pew-danger-zone"&gt;my own game submission to the jam&lt;/a&gt; as well I decided to jot my steps down here (and on the Discord too) as it seems to be a bit of a pain for people.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;First of all export your game using the &amp;ldquo;Web&amp;rdquo; export template&lt;/p&gt;</description><content:encoded><![CDATA[<p>On the <a href="https://itch.io/jam/go-godot-jam-4">Go Godot Jam 4</a> Discord I just saw some people having issues with how to get HTML5 exports from Godot 4 to work on itch.io, and since I just had to do this for <a href="https://foosel.itch.io/pew-pew-pew-danger-zone">my own game submission to the jam</a> as well I decided to jot my steps down here (and on the Discord too) as it seems to be a bit of a pain for people.</p>
<ol>
<li>
<p>First of all export your game using the &ldquo;Web&rdquo; export template</p>
<p><img alt="The export dialog of the Godot Engine 4 editor with the Web export template selected" loading="lazy" src="/til/how-to-export-a-godot-4-game-to-run-on-the-web-on-itchio/export.png"></p>
</li>
<li>
<p>Navigate to your export folder, make sure to rename your <code>.html</code> file to <code>index.html</code></p>
</li>
<li>
<p>Zip all of it up, with all the files right within the root of the zip file</p>
<p><img alt="The contents of the created zip file, the html file has been renamed to index.html, all files are in the zip&rsquo;s root" loading="lazy" src="/til/how-to-export-a-godot-4-game-to-run-on-the-web-on-itchio/zip.png"></p>
</li>
<li>
<p>Upload to itch, make sure to check &ldquo;This file will be played in the browser&rdquo;</p>
<p><img alt="itch.io&rsquo;s upload dialog, the mentioned option is checked next to the zip upload" loading="lazy" src="/til/how-to-export-a-godot-4-game-to-run-on-the-web-on-itchio/upload.png"></p>
</li>
<li>
<p>Scroll down to &ldquo;Embed Options&rdquo; and make sure &ldquo;<code>SharedArrayBuffer</code> support&rdquo; is checked</p>
<p><img alt="The mentioned embed options, &ldquo;Shared Array Buffer support&rdquo; is checked" loading="lazy" src="/til/how-to-export-a-godot-4-game-to-run-on-the-web-on-itchio/embed_options.png"></p>
</li>
</ol>
]]></content:encoded></item><item><title>How to make the Home Assistant app sync properly under iOS</title><link>https://foosel.net/til/how-to-make-the-home-assistant-app-sync-properly-under-ios/</link><pubDate>Sun, 30 Apr 2023 00:00:00 +0000</pubDate><guid>https://foosel.net/til/how-to-make-the-home-assistant-app-sync-properly-under-ios/</guid><description>&lt;p&gt;While I&amp;rsquo;m strongly rooted in the Android camp, my partner has an iPhone, and on what seems to be every iOS update, the Home Assistant app installed on his phone stops syncing in the background.&lt;/p&gt;
&lt;p&gt;That wouldn&amp;rsquo;t be so bad if a lot of the home automations didn&amp;rsquo;t factor in presence status which gets synced through that, so this has been a source of minor annoyance whenever his status refused to mirror his presence or absence. It just happened again and because every single time now we&amp;rsquo;ve had to try to remember how to fix it, here&amp;rsquo;s a quick TIL to encourage my memory 😅&lt;/p&gt;</description><content:encoded><![CDATA[<p>While I&rsquo;m strongly rooted in the Android camp, my partner has an iPhone, and on what seems to be every iOS update, the Home Assistant app installed on his phone stops syncing in the background.</p>
<p>That wouldn&rsquo;t be so bad if a lot of the home automations didn&rsquo;t factor in presence status which gets synced through that, so this has been a source of minor annoyance whenever his status refused to mirror his presence or absence. It just happened again and because every single time now we&rsquo;ve had to try to remember how to fix it, here&rsquo;s a quick TIL to encourage my memory 😅</p>
<p>And it&rsquo;s simple really. Open the iOS settings, scroll down to the Home Assistant settings and make sure that location sharing is set to &ldquo;Always&rdquo;.</p>
]]></content:encoded></item><item><title>Some game design centric YouTube recommendations</title><link>https://foosel.net/blog/2023-04-20-some-game-design-centric-youtube-recommendations/</link><pubDate>Thu, 20 Apr 2023 00:00:00 +0000</pubDate><guid>https://foosel.net/blog/2023-04-20-some-game-design-centric-youtube-recommendations/</guid><description>A quick list of some channels on the topic of video game design that I really enjoy watching and can recommend!</description><content:encoded><![CDATA[<p><a href="../2023-04-16-the-path-to-super-bunny-hoppers/">In my last post</a> I told you that I&rsquo;ve been very much interested into video game development for a while now, and just never got around it until the personal Gamejam gift from my partner. Due to this interest I&rsquo;ve also been subscribed to a bunch of game design and documentary specific YouTube channels for a few years now, and since I consider their content pretty interesting &ndash; even if you are not interested in game development and just games in general! &ndash; I thought I might just share my list here. So let&rsquo;s go!</p>
<h2 id="adam-millard---the-architect-of-games">Adam Millard - The Architect of Games</h2>
<p><img alt="Screenshot of Adam Millard&rsquo;s YouTube channel" loading="lazy" src="/blog/2023-04-20-some-game-design-centric-youtube-recommendations/architectofgames.jpg"></p>
<p>Link: <a href="https://www.youtube.com/@ArchitectofGames">https://www.youtube.com/@ArchitectofGames</a></p>
<p>Video length: usually 20-30min</p>
<p>I stumbled over Adam Millard&rsquo;s channel thanks to his amazing video <a href="https://www.youtube.com/watch?v=Sz80210ipGc">&ldquo;How Subnautica Uses TERROR&rdquo;</a>. Each video usually centers on one specific topic of game design (recent examples include video game economies, metagaming and why some games that shouldn&rsquo;t be fun are in fact fun) and then discusses it with examples taken from well known and less well known games. Not only have I found the one or other gem this way, I&rsquo;ve learned a TON about what makes games fun, what makes them tick, and hopefully that will also allow me to make fun games myself, in the long run.</p>
<h2 id="design-doc">Design Doc</h2>
<p><img alt="Screenshot of the Design Doc YouTube channel" loading="lazy" src="/blog/2023-04-20-some-game-design-centric-youtube-recommendations/designdoc.jpg"></p>
<p>Link: <a href="https://www.youtube.com/@DesignDoc">https://www.youtube.com/@DesignDoc</a></p>
<p>Video length: usually 15-25min</p>
<p>Design Doc follows a similar approach: a dedicated topic (recent examples: scaling difficulty, winter levels, rhythm game UX), discussed by means of examples from a multitude of games.</p>
<h2 id="game-makers-toolkit">Game Maker&rsquo;s Toolkit</h2>
<p><img alt="Screenshot of the Game Maker&rsquo;s Toolkit YouTube channel" loading="lazy" src="/blog/2023-04-20-some-game-design-centric-youtube-recommendations/gmtk.jpg"></p>
<p>Link: <a href="https://www.youtube.com/@GMTK">https://www.youtube.com/@GMTK</a></p>
<p>Video length: usually 10-20min</p>
<p>Whenever there&rsquo;s a new GMTK video, I&rsquo;m sure to watch it ASAP. Mark Brown does an amazing job in disecting game design topics, and his recent reporting on his own gamedev experience with his &ldquo;Untitled Magnet Game&rdquo; has also been quite interesting to follow. Who knows, maybe I&rsquo;ll have the courage soon to participate in a <a href="https://itch.io/jam/gmtk-jam-2022">GMTK Jam</a>, too? 😅</p>
<h2 id="gdc">GDC</h2>
<p><img alt="Screenshot of the GDC YouTube channel" loading="lazy" src="/blog/2023-04-20-some-game-design-centric-youtube-recommendations/gdc.jpg"></p>
<p>Link: <a href="https://www.youtube.com/@Gdconf">https://www.youtube.com/@Gdconf</a></p>
<p>Video length: usually 30 or 60min</p>
<p>The yearly Game Developer&rsquo;s Conference (short: GDC) happens to have a quite interesting channel indeed, with tons of conference talks spanning across a multitude of topics, such as postmortems, programming, marketing, UX etc. I recently watched an amazing talk about <a href="https://www.youtube.com/watch?v=4RlpMhBKNr0">Celeste&rsquo;s level design</a> on there, and that was fascinating and certainly a video I&rsquo;ll have to come back to. Same goes for the one on <a href="https://www.youtube.com/watch?v=hG9SzQxaCm8">&ldquo;Building a Better Jump&rdquo;</a>. At the top of my &ldquo;Watch Later&rdquo; I also still have some talks sitting on creativity, map generation and open source in gamedev.</p>
<h2 id="noclip---video-game-documentaries">Noclip - Video Game Documentaries</h2>
<p><img alt="Screenshot of the Noclip YouTube channel" loading="lazy" src="/blog/2023-04-20-some-game-design-centric-youtube-recommendations/noclip.jpg"></p>
<p>Link: <a href="https://www.youtube.com/@NoclipDocs">https://www.youtube.com/@NoclipDocs</a></p>
<p>Video length: 30min and way more, but also some shorter ones in the 10min range</p>
<p>Noclip might not entirely fit the general game design focus of this list, but the gaming documentaries they put out are done amazingly well and offer an interesting look behind the scenes of a ton of AAA and indie games and how they came to be. That&rsquo;s less valuable in terms of game design and more in gaming history, but there&rsquo;s definitely something to learn from it as well.</p>
<h2 id="razbuten">Razbuten</h2>
<p><img alt="Screenshot of Razbuten&rsquo;s YouTube channel" loading="lazy" src="/blog/2023-04-20-some-game-design-centric-youtube-recommendations/razbuten.jpg"></p>
<p>Link: <a href="https://www.youtube.com/@razbuten/">https://www.youtube.com/@razbuten/</a></p>
<p>Video length: usually around 20min</p>
<p>Raz has a great series on what video games are like for people who don&rsquo;t usually play them (<a href="https://www.youtube.com/playlist?list=PLordXx8iNEyStcX_WzqM0JCpiJYgqhinc">&ldquo;Gaming For A Non-Gamer&rdquo;</a>), which in and of itself is a pretty great eye-opener. Apart from that they do what I&rsquo;d call opinion pieces on certain game mechanics and other aspects of game design, and why I don&rsquo;t always agree with their opinion, the topics always make me think.</p>
<hr>
<p>If you have any further tips along those lines, I&rsquo;d <em>love</em> to get some more recommendations! Feel free to ping me on Mastodon at <a href="https://chaos.social/@foosel">@foosel@chaos.social</a> 😊</p>
]]></content:encoded></item><item><title>The path to Super Bunny Hoppers</title><link>https://foosel.net/blog/2023-04-16-the-path-to-super-bunny-hoppers/</link><pubDate>Sun, 16 Apr 2023 00:00:00 +0000</pubDate><guid>https://foosel.net/blog/2023-04-16-the-path-to-super-bunny-hoppers/</guid><description>A report on an amazingly thoughtful gift and how it made me spent my Easter holiday with cute bunnies jumping on top of garden gnomes</description><content:encoded><![CDATA[<p>For my 40th birthday a few weeks ago, I got a pretty amazing gift from my partner: a coupon for a personal 4-day Gamejam/Gamedev experience, just the two of us.</p>
<p><img alt="A coupon saying &ldquo;Make and save your date for 4 days of Make Your Own Gamejam with Godot Game Engine&rdquo;" loading="lazy" src="/blog/2023-04-16-the-path-to-super-bunny-hoppers/gamejam-coupon.jpg"></p>
<p>I&rsquo;ve been a gamer pretty much my whole life, I&rsquo;ve been a coder pretty much my whole life, but I never so far managed to combine these things. Apart from copying some Star Trek game code in BASIC from a book in my teens, the probably quite common tinkering around with the parameters in <a href="https://en.wikipedia.org/wiki/Gorillas_(video_game)"><code>gorilla.bas</code></a>, and a Snake clone in <a href="https://www.opengl.org/resources/libraries/glut/glut_downloads.php">GLUT</a> in preparation for a 3d graphics course in university, I never so far got into game development. It has been something high on my bucket list however, and I&rsquo;ve been fascinated by the concept of gamejams like <a href="https://ldjam.com/">Ludum Dare</a>, <a href="https://gmtk.itch.io/">GMTK Jam</a> etc., where you build a game matching a provided topic within a set time limit (usually between two to ten days) and within a set of rules (e.g. do you need to do everything yourself, including graphics and music, or are you allowed to use certain existing assets, do you need to use a specific game engine etc). Add to that the fact that these days I&rsquo;m way more into playing indie games than the AAA stuff from the big players<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>, a love for learning new stuff and the release of the latest major version 4 of the fully open source <a href="https://godotengine.org/">Godot Engine</a> that has made me curious for a while now, and you might see why this really WAS an absolutely amazing gift by my partner.</p>
<p>We did this event from April 5th to 8th (and I kept a <a href="https://chaos.social/@foosel/110145537112426679">Mastodon thread</a> going throughout it). The first two days were a crash course in Godot Engine for me and in pixel art creation for him. The last two days were spent on creating a full (albeit short) platformer game from scratch.</p>
<h2 id="day-1">Day 1</h2>
<p>The first day was dedicated to getting to know the Godot IDE, going through the <a href="https://docs.godotengine.org/en/stable/getting_started/first_2d_game/index.html">2d game tutorial</a> and then further experimenting with whatever tickled our fancy.</p>
<p>I quickly went through the tutorial and soon had some first results to show for it. In the end, I even managed to get a &ldquo;Dodge the Creeps&rdquo; build exported for Linux and up and running on my Steamdeck, which was a very rewarding outcome indeed! I can&rsquo;t say that I ever found it THAT easy to get Homebrew to run on any of my consoles as it was with the Steamdeck - <code>scp</code> the binary over, add to Steam as external game, done 😊</p>
<p><img alt="Me, holding up my Steamdeck with a big grin and pointing to the screen, which is showing the &ldquo;Dodge the Creeps&rdquo; game I just got to run on it." loading="lazy" src="/blog/2023-04-16-the-path-to-super-bunny-hoppers/happy-foosel.jpg"></p>
<p>Still, as you can see, I was quite happy about that!</p>
<p>I also added some very basic sound effects to the game, which was not very tricky to achieve but had a great effect on the overall polish of that simple first try.</p>
<video controls preload="auto" width="100%"  playsinline class="html-video">
    <source src="/blog/2023-04-16-the-path-to-super-bunny-hoppers/dodge-the-creeps.webm" type="video/webm">
    <source src="/blog/2023-04-16-the-path-to-super-bunny-hoppers/dodge-the-creeps.mp4" type="video/mp4">
  <span></span>
</video>
<p>Next - while my partner was making a deep dive into some pixel art courses - I decided to play around a bit with the physics engine, particle system, sound effects etc by building a small Asteroid-like playground thingy that consisted of just one screen with a little player controlled <a href="https://www.kenney.nl/assets/pixel-shmup">space ship</a>, <a href="https://faktory.itch.io/pixel-planets">two small planets</a> that the spaceship could shoot at (doubtlessly a morally highly questionable mechanic) and some wrap-around behaviour.</p>
<p>At some point, my bullet spawning code was broken, making my ship lay eggs, which nicely fit the fact that we were doing this event over the Easter holidays 😉</p>
<video controls preload="auto" width="100%"  autoplay loop playsinline class="html-video">
    <source src="/blog/2023-04-16-the-path-to-super-bunny-hoppers/physics-experiments-bug.webm" type="video/webm">
  <span></span>
</video>
<p>Originally I also wanted to have the planets excert gravity on the ship and the bullets, and initially that did work, but at some point it stopped working. I still haven&rsquo;t figured out why 😅 I ended the first day with pew-pew-pew.</p>
<video controls preload="auto" width="100%"  playsinline class="html-video">
    <source src="/blog/2023-04-16-the-path-to-super-bunny-hoppers/physics-experiments.webm" type="video/webm">
    <source src="/blog/2023-04-16-the-path-to-super-bunny-hoppers/physics-experiments.mp4" type="video/mp4">
  <span></span>
</video>
<h2 id="day-2">Day 2</h2>
<p>For the second day, we had planned creating two simple prototypes, one Pong like game and one basic platformer.</p>
<p>We started with the Pong like game and agreed on a slightly futuristic but pixel based graphics style and the name &ldquo;Neon Pong&rdquo;. As my partner was still working through some more pixel art courses I started with some basic placeholder graphics (white rectangles ftw). My idea was to use <a href="https://docs.godotengine.org/en/stable/tutorials/physics/index.html">Godot&rsquo;s physics engine</a>, and my first implementation attempt led to yet another hilarious bug:</p>
<video controls preload="auto" width="100%"  autoplay loop playsinline class="html-video">
    <source src="/blog/2023-04-16-the-path-to-super-bunny-hoppers/neon-pong-bug.webm" type="video/webm">
  <span></span>
</video>
<p>After ironing that out, I got some simple two player controls implemented, as well as a slightly randomised spawning angle for the ball, scoring and win and lose detection. That involved figuring out how to detect when the ball goes out of bounds (which I already had learned during the tutorial: <a href="https://docs.godotengine.org/en/4.0/classes/class_visibleonscreennotifier2d.html"><code>VisibleOnScreenNotifier2D</code></a>) and some other stuff. I also added some sound effects for the ball bouncing off of obstacles, courtesy of <a href="https://www.drpetter.se/project_sfxr.html">sfxr</a> and some light effects that turned out to be less visible than I had hoped for.</p>
<p>At some point my partner sent me some assets to use for the paddles, the ball, the walls and the background and I also picked out <a href="https://opengameart.org/content/flux-capacitor-no-marty">a nice CC0 synthwave song called &ldquo;Flux Capacitor&rdquo; by &ldquo;Frenchyboy&rdquo;</a>, combined everything, and we had our basic Pong prototype:</p>
<video controls preload="auto" width="100%"  playsinline class="html-video">
    <source src="/blog/2023-04-16-the-path-to-super-bunny-hoppers/neon-pong.webm" type="video/webm">
    <source src="/blog/2023-04-16-the-path-to-super-bunny-hoppers/neon-pong.mp4" type="video/mp4">
  <span></span>
</video>
<p>At that point my partner communicated that he&rsquo;d be quite busy for yet a while to come with the pixel art course, so we decided on me just grabbing some more ready-made assets for the platformer prototype to experiment a bit with the mechanics we&rsquo;d need for the coming days. So I got <a href="https://egordorichev.itch.io/adve">a neat 8x8px dungeon tileset</a> and also treated myself to some <a href="https://egordorichev.itch.io/chare">8x8 animated characters by the same author</a> and started building. First of all I learned how to use Godot&rsquo;s <a href="https://docs.godotengine.org/en/stable/tutorials/2d/using_tilesets.html">tileset feature</a>, so I wouldn&rsquo;t have to manually place each individual tile but rather got just draw full rectangles and such. After that was done I created a very basic single-screen dungeon and a player character. I once again turned to the physics engine to do the heavy lifting, set up collisions, jumping, and also - since the tiles I used came with some wooden platforms - figured out how to make the character fall through those on a press on the down key (hint: manually increasing the y position by 1, negating the collision). Then I added some basic light to the torches I had placed and created some basic enemies and their pathing. For that I had to figure out how to keep them from falling off of platforms but instead switching directions on collisions with walls or cliffs (for the walls, the physics engine already helped, but for the cliff detection I had to learn how to use <a href="https://docs.godotengine.org/en/stable/classes/class_raycast2d.html"><code>Raycast2D</code> nodes</a>).</p>
<p>I didn&rsquo;t manage to implement a proper jump-on-top-the-enemy-to-kill-them mechanic as it was growing quite late already (and we had set ourselves fixed hours to not overdo it), but that led to yet another fun little bug with my character riding on top of a pathing enemy which gave me a good laugh at the end of day 2:</p>
<video controls preload="auto" width="100%"  autoplay loop playsinline class="html-video">
    <source src="/blog/2023-04-16-the-path-to-super-bunny-hoppers/yap-yap-yap.webm" type="video/webm">
  <span></span>
</video>
<h2 id="day-3">Day 3</h2>
<p>The third day started at the climbing gym, but right after that we got started on our opus magnum for this event: our little platformer. The idea was to build something heavily inspired by Super Mario, but with cute little bunnies hunting carrots and easter eggs - a wish by one of my partner&rsquo;s colleagues. For enemies, I had the idea to add some invading garden gnomes into the mix. We settled on a tilesize (16x16px), some basic mechanics (two selectable characters, jump on enemies, progress through level from left to right, have some predefined checkpoints that act as respawn points upon death, two hit points, &hellip;). While my partner was still working through the final parts of his pixel art course<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>, after getting my hands on some <a href="https://v3x3d.itch.io/retro-lines">placeholder assets</a> I started coding.</p>
<p>This time I started with the player. In order to be able to make the code a bit cleaner and more modular, I decided on implementing a state machine for it, and defined states for &ldquo;idle&rdquo;, &ldquo;walking&rdquo;, &ldquo;jumping&rdquo; and &ldquo;falling&rdquo;, and later also added &ldquo;spawned&rdquo; and &ldquo;hit&rdquo;. The states took care of parsing the possible inputs, triggering animations and sound effects and so on, and having that nicely packaged up into each individual state turned out to make the logic WAY easier to implement. I also added a configurable sprite set slot, in order to support multiple player skins and thus characters.</p>
<p>For the enemies, I implemented a parent scene with physics and pathing (once again the wall and cliff detection with direction change), configurable sprites and score. I learned how to scene inheritence and created two enemy types.</p>
<p>Next came the biggest challenge. Since we wanted to have at least two levels (spoiler alert: we only made two levels), I needed a way to dynamically load a level, spawn enemies etc. I didn&rsquo;t want to hard code everything per level and have code repetition, so instead I build a modular level loader. Levels are individual scenes with a tilemap node as the root node that defines the level tiles. Below that node, a specially named <a href="https://docs.godotengine.org/en/stable/classes/class_marker2d.html"><code>Marker2D</code></a> node defines the player spawn, a flexible number of <code>Marker2D</code> nodes assigned to the <code>enemies</code> group act as enemy spawns. A special checkpoint scene was implemented with an <a href="https://docs.godotengine.org/en/stable/tutorials/physics/using_area_2d.html"><code>Area2D</code></a> and a custom <code>checkpoint_reached</code> signal, and some code was added to make sure the last reached checkpoint per level would act as spawnpoint. Instances of this checkpoint scene can then be placed in the level. Finally, a special level exit scene listens for collisions with the players and signals level completion, and another <code>Marker2D</code> level end marker sets the limits of the camera.</p>
<p><img alt="A screenshot from Godot Engine, showing a level scene with the aforementioned additional nodes below the tilemap" loading="lazy" src="/blog/2023-04-16-the-path-to-super-bunny-hoppers/sbh-level-design.png"></p>
<p>To load a level now, the loader would get the name of the level scene to load, instantiate it and insert it into the tree, then fetch the player and enemy spawns and populate them, and also wire up all the required signals. That worked so well that I quickly created two basic test scenes, threw them into an array and wired up the level exit to unloading the current level and loading the next from the array.</p>
<p>The rest of the day was spent further refining the gameplay and fixing bugs in the quickly threwn together dummy level and then starting on the main menu and the character selection screen, and getting some glimpses here and there at the amazing assets my partner was busy working on<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> 😊</p>
<h2 id="day-4">Day 4</h2>
<p>The fourth and final day started with me continuing on the menu and getting it to work 🥳 I then also implemented a pause menu, credits (that would load and roll after reaching the final level exit, and loop back to the main menu), a game over screen and while doing all of that also got back again and again to the gameplay itself, ironing out bugs as I went.</p>
<p>Some time before noon, I started getting the first asset dumps from my partner and the integration started. The first assets I got where the character and enemy assets, and I created spritesets from all of them and wired them up correctly. At that point I also slightly changed the player character&rsquo;s animation behaviours, so that when the player would switch to falling after jumping, the animation would also switch from a jumping to a falling bunny. Ridiculously small effect, but it somehow tied everything a bit together in my humble opinion.</p>
<p>The game was still completely silent, so I sat down with sfxr again and created sound effects for jumping, the player getting hurt &amp; dying, an enemy getting killed, collecting a carrot and collecting an egg. That kept me busy until my partner dropped the tilesets into our shared folder.</p>
<p>I then figured out <a href="https://foosel.net/til/how-to-create-an-animated-tile-in-godot-4s-tilemaps/">how to animate tilemap items</a> for the collectable carrots and easter eggs, and then drew up and populated the actual levels from two templates my partner had created, heavily based on the first two levels of Super Mario Bros. Thanks to day 3&rsquo;s work on the dynamic level loading that was a <em>breeze</em> and my partner seemed quite amazed at how fast I got this done ^^.</p>
<p>We decided that there definitely was not enough time left to create music from scratch as well (it was already getting dark and our personal cut off time was drawing close), so instead I went hunting for some nice chiptunes and with <a href="https://tallbeard.itch.io/three-red-hearts-prepare-to-dev">&ldquo;Three Red Hearts&rdquo;</a> found some amazing ones courtesy of <a href="https://abstractionmusic.com">Abstraction</a>. We decided on four tracks (main menu, level 1, level 2, credits), I threw everything together and some final testing and debugging later we had our first game 😊</p>
<p>And this is it, <strong>Super Bunny Hoppers</strong>:</p>
<video controls preload="auto" width="100%"  playsinline class="html-video">
    <source src="/blog/2023-04-16-the-path-to-super-bunny-hoppers/super-bunny-hoppers.webm" type="video/webm">
    <source src="/blog/2023-04-16-the-path-to-super-bunny-hoppers/super-bunny-hoppers.mp4" type="video/mp4">
  <span></span>
</video>
<h2 id="the-days-after">The days after</h2>
<p>We had to take a break the next day since these four days were quite intense, but two days later after some pleas on Mastodon to put up the game up somewhere, I polished things up slightly (by e.g. making the game be controller and fully keyboard playable) and - thanks to Godot&rsquo;s HTML5 export - quite quickly threw it up on <a href="https://foosel.itch.io/super-bunny-hoppers">foosel.itch.io</a> where you can now play it yourself in all its glory!</p>
<p><img alt="Screenshot of the linked itch.io page, showing the embedded game" loading="lazy" src="/blog/2023-04-16-the-path-to-super-bunny-hoppers/itch-io.png"></p>
<p>All in all, I had an absolute blast during these four days (which I&rsquo;ve also told my partner repeatedly), and so did my partner. I see the one or other actual gamejam participation in mine and his future now - I&rsquo;m currently planning to attend the upcoming <a href="https://gogodotjam.com/">Go Godot Jam</a>, though he probably won&rsquo;t be able to thanks to a colliding work event - and am currently spending way too much time soaking up gamedev content on YouTube, am making my way through Scott Rogers&rsquo; &ldquo;Level Up! The Guide to great video game design&rdquo; (another amazing birthday gift by my partner), have started a backlog of game ideas and experiments I want to look into implementing and overall feel quite confident now that I <em>can</em> in fact create games - I just have to do it 😊</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>I prefer creative game mechanics and new ideas over the 24th installation of &ldquo;shoot the bad guys with ultra realistic graphics and a licensed soundtrack&rdquo; or any such franchise - especially since said graphics often give me severe motion sickness.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>It was a long one, but it definitely paid off in my opinion 😊&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>And which I&rsquo;ve since <a href="https://chaos.social/@foosel/110198016865272377">turned into custom coasters for him</a>.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>How to add Battle.net games to the Steamdeck</title><link>https://foosel.net/til/how-to-add-battle-net-games-to-the-steamdeck/</link><pubDate>Sat, 15 Apr 2023 00:00:00 +0000</pubDate><guid>https://foosel.net/til/how-to-add-battle-net-games-to-the-steamdeck/</guid><description>&lt;p&gt;&lt;em&gt;Update 2023-06-07: It turns out that these days, the by far easiest way to get Battle.net on the SteamDeck is using &lt;a href="https://github.com/moraroy/NonSteamLaunchers-On-Steam-Deck"&gt;NonSteamLaunchers-On-Steam-Deck&lt;/a&gt;, as I recently saw on &lt;a href="https://www.gamingonlinux.com/2023/05/get-battlenet-ea-epic-games-and-more-on-steam-deck-the-easy-way/"&gt;Gaming On Linux&lt;/a&gt;. I haven&amp;rsquo;t gotten a chance to try this myself yet, but it certainly looks very much straight forward, albeit not featuring individual game entries in Steam. For your quick Diablo fix, it should hopefully be just fine though.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Battle.net is currently having a spring sale and I&amp;rsquo;ve been eyeing Diablo II: Resurrected for a while now, so I jumped on the chance (and while at it also got StarCraft Remastered). But given that these days I primarily game on the Steamdeck, I needed to find a way to install Battle.net on my deck and also install individual launchers for the games.&lt;/p&gt;</description><content:encoded><![CDATA[<p><em>Update 2023-06-07: It turns out that these days, the by far easiest way to get Battle.net on the SteamDeck is using <a href="https://github.com/moraroy/NonSteamLaunchers-On-Steam-Deck">NonSteamLaunchers-On-Steam-Deck</a>, as I recently saw on <a href="https://www.gamingonlinux.com/2023/05/get-battlenet-ea-epic-games-and-more-on-steam-deck-the-easy-way/">Gaming On Linux</a>. I haven&rsquo;t gotten a chance to try this myself yet, but it certainly looks very much straight forward, albeit not featuring individual game entries in Steam. For your quick Diablo fix, it should hopefully be just fine though.</em></p>
<p>Battle.net is currently having a spring sale and I&rsquo;ve been eyeing Diablo II: Resurrected for a while now, so I jumped on the chance (and while at it also got StarCraft Remastered). But given that these days I primarily game on the Steamdeck, I needed to find a way to install Battle.net on my deck and also install individual launchers for the games.</p>
<h2 id="battlenet">Battle.net</h2>
<p>I first made the mistake of trying my luck with installing the Battle.net launcher directly via Steam Proton. Trust me: Don&rsquo;t. Lots of work, doesn&rsquo;t end up well. Instead, I went with <a href="https://lutris.net/">Lutris</a>. Lutris has been installed for a while, but if that&rsquo;s not yet on your deck, install that first via the Discover app. Then:</p>
<ol>
<li>Enter desktop mode</li>
<li>Navigate to <a href="https://lutris.net/games/battlenet/">https://lutris.net/games/battlenet/</a> and click on &ldquo;Install&rdquo;. That will fire up an install script in your Lutris setup. Follow the steps shown to you by the app.</li>
<li>Once the installer has run its course, log into Battle.net. You should now be able to download stuff.</li>
<li>Right click on Battle.net in Lutris, select &ldquo;Add steam shortcut&rdquo; (if that&rsquo;s not available, but &ldquo;Delete steam shortcut&rdquo; is - nothing to do, the shortcut has already been added). This will become available after Steam has been restarted, don&rsquo;t worry about it now.</li>
</ol>
<h2 id="diablo-ii-resurrected">Diablo II: Resurrected</h2>
<p>This will create a Lutris app that uses the same Wine prefix as your Battle.net install. This is important! In desktop mode:</p>
<ol>
<li>Navigate to <a href="https://lutris.net/games/diablo-2-ressurected/">https://lutris.net/games/diablo-2-ressurected/</a> (not a typo, there <em>is</em> a typo in that slug indeed), click &ldquo;Install&rdquo;. Follow the steps.</li>
<li>If not yet done, launch Battle.net and install Diablo II: Resurrected.</li>
<li>Launch Diablo once via Battle.net, exit as soon as you can.</li>
<li>In Lutris, right click on the Diablo entry, select &ldquo;Configure&rdquo; and go to &ldquo;Game options&rdquo;</li>
<li>Click &ldquo;Browse&rdquo; next to &ldquo;Executable&rdquo;. Change it to to <code>/home/deck/Games/battlenet/drive_c/Program Files (x86)/Diablo II Resurrected/D2R.exe</code> (so, one folder up, into the Diablo folder and there select <code>D2R.exe</code>)</li>
<li>Under Arguments add <code>-launch</code></li>
<li>Save</li>
</ol>
<p>Make sure the Steam shortcut for the app is added.</p>
<h2 id="starcraft">StarCraft</h2>
<p>Just as with Diablo II, it is important that the StarCraft entry uses the same Wine prefix as Battle.net. Either duplicate the existing &ldquo;Diablo II: Resurrected&rdquo; entry or alternatively follow steps 1 and 2 above. Then do the following with your game entry:</p>
<ol>
<li>Launch Battle.net, install Starcraft Remastered.</li>
<li>Right click on the designated game entry (either your duplicate of Diablo II, or the Diablo II entry you don&rsquo;t intend to use), select &ldquo;Configure&rdquo;</li>
<li>Under &ldquo;Game info&rdquo;, change the name to &ldquo;Starcraft Remastered&rdquo; and the identifier to &ldquo;starcraft-remastered&rdquo;</li>
<li>Under &ldquo;Game options&rdquo;, change the executable to <code>/home/deck/Games/battlenet/drive_c/Program Files (x86)/StarCraft/x86_64/StarCraft.exe</code> and add <code>-launch</code> to the Arguments</li>
<li>Save</li>
</ol>
<p>The art in Lutris should update automatically. Make sure the Steam shortcut for the app is added.</p>
<p>Once all of that is done, head back into game mode. Feel free to change the art for the newly created entries, e.g. through something like the <a href="https://github.com/SteamGridDB/decky-steamgriddb">SteamGridDB</a> plugin for <a href="https://deckbrew.xyz/">Decky</a>. I also set up a dedicated &ldquo;Battle.net&rdquo; collection and added Battle.net itself plus both games to it.</p>
<p><img alt="Screenshot from the Steamdeck, showing a &ldquo;Battle.net&rdquo; collection containing Battle.net, Diablo II: Resurrected and StarCraft Remastered shortcuts" loading="lazy" src="/til/how-to-add-battle-net-games-to-the-steamdeck/steamdeck-battlenet-1.png"></p>
<p>You should now be able to launch your games through their individual Steam shortcuts. Note that the way we have set up things, Battle.net will not be fully available, so if you need that (for multiplayer or for unlocking DLCs) you&rsquo;ll need to launch through the Battle.net shortcut instead. In the case of the Cartoon skin for StarCraft Remastered, launching Starcraft once through Battle.net sufficed however ^^</p>
<p><img alt="Screenshot of the cartoon skin for StarCraft Remastered" loading="lazy" src="/til/how-to-add-battle-net-games-to-the-steamdeck/steamdeck-battlenet-2.png"></p>
]]></content:encoded></item><item><title>How to use Obsidian's Dataview plugin to visualize frontmatter</title><link>https://foosel.net/til/how-to-use-obsidians-dataview-plugin-to-visualize-frontmatter/</link><pubDate>Fri, 14 Apr 2023 00:00:00 +0000</pubDate><guid>https://foosel.net/til/how-to-use-obsidians-dataview-plugin-to-visualize-frontmatter/</guid><description>&lt;p&gt;For every &lt;a href="https://octoprint.org"&gt;OctoPrint&lt;/a&gt; release I run through several update tests: I flash a specific OctoPi version, push it to a specific OctoPrint version, configure the release channel, then see if updating to the newest release (candidate) works fine. I use &lt;a href="https://octoprint.org/blog/2020/07/29/automating-octoprints-release-tests/"&gt;my testrig and its automation scripts&lt;/a&gt; for that and usually go through something between 5 and 10 separate scenarios.&lt;/p&gt;
&lt;p&gt;So far all of these scenarios were noted down as a Markdown table in my release checklist that these days I prepare in my &lt;a href="https://obsidian.md"&gt;Obsidian&lt;/a&gt; vault, including manually adjusting the testrig commands to match the scenario. Having to take care of the latter is something that has been annoying for a long time now, and during the preparation for &lt;a href="https://github.com/OctoPrint/OctoPrint/releases/tag/1.9.0rc5"&gt;yesterday&amp;rsquo;s release candidate&lt;/a&gt; I decided enough is enough and looked into improving my tooling a bit. In the end, I used Obsidian&amp;rsquo;s quite amazing &lt;a href="https://blacksmithgu.github.io/obsidian-dataview/"&gt;Dataview plugin&lt;/a&gt; to query the information about the planned test scenarios from the checklist&amp;rsquo;s frontmatter, build the testrig command from that, then render all of this as a table, complete with some checkboxes for state logging during the tests and a copy button for the command.&lt;/p&gt;</description><content:encoded><![CDATA[<p>For every <a href="https://octoprint.org">OctoPrint</a> release I run through several update tests: I flash a specific OctoPi version, push it to a specific OctoPrint version, configure the release channel, then see if updating to the newest release (candidate) works fine. I use <a href="https://octoprint.org/blog/2020/07/29/automating-octoprints-release-tests/">my testrig and its automation scripts</a> for that and usually go through something between 5 and 10 separate scenarios.</p>
<p>So far all of these scenarios were noted down as a Markdown table in my release checklist that these days I prepare in my <a href="https://obsidian.md">Obsidian</a> vault, including manually adjusting the testrig commands to match the scenario. Having to take care of the latter is something that has been annoying for a long time now, and during the preparation for <a href="https://github.com/OctoPrint/OctoPrint/releases/tag/1.9.0rc5">yesterday&rsquo;s release candidate</a> I decided enough is enough and looked into improving my tooling a bit. In the end, I used Obsidian&rsquo;s quite amazing <a href="https://blacksmithgu.github.io/obsidian-dataview/">Dataview plugin</a> to query the information about the planned test scenarios from the checklist&rsquo;s frontmatter, build the testrig command from that, then render all of this as a table, complete with some checkboxes for state logging during the tests and a copy button for the command.</p>
<p>Here&rsquo;s the format of the frontmatter for the latest release candidate 1.9.0rc5:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">update_test_type</span>: <span style="color:#ae81ff">maintenance</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">update_tests</span>:
</span></span><span style="display:flex;"><span>- <span style="color:#f92672">image</span>: <span style="color:#ae81ff">0.17.0</span>-<span style="color:#ae81ff">py3</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">update_test_type</span>: <span style="color:#ae81ff">simplepip</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">additional</span>: <span style="color:#ae81ff">pip=21.3.1</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">wip</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">done</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>- <span style="color:#f92672">image</span>: <span style="color:#ae81ff">0.18.0</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">channel</span>: <span style="color:#ae81ff">maintenance</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">start</span>: <span style="color:#ae81ff">1.8.7</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">wip</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">done</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>- <span style="color:#f92672">image</span>: <span style="color:#ae81ff">0.18.0</span>-<span style="color:#ae81ff">latest</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">channel</span>: <span style="color:#ae81ff">maintenance</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">wip</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">done</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>- <span style="color:#f92672">image</span>: <span style="color:#ae81ff">1.0.0</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">channel</span>: <span style="color:#ae81ff">maintenance</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">start</span>: <span style="color:#ae81ff">1.8.7</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">wip</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">done</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>- <span style="color:#f92672">image</span>: <span style="color:#ae81ff">1.0.0</span>-<span style="color:#ae81ff">latest</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">channel</span>: <span style="color:#ae81ff">stable</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">wip</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">done</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>- <span style="color:#f92672">image</span>: <span style="color:#ae81ff">1.0.0</span>-<span style="color:#ae81ff">latest</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">channel</span>: <span style="color:#ae81ff">maintenance</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">start</span>: <span style="color:#ae81ff">1.9</span><span style="color:#ae81ff">.0rc3</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">wip</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">done</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>- <span style="color:#f92672">image</span>: <span style="color:#ae81ff">1.0.0</span>-<span style="color:#ae81ff">latest</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">channel</span>: <span style="color:#ae81ff">maintenance</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">start</span>: <span style="color:#ae81ff">1.9</span><span style="color:#ae81ff">.0rc4</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">wip</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">done</span>: <span style="color:#66d9ef">true</span>
</span></span></code></pre></div><p>And this is the <code>dataviewjs</code> query I used:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">createCheckbox</span> <span style="color:#f92672">=</span> (<span style="color:#a6e22e">checked</span>) =&gt; {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">const</span> <span style="color:#a6e22e">checkbox</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">this</span>.<span style="color:#a6e22e">container</span>.<span style="color:#a6e22e">createEl</span>(<span style="color:#e6db74">&#34;input&#34;</span>, {<span style="color:#e6db74">&#34;type&#34;</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;checkbox&#34;</span>});
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">checkbox</span>.<span style="color:#a6e22e">checked</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">checked</span>;
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">checkbox</span>;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">createDropdown</span> <span style="color:#f92672">=</span> (<span style="color:#a6e22e">options</span>, <span style="color:#a6e22e">selected</span>) =&gt; {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">const</span> <span style="color:#a6e22e">dropdown</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">this</span>.<span style="color:#a6e22e">container</span>.<span style="color:#a6e22e">createEl</span>(<span style="color:#e6db74">&#34;select&#34;</span>);
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">options</span>.<span style="color:#a6e22e">map</span>((<span style="color:#a6e22e">value</span>, <span style="color:#a6e22e">idx</span>) =&gt; {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">const</span> <span style="color:#a6e22e">option</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">dropdown</span>.<span style="color:#a6e22e">createEl</span>(<span style="color:#e6db74">&#34;option&#34;</span>, {<span style="color:#e6db74">&#34;text&#34;</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">value</span>, <span style="color:#e6db74">&#34;value&#34;</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">value</span>});
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">option</span>.<span style="color:#a6e22e">selected</span> <span style="color:#f92672">=</span> (<span style="color:#a6e22e">value</span> <span style="color:#f92672">==</span> <span style="color:#a6e22e">selected</span>);
</span></span><span style="display:flex;"><span>	});
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">dropdown</span>;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">createCopyButton</span> <span style="color:#f92672">=</span> (<span style="color:#a6e22e">command</span>) =&gt; {
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">const</span> <span style="color:#a6e22e">button</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">this</span>.<span style="color:#a6e22e">container</span>.<span style="color:#a6e22e">createEl</span>(<span style="color:#e6db74">&#34;button&#34;</span>, {<span style="color:#e6db74">&#34;text&#34;</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;Copy&#34;</span>})
</span></span><span style="display:flex;"><span>	<span style="color:#a6e22e">button</span>.<span style="color:#a6e22e">addEventListener</span>(<span style="color:#e6db74">&#34;click&#34;</span>, (<span style="color:#a6e22e">evt</span>) =&gt; {
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">evt</span>.<span style="color:#a6e22e">preventDefault</span>();
</span></span><span style="display:flex;"><span>		<span style="color:#a6e22e">navigator</span>.<span style="color:#a6e22e">clipboard</span>.<span style="color:#a6e22e">writeText</span>(<span style="color:#a6e22e">command</span>);
</span></span><span style="display:flex;"><span>	});
</span></span><span style="display:flex;"><span>	<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">button</span>;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">DUTS</span> <span style="color:#f92672">=</span> [<span style="color:#e6db74">&#34;pia&#34;</span>, <span style="color:#e6db74">&#34;pib&#34;</span>, <span style="color:#e6db74">&#34;pic&#34;</span>];
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">update_type</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">dv</span>.<span style="color:#a6e22e">current</span>().<span style="color:#a6e22e">update_test_type</span> <span style="color:#f92672">||</span> <span style="color:#e6db74">&#34;maintenance&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">tests</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">dv</span>.<span style="color:#a6e22e">current</span>().<span style="color:#a6e22e">update_tests</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">dv</span>.<span style="color:#a6e22e">table</span>([<span style="color:#e6db74">&#34;ID&#34;</span>, <span style="color:#e6db74">&#34;WIP&#34;</span>, <span style="color:#e6db74">&#34;Done&#34;</span>, <span style="color:#e6db74">&#34;DUT&#34;</span>, <span style="color:#e6db74">&#34;OctoPi&#34;</span>, <span style="color:#e6db74">&#34;Channel&#34;</span>, <span style="color:#e6db74">&#34;Start&#34;</span>, <span style="color:#e6db74">&#34;Command&#34;</span>, <span style="color:#e6db74">&#34;&#34;</span>], <span style="color:#a6e22e">tests</span>
</span></span><span style="display:flex;"><span>	.<span style="color:#a6e22e">map</span>((<span style="color:#a6e22e">t</span>, <span style="color:#a6e22e">idx</span>) =&gt; {
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">const</span> <span style="color:#a6e22e">dut</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">dut</span> <span style="color:#f92672">||</span> <span style="color:#a6e22e">DUTS</span>[<span style="color:#a6e22e">idx</span> <span style="color:#f92672">%</span> <span style="color:#a6e22e">DUTS</span>.<span style="color:#a6e22e">length</span>];
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">const</span> <span style="color:#a6e22e">version</span> <span style="color:#f92672">=</span> (<span style="color:#f92672">!</span><span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">start</span> <span style="color:#f92672">||</span> <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">start</span> <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;stock&#34;</span>) <span style="color:#f92672">?</span> <span style="color:#e6db74">&#34;&#34;</span> <span style="color:#f92672">:</span> <span style="color:#e6db74">`,version=</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">start</span><span style="color:#e6db74">}</span><span style="color:#e6db74">`</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">let</span> <span style="color:#a6e22e">command</span>;
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">switch</span> (<span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">update_test_type</span>) {
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">case</span> <span style="color:#e6db74">&#34;simplepip&#34;</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>				<span style="color:#a6e22e">command</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">`fab flashhost_flash_and_provision:</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">image</span><span style="color:#e6db74">}</span><span style="color:#e6db74"> octopi_test_simplepip`</span>;
</span></span><span style="display:flex;"><span>				<span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">additional</span>) {
</span></span><span style="display:flex;"><span>					<span style="color:#a6e22e">command</span> <span style="color:#f92672">+=</span> <span style="color:#e6db74">&#34;:&#34;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">additional</span>;
</span></span><span style="display:flex;"><span>				}
</span></span><span style="display:flex;"><span>				<span style="color:#66d9ef">break</span>;
</span></span><span style="display:flex;"><span>			}
</span></span><span style="display:flex;"><span>			<span style="color:#66d9ef">default</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>				<span style="color:#a6e22e">command</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">`fab flashhost_flash_and_provision:</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">image</span><span style="color:#e6db74">}</span><span style="color:#e6db74"> octopi_test_update_</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">update_type</span><span style="color:#e6db74">}</span><span style="color:#e6db74">:</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">channel</span><span style="color:#e6db74">}${</span><span style="color:#a6e22e">version</span><span style="color:#e6db74">}</span><span style="color:#e6db74">`</span>;
</span></span><span style="display:flex;"><span>				<span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">additional</span>) {
</span></span><span style="display:flex;"><span>				  <span style="color:#a6e22e">command</span> <span style="color:#f92672">+=</span> <span style="color:#e6db74">&#34;,&#34;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">additional</span>
</span></span><span style="display:flex;"><span>				}
</span></span><span style="display:flex;"><span>				<span style="color:#66d9ef">break</span>;
</span></span><span style="display:flex;"><span>			}
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">const</span> <span style="color:#a6e22e">wipCheckbox</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">createCheckbox</span>(<span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">wip</span>);
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">const</span> <span style="color:#a6e22e">doneCheckbox</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">createCheckbox</span>(<span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">done</span>);
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">const</span> <span style="color:#a6e22e">dutDropdown</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">createDropdown</span>(<span style="color:#a6e22e">DUTS</span>, <span style="color:#a6e22e">dut</span>);
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">const</span> <span style="color:#a6e22e">copyButton</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">createCopyButton</span>(<span style="color:#a6e22e">command</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		<span style="color:#66d9ef">return</span> [<span style="color:#a6e22e">idx</span> <span style="color:#f92672">+</span> <span style="color:#ae81ff">1</span>, <span style="color:#a6e22e">wipCheckbox</span>, <span style="color:#a6e22e">doneCheckbox</span>, <span style="color:#a6e22e">dutDropdown</span>, <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">image</span>, <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">channel</span>, <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">start</span> <span style="color:#f92672">?</span> <span style="color:#a6e22e">t</span>.<span style="color:#a6e22e">start</span> <span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;stock&#34;</span>, <span style="color:#e6db74">&#34;`&#34;</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">command</span> <span style="color:#f92672">+</span> <span style="color:#e6db74">&#34;`&#34;</span>, <span style="color:#a6e22e">copyButton</span>]
</span></span><span style="display:flex;"><span>	}));
</span></span></code></pre></div><p>This iterates over the list of scenarios, and for each generates a command, assigns one of the DUTs (Device Under Test) of the testrig, creates checkboxes for WIP and Done seeded from the frontmatter data and also a copy button. This then gets rendered as a table.</p>
<p>Throwing that query in a <code>dataviewjs</code> typed markdown code fence yields a nice rendition of the data that allows me to check if I have forgotten anything, track my progress with the checkboxes and also allows me to easily copy the generated command with the click of a button, so I don&rsquo;t have to manually copy paste stuff anymore:</p>
<p><img alt="A screenshot of a table visualizing the test scenarios. The columns are ID, WIP, Done, DUT, OctoPi, Channel, Start and Command, the rows are the secnarios. Each scenario has a testrig command attached that can be copied with a dedicated button." loading="lazy" src="/til/how-to-use-obsidians-dataview-plugin-to-visualize-frontmatter/scenario-table.png"></p>
<p>What I have not yet figured out is how to manipulate the frontmatter directly via the checkboxes and DUT dropdowns (for logging purposes) - right now I still have to edit the state changes manually to persist them. The <a href="https://github.com/chhoumann/MetaEdit">MetaEdit</a> plugin for Obsidian looked promising, but that doesn&rsquo;t seem to be flexible enough to manipulate a list of items (yet).</p>
<p>In any case, this helped me a ton yesterday, and will save me a lot of time with future releases and release candidates!</p>
]]></content:encoded></item><item><title>How to override the EDID data of a monitor under Linux</title><link>https://foosel.net/til/how-to-override-the-edid-data-of-a-monitor-under-linux/</link><pubDate>Tue, 11 Apr 2023 00:00:00 +0000</pubDate><guid>https://foosel.net/til/how-to-override-the-edid-data-of-a-monitor-under-linux/</guid><description>&lt;p&gt;I&amp;rsquo;m slowly but surely fixing all the issues I had after switching back to Linux as my main OS, so here&amp;rsquo;s another TIL 😉&lt;/p&gt;
&lt;p&gt;My secondary monitor is a 24&amp;quot; DELL with a resolution of 1920x1200, so 16:10, instead of the more common 1080p and 16:9. In order to be able to connect all my three monitors to my laptop, I make use of both the laptop&amp;rsquo;s HDMI port as well as a USB-C dock that has 2 HDMI ports. The 4k main display is connected directly to the laptop&amp;rsquo;s HDMI port, secondary and tertiary display are connected to the dock.&lt;/p&gt;</description><content:encoded><![CDATA[<p>I&rsquo;m slowly but surely fixing all the issues I had after switching back to Linux as my main OS, so here&rsquo;s another TIL 😉</p>
<p>My secondary monitor is a 24&quot; DELL with a resolution of 1920x1200, so 16:10, instead of the more common 1080p and 16:9. In order to be able to connect all my three monitors to my laptop, I make use of both the laptop&rsquo;s HDMI port as well as a USB-C dock that has 2 HDMI ports. The 4k main display is connected directly to the laptop&rsquo;s HDMI port, secondary and tertiary display are connected to the dock.</p>
<p>The problem now was that as long as I had both secondary and tertiary connected, I could only select up to 1080p for the secondary. That would not have been that big of an issue if that wouldn&rsquo;t have caused the monitor to try to scale the 16:9 output to 16:10, stretching it. With only the secondary display attached however, it would detect the 16:10 resolution just fine.</p>
<p>On Windows, with the same hardware setup (albeit a different laptop), this was quickly solved with <a href="https://www.monitortests.com/forum/Thread-Custom-Resolution-Utility-CRU">a third party tool</a> and ran just fine for months. On Linux I was stumped for the past few weeks since switching. Adding <code>video=</code> lines to the kernel parameters - as suggested by various guides - didn&rsquo;t seem to do the trick, and that appears to be the only option these days with Wayland in the mix to do something like this.</p>
<p>Today however I fell over <a href="https://wiki.archlinux.org/title/kernel_mode_setting#Forcing_modes_and_EDID">this helpful section</a> on the ArchLinux wiki, and that gave me an idea. Apparently the EDID information of the secondary is correct when it is connected alone, or I wouldn&rsquo;t be able to select 1920x1200 then. So I should be able to just grab the EDID data from it when working and force that to be used all the time via a kernel parameter.</p>
<p>I disconnected my tertiary display and verified the secondary was now again being detected as supporting 1920x1200. Then I first figured out its port by checking which of the available devices showed as connected (and wasn&rsquo;t the internal laptop screen or the 4k primary):</p>
<pre tabindex="0"><code>$ for p in /sys/class/drm/*/status; do con=${p%/status}; echo -n &#34;${con#*/card?-}: &#34;; cat $p; done
DP-1: disconnected
DP-2: connected
DP-3: disconnected
DP-4: disconnected
DP-5: disconnected
DP-6: connected
DP-7: disconnected
eDP-1: connected
</code></pre><p>In my case that turned out to be <code>DP-6</code><sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>. Next I quickly dumped the EDID information to a new file <code>/usr/lib/firmware/edid/dell-24-1200p.bin</code>:</p>
<pre tabindex="0"><code>sudo mkdir -p /usr/lib/firmware/edid
sudo cp /sys/class/drm/card1-DP-6/edid /usr/lib/firmware/edid/dell-24-1200p.bin
</code></pre><p>Then I used <code>grubby</code> to add a <code>drm.edid_firmware</code> kernel mode setting to all kernel entries for <code>DP-6</code> that tells the kernel to use this EDID file:</p>
<pre tabindex="0"><code>sudo grubby --update-kernel=ALL --args=&#34;drm.edid_firmware=DP-6:edid/dell-24-1200p.bin&#34;
</code></pre><p>And one reboot later I could finally select 1920x1200 for my display! 🥳</p>
<p><em>Addendum from 2023-05-24</em>*: Recently the display&rsquo;s identifier&rsquo;s started switching between <code>DP-6</code> and <code>DP-8</code>, sometimes even when waking up from sleep. I&rsquo;m not sure why, and I so far did not have time to investigate, but just so I or anyone else stumbling over this knows how to do this in the future, it&rsquo;s easy to set custom edid data on multiple devices as well, in my case:</p>
<pre tabindex="0"><code>sudo grubby --update-kernel=ALL --args=&#34;drm.edid_firmware=DP-6:edid/dell-24-1200p.bin,DP-8:edid/dell-24-1200p.bin&#34;
</code></pre><div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><code>DP-2</code> is the primary and <code>eDP-1</code> the laptop&rsquo;s internal display&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>How to reduce the titlebar size of Gnome 43</title><link>https://foosel.net/til/how-to-reduce-the-titlebar-size-of-gnome/</link><pubDate>Tue, 11 Apr 2023 00:00:00 +0000</pubDate><guid>https://foosel.net/til/how-to-reduce-the-titlebar-size-of-gnome/</guid><description>&lt;p&gt;A few weeks ago I switched back to Linux as my primary OS, on a newly acquired refurbished &lt;a href="https://frame.work"&gt;Framework Laptop 11&lt;/a&gt;, and one thing that&amp;rsquo;s since been bothering me on my chosen desktop environment Gnome&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt; has been the HUGE titlebars:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Before: A quite tall title bar with a lot of padding, wasting space" loading="lazy" src="https://foosel.net/til/how-to-reduce-the-titlebar-size-of-gnome/before.png"&gt;&lt;/p&gt;
&lt;p&gt;So I finally dug into solving this quickly, and came across &lt;a href="https://www.reddit.com/r/gnome/comments/y61xhm/comment/ivay6db/"&gt;this post on Reddit&lt;/a&gt; with a quite nice solution. I modified &lt;code&gt;~/.config/gtk-3.0/gtk.css&lt;/code&gt; and added the following contents:&lt;/p&gt;</description><content:encoded><![CDATA[<p>A few weeks ago I switched back to Linux as my primary OS, on a newly acquired refurbished <a href="https://frame.work">Framework Laptop 11</a>, and one thing that&rsquo;s since been bothering me on my chosen desktop environment Gnome<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> has been the HUGE titlebars:</p>
<p><img alt="Before: A quite tall title bar with a lot of padding, wasting space" loading="lazy" src="/til/how-to-reduce-the-titlebar-size-of-gnome/before.png"></p>
<p>So I finally dug into solving this quickly, and came across <a href="https://www.reddit.com/r/gnome/comments/y61xhm/comment/ivay6db/">this post on Reddit</a> with a quite nice solution. I modified <code>~/.config/gtk-3.0/gtk.css</code> and added the following contents:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-css" data-lang="css"><span style="display:flex;"><span><span style="color:#f92672">window</span>.<span style="color:#a6e22e">ssd</span> <span style="color:#f92672">headerbar</span>.<span style="color:#a6e22e">titlebar</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">padding-top</span>: <span style="color:#ae81ff">2</span><span style="color:#66d9ef">px</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">padding-bottom</span>: <span style="color:#ae81ff">2</span><span style="color:#66d9ef">px</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">min-height</span>: <span style="color:#ae81ff">0</span>;
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#f92672">window</span>.<span style="color:#a6e22e">ssd</span> <span style="color:#f92672">headerbar</span>.<span style="color:#a6e22e">titlebar</span> <span style="color:#f92672">button</span>.<span style="color:#a6e22e">titlebutton</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">padding</span>: <span style="color:#ae81ff">1</span><span style="color:#66d9ef">px</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">min-height</span>: <span style="color:#ae81ff">0</span>;
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">min-width</span>: <span style="color:#ae81ff">0</span>;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>That resulted in this:</p>
<p><img alt="After: The title bar reduced to the bare minimum in height, with only a minimal amount of padding, no more wasting space" loading="lazy" src="/til/how-to-reduce-the-titlebar-size-of-gnome/after.png"></p>
<p>And now I&rsquo;m happy, at least with non-Gnome apps, my chosen development environment VSCode included.</p>
<p><em>Update 2023-04-30: Alas, that no longer works under Gnome 44, so for now I&rsquo;m stuck with oversized titlebars again.</em></p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Currently Gnome 43.3 running under Wayland on Fedora Workstation 37&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>How to create an animated tile in Godot 4's tilemaps</title><link>https://foosel.net/til/how-to-create-an-animated-tile-in-godot-4s-tilemaps/</link><pubDate>Sun, 09 Apr 2023 00:00:00 +0000</pubDate><guid>https://foosel.net/til/how-to-create-an-animated-tile-in-godot-4s-tilemaps/</guid><description>&lt;p&gt;Over the past four days I&amp;rsquo;ve been doing &lt;a href="https://chaos.social/@foosel/110145537112426679"&gt;a personal crash course on game development&lt;/a&gt; together with my partner&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;, ending up in building &lt;a href="https://chaos.social/@foosel/110165066201016720"&gt;a small platformer in two days&lt;/a&gt;. We decided to use &lt;a href="https://godotengine.org"&gt;Godot Engine&lt;/a&gt;, as I had been circling it for a while now &lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;, and it turned out this decision was good because it was been a quite amazing experience, even for a total gamedev beginner like me 👍&lt;/p&gt;
&lt;p&gt;What cost me a bunch of time however is trying to figure out how to create an animated tile in a &lt;a href="https://docs.godotengine.org/en/stable/tutorials/2d/using_tilemaps.html"&gt;tilemap&lt;/a&gt;, which I finally figured out yesterday, and so I&amp;rsquo;m writing it down here for future me and everyone else wondering about this.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Over the past four days I&rsquo;ve been doing <a href="https://chaos.social/@foosel/110145537112426679">a personal crash course on game development</a> together with my partner<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>, ending up in building <a href="https://chaos.social/@foosel/110165066201016720">a small platformer in two days</a>. We decided to use <a href="https://godotengine.org">Godot Engine</a>, as I had been circling it for a while now <sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>, and it turned out this decision was good because it was been a quite amazing experience, even for a total gamedev beginner like me 👍</p>
<p>What cost me a bunch of time however is trying to figure out how to create an animated tile in a <a href="https://docs.godotengine.org/en/stable/tutorials/2d/using_tilemaps.html">tilemap</a>, which I finally figured out yesterday, and so I&rsquo;m writing it down here for future me and everyone else wondering about this.</p>
<p>First of all, you need a graphic of your tile-to-be-animated. It should have all frames side by side, like this example:</p>
<p><img alt="Screenshot of a png, 64x16px in size, with four frames of a cartoonish floating carrot" loading="lazy" src="/til/how-to-create-an-animated-tile-in-godot-4s-tilemaps/step1.png"></p>
<p>This is going to become a floating 16x16px carot, its animation consisting of four frames.</p>
<p>Add it to a tileset by dragging the resource into the left column, and make sure that <em>only the first frame</em> becomes part of your tileset! If you have let automatic atlas tooling do its thing, <em>delete</em> the other frames again under &ldquo;Setup&rdquo;:</p>
<p><img alt="Screenshot of Godot 4&rsquo;s tileset editor, showing the freshly added carrot, deleting wrongly added tiles" loading="lazy" src="/til/how-to-create-an-animated-tile-in-godot-4s-tilemaps/step2.png"></p>
<p>Then switch to the tileset editor&rsquo;s &ldquo;Select&rdquo; panel and click on the first frame of your animation. On the left you&rsquo;ll see some options for that tile. Open &ldquo;Animation&rdquo;, then increase the number of animation frames to the number of your available frames (4 for our carrot here), and after that make sure to click on &ldquo;Add Frame&rdquo; once for each of your frames. You then can also define how long the frame will be shown, 1s by default. 0.3s will result in something like 10fps.</p>
<p><img alt="Screenshot of Godot 4&rsquo;s tileset editor, showing the final animation setup on the &ldquo;Select&rdquo; panel as described in the text" loading="lazy" src="/til/how-to-create-an-animated-tile-in-godot-4s-tilemaps/step3.png"></p>
<p>After that, placing the <em>first frame</em> in a tilemap should show it being animated now:</p>
<video controls preload="auto" width="100%"  playsinline class="html-video">
    <source src="/til/how-to-create-an-animated-tile-in-godot-4s-tilemaps/step4.webm" type="video/webm">
  <span></span>
</video>
<p>🥳</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Him focusing on pixel art, me on the coding bits.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Not only because it&rsquo;s also open source, though that also plays a big part!&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>About SSH escape sequences</title><link>https://foosel.net/til/about-ssh-escape-sequences/</link><pubDate>Wed, 22 Mar 2023 00:00:00 +0000</pubDate><guid>https://foosel.net/til/about-ssh-escape-sequences/</guid><description>&lt;p&gt;OpenSSH&amp;rsquo;s &lt;code&gt;ssh&lt;/code&gt; command supports a bunch of escape sequences while a session is running, by default triggered by the &lt;code&gt;~&lt;/code&gt; character. According to &lt;a href="https://linux.die.net/man/1/ssh"&gt;&lt;code&gt;man ssh&lt;/code&gt;&lt;/a&gt; a list of available commands can be requested with &lt;code&gt;~?&lt;/code&gt;. And indeed, hitting &lt;code&gt;~?&lt;/code&gt; within an open SSH session prints some helpful information:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-plain" data-lang="plain"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ ~?
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Supported escape sequences:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ~. - terminate connection (and any multiplexed sessions)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ~B - send a BREAK to the remote system
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ~C - open a command line
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ~R - request rekey
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ~V/v - decrease/increase verbosity (LogLevel)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ~^Z - suspend ssh
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ~# - list forwarded connections
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ~&amp;amp; - background ssh (when waiting for connections to terminate)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ~? - this message
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ~~ - send the escape character by typing it twice
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;(Note that escapes are only recognized immediately after newline.)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I most commonly require &lt;code&gt;~.&lt;/code&gt; to disconnect from a broken SSH session (e.g. something I still had open on my laptop when I sent it to sleep).&lt;/p&gt;</description><content:encoded><![CDATA[<p>OpenSSH&rsquo;s <code>ssh</code> command supports a bunch of escape sequences while a session is running, by default triggered by the <code>~</code> character. According to <a href="https://linux.die.net/man/1/ssh"><code>man ssh</code></a> a list of available commands can be requested with <code>~?</code>. And indeed, hitting <code>~?</code> within an open SSH session prints some helpful information:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-plain" data-lang="plain"><span style="display:flex;"><span>$ ~?
</span></span><span style="display:flex;"><span>Supported escape sequences:
</span></span><span style="display:flex;"><span> ~.   - terminate connection (and any multiplexed sessions)
</span></span><span style="display:flex;"><span> ~B   - send a BREAK to the remote system
</span></span><span style="display:flex;"><span> ~C   - open a command line
</span></span><span style="display:flex;"><span> ~R   - request rekey
</span></span><span style="display:flex;"><span> ~V/v - decrease/increase verbosity (LogLevel)
</span></span><span style="display:flex;"><span> ~^Z  - suspend ssh
</span></span><span style="display:flex;"><span> ~#   - list forwarded connections
</span></span><span style="display:flex;"><span> ~&amp;   - background ssh (when waiting for connections to terminate)
</span></span><span style="display:flex;"><span> ~?   - this message
</span></span><span style="display:flex;"><span> ~~   - send the escape character by typing it twice
</span></span><span style="display:flex;"><span>(Note that escapes are only recognized immediately after newline.)
</span></span></code></pre></div><p>I most commonly require <code>~.</code> to disconnect from a broken SSH session (e.g. something I still had open on my laptop when I sent it to sleep).</p>
<p>The command line opened via <code>~C</code> is quite interesting as well, as it allows configuration of port forwards on the fly, while the session is already running:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-plain" data-lang="plain"><span style="display:flex;"><span>ssh&gt; help
</span></span><span style="display:flex;"><span>Commands:
</span></span><span style="display:flex;"><span>      -L[bind_address:]port:host:hostport    Request local forward
</span></span><span style="display:flex;"><span>      -R[bind_address:]port:host:hostport    Request remote forward
</span></span><span style="display:flex;"><span>      -D[bind_address:]port                  Request dynamic forward
</span></span><span style="display:flex;"><span>      -KL[bind_address:]port                 Cancel local forward
</span></span><span style="display:flex;"><span>      -KR[bind_address:]port                 Cancel remote forward
</span></span><span style="display:flex;"><span>      -KD[bind_address:]port                 Cancel dynamic forward
</span></span></code></pre></div><p>This is once again a &ldquo;TIL&rdquo; that I didn&rsquo;t actually learn about only today, but I keep forgetting about it and then need to frantically google whenever I need it. I hope this way I&rsquo;ll finally remember this stuff 😅</p>
]]></content:encoded></item><item><title>How to add an audio delay for video conferencing on Linux/Pulseaudio</title><link>https://foosel.net/til/how-to-add-an-audio-delay-for-video-conferencing-on-linuxpulseaudio/</link><pubDate>Sat, 11 Mar 2023 00:00:00 +0000</pubDate><guid>https://foosel.net/til/how-to-add-an-audio-delay-for-video-conferencing-on-linuxpulseaudio/</guid><description>&lt;p&gt;After recently switching to work under Linux, I needed a way to replicate &lt;a href="../how-to-add-an-audio-delay-for-video-conferencing-on-windows"&gt;my existing solution for delaying audio under Windows&lt;/a&gt; under Linux/Pulseaudio.&lt;/p&gt;
&lt;p&gt;To once again explain my situation, I use &lt;a href="https://obsproject.com/"&gt;OBS&lt;/a&gt; also for video conferencing, through the virtual camera&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;. OBS does not offer a built-in way to provide a virtual microphone with all the filters and such applied as well (in my case noise reduction and a limiter), so I need to solve this in a separate way. Additionally, my camera setup has a small delay of around 350ms that I also need to compensate by delaying my audio.&lt;/p&gt;</description><content:encoded><![CDATA[<p>After recently switching to work under Linux, I needed a way to replicate <a href="../how-to-add-an-audio-delay-for-video-conferencing-on-windows">my existing solution for delaying audio under Windows</a> under Linux/Pulseaudio.</p>
<p>To once again explain my situation, I use <a href="https://obsproject.com/">OBS</a> also for video conferencing, through the virtual camera<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>. OBS does not offer a built-in way to provide a virtual microphone with all the filters and such applied as well (in my case noise reduction and a limiter), so I need to solve this in a separate way. Additionally, my camera setup has a small delay of around 350ms that I also need to compensate by delaying my audio.</p>
<p>OBS offers a monitoring port that you can push your audio devices on, and that will get the filters applied, but <a href="https://obsproject.com/forum/threads/connecting-obs-with-zoom-without-av-syncing-issues.123960/post-469274">none of the configured offsets</a>. On Windows, I solved this <a href="../how-to-add-an-audio-delay-for-video-conferencing-on-windows">by using a combination of two Virtual Cable devices and RadioDelay between the two</a>.</p>
<p>Today I rebuild basically the same setup on Linux via a bunch of virtual <a href="https://www.freedesktop.org/wiki/Software/PulseAudio/">Pulseaudio</a> devices. I have this in <code>~/.local/bin/obs-virtual-mic</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e">#!/bin/bash
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># create virtual speaker</span>
</span></span><span style="display:flex;"><span>pactl load-module module-null-sink sink_name<span style="color:#f92672">=</span>Virtual-Speaker sink_properties<span style="color:#f92672">=</span>device.description<span style="color:#f92672">=</span>Virtual-Speaker
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># create delayed virtual speaker &amp; associated mic</span>
</span></span><span style="display:flex;"><span>pactl load-module module-null-sink sink_name<span style="color:#f92672">=</span>Virtual-Speaker-Delayed sink_properties<span style="color:#f92672">=</span>device.description<span style="color:#f92672">=</span>Virtual-Speaker-Delayed
</span></span><span style="display:flex;"><span>pactl load-module module-remap-source source_name<span style="color:#f92672">=</span>Remap-Source master<span style="color:#f92672">=</span>Virtual-Speaker-Delayed.monitor
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># copy from virtual to delayed virtual with 350ms delay</span>
</span></span><span style="display:flex;"><span>pactl load-module module-loopback latency_msec<span style="color:#f92672">=</span><span style="color:#ae81ff">350</span> source<span style="color:#f92672">=</span>Virtual-Speaker.monitor sink<span style="color:#f92672">=</span>Virtual-Speaker-Delayed
</span></span></code></pre></div><p>Let&rsquo;s take a closer look at what this does:</p>
<ul>
<li>create two virtual sinks <code>Virtual-Speaker</code> and <code>Virtual-Speaker-Delayed</code> with <code>pactl load-module module-null-sink ...</code></li>
<li>create a virtual source for the delayed sink via <code>pactl load-module module-remap-source ...</code></li>
<li>finally mirror the sound from <code>Virtual-Speaker.monitor</code> to <code>Virtual-Speaker-Delayed</code> while adding a latency of 350ms</li>
</ul>
<p>I run this. Then OBS gets set to use <code>Virtual-Speaker</code> as monitor. In my video conferencing software I then use <code>Virtual-Speaker-Delayed</code> as my input to get my video and audio synced up.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>I prefer the flexibility and control it gives me with regards to how I show up, how my screen shows up, etc, vs what you usually get from your run-of-the-mill video conferencing tool.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>How to remap keys under Linux and Wayland</title><link>https://foosel.net/til/how-to-remap-keys-under-linux-and-wayland/</link><pubDate>Fri, 03 Mar 2023 00:00:00 +0000</pubDate><guid>https://foosel.net/til/how-to-remap-keys-under-linux-and-wayland/</guid><description>&lt;p&gt;&lt;em&gt;Edit 2024-09-09: Please be advised that this post refers to an older version of &lt;code&gt;keyd&lt;/code&gt; that still used a different configuration format. An older version also stated the config file was stored at &lt;code&gt;~/.config/keyd&lt;/code&gt;, that was an error on my part. Thanks to a reader for the related heads-up!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;As a German living in Germany with umlauts in my last name and a US ANSI keyboard layout on all my devices&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt; I need to remap some stuff to be able to easily type ä, ö, ü and ß. On Windows I solved this with &lt;a href="https://www.autohotkey.com/"&gt;Autohotkey&lt;/a&gt;, mapping &lt;code&gt;AltGr&lt;/code&gt;+&lt;code&gt;a&lt;/code&gt; to &lt;code&gt;ä&lt;/code&gt;, &lt;code&gt;AltGr&lt;/code&gt;+&lt;code&gt;o&lt;/code&gt; to &lt;code&gt;ö&lt;/code&gt;, &lt;code&gt;AltGr&lt;/code&gt;+&lt;code&gt;u&lt;/code&gt; to &lt;code&gt;ü&lt;/code&gt; and &lt;code&gt;AltGr&lt;/code&gt;+&lt;code&gt;s&lt;/code&gt; to &lt;code&gt;ß&lt;/code&gt; (well, technically &lt;code&gt;RAlt&lt;/code&gt; - the right &lt;code&gt;Alt&lt;/code&gt; key). That has burned itself into my muscle memory now, and so while currently setting up my new Framework laptop under Linux, with Gnome running on Wayland, I was looking for a way to remap the keys to this layout as well.&lt;/p&gt;</description><content:encoded><![CDATA[<p><em>Edit 2024-09-09: Please be advised that this post refers to an older version of <code>keyd</code> that still used a different configuration format. An older version also stated the config file was stored at <code>~/.config/keyd</code>, that was an error on my part. Thanks to a reader for the related heads-up!</em></p>
<p>As a German living in Germany with umlauts in my last name and a US ANSI keyboard layout on all my devices<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> I need to remap some stuff to be able to easily type ä, ö, ü and ß. On Windows I solved this with <a href="https://www.autohotkey.com/">Autohotkey</a>, mapping <code>AltGr</code>+<code>a</code> to <code>ä</code>, <code>AltGr</code>+<code>o</code> to <code>ö</code>, <code>AltGr</code>+<code>u</code> to <code>ü</code> and <code>AltGr</code>+<code>s</code> to <code>ß</code> (well, technically <code>RAlt</code> - the right <code>Alt</code> key). That has burned itself into my muscle memory now, and so while currently setting up my new Framework laptop under Linux, with Gnome running on Wayland, I was looking for a way to remap the keys to this layout as well.</p>
<p>In the old days, I would have written an <code>.Xmodmap</code> file and called it a day, but that no longer works under Wayland. Thankfully however there&rsquo;s a whole new generation of mapping tools that instead of depending on the X server allow remapping right at the kernel input level, and one of them is <a href="https://github.com/rvaiya/keyd">keyd</a> which I used to solve my umlaut problem, and while at it also added a fancy mod layer and even a mouse layer.</p>
<p>First of all, in Gnome I set the keyboard layout to &ldquo;English (intl., with AltGr dead keys)&rdquo;, making it look like this:</p>
<p><img alt="A screenshot of the US international keyboard layout in the Gnome Settings. It&rsquo;s visible that on the third level ä is on q, ö on p, ü on y and ß on s." loading="lazy" src="/til/how-to-remap-keys-under-linux-and-wayland/us-intl-layout.png"></p>
<p>Then I downloaded, compiled and installed <code>keyd</code> and created a config file at <code>/etc/keyd/default.cfg</code> with the following contents:</p>
<pre tabindex="0"><code>[ids]
*

[main]
capslock = layer(mod)
rightalt = layer(dia)
rightcontrol = overload(control, sysrq)

[dia]
a = G-q
o = G-p
u = G-y
s = G-s
e = G-5
` = G-S-;

[mod]
alt = layer(mouse)
j = left
k = down
l = right
i = up
u = home
o = end
y = pageup
h = pagedown
p = delete
; = insert

[mouse]
j = kp4
k = kp2
l = kp6
i = kp8
f = leftmouse
s = rightmouse
d = middlemouse
</code></pre><p>What this does is first of all attach  two layers <code>mod</code> and <code>dia</code> to <code>CapsLock</code> and right <code>Alt</code> respectively, and then it also gives the right <code>Ctrl</code> key a second purpose in life. Holding it still makes it act like your regular <code>Ctrl</code> key, but merely tapping it now will make it act like <code>PrintScreen</code> aka <code>SysRq</code>, allowing me to take screenshots more quickly than my laptop&rsquo;s keyboard layout would regularly allow<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>.</p>
<p>But let&rsquo;s take a closer look at the layers.</p>
<h2 id="solving-my-umlaut-problem">Solving my umlaut problem</h2>
<p>Let&rsquo;s start with the <code>dia</code> layer, since that is what solves my umlaut problem. I couldn&rsquo;t get the compose key to work for me, so I went with mapping my desired shortcuts to the right shortcuts on the international layout for the key to pop up:</p>
<ul>
<li><code>a</code> -&gt; <code>AltGr</code>+<code>q</code> (<code>ä</code>)</li>
<li><code>o</code> -&gt; <code>AltGr</code>+<code>p</code> (<code>ö</code>)</li>
<li><code>u</code> -&gt; <code>AltGr</code>+<code>y</code> (<code>ü</code>)</li>
<li><code>s</code> -&gt; <code>AltGr</code>+<code>s</code> (<code>ß</code>)</li>
<li><code>e</code> -&gt; <code>AltGr</code>+<code>5</code> (<code>€</code>)</li>
<li><code>`</code> -&gt; <code>AltGr</code>+<code>Shift</code>+<code>;</code> (<code>°</code>)</li>
</ul>
<h2 id="adding-a-mod-layer">Adding a mod layer&hellip;</h2>
<p>While at it I decided to also add another feature I&rsquo;m used to from my UHK, and that is the Mod layer together with its arrow keys, home, end etc. So I replicated that as well, which is the <code>mod</code> layer here. And because I cannot remember a single time in my life where I ever needed <code>CapsLock</code>, that became my mod key. With Capslock held, we have the following mappings:</p>
<ul>
<li><code>j</code> -&gt; <code>left</code></li>
<li><code>k</code> -&gt; <code>down</code></li>
<li><code>l</code> -&gt; <code>right</code></li>
<li><code>i</code> -&gt; <code>up</code></li>
<li><code>u</code> -&gt; <code>home</code></li>
<li><code>o</code> -&gt; <code>end</code></li>
<li><code>y</code> -&gt; <code>pageup</code></li>
<li><code>h</code> -&gt; <code>pagedown</code></li>
<li><code>p</code> -&gt; <code>delete</code></li>
<li><code>;</code> -&gt; <code>insert</code></li>
</ul>
<p>I&rsquo;ve since also enabled <code>CapsLock</code> as <code>Mod</code> key on my UHK, in the hopes that this will accelerate my muscle memory learning process.</p>
<h2 id="-and-adding-a-mouse-layer-too">&hellip; and adding a mouse layer too!</h2>
<p>And then I thought, hm, can I maybe even add a mouse layer? And yes, I can. So I added a <code>mouse</code> layer, which is activated by holding <code>Alt</code> and <code>CapsLock</code> together. I enabled mouse keys in Gnome&rsquo;s accessibility settings, which allows me to move the mouse cursor with the numpad keys. My keyboard does not <em>have</em> numpad keys, but keyd doesn&rsquo;t care, and so we end up with this mapping:</p>
<ul>
<li><code>j</code> -&gt; move cursor left</li>
<li><code>k</code> -&gt; move cursor down</li>
<li><code>l</code> -&gt; move cursor right</li>
<li><code>i</code> -&gt; move cursor up</li>
<li><code>f</code> -&gt; left mouse button</li>
<li><code>s</code> -&gt; right mouse button</li>
<li><code>d</code> -&gt; middle mouse button</li>
</ul>
<p>I&rsquo;m not sure I&rsquo;ll actually use this a lot tbh, this was more a case of &ldquo;can it be done?&rdquo; and &ldquo;why not?&rdquo;. But it&rsquo;s nice to have the option. Something I still need to experiment on however are the acceleration settings, because out of the box this was way too slow for me, so after finding an answer <a href="https://askubuntu.com/a/1234995">here</a> I changed the mouse key parameters a bit via</p>
<pre tabindex="0"><code>gsettings set org.gnome.desktop.a11y.keyboard mousekeys-max-speed 2000;
gsettings set org.gnome.desktop.a11y.keyboard mousekeys-init-delay 20;
gsettings set org.gnome.desktop.a11y.keyboard mousekeys-accel-time 2000;
</code></pre><p>But I&rsquo;m not 100% happy with this yet and need to play around with things a bit more.</p>
<h2 id="fixing-the-disable-touchpad-while-typing-feature">Fixing the &ldquo;Disable Touchpad while Typing&rdquo; feature</h2>
<p>One problem arose from all of this reconfiguration, and that was that the &ldquo;Disable Touchpad while Typing&rdquo; (DWT) feature was no longer working, which turned out to be a rather big deal - I kept unintentionally moving the cursor or even clicking on things while typing. Thankfully a quick search made me stumble over <a href="https://linuxtouchpad.org/libinput/2022/05/07/disable-while-typing.htmls">this helpful post</a> that not only explained the issue but also showed me how to solve it.</p>
<p>To summarize, DWT works by pairing up touchpad and keyboard either by them having the same vendor and product id, or being marked as &ldquo;internal&rdquo;. In the case of keyd acting as my keyboard, neither was true anymore and thus DWT no longer worked. The solution was to modify the <code>libinput</code> properties of the virtual keyd keyboard such that it would be marked as &ldquo;internal&rdquo;. Putting this into <code>/etc/libinput/local-overrides.quirks</code> sufficed:</p>
<pre tabindex="0"><code>[Virtual Keyboard]
MatchUdevType=keyboard
MatchName=keyd virtual keyboard
AttrKeyboardIntegration=internal
</code></pre><h2 id="conclusion">Conclusion</h2>
<p>keyd seems like a powerful tool, and even though I haven&rsquo;t actually yet tested it, should at any point I want to switch to X11 or a blank terminal on this laptop now, all these mappings should continue to function (as long as I set the keyboard layout to English international). That&rsquo;s definitely a way cleaner solution than the <code>xmodmap</code> approach I was using in the past. And I can now finally type my name again!</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>US ANSI layout is <em>so</em> much nicer for programming than the German ISO DE layout. I switched around three years ago when I got my first <a href="https://ultimatehackingkeyboard.com/">UHK</a> and haven&rsquo;t looked back.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>I tend take a <em>lot</em> of screenshots (mostly OctoPrint related), so this is a big deal for me.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>How to make dnf default to yes</title><link>https://foosel.net/til/how-to-make-dnf-default-to-yes/</link><pubDate>Thu, 02 Mar 2023 00:00:00 +0000</pubDate><guid>https://foosel.net/til/how-to-make-dnf-default-to-yes/</guid><description>&lt;p&gt;I&amp;rsquo;m currently in the process of setting up my new &lt;a href="https://frame.work"&gt;Frame.work laptop&lt;/a&gt;, and since I&amp;rsquo;ve been using Debian-derivatives for the past two decades now, I decided to use the opportunity, try something new for once and installed &lt;a href="https://fedoraproject.org"&gt;Fedora&lt;/a&gt;&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Something that got annoying quickly is that the package manager command &lt;code&gt;dnf&lt;/code&gt; defaults to &amp;ldquo;no&amp;rdquo; when asking if you really want to install a package plus its dependencies. I&amp;rsquo;m very used to &lt;code&gt;apt&lt;/code&gt;&amp;rsquo;s behaviour here that allows me to type &lt;code&gt;sudo apt install &amp;lt;package&amp;gt;&lt;/code&gt; and then just hit &lt;code&gt;Enter&lt;/code&gt; on the sanity check. I wanted the same for &lt;code&gt;dnf&lt;/code&gt;, but without bypassing the sanity check altogether. I did some digging together with my buddy &lt;a href="https://ben.sycha.uk/"&gt;Ben&lt;/a&gt; and we found the answer.&lt;/p&gt;</description><content:encoded><![CDATA[<p>I&rsquo;m currently in the process of setting up my new <a href="https://frame.work">Frame.work laptop</a>, and since I&rsquo;ve been using Debian-derivatives for the past two decades now, I decided to use the opportunity, try something new for once and installed <a href="https://fedoraproject.org">Fedora</a><sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>.</p>
<p>Something that got annoying quickly is that the package manager command <code>dnf</code> defaults to &ldquo;no&rdquo; when asking if you really want to install a package plus its dependencies. I&rsquo;m very used to <code>apt</code>&rsquo;s behaviour here that allows me to type <code>sudo apt install &lt;package&gt;</code> and then just hit <code>Enter</code> on the sanity check. I wanted the same for <code>dnf</code>, but without bypassing the sanity check altogether. I did some digging together with my buddy <a href="https://ben.sycha.uk/">Ben</a> and we found the answer.</p>
<p>Edit the file <code>/etc/dnf/dnf.conf</code> and add the following line to the <code>[main]</code> section:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>defaultyes<span style="color:#f92672">=</span>True
</span></span></code></pre></div><p>And once that&rsquo;s done, the sanity check now is <code>Y/n</code> instead of <code>y/N</code> and you can just hit <code>Enter</code> to install the package.</p>
<p>(This is btw the first post written on the new laptop and I&rsquo;m really enjoying it so far!)</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>To all my Debian friends: It&rsquo;s really just curiousity and expanding my horizon, no need to try to convert me back or anything like that 😉&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>(Almost) one year with the Steamdeck</title><link>https://foosel.net/blog/2023-02-26-one-year-with-the-steamdeck/</link><pubDate>Sun, 26 Feb 2023 00:00:00 +0000</pubDate><guid>https://foosel.net/blog/2023-02-26-one-year-with-the-steamdeck/</guid><description>Yes, it was totally worth it!</description><content:encoded><![CDATA[<p>When Valve announced the Steamdeck back in summer of 2021, I was immediately sold. At that point my primary gaming device - despite owning a quite capable PC with enough computing and graphics power to run even the latest AAA games at a decent enough quality - was my Nintendo Switch, simply because I was not particularly happy about spending my after hours in front of the same PC that I already spend my working hours on. Sitting on the couch and playing something in handheld mode while my partner was playing something on his gaming laptop or the PS4 turned out to be an &ldquo;alone time together&rdquo; scenario we both enjoy very much, and was an additional reason I didn&rsquo;t want to spend hours alone in front of my PC. So, when the reservations opened on July 16th 2021 at 19:00 CEST, I was prepared and spent the next 22min frantically trying and finally succeeding to reserve my spot in the EU 256GB queue. And then the waiting began.</p>
<p>As late February of 2022 rolled around, Valve finally started to work through the queue in weekly batches, and I was eyeing my inbox quite nervously in constant fear of missing my order window. I finally got my mail as part of the third batch on March 14th at 17:36 and ordered the deck minutes later. Then the second phase of waiting started, first for the shipment notification which came on March 16th, and then for the actual delivery. Some delays in shipping made my deck just miss a Friday delivery window and instead I got it into my hands only on the coming Monday, which happened to be March 21st - my birthday. And thus the Steamdeck turned into an unintended birthday present to myself 😅</p>
<p>Now almost one year later I can confirm what I hoped when I reserved mine and what I suspected when the first reviews came in: <strong>Yes, this was one of my best acquisitions of 2022!</strong><sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<p>I may not use it daily, there are still days and sometimes weeks during which I don&rsquo;t play due to the one or other reason, but when I play something it&rsquo;s been pretty much exclusively on my deck for the past year. Pretty much anything I have so far thrown at it has worked just fine and I&rsquo;ve spent some wonderful hours on it, playing things like Death Stranding, In Other Waters, Need for Speed: Heat, Gears: Tactics, Return to Monkey Island and much more, and also the one or other multiplayer session of the The Ascent with my partner.</p>
<p><img alt="foosel&rsquo;s Steam Replay 2022. 79 Games Played, top 2% of players. 266 Achievements, 383 Sessions, 63 New Games" loading="lazy" src="/blog/2023-02-26-one-year-with-the-steamdeck/steam_2022.png"></p>
<p>Not only did I game on it however, it also fuelled my tinkering and making hobby. Right at the start of this year on January 1st I swapped its noisy Delta with a much more silent Huaying fan that I bought from iFixit, and also replaced the 256GB SSD with a 1TB one. The <a href="https://www.ifixit.com/Device/Steam_Deck">iFixit guides</a> where spot on and the deck turned out to be even more self repair compatible than the iFixit repairability score of 7/10 suggested. Only a few days later I opened up my deck again on my quest to create <a href="/blog/2023-01-19-custom-steamdeck-buttons/">custom action buttons</a> for my partner&rsquo;s deck<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> for our anniversary, which were a huge success not only with him - I made it on <a href="https://hackaday.com/2023/01/26/casting-custom-resin-buttons-for-the-steam-deck/">Hackaday</a> for the second time in my life <sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> 🤭 And now I already have two third party hall effect joysticks lying around here that I plan to swap in ASAP, and of course I still have plans for even more custom buttons.</p>
<p><img alt="Finished custom coloured buttons in a Steamdeck" loading="lazy" src="../2023-01-19-custom-steamdeck-buttons/poster.jpg"></p>
<p>Recently I also set up <a href="https://www.emudeck.com/">Emudeck</a> on it and immediately played through Simon the Sorcerer on ScummVM. Of course there&rsquo;s now also some Zelda ROMs on there that are just waiting for me to give Hyrule another visit. And a gift of <a href="http://cleogame.com/">Cleo</a> on GOG also made me set up <a href="https://heroicgameslauncher.com/">Heroic Launcher</a> and <a href="/til/how-to-add-itchio-games-to-the-steamdeck/">itch.io</a> for more store options. Of course at some point I also ensured I can use the deck as a development system and installed VS Code on it plus a full OctoPrint dev environment. I installed <a href="https://deckbrew.xyz/">Decky</a> and am very much enjoying plugins like <a href="https://github.com/hulkrelax/hltb-for-deck">HLTB</a> and <a href="https://github.com/SteamGridDB/decky-steamgriddb">SteamGridDB</a>. I&rsquo;ve made sure <a href="/til/how-to-automatically-sync-screenshots-from-the-steamdeck-to-google-photos/">my screenshots get synced to a special album in Google Photos</a> so sharing stuff is ridiculously easy. And I printed the one or other accessory for myself, my partner and friends.</p>
<p><img alt="Visual Studio Code with OctoPrint&rsquo;s code running on a Steamdeck in desktop mode. In front of the deck there&rsquo;s a foldable Bluetooth keyboard." loading="lazy" src="/blog/2023-02-26-one-year-with-the-steamdeck/octoprint_ide.jpg"></p>
<p>Finally, I should also say that it has changed my perspective on PC gaming. Before I got the deck, there were regular investments in a faster GPU, more RAM, a faster CPU, a better power supply, to keep a capable gaming system on hand and not lock myself out from the wonderful world that is PC games. But now? I rarely power up my PC these days, after also switching to working from my laptop in late 2022, for energy saving reasons. I&rsquo;m still happy to have it available, for streaming, video cutting, Blender and the one or other Tabletop Simulator session, but overall I&rsquo;m going to save a ton of money in the long-term now I think. The 1080Ti I have in there is still good for all the above for a long time to come, and with the deck being able to play what I tend to play so far, and in a way better way for me - handheld on the couch - I see myself rather invest in a Steamdeck 2.0 than regularly throwing money at more and more ridiculous GPUs.</p>
<p>So, all in all, even one year later I&rsquo;m absolutely happy with my deck as you can see. It turned out to be the <em>perfect</em> device for me and has finally made me tackle my gaming backlog<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup>. I&rsquo;m a happy gamer 😊</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>The other was my Ebike.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>He ordered his a day after I received mine and got it half a year later in mid September. Shortly after that the queue was done and since then it&rsquo;s been way faster to get your hands on a deck in EU, US or UK.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>The first time was OctoPrint 😊&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>Admittedly it has also contributed to its growth at the same time due to me now buying more stuff on sales and even having subscribed to Humble Choice (<a href="https://www.humblebundle.com/membership?refc=WhoXKx">referral link</a>) 😅&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>How to add itch.io games to the Steamdeck</title><link>https://foosel.net/til/how-to-add-itchio-games-to-the-steamdeck/</link><pubDate>Fri, 24 Feb 2023 00:00:00 +0000</pubDate><guid>https://foosel.net/til/how-to-add-itchio-games-to-the-steamdeck/</guid><description>&lt;p&gt;I&amp;rsquo;m currently setting up some alternative game stores on my Steamdeck, specifically &lt;a href="https://www.emudeck.com/"&gt;Emudeck&lt;/a&gt; for my retro collection, &lt;a href="https://heroicgameslauncher.com/"&gt;Heroic Launcher&lt;/a&gt;&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt; for GOG and Epic, and also &lt;a href="https://itch.io"&gt;itch.io&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I stumbled across &lt;a href="https://www.reddit.com/r/SteamDeck/comments/vwili3/better_way_to_itchio_on_steam_deck/"&gt;this Reddit post&lt;/a&gt; that recommended to use the itch.io Windows launcher instead of the native Linux one:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Itch.io has an app, that even has linux version. But it has issues - it can only use one wine version, if you have it installed globally, it can&amp;rsquo;t even handle linux games well. It pretends to install them, and when you launch them it opens a directory with the zip file&amp;hellip; Or it just doesn&amp;rsquo;t work after installation. Then you need to add all the games to steam, setup their images, and other stuff. There&amp;rsquo;s boilr for that, but it doesn&amp;rsquo;t find everything, and most of the indies are not in the database anyway.&lt;/p&gt;</description><content:encoded><![CDATA[<p>I&rsquo;m currently setting up some alternative game stores on my Steamdeck, specifically <a href="https://www.emudeck.com/">Emudeck</a> for my retro collection, <a href="https://heroicgameslauncher.com/">Heroic Launcher</a><sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> for GOG and Epic, and also <a href="https://itch.io">itch.io</a>.</p>
<p>I stumbled across <a href="https://www.reddit.com/r/SteamDeck/comments/vwili3/better_way_to_itchio_on_steam_deck/">this Reddit post</a> that recommended to use the itch.io Windows launcher instead of the native Linux one:</p>
<blockquote>
<p>Itch.io has an app, that even has linux version. But it has issues - it can only use one wine version, if you have it installed globally, it can&rsquo;t even handle linux games well. It pretends to install them, and when you launch them it opens a directory with the zip file&hellip; Or it just doesn&rsquo;t work after installation. Then you need to add all the games to steam, setup their images, and other stuff. There&rsquo;s boilr for that, but it doesn&rsquo;t find everything, and most of the indies are not in the database anyway.</p>
</blockquote>
<p>Sounds reasonable to go with the Windows version then, so I followed the post and got everything working. Quick summary in case the link goes stale:</p>
<ul>
<li>In desktop mode, download the Windows installer from <a href="https://itch.io/app">https://itch.io/app</a>, add it as a non steam game, configure stable Proton for it, launch it, complete the installer and log in.</li>
<li>Open Dolphin, navigate to <code>home/deck/.steam/steam/steamapps/compatdata</code></li>
<li>Click on the search icon, check &ldquo;From here&rdquo;, search for <code>itch</code> and enter the first found folder of that name. Look at the address bar, you&rsquo;ll be in a subfolder of something like <code>/home/deck/.steam/steam/steamapps/compatdata/&lt;number&gt;</code> for a random <code>&lt;number&gt;</code>, this parent folder is what to use for <code>&lt;basefolder&gt;</code> in any following steps.</li>
<li>In desktop Steam, open the preferences of your non-steam itch.io installer &ldquo;game&rdquo;. Replace &ldquo;Target&rdquo; with <code>&lt;basefolder&gt;/pfx/drive_c/users/steamuser/Desktop/itch.lnk</code> and &ldquo;Start in&rdquo; with <code>&lt;basefolder&gt;/pfx/drive_c/users/steamuser/AppData/Local/Itch</code>. Rename it to &ldquo;itch.io&rdquo; or whatever else you want it to be called<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>.</li>
</ul>
<p>I then followed the steps to also allow <a href="https://steamgriddb.github.io/steam-rom-manager/">Steam Rom Manager</a> to detect my itch.io games, and created a custom itch.io parser. Here I had to slightly deviate from the suggested steps. Again, summarised here for reference:</p>
<ul>
<li>Parser type: glob</li>
<li>Title: <code>itch.io</code></li>
<li>Steam category: <code>${itch.io}</code></li>
<li>Steam directory: <code>${steamdirglobal}</code></li>
<li>ROMs directory: <code>&lt;basefolder&gt;/pfx/drive_c/users/steamuser/AppData/Roaming/itch/apps</code></li>
<li>Executable modifier: <code>&quot;${exePath}&quot;</code> (with quotes!)</li>
<li>User&rsquo;s glob: <code>${title}/{*/,}!(Unity*).exe</code> <strong>(this one is different than on the Reddit post, see below for why!)</strong></li>
<li>Leave anything else as is.</li>
</ul>
<p>I changed the glob pattern as the original setting of <code>${title}/{*/*,*}.exe</code> was happily detecting the Unity crash handler executable contained in some games as additional entry, obviously not what I wanted.</p>
<p>After some trial and error I thankfully was able to solve this with the slightly different glob pattern of <code>${title}/{*/,}!(Unity*).exe</code>. It now matches any <code>exe</code> right in the game folder or one folder deep that <em>doesn&rsquo;t</em> start with the string <code>Unity</code>. And if push comes to shove I can add additional forbidden patterns as well.</p>
<p>Even Unity games now only generate one entry, and seem to work fine once I&rsquo;ve figured out the right Proton version 👍</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Basically a one click install from the &ldquo;Discover&rdquo; app in desktop mode.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>My SteamGridDB <a href="https://deckbrew.xyz/">Decky</a> plugin seemed happy with that name as I was able to quickly download matching artwork once back in Game mode.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>On success</title><link>https://foosel.net/blog/2023-02-21-on-success/</link><pubDate>Tue, 21 Feb 2023 00:00:00 +0000</pubDate><guid>https://foosel.net/blog/2023-02-21-on-success/</guid><description>Everything has a price, nothing in life is free</description><content:encoded><![CDATA[<p><em>I originally published this in October 2020 as a Twitter thread. Even though it&rsquo;s been a couple of years, the points still stand, so I decided to preserve it here as a short blog post.</em></p>
<p>It&rsquo;s easy to only see the success and also to only <em>share</em> the success in our journeys. But success is usually just the end of a long path of struggle, compromises, setbacks, failure.</p>
<p>Over the years, <a href="/talks/">I&rsquo;ve talked about my journey a lot</a>. About the kind of measures I took and continue to take to protect my physical and mental health from the effects of running a project like OctoPrint.</p>
<p>Sometimes you might find me posting a GIF on social media of me using a heavy bag in my office:</p>
<p><img alt="A perfectly looping GIF of me punching my heavy bag" loading="lazy" src="/blog/2023-02-21-on-success/gina_angry.gif#center"></p>
<p>That GIF and the bag is a big part of my coping strategy and has actually spawned a whole talk (&ldquo;How to deal with toxic people&rdquo;) that I&rsquo;ve now had the pleasure of giving a bunch of times.</p>
<p>I have been running OctoPrint for a decade now. It&rsquo;s my baby, it&rsquo;s a story of success for me. But it has a cost not many see.</p>
<p>Stress. A sheer <strong>ton</strong> of pressure on my shoulders. Sleepless nights here and there. Worry about funding, about retirement, about security, about the future as a whole. A <strong>strong</strong> imposter syndrome I have to combat daily (&ldquo;this was all just luck and I don&rsquo;t belong here&rdquo;). Lots of set backs. Aggressive people. Burnout. A lot of &ldquo;I&rsquo;m not good enough&rdquo; and &ldquo;I&rsquo;m not doing enough&rdquo; and &ldquo;I&rsquo;m not doing it right&rdquo;. And of course a lot of &ldquo;I&rsquo;m not doing enough to protect myself from all of this&rdquo;.</p>
<p>It&rsquo;s easy to put a smile on your face and radiate success and security and all that stuff we get drilled into us that we need to be. This is what you are taught to show the world. But this is just the surface. <strong>No</strong> successful project I know of didn&rsquo;t have a price.</p>
<p>I would be lying if I claimed I have never just sat there and wondered if I should just stop doing this to myself. So far the good outweighs <a href="https://youtu.be/6ILoSjQ94HY">the bad and the ugly</a>. I sincerely hope it stays that way. But never assume someone &ldquo;just has success&rdquo;. Everything has a price, nothing in life is free.</p>
<p>With that being said, I feel obligated to confirm that I am in fact ok, and you all don&rsquo;t have to worry about me 🙂 (at least not more than usual 😉). I&rsquo;ve posted this because I think it is important to talk about these things a bit more publicly, since it&rsquo;s indeed way too often we only see the success and not what lead to it, what it cost or what it continues to cost.</p>
]]></content:encoded></item><item><title>How to automatically sync screenshots from the Steamdeck to Google Photos</title><link>https://foosel.net/til/how-to-automatically-sync-screenshots-from-the-steamdeck-to-google-photos/</link><pubDate>Sun, 19 Feb 2023 00:00:00 +0000</pubDate><guid>https://foosel.net/til/how-to-automatically-sync-screenshots-from-the-steamdeck-to-google-photos/</guid><description>&lt;p&gt;As a follow-up to &lt;a href="https://foosel.net/til/how-to-automatically-sync-screenshots-from-the-steamdeck-to-google-drive/"&gt;my earlier post about how to sync screenshots to Google Drive&lt;/a&gt; here&amp;rsquo;s how to achieve the same but with a dedicated &amp;ldquo;Steamdeck&amp;rdquo; album on Google Photos instead.&lt;/p&gt;
&lt;p&gt;Once again we are using &lt;code&gt;rclone&lt;/code&gt; for syncing.&lt;/p&gt;
&lt;p&gt;First I created a new target &lt;code&gt;gphoto&lt;/code&gt; by running &lt;code&gt;~/bin/rclone config&lt;/code&gt; again and then following &lt;a href="https://rclone.org/googlephotos/"&gt;these steps&lt;/a&gt;. Quick summary:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;New remote&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gphoto&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Empty application ID and secret&lt;/li&gt;
&lt;li&gt;Full access&lt;/li&gt;
&lt;li&gt;No advanced config&lt;/li&gt;
&lt;li&gt;Use web browser to authenticate&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I then created a new album:&lt;/p&gt;</description><content:encoded><![CDATA[<p>As a follow-up to <a href="/til/how-to-automatically-sync-screenshots-from-the-steamdeck-to-google-drive/">my earlier post about how to sync screenshots to Google Drive</a> here&rsquo;s how to achieve the same but with a dedicated &ldquo;Steamdeck&rdquo; album on Google Photos instead.</p>
<p>Once again we are using <code>rclone</code> for syncing.</p>
<p>First I created a new target <code>gphoto</code> by running <code>~/bin/rclone config</code> again and then following <a href="https://rclone.org/googlephotos/">these steps</a>. Quick summary:</p>
<ol>
<li><code>New remote</code></li>
<li><code>gphoto</code></li>
<li>Empty application ID and secret</li>
<li>Full access</li>
<li>No advanced config</li>
<li>Use web browser to authenticate</li>
</ol>
<p>I then created a new album:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>rclone mkdir gphoto:album/Steamdeck
</span></span></code></pre></div><p>and adjusted <code>~/bin/sync_screenshots</code> to use the new remote and remote path:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>REMOTE_NAME<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;gphoto&#39;</span>
</span></span><span style="display:flex;"><span>REMOTE_DIR<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;album/Steamdeck&#39;</span>
</span></span></code></pre></div><p>That was all.</p>
<p>Obviously the same can be done with any of the other sync targets that <code>rclone</code> supports, of which <a href="https://rclone.org/overview/">there are many</a>. For ownCloud or NextCloud it looks like <a href="https://rclone.org/webdav/">WebDAV</a> is the right option to choose.</p>
<p><em>Update 2025-03-25</em>: There&rsquo;s now also a <a href="/til/how-to-automatically-sync-screenshots-from-the-steamdeck-to-immich/">TIL on how to do the same for Immich</a>.</p>
]]></content:encoded></item><item><title>How to add a switch for a port forward on Unifi to Home Assistant</title><link>https://foosel.net/til/how-to-add-a-switch-for-a-port-forward-on-unifi-to-home-assistant/</link><pubDate>Fri, 17 Feb 2023 00:00:00 +0000</pubDate><guid>https://foosel.net/til/how-to-add-a-switch-for-a-port-forward-on-unifi-to-home-assistant/</guid><description>&lt;p&gt;This is admittedly something I did not learn today but rather learned and adapted a couple years ago &lt;a href="https://community.home-assistant.io/t/automating-unifi-port-forwarding-based-upon-presence-detection/168185"&gt;from this post on the Home Assistant forum&lt;/a&gt;, but I just had to use it again today and so I figured I&amp;rsquo;d write it down with all the bells and whistles just in case I ever need this information again - or anyone else does.&lt;/p&gt;
&lt;p&gt;First of all, in your unifi controller you should create a new user that Home Assistant will act as to manage your port forward(s) for you. So, log into the controller, go into Settings &amp;gt; Administrators and add a new Administrator user&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;</description><content:encoded><![CDATA[<p>This is admittedly something I did not learn today but rather learned and adapted a couple years ago <a href="https://community.home-assistant.io/t/automating-unifi-port-forwarding-based-upon-presence-detection/168185">from this post on the Home Assistant forum</a>, but I just had to use it again today and so I figured I&rsquo;d write it down with all the bells and whistles just in case I ever need this information again - or anyone else does.</p>
<p>First of all, in your unifi controller you should create a new user that Home Assistant will act as to manage your port forward(s) for you. So, log into the controller, go into Settings &gt; Administrators and add a new Administrator user<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>.</p>
<p>Then create your port forward in Settings &gt; Routing &amp; Firewall &gt; Port Forwarding. Take note of the id of the port forward you have created - you can find it by clicking edit on it again, it will be the number at the end of the URL of the edit page. E.g. if the URL looks like this: <code>https://my.unifi.controller/manage/site/default/settings/portforward/edit/1234567890</code> then this is the id of the port forward: <code>1234567890</code>.</p>
<p>Next, copy this shell script to <code>/config/scripts/unifi.sh</code> in your Home Assistant. Make sure to adjust <code>https://my.unifi.controller</code> (and, if necessary, the site <code>default</code>) to your own values.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e">#!/bin/sh
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>set -e
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># based on https://community.home-assistant.io/t/automating-unifi-port-forwarding-based-upon-presence-detection/168185</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>cookie<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>mktemp<span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>headers<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>mktemp<span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>curl_cmd<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;curl --silent --cookie </span><span style="color:#e6db74">${</span>cookie<span style="color:#e6db74">}</span><span style="color:#e6db74"> --cookie-jar </span><span style="color:#e6db74">${</span>cookie<span style="color:#e6db74">}</span><span style="color:#e6db74"> -D </span><span style="color:#e6db74">${</span>headers<span style="color:#e6db74">}</span><span style="color:#e6db74"> --insecure&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>BASEURL<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;https://my.unifi.controller&#34;</span>
</span></span><span style="display:flex;"><span>SITE<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;default&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>auth<span style="color:#f92672">()</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>  USERNAME<span style="color:#f92672">=</span>$1
</span></span><span style="display:flex;"><span>  PASSWORD<span style="color:#f92672">=</span>$2
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e"># authenticate against unifi controller</span>
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">${</span>curl_cmd<span style="color:#e6db74">}</span> --output /dev/null -d <span style="color:#e6db74">&#34;{\&#34;username\&#34;:\&#34;</span>$USERNAME<span style="color:#e6db74">\&#34;, \&#34;password\&#34;:\&#34;</span>$PASSWORD<span style="color:#e6db74">\&#34;}&#34;</span> $BASEURL/api/login
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e"># grab the `x-csrf-token` and strip the newline (added when upgraded to controller 6.1.26)</span>
</span></span><span style="display:flex;"><span>  csrf<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;</span><span style="color:#66d9ef">$(</span>awk -v FS<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;: &#39;</span> <span style="color:#e6db74">&#39;/^x-csrf-token/{print $2}&#39;</span> <span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>headers<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span> | tr -d <span style="color:#e6db74">&#39;\r&#39;</span><span style="color:#66d9ef">)</span><span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>  echo $csrf
</span></span><span style="display:flex;"><span><span style="color:#f92672">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>portfwd<span style="color:#f92672">()</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>  USERNAME<span style="color:#f92672">=</span>$1
</span></span><span style="display:flex;"><span>  PASSWORD<span style="color:#f92672">=</span>$2
</span></span><span style="display:flex;"><span>  FORWARD_ID<span style="color:#f92672">=</span>$3
</span></span><span style="display:flex;"><span>  FORWARD_ENABLED<span style="color:#f92672">=</span>$4
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e"># authenticate against unifi controller</span>
</span></span><span style="display:flex;"><span>  csrf<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>auth $USERNAME $PASSWORD<span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e"># enable/disable firewall rule</span>
</span></span><span style="display:flex;"><span>  body<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span><span style="color:#e6db74">${</span>curl_cmd<span style="color:#e6db74">}</span> -X GET $BASEURL/api/s/default/rest/portforward/$FORWARD_ID | jq <span style="color:#e6db74">&#39;.data[0] | .enabled=&#39;</span>$FORWARD_ENABLED<span style="color:#e6db74">&#39;&#39;</span><span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">${</span>curl_cmd<span style="color:#e6db74">}</span> -X PUT $BASEURL/api/s/default/rest/portforward/$FORWARD_ID -H <span style="color:#e6db74">&#34;Content-Type: application/json&#34;</span> -H <span style="color:#e6db74">&#34;x-csrf-token: </span><span style="color:#e6db74">${</span>csrf<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span> -d @&lt;<span style="color:#f92672">(</span>echo <span style="color:#e6db74">&#34;</span>$body<span style="color:#e6db74">&#34;</span><span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>isportfwd<span style="color:#f92672">()</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>  USERNAME<span style="color:#f92672">=</span>$1
</span></span><span style="display:flex;"><span>  PASSWORD<span style="color:#f92672">=</span>$2
</span></span><span style="display:flex;"><span>  FORWARD_ID<span style="color:#f92672">=</span>$3
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e"># authenticate against unifi controller</span>
</span></span><span style="display:flex;"><span>  csrf<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>auth $USERNAME $PASSWORD<span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#e6db74">${</span>curl_cmd<span style="color:#e6db74">}</span> -X GET $BASEURL/api/s/default/rest/portforward/$FORWARD_ID | jq <span style="color:#e6db74">&#39;.data[0].enabled&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#e6db74">&#34;</span>$@<span style="color:#e6db74">&#34;</span>
</span></span></code></pre></div><p>Now, let&rsquo;s imagine you want to add a switch for an SFTP port forward that you&rsquo;ve just created. Then, in your <code>secrets.yaml</code> file, add the following:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">unifi_forward_sftp_check</span>: <span style="color:#e6db74">&#39;/bin/bash /config/scripts/unifi.sh isportfwd &lt;user&gt; &lt;password&gt; &lt;forward_id&gt;&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">unifi_forward_sftp_enable</span>: <span style="color:#e6db74">&#39;/bin/bash /config/scripts/unifi.sh portfwd &lt;user&gt; &lt;password&gt; &lt;forward_id&gt; true&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">unifi_forward_sftp_disable</span>: <span style="color:#e6db74">&#39;/bin/bash /config/scripts/unifi.sh portfwd &lt;user&gt; &lt;password&gt; &lt;forward_id&gt; false&#39;</span>
</span></span></code></pre></div><p>Replace <code>&lt;user&gt;</code>, <code>&lt;password&gt;</code> and <code>&lt;forward_id&gt;</code> with the login credentials and id of the forward you just created.</p>
<p>Next, add a command line switch definition to your <code>configuration.yaml</code> (or in my case to my <code>packages/network.yaml</code> file):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">switch</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">platform</span>: <span style="color:#ae81ff">command_line</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">switches</span>: 
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">sftp_port_forward</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">friendly_name</span>: <span style="color:#e6db74">&#34;SFTP Port Forward&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">command_state</span>: !<span style="color:#ae81ff">secret unifi_forward_sftp_check</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">command_on</span>: !<span style="color:#ae81ff">secret unifi_forward_sftp_enable</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">command_off</span>: !<span style="color:#ae81ff">secret unifi_forward_sftp_disable</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">value_template</span>: <span style="color:#e6db74">&#39;{{ bool(value, false) }}&#39;</span>
</span></span></code></pre></div><p>Throw that somewhere on your dashboard, or alternatively tie it into some automation, and you&rsquo;re good to go!</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Maybe a regular user suffices as well, I honestly can&rsquo;t remember, but I&rsquo;m using an admin user here.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>How to automatically sync screenshots from the Steamdeck to Google Drive</title><link>https://foosel.net/til/how-to-automatically-sync-screenshots-from-the-steamdeck-to-google-drive/</link><pubDate>Sat, 11 Feb 2023 00:00:00 +0000</pubDate><guid>https://foosel.net/til/how-to-automatically-sync-screenshots-from-the-steamdeck-to-google-drive/</guid><description>&lt;p&gt;I wanted to automatically sync the screenshots I take on my Steamdeck to some cloud, without having to manually do it for every single one in the Steamdeck&amp;rsquo;s own uploader.&lt;/p&gt;
&lt;p&gt;I came across &lt;a href="https://gist.github.com/pegasd/048bd5d53558f066765253d55a456306"&gt;this gist by pegasd&lt;/a&gt; that accomplishes this via rclone, a path monitoring systemd service and some reconfiguration in Steam. However, I had to adjust things slightly for everything to really work - I could imagine that some past Steam update changed things slightly vs when the gist was created:&lt;/p&gt;</description><content:encoded><![CDATA[<p>I wanted to automatically sync the screenshots I take on my Steamdeck to some cloud, without having to manually do it for every single one in the Steamdeck&rsquo;s own uploader.</p>
<p>I came across <a href="https://gist.github.com/pegasd/048bd5d53558f066765253d55a456306">this gist by pegasd</a> that accomplishes this via rclone, a path monitoring systemd service and some reconfiguration in Steam. However, I had to adjust things slightly for everything to really work - I could imagine that some past Steam update changed things slightly vs when the gist was created:</p>
<ol>
<li>I had to make sure that I selected the option to make Steam create uncompressed screenshots.</li>
<li>I had to manually start the path watcher.</li>
</ol>
<p>Here&rsquo;s a quick summary of how I managed to make things work on my deck (all credits go to <a href="https://github.com/pegasd">@pegasd</a>, replicating things here mostly so they don&rsquo;t get lost in the future).</p>
<h2 id="prerequisites">Prerequisites</h2>
<p>First of all I switched into Desktop mode.</p>
<p>Since I had not yet done this since upgrading my Deck&rsquo;s SSD, I set a password for my user account by opening a terminal and running <code>passwd</code>. I also made sure to install Firefox from the package manager.</p>
<h2 id="setting-up-the-screenshot-directory">Setting up the screenshot directory</h2>
<p>I opened a terminal and created a dedicated screenshot folder:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>mkdir ~/.steam_screenshots
</span></span></code></pre></div><p>Next I had to tell Steam to use this folder. Still in desktop mode I opened Steam settings and on the &ldquo;In-Game&rdquo; tab set the screenshot folder to the one I had just created. I also made sure to check &ldquo;Save uncompressed copy&rdquo;, as <a href="https://steamcommunity.com/discussions/forum/1/4329623982989743690/#c4329623982989971883">otherwise Steam won&rsquo;t use the just configured custom folder</a>.</p>
<h2 id="installing-and-configuring-rclone">Installing and configuring rclone</h2>
<p>Next, still on the deck, I <a href="https://rclone.org/downloads/">downloaded rclone</a> (&ldquo;Intel/AMD - 64 Bit / Linux&rdquo;). I opened the file browser, opened the archive I had just downloaded and extracted the <code>rclone</code> binary into <code>~/bin</code> (if that doesn&rsquo;t exist yet, just create it).</p>
<p>I then went back to the terminal, ran <code>~/bin/rclone config</code> and configured a new remote <code>gdrive</code> following <a href="https://rclone.org/drive/">these steps</a>. Quick summary:</p>
<ol>
<li><code>New remote</code></li>
<li><code>gdrive</code></li>
<li>Empty application ID and secret</li>
<li>Full access to all files</li>
<li>No service account credentials file</li>
<li>Use web browser to authenticate</li>
<li>Not configured as a shared drive</li>
</ol>
<h2 id="automatic-sync">Automatic sync</h2>
<p>I created a file <code>~/bin/sync_screenshots</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e">#!/usr/bin/env bash
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>RCLONE_BIN<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>HOME<span style="color:#e6db74">}</span><span style="color:#e6db74">/bin/rclone&#34;</span>
</span></span><span style="display:flex;"><span>REMOTE_NAME<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;gdrive&#39;</span>
</span></span><span style="display:flex;"><span>REMOTE_DIR<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;Privat/Steamdeck/Screenshots&#39;</span>
</span></span><span style="display:flex;"><span>SOURCE_DIR<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>HOME<span style="color:#e6db74">}</span><span style="color:#e6db74">/.steam_screenshots&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#e6db74">${</span>RCLONE_BIN<span style="color:#e6db74">}</span> sync <span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>SOURCE_DIR<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span> <span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>REMOTE_NAME<span style="color:#e6db74">}</span><span style="color:#e6db74">:</span><span style="color:#e6db74">${</span>REMOTE_DIR<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>
</span></span></code></pre></div><p>and made it executable with <code>chmod +x ~/bin/sync_screenshots</code>.</p>
<p>Then I created a service file for it, <code>~/.config/systemd/user/sync_screenshots.service</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-plain" data-lang="plain"><span style="display:flex;"><span>[Unit]
</span></span><span style="display:flex;"><span>Description=Sync Steam Screenshots
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>[Service]
</span></span><span style="display:flex;"><span>Type=oneshot
</span></span><span style="display:flex;"><span>ExecStart=%h/bin/sync_screenshots
</span></span></code></pre></div><p>and another file setting up a path watcher, <code>~/.config/systemd/user/sync_screenshots.path</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-plain" data-lang="plain"><span style="display:flex;"><span>[Unit]
</span></span><span style="display:flex;"><span>Description=Sync Steam Screenshots
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>[Path]
</span></span><span style="display:flex;"><span>PathModified=%h/.steam_screenshots
</span></span><span style="display:flex;"><span>Unit=sync_screenshots.service
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>[Install]
</span></span><span style="display:flex;"><span>WantedBy=default.target
</span></span></code></pre></div><p>I enabled the automation:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>sudo systemctl daemon-reload
</span></span><span style="display:flex;"><span>systemctl --user enable sync_screenshots.path
</span></span><span style="display:flex;"><span>systemctl --user start sync_screenshots.path
</span></span></code></pre></div><h2 id="a-quick-test">A quick test</h2>
<p>I checked that the path watcher was up and running with <code>systemctl --user status sync_screenshots.path</code>.</p>
<p>Then I created a quick test file in the synced folder</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>touch ~/.steam_screenshots/test
</span></span></code></pre></div><p>and verified it showed up in the target folder on my Google Drive.</p>
<p>Next I deleted the file on the deck and verified it got deleted in Google Drive.</p>
<p>Finally I booted back into Game mode, took a screenshot there as well with <code>Steam</code>+<code>R1</code> and verified this showed up on my Drive.</p>
<p><img alt="A freshly synced screenshot of my Steamdeck&rsquo;s home screen" loading="lazy" src="/til/how-to-automatically-sync-screenshots-from-the-steamdeck-to-google-drive/screenshot.png"></p>
<p>Success!</p>
<p><em>Update 2023-02-19</em>: There is now also a <a href="/til/how-to-automatically-sync-screenshots-from-the-steamdeck-to-google-photos/">TIL on how to do the same for Google Photos</a>.
<em>Update 2025-03-25</em>: And now there&rsquo;s also a <a href="/til/how-to-automatically-sync-screenshots-from-the-steamdeck-to-immich/">TIL on how to do the same for Immich</a>.</p>
]]></content:encoded></item><item><title>How to trim screenshots via the commandline</title><link>https://foosel.net/til/how-to-trim-screenshots-via-the-commandline/</link><pubDate>Thu, 09 Feb 2023 00:00:00 +0000</pubDate><guid>https://foosel.net/til/how-to-trim-screenshots-via-the-commandline/</guid><description>&lt;p&gt;I just had to trim a bunch of screenshots that had some black borders around them. I didn&amp;rsquo;t want to do this manually or via a GUI, but ideally batch-able via the commandline. Thankfully, that&amp;rsquo;s one of the many things that &lt;a href="https://imagemagick.org/"&gt;ImageMagick&lt;/a&gt; can do for you.&lt;/p&gt;
&lt;p&gt;I put all my screenshot PNGs into a folder, and then in that folder ran this &lt;a href="https://imagemagick.org/script/mogrify.php"&gt;&lt;code&gt;mogrify&lt;/code&gt;&lt;/a&gt; command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;magick mogrify -trim -define trim:percent-background&lt;span style="color:#f92672"&gt;=&lt;/span&gt;0% -background black -path output/ *.png
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I found that I had to add the &lt;code&gt;-define trim:percent-background=0%&lt;/code&gt; option to get rid of &lt;em&gt;all&lt;/em&gt; black borders, as otherwise on some of the images a very slim one ended up remaining. I also specified the background color with &lt;code&gt;-background black&lt;/code&gt; to make sure that it really only trimmed the black borders.&lt;/p&gt;</description><content:encoded><![CDATA[<p>I just had to trim a bunch of screenshots that had some black borders around them. I didn&rsquo;t want to do this manually or via a GUI, but ideally batch-able via the commandline. Thankfully, that&rsquo;s one of the many things that <a href="https://imagemagick.org/">ImageMagick</a> can do for you.</p>
<p>I put all my screenshot PNGs into a folder, and then in that folder ran this <a href="https://imagemagick.org/script/mogrify.php"><code>mogrify</code></a> command:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>magick mogrify -trim -define trim:percent-background<span style="color:#f92672">=</span>0% -background black -path output/ *.png
</span></span></code></pre></div><p>I found that I had to add the <code>-define trim:percent-background=0%</code> option to get rid of <em>all</em> black borders, as otherwise on some of the images a very slim one ended up remaining. I also specified the background color with <code>-background black</code> to make sure that it really only trimmed the black borders.</p>
<p>I then could combine the resulting images into a PDF with <a href="https://pypi.org/project/img2pdf/">img2pdf</a><sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>img2pdf --output output.pdf *.png
</span></span></code></pre></div><div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Look, a hidden bonus TIL!&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>How to quickly generate a QR Code with transparent background</title><link>https://foosel.net/til/how-to-quickly-generate-a-qr-code-with-transparent-background/</link><pubDate>Mon, 06 Feb 2023 00:00:00 +0000</pubDate><guid>https://foosel.net/til/how-to-quickly-generate-a-qr-code-with-transparent-background/</guid><description>&lt;p&gt;For an upcoming presentation I wanted to quickly generate a QR Code of my web site&amp;rsquo;s URL to include on the final slide. Since my slide theme has a green gradient background with white text, I wanted the QR Code to be white on a transparent background, as a PNG.&lt;/p&gt;
&lt;p&gt;Enter &lt;a href="https://github.com/soldair/node-qrcode"&gt;node-qrcode&lt;/a&gt; which runs easily via &lt;code&gt;npx&lt;/code&gt;&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;npx qrcode -o output.png -d FFFF -l &lt;span style="color:#ae81ff"&gt;0000&lt;/span&gt; -w &lt;span style="color:#ae81ff"&gt;500&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;https://foosel.net&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-o output.png&lt;/code&gt; sets the output file&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-d FFFF&lt;/code&gt; sets the dark color (usually black) to white with 100% opacity&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-l 0000&lt;/code&gt; sets the light color (usually white) to black with 0% opacity - fully transparent&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-w 500&lt;/code&gt; sets the size to 500px&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For further options like error correction or QR code version, or how to use it as a library or in the browser, see node-qrcode&amp;rsquo;s repo linked above.&lt;/p&gt;</description><content:encoded><![CDATA[<p>For an upcoming presentation I wanted to quickly generate a QR Code of my web site&rsquo;s URL to include on the final slide. Since my slide theme has a green gradient background with white text, I wanted the QR Code to be white on a transparent background, as a PNG.</p>
<p>Enter <a href="https://github.com/soldair/node-qrcode">node-qrcode</a> which runs easily via <code>npx</code><sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>npx qrcode -o output.png -d FFFF -l <span style="color:#ae81ff">0000</span> -w <span style="color:#ae81ff">500</span>  <span style="color:#e6db74">&#34;https://foosel.net&#34;</span>
</span></span></code></pre></div><ul>
<li><code>-o output.png</code> sets the output file</li>
<li><code>-d FFFF</code> sets the dark color (usually black) to white with 100% opacity</li>
<li><code>-l 0000</code> sets the light color (usually white) to black with 0% opacity - fully transparent</li>
<li><code>-w 500</code> sets the size to 500px</li>
</ul>
<p>For further options like error correction or QR code version, or how to use it as a library or in the browser, see node-qrcode&rsquo;s repo linked above.</p>
<p>I for one am happy with the result of this little exercise:</p>
<p><img alt="The final slide of a presentation. It says &ldquo;Thank you for you attention!&rdquo;. Below that I&rsquo;ve listed my Mastodon account @foosel@chaos.social, my GitHub account @foosel and my website&rsquo;s address foosel.net. A big white QR Code is placed right underneath, a handdrawn arrow points from website to code." loading="lazy" src="/til/how-to-quickly-generate-a-qr-code-with-transparent-background/slide.png"></p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Yes, there are also online services that can do this for you, no, I didn&rsquo;t want to look through dozens of them trying to find one that didn&rsquo;t attempt to make me subscribe to something just to change the color of the generated QR code. Local CLI ftw.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>How to use jq to extract new posts from a JSON Feed</title><link>https://foosel.net/til/how-to-use-jq-to-extract-new-posts-from-a-json-feed/</link><pubDate>Thu, 02 Feb 2023 00:00:00 +0000</pubDate><guid>https://foosel.net/til/how-to-use-jq-to-extract-new-posts-from-a-json-feed/</guid><description>&lt;p&gt;I&amp;rsquo;m currently looking into ways to automate some stuff around new posts on this page (be it blog or TIL post) directly during the page build on GitHub Actions. For this, I first need to be able to reliably &lt;em&gt;detect&lt;/em&gt; new posts, from a bash run step. So here&amp;rsquo;s how to do that with &lt;a href="https://stedolan.github.io/jq/"&gt;&lt;code&gt;jq&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The idea is to get the current &lt;a href="https://foosel.net/til/how-to-add-json-feed-support-to-hugo/"&gt;&lt;code&gt;feed.json&lt;/code&gt;&lt;/a&gt; prior to publishing the page, and then compare it to the one that was just generated during the build. If there are any differences, we know that there are new posts and can trigger further actions from there.&lt;/p&gt;</description><content:encoded><![CDATA[<p>I&rsquo;m currently looking into ways to automate some stuff around new posts on this page (be it blog or TIL post) directly during the page build on GitHub Actions. For this, I first need to be able to reliably <em>detect</em> new posts, from a bash run step. So here&rsquo;s how to do that with <a href="https://stedolan.github.io/jq/"><code>jq</code></a>.</p>
<p>The idea is to get the current <a href="/til/how-to-add-json-feed-support-to-hugo/"><code>feed.json</code></a> prior to publishing the page, and then compare it to the one that was just generated during the build. If there are any differences, we know that there are new posts and can trigger further actions from there.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># Get current feed.json</span>
</span></span><span style="display:flex;"><span>curl -s https://foosel.net/til/feed.json &gt; feed.current.json
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Get new feed.json</span>
</span></span><span style="display:flex;"><span>cp public/til/feed.json feed.next.json
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Compare the two, this is where the magic happens</span>
</span></span><span style="display:flex;"><span>jq --slurpfile current til.current.json --slurpfile next til.next.json -n <span style="color:#e6db74">&#39;$next[0].items - $current[0].items&#39;</span> &gt; til.json
</span></span></code></pre></div><p>Let&rsquo;s go through this <code>jq</code> command there:</p>
<ul>
<li><code>--slurpfile &lt;variable&gt; &lt;file&gt;</code> reads in the given files and makes it accessible as an array contained in the given variable. In this case we read in <code>til.current.json</code> and make it accessible as <code>$current</code>, and also read in <code>til.next.json</code> and make it accessible as <code>$next</code>.</li>
<li><code>-n</code> doesn&rsquo;t wait for input on stdin.</li>
<li><code>'$next[0].items - $current[0].items'</code> subtracts the items from the new feed from the items in the current feed<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>.</li>
<li><code>&gt; til.json</code> writes the output to <code>til.json</code>.</li>
</ul>
<p><code>til.json</code> will then contain all new items (as long as there weren&rsquo;t more than the feed&rsquo;s item size), can be uploaded as an artifact and then used in further jobs<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>The indexing (e.g. <code>$new[0]</code>) here is needed due to <code>--slurpfile</code> creating an array from the read file. I admittedly need to experiment more with this option to fully understand it, but for the purpose here it works.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>My current goal is to move my announcements on Mastodon for new posts from my NodeRED install got the page build, and also send any webmentions for links contained in new posts as well.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>How to grep a log for multiline errors</title><link>https://foosel.net/til/how-to-grep-a-log-for-multline-errors/</link><pubDate>Wed, 01 Feb 2023 00:00:00 +0000</pubDate><guid>https://foosel.net/til/how-to-grep-a-log-for-multline-errors/</guid><description>&lt;p&gt;I just found myself in the position to have to &lt;code&gt;grep&lt;/code&gt; an OctoPrint log file for error log entries with attached Python stack traces. I wanted to not only get the starting line where the exception log output starts, but the full stack trace up until the next regular log line.&lt;/p&gt;
&lt;p&gt;The format of the lines in &lt;code&gt;octoprint.log&lt;/code&gt; is a simple &lt;code&gt;%(asctime)s - %(name)s - %(levelname)s - %(message)s&lt;/code&gt;, so a log with an error and attached exception looks like this:&lt;/p&gt;</description><content:encoded><![CDATA[<p>I just found myself in the position to have to <code>grep</code> an OctoPrint log file for error log entries with attached Python stack traces. I wanted to not only get the starting line where the exception log output starts, but the full stack trace up until the next regular log line.</p>
<p>The format of the lines in <code>octoprint.log</code> is a simple <code>%(asctime)s - %(name)s - %(levelname)s - %(message)s</code>, so a log with an error and attached exception looks like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-plain" data-lang="plain"><span style="display:flex;"><span>2023-01-30 17:50:45,704 - octoprint.events.fire - DEBUG - Firing event: Disconnecting (Payload: None)
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,704 - octoprint.events - DEBUG - Sending action to &lt;bound method PrinterStateConnection._onEvent of &lt;octoprint.server.util.sockjs.PrinterStateConnection object at 0x000001635CB6EE50&gt;&gt;
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,705 - octoprint.plugin - DEBUG - Calling on_event on action_command_notification
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,705 - octoprint.plugin - DEBUG - Calling on_event on action_command_prompt
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,705 - octoprint.plugin - DEBUG - Calling on_event on announcements
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,705 - octoprint.plugin - DEBUG - Calling on_event on file_check
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,706 - octoprint.plugin - DEBUG - Calling on_event on firmware_check
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,706 - octoprint.plugin - DEBUG - Calling on_event on pluginmanager
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,706 - octoprint.plugin - DEBUG - Calling on_event on softwareupdate
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,706 - octoprint.plugin - DEBUG - Calling on_event on tracking
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,711 - octoprint.plugin - DEBUG - Calling on_event on mqtt
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,732 - octoprint.events.fire - DEBUG - Firing event: Disconnected (Payload: None)
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,735 - octoprint.events - DEBUG - Sending action to &lt;function Server.run.&lt;locals&gt;.&lt;lambda&gt; at 0x000001635BDB2CA0&gt;
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,750 - octoprint.events - ERROR - Got an exception while sending event Disconnected (Payload: None) to &lt;function Server.run.&lt;locals&gt;.&lt;lambda&gt; at 0x000001635BDB2CA0&gt;
</span></span><span style="display:flex;"><span>Traceback (most recent call last):
</span></span><span style="display:flex;"><span>  File &#34;C:\Devel\OctoPrint\OctoPrint\src\octoprint\events.py&#34;, line 197, in _work
</span></span><span style="display:flex;"><span>    listener(event, payload)
</span></span><span style="display:flex;"><span>  File &#34;C:\Devel\OctoPrint\OctoPrint\src\octoprint\server\__init__.py&#34;, line 1212, in &lt;lambda&gt;
</span></span><span style="display:flex;"><span>    octoprint.events.Events.DISCONNECTED, lambda e, p: run_autorefresh()
</span></span><span style="display:flex;"><span>                                                       ^^^^^^^^^^^^^^^^^
</span></span><span style="display:flex;"><span>  File &#34;C:\Devel\OctoPrint\OctoPrint\src\octoprint\server\__init__.py&#34;, line 1195, in run_autorefresh
</span></span><span style="display:flex;"><span>    autorefresh.stop()
</span></span><span style="display:flex;"><span>    ^^^^^^^^^^^^^^^^
</span></span><span style="display:flex;"><span>AttributeError: &#39;RepeatedTimer&#39; object has no attribute &#39;stop&#39;
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,753 - octoprint.events - DEBUG - Sending action to &lt;bound method PrinterStateConnection._onEvent of &lt;octoprint.server.util.sockjs.PrinterStateConnection object at 0x000001635CB6EE50&gt;&gt;
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,755 - octoprint.plugin - DEBUG - Calling on_event on action_command_notification
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,756 - octoprint.server.util.sockjs - DEBUG - Socket message held back until permissions cleared, added to backlog: plugin
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,758 - octoprint.plugins.action_command_notification - INFO - Notifications cleared
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,758 - octoprint.plugin - DEBUG - Calling on_event on action_command_prompt
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,758 - octoprint.plugin - DEBUG - Calling on_event on announcements
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,759 - octoprint.plugin - DEBUG - Calling on_event on file_check
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,759 - octoprint.plugin - DEBUG - Calling on_event on firmware_check
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,759 - octoprint.server.util.sockjs - DEBUG - Socket message held back until permissions cleared, added to backlog: plugin
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,763 - octoprint.plugin - DEBUG - Calling on_event on pluginmanager
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,764 - octoprint.plugin - DEBUG - Calling on_event on softwareupdate
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,764 - octoprint.plugin - DEBUG - Calling on_event on tracking
</span></span></code></pre></div><p>What I now wanted is for <code>grep</code> to spit out just the <code>ERROR</code> line and the attached stack trace:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-plain" data-lang="plain"><span style="display:flex;"><span>2023-01-30 17:50:45,750 - octoprint.events - ERROR - Got an exception while sending event Disconnected (Payload: None) to &lt;function Server.run.&lt;locals&gt;.&lt;lambda&gt; at 0x000001635BDB2CA0&gt;
</span></span><span style="display:flex;"><span>Traceback (most recent call last):
</span></span><span style="display:flex;"><span>  File &#34;C:\Devel\OctoPrint\OctoPrint\src\octoprint\events.py&#34;, line 197, in _work
</span></span><span style="display:flex;"><span>    listener(event, payload)
</span></span><span style="display:flex;"><span>  File &#34;C:\Devel\OctoPrint\OctoPrint\src\octoprint\server\__init__.py&#34;, line 1212, in &lt;lambda&gt;
</span></span><span style="display:flex;"><span>    octoprint.events.Events.DISCONNECTED, lambda e, p: run_autorefresh()
</span></span><span style="display:flex;"><span>                                                       ^^^^^^^^^^^^^^^^^
</span></span><span style="display:flex;"><span>  File &#34;C:\Devel\OctoPrint\OctoPrint\src\octoprint\server\__init__.py&#34;, line 1195, in run_autorefresh
</span></span><span style="display:flex;"><span>    autorefresh.stop()
</span></span><span style="display:flex;"><span>    ^^^^^^^^^^^^^^^^
</span></span><span style="display:flex;"><span>AttributeError: &#39;RepeatedTimer&#39; object has no attribute &#39;stop&#39;
</span></span></code></pre></div><p>For this I needed a way to set <code>grep</code> to match multiple lines and do a (non-matching) look ahead for the end. It turns out that the secret to success here is to treat the whole input as one line, use Perl compatible regex mode, and make sure to set the multiline flag. After some fiddling around on <a href="https://regex101.com/r/qYOrnT/1">regex101.com</a> and reading up on <a href="https://perldoc.perl.org/perlre#Extended-Patterns">Perl&rsquo;s regex options</a><sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>, I came up with the following:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-plain" data-lang="plain"><span style="display:flex;"><span>grep -Pazo &#39;(?m)^\N+\- ERROR \-\N*\n(^\N*?\n)*?(?=\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3} \- )&#39; octoprint.log
</span></span></code></pre></div><p>Let&rsquo;s walk through this:</p>
<ul>
<li><code>-P</code> enables Perl compatible regex mode</li>
<li><code>-a</code> enables text mode</li>
<li><code>-z</code> turns all newlines into null bytes and thus treats the whole input as a single line for finding matches<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></li>
<li><code>-o</code> only outputs the matched part of the line (otherwise we&rsquo;d get the whole file printed out)</li>
<li><code>(?m)</code> enables multiline mode</li>
<li><code>^\N+\- ERROR \-\N*\n</code> matches the first line of the error, which is the one that starts with the timestamp and package and contains the word <code>ERROR</code></li>
<li><code>(^\N*?\n)*?</code> non-greedily matches all following lines of the error, which are anything but a newline followed by a newline</li>
<li><code>(?=\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3} \- )</code> is a positive look-ahead that matches a line starting with a timestamp again, which signifies the end of the error&rsquo;s lines</li>
</ul>
<p>Hooray, it works 🥳:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-plain" data-lang="plain"><span style="display:flex;"><span>❯ grep -Pazo &#39;(?m)^\N+\- ERROR \-\N*\n(^\N*?\n)*?(?=\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3} \- )&#39; octoprint.log
</span></span><span style="display:flex;"><span>2023-01-30 17:50:45,750 - octoprint.events - ERROR - Got an exception while sending event Disconnected (Payload: None) to &lt;function Server.run.&lt;locals&gt;.&lt;lambda&gt; at 0x000001635BDB2CA0&gt;
</span></span><span style="display:flex;"><span>Traceback (most recent call last):
</span></span><span style="display:flex;"><span>  File &#34;C:\Devel\OctoPrint\OctoPrint\src\octoprint\events.py&#34;, line 197, in _work
</span></span><span style="display:flex;"><span>    listener(event, payload)
</span></span><span style="display:flex;"><span>  File &#34;C:\Devel\OctoPrint\OctoPrint\src\octoprint\server\__init__.py&#34;, line 1212, in &lt;lambda&gt;
</span></span><span style="display:flex;"><span>    octoprint.events.Events.DISCONNECTED, lambda e, p: run_autorefresh()
</span></span><span style="display:flex;"><span>                                                       ^^^^^^^^^^^^^^^^^
</span></span><span style="display:flex;"><span>  File &#34;C:\Devel\OctoPrint\OctoPrint\src\octoprint\server\__init__.py&#34;, line 1195, in run_autorefresh
</span></span><span style="display:flex;"><span>    autorefresh.stop()
</span></span><span style="display:flex;"><span>    ^^^^^^^^^^^^^^^^
</span></span><span style="display:flex;"><span>AttributeError: &#39;RepeatedTimer&#39; object has no attribute &#39;stop&#39;
</span></span></code></pre></div><p>(And yes, I&rsquo;ve fixed the error that lead to this stack trace as well 😉)</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>I don&rsquo;t know about you, but I always forget about positive/negative look-ahead/behind and pattern-match modifiers.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>The downside of this is that now <code>-n</code> (print line number of match) will not work anymore and just happily report line 1 for every single match.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>How to run Playwright on GitHub Actions</title><link>https://foosel.net/til/how-to-run-playwright-on-github-actions/</link><pubDate>Tue, 31 Jan 2023 00:00:00 +0000</pubDate><guid>https://foosel.net/til/how-to-run-playwright-on-github-actions/</guid><description>&lt;p&gt;Running Playwright on GitHub Actions is fairly straightforward at first glance, however it becomes a bit more tricky when you don&amp;rsquo;t want to download the whole browser binary zoo on every single CI build.&lt;/p&gt;
&lt;p&gt;Looking around a bit on how to go about caching these, I came across various approaches listed in &lt;a href="https://github.com/microsoft/playwright/issues/7249"&gt;this GitHub issue on the Playwright repo&lt;/a&gt;. Below is the result of reading through most of them and figuring out what works best for me and my use case (OctoPrint&amp;rsquo;s E2E tests, &lt;code&gt;npm&lt;/code&gt; based test project).&lt;/p&gt;</description><content:encoded><![CDATA[<p>Running Playwright on GitHub Actions is fairly straightforward at first glance, however it becomes a bit more tricky when you don&rsquo;t want to download the whole browser binary zoo on every single CI build.</p>
<p>Looking around a bit on how to go about caching these, I came across various approaches listed in <a href="https://github.com/microsoft/playwright/issues/7249">this GitHub issue on the Playwright repo</a>. Below is the result of reading through most of them and figuring out what works best for me and my use case (OctoPrint&rsquo;s E2E tests, <code>npm</code> based test project).</p>
<p>These steps make sure to install Playwright, fetching the browser binaries from cache if possible, in any case installing the OS depencies, running the tests (replace <code>./path/to/tests</code> accordingly for your setup) and finally upload the generated report as artifact, regardless of whether the tests succeeded or not:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#75715e"># Run npm ci and get Playwright version</span>
</span></span><span style="display:flex;"><span>- <span style="color:#f92672">name</span>: <span style="color:#ae81ff">🏗 Prepare Playwright env</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">working-directory</span>: <span style="color:#ae81ff">./path/to/tests</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">run</span>: |<span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    npm ci
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    PLAYWRIGHT_VERSION=$(npm ls --json @playwright/test | jq --raw-output &#39;.dependencies[&#34;@playwright/test&#34;].version&#39;)
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    echo &#34;PLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION&#34; &gt;&gt; $GITHUB_ENV</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Cache browser binaries, cache key is based on Playwright version and OS</span>
</span></span><span style="display:flex;"><span>- <span style="color:#f92672">name</span>: <span style="color:#ae81ff">🧰 Cache Playwright browser binaries</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">actions/cache@v3</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">id</span>: <span style="color:#ae81ff">playwright-cache</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">with</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">path</span>: <span style="color:#e6db74">&#34;~/.cache/ms-playwright&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">key</span>: <span style="color:#e6db74">&#34;${{ runner.os }}-playwright-${{ env.PLAYWRIGHT_VERSION }}&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">restore-keys</span>: |<span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">      ${{ runner.os }}-playwright-</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Install browser binaries &amp; OS dependencies if cache missed</span>
</span></span><span style="display:flex;"><span>- <span style="color:#f92672">name</span>: <span style="color:#ae81ff">🏗 Install Playwright browser binaries &amp; OS dependencies</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">if</span>: <span style="color:#ae81ff">steps.playwright-cache.outputs.cache-hit != &#39;true&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">working-directory</span>: <span style="color:#ae81ff">./path/to/tests</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">run</span>: |<span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    npx playwright install --with-deps</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Install only the OS dependencies if cache hit</span>
</span></span><span style="display:flex;"><span>- <span style="color:#f92672">name</span>: <span style="color:#ae81ff">🏗 Install Playwright OS dependencies</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">if</span>: <span style="color:#ae81ff">steps.playwright-cache.outputs.cache-hit == &#39;true&#39;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">working-directory</span>: <span style="color:#ae81ff">./path/to/tests</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">run</span>: |<span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    npx playwright install-deps</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>- <span style="color:#f92672">name</span>: <span style="color:#ae81ff">🚀 Run Playwright</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">working-directory</span>: <span style="color:#ae81ff">./path/to/tests</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">run</span>: |<span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    npx playwright test</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>- <span style="color:#f92672">name</span>: <span style="color:#ae81ff">⬆ Upload report</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">actions/upload-artifact@v3</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">if</span>: <span style="color:#ae81ff">always()</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">with</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">name</span>: <span style="color:#ae81ff">playwright-report</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">path</span>: <span style="color:#ae81ff">tests/playwright/playwright-report</span>
</span></span></code></pre></div>]]></content:encoded></item><item><title>How to add JSON Feed support to Hugo</title><link>https://foosel.net/til/how-to-add-json-feed-support-to-hugo/</link><pubDate>Sun, 29 Jan 2023 00:00:00 +0000</pubDate><guid>https://foosel.net/til/how-to-add-json-feed-support-to-hugo/</guid><description>&lt;p&gt;In order to add &lt;a href="https://www.jsonfeed.org/"&gt;JSON Feed 1.1 support&lt;/a&gt; to &lt;a href="https://gohugo.io"&gt;Hugo&lt;/a&gt; you need to first add a new &lt;code&gt;jsonfeed&lt;/code&gt; output format in &lt;code&gt;config.yaml&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;mediaTypes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;application/feed+json&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;suffixes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;json&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;outputFormats&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;jsonfeed&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;mediaType&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;application/feed+json&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;baseName&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;feed&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;rel&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;alternate&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;isPlainText&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This adds a new media type &lt;code&gt;application/feed+json&lt;/code&gt; with the extension &lt;code&gt;json&lt;/code&gt; and creates a new output format &lt;code&gt;jsonfeed&lt;/code&gt; rendering into that media type with a base name of &lt;code&gt;feed&lt;/code&gt; (so &lt;code&gt;feed.json&lt;/code&gt; as &lt;a href="https://www.jsonfeed.org/version/1.1/#discovery"&gt;recommended by the JSON Feed spec&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;This then needs to be added to the outputs it should be generated for - on this page I&amp;rsquo;ve only added it to &lt;code&gt;section&lt;/code&gt;s. Again, in &lt;code&gt;config.yaml&lt;/code&gt;:&lt;/p&gt;</description><content:encoded><![CDATA[<p>In order to add <a href="https://www.jsonfeed.org/">JSON Feed 1.1 support</a> to <a href="https://gohugo.io">Hugo</a> you need to first add a new <code>jsonfeed</code> output format in <code>config.yaml</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">mediaTypes</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">application/feed+json</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">suffixes</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#ae81ff">json</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">outputFormats</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">jsonfeed</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">mediaType</span>: <span style="color:#ae81ff">application/feed+json</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">baseName</span>: <span style="color:#ae81ff">feed</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">rel</span>: <span style="color:#ae81ff">alternate</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">isPlainText</span>: <span style="color:#66d9ef">true</span>
</span></span></code></pre></div><p>This adds a new media type <code>application/feed+json</code> with the extension <code>json</code> and creates a new output format <code>jsonfeed</code> rendering into that media type with a base name of <code>feed</code> (so <code>feed.json</code> as <a href="https://www.jsonfeed.org/version/1.1/#discovery">recommended by the JSON Feed spec</a>).</p>
<p>This then needs to be added to the outputs it should be generated for - on this page I&rsquo;ve only added it to <code>section</code>s. Again, in <code>config.yaml</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">outputs</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">home</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#ae81ff">HTML</span>
</span></span><span style="display:flex;"><span>    - <span style="color:#ae81ff">JSON</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">section</span>:
</span></span><span style="display:flex;"><span>    - <span style="color:#ae81ff">HTML</span>
</span></span><span style="display:flex;"><span>    - <span style="color:#ae81ff">RSS</span>
</span></span><span style="display:flex;"><span>    - <span style="color:#ae81ff">jsonfeed</span>
</span></span></code></pre></div><p>Finally, a template needs to be created so that Hugo can actually render something. I&rsquo;ve put this into <code>layouts/_default/list.jsonfeed.json</code> (following the expected naming scheme of <code>list.&lt;outputFormat&gt;.&lt;extension&gt;</code>):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go-text-template" data-lang="go-text-template"><span style="display:flex;"><span><span style="color:#75715e">{{-</span> <span style="color:#a6e22e">$pctx</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">.</span> <span style="color:#75715e">-}}</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">{{-</span> <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">.IsHome</span> <span style="color:#75715e">-}}{{</span> <span style="color:#a6e22e">$pctx</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">site</span> <span style="color:#75715e">}}{{-</span> <span style="color:#66d9ef">end</span> <span style="color:#75715e">-}}</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">{{-</span> <span style="color:#a6e22e">$pages</span> <span style="color:#f92672">:=</span> <span style="color:#66d9ef">slice</span> <span style="color:#75715e">-}}</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">{{-</span> <span style="color:#66d9ef">if</span> <span style="color:#66d9ef">or</span> <span style="color:#a6e22e">$.IsHome</span> <span style="color:#a6e22e">$.IsSection</span> <span style="color:#75715e">-}}</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">{{-</span> <span style="color:#a6e22e">$pages</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">$pctx</span><span style="color:#a6e22e">.RegularPages</span> <span style="color:#75715e">-}}</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">{{-</span> <span style="color:#66d9ef">else</span> <span style="color:#75715e">-}}</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">{{-</span> <span style="color:#a6e22e">$pages</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">$pctx</span><span style="color:#a6e22e">.Pages</span> <span style="color:#75715e">-}}</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">{{-</span> <span style="color:#66d9ef">end</span> <span style="color:#75715e">-}}</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">{{-</span> <span style="color:#a6e22e">$limit</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">site</span><span style="color:#a6e22e">.Config.Services.RSS.Limit</span> <span style="color:#75715e">-}}</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">{{-</span> <span style="color:#66d9ef">if</span> <span style="color:#66d9ef">ge</span> <span style="color:#a6e22e">$limit</span> <span style="color:#a6e22e">1</span> <span style="color:#75715e">-}}</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">{{-</span> <span style="color:#a6e22e">$pages</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">$pages</span> <span style="color:#f92672">|</span> <span style="color:#a6e22e">first</span> <span style="color:#a6e22e">$limit</span> <span style="color:#75715e">-}}</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">{{-</span> <span style="color:#66d9ef">end</span> <span style="color:#75715e">-}}</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">{{-</span> <span style="color:#a6e22e">$title</span> <span style="color:#f92672">:=</span> <span style="color:#e6db74">&#34;&#34;</span> <span style="color:#75715e">}}</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">{{-</span> <span style="color:#66d9ef">if</span> <span style="color:#66d9ef">eq</span> <span style="color:#a6e22e">.Title</span> <span style="color:#a6e22e">.Site.Title</span> <span style="color:#75715e">}}</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">{{-</span> <span style="color:#a6e22e">$title</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">.Site.Title</span> <span style="color:#75715e">}}</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">{{-</span> <span style="color:#66d9ef">else</span> <span style="color:#75715e">}}</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">{{-</span> <span style="color:#66d9ef">with</span> <span style="color:#a6e22e">.Title</span> <span style="color:#75715e">}}</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">{{-</span> <span style="color:#a6e22e">$title</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">print</span> <span style="color:#a6e22e">.</span> <span style="color:#e6db74">&#34; on &#34;</span><span style="color:#75715e">}}</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">{{-</span> <span style="color:#66d9ef">end</span> <span style="color:#75715e">}}</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">{{-</span> <span style="color:#a6e22e">$title</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">print</span> <span style="color:#a6e22e">$title</span> <span style="color:#a6e22e">.Site.Title</span> <span style="color:#75715e">}}</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">{{-</span> <span style="color:#66d9ef">end</span> <span style="color:#75715e">}}</span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    &#34;version&#34;: &#34;https://jsonfeed.org/version/1.1&#34;,
</span></span><span style="display:flex;"><span>    &#34;title&#34;: <span style="color:#75715e">{{</span> <span style="color:#a6e22e">$title</span> <span style="color:#f92672">|</span> <span style="color:#a6e22e">jsonify</span> <span style="color:#75715e">}}</span>,
</span></span><span style="display:flex;"><span>    &#34;home_page_url&#34;: <span style="color:#75715e">{{</span> <span style="color:#a6e22e">.Permalink</span> <span style="color:#f92672">|</span> <span style="color:#a6e22e">jsonify</span> <span style="color:#75715e">}}</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">{{-</span> <span style="color:#66d9ef">with</span>  <span style="color:#a6e22e">.OutputFormats.Get</span> <span style="color:#e6db74">&#34;jsonfeed&#34;</span> <span style="color:#75715e">}}</span>
</span></span><span style="display:flex;"><span>    &#34;feed_url&#34;: <span style="color:#75715e">{{</span> <span style="color:#a6e22e">.Permalink</span> <span style="color:#f92672">|</span> <span style="color:#a6e22e">jsonify</span>  <span style="color:#75715e">}}</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">{{-</span> <span style="color:#66d9ef">end</span> <span style="color:#75715e">}}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">{{-</span> <span style="color:#66d9ef">if</span> <span style="color:#f92672">(</span><span style="color:#66d9ef">or</span> <span style="color:#a6e22e">.Site.Params.author</span> <span style="color:#a6e22e">.Site.Params.author_url</span><span style="color:#f92672">)</span> <span style="color:#75715e">}}</span>
</span></span><span style="display:flex;"><span>    &#34;authors&#34;: [{
</span></span><span style="display:flex;"><span>      <span style="color:#75715e">{{-</span> <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">.Site.Params.author</span> <span style="color:#75715e">}}</span>
</span></span><span style="display:flex;"><span>        &#34;name&#34;: <span style="color:#75715e">{{</span> <span style="color:#a6e22e">.Site.Params.author</span> <span style="color:#f92672">|</span> <span style="color:#a6e22e">jsonify</span> <span style="color:#75715e">}}</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#75715e">{{-</span> <span style="color:#66d9ef">end</span> <span style="color:#75715e">}}</span>
</span></span><span style="display:flex;"><span>      <span style="color:#75715e">{{-</span> <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">.Site.Params.author_url</span> <span style="color:#75715e">}}</span>
</span></span><span style="display:flex;"><span>        &#34;url&#34;: <span style="color:#75715e">{{</span> <span style="color:#a6e22e">.Site.Params.author_url</span> <span style="color:#f92672">|</span> <span style="color:#a6e22e">jsonify</span> <span style="color:#75715e">}}</span>
</span></span><span style="display:flex;"><span>      <span style="color:#75715e">{{-</span> <span style="color:#66d9ef">end</span> <span style="color:#75715e">}}</span>
</span></span><span style="display:flex;"><span>    }],
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">{{-</span> <span style="color:#66d9ef">end</span> <span style="color:#75715e">}}</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">{{-</span> <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">$pages</span> <span style="color:#75715e">}}</span>
</span></span><span style="display:flex;"><span>    &#34;items&#34;: [
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">{{-</span> <span style="color:#66d9ef">range</span> <span style="color:#a6e22e">$index</span><span style="color:#f92672">,</span> <span style="color:#a6e22e">$element</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">$pages</span> <span style="color:#75715e">}}</span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">{{-</span> <span style="color:#66d9ef">with</span> <span style="color:#a6e22e">$element</span> <span style="color:#75715e">}}</span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">{{-</span> <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">$index</span> <span style="color:#75715e">}}</span>,<span style="color:#75715e">{{</span><span style="color:#66d9ef">end</span><span style="color:#75715e">}}</span> {
</span></span><span style="display:flex;"><span>            &#34;title&#34;: <span style="color:#75715e">{{</span> <span style="color:#a6e22e">.Title</span> <span style="color:#f92672">|</span> <span style="color:#a6e22e">jsonify</span> <span style="color:#75715e">}}</span>,
</span></span><span style="display:flex;"><span>            &#34;id&#34;: <span style="color:#75715e">{{</span> <span style="color:#a6e22e">.Permalink</span> <span style="color:#f92672">|</span> <span style="color:#a6e22e">jsonify</span> <span style="color:#75715e">}}</span>,
</span></span><span style="display:flex;"><span>            &#34;url&#34;: <span style="color:#75715e">{{</span> <span style="color:#a6e22e">.Permalink</span> <span style="color:#f92672">|</span> <span style="color:#a6e22e">jsonify</span> <span style="color:#75715e">}}</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#75715e">{{-</span> <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">.Site.Params.showFullTextinJSONFeed</span> <span style="color:#75715e">}}</span>
</span></span><span style="display:flex;"><span>            &#34;summary&#34;: <span style="color:#75715e">{{</span> <span style="color:#66d9ef">with</span> <span style="color:#a6e22e">.Description</span> <span style="color:#75715e">}}{{</span> <span style="color:#a6e22e">.</span> <span style="color:#f92672">|</span> <span style="color:#a6e22e">jsonify</span> <span style="color:#75715e">}}{{</span> <span style="color:#66d9ef">else</span> <span style="color:#75715e">}}{{</span> <span style="color:#a6e22e">.Summary</span> <span style="color:#f92672">|</span> <span style="color:#a6e22e">jsonify</span> <span style="color:#75715e">}}{{</span> <span style="color:#66d9ef">end</span> <span style="color:#75715e">-}}</span>,
</span></span><span style="display:flex;"><span>            &#34;content_html&#34;: <span style="color:#75715e">{{</span> <span style="color:#a6e22e">.Content</span> <span style="color:#f92672">|</span> <span style="color:#a6e22e">jsonify</span> <span style="color:#75715e">}}</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#75715e">{{-</span> <span style="color:#66d9ef">else</span> <span style="color:#75715e">}}</span>
</span></span><span style="display:flex;"><span>            &#34;content_text&#34;: <span style="color:#75715e">{{</span> <span style="color:#66d9ef">with</span> <span style="color:#a6e22e">.Description</span> <span style="color:#75715e">}}{{</span> <span style="color:#a6e22e">.</span> <span style="color:#f92672">|</span> <span style="color:#a6e22e">jsonify</span> <span style="color:#75715e">}}{{</span> <span style="color:#66d9ef">else</span> <span style="color:#75715e">}}{{</span> <span style="color:#a6e22e">.Summary</span> <span style="color:#f92672">|</span> <span style="color:#a6e22e">jsonify</span> <span style="color:#75715e">}}{{</span> <span style="color:#66d9ef">end</span> <span style="color:#75715e">-}}</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#75715e">{{-</span> <span style="color:#66d9ef">end</span> <span style="color:#75715e">}}</span>
</span></span><span style="display:flex;"><span>            <span style="color:#75715e">{{-</span> <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">.Params.cover.image</span> <span style="color:#75715e">}}</span>
</span></span><span style="display:flex;"><span>            <span style="color:#75715e">{{-</span> <span style="color:#a6e22e">$cover</span> <span style="color:#f92672">:=</span> <span style="color:#f92672">(</span><span style="color:#a6e22e">.Resources.ByType</span> <span style="color:#e6db74">&#34;image&#34;</span><span style="color:#f92672">)</span><span style="color:#a6e22e">.GetMatch</span> <span style="color:#f92672">(</span><span style="color:#66d9ef">printf</span> <span style="color:#e6db74">&#34;*%s*&#34;</span> <span style="color:#f92672">(</span><span style="color:#a6e22e">.Params.cover.image</span><span style="color:#f92672">))</span> <span style="color:#75715e">}}</span>
</span></span><span style="display:flex;"><span>            <span style="color:#75715e">{{-</span> <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">$cover</span> <span style="color:#75715e">}}</span>
</span></span><span style="display:flex;"><span>            &#34;image&#34;: <span style="color:#75715e">{{</span> <span style="color:#f92672">(</span><span style="color:#a6e22e">path</span><span style="color:#a6e22e">.Join</span> <span style="color:#a6e22e">.RelPermalink</span> <span style="color:#a6e22e">$cover</span><span style="color:#f92672">)</span> <span style="color:#f92672">|</span> <span style="color:#a6e22e">absURL</span> <span style="color:#f92672">|</span> <span style="color:#a6e22e">jsonify</span> <span style="color:#75715e">}}</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#75715e">{{-</span> <span style="color:#66d9ef">end</span> <span style="color:#75715e">}}</span>
</span></span><span style="display:flex;"><span>            <span style="color:#75715e">{{-</span> <span style="color:#66d9ef">end</span> <span style="color:#75715e">}}</span>
</span></span><span style="display:flex;"><span>            &#34;date_published&#34;: <span style="color:#75715e">{{</span> <span style="color:#a6e22e">.Date.Format</span> <span style="color:#e6db74">&#34;2006-01-02T15:04:05Z07:00&#34;</span> <span style="color:#f92672">|</span> <span style="color:#a6e22e">jsonify</span> <span style="color:#75715e">}}</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">{{-</span> <span style="color:#66d9ef">end</span> <span style="color:#75715e">}}</span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">{{-</span> <span style="color:#66d9ef">end</span> <span style="color:#75715e">}}</span>
</span></span><span style="display:flex;"><span>    ]
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">{{</span> <span style="color:#66d9ef">end</span> <span style="color:#75715e">}}</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>By default, this generates a feed with summaries only. If you want a full content feed, set <code>params.showFullTextinJSONFeed</code> to <code>true</code> in <code>config.yaml</code>.</p>
<p>The relevant docs for custom media types, output formats and template locations can be found <a href="https://gohugo.io/templates/output-formats/">here</a>.</p>
<p>On <a href="https://github.com/adityatelange/hugo-PaperMod">the Papermod theme</a> the above will automatically cause something like</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-html" data-lang="html"><span style="display:flex;"><span>&lt;<span style="color:#f92672">link</span> <span style="color:#a6e22e">rel</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;alternate&#34;</span> <span style="color:#a6e22e">type</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;application/feed+json&#34;</span> <span style="color:#a6e22e">href</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;https://foosel.net/til/feed.json&#34;</span>&gt;
</span></span></code></pre></div><p>to be added to the <code>head</code> of the page, as needed for <a href="https://www.jsonfeed.org/version/1.1/#discovery">discovery</a>. In other themes you might have to do it yourself.</p>
<p>The result of all of this is something like <a href="https://foosel.net/til/feed.json">this</a>.</p>
]]></content:encoded></item><item><title>How to view the page source on Firefox and Chrome mobile</title><link>https://foosel.net/til/how-to-view-the-page-source-on-firefox-and-chrome-mobile/</link><pubDate>Sat, 28 Jan 2023 00:00:00 +0000</pubDate><guid>https://foosel.net/til/how-to-view-the-page-source-on-firefox-and-chrome-mobile/</guid><description>&lt;p&gt;Viewing the page source on Firefox and Chrome mobile is as easy as prepending &lt;code&gt;view-source:&lt;/code&gt; to the URL.&lt;/p&gt;
&lt;p&gt;Example: &lt;code&gt;https://foosel.net&lt;/code&gt; becomes &lt;code&gt;view-source:https://foosel.net&lt;/code&gt;.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Viewing the page source on Firefox and Chrome mobile is as easy as prepending <code>view-source:</code> to the URL.</p>
<p>Example: <code>https://foosel.net</code> becomes <code>view-source:https://foosel.net</code>.</p>
]]></content:encoded></item><item><title>Review: Onyx Boox Nova 3 Color</title><link>https://foosel.net/blog/2023-01-27-review-onyx-boox-nova-3-color/</link><pubDate>Fri, 27 Jan 2023 00:00:00 +0000</pubDate><guid>https://foosel.net/blog/2023-01-27-review-onyx-boox-nova-3-color/</guid><description>A long overdue e-reader review, after 1.5 years of use</description><content:encoded><![CDATA[<p>I&rsquo;ve always been a bookworm. As a child, my favourite past time was reading. I usually dragged my current book with me <em>everywhere</em> and would read at any opportunity<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>. So, when e-readers became a thing, I was very excited to save on a ton of shelf space in the future and got myself an Amazon Kindle at the first chance I got. Back then in 2010 I actually had to import my Kindle 3 Keyboard from the US since it wasn&rsquo;t available in Germany yet. And since then I&rsquo;ve gone through a couple of e-readers, all of them Kindles. But all the time I was a bit annoyed at two things:</p>
<ol>
<li>Vendor lock-in. Yes, you <em>can</em> send books from other sources than Amazon itself to your Kindle, can convert PDF and EPUB and whatnot, but in my experience it was always a bit of a hassle and especially in the case of PDFs a mess. Even with <a href="https://calibre-ebook.com/">Calibre</a> in my toolbelt I didn&rsquo;t feel all too happy and most of the books I got through <a href="https://www.humblebundle.com/">Humble Bundle</a> and the like - which tend to be EPUB or PDF - stayed unread in my collection while I kept throwing money at Amazon.</li>
<li>Black and white only. That&rsquo;s still an issue with pretty much all e-readers out there, as color displays are only now slowly becoming a thing. But the lack of color means that things that very much profit from color, like coding related books (syntax highlighting),  comics, RPG source books, etc. so far were something I had to read on my phone or tablet.</li>
</ol>
<p>Then I came across <a href="https://www.hanselman.com/blog/the-quiet-rise-of-e-ink-tablets-and-infinite-paper-note-takers-remarkable-2-vs-onyx-boox-note-air">Scott Hanselman&rsquo;s post &ldquo;The quiet rise of E Ink Tablets and Infinite Paper Note Takers - reMarkable 2 vs Onyx Boox Note Air
&ldquo;</a> back in June of 2021, and immediately was intrigued. I had heard of the Remarkable 2 before,
but decided against it due to yet another case of vendor lock-in. But through Scott&rsquo;s post I learned about Onyx&rsquo; e-reader line-up. The fact that they run Android, have Google Play store integration and you can just install your own stuff on them out of the box was very much intriguing, and the Wacom digitizer was an additional point for that model range<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>. And after perusing the Onyx website for a while, I decided to pull the trigger on a
<a href="https://onyxboox.com/boox_nova3color">Nova 3 Color</a> - the color e-ink intrigued me and the 7.8&rdquo; size was right on the sweet spot. I ordered it on June 14th 2021 directly from the Boox EU store, and it arrived on June 16th<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>. All in all I paid 399€ for a bundle of the e-reader itself, a case, some screen protectors (that I don&rsquo;t use) and of course a stylus. Not too shabby.</p>
<p>So now, 1.5 years later, how do I like it? Let&rsquo;s find out!</p>
<h2 id="visuals">Visuals</h2>
<p>To just address the elephant in the room right away: How does the color screen perform? Well, it&rsquo;s obviously not something you can compare to a tablet OLED. The colors are way more muted and the contrast is not as high. If you&rsquo;ve ever seen a comic printed on newspaper paper, that&rsquo;s pretty much the same kind of color reproduction you can expect here. But for what I got it for, it&rsquo;s absolutely fine.</p>
<p>Syntax highlighting in code snippets is now visible beyond grayscale, and for comics and similar it also works. Given, if I want to really enjoy the colors, I&rsquo;m still getting a hold of my tablet, but for the use case of actually reading long form books with the occasional code snippet, illustration, map or even short comic strip, it&rsquo;s absolutely fine. And for highlighting text, it&rsquo;s also a great feature to have.</p>
<p>To give you an idea here are a couple of pictures of the screen, each with a shot of how the same thing looks like on a regular screen added to the right<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup>. First we have a code snippet in the PDF version of &ldquo;Fluent Python 2nd edition&rdquo;:</p>
<p><img alt="Side by side comparison of a page from a coding book on the Nova and a screenshot of the PDF." loading="lazy" src="/blog/2023-01-27-review-onyx-boox-nova-3-color/code_comparison.jpg"></p>
<p><img alt="Detail view of the coding book on the Nova." loading="lazy" src="/blog/2023-01-27-review-onyx-boox-nova-3-color/code_detail.jpg"></p>
<p>Then a page from the &ldquo;Attack on Titan Anthology&rdquo;:</p>
<p><img alt="Side by side comparison of a page from a comic on the Nova and a screenshot of the CBZ." loading="lazy" src="/blog/2023-01-27-review-onyx-boox-nova-3-color/comic_comparison.jpg"></p>
<p><img alt="Detail view of the comic on the Nova." loading="lazy" src="/blog/2023-01-27-review-onyx-boox-nova-3-color/comic_detail.jpg"></p>
<p>The third one is a page from the &ldquo;Tales from the Loop&rdquo; RPG source book:</p>
<p><img alt="Side by side comparison of a page from an RPG book on the Nova and a screenshot of the PDF." loading="lazy" src="/blog/2023-01-27-review-onyx-boox-nova-3-color/rpg_comparison.jpg"></p>
<p><img alt="Detail view of the RPG book on the Nova." loading="lazy" src="/blog/2023-01-27-review-onyx-boox-nova-3-color/rpg_detail.jpg"></p>
<p>And the final one shows a page from the rules PDF of the boardgame &ldquo;Nemesis&rdquo;:</p>
<p><img alt="Side by side comparison of a page from boardgame rules on the Nova and a screenshot of the PDF." loading="lazy" src="/blog/2023-01-27-review-onyx-boox-nova-3-color/boardgame_comparison.jpg"></p>
<p><img alt="Detail view of the boardgame rules on the Nova." loading="lazy" src="/blog/2023-01-27-review-onyx-boox-nova-3-color/boardgame_detail.jpg"></p>
<p>So, if you expect vibrant colors like on OLED from a color e-ink, I have to disappoint you. At least with the current technology we aren&rsquo;t there yet. But it does add a needed touch of color to books like the above examples that I sorely missed before, and I think I wouldn&rsquo;t want to go back to a black-and-white only e-ink display at this point anymore.</p>
<p>Now, what makes the display a bit more hit and miss when it comes to things like the shown boardgame rules is its size. If this was a 10&rdquo; screen, the rules PDF would be far easier on the eyes. As things are, the pixels wash a bit into each other. But truth be told, that particular PDF is something that even on my 10&quot; tablet I only view zoomed in, and I have some other examples like this - mostly A4 sized RPG books - that simply become too small on the 7.8&quot; display here.</p>
<p>When reading e-reader optimized formats like EPUB or MOBI, or in the Amazon Kindle app, and also with PDFs that are more suited for the form factor, the text is great to read. No eye strain, no blurry text. There is however always a bit of a grainy texture to the screen, and that is due to the lower resolution color layer that&rsquo;s on top of the grayscale one. It&rsquo;s not something I notice while reading however. Overall, I&rsquo;m totally happy with the text quality!</p>
<p><strong>Update:</strong> <a href="https://mastodon.social/@Mihir/109761667962680477">Mihir Joshi asked me on Mastodon how fast  screen refreshes perform on the Nova</a>, and I made a quick video to demonstrate:</p>
<video controls preload="auto" width="100%"  playsinline class="html-video">
    <source src="/blog/2023-01-27-review-onyx-boox-nova-3-color/page_change_video.mp4" type="video/mp4">
  <span></span>
</video>
<p>Imho, the performance is on par with a classic e-ink reader, it certainly doesn&rsquo;t feel slower than my Kindle did.</p>
<h2 id="touch--feel">Touch &amp; feel</h2>
<p>So, we&rsquo;ve covered how it looks, but how does it feel?</p>
<p>The reader&rsquo;s shell is all plastic, but it feels solid. No sharp edges, all just nice to the touch. It certainly feels cheaper than my tablet or phone, but honestly about the same as my various Kindles. The case that came with it snaps to it and has a magnetic cover, and the reader wakes up when I open it - just like I&rsquo;m used to from my Kindles and the tablet. The stylus fits into a small loop on the cover flap; it feels a bit on the cheap side, but it works just fine and is pressure sentitive as expected.</p>
<p>The reader only has two hardware buttons, a power button on the top edge and a button centered beneath the display that acts as back button and backlight toggle (on long press). Everything else is done through configurable gestures and a little hover menu. The top edge has two drag zones, left opens the Android notification area, right the reader&rsquo;s quick settings menu.</p>
<p>The left and right side can be configured as touch sliders, and I&rsquo;ve set mine to have brightness control on the left and nothing on the right.</p>
<p>The bottom edge supports three zones for dragging upwards from as gestures, and I had them mapped to the standard Android buttons back, home, recent.</p>
<p><img alt="Configuration menu for the gestures. Upper half shows config for the gestures that swipe up from the bottom, left, center and right of the screen. Lower half shows config for the side scroll areas." loading="lazy" src="/blog/2023-01-27-review-onyx-boox-nova-3-color/gesture_config.png"></p>
<p>However, while writing this post and verifying some things, I noticed the latest firmware update added an optional dedicated navigation bar at the bottom, so I&rsquo;m going to test this out now for a while 😁</p>
<p>What about page turning? Well, there&rsquo;s nothing predefined here - it&rsquo;s app specific. But I&rsquo;ve found that everything I&rsquo;ve thrown at it in terms of reader software either supported &ldquo;tap to turn&rdquo; out of the box, or could be configured this way. You probably won&rsquo;t get happy with an app that demands &ldquo;drag to turn&rdquo; - with the refresh rate of an e-ink screen that is just too laggy.</p>
<h2 id="software">Software</h2>
<p>The fact that the Nova 3 Color runs Android is a huge plus for me. I&rsquo;ve been using Android for years now (funnily enough as long as I&rsquo;ve been an e-reader owner: 2010), and therefore I felt right at home when it came to customizing my Nova.</p>
<p><img alt="Homescreen with installed apps. From left to right and top to bottom: A folder called &ldquo;Stock&rdquo; with 14 items, Audible, Drive, DriveSync, Ebook Reader, Firefox, Gumroad, Kindle, NetGuard, Play Store, Raindrop, ReadEra Premium and Solid Explorer." loading="lazy" src="/blog/2023-01-27-review-onyx-boox-nova-3-color/apps.png"></p>
<p>Now, the first thing I actually installed after <a href="https://help.boox.com/hc/en-us/articles/8569260963732-Google-Play-Store">enabling the Play Store</a> was <a href="https://play.google.com/store/apps/details?id=eu.faircode.netguard">NetGuard</a>. I had read online that Onyx devices tend to phone home to China a lot, and that is not something I like very much. So, I threw NetGuard on there, denied everything by default, and from then on only allowed the additional stuff I put on there through to the internet (and occasionally the whole device when I let it check for updates). Rooting it would probably have been even better, but frankly after some network traffic sniffing it looked like NetGuard was enough.</p>
<p>The second thing I installed was the official Kindle app, as that is where I have most of my fiction and also the one or other non fiction books. It&rsquo;s probably what I&rsquo;ve used the most on the device, and it works great. Until a recent update it sometimes crashed and I had to restart it (sometimes twice or thrice before it worked) and scroll to the last page I was on, but with the last OS update I installed, 2022-11-24, this has so far not happened again. In any case it wasn&rsquo;t something that happened on a daily basis, more like once or twice per month, and while definitely annoying never became bad enough to make me start debugging this.</p>
<p>The third app was <a href="https://play.google.com/store/apps/details?id=org.readera.premium">ReadEra Premium</a>. I tested many PDF/EPUB readers throughout the years, and currently that is the one that works best for me. On top of PDF and EPUB it also supports CBR/CBZ, which is commonly used for comics, and a whole bunch of other formats. On the Nova, I had to fiddle around a bit with the settings to enable page switch by tap, optimize the display and such, but I sadly cannot remember what exactly I did. In any case, it works well with almost anything I throw at it now (large format PDFs being hit and miss).</p>
<p>I have my book collection stored in my Google Drive, and so I installed DriveSync to be able to keep it in sync with that and mirrored on the device. So far, every time that I&rsquo;ve opened up the Nova to check out a new book I had just pushed into Drive earlier, it was usually already there, so that&rsquo;s a nice workflow.</p>
<p>In theory there&rsquo;s also a full featured e-reader app integrated and I briefly played around with it, but I was already used to ReadEra from my tablet and so stuck with it.</p>
<p>So far, pretty much everything that I threw at it worked, albeit with some visual glitches here and there (most apps really don&rsquo;t expect to be run on an e-ink display).</p>
<p>There are also a bunch of stock apps installed on the reader, apart from stock options for e-reading and note taking that have their own buttons in the navbar on the left.</p>
<p><img alt="View of the Stock folder, 14 items. From left to right and top to bottom: App Store, Calculator, Calendar Memo, Clock, Dictionary, Email, Gallery, Music, Navigation Ball, NeoBrowser, PushRead, Recorder, Screensaver, BooxDrop and a button to add more." loading="lazy" src="/blog/2023-01-27-review-onyx-boox-nova-3-color/stock_apps.png"></p>
<p>Everything else is just pretty much the usual suspects, with two exceptions. The navball app that allows you to configure the menu behind the little button always hovering on your display (I have things in there like taking a screenshot, triggering a manual refresh and similar), and the screensaver app that allows you to configure the cover displayed whole the reader is sleeping. I&rsquo;ve put a little fake book cover on there that always makes me laugh 😅</p>
<p>But apart from these two, the only stock app I actually <em>did</em> use for a while was the app for note taking.</p>
<h2 id="note-taking">Note taking</h2>
<p>The original idea when I got this device to use it as both my e-reader and my notebook. And that&rsquo;s what I tried for the first week or so. But there were two reasons why I stopped doing that and went back to first paper and now my tablet again.</p>
<p>One was that I could only use the stock app for note taking. While every other app did in fact get the stylus input, anything but the (probably heavily optimized) stock note taking app lagged severely, to the point of being not only frustrating but actually impossible to use. And I didn&rsquo;t find any way to sync my notes with any standardized format without Onyx&rsquo; own cloud (and I&rsquo;m unclear what would have been possible there) - I simply don&rsquo;t want to require yet another account on someone else&rsquo;s server farm for that, I have enough of these already. Also, the app felt very basic functionality wise.</p>
<p>The other issue was the display size - perfect for reading, definitely too small for my handwriting in combination with a Wacom digitiser. I had thought that it would be fine for me since I also used to be quite happy with A5 sized Moleskine notebooks, but I seem to write slightly bigger with a stylus than with the Uniball Signo 307 that is my usual go to pen, and so closer to A4 like 10&quot; as found on my tablet definitely works way better.</p>
<p><img alt="Comparison of a full page note on the Nova and a full page note on the Galaxy Tab S4. There is way more space for notetaking on the larger device." loading="lazy" src="/blog/2023-01-27-review-onyx-boox-nova-3-color/note_comparison.jpg"></p>
<p><img alt="Detail view of the note on the Nova." loading="lazy" src="/blog/2023-01-27-review-onyx-boox-nova-3-color/note_detail.jpg"></p>
<p>So, I guess if note taking is your goal, go with something bigger than the Nova.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Would I buy it again? Yes, as an e-reader, but not as a note taking device.</p>
<p>For reading, it has the right size, with even most PDFs working just fine. The color display adds just the right dusting of color to make some things previously quite annoying to read on grayscale e-ink fine, and having it run stock unlocked Android means I can use whatever app I want to do the reading in. It has fully replaced the Kindle Paperwhite I used before - I frankly don&rsquo;t even know where that is at the moment 😅 - and I&rsquo;ve read many a book on it now.</p>
<p>For note taking however I found 7.8&quot; to be just too small to be useful. And the fact that only the stock app works well and doesn&rsquo;t seem to have some well supported way to get the notes out of it makes it a bit of a suboptimal experience as well.</p>
<p>If note taking was my focus, I&rsquo;d look at something larger, 10&quot;, and pay close attention to the sync options of the notebooks. Something like OCR enabled PDFs synced into the file system would already be nice (then DriveSync or Syncthing could take care of the rest). From what I&rsquo;ve read online however, something like that doesn&rsquo;t seem to really be available yet.</p>
<p>For either though I think I&rsquo;ll really want color 🌈 from now on 😁</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Just not in the car thanks to this day suffering from severe motion
sickness <em>sigh</em>. I very much envy people who can read in the car.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>My tablet - a Samsung Galaxy Tab S4 that I use for handwritten note taking - has a Wacom digitizer, as does my Wacom Intuous S writing tablet. I&rsquo;ve found the
range of third party stylus options quite amazing, and as a Lamy AL-star fan am the proud owner of both a
<a href="https://www.lamy.com/en/lamy-al-star/">Lamy AL-star EMR stylus</a> and a <a href="https://www.staedtler.com/intl/en/discover/noris-digital/">Staedtler Noris Digital Jumbo</a>.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>This even includes a short delay due to a mail exchange on June 15th - it turns out they couldn&rsquo;t handle the umlauts in my name and needed something ASCII only for the shipping label 😬&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>I adjusted the contrast slightly on the photos of the Nova&rsquo;s screen to better reflect how it looks in real life.&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>How to detect Termux in a script</title><link>https://foosel.net/til/how-to-detect-termux-in-a-script/</link><pubDate>Mon, 23 Jan 2023 00:00:00 +0000</pubDate><guid>https://foosel.net/til/how-to-detect-termux-in-a-script/</guid><description>&lt;p&gt;If you need to detect whether you are running in Termux from a bash script, check if &lt;code&gt;$PREFIX&lt;/code&gt; contains the string &lt;code&gt;com.termux&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo $PREFIX | grep -o &lt;span style="color:#e6db74"&gt;&amp;#34;com.termux&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This can also be used to set a variable in a &lt;a href="https://taskfile.dev"&gt;Taskfile&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;vars&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;TERMUX&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#39;{{and .PREFIX (contains &amp;#34;com.termux&amp;#34; .PREFIX)}}&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;a href="https://www.reddit.com/r/termux/comments/co46qw/how_to_detect_in_a_bash_script_that_im_in_termux/"&gt;Source&lt;/a&gt;&lt;/p&gt;</description><content:encoded><![CDATA[<p>If you need to detect whether you are running in Termux from a bash script, check if <code>$PREFIX</code> contains the string <code>com.termux</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>echo $PREFIX | grep -o <span style="color:#e6db74">&#34;com.termux&#34;</span>
</span></span></code></pre></div><p>This can also be used to set a variable in a <a href="https://taskfile.dev">Taskfile</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">vars</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">TERMUX</span>: <span style="color:#e6db74">&#39;{{and .PREFIX (contains &#34;com.termux&#34; .PREFIX)}}&#39;</span>
</span></span></code></pre></div><p><a href="https://www.reddit.com/r/termux/comments/co46qw/how_to_detect_in_a_bash_script_that_im_in_termux/">Source</a></p>
]]></content:encoded></item><item><title>How to open a file from Tasker in Markor</title><link>https://foosel.net/til/how-to-open-a-file-from-tasker-in-markor/</link><pubDate>Sat, 21 Jan 2023 00:00:00 +0000</pubDate><guid>https://foosel.net/til/how-to-open-a-file-from-tasker-in-markor/</guid><description>&lt;p&gt;In order to open a file from Tasker in Markor (e.g. to edit a &lt;a href="https://foosel.net/blog/2023-01-21-hugo-meet-android/"&gt;newly created blog post&lt;/a&gt;), create a &amp;ldquo;Send Intent&amp;rdquo; step with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Action: &lt;code&gt;android.intent.action.SEND&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Cat: &lt;code&gt;None&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Mime Type: &lt;code&gt;text/plain&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Data: &lt;code&gt;content://net.dinglisch.android.taskerm.fileprovider/external_files/path/to/the/file&lt;/code&gt; (be sure to replace &lt;code&gt;/path/to/the/file&lt;/code&gt; with the absolute path to the file you want to open)&lt;/li&gt;
&lt;li&gt;Package: &lt;code&gt;net.gsantner.markor&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Class: &lt;code&gt;net.gsantner.markor.activity.DocumentActivity&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="https://www.reddit.com/r/tasker/comments/xbspjr/send_intent_to_markor/"&gt;Source&lt;/a&gt;&lt;/p&gt;</description><content:encoded><![CDATA[<p>In order to open a file from Tasker in Markor (e.g. to edit a <a href="/blog/2023-01-21-hugo-meet-android/">newly created blog post</a>), create a &ldquo;Send Intent&rdquo; step with:</p>
<ul>
<li>Action: <code>android.intent.action.SEND</code></li>
<li>Cat: <code>None</code></li>
<li>Mime Type: <code>text/plain</code></li>
<li>Data: <code>content://net.dinglisch.android.taskerm.fileprovider/external_files/path/to/the/file</code> (be sure to replace <code>/path/to/the/file</code> with the absolute path to the file you want to open)</li>
<li>Package: <code>net.gsantner.markor</code></li>
<li>Class: <code>net.gsantner.markor.activity.DocumentActivity</code></li>
</ul>
<p><a href="https://www.reddit.com/r/tasker/comments/xbspjr/send_intent_to_markor/">Source</a></p>
]]></content:encoded></item><item><title>Hugo, meet Android</title><link>https://foosel.net/blog/2023-01-21-hugo-meet-android/</link><pubDate>Sat, 21 Jan 2023 00:00:00 +0000</pubDate><guid>https://foosel.net/blog/2023-01-21-hugo-meet-android/</guid><description>Setting up a mobile blogging workflow</description><content:encoded><![CDATA[<p>One thing that kept me from blogging more so far was the difficulty in working on posts on my phone. So after switching this blog over to Hugo I decided to see if I couldn&rsquo;t improve on this situation.</p>
<p>I needed a solution that would allow me to</p>
<ol>
<li>Checkout my page&rsquo;s git repository from GitHub</li>
<li>Quickly create a new post, consisting of a new folder inside <code>content/blog</code> matching my chosen <code>&lt;year&gt;-&lt;month&gt;-&lt;day&gt;-&lt;title slug&gt;/index.md</code> folder structure, based on just a title</li>
<li>Allow me to edit the post, preferably in a text editor focused on Markdown (and ideally allow me to preview the post as well)</li>
<li>Optional: Run the Hugo build on my phone as well for final checks</li>
<li>Finally, commit the new post and push it so that my GitHub Action workflow can take care of the rest</li>
</ol>
<p>I started hunting for options, and I&rsquo;m happy to report that for now I seem to have found a - quite geeky - solution that involves the use of <a href="https://termux.dev/en">Termux</a> (Linux terminal environment for Android), <a href="https://gsantner.net/project/markor.html">Markor</a> (Markdown editor) and <a href="https://tasker.joaoapps.com/">Tasker</a> (Automation tool)<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>.</p>
<p>I installed Termux, Termux:Tasker, Termux:Widget and Markor via <a href="https://f-droid.org/">F-Droid</a> - the versions available on Google Play are outdated and no longer supported. I already had Tasker installed, but I made sure to give it the additional permission to send commands to Termux. For Termux, I also made sure to allow it to draw over other apps.</p>
<p>I then fired up Termux and took care of storage access and some packages first:</p>
<pre tabindex="0"><code>termux-setup-storage
pkg upgrade
pkg install git gh hugo iconv vim
</code></pre><p>Markor&rsquo;s default folder is located at <code>Documents/markor</code> and so this is where I decided to checkout my page&rsquo;s repository to. I also made sure to set some config settings needed for stuff to work<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>:</p>
<pre tabindex="0"><code>gh auth login
cd storage/shared/Documents/markor
git clone https://github.com/foosel/foosel.github.io
git config --global --add safe.directory /storage/emulated/0/Documents/markor/foosel.github.io
git config --global user.email &#34;you@example.com&#34;
git config --global user.name &#34;Your Name&#34;
</code></pre><p>Next I took care of some helper scripts for Termux:Widget and Tasker. I first created some folders:</p>
<pre tabindex="0"><code>mkdir -p ~/.shortcuts
chmod 700 -R ~/.shortcuts
mkdir -p ~/.termux/tasker
chmod 700 -R ~/.termux/tasker
</code></pre><p>Then I added some helper scripts to them:</p>
<p><code>~/.shortcuts/pull_blog</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e">#!/bin/sh
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>cd ~/storage/shared/Documents/markor/foosel.github.io
</span></span><span style="display:flex;"><span>git pull
</span></span></code></pre></div><p><code>~/.shortcuts/push_blog</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e">#!/bin/sh
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>cd ~/storage/shared/Documents/markor/foosel.github.io
</span></span><span style="display:flex;"><span>git push
</span></span></code></pre></div><p><code>~/.termux/tasker/serve_blog</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e">#!/bin/sh
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>cd ~/storage/shared/Documents/markor/foosel.github.io
</span></span><span style="display:flex;"><span>hugo server -D -F --noBuildLock
</span></span></code></pre></div><p><code>~/.termux/tasker/new_blog_post</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e">#!/bin/bash
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>checkout<span style="color:#f92672">=</span>~/storage/shared/Documents/markor/foosel.github.io
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>slugify <span style="color:#f92672">()</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>     echo <span style="color:#e6db74">&#34;</span>$1<span style="color:#e6db74">&#34;</span> | iconv -t ascii//TRANSLIT | sed -r s/<span style="color:#f92672">[</span>~<span style="color:#ae81ff">\^</span><span style="color:#f92672">]</span>+//g | sed -r s/<span style="color:#f92672">[</span>^a-zA-Z0-9<span style="color:#f92672">]</span>+/-/g | sed -r s/^-+<span style="color:#ae81ff">\|</span>-+$//g | tr A-Z a-z
</span></span><span style="display:flex;"><span><span style="color:#f92672">}</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>title<span style="color:#f92672">=</span>$1
</span></span><span style="display:flex;"><span>date<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>date +%Y-%m-%d<span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>slug<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>slugify <span style="color:#e6db74">&#34;</span>$title<span style="color:#e6db74">&#34;</span><span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>cd $checkout
</span></span><span style="display:flex;"><span>mkdir -p content/blog/$date-$slug
</span></span><span style="display:flex;"><span>cat &gt; content/blog/$date-$slug/index.md <span style="color:#e6db74">&lt;&lt;EOF
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">---
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">title: &#34;$title&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">date: $date
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">draft: true
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">---
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">EOF</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>echo $date-$slug/index.md
</span></span></code></pre></div><p><code>~/.termux/tasker/commit_blog</code></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e">#!/bin/sh
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>message<span style="color:#f92672">=</span>$1
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>cd ~/storage/shared/Documents/markor/foosel.github.io
</span></span><span style="display:flex;"><span>git add .
</span></span><span style="display:flex;"><span>git commit -m <span style="color:#e6db74">&#34;</span>$message<span style="color:#e6db74">&#34;</span>
</span></span></code></pre></div><p><code>pull_blog</code> and <code>push_blog</code> take care of git synchronization.</p>
<p><code>serve_blog</code> runs Hugo with draft and future posts visible. The page can be viewed in the browser on the phone at <code>http://localhost:1313</code>.</p>
<p><code>new_blog_post</code> takes a post title as its first argument and from that creates the aforementioned folder structure within the <code>content/blog</code> folder, including a prefilled <code>index.md</code>.<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup></p>
<p>And finally <code>commit_blog</code> takes a commit message as its first argument, stages all changes in the checkout and commits them with the supplied message.</p>
<p>Next, I created Termux:Widget widgets on my desktop for <code>pull_blog</code>, <code>push_blog</code> and <code>run_hugo</code>.</p>
<p>Then I opened Tasker and created two new tasks:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-plain" data-lang="plain"><span style="display:flex;"><span>    Task: New Post
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    A1: Input Dialog [
</span></span><span style="display:flex;"><span>         Title: New post
</span></span><span style="display:flex;"><span>         Text: Enter title
</span></span><span style="display:flex;"><span>         Close After (Seconds): 30
</span></span><span style="display:flex;"><span>         Input Type: 540673 ]
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    A2: Termux [
</span></span><span style="display:flex;"><span>         Configuration: new_blog_post &#34;%input&#34;
</span></span><span style="display:flex;"><span>         
</span></span><span style="display:flex;"><span>         Working Directory ✕
</span></span><span style="display:flex;"><span>         Stdin ✕
</span></span><span style="display:flex;"><span>         Custom Log Level null
</span></span><span style="display:flex;"><span>         Terminal Session ✕
</span></span><span style="display:flex;"><span>         Wait For Result ✓
</span></span><span style="display:flex;"><span>         Timeout (Seconds): 10
</span></span><span style="display:flex;"><span>         Structure Output (JSON, etc): On ]
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    A3: JavaScriptlet [
</span></span><span style="display:flex;"><span>         Code: var uri = &#34;content://net.dinglisch.android.taskerm.fileprovider/external_files/storage/emulated/0/Documents/markor/foosel.github.io/content/blog/&#34; + stdout.trim();
</span></span><span style="display:flex;"><span>         Auto Exit: On
</span></span><span style="display:flex;"><span>         Timeout (Seconds): 45 ]
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    A4: Send Intent [
</span></span><span style="display:flex;"><span>         Action: android.intent.action.SEND
</span></span><span style="display:flex;"><span>         Cat: None
</span></span><span style="display:flex;"><span>         Mime Type: text/markdown
</span></span><span style="display:flex;"><span>         Data: %uri
</span></span><span style="display:flex;"><span>         Package: net.gsantner.markor
</span></span><span style="display:flex;"><span>         Class: net.gsantner.markor.activity.DocumentActivity
</span></span><span style="display:flex;"><span>         Target: Activity ]
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-plain" data-lang="plain"><span style="display:flex;"><span>    Task: Commit Blog
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    A1: Input Dialog [
</span></span><span style="display:flex;"><span>         Title: Commit Blog
</span></span><span style="display:flex;"><span>         Text: Enter commit message
</span></span><span style="display:flex;"><span>         Close After (Seconds): 30 ]
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    A2: Termux [
</span></span><span style="display:flex;"><span>         Configuration: commit_blog &#34;%input&#34;
</span></span><span style="display:flex;"><span>         
</span></span><span style="display:flex;"><span>         Working Directory ✕
</span></span><span style="display:flex;"><span>         Stdin ✕
</span></span><span style="display:flex;"><span>         Custom Log Level null
</span></span><span style="display:flex;"><span>         Terminal Session ✕
</span></span><span style="display:flex;"><span>         Wait For Result ✓
</span></span><span style="display:flex;"><span>         Timeout (Seconds): 10
</span></span><span style="display:flex;"><span>         Structure Output (JSON, etc): On ]
</span></span></code></pre></div><p>&ldquo;New Post&rdquo; queries a post title from the user, calls <code>new_post</code> with that and then opens the new post in Markor.</p>
<p>&ldquo;Commit Blog&rdquo; queries a commit message from the user and calls <code>commit_blog</code> with that.</p>
<p>I created desktop shortcuts for these too and placed all of them, together with Markor, Termux and a browser shortcut, in a new folder &ldquo;Blog&rdquo;.</p>
<p><img alt="Screenshot of the shortcut folder dedicated to my blog." loading="lazy" src="/blog/2023-01-21-hugo-meet-android/shortcuts.jpg"></p>
<p>So, my workflow now consists of pulling, creating a new post, editing it, optionally firing up Hugo to check on the whole thing locally, committing and pushing, all at least without <em>needing</em> to touch the terminal, but always <em>able</em> to if so desired.</p>
<p>It&rsquo;s not perfect, and some app that takes care of all of this from a nice UI would certainly be better (<em>hint hint</em>). But - as this post created entirely from my phone proves - it works for now 😁</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Quick shoutout to <a href="https://pipe.how/write-androidblog/">this post by Emanuel Palm</a> who found himself in a similar situation (prior to boarding a plane to boot) and showed me the path on which I was then able to set up a neat workflow.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>You&rsquo;ll obviously have to adjust that and the following bits with your own repo, name and email.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>Note that I&rsquo;m aware of the existence of <code>hugo new</code>, however it insisted on prefilling the title with the date included and otherwise also felt a bit too inflexible, hence I decided to implement what I wanted directly in bash.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>Meta: Hello Hugo!</title><link>https://foosel.net/blog/2023-01-20-hello-hugo/</link><pubDate>Fri, 20 Jan 2023 00:00:00 +0000</pubDate><guid>https://foosel.net/blog/2023-01-20-hello-hugo/</guid><description>Trying another static site generator in 2023</description><content:encoded><![CDATA[<p>Two years ago I was into learning <a href="https://reactjs.org/">React</a>, since that is what I&rsquo;m planning to use for a future
OctoPrint UI. And when I decided to reboot this webpage, I also figured I would use that
as a reason for experimenting with new build tools and techstacks. So I built this site
with <a href="https://nextjs.org/">next.js</a>, <a href="https://tailwindcss.com/">Tailwind CSS</a> and a bunch of other stuff, and spent a lot
of time implementing basic content management tooling and rendering.</p>
<p>However, once that was done, I realized that while I had learned a lot about React and next.js
in the process, I had not really created something that is fun to use, which reflected in my
reluctance to actually use it. Writing the post about <a href="/blog/2023-01-19-custom-steamdeck-buttons/">my custom SteamDeck buttons</a>,
yesterday really drove that point home again, and in fact kinda broke the camel&rsquo;s back for good 😅</p>
<p>So I decided to do what I&rsquo;d been meaning to do for a long time and finally took a look at <a href="https://gohugo.io/">Hugo</a>,
which I had seen used a lot by others for their blogs over the past years, and also its
<a href="https://github.com/adityatelange/hugo-PaperMod/">PaperMod theme</a> that I had seen on <a href="https://jugmac00.github.io/">Jürgen Gmach&rsquo;s website</a> (👋).</p>
<p>Color me extremely surprised when I had my page rebuilt within a couple of hours, with all the features I wanted and more, and a way nicer
experience for me as the content creator. I didn&rsquo;t even have to touch my posts that much, mostly some small changes on the frontmatter and boom, done.</p>
<p>So I went down the rabbit hole further, fine tuned some things, customized some others, and now I&rsquo;m really happy with the result.</p>
<p>And thus, please say hello to a new version of  <code>foosel.net</code>, now powered by Hugo and PaperMod.</p>
]]></content:encoded></item><item><title>Custom SteamDeck Buttons</title><link>https://foosel.net/blog/2023-01-19-custom-steamdeck-buttons/</link><pubDate>Thu, 19 Jan 2023 00:00:00 +0000</pubDate><guid>https://foosel.net/blog/2023-01-19-custom-steamdeck-buttons/</guid><description>Color coded for your convenience</description><content:encoded><![CDATA[<p><em><strong>Update from 2023-02-02</strong> To answer the most common question I&rsquo;ve gotten after publishing this right away: No, I do not plan on selling these. This is something I did for fun and out of love, I&rsquo;m not interested in making money off of it, and I frankly already have my hands full enough as is without adding a custom button business to the mix as well. I&rsquo;ve provided this write-up to give you an idea of how to do it yourself, and I hope you&rsquo;ll enjoy it.</em></p>
<p>My partner and I both got ourselves SteamDecks in 2022. Since then he&rsquo;s repeatedly mentioned that he&rsquo;d love to have
colored buttons on his, matching the XBox controller layout, as many PC games use these colors for their
button hints during quick time events and similar. But sadly, nothing like that is available yet.</p>
<p>With that in mind, and after losing my initial fear about opening up the deck thanks to swapping my fan and upgrading
my SSD on January 1st of this year, an idea for a surprise started to form in my head. I had watched a ton of videos
on silicone molding and resin casting, I have several 3d printers at my disposal, and there was just enough time left
in my vacation and until our anniversary to pull this off. So I went to work. In total secrecy.</p>
<h2 id="research--material-collection">Research &amp; material collection</h2>
<p>The first thing I jumped into was some research in order to be able to make a plan and know what to get.</p>
<p>I ran across <a href="https://bitbuilt.net/forums/index.php?threads/resin-casting-molding-buttons.2316/">this interesting thread</a>
on button casting which gave me a good idea of what I&rsquo;d need in terms of materials. It also taught me that I needed to
figure out what kind of mold I&rsquo;d even need to create for the buttons. Were they flat on the bottom in which case a single part
mold would suffice, or were they curved or otherwise featured, in which case I&rsquo;d need to create a two part mold? Instead of
disassembling my deck right away (too obvious with my partner being around) I decided to instead checkout
<a href="https://www.ifixit.com/Guide/Steam+Deck+Action+Buttons+(ABXY)+Replacement/148950">this excellent disassembly guide on iFixit</a>
which showed me that the buttons are indeed inset on the bottom, and so I&rsquo;d need a two part mold.</p>
<p>That in turn meant I needed to look into mold release for multi part silicone molds in addition to silicone and resin. The
material list in the thread sadly didn&rsquo;t help me - I couldn&rsquo;t get half of this stuff in Germany - but here&rsquo;s what I finally settled on:</p>
<ul>
<li><strong>Reschimica Silicone RPRO 30</strong> (silicone)</li>
<li><strong>Trollfactory Silicone Mold Separation Cream</strong> (two part mold release)</li>
<li><strong>clear two part epoxy resin</strong> (I got this from a friend who happened to have some collecting dust for several months, still sealed)</li>
<li><strong>food vacuumizer pump and container</strong> (to degass silicone and resin, based on an idea from <a href="https://hackaday.com/2019/12/19/degassing-epoxy-resin-on-the-very-cheap/">this hackaday article</a> - a real vaccum chamber and also a pressure pot would have been nice, but I didn&rsquo;t want to break the bank over a bunch of buttons here 😅)</li>
<li><strong>Mica powder</strong> (to color the resin)</li>
<li><strong>white 4mm rub on letters</strong> (to put the lettering on the buttons)</li>
<li><strong>clear UV resin</strong> (to seal the lettering in)</li>
<li><strong>wooden stir sticks</strong></li>
<li><strong>plastic mixing cups</strong></li>
<li><strong>small paper mixing cups</strong></li>
<li><strong>10ml syringes</strong> and <strong>14g blunt needles</strong> (for injecting the resin into the mold)</li>
<li><strong>plastillina clay</strong> (to fix the buttons during the silicone pour)</li>
</ul>
<p>Thanks to owning a resin printer, I already had <strong>a UV flashlight</strong>, <strong>nitrile gloves</strong> and a <strong>respirator</strong> on hand.</p>
<p>And at least one ready to go <strong>FDM 3d printer</strong> for helping me in the mold creation process.</p>
<p><img alt="Most of the materials and tools that I used for this project, as mentioned above." loading="lazy" src="/blog/2023-01-19-custom-steamdeck-buttons/materials.jpg"></p>
<h2 id="creating-a-two-part-silicone-mold">Creating a two part silicone mold</h2>
<p>Next step was to create my two part silicone mold and for that I first needed something to fix the buttons to, do the pour for the first
part of the mold, flip that over and create the second part of the mold. I did some more research and came across two
interesting videos, <a href="https://www.youtube.com/watch?v=mjKAkul-VDQ">&ldquo;How To Make Custom PS5 Controller Buttons&rdquo;</a> and
<a href="https://www.youtube.com/watch?v=DfbIYH3xauc">&ldquo;Upgrade Your PS5 Controller with DIY Resin Buttons - Better than the Original!&rdquo;</a>.
In both, EJ uses a 3d printed box with custom bottom to hold the buttons in place and create a keyed two part mold. So, I did
create just that as well. My mold box consists of several parts: two halves forming the box, a bottom for the first part (creating the
keying), a smooth bottom and a top brace for the second part. The bottoms slot into the box halves, the top brace is just friction
fit. Why the top brace? To hold some toothpick halves in place that create channels for resin to go in and air to go out when the
mold is closed.</p>
<p>I designed all this in FreeCAD<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> and this is how it looks:</p>
<p><img alt="The mold box configuration for the first pour. Both halves and the keyed bottom." loading="lazy" src="/blog/2023-01-19-custom-steamdeck-buttons/mold_case_part1.png"></p>
<p><img alt="The mold box configuration for the second pour. Both halves, the smooth bottom and the cross brace." loading="lazy" src="/blog/2023-01-19-custom-steamdeck-buttons/mold_case_part2.png"></p>
<p>You can find the STLs <a href="https://www.printables.com/model/374098-steamdeck-button-mold-case">here</a>. All of the parts were printed
on my heavily modified Prusa MK3 with a 0.6mm nozzle and a 0.3mm layer height in black extrudr PLA NX2 - you might have to adjust
the tolerances on other printers or with other filaments, which is why I also included the FreeCAD file (which could be cleaner,
but it worked for me).</p>
<p>Once I had the mold box ready it was time to disassemble the deck and get the buttons in my hand. So I waited until my partner
was out of the house and then got going.</p>
<p>First, I disassembled everything based on the <a href="https://www.ifixit.com/Guide/Steam+Deck+Action+Buttons+(ABXY)+Replacement/148950">aforementioned iFixit guide</a>. I attached the buttons to their spots on the keyed bottom plate of the mold box with some thin, rolled clay wormy
dealies and then thoroughly cleaned them with some q-tips and isopropyl alcohol. It is important to be <em>very</em> thorough here - any
dirt or even just a fingerprint <em>will</em> show up in the silicone mold and thus in the resin casting as well. I actually found that
the outlines of the letters molded into the original buttons left an impression. The level of details you can get from silicone
molds is astonishing!</p>
<p>Also make sure that you keep the lettering of the buttons oriented the same way, that way you will also
be able to re-use the mold later for buttons with inlayed lettering (which is my plan for version 2.0 of this project).</p>
<p><img alt="The four action buttons mounted to the keyed mold case bottom. My hand hovering over them with a q-tip, a bottle of isopropyl alcohol in reach. In the background the disassembled deck." loading="lazy" src="/blog/2023-01-19-custom-steamdeck-buttons/silicone_mold_step1.jpg"></p>
<p>I then slid the bottom plate into the grooves of the mold box halves, sealed the seam with some blue painters tape and just to be
safe also wrapped two rubber bands around it.</p>
<p><img alt="The assembled mold box for the first pour. The buttons are mounted to the bottom. The seems seasled with tape. Two rubber bands go around." loading="lazy" src="/blog/2023-01-19-custom-steamdeck-buttons/silicone_mold_step2.jpg"></p>
<p>Now came the time for the first pour. I weighed out 35g each of part A and B into a plastic mixing cup (my silicone gave instructions for mixing 1:1 by weight, stick to your instructions!) and then thoroughly mixed
it with a stir stick. Then I poured that into a <em>second</em> cup, from high above, in a thin stream - this is first to get some of
the bubbles out but more importantly to prevent any unmixed silicone from getting into the mold. This cup I then degassed. For my
first pour I actually used a power sander to vibrate the bubbles out, but for the second pour I went with the above mentioned
food vacuumizer - it&rsquo;s easier, you get way less shaky hands out of it, and the results also look better. So, into the food container,
lid on, pump on. I degassed until bubbles stop coming out. Then I slowly poured the silicone into a corner of the mold
box, once again in a thin stream from up high. Take your time here, the slower, the less risk of errant bubbles making it into
the mold. Then I degassed the mold again for a couple minutes and let it cure based on the instructions.</p>
<p><img alt="The box filled with silicone after the first pour." loading="lazy" src="/blog/2023-01-19-custom-steamdeck-buttons/silicone_mold_step3.jpg"></p>
<p>Next, I demolded the first part by removing rubber bands and the tape around the box and then carefully pulling the two halves
apart. I then slowly removed the bottom plate from the silicone part as well, being careful to keep the buttons inside<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>.
I then cleaned them off of any leftover plastillina clay and any small bits of silicone.</p>
<p><img alt="The cured first part of the silicone mold, with the buttons still inside. The keying created by the bottom plate is visible." loading="lazy" src="/blog/2023-01-19-custom-steamdeck-buttons/silicone_mold_step4.jpg"></p>
<p>After that I placed the first part of the mold on the smooth bottom plate and slid that back into the mold box. I then applied a
generous coating of two part mold release. I used an old drybrush for that and liberately spread it all across the silicone and
box surfaces, making extra sure to get into all the corners and creases. I taped the box seams again and then put the top brace
in place. I broke four toothpicks in half, also broke off most of their tips, and then inserted one into each of the brace holds,
pushing into opposite ends of the buttons underneath. This was to create channels for the resin to flow into and air to push
out of the mold.</p>
<p><img alt="The first part of the silicone mold placed back into the mold box. The cross brace is installed and two toothpick halves lead to each button." loading="lazy" src="/blog/2023-01-19-custom-steamdeck-buttons/silicone_mold_step5.jpg"></p>
<p>I once again mixed 70g of silicone from 35g of each part A and B, moved into a second cup, degassed it and slowly poured it into
the mold. Then that was degassed as well and left to cure.</p>
<p><img alt="The mold box, once again filled with silicone, and placed in the vacuum container." loading="lazy" src="/blog/2023-01-19-custom-steamdeck-buttons/silicone_mold_step6.jpg"></p>
<p>Another 5h later I carefully demolded my two part mold. I once again removed tape, rubber bands and the top brace, pulling out
the toothpick halves in the process. I then carefully pulled the two halves of the box apart again and equally carefully peeled
the two parts of the mold apart from each other.<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> I could now remove the buttons, clean them, place them back into the deck and
reassemble it. Then I cut off some of the silicone bits that had been sucked into the internal hollow structure of the buttons
which I <em>did</em> not want to replicate. I was very diligent here to not cut away too much. And then I was the proud owner of a two
part silicone mold for SteamDeck action buttons.</p>
<p><img alt="The finished two part mold, visibly keyed and interlockable." loading="lazy" src="/blog/2023-01-19-custom-steamdeck-buttons/silicone_mold_step7.jpg"></p>
<h2 id="resin-time-resin-time-do-do-do-do-resin-time">Resin time, resin time, do do do do, resin time</h2>
<p><em>Wear gloves and a respirator during this!</em></p>
<p>With the mold now ready for action, it was time to try my hand at resin casting. I first assembled the mold, securing the two
halves with four rubber bands. I also attached the mold to a piece of cardboard in the process on which I noted down the location
of each of the buttons inside the mold. This is really important to keep track of which button goes where. With everything being
mirrored thanks to the buttons basically lying on their faces in the mold you otherwise get terribly puzzled and end up with
buttons of the wrong color. Ask me how I know 😅 If you get confused on which button is which, take a close look at the
spaces they left in the mold. The B button of the deck is slightly curved on its outer side due to following the deck&rsquo;s case
curving, and that has helped me a ton to keep track of it and everything else in relation to it.</p>
<p>Next, I mixed up 40ml of resin, so 20ml of each part A and B (my resin gave instructions for mixing 1:1 by volume, check your
instructions!). I degassed it in the vacuum container and then spread it across
four small paper cups, roughly 10ml each. I then added red, greed, blue and yellow mica powder to each of the cups, mixing that
in thoroughly, before placing the cups into the vacuum container and degassing them again. Next, four syringes with blunt 14g
needles were filled with the four colors of resin and then the buttons were filled with the respective color. After getting
confused with the colors on my first try, I double and triple checked each color before filling it in on the second. I carefully inserted
the needle into the inner channel and then slowly pressed the resin in until it came out of the outer channel. On my first
try I made the mistake to overfill, which caused some unintentional color mixing, so be sure to really stop right when the
resin comes out of the air channel.</p>
<p><img alt="The button mold filled with the colored resin, the four small paper cups holding the unused resin sitting next to it in an aluminium tray." loading="lazy" src="/blog/2023-01-19-custom-steamdeck-buttons/resin_cast_step1.jpg"></p>
<p>I then let the buttons cure for 24h before taking a first peek.</p>
<p><img alt="The buttons after 24h of curing." loading="lazy" src="/blog/2023-01-19-custom-steamdeck-buttons/resin_cast_step2.jpg"></p>
<p>They looked great, but a quick fingernail test on one of the resin pots showed the stuff was not fully hardened yet. It turned
out to take 72h until I could proceed with the finishing steps.</p>
<h2 id="finishing-the-buttons">Finishing the buttons</h2>
<p>I kept the buttons attached to the mold for the final steps, as that helped a lot with keeping everything aligned and
less fiddly (it was fiddly enough as is). I carefully placed the sheet with rub on lettering I had bought over each button,
making sure to center the corresponding letter. Then I rubbed the letter on using the blunt tip of my letter opener. The stuff
didn&rsquo;t want to stick to the smooth top surface very well, which had the upside of allowing me to redo something if I messed up, but also
the downside of me having to be <em>very</em> careful to not mess things up that were already fine. In the end, it took me some tries
but I prevailed.</p>
<p>Then I got out the UV resin, put on the respirator and gloves, and with an old brush softly brushed on a thin layer of
resin on each button, careful not get any drops on the side or pooling, but sealing in the letter. I then cured that for several minutes
with the UV flashlight.</p>
<p>Once the resin was cured I carefully pulled out the buttons from the mold and then cut off the sprue with a flush cutter.</p>
<p><img alt="The four custom buttons sitting on a post-it note." loading="lazy" src="/blog/2023-01-19-custom-steamdeck-buttons/finished_buttons1.jpg"></p>
<p>A quick test fit in my deck showed that I needed some light sanding on one side of X, but that was quickly taken care of and
then I had a working set of custom SteamDeck buttons 👍</p>
<p><img alt="Colorful custom action buttons on the right side of a SteamDeck held up to the camera." loading="lazy" src="/blog/2023-01-19-custom-steamdeck-buttons/finished_buttons2.jpg"></p>
<h2 id="where-do-we-go-from-here">Where do we go from here?</h2>
<p>Considering that until Monday January 9th 2023 I had never before touched silicone or epoxy resin, and that by Monday January 16th 2023 I had four self-cast SteamDeck buttons in my hand that while far from perfect looked <em>great</em>, I&rsquo;m <em>very</em>
happy with the result. And the same goes for my partner, who really had absolutely no idea of this until I presented him the
finished buttons on our anniversary. He was and is in awe 😊</p>
<p>However, single colored buttons with rubbed on letters sealed in with UV resin is not my end goal here. After seeing the amazing results one can
achieve with inlaying in EJ&rsquo;s videos, I&rsquo;m really looking forward to trying that out. So the next step will be to cast some inlayed buttons
with the same mold. And I have already printed out the letters on my resin printer 😁</p>
<p><em><strong>Update from 2024-02-14</strong>: The buttons have now been in <strong>very heavy</strong> use by my partner for over a year, and they still look the same as on day 1! No changes on the letters that I rubbed on and sealed with UV resin, and no changes on the buttons themselves either. 👍</em></p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>To be more precise, FreeCAD Link Branch version 2022.09.07&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>If one of the buttons slips out of the mold, you can just press it back in. Just make sure it really slots right back in
where it was, same orientation, full depth and everything. I actually had to do this a bunch of times due to the mold making
stretching over several days due to some issues (see next footnote), and having to reassemble the deck in between to keep
the project a secret from my partner. As a consequence, I can now disassemble and reassemble the deck down to the buttons in
around 20min without the guide 😄&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>I actually had to do the second pour thrice: The first time I didn&rsquo;t create the channels with toothpicks, thinking I could
punch them out afterwards - I couldn&rsquo;t. That led to the creation of the top brace. I then made the mistake to further secure the
toothpicks with superglue, which seems to have interacted with the curing process and caused the top layer of the silicone pour
to stay soft, smeary and sticky. So I did it a third time, exactly as described above, just relying on the friction fit of the
toothpicks, and this time everything cured as expected and I had usable channels 😅&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>How to edit an STL file in FreeCAD</title><link>https://foosel.net/til/how-to-edit-an-stl-file-in-freecad/</link><pubDate>Fri, 30 Sep 2022 00:00:00 +0000</pubDate><guid>https://foosel.net/til/how-to-edit-an-stl-file-in-freecad/</guid><description>&lt;ol&gt;
&lt;li&gt;Create a new file&lt;/li&gt;
&lt;li&gt;&amp;ldquo;File&amp;rdquo; &amp;gt; &amp;ldquo;Import&amp;rdquo;, import the STL&lt;/li&gt;
&lt;li&gt;Select the Part workbench&lt;/li&gt;
&lt;li&gt;Select the imported model&lt;/li&gt;
&lt;li&gt;&amp;ldquo;Part&amp;rdquo; &amp;gt; &amp;ldquo;Create shape from mesh&amp;rdquo;. A tesselation distance of 0.10 should work. Delete or hide import.&lt;/li&gt;
&lt;li&gt;&amp;ldquo;Part&amp;rdquo; &amp;gt; &amp;ldquo;Convert to solid&amp;rdquo;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;a href="https://all3dp.com/1/7-free-stl-editors-edit-repair-stl-files/"&gt;Source&lt;/a&gt;&lt;/p&gt;</description><content:encoded><![CDATA[<ol>
<li>Create a new file</li>
<li>&ldquo;File&rdquo; &gt; &ldquo;Import&rdquo;, import the STL</li>
<li>Select the Part workbench</li>
<li>Select the imported model</li>
<li>&ldquo;Part&rdquo; &gt; &ldquo;Create shape from mesh&rdquo;. A tesselation distance of 0.10 should work. Delete or hide import.</li>
<li>&ldquo;Part&rdquo; &gt; &ldquo;Convert to solid&rdquo;</li>
</ol>
<p><a href="https://all3dp.com/1/7-free-stl-editors-edit-repair-stl-files/">Source</a></p>
]]></content:encoded></item><item><title>How to add an audio delay for video conferencing on Windows</title><link>https://foosel.net/til/how-to-add-an-audio-delay-for-video-conferencing-on-windows/</link><pubDate>Thu, 29 Sep 2022 00:00:00 +0000</pubDate><guid>https://foosel.net/til/how-to-add-an-audio-delay-for-video-conferencing-on-windows/</guid><description>&lt;h1 id="situation"&gt;Situation&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://obsproject.com/"&gt;OBS&lt;/a&gt; used for video conferences through the virtual camera. Audio virtualized and with active OBS filters applied (limiter, noise suppression) through means of setting the monitor device to a sink created with &lt;a href="https://vac.muzychenko.net/en/"&gt;VirtualCable&lt;/a&gt; and using its source in the video conferences tools.&lt;/p&gt;
&lt;h1 id="problem"&gt;Problem&lt;/h1&gt;
&lt;p&gt;The camera feed has a slight delay of 300-400ms. The audio is thus ahead.&lt;/p&gt;
&lt;p&gt;Adding a delay through OBS doesn&amp;rsquo;t get applied to the monitor device (&lt;a href="https://obsproject.com/forum/threads/connecting-obs-with-zoom-without-av-syncing-issues.123960/post-469274"&gt;Source&lt;/a&gt;):&lt;/p&gt;</description><content:encoded><![CDATA[<h1 id="situation">Situation</h1>
<p><a href="https://obsproject.com/">OBS</a> used for video conferences through the virtual camera. Audio virtualized and with active OBS filters applied (limiter, noise suppression) through means of setting the monitor device to a sink created with <a href="https://vac.muzychenko.net/en/">VirtualCable</a> and using its source in the video conferences tools.</p>
<h1 id="problem">Problem</h1>
<p>The camera feed has a slight delay of 300-400ms. The audio is thus ahead.</p>
<p>Adding a delay through OBS doesn&rsquo;t get applied to the monitor device (<a href="https://obsproject.com/forum/threads/connecting-obs-with-zoom-without-av-syncing-issues.123960/post-469274">Source</a>):</p>
<blockquote>
<p>The sync offset that you apply in OBS only applies to either the recording from OBS or the output stream from OBS. It does not apply to the monitor. When you use a virtual cable, and you set it up as a monitor in OBS, you will hear the inputs without any sync delay. As far as I know there is no way to apply your delays to the audio monitor output.</p>
</blockquote>
<h1 id="solution">Solution</h1>
<p>Install <a href="https://www.daansystems.com/radiodelay/">RadioDelay</a>.</p>
<p>Create a second virtual cable device. Use its sink as the monitoring device in OBS, its source as source in RadioDelay, and set the sink of the first virtual cable as sink in RadioDelay. Apply the delay in RadioDelay.</p>
<p>The first cable now outputs delayed audio and can be used as audio source in the video conferencing tools.</p>
<p>You can also create a shortcut to fire up RadioDelay with the right devices selected, the delay applied and the output active with something like this:</p>
<pre tabindex="0"><code>&#34;C:\Program Files\Radiodelay\radiodelay.exe&#34; -delay 0.3 -in 5 -out 3
</code></pre><p><code>in</code> and <code>out</code> are the positions of the audio device in the drop-down list. <code>delay</code> is the delay in seconds.</p>
<p>Can of course also be used without OBS.</p>
<p><em>edit 2023-03-11:</em> <a href="../how-to-add-an-audio-delay-for-video-conferencing-on-linuxpulseaudio">Here&rsquo;s how I solved the same problem under Linux</a>.</p>
]]></content:encoded></item><item><title>How to determine an RPi kernel version and build without booting it</title><link>https://foosel.net/til/how-to-determine-an-rpi-kernel-version-and-build-without-booting-it/</link><pubDate>Thu, 16 Jun 2022 00:00:00 +0000</pubDate><guid>https://foosel.net/til/how-to-determine-an-rpi-kernel-version-and-build-without-booting-it/</guid><description>&lt;p&gt;To figure out the kernel version and build without booting it, e.g. to install matching device drivers during an automated image build in something like &lt;a href="https://github.com/OctoPrint/CustoPiZer"&gt;CustoPiZer&lt;/a&gt;, use something like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;function&lt;/span&gt; version_and_build_for_kernelimg&lt;span style="color:#f92672"&gt;()&lt;/span&gt; &lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; kernelimg&lt;span style="color:#f92672"&gt;=&lt;/span&gt;$1
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# uncompressed kernel?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; output&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;strings $kernelimg | grep &lt;span style="color:#e6db74"&gt;&amp;#39;Linux version&amp;#39;&lt;/span&gt; &lt;span style="color:#f92672"&gt;||&lt;/span&gt; echo&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt; -z &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$output&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;]&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# compressed kernel, needs more work, see https://raspberrypi.stackexchange.com/a/108107&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; pos&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;LC_ALL&lt;span style="color:#f92672"&gt;=&lt;/span&gt;C grep -P -a -b -m &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; --only-matching &lt;span style="color:#e6db74"&gt;&amp;#39;\x1f\x8b\x08&amp;#39;&lt;/span&gt; $kernelimg | cut -f &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; -d :&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; dd &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;$kernelimg of&lt;span style="color:#f92672"&gt;=&lt;/span&gt;kernel.gz skip&lt;span style="color:#f92672"&gt;=&lt;/span&gt;$pos iflag&lt;span style="color:#f92672"&gt;=&lt;/span&gt;skip_bytes
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; output&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;gzip --decompress --stdout kernel.gz | strings | grep &lt;span style="color:#e6db74"&gt;&amp;#39;Linux version&amp;#39;&lt;/span&gt; &lt;span style="color:#f92672"&gt;||&lt;/span&gt; echo&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; version&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;echo $output | awk &lt;span style="color:#e6db74"&gt;&amp;#39;{print $3}&amp;#39;&lt;/span&gt; | tr -d &lt;span style="color:#e6db74"&gt;&amp;#39;+&amp;#39;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; build&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;echo $output | awk -F&lt;span style="color:#e6db74"&gt;&amp;#34;#&amp;#34;&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;{print $NF}&amp;#39;&lt;/span&gt; | awk &lt;span style="color:#e6db74"&gt;&amp;#39;{print $1}&amp;#39;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#f92672"&gt;[[&lt;/span&gt; -n &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$version&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; -n &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$build&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;]]&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; echo &lt;span style="color:#e6db74"&gt;&amp;#34;Version: &lt;/span&gt;$kernel&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; echo &lt;span style="color:#e6db74"&gt;&amp;#34;Build: &lt;/span&gt;$build&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; echo
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; echo &lt;span style="color:#e6db74"&gt;&amp;#34;Cannot determine kernel version and build number for &lt;/span&gt;$kernelimg&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note that this has only been tested with kernels on RaspberryPi OS images, YMMV.&lt;/p&gt;</description><content:encoded><![CDATA[<p>To figure out the kernel version and build without booting it, e.g. to install matching device drivers during an automated image build in something like <a href="https://github.com/OctoPrint/CustoPiZer">CustoPiZer</a>, use something like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#66d9ef">function</span> version_and_build_for_kernelimg<span style="color:#f92672">()</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>    kernelimg<span style="color:#f92672">=</span>$1
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># uncompressed kernel?</span>
</span></span><span style="display:flex;"><span>    output<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>strings $kernelimg | grep <span style="color:#e6db74">&#39;Linux version&#39;</span> <span style="color:#f92672">||</span> echo<span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#f92672">[</span> -z <span style="color:#e6db74">&#34;</span>$output<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">]</span>; <span style="color:#66d9ef">then</span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e"># compressed kernel, needs more work, see https://raspberrypi.stackexchange.com/a/108107</span>
</span></span><span style="display:flex;"><span>        pos<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>LC_ALL<span style="color:#f92672">=</span>C grep -P -a -b -m <span style="color:#ae81ff">1</span> --only-matching <span style="color:#e6db74">&#39;\x1f\x8b\x08&#39;</span> $kernelimg | cut -f <span style="color:#ae81ff">1</span> -d :<span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>        dd <span style="color:#66d9ef">if</span><span style="color:#f92672">=</span>$kernelimg of<span style="color:#f92672">=</span>kernel.gz skip<span style="color:#f92672">=</span>$pos iflag<span style="color:#f92672">=</span>skip_bytes
</span></span><span style="display:flex;"><span>        output<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>gzip --decompress --stdout kernel.gz | strings | grep <span style="color:#e6db74">&#39;Linux version&#39;</span> <span style="color:#f92672">||</span> echo<span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">fi</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    version<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>echo $output | awk <span style="color:#e6db74">&#39;{print $3}&#39;</span> | tr -d <span style="color:#e6db74">&#39;+&#39;</span><span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>    build<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>echo $output | awk -F<span style="color:#e6db74">&#34;#&#34;</span> <span style="color:#e6db74">&#39;{print $NF}&#39;</span> | awk <span style="color:#e6db74">&#39;{print $1}&#39;</span><span style="color:#66d9ef">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#f92672">[[</span> -n <span style="color:#e6db74">&#34;</span>$version<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">&amp;&amp;</span> -n <span style="color:#e6db74">&#34;</span>$build<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">]]</span>; <span style="color:#66d9ef">then</span>
</span></span><span style="display:flex;"><span>        echo <span style="color:#e6db74">&#34;Version: </span>$kernel<span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>        echo <span style="color:#e6db74">&#34;Build: </span>$build<span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">else</span>
</span></span><span style="display:flex;"><span>        echo
</span></span><span style="display:flex;"><span>        echo <span style="color:#e6db74">&#34;Cannot determine kernel version and build number for </span>$kernelimg<span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">fi</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">}</span>
</span></span></code></pre></div><p>Note that this has only been tested with kernels on RaspberryPi OS images, YMMV.</p>
]]></content:encoded></item><item><title>How to sync starred GitHub repos to Raindrop via NodeRED</title><link>https://foosel.net/til/how-to-sync-starred-github-repos-to-raindrop-via-nodered/</link><pubDate>Thu, 16 Jun 2022 00:00:00 +0000</pubDate><guid>https://foosel.net/til/how-to-sync-starred-github-repos-to-raindrop-via-nodered/</guid><description>&lt;p&gt;The following flow syncs starred repos of a GitHub user to Raindrop.io every 10min and on trigger:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of a NodeRED flow. Two nodes &amp;ldquo;every 10min&amp;rdquo; and &amp;ldquo;timestamp&amp;rdquo; lead to a node &amp;ldquo;Starred repos for foosel&amp;rdquo;. That is wired to &amp;ldquo;Preprocess&amp;rdquo; which in turn is wired to &amp;ldquo;Save in Raindrop.io&amp;rdquo;" loading="lazy" src="https://foosel.net/til/how-to-sync-starred-github-repos-to-raindrop-via-nodered/nodered.png"&gt;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Every 10min&amp;rdquo; is a cron trigger node that fires every 10min. &amp;ldquo;timestamp&amp;rdquo; is an inject node for triggering the flow manually.&lt;/p&gt;</description><content:encoded><![CDATA[<p>The following flow syncs starred repos of a GitHub user to Raindrop.io every 10min and on trigger:</p>
<p><img alt="Screenshot of a NodeRED flow. Two nodes &ldquo;every 10min&rdquo; and &ldquo;timestamp&rdquo; lead to a node &ldquo;Starred repos for foosel&rdquo;. That is wired to &ldquo;Preprocess&rdquo; which in turn is wired to &ldquo;Save in Raindrop.io&rdquo;" loading="lazy" src="/til/how-to-sync-starred-github-repos-to-raindrop-via-nodered/nodered.png"></p>
<p>&ldquo;Every 10min&rdquo; is a cron trigger node that fires every 10min. &ldquo;timestamp&rdquo; is an inject node for triggering the flow manually.</p>
<p>&ldquo;Starred repos for foosel&rdquo; is an HTTP GET request against <code>https://api.github.com/users/foosel/starred</code>, set to return an object parsed from the response JSON. Change <code>foosel</code> to your own username.</p>
<p>The &ldquo;Preprocess&rdquo; function node has this source:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">key</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;feed_githubstars_repos&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">repos</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">flow</span>.<span style="color:#a6e22e">get</span>(<span style="color:#a6e22e">key</span>) <span style="color:#f92672">||</span> [];
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">added</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">msg</span>.<span style="color:#a6e22e">payload</span>.<span style="color:#a6e22e">filter</span>(<span style="color:#a6e22e">repo</span> =&gt; <span style="color:#f92672">!</span><span style="color:#a6e22e">repos</span>.<span style="color:#a6e22e">includes</span>(<span style="color:#a6e22e">repo</span>.<span style="color:#a6e22e">full_name</span>));
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">flow</span>.<span style="color:#a6e22e">set</span>(<span style="color:#a6e22e">key</span>, <span style="color:#a6e22e">repos</span>.<span style="color:#a6e22e">concat</span>(<span style="color:#a6e22e">added</span>.<span style="color:#a6e22e">map</span>(<span style="color:#a6e22e">repo</span> =&gt; <span style="color:#a6e22e">repo</span>.<span style="color:#a6e22e">full_name</span>)));
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">return</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">payload</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">items</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">added</span>.<span style="color:#a6e22e">map</span>(<span style="color:#a6e22e">repo</span> =&gt; {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> {
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">title</span><span style="color:#f92672">:</span> <span style="color:#e6db74">`</span><span style="color:#e6db74">${</span><span style="color:#a6e22e">repo</span>.<span style="color:#a6e22e">full_name</span><span style="color:#e6db74">}</span><span style="color:#e6db74">: </span><span style="color:#e6db74">${</span><span style="color:#a6e22e">repo</span>.<span style="color:#a6e22e">description</span><span style="color:#e6db74">}</span><span style="color:#e6db74">`</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">link</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">repo</span>.<span style="color:#a6e22e">html_url</span>,
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">tags</span><span style="color:#f92672">:</span> [<span style="color:#e6db74">&#34;github&#34;</span>, <span style="color:#e6db74">&#34;starred&#34;</span>]
</span></span><span style="display:flex;"><span>            }
</span></span><span style="display:flex;"><span>        })
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>&ldquo;Save in Raindrop.io&rdquo; performs an HTTP POST request against <code>https://api.raindrop.io/rest/v1/raindrops</code> with authentication type&quot;Bearer&quot; and an API token created <a href="https://app.raindrop.io/settings/integrations">here</a>.</p>
]]></content:encoded></item><item><title>TFA Dostmann meets ESPHome</title><link>https://foosel.net/blog/2022-01-03-tfa-dostmann-meets-esphome/</link><pubDate>Mon, 03 Jan 2022 00:00:00 +0000</pubDate><guid>https://foosel.net/blog/2022-01-03-tfa-dostmann-meets-esphome/</guid><description>Integrating a CO2 sensor into my HA setup</description><content:encoded><![CDATA[<p>I attended RC3 from December 27th until December 30th. While it was (once again)
only a virtual edition of the Chaos Communication Congress, at least this time
around I managed to have a similar experience to 36c3, as in, I spent the last two days
mostly hanging out with a bunch of fellow geeks in a fun location (a jitsi conference
that also included a camera pointing at an aquarium full of fish) and nerding out
while tinkering around with electronics.</p>
<p>And thus I finally integrated the CO2 sensor unit I bought a couple weeks ago into
my Home Automation setup, with the help of a Wemos D1 Mini and <a href="https://esphome.io">ESPHome</a>. At first
I went with an ESP12, a voltage regulator and a <a href="https://github.com/schinken/esp8266-co2monitor">different firmware</a>,
but that didn&rsquo;t work out due to the ESP not wanting to behave (my guess is I didn&rsquo;t wire the barebone module
up correctly or the voltage regulator was causing issues) and I also got some weird readings
reported by the firmware (20k ppm CO2 - I know the air in my office can get bad after a couple of hours of
coding, but not THAT bad).</p>
<p>The CO2 sensor is an &ldquo;AIRCO2NTROL MINI&rdquo; from TFA Dostmann, but it is also available under other names
with a very similar case and more or less the same internals, as I learned from the
<a href="https://esphome.io/components/sensor/zyaura.html">ESPHome docs</a>. Where my Dostmann edition seems to differ from the majority is the
pin order of the internal debug port, which turned out to have CLK and DATA swapped
in my case, which caused me quite the headache and a bit of frustration. So just for future reference,
the pin order I found in my device is 5V - Data - CLK - Gnd from left to right with the hose to the left:</p>
<p><img alt="The pinout of the sensor&rsquo;s debug port, 5V - Data - CLK - Gnd" loading="lazy" src="/blog/2022-01-03-tfa-dostmann-meets-esphome/tfa-dostmann-pinout.jpg"></p>
<p>I hooked these up to the Wemos D1 Mini (clone) like this:</p>
<p><img alt="A wiring diagram of how to hookup the Wemos D1 mini to the debug port, 5V to 5V, Gnd to Gnd, Data to D1 and CLK to D2" loading="lazy" src="/blog/2022-01-03-tfa-dostmann-meets-esphome/tfa-dostmann-wemos-d1-wiring.png"></p>
<p>So 5V to 5V, Gnd to Gnd, Data to D1 and CLK to D2.</p>
<p>The ESPHome config I then flashed to the D1 is the following:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">esphome</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">name</span>: <span style="color:#ae81ff">dostmann-office</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">platform</span>: <span style="color:#ae81ff">ESP8266</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">board</span>: <span style="color:#ae81ff">d1_mini</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">logger</span>:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">mqtt</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">broker</span>: !<span style="color:#ae81ff">secret mqtt_iot_broker</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">ota</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">password</span>: !<span style="color:#ae81ff">secret ota_pass</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">wifi</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">ssid</span>: !<span style="color:#ae81ff">secret wifi_iot_ssid</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">password</span>: !<span style="color:#ae81ff">secret wifi_iot_pass</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">ap</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">ssid</span>: <span style="color:#e6db74">&#34;Dostmann Office Fallback&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">password</span>: !<span style="color:#ae81ff">secret fallback_pass</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">power_save_mode</span>: <span style="color:#ae81ff">high</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">captive_portal</span>:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">sensor</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">platform</span>: <span style="color:#ae81ff">zyaura</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">clock_pin</span>: <span style="color:#ae81ff">D2</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">data_pin</span>: <span style="color:#ae81ff">D1</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">co2</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">name</span>: <span style="color:#e6db74">&#34;Office Dostmann CO2&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">temperature</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">name</span>: <span style="color:#e6db74">&#34;Office Dostmann Temperature&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">platform</span>: <span style="color:#ae81ff">wifi_signal</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">name</span>: <span style="color:#e6db74">&#34;Office Dostmann WiFi Signal&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">update_interval</span>: <span style="color:#ae81ff">60s</span>
</span></span></code></pre></div><p>(If you are wondering about the <code>!secret</code> stuff, those values are contained in
a <code>secret.yaml</code> file in my esphome folder, and you can read all about that
<a href="https://esphome.io/guides/faq.html#tips-for-using-esphome">in the ESPHome docs here</a>.)</p>
<p>And with that I could now see the sensor in my Home Assistant instance and forward the data
easily to my InfluxDB &amp; Grafana monitoring stack.</p>
<p>To put everything together physically, fully contained, I used <a href="https://www.thingiverse.com/thing:4225732">this alternative backplate</a>
by Stefan Kern.</p>
<p><img alt="The alternative backplate in place, closing up the sensor and containing the Wemos D1 Mini as well" loading="lazy" src="/blog/2022-01-03-tfa-dostmann-meets-esphome/tfa-dostmann-back.jpg"></p>
<p>However, I&rsquo;ve noticed a temperature increase of around 3°C with it and the ESP
in place, and I fear this might be screwing with the CO2 sensor&rsquo;s calibration (as the measurement
is temperature sensitive). I already mitigated this a bit by setting the chip to power save and adding some strategically placed aluminium tape,
but that&rsquo;s only improved things slightly. Due to that I plan to redesign the
backplate to have the ESP outside the sensor case, in its own compartment. I
hope that will solve the &ldquo;running hot&rdquo; issue for good then, but we&rsquo;ll see.</p>
<p><em>Update from January 27th 2022</em> I redesigned the backplate and now have a solution that seems to work better, based on the reported temperature and CO2. <a href="https://www.prusaprinters.org/prints/119968-airco2ntrol-mini-backplate-with-wemos-d1-mini">I&rsquo;ve published it here</a>.</p>
<p>In any case, for now I at least got a reliable indicator of my office&rsquo;s CO2 levels that also now
are trackable long term, and I have also forwarded the current values to my <a href="https://awtrixdocs.blueforcer.de/">AWTRIX mini</a>
via some NodeRED flow that also takes care of color coding. Further possibilities include
flashing the office lights or some audio cues, should just the visual warning turn out
to be insufficient in the long term 😉</p>
<p><img alt="CO2 sensor data graphed over two days" loading="lazy" src="/blog/2022-01-03-tfa-dostmann-meets-esphome/grafana-co2.png"></p>
]]></content:encoded></item><item><title>Being patient with yourself is hard</title><link>https://foosel.net/blog/2021-09-28-being-patient-with-yourself-is-hard/</link><pubDate>Tue, 28 Sep 2021 00:00:00 +0000</pubDate><guid>https://foosel.net/blog/2021-09-28-being-patient-with-yourself-is-hard/</guid><description>What a knee surgery taught me about myself</description><content:encoded><![CDATA[<p>I&rsquo;ve been having knee issues all my life. I can&rsquo;t remember a time when the occasional pain, stiffness and crunchy noises in both knees weren&rsquo;t part of my regular experience. The past three years however the left knee got a ton worse, frequently hurting for days on end, getting hot, and overall just being a literal pain. That is why earlier this year I finally decided, enough is enough, got another opinion from my orthopedist and we scheduled a key hole surgery to fix what was suspected to be a very active plica syndrome. This surgery happened on September 20th and it made me learn a LOT about myself.</p>
<p>I went into this knowing that I&rsquo;d be immobilised for a few days, that I&rsquo;d need crutches, that there&rsquo;d be pain and also several weeks of physical therapy following the surgery. What I did not expect was how <em>utterly</em> impatient I would be with myself.</p>
<p>See, this was the first surgery I ever had as a conscious person. I did have two minor ear surgeries as a toddler, at two and four years of age, and while both of them make for my earliest (and quite scary) memories, I don&rsquo;t have any feeling on how long recovery took, what it entailed and so on. And in any case, that wouldn&rsquo;t have allowed me to judge what I was looking at with a knee surgery anyhow. I had read up on this of course. How I could expect full weight bearing of my leg by day two or three, and how I was even encouraged to try to really use it again right after. That there would be some swelling for a few days. In my mind this resulted in &ldquo;I&rsquo;ll be back on my feet with everything working in no time&rdquo;.</p>
<p>Colour me surprised when I woke up from anesthesia to a ridiculous amount of pain, the inability to lift the leg, swelling beyond expectation and the information that there had been a so far undetected meniscus tear that had since scarred over. And thus began a journey of self discovery 😅</p>
<p>What this experience taught me has been eye opening. It turns out I have next to no patience with myself. Consciously I understood that the knee would need time to recover, that the swelling would not come down one day to the next and that I&rsquo;d have to give myself time to heal. But at the back of my head almost immediately I started judging myself and falling short of my expectations. Here I am, a week post surgery, and feeling bad for still preferring to have my crutches in reach even though the leg is fully weight bearing again simply because walking without them feels utterly unstable and outright hurts too after one too many attempts at pushing myself. Here I am fretting over the inability to achieve full motion in the leg again even though it has gotten better day after day and I have been taking small milestones all the time. And here I am growing increasingly annoyed by my general reliance on help.</p>
<p>This surgery made me discover a new flaw for good that I had suspected to be there for a long time. I&rsquo;m utterly impatient with myself. I have absolutely no problem with other people taking all the time they need to recover, to heal, to work through a problem, but when it comes to myself I have such high expectations that I&rsquo;m absolutely bound to fail them and get frustrated by that. I&rsquo;ve pushed myself through bad times before - my mental health did not thank me but yielded and I made it work somehow. My body however is <strong>not</strong> taking this kind of crap from me and showing me some <em>strong</em> boundaries right now, and that is an utterly humbling learning experience.</p>
<p>I wouldn&rsquo;t say you should get yourself a knee surgery, because frankly this experience so far sucks a lot. But if you do, take it as a chance to practice patience with yourself. It&rsquo;s what I&rsquo;m now seeing myself forced to do and it was long overdue.</p>
]]></content:encoded></item><item><title>Hydroponics the Kratky way</title><link>https://foosel.net/blog/2021-09-12-hydroponics-the-kratky-way/</link><pubDate>Sun, 12 Sep 2021 00:00:00 +0000</pubDate><guid>https://foosel.net/blog/2021-09-12-hydroponics-the-kratky-way/</guid><description>How I found myself with way too much Basil on my hands</description><content:encoded><![CDATA[<p>Since my partner loves spicy food, earlier this year I got us a chili growing kit. What was meant as an experiment has since become a full blown pepper growing operation that has taken over my livingroom window sills and half the balcony and already yielded its first fruits. And because whenever I try something new I also tend to do way too much research on it, while getting the plants started the normal way in soil (and then promptly running into the first issues with fungus gnats) I started to look deeper into <a href="https://en.wikipedia.org/wiki/Hydroponics">hydroponics</a>.</p>
<p>I had heard of this approach before: growing plants in an inert grow medium instead of soil and feeding them with controlled nutrient solutions pumped around the roots. It&rsquo;s a fascinating rabbit hole to go down and it always tickled my interest (especially given the automation and sensoric evaluation possibilities), however it also felt way too involved to get started with unless I had some more actual space and some actual need to grow plants, what with all the lights and pumps and pipe systems that I saw associated with it. That is, until I came across the <a href="https://en.wikipedia.org/wiki/Kratky_method">Kratky method</a>.</p>
<p>The Kratky method is a pretty much passive approach to hydroponics. The idea is simple: you take an opaque container for your nutrient solution, cut a bunch of holes in the lid and then place your plants into netpots or similar in those holes so their roots reach into the solution. So far, so similar to the <a href="https://en.wikipedia.org/wiki/Deep_water_culture">Deep Water Culture</a> approach. But instead of now aerating the solution with an aquarium air stone or similar to give your roots the oxygen they need, you instead simply allow the level of your nutrient solution to drop, creating an air gap and leaving parts of the roots hanging in the air. That way the roots can get nutrients but can also breathe (which is important so they don&rsquo;t rot). You&rsquo;ll have to fill up the solution a bit if needed (always leaving an airgap), but apart from that the whole setup is completely self managed. No power needed, no moving parts. And easily set up in something as small as a mason jar.</p>
<p>This was intriguing to me, and I had wanted to try my luck with growing some basil and oregano for pizza and such anyhow, so I decided to give this approach a shot. I ordered a kitchen herb seed kit, some 1l mason jars, some rock wool cubes (as growing substrate) and some hydroponic fertilizer. Then I fired up FreeCAD and designed a small lid with integrated rock wool cube holder (STL <a href="quattro_stagioni_kratky_1l_36mm.v2.stl">here</a>, FreeCAD file <a href="quattro_stagioni_kratky_1l.FCStd">here</a>), printed a bunch of them and then went to work.</p>
<p><img alt="The lid design with rock wool cube holder in FreeCAD" loading="lazy" src="/blog/2021-09-12-hydroponics-the-kratky-way/lid-design.png"></p>
<p>The seeds were planted in the rock wool cubes, watered and then I waited. The first green soon showed up and thus I transferred the cubes into their holders, filled the jars with 1l water plus the recommended fertilizer amount (thus creating a nutrient solution) and then put the lids on. The cubes sat right in the solution and thus were kept watered. To keep light out of the container (to prevent algae growth and also to not have everything heat up so much) I simply wrapped some aluminium foil around the jars. If I&rsquo;m honest this was just meant as a temporary solution until I got around to sewing a little cover out of some light blocking fabric I have on hand, but I still haven&rsquo;t gotten around to do that and the foil works just fine 🤷‍♀️</p>
<p><img alt="The two baby Basil plants at the start of their Krakty journey" loading="lazy" src="/blog/2021-09-12-hydroponics-the-kratky-way/start.jpg"></p>
<p>That was on July 18th and since then I&rsquo;ve been able to witness some astonishing growth, especially on the Basil. Have a look at the progress:</p>
<p><img alt="Progress on July 27th, the two plants are around 5cm high." loading="lazy" src="/blog/2021-09-12-hydroponics-the-kratky-way/progress-07-27.jpg"></p>
<p><img alt="Progress on August 4th, around 12-15cm high." loading="lazy" src="/blog/2021-09-12-hydroponics-the-kratky-way/progress-08-04.jpg"></p>
<p><img alt="Progress on August 14th, around 20-25cm in height." loading="lazy" src="/blog/2021-09-12-hydroponics-the-kratky-way/progress-08-14.jpg"></p>
<p><img alt="Progress on August 27th, more than 30cm high." loading="lazy" src="/blog/2021-09-12-hydroponics-the-kratky-way/progress-08-27.jpg"></p>
<p><img alt="Progress on September 10th, the plants are now around 50cm height each." loading="lazy" src="/blog/2021-09-12-hydroponics-the-kratky-way/progress-09-10.jpg"></p>
<p>As of today, both Basil plants had reached a plant height of around 50cm. The Oregano also looks healthy, but sadly I accidentally got hold of a hanging variant, making everything a bit tricky in this setup 😅</p>
<p>Sadly, I had to emergency-harvest one of the Basil&rsquo;s today. While topping up the nutrient solution the other day I noticed the roots to be way darker than they should be. They didn&rsquo;t look particularly healthy. Some of the leaves also started to look a tad unhappy. I came to the conclusion that I might have caught myself some root rot on this plant. Doing some research I learned that with the Kratky method, if you grow for longer than 4-6 weeks you really should occasionally replace the nutrient solution completely and clean the container to keep bacteria from taking root in your roots (pun not intended). I guess this is what happened here. I tried to save the plant with a 2:1 water and 3% hydrogen peroxide bath yesterday, but either it was already too late or that was too much stress. This morning the leaves were hanging and the whole plant looked quite sorry so I decided it was time to harvest and now I have a jar of fresh pesto verde.</p>
<p><img alt="One small jar of freshly made pesto" loading="lazy" src="/blog/2021-09-12-hydroponics-the-kratky-way/pesto.jpg"></p>
<p>Further reading revealed that it&rsquo;s apparently an option to add a teeny tiny amount of 3% hydrogen peroxide to the nutrient solution itself to help keep any unwelcome guests away (and also help with aeration), so I&rsquo;ve now added 0.5ml to my freshly topped off jar of the other Basil and hope that won&rsquo;t do harm but rather good. Wish me luck 😉</p>
<p>All in all, I call this whole first dabbling with hydroponics a great success. The Kratky method allowed me to get up and running quickly without huge investments and space requirements, and the results were amazing. I&rsquo;ve also now planted some peppermint seeds and they are already sprouting, so my hopes are high for some amazing fresh teas later this fall! If you&rsquo;ve always wanted to experiment with hydroponics but it always felt way too involved, maybe take a look at the Kratky method 😊</p>
]]></content:encoded></item><item><title>A debugging story</title><link>https://foosel.net/blog/2021-05-09-a-debugging-story/</link><pubDate>Sun, 09 May 2021 00:00:00 +0000</pubDate><guid>https://foosel.net/blog/2021-05-09-a-debugging-story/</guid><description>Could this bug for once not be my fault?</description><content:encoded><![CDATA[<p>About a week ago I got a new <a href="https://github.com/OctoPrint/OctoPrint/issues/4117">bug report</a> on <a href="https://octoprint.org">OctoPrint&rsquo;s</a> issue tracker:</p>
<blockquote>
<p><strong>GCode Viewer Visualisation Problem</strong></p>
<p><em>The problem</em></p>
<p>The visualisation in GCode viewer ist not correct. The print is OK.
See gcode file (zip) on Layer 43 to 47 and 49</p>
<p>And screenshot</p>
</blockquote>
<p>You already saw the included screenshot, and it shows that there was a spike being visualized in the GCode Viewer that
wasn&rsquo;t actually there. My first attempt at reproduction failed spectacularly &ndash; the file looked exactly like
it was supposed to. Then I noticed that the OP was using Google Chrome however (adding the detected user agent
to the system information contained in OctoPrint&rsquo;s new System Info Bundles already paid off!) and tried with that
instead of my usual Firefox, and lo and behold, I saw the issue.</p>
<p>Scrolling a bit through the file revealed further defects, as also mentioned by the OP, e.g. this one:</p>
<p><img alt="Another defect, this time a whole part of the outline is being misplaced" loading="lazy" src="/blog/2021-05-09-a-debugging-story/issue_4117_2.png"></p>
<p>At this point it was clear that this was a Chrome-only issue. But was it a bug in OctoPrint or possibly a browser
bug? More information for that was needed but not readily available, and the file was also too big to quickly
gleam anything from the GCode itself that could possibly help to narrow down on the problem.</p>
<p>So the first step was to create a minimal GCode file that showed the same error. For this I took a look at
the reported layer height in the viewer on the layer a defect was visible and then narrowed down on the affected lines
by using the horizontal command sliders to further limit the view. That way I quickly found that these were the
problematic lines:</p>
<pre tabindex="0"><code class="language-gcode" data-lang="gcode">G1 X173.595 Y103.9 E247.16716
G3 X173.600 Y126.097 I-105613.507 J39.645 E248.20080
G1 X169.552 Y126.098 E248.38933
</code></pre><p>More specifically, the error was caused by the contained <a href="https://reprap.org/wiki/G-code#G2_.26_G3:_Controlled_Arc_Move"><code>G3</code> command</a>,
which instructs the printer to move in a counter clockwise arc
from its current position to the given X and Y coordinates, with the center of said arc offset by the given I and J
parameters. In the case of these lines, that meant to move in an arc from <code>(173.595, 103.0)</code> to <code>(173.600, 126.097)</code>
with the arc&rsquo;s center at <code>x = 173.595 + (-105613.507) = -105439.912</code> and <code>y = 103.9 + 39.645 = 143.54500000000002</code>.
Or in other words, a rather short arc with an enormous radius of over 105m that was more a straight line than
an arc really. And that line was being drawn too long, causing the weird spike in the rendition.</p>
<p>In order to understand how that could happen however we need to take a look at how the GCode viewer is implemented and how
arcs work in that implementation. At its core, the GCode viewer is an HTML5 2D canvas on which the path described in
a GCode file gets drawn. Commands like <code>G0</code> and <code>G1</code> that describe straight lines are drawn using <a href="https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineTo"><code>lineTo</code></a>,
arcs as described by <code>G2</code> and <code>G3</code> are drawn using <a href="https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/arc"><code>arc</code></a>.</p>
<p><code>arc</code> takes six parameters: the <code>x</code> and <code>y</code> coordinate of the center of the arc, the radius <code>r</code>, the <code>startAngle</code> determining from which angle to start
drawing the arc and the <code>endAngle</code> until which to draw the arc, and a flag that&rsquo;s <code>true</code> for counter clockwise and <code>false</code> or empty for clockwise.
It is obvious this doesn&rsquo;t directly translate to the data contained in the GCode itself, where we rather have three points defining the arc &ndash; a start
point, and end point, and the arc&rsquo;s center. So we need to translate this into the data required by the <code>arc</code> method. Using some trigonometry,
that is fairly straightforward:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-js" data-lang="js"><span style="display:flex;"><span><span style="color:#75715e">// given: G2/G3 X&lt;endX&gt; Y&lt;endY&gt; I&lt;i&gt; J&lt;j&gt;, &lt;startX&gt;, &lt;startY&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">arcX</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">startX</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">i</span>;
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">arcY</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">startY</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">j</span>;
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">r</span> <span style="color:#f92672">=</span> Math.<span style="color:#a6e22e">sqrt</span>(<span style="color:#a6e22e">i</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">i</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">j</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">j</span>);
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">startAngle</span> <span style="color:#f92672">=</span> Math.<span style="color:#a6e22e">atan2</span>(<span style="color:#a6e22e">startY</span> <span style="color:#f92672">-</span> <span style="color:#a6e22e">arcY</span>, <span style="color:#a6e22e">startX</span> <span style="color:#f92672">-</span> <span style="color:#a6e22e">arcX</span>);
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">endAngle</span> <span style="color:#f92672">=</span> Math.<span style="color:#a6e22e">atan2</span>(<span style="color:#a6e22e">endY</span> <span style="color:#f92672">-</span> <span style="color:#a6e22e">arcY</span>, <span style="color:#a6e22e">endX</span> <span style="color:#f92672">-</span> <span style="color:#a6e22e">arcX</span>);
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">ccw</span> <span style="color:#f92672">=</span> (<span style="color:#a6e22e">command</span> <span style="color:#f92672">===</span> <span style="color:#e6db74">&#34;G3&#34;</span>)
</span></span></code></pre></div><p><img alt="The parameters and their relation" loading="lazy" src="/blog/2021-05-09-a-debugging-story/drawing.png"></p>
<p>My first guess was that the result of this conversion was somehow different between Firefox and Chrome, and so I modified the GCode viewer to log
the calculated values and then compared the two outcomes. The values were completely identical between both browsers, so what was being fed
into the canvas <code>arc</code> command was identical and yet produces different results. Why?</p>
<p>My next approach was to add some more visual debug output to the viewer itself. I modified it such that the arc parameters as calculated would
actually be drawn on the canvas as well, in form of a geometrical pizza slice showing the arc&rsquo;s center, its &ldquo;legs&rdquo; and its rim. And this is where
I saw a difference in the rendered output. Where in Firefox the arc&rsquo;s rim and its legs met perfectly:</p>
<p><img alt="The arc in Firefox is rendered correctly" loading="lazy" src="/blog/2021-05-09-a-debugging-story/arc_ff.png"></p>
<p>in Chrome the rim overshot:</p>
<p><img alt="The same arc in Chrome is rendered wrong" loading="lazy" src="/blog/2021-05-09-a-debugging-story/arc_chrome.png"></p>
<p>So while the calculated parameters were correct and in both cases provided to the <code>arc</code> method just the same, Chrome was rendering the wrong segment length!</p>
<p>I suspected a rounding error and thus started searching for matching reports from other people. I couldn&rsquo;t find a specific bug report, but I came across a post on Stack Overflow that sounded mightily familiar: <a href="https://stackoverflow.com/questions/8603656/html5-canvas-arcs-not-rendering-correctly-in-google-chrome">HTML5 canvas arcs not rendering correctly in Google Chrome</a>, from 2011. A ten year old post&hellip; could it be?</p>
<p>Honestly, I still do not know if this indeed described the same issue or not, or if there&rsquo;s a Chrome ticket describing this behaviour &ndash; I&rsquo;ll continue to look, but first and foremost I was focused on fixing this problem in OctoPrint&rsquo;s GCode viewer. The Stack Overflow post provided a code snippet that reimplements <code>arc</code> utilizing bezier curves, and so I gave this a try. Long story short, OctoPrint&rsquo;s GCode Viewer as part of version 1.7.0+ will ship with a Chrome-only <code>arc</code> replacement that will be enabled by default, but can also be disabled in real time, with great effect:</p>
<p><img alt="Enabling and disabling the arc workaround makes the defects disappear and reappear" loading="lazy" src="/blog/2021-05-09-a-debugging-story/arc_fix.gif"></p>
<p>And the moral of the story: It rarely is a browser bug. But sometimes, all signs say it indeed <em>is</em> and a workaround is the easiest solution.</p>
]]></content:encoded></item><item><title>Homelab uplink monitoring</title><link>https://foosel.net/blog/2021-03-28-homelab-uplink-monitoring/</link><pubDate>Sun, 28 Mar 2021 00:00:00 +0000</pubDate><guid>https://foosel.net/blog/2021-03-28-homelab-uplink-monitoring/</guid><description>Keeping an eye on my ISP&amp;#39;s performance</description><content:encoded><![CDATA[<p>For a bit more than two years now I&rsquo;ve been closely monitoring my network uplink. In the past I had a ton of issues with up- or download speeds not being what I paid for, packet loss issues and outright full blown outages. In order to put myself into a better position when reaching out to the ISP&rsquo;s support hotline I figured it would be good to be able to proof not only the existence of these issues but to also be able to determine the exact times they happened at and also to verify and show that in fact it was only external connections that were suffering and it was not an issue with my own internal network. Given that I don&rsquo;t trust the cable modem/router they force on me to be my edge router and instead have my own Unifi gear set up behind it (considering anything not exclusively under my control to be part of the hostile public internet) this otherwise will usually lead to endless attempts to blame my LAN when in fact the issue lies outside of my reach.</p>
<p>I already had an <a href="https://www.influxdata.com/">InfluxDB</a> and <a href="https://grafana.com/">Grafana</a> setup running anyhow for my <a href="https://home-assistant.io/">Home Assistant instance</a> to dump values from my home climate sensors into, so it was a logical next step to simply add some additional sensors to the mix.</p>
<h2 id="throughput">Throughput</h2>
<p>I currently run a speed test of the network throughput every 20min and log the results via MQTT into InfluxDB. I had to find out that neither the speed test integration in Home Assistant nor the official speedtest-cli tool were performing reliably enough for this &ndash; I was constantly getting dips in measured throughput and thus alerts, even when everything was completely fine with my uplink.</p>
<p>I solved this by turning to <a href="https://github.com/nelsonjchen/speedtest-rs">speedtest-rs</a> and a small shell script that parses the output and pushes it into MQTT to Home Assistant, which then processes it further for some visualization right on my dashboard but also forwards it further into InfluxDB. You can find the <code>Dockerfile</code> and the script plus some further info <a href="https://gist.github.com/foosel/ef98a5774d1a495ab3781eba8a157fee">in this gist</a>.</p>
<p>In Grafana I then use this data to provide me with some single stat panels for the current downstream, upstream and ping values as well as the averages over the selected time range:</p>
<p><img alt="Some single stat panels show current and average down- and upstream speed and measured ping" loading="lazy" src="/blog/2021-03-28-homelab-uplink-monitoring/currentspeed.png"></p>
<p>Additionally, I also plot the down- and upstream speed in a timeline, together with the current bandwidth consumption as extracted by Home Assistant from my ISP&rsquo;s cable modem/router (thanks to the <a href="https://www.home-assistant.io/integrations/fritzbox_netmonitor/">Fritzbox NetMonitor integration</a>). Together, this gives me a good picture of whether there is actually an issue when I see a dip in the measured values, or if it&rsquo;s just too high bandwidth utilization:</p>
<p><img alt="A graph showing measured up- and downstream speed vs consumed up- and downstream bandwidth utilization" loading="lazy" src="/blog/2021-03-28-homelab-uplink-monitoring/bandwidth.png"></p>
<p>You can see in these screenshots that I recently upgraded my plan with my ISP &ndash; from 200/20 to 500/50 MBit. The problem: The speedtest run by my monitoring setup doesn&rsquo;t hit the 500 mark, whereas running a manual test on speedtest.net works just fine. Looking at the <code>speedtest-rs</code> README it becomes apparent that this is a known issue with the legacy (open) Speedtest.net API:</p>
<blockquote>
<p>This tool currently only supports <a href="http://www.ookla.com/support/a84541858">HTTP Legacy Fallback</a> for testing.</p>
<p>High bandwidth connections higher than ~200Mbps may return incorrect results!</p>
<p>The testing operations are different from socket versions of tools connecting to speedtest.net infrastructure. In the many FOSS Go versions, tests are done to find an amount of data that can run for a default of 3 seconds over some TCP connection. In particular, <code>speedtest-cli</code> and <code>speedtest-rs</code> tests with what Ookla calls the <a href="http://www.ookla.com/support/a84541858">&ldquo;HTTP Legacy Fallback&rdquo;</a> for hosts that cannot establish a direct TCP connection.</p>
</blockquote>
<p>I fear I might have to look into reimplementing the current speedtest-to-mqtt setup with another container utilizing the official (and sadly proprietary) Speedtest CLI tool to mitigate this issue. Thankfully, it should be quite easy to build a drop-in replacement thanks to the modularization in effect.</p>
<p><em>Update from March 30th 2021</em> I&rsquo;ve now done that and <a href="https://gist.github.com/foosel/70ecbeade55cc852dbc0a4f7c4040adc">here&rsquo;s an updated gist</a> that works identically to the <code>speedtest-rs</code> approach, but instead utilizes <a href="https://www.speedtest.net/apps/cli">Ookla&rsquo;s official command line tool</a>. The results are stable numbers that reflect the expected bandwidth and also match the web based test results.</p>
<p><em>Update from March 31st, 2021</em> I wasn&rsquo;t too happy with running a proprietary tool for my speed testing, went looking for an OSS alternative, came across <a href="https://librespeed.org/">librespeed</a> and therefore have now <a href="https://gist.github.com/foosel/f7d9a08c0445454ab90d6c4974a9e316">replicated the setup again using that</a>. You might want to experiment a bit to find a server close to you and define that via <code>--server &lt;id&gt;</code>, the auto discovery appears to be a bit wonky. Or just use your own server list via <code>--server-json</code> or <code>--local-json</code>.</p>
<h2 id="latency-and-packet-loss">Latency and packet loss</h2>
<p>In addition to the available up- and downstream speeds, I constantly monitor latency and packet loss to a selected number of hosts both external and internal to my network as well. For this I ping some public DNS servers (Google, Cloudflare and Quadnine) and some of my own vservers for the remote side, and the ISP&rsquo;s Fritzbox, my managed network gear and internal servers for the LAN side. I used to do this via <a href="https://oss.oetiker.ch/smokeping/">Smokeping</a>, but when I set up my InfluxDB/Grafana stack I wanted to find a solution to have everything together in one place.</p>
<p>Thankfully I almost immediately found <a href="https://hveem.no/visualizing-latency-variance-with-grafana">this post by Tor Hveem</a> who solved this with a little custom Go tool to run <code>fping</code> against a number of configurable hosts and push the results right into InfluxDB. This was exactly what I wanted and thus I replicated the outlined setup, albeit with a slightly different color scheme.</p>
<p>I use a <a href="https://github.com/nickvanw/infping">modified version of Tor&rsquo;s <code>infping</code> tool maintained by Nick Van Wiggeren</a> and run that in a Docker container on my NAS. You can find everything needed to run this on your own <a href="https://gist.github.com/foosel/46804306d510d79f14117f95ed64b877">in this gist</a>.</p>
<p>As a result I get ping output for all hosts every 60 sec with times and packet loss information pushed right into InfluxDB. This is easily queried by Grafana and looks quite nice when visualized:</p>
<p><img alt="A graph showing min, avg and max latency and packet loss data for 8.8.8.8" loading="lazy" src="/blog/2021-03-28-homelab-uplink-monitoring/smokeping.png"></p>
<p>And on my network dashboard, I plot only the <code>avg</code> values across all hosts and a mean <code>loss</code> value into one single graph each for external and internal hosts:</p>
<p><img alt="A graph showing avg latency and packet loss data for all remote hosts" loading="lazy" src="/blog/2021-03-28-homelab-uplink-monitoring/latency.png"></p>
<p>This allows me a good overview of the current state of uplink and internal network at one glance.</p>
<h2 id="alerts">Alerts</h2>
<p>Since just graphs won&rsquo;t give me an immediate heads-up when something goes wrong, I have a bunch of alerts set up in Grafana:</p>
<ul>
<li>Measured download speed falls beneath 250MBit for more than one hour</li>
<li>Measured upload speed falls beneath 35MBit for more than one hour</li>
<li>Mean packet loss across all external hosts rises above 25% for more than ten minutes</li>
</ul>
<p>All of those trigger a notification to a private Discord server (via Grafana&rsquo;s own notification mechanism). In theory this notification should even include a screenshot of the panel for which the alert was triggered for, but I&rsquo;m having some problems with that still that I need to investigate.</p>
<p><img alt="An example alert and alert clearance notification in Discord" loading="lazy" src="/blog/2021-03-28-homelab-uplink-monitoring/discord.png"></p>
<p>This notification channel has an obvious problem: When the uplink goes out completely, I won&rsquo;t get the notification if my phone is in my LAN. I really need to add a local alert as well at some point 😅</p>
<p>Still, it usually will give me a heads-up in time for me to reach out to my ISP on short notice and request they start troubleshooting.</p>
<h2 id="conclusion">Conclusion</h2>
<p>This monitoring setup has proven valuable in debugging network performance issues and also getting an early heads-up about current ISP issues. I have successfully used screenshots for proving ongoing issues to my ISP, and also sped up the one or other troubleshooting session when there was in fact an issue with my LAN. In my book, that makes it absolutely worth the time it took me to set this up and maintain it. And: it kinda looks cool 😎</p>
<p>If you want to give this a go yourself, this might be of interest to you:</p>
<ul>
<li><a href="https://gist.github.com/foosel/f7d9a08c0445454ab90d6c4974a9e316">Dockerfile, compose and instructions for speedtest container</a>
<ul>
<li><a href="https://gist.github.com/foosel/70ecbeade55cc852dbc0a4f7c4040adc">Ookla speedtest based version</a></li>
<li><a href="https://gist.github.com/foosel/ef98a5774d1a495ab3781eba8a157fee">speedtest-rs based version</a></li>
</ul>
</li>
<li><a href="https://gist.github.com/foosel/46804306d510d79f14117f95ed64b877">Dockerfile, compose and instructions for infping container</a></li>
<li><a href="https://gist.github.com/foosel/ec0b6355d1d0c3ab65ee4df79d795a73">Panel JSON for the mentioned visualizations</a></li>
</ul>
]]></content:encoded></item><item><title>On wrong assumptions</title><link>https://foosel.net/blog/2021-03-19-on-wrong-assumptions/</link><pubDate>Fri, 19 Mar 2021 00:00:00 +0000</pubDate><guid>https://foosel.net/blog/2021-03-19-on-wrong-assumptions/</guid><description>How I once spent two weeks barking up the wrong tree</description><content:encoded><![CDATA[<p><em>The original version of this post was published as a <a href="https://twitter.com/foosel/status/1242121324438355974">Twitter thread on March 23rd 2020</a>. I figured I should give it a more permanent home here since IMHO it was a quite fun story.</em></p>
<p>Since everyone can use some entertainment right now, how about a battle story on how a year ago I spent almost two weeks trying to wrap my head around a really weird issue of a lagging GCODE viewer and overall print progress reporting in <a href="https://octoprint.org">OctoPrint</a> and finally figuring it out?</p>
<p>Our story begins around the release of 1.4.0, when <a href="https://community.octoprint.org/t/curious-issue-with-print-progress/16304">a new topic on the community forum</a> showed up:</p>
<blockquote>
<h3 id="curious-issue-with-print-progress">Curious issue with print progress</h3>
<p>The print progress figures on my Octopi setup are lagging behind the actual print. [&hellip;] Nothing is broken - anything I throw at it (an Ender 3) prints fine but as a print progresses, the percentage complete, current layer, and sync&rsquo;d gcode viewer gradually lag behind what is actually being printed. For example, on a print with 400 layers, as the last layer is printed the reported progress and current layer is around 96% and 385 respectively. If I do a quick calculation of the displayed Printed/Total file size figures it works out at 96% but what it has actually printed is over 99%. When the print finishes the numbers jump to 100% and 400 and everything is fine.</p>
<p>[&hellip;]</p>
</blockquote>
<p>This was indeed a very curious issue, since due to the nature of the communication with the printer and buffering in the firmware the progress is usually rather slightly <em>ahead</em> than behind. Some quick testing on my end showed no reproduction, however more and more people chimed in with the same observation.</p>
<p>I was stumped.</p>
<p>My first approach was to collect information from those affected by it. Printer model, firmware version, installed plugins, used slicer and so on. It soon turned out that all affected installations were using Ultimaker Cura as the slicer.</p>
<p>A quick test by the OP with a different slicer confirmed that it indeed just occurred with GCODE sliced by Cura for him, same file in another slicer had everything work as designed. However, comparing the GCODE revealed no immediate differences that would explain this, and what actually is <em>in</em> the file also doesn&rsquo;t really play into progress tracking. My own experiments with Cura failed to reproduce.</p>
<p>Convinced that the issue must be some sort of delay between the backend and the frontend &ndash; maybe due to network issues? &ndash; I whipped up a plugin (since deleted) to log progress on both ends to a log which could then be shared and analysed. The first results came in an guess what? I had barked up the wrong tree, the reported progress was identical. So back to square one.</p>
<p>I still couldn&rsquo;t reproduce it on my end and was starting to get really angry at this issue 😅 I finally threw a copy of some GCODE files now shared by the reporter of the issue on my own printer and <em>finally</em> I could reproduce. Which doesn&rsquo;t mean I had any idea WTF was going on though.</p>
<p>After many test prints, head scratching and going through the files with a comb I finally noticed something. The files with the issue had <code>CRLF</code> (or <code>\r\n</code>) line endings. Those without (including my own sliced files) had just <code>LF</code> (or <code>\n</code>) line endings.</p>
<p>So that made me go 🤨 Some cursing and breakpoint setting later I had proof that the reported progress in backend and frontend was flawed to begin with. I could see that a line was being reported with a file position that it actually was not located at in the file, and which instead belonged to a couple lines earlier. Which meant my positions were reported wrong right at the source &ndash; with a lag. And then it suddenly hit me.</p>
<p>But before I can tell you what was happening I need to give you some background on how OctoPrint reads GCODE files it&rsquo;s printing in order to understand what was going on. Printed files are read line by line because that is how they are sent to the printer. For that OctoPrint uses the <a href="https://docs.python.org/3/library/io.html?highlight=readline#io.IOBase.readline"><code>readline</code></a> method of the file stream. And that works by reading chunks of data from the file until a line separator is found, returning everything read up to this separator and saving the rest for the next line to be read. That means the file will have to be read further than what is returned. And that means that the position in the open file as reported by <a href="https://docs.python.org/3/library/io.html?highlight=readline#io.IOBase.tell"><code>tell</code></a> on the file stream will always be slightly ahead. For progress reporting in OctoPrint however I need to know the exact byte position of each line in the file. So what I do instead of relying on the internal and slightly ahead file position is that I increase my own position indicator by the length of the line read from the file. And this is where my problem was located.</p>
<p>It turns out that for some reason I wasn&rsquo;t getting the lines back from <code>readline</code> with the original line endings attached. Instead I always got <code>LF</code>, even for files with <code>CRLF</code>. And that means I was counting one byte short for every single line in <code>CRLF</code> terminated files. One byte short per line doesn&rsquo;t sound like much, but that adds up through a file with several hundred thousands of lines, to a point where progress reporting will be off by whole layers the further in the print and thus the file you are.</p>
<p>But what was the reason for this popping up in 1.4.0? I hadn&rsquo;t modified the code in question at all. It had been the same since 2016 actually. Well, it turns out that a tiny change during the Python 3 compatibility migration done to a helper function I used in that code had interesting side effects: switching from <a href="https://docs.python.org/3/library/codecs.html#codecs.open"><code>codecs.open</code></a> to <a href="https://docs.python.org/3/library/io.html#io.open"><code>io.open</code></a>.</p>
<p>It turns out that <code>io.open</code> (and thus Python 3&rsquo;s built-in <code>open</code>) by default will open text files in &ldquo;universal newlines mode&rdquo; (see <a href="https://www.python.org/dev/peps/pep-0278/">PEP278</a>), meaning it will happily parse every common line ending, but convert it to <code>LF</code> before returning. Which caused my off-by-one issue in files with <code>CRLF</code>.</p>
<p>And the fix? <a href="https://github.com/foosel/OctoPrint/commit/27bbab9582eb3a1a9fca8f2b203e88b1682fcdc5">Setting <code>newline=&quot;&quot;</code> on the open call</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>diff --git a/src/octoprint/util/comm.py b/src/octoprint/util/comm.py
</span></span><span style="display:flex;"><span>index 67191a7af..a6dfc1e24 100644
</span></span><span style="display:flex;"><span><span style="color:#f92672">--- a/src/octoprint/util/comm.py
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+++ b/src/octoprint/util/comm.py
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">@@ -4078,7 +4078,7 @@ def start(self):
</span></span></span><span style="display:flex;"><span> 		&#34;&#34;&#34;
</span></span><span style="display:flex;"><span> 		PrintingFileInformation.start(self)
</span></span><span style="display:flex;"><span> 		with self._handle_mutex:
</span></span><span style="display:flex;"><span><span style="color:#f92672">-			self._handle = bom_aware_open(self._filename, encoding=&#34;utf-8&#34;, errors=&#34;replace&#34;)
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+			self._handle = bom_aware_open(self._filename, encoding=&#34;utf-8&#34;, errors=&#34;replace&#34;, newline=&#34;&#34;)
</span></span></span><span style="display:flex;"><span> 			self._pos = self._handle.tell()
</span></span><span style="display:flex;"><span> 			if self._handle.encoding.endswith(&#34;-sig&#34;):
</span></span><span style="display:flex;"><span> 				# Apparently we found an utf-8 bom in the file.
</span></span></code></pre></div><p>The moral of the story? Don&rsquo;t trust your file position calculations. I could have saved myself a lot of time on debugging this if I had just looked there <em>first</em> instead of assuming this code to be fine 😅</p>
<p>In the end, even a year later, I still have no idea why Cura produced <code>CRLF</code> code for some and <code>LF</code> for me, but I also never really looked hard. A UNIX vs Windows issue can be ruled out here since the affected parties and me were all using Windows. It made me learn something about <code>io.open</code> and was a valuable lesson on wrong assumptions however!</p>
]]></content:encoded></item><item><title>My workplace setup</title><link>https://foosel.net/blog/2021-03-13-my-workplace/</link><pubDate>Sat, 13 Mar 2021 00:00:00 +0000</pubDate><guid>https://foosel.net/blog/2021-03-13-my-workplace/</guid><description>How I try to keep backpain and RSI at bay</description><content:encoded><![CDATA[<p>I&rsquo;ve been working full time from my home office since mid-2014 now. At the time of writing this post this is nearing 7 years. Naturally, considering how much time I spend there, I&rsquo;ve also spent a lot of thought and money on making sure my workplace helps to keep the usual side effects of the mostly sedentary lifestyle of a developer at bay.</p>
<p>Over the years I&rsquo;ve had some run ins with RSI and backpain. My first wrist issues developed more than 10 years ago. Pain in my lower back beyond &ldquo;all fine again after a good night&rsquo;s sleep&rdquo; started in February of 2014. Both have been repeating visitors since then. You can probably imagine that that has led to a lot of research and experimentation to see what works and what doesn&rsquo;t for me. So, here&rsquo;s a summary of my findings as of March 2021. Quick disclaimer though, this is what has proven to work for <strong>me</strong>, that doesn&rsquo;t mean it will work for you, if in doubt please consult a professional. Also, I do link to some products here &ndash; consider those references to give you more details on my setup, not official endorsement or anything like that.</p>
<h2 id="keyboard-and-mouse">Keyboard and mouse</h2>
<p>For my wrists, <strong>ergonomic keyboards</strong> have proven to be crucial in combating the dreaded pain and numbness. I started with a <a href="https://www.microsoft.com/en-us/p/natural-ergonomic-keyboard-4000/">Microsoft Natural Ergonomic Keyboard 4000</a> (what a name&hellip;), switched over to an <a href="https://www.microsoft.com/en-us/p/microsoft-sculpt-ergonomic-desktop/">Microsoft Ergo Sculpt</a>, had a quick detour over a regularly shaped <a href="https://www.duckychannel.com.tw/en/Ducky-One-RGB-TKL">Ducky One TKL</a> to get my feet wet in mechanical keyboards and these days have arrived at the 1st gen <a href="https://ultimatehackingkeyboard.com/">Ultimate Hacking Keyboard</a>. It&rsquo;s a mechanical split keybord, sized at 60% (which means it has less keys than your common 101-key keyboard, only 60% of them to be precise, and compensates for that with the use of layers reached through modified keys) and fully programmable. I&rsquo;m still optimizing the macros I have configured on it. I got it with red switches (linear and non clicky, I can&rsquo;t stand keyboards I can&rsquo;t use while holding a conversation ;)) and put a Git-themed keyset on it which I absolutely adore. And it&rsquo;s finally made me switch to US ANSI layout, which indeed is way better suited for coding than ISO DE. The UHK also supports some additional modules, and I have a trackpoint and an additional thumb keycluster on order once they finally release.</p>
<p><img alt="Closeup of my Ultimate Hacking Keyboard" loading="lazy" src="/blog/2021-03-13-my-workplace/keyboard.jpg"></p>
<p>No matter how good you can memorize keyboard shortcuts (or how well the mouse layer of the UHK works), you still also need a <strong>mouse</strong>. In my case that&rsquo;s been gaming mice exclusively for 15+ years now, with a ton of turnover due to wear out or quality issues. Currently I&rsquo;m sporting a <a href="https://steelseries.com/gaming-mice/rival-310">Steelseries Rival 310</a> after my last mouse, a Roccat Kone XTD, developed a flaky mouse wheel I couldn&rsquo;t fix, even though I tried my best. Apparently a design flaw. The Steelseries has so far worked nicely, but I&rsquo;ve only had it for less than six months at the time of writing this. It&rsquo;s an asymmetric mouse tailored for use with the right hand. I can reach the side buttons easily and it isn&rsquo;t too heavy or too light either.</p>
<p>I also have a <strong>trackball</strong> though, dedicated to be used with my <em>left</em> hand. Whenever I notice my right wrist acting up, I switch to exclusive trackball use for a while, and that has managed to still avert Bad Things a number of times now. If you find yourself regularly suffering from RSI issues on your mouse hand, I can really recommend to mix things up with a trackball on your left. Personally I got a <a href="https://www.kensington.com/p/products/electronic-control-solutions/trackball-products/slimblade-trackball/">Kensington Slimblade</a>. It&rsquo;s symmetrical and the buttons are easily remapped to fit a left hand operation. And the huge ball can also be used as a scrollwheel and is actually also a great fidget toy to have on the desk at all times ;)</p>
<h2 id="desk-and-chair">Desk and chair</h2>
<p>In my opinion, there are two pieces of furniture you should never cheap out on: your <strong>office chair</strong> and your bed. I bought my <a href="https://www.sedus.com/en/products/chairs/netwin">sedus netwin</a> office chair right after finishing university in 2007 and apart from having gotten a bit more dirty here and there it&rsquo;s as good as new. I initially got it with a set of arm rests, but quickly figured out that those were actually detrimental to my posture and made me pull my shoulders up, leading to tension related pain. So I removed them. I&rsquo;ve also gotten it a bit more pronounced lumbar support through the aid of <a href="https://www.amazon.de/gp/product/B07PB7G3QJ/">an add-on</a>, the likes of which you can order online for less than 10€ a piece. If I were to buy a new chair today, I&rsquo;d probably get one with a head rest to keep me a bit more from slouching during long debugging or gaming sessions, but all in all I&rsquo;m still completely happy with it, and the mesh back has proven to make sitting on it on hot summer days more bearable.</p>
<p><img alt="My office chair, with a lumbar support addon" loading="lazy" src="/blog/2021-03-13-my-workplace/chair.jpg"></p>
<p>I&rsquo;ve now had an <strong>electric standing desk</strong> since January 2016. I&rsquo;m currently trying to get into the habit of daily use of the standing mode again and so far it&rsquo;s looking good. I got the <a href="https://www.ikea.com/de/de/p/bekant-schreibtisch-sitz-steh-weiss-s69022537/">IKEA Bekant</a> desk, and have since modified it to use the alternative <a href="https://github.com/gcormier/megadesk">Megadesk</a> controller to give it position memory slots (and hopefully also to hook it up into my home automation system long term). I can&rsquo;t stress enough what a difference it can make to just stand for a while during your work day. Exclusively standing should definitely also be avoided (I&rsquo;ve had to do this recently for a couple of days because my back would <em>not</em> take sitting for an answer anymore, and it did a bit of a number on my ankles and knees), but regularly switching between sitting and standing is a great way not only to keep your posture intact but also to just get back your concentration. I currently work sitting until my lunch break, then switch to a couple hours of standing before either returning to sitting or calling it a day.</p>
<p>Speaking of sitting and standing &ndash; I also recently acquired a bunch of accessories to make that more dynamic. While standing, I now regularly plant my feet on a <strong>balance board</strong> (a <a href="https://www.my-gymba.de/en">Gymba</a> one in my case). It allows me to move while standing, to vary my stance more easily and frankly, it&rsquo;s also a ton of fun to seesaw back and forth while working. I use it with shoes - you can also use it barefoot/in socks, but frankly that was a bit to tough a surface or my feet. Your mileage may vary of course.</p>
<p>I also got myself a <strong>wobble stool</strong> from <a href="https://www.flexispot.com/height-adjustable-wobble-stool-bh1b">Flexispot</a>. Imagine a stool, but instead of being stable it has a rounded base that makes it constantly wobble around. You cannot easily slouch on that, you&rsquo;ll fall over. Mine can be height adjusted from 61 to 82cm, so I use it both while standing as a small break, but also while sitting. I&rsquo;m still getting used to it and am experimenting with heights and best way to sit on it, but it&rsquo;s so far been a great addition and doesn&rsquo;t take up much space (a serious plus in my limited office space).</p>
<h2 id="monitor-mounts">Monitor mounts</h2>
<p>Last but not least, I&rsquo;ve got my two main monitors mounted on a <strong>dual monitor mount</strong>, in my case a <a href="http://www.puremounts.de/pm-office-dm-23d.html">gas lift one from PureMounts</a> (the small third monitor is mounted to the second one with a self designed printed mounting solution). The stands usually included with monitors tend to not offer enough flexibility to truly dial in the position of the screen in my experience, and this also managed to free up a <em>ton</em> of desktop real estate that I can now utilize. In my case, a wallmount is not an option due to the standing desk situation, so I instead went for a desk mount. A gas lift is not the most stable option in my experience: things can be a bit shaky when I accidentally bump against the desk due to the rather extreme lever position I had to chose to make things work in my office, but it has been working just fine now since 2012. Still, at some point I might get something a bit more static. In any case, a monitor mount is something I&rsquo;d highly recommended for everyone really, even if you don&rsquo;t want if for ergonomic reasons &ndash; I cannot emphasize the increase in desk space enough ;)</p>
<p><img alt="My monitors, mounted on a gas lift mount" loading="lazy" src="/blog/2021-03-13-my-workplace/monitors.jpg"></p>
<h2 id="tldr">tl;dr</h2>
<p>Get a split ergo keyboard, a gaming mouse for your primary and a trackball for your off hand. Don&rsquo;t cheap out on your office chair, seriously consider investment in a standing desk, get a balance board with it and finally reclaim your desk and improve your workplace&rsquo;s ergonomy at the same time with a monitor mount.</p>
]]></content:encoded></item><item><title>Meta: Hello World!</title><link>https://foosel.net/blog/2021-03-12-hello-world/</link><pubDate>Fri, 12 Mar 2021 00:00:00 +0000</pubDate><guid>https://foosel.net/blog/2021-03-12-hello-world/</guid><description>The long overdue foosel.net reboot</description><content:encoded><![CDATA[<p>It&rsquo;s been a couple years since I last tried to maintain a blog. Back then I was still living life as a corporate drone,
employed as a Software Architect to consult other people on their IT problems. I rarely had anything I could blog about
&ndash; either things were under NDA, or they were simply uninteresting. Since then my life has been turned completely on its head.
In late 2012 I got myself a 3d printer, spent my Christmas break to develop a small web interface for it, that grew into a full
sized Open Source project called <a href="https://octoprint.org">OctoPrint</a> and these days I work full time on it.</p>
<p>I&rsquo;ve learned a lot not only on 3d printers &amp; Python, but also on Open Source development, crowdfunding, the challenges of
community management, but also on work life balance, workplace ergonomics, home office life and stress management. And that&rsquo;s
just from my job! In my personal life I&rsquo;ve also spent a lot of time tinkering with electronics, learned how to bake bread,
discovered cooking and went completely down the home automation rabbit hole.</p>
<p>I don&rsquo;t know about you, but I think that should hopefully make for some good opportunities to blog again, and save some of
my learnings in a more persistent way than the ephemeral nature of twitter threads. So I did what apparently every dev seems
to do in such a case and spent way too much time on a webpage reboot with some new tooling that I wanted to try anyway,
and this is the result. This whole page is still a static page, but I&rsquo;ve switched it from <a href="https://jekyllrb.com">Jekyll</a>
to <a href="https://nextjs.org/">next.js</a>. Why? I wanted to get some more hands-on experience with React since I&rsquo;m evaluating it for a
new UI for OctoPrint, and I also never really warmed up to Ruby but know JS, so with expandability in mind this just feels like a
better fit. If you want to study the source, you can find that <a href="https://github.com/foosel/foosel.github.io">here</a> (but please
don&rsquo;t look too closely, I&rsquo;m still learning and things are probably not even remotely optimally implemented).</p>
<p>So here we are, and it&rsquo;s time to fill this up a bit more. Thankfully I already got some ideas&hellip;</p>
]]></content:encoded></item></channel></rss>