<?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>bash on foosel.net</title><link>https://foosel.net/tags/bash/</link><description>Recent content in bash on foosel.net</description><generator>Hugo</generator><language>en-us</language><copyright>Gina Häußge (foosel)</copyright><lastBuildDate>Tue, 25 Mar 2025 00:00:00 +0000</lastBuildDate><atom:link href="https://foosel.net/tags/bash/feed.xml" rel="self" type="application/rss+xml"/><item><title>TIL: How to automatically sync screenshots from the Steamdeck to Immich</title><link>https://foosel.net/til/2025-03-25-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/2025-03-25-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="https://foosel.net/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="https://foosel.net/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>TIL: How to make transparent GIFs more easily sharable by adding a checkerboard background</title><link>https://foosel.net/til/2024-10-15-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/2024-10-15-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" style="display: inline-block">
                    <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"
                            style="width: 30px; height: 30px; display: inline-block; border-radius: 30%; vertical-align: middle"
                        />
                    </a>
                    <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>
                <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>TIL: How to check for cloud IPs in nginx</title><link>https://foosel.net/til/2024-07-01-how-to-check-for-cloud-ips-in-nginx/</link><pubDate>Mon, 01 Jul 2024 00:00:00 +0000</pubDate><guid>https://foosel.net/til/2024-07-01-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>TIL: How to print Deutsche Post stamps via the command line on a Brother QL label printer</title><link>https://foosel.net/til/2024-01-11-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/2024-01-11-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 src="https://foosel.net/til/2024-01-11-how-to-print-deutsche-post-stamps-via-the-command-line-on-a-brother-ql-label-printer/result.jpg" alt="Printed stamp and printed stamp with address label, freshly printed from example files through the two scripts" loading="lazy">
</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>TIL: How to automatically sync screenshots from the Steamdeck to Google Photos</title><link>https://foosel.net/til/2023-02-19-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/2023-02-19-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="https://foosel.net/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="https://foosel.net/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>TIL: How to add a switch for a port forward on Unifi to Home Assistant</title><link>https://foosel.net/til/2023-02-17-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/2023-02-17-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>TIL: How to automatically sync screenshots from the Steamdeck to Google Drive</title><link>https://foosel.net/til/2023-02-11-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/2023-02-11-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 src="https://foosel.net/til/2023-02-11-how-to-automatically-sync-screenshots-from-the-steamdeck-to-google-drive/screenshot.png" alt="A freshly synced screenshot of my Steamdeck&rsquo;s home screen" loading="lazy">
</p>
<p>Success!</p>
<p><em>Update 2023-02-19</em>: There is now also a <a href="https://foosel.net/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="https://foosel.net/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>TIL: How to use jq to extract new posts from a JSON Feed</title><link>https://foosel.net/til/2023-02-02-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/2023-02-02-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="https://foosel.net/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>TIL: How to detect Termux in a script</title><link>https://foosel.net/til/2023-01-23-how-to-detect-termux-in-a-script/</link><pubDate>Mon, 23 Jan 2023 00:00:00 +0000</pubDate><guid>https://foosel.net/til/2023-01-23-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>TIL: How to determine an RPi kernel version and build without booting it</title><link>https://foosel.net/til/2022-06-16-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/2022-06-16-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></channel></rss>