<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>Posts on SergeantBiggs Blog</title><link>https://blog.sergeantbiggs.net/posts/</link><description>Recent content in Posts on SergeantBiggs Blog</description><generator>Hugo -- 0.152.2</generator><language>en-us</language><copyright>CC-BY 4.0</copyright><lastBuildDate>Sun, 31 Aug 2025 14:25:32 +0200</lastBuildDate><atom:link href="https://blog.sergeantbiggs.net/posts/index.xml" rel="self" type="application/rss+xml"/><item><title>EPC Codes: Paying via QR</title><link>https://blog.sergeantbiggs.net/posts/epc-codes/</link><pubDate>Sun, 31 Aug 2025 14:25:32 +0200</pubDate><author>SergeantBiggs</author><guid>https://blog.sergeantbiggs.net/posts/epc-codes/</guid><description>&amp;lt;no value&amp;gt;</description><content type="text/html" mode="escaped"><![CDATA[<p>I recently learned about a new method of transferring money through SEPA: <a href="https://en.wikipedia.org/wiki/EPC_QR_code">EPC
Codes</a>.</p>
<p>I encountered them when I got a bill with a QR code on it that I could scan
with my banking app. When I did it, it autopopulated all of the necessary
fields (recipient, IBAN, amount, etc). I wondered how it all works and it
turns out being much simpler than I thought.</p>
<p>I initially thought it must be some kind of URL that causes an API request to
be made to populate the necessary data. But when I scanned the code with a
regular QR scanner I noticed that it&rsquo;s a simple text format:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">BCD
</span></span><span class="line"><span class="cl">002
</span></span><span class="line"><span class="cl">1
</span></span><span class="line"><span class="cl">SCT
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Alice
</span></span><span class="line"><span class="cl">DE89370400440532013000
</span></span><span class="line"><span class="cl">EUR5
</span></span></code></pre></div><p>The lines mean the following and are quite self-explanatory:</p>
<ol>
<li>Service Tag (always <code>BCD</code>)</li>
<li>Version (001 or 002)</li>
<li>Character encoding (1=UTF-8)</li>
<li>&ldquo;Identification&rdquo; (always <code>SCT</code>)</li>
<li>BIC of the recipient (optional in version 2 if inside the
<a href="https://en.wikipedia.org/wiki/European_Economic_Area">EEA</a>)</li>
<li>Name of recipient</li>
<li>IBAN of recipient</li>
<li>Payment amount (optional)</li>
<li>Purpose (optional)</li>
<li>Reference (optional)</li>
<li>Reason for transfer (optional)</li>
<li>additional (optional) (note to the user of max. 70 characters)</li>
</ol>
<p>All optional fields can be either left empty or completely omitted if they are
not followed by other non-empty fields.</p>
<p>All in all, it&rsquo;s a pretty simple and robust system. It&rsquo;s mostly handy for
organisations sending out bills. QR codes can be autogenerated and scanning one
is simpler and less error-prone than entering the information manually. It can
also be used by organisations soliciting donations.</p>
<p>It (currently) isn&rsquo;t ideal for quickly sending money between friends, because
there&rsquo;s no &ldquo;user-friendly&rdquo; way of generating these codes. Theoretically mobile
banking apps could generate them pretty easily, since they already have all of
the information. The user would just have to optionally enter an amount.
Combined with <a href="https://www.europeanpaymentscouncil.eu/what-we-do/sepa-instant-credit-transfer">SEPA instant
transfer</a>
(which all Eurozone payment providers now have to support), it would be a
pretty simple, decentralised, and privacy-preserving way of sending money
between friends. I&rsquo;m not aware of any banking application that uses this
scheme, but it would be quite easy to implement and a nice alternative to
centralised payment providers (e.g. Paypal).</p>
]]></content></item><item><title>Aerc: a Well-Crafted Tui for Email</title><link>https://blog.sergeantbiggs.net/posts/aerc-a-well-crafted-tui-for-email/</link><pubDate>Tue, 26 Mar 2024 21:05:19 +0100</pubDate><author>SergeantBiggs</author><guid>https://blog.sergeantbiggs.net/posts/aerc-a-well-crafted-tui-for-email/</guid><description>&amp;lt;no value&amp;gt;</description><content type="text/html" mode="escaped"><![CDATA[<p><a href="https://aerc-mail.org/">Aerc</a> is a TUI email client.  It had its first release
~4 years ago. This makes it a &ldquo;baby&rdquo; compared to most of its &ldquo;competitors&rdquo; (Pine
was released in 1992, Mutt in 1995). I heard about this program shortly after
its first release but ignored it at the time, because I was still reasonably
happy with Thunderbird and it seemed quite bare-bones in comparison.
I recently decided to revisit this piece of software.  It seemed to have reached a
certain level of maturity and had enough features that I wanted to use
it as a daily driver.</p>
<p>Full disclosure: I tried to use neomutt as an email client multiple times but
got frustrated and gave up quite quickly. I was prepared for the eventuality
that I would have the same experience with aerc, so I approached it with almost
zero expectations. I&rsquo;m glad to say that not only were my expectations more than
met, I can tentatively say that I&rsquo;ve enjoyed using this client more than any
other client I&rsquo;ve ever used. Whether it is <em>better</em> than any other email client
remains to be seen.</p>
<p>This article will contain a quick rundown of the different features of the
client and how I interacted with them. It tries to highlight how I personally
experienced and used the program, so it will be highly subjective.</p>
<h2 id="documentation">Documentation<a href="#documentation" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<p>The program has an inbuilt help feature that can be accessed via the command
<code>:help</code>. The tutorial is a good place to start and succinctly describes the
most important features and keybinds to get set up quickly. The other help
pages describe different parts of the application and are written clearly and
concisely. It makes it possible to use the client and all of its features
without having to rely on any external source. A handy feature is that all
of the help articles are just man pages that are piped to less. This means
they are also installed as plain man pages
(eg. <code>aerc-tutorial (7)</code>). This is a really nice way of shipping a &ldquo;portable&rdquo;
help system inside your TUI applications! I hope other applications/projects start
using this approach.</p>
<h2 id="account-configuration">Account Configuration<a href="#account-configuration" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<p>Accounts are relatively easy to add, thanks to a simple and intuitive wizard.
This wizard creates a new entry in the accounts config file
(<code>~/.config/aerc/accounts.conf</code>). This file uses a simple INI syntax, so it is
really easy to read and modify. For example, this is my config section for a
private account:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[Sergeant Personal]</span>
</span></span><span class="line"><span class="cl"><span class="na">source</span>        <span class="o">=</span> <span class="s">imaps://user:password@mail.example.com:993</span>
</span></span><span class="line"><span class="cl"><span class="na">outgoing</span>      <span class="o">=</span> <span class="s">smtps://user:password@mail.example.com:465</span>
</span></span><span class="line"><span class="cl"><span class="na">default</span>       <span class="o">=</span> <span class="s">INBOX</span>
</span></span><span class="line"><span class="cl"><span class="na">from</span>          <span class="o">=</span> <span class="s">&#34;User&#34; &lt;user@example.com&gt;</span>
</span></span><span class="line"><span class="cl"><span class="na">copy-to</span>       <span class="o">=</span> <span class="s">Sent</span>
</span></span><span class="line"><span class="cl"><span class="na">cache-headers</span> <span class="o">=</span> <span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">folders-sort</span>  <span class="o">=</span> <span class="s">INBOX, Drafts, Sent, spambucket, Active, Waiting, Done</span>
</span></span><span class="line"><span class="cl"><span class="na">archive</span>       <span class="o">=</span> <span class="s">Archives</span>
</span></span><span class="line"><span class="cl"><span class="na">check-mail</span>    <span class="o">=</span> <span class="s">1m</span>
</span></span><span class="line"><span class="cl"><span class="na">folder-map</span>    <span class="o">=</span> <span class="s">/home/user/.config/aerc/folder-maps/foo.map</span>
</span></span></code></pre></div><p>Most of these options are self-explanatory, but there are a few that I want to
draw special attention to. These are the options for folder management
(<code>folders-sort</code> and <code>folder-map</code>)</p>
<h3 id="folders-sort">folders-sort<a href="#folders-sort" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h3>
<p>This option is really simple: It allows us to override automatic sorting
for certain folders. The specified folders are shown at the top of
the folder list in the provided order. The rest are sorted
alphabetically. This option should be in every email client. Sadly, I&rsquo;ve never
seen it in a graphical client (Thunderbird/Outlook) before.</p>
<h3 id="folder-map">folder-map<a href="#folder-map" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h3>
<p>This is an amazing feature that solves a really annoying problem:
different email providers have different folder structures. For example, Gmail puts
everything in a folder named [Gmail], some providers put &lsquo;Sent&rsquo; and &lsquo;Drafts&rsquo;
under &lsquo;INBOX&rsquo;, others don&rsquo;t, etc. Since this folder structure is provided by
the server, there is no way to change it (unless you administer the email
server). Aerc has a really nice solution for this problem.
It consists of a
file with mapping rules, that maps server folders to displayed folder names.
This makes for a flexible remapping system that works on single and multiple folders,
allows removing prefixes from folders and much more. The
examples from the man page show this feature quite well.</p>
<pre tabindex="0"><code>Remap a single folder:
Spam = [Gmail]/Spam

Remap the folder and all of its subfolders:
G = [Gmail]*

Remove a prefix for all subfolders:
* = [Gmail]/*

Remap all subfolders and avoid a folder collision:
Archive/existing = Archive*
Archive = OldArchive*
</code></pre><p>It&rsquo;s hard to overstate how much I love this feature. It allows me to have a
consistent folder structure that I can interact with in exactly the same way
for each and every one of my accounts (regardless of the folder structure on
the server). Consistent folder structures are especially important when you manage emails via a CLI
instead of a GUI.</p>
<h2 id="email-viewing">Email Viewing<a href="#email-viewing" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<p>My email viewing experience has been generally mediocre. This has nothing
to do with aerc itself, but rather with HTML emails. I suspected that this
would be non-ideal in a TUI client and that fact has been proven correct.
In most multipart emails the plain text version is
much less readable than the HTML version. It&rsquo;s possible to grasp the meaning
most of the time but sometimes it is just a garbled mess.
Aerc has a few features to make this experience less bad. One option is
viewing them through a HTML parser/browser (a combination of
<a href="https://w3m.sourceforge.net/">w3m</a> and <a href="https://www.inet.no/dante/">dante</a>.
This works well for some emails. The generated output is sometimes better than
the plain text version of the email. As a last resort I just use <code>:open</code> to open
them in my default web browser.
The way aerc does this is actually quite nice: it uses custom filters. These
are *nix pipelines that allow the message to be piped through the
filter before being displayed. It has default filters for plain text
(colorising rules), calendar entries and HTML emails. These filters can be set
on mimetypes but can also be matched with specific headers. The configuration
contains a few examples and shows the flexibility and power of this feature:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="na">text/plain</span><span class="o">=</span><span class="s">colorize</span>
</span></span><span class="line"><span class="cl"><span class="na">text/calendar</span><span class="o">=</span><span class="s">calendar</span>
</span></span><span class="line"><span class="cl"><span class="na">message/delivery-status</span><span class="o">=</span><span class="s">colorize</span>
</span></span><span class="line"><span class="cl"><span class="na">message/rfc822</span><span class="o">=</span><span class="s">colorize</span>
</span></span><span class="line"><span class="cl"><span class="c1">#text/html=pandoc -f html -t plain | colorize</span>
</span></span><span class="line"><span class="cl"><span class="na">text/html</span><span class="o">=</span><span class="s">html | colorize</span>
</span></span><span class="line"><span class="cl"><span class="c1">#text/*=bat -fP --file-name=&#34;$AERC_FILENAME&#34;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#application/x-sh=bat -fP -l sh</span>
</span></span><span class="line"><span class="cl"><span class="c1">#image/*=catimg -w $(tput cols) -</span>
</span></span><span class="line"><span class="cl"><span class="c1">#subject,~Git(hub|lab)=lolcat -f</span>
</span></span><span class="line"><span class="cl"><span class="c1">#from,thatguywhodoesnothardwraphismessages=wrap -w 100 | colorize</span>
</span></span></code></pre></div><h2 id="email-authoring">Email Authoring<a href="#email-authoring" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<p>Email authoring is an absolute pleasure. I really like the fact I can now
compose my emails in vim.</p>
<h2 id="address-book">Address Book<a href="#address-book" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<p>The address book was something I struggled with quite a bit.
Like many things, theoretically this is handled quite elegantly in
aerc: it defers address book organisation to an external program. This is done
by allowing the user to specify a search query to the address book
software in question. The results of that query are then used for autocomplete
in relevant header fields. The default program aerc uses for this is
<a href="https://github.com/hhirsch/abook">abook</a>. My problems with this
software were twofold: first (at least in my tests) it did not seem to support
names with non-ascii characters. Since I frequently converse with German
people, this made this software useless to me. Also, there wasn&rsquo;t an easy way
to get new addresses from the email program to abook, since it can not parse a
complete email and take out all of the addresses. This might have been solvable
with custom filters, but I didn&rsquo;t want to put much time into this since it
didn&rsquo;t work with non-ascii characters anyway. The second piece of software I
tried was <a href="https://sr.ht/~renerocksai/aercbook/">aercbook</a>. This had the same
problem with non-ascii characters as abook, but it does support parsing all
header fields for email addresses. I then found a third option, called
<a href="https://git.sr.ht/~maxgyver83/emailbook-janet">emailbook-janet</a>. This is a
rewrite of aercbook that is unfortunately not as performant, but it solves the
ascii problem. Using keybinds, one can even select emails, parse them and
automatically add all addresses to the address book:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="na">aa</span> <span class="o">=</span> <span class="s">:pipe -m emailbook.janet /home/biggs/.config/aerc/address_book --parse --all&lt;Enter&gt;</span>
</span></span></code></pre></div><p>One limitation of this software (and aercbook) is that they are
not really &ldquo;address books&rdquo;. They simply save the name and email of contacts, to
make autocomplete work. This is all I need at the moment, so that
works out. If I ever need a more powerful solution, I might switch over to
<a href="https://github.com/lucc/khard">khard</a> or some similar solution. Who knows, I
might even set up an LDAP Server :)</p>
<h2 id="pgp-support">PGP Support<a href="#pgp-support" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<p>One big problem I&rsquo;ve had with Thunderbird is its strange PGP/GPG support.
The biggest annoyance is its separate keychain. This makes using Thunderbird
with something like a smartcard finicky at best and impossible at worst (at
least, I&rsquo;ve never got it to work properly). Aerc also has its own keychain, but
it is entirely optional. The default mode is to just use the GPG keychain.
You can set a specific key ID to sign messages, or just let aerc look up the
key by email. There are options for automatically signing all emails and to use
opportunistic encryption. All in all, as long as you have a working GPG setup,
this works flawlessly.</p>
<h2 id="general-configuration">General Configuration<a href="#general-configuration" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<p>Here are some general features that did not really fit into a separate
category, but that I wanted to highlight nonetheless.</p>
<p>The first is that aerc supports signatures. Hardly a surprising or
groundbreaking feature for an email client, but nice nonetheless. Signatures
are just text files that can be included for each account with
<code>signature-file=</code>. Alternatively, aerc can also execute an external command to
create a signature.</p>
<p>Aerc also supports email templates. I haven&rsquo;t looked at those in detail yet,
but it seems like a nice feature.</p>
<p>It also tries to intelligently remove repetitions of &lsquo;Re:&rsquo; prefixes in email
headers (in multiple languages). To accomplish this, it uses a regex that can
be optionally overwritten or extended.</p>
<p>Two other nice (but kinda default) features of aerc are its forget subject and
attachment reminder. The first just checks if the subject is empty, but the
second one is actually quite clever, and very easily extensible: it&rsquo;s just a
regex! Specifically, this one:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-re" data-lang="re"><span class="line"><span class="cl"><span class="o">^</span><span class="ow">[</span><span class="o">^&gt;</span><span class="ow">]*</span><span class="n">attach</span><span class="ow">(</span><span class="n">ed</span><span class="ow">|</span><span class="n">ment</span><span class="ow">)</span>
</span></span></code></pre></div><p>This does several smart things, one of them is filtering the line out if it
begins with a &lsquo;&gt;&rsquo; (which would mean that someone else mentioned an attachment,
not you). Because I also write a lot of emails in German, I decided to extend
this regex so it also works in German (NB: the regex is case-insensitive and
multi-line by default):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-re" data-lang="re"><span class="line"><span class="cl"><span class="o">^</span><span class="ow">[</span><span class="o">^&gt;</span><span class="ow">]*(</span><span class="n">attach</span><span class="ow">(</span><span class="n">ed</span><span class="ow">|</span><span class="n">ment</span><span class="ow">))|</span><span class="n">an</span><span class="ow">(</span><span class="n">ge</span><span class="ow">)?</span><span class="n">h</span><span class="ow">[</span><span class="n">aä</span><span class="ow">]</span><span class="n">ngt</span><span class="ow">?</span>
</span></span></code></pre></div><p>All in all, I&rsquo;ve really enjoyed using aerc over the last few days. It seems
really polished and it is a pleasure to use. I think I&rsquo;ll
stick with this email client for a while longer and see how it goes.</p>
]]></content></item><item><title>Notification Management; or, Trying to Live Without Distractions</title><link>https://blog.sergeantbiggs.net/posts/notifications/</link><pubDate>Mon, 25 Mar 2024 18:54:11 +0100</pubDate><author>SergeantBiggs</author><guid>https://blog.sergeantbiggs.net/posts/notifications/</guid><description>&amp;lt;no value&amp;gt;</description><content type="text/html" mode="escaped"><![CDATA[<p>The way most operating systems deliver notifications by default has always
bothered me. Notifications are often distracting, since they pop up suddenly on
the screen and demand you do something <em>right now</em>. They also don&rsquo;t go away
until you click on them, but after that they are gone forever. This frequently
resulted in me being distracted by the notification, distractingly clicking it
away, resulting in neither knowing what I did before nor what the notification
that I clicked away was about. Even worse than that is missing notifications
because they pop up on the &ldquo;wrong&rdquo; screen.</p>
<p>For me notifications should ideally have the following properties:</p>
<ul>
<li>Stay somewhere until they are manually dismissed (even after a reboot)</li>
<li>Not be on the screen all the time</li>
<li>Be sorted in reverse chronological order</li>
<li>Be easily accessible (via a shortcut or the GUI)</li>
</ul>
<p>Some OSes get this down quite well. One that comes to mind is Windows 10/11.
The one that does this the best (in my opinion) is Android (looking at you,
Apple. Whats up with the weird notification pop-ups???). The current state on
Linux is a far cry from that reality.</p>
<p>When I found about <a href="https://github.com/DaveDavenport/Rofication">Rofication</a> I
thought this tool was written for me personally.  The tool is quite bare-bones,
but it brings everything that I need to make a functional &ldquo;notification center&rdquo;
in Sway.</p>
<h1 id="my-setup">My Setup<a href="#my-setup" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h1>
<p>Rofication consists of 2 parts, the daemon and an example GUI client. After
installing it, using it in sway is just the matter of autostarting it and
adding an appropriate keybind:</p>
<pre tabindex="0"><code class="language-conf" data-lang="conf">exec rofication-daemon.py
bindsym $mod+n exec rofication-gui.py
</code></pre><p>After that we are ready to receive and view our notifications. They are
persisted across reboots and can be interacted with via different key bindings
(eg. Alt+s to dismiss).</p>
<p>This gets us up and running, but are missing one more component: a widget that
shows us how many notifications we have. Ideally, it should also change color
if we have notifications, so that we can ascertain this at a glance.</p>
<p>One part of this is already provided by the original author, in the form of
<code>rofication-statusi3blocks.py</code>. This simply prints out the number of
notifications and could be easily integrated into a widget. To make our widget
change color depending on the number of notifications, we need to extend this a
bit. The bar I&rsquo;m using (<a href="https://github.com/Alexays/Waybar/wiki/Module:-Custom#return-type">Waybar</a>),
expects a simple JSON object in the following form:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;text&#34;</span><span class="p">:</span> <span class="s2">&#34;$text&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;alt&#34;</span><span class="p">:</span> <span class="s2">&#34;$alt&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;tooltip&#34;</span><span class="p">:</span> <span class="s2">&#34;$tooltip&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;class&#34;</span><span class="p">:</span> <span class="s2">&#34;$class&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;percentage&#34;</span><span class="p">:</span> <span class="s2">&#34;$percentage&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>NB: Waybar expects this to be in a single line, I just formatted it this way
for demonstration purposes.</p>
<p>To make this work, we pass the name of a CSS class, and change the waybar config to make
that class use a specific colour. Using <code>jq</code>, it is quite simple to &ldquo;extend&rdquo;
the original script to output JSON instead:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="cp">#!/usr/bin/env sh
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl">rofication-statusi3blocks.py <span class="p">|</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    jq --unbuffered <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    --compact-output <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    <span class="s1">&#39;{&#34;text&#34;: .,&#34;alt&#34;: ., &#34;tooltip&#34;: &#34;notifications&#34;, &#34;class&#34;: (if . &gt; 0 then &#34;multiple&#34; else &#34;none&#34; end)}&#39;</span>
</span></span></code></pre></div><p>After that, we can add a new module to our waybar-config:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;custom/rofication&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;format&#34;</span><span class="p">:</span> <span class="s2">&#34;{} &#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;exec&#34;</span><span class="p">:</span> <span class="s2">&#34;notif&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;interval&#34;</span><span class="p">:</span> <span class="mi">5</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;return-type&#34;</span><span class="p">:</span> <span class="s2">&#34;json&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;on-click&#34;</span><span class="p">:</span> <span class="s2">&#34;rofication-gui.py&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Add the appropriate CSS code as well:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-css" data-lang="css"><span class="line"><span class="cl"><span class="p">#</span><span class="nn">custom-rofication</span><span class="p">.</span><span class="nc">multiple</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">background-color</span><span class="p">:</span> <span class="mh">#eb4d4b</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>After that we should have a working setup!</p>
<p>I&rsquo;ve been using this setup for quite a while now and I&rsquo;m really happy with it.
It is pretty much an ideal notification setup for me. The only thing I&rsquo;m
missing is a timestamp for the specific notifications. Maybe this is possible,
but I have not found an option to enable this yet.</p>
<p>Hope you enjoyed reading this article!</p>
]]></content></item><item><title>Project Zomboid; or, Taming Misbehaving Services With systemd</title><link>https://blog.sergeantbiggs.net/posts/project-zomboid-or-taming-misbehaving-services-with-systemd/</link><pubDate>Fri, 03 Nov 2023 11:29:09 +0100</pubDate><author>SergeantBiggs</author><guid>https://blog.sergeantbiggs.net/posts/project-zomboid-or-taming-misbehaving-services-with-systemd/</guid><description>&amp;lt;no value&amp;gt;</description><content type="text/html" mode="escaped"><![CDATA[<p>My friends and I recently discovered <a href="https://store.steampowered.com/app/108600/Project_Zomboid/">Project
Zomboid</a>. It seems
like a pretty fun little survival game.</p>
<p>To play this game in multiplayer, you need a dedicated server. No problem I
thought, I&rsquo;ll just set it up quickly. This decision sent me on a 3-day trip of
trial, despair and ultimately, success. I&rsquo;ll use this blog post to document the
problems I went through, as well as to generally muse about the state of this
game.</p>
<p>Note: This article might be a bit harsh at times. It is not intended to
disparage any single individual that was involved in the development of this
game. Also keep in mind that the game is in its early stages, so we will
hopefully see some improvement in these areas.</p>
<p>I first installed this game using
<a href="https://developer.valvesoftware.com/wiki/SteamCMD">steamCMD</a>. After that was
done, I started configuring the service with systemd. Usually, the first thing
I look at, is how the program is started normally. The server version of
Project Zomboid is a Java application that is started by a bash script.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/bin/bash
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="nv">INSTDIR</span><span class="o">=</span><span class="s2">&#34;`dirname </span><span class="nv">$0</span><span class="s2">`&#34;</span> <span class="p">;</span> <span class="nb">cd</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">INSTDIR</span><span class="si">}</span><span class="s2">&#34;</span> <span class="p">;</span> <span class="nv">INSTDIR</span><span class="o">=</span><span class="s2">&#34;`pwd`&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">INSTDIR</span><span class="si">}</span><span class="s2">/jre64/bin/java&#34;</span> -version &gt; /dev/null 2&gt;<span class="p">&amp;</span>1<span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">        <span class="nb">echo</span> <span class="s2">&#34;64-bit java detected&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="nb">export</span> <span class="nv">PATH</span><span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">INSTDIR</span><span class="si">}</span><span class="s2">/jre64/bin:</span><span class="nv">$PATH</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="nb">export</span> <span class="nv">LD_LIBRARY_PATH</span><span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">INSTDIR</span><span class="si">}</span><span class="s2">/linux64:</span><span class="si">${</span><span class="nv">INSTDIR</span><span class="si">}</span><span class="s2">/natives:</span><span class="si">${</span><span class="nv">INSTDIR</span><span class="si">}</span><span class="s2">:</span><span class="si">${</span><span class="nv">INSTDIR</span><span class="si">}</span><span class="s2">/jre64/lib/amd64:</span><span class="si">${</span><span class="nv">LD_LIBRARY_PATH</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="nv">JSIG</span><span class="o">=</span><span class="s2">&#34;libjsig.so&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="nv">LD_PRELOAD</span><span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">LD_PRELOAD</span><span class="si">}</span><span class="s2">:</span><span class="si">${</span><span class="nv">JSIG</span><span class="si">}</span><span class="s2">&#34;</span> ./ProjectZomboid64 <span class="s2">&#34;</span><span class="nv">$@</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">elif</span> <span class="s2">&#34;</span><span class="si">${</span><span class="nv">INSTDIR</span><span class="si">}</span><span class="s2">/jre/bin/java&#34;</span> -client -version &gt; /dev/null 2&gt;<span class="p">&amp;</span>1<span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">        <span class="nb">echo</span> <span class="s2">&#34;32-bit java detected&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="nb">export</span> <span class="nv">PATH</span><span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">INSTDIR</span><span class="si">}</span><span class="s2">/jre/bin:</span><span class="nv">$PATH</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="nb">export</span> <span class="nv">LD_LIBRARY_PATH</span><span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">INSTDIR</span><span class="si">}</span><span class="s2">/linux32:</span><span class="si">${</span><span class="nv">INSTDIR</span><span class="si">}</span><span class="s2">/natives:</span><span class="si">${</span><span class="nv">INSTDIR</span><span class="si">}</span><span class="s2">:</span><span class="si">${</span><span class="nv">INSTDIR</span><span class="si">}</span><span class="s2">/jre/lib/i386:</span><span class="si">${</span><span class="nv">LD_LIBRARY_PATH</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="nv">JSIG</span><span class="o">=</span><span class="s2">&#34;libjsig.so&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="nv">LD_PRELOAD</span><span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">LD_PRELOAD</span><span class="si">}</span><span class="s2">:</span><span class="si">${</span><span class="nv">JSIG</span><span class="si">}</span><span class="s2">&#34;</span> ./ProjectZomboid32 <span class="s2">&#34;</span><span class="nv">$@</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">else</span>
</span></span><span class="line"><span class="cl">        <span class="nb">echo</span> <span class="s2">&#34;couldn&#39;t determine 32/64 bit of java&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span><span class="line"><span class="cl"><span class="nb">exit</span> <span class="m">0</span>
</span></span></code></pre></div><p>This script seems to be written by someone that is not very familiar with
bash/shell scripting. There are several anti-patterns here (re-assigning a
&ldquo;constant&rdquo;, using <code>exit 0</code> at the end of the script, no error handling, using
backticks, no double quotes, etc). Many of these errors are
picked up (and optionally automatically fixed) with
<code>shellcheck --diff start-server.sh</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-diff" data-lang="diff"><span class="line"><span class="cl"><span class="gd">--- a/start-server.sh
</span></span></span><span class="line"><span class="cl"><span class="gd"></span><span class="gi">+++ b/start-server.sh
</span></span></span><span class="line"><span class="cl"><span class="gi"></span><span class="gu">@@ -7,7 +7,7 @@
</span></span></span><span class="line"><span class="cl"><span class="gu"></span> #
</span></span><span class="line"><span class="cl"> ############
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gd">-INSTDIR=&#34;`dirname $0`&#34; ; cd &#34;${INSTDIR}&#34; ; INSTDIR=&#34;`pwd`&#34;
</span></span></span><span class="line"><span class="cl"><span class="gd"></span><span class="gi">+INSTDIR=&#34;$(dirname &#34;$0&#34;)&#34; ; cd &#34;${INSTDIR}&#34; || exit ; INSTDIR=&#34;$(pwd)&#34;
</span></span></span><span class="line"><span class="cl"><span class="gi"></span>
</span></span><span class="line"><span class="cl"> if &#34;${INSTDIR}/jre64/bin/java&#34; -version &gt; /dev/null 2&gt;&amp;1; then
</span></span><span class="line"><span class="cl">        echo &#34;64-bit java detected&#34;
</span></span></code></pre></div><p>What&rsquo;s stranger is the way this script detects the java version. It does not
actually parse the output of the version information, even though it executes
the relevant command (<code>java -version</code>). Instead, it checks where Steam installed the
bundled JRE and uses the name of the path as an indication for the version.</p>
<p>This script could be replaced by a systemd service completely, but I decided to
keep it for now. Time will tell whether this is a good idea.</p>
<h2 id="systemd">systemd<a href="#systemd" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<p>I created a simple systemd service file to execute the server. The first
problem presented itself immediately: The application asked for the
administrator password on stdin. I reran the binary without systemd to be able
to enter this password.</p>
<p>After creating the service and adding some hardening options, I found out after
some research that the server does not <a href="https://theindiestone.com/forums/index.php?/topic/63563-4178-multiplayer-zomboid-dedicated-server-does-not-handle-sigterm/#comment-376957">implement signal
handling</a>.
The developers explicitly say that running the binary in the foreground is the
only supported method, since the server can only be shut down safely using the
console commands. To circumvent this, I decided to create a systemd socket that
creates a named pipe. This pipe is connected to the stdin of the process.
This means I can send commands to the server to manage it, even when it is run
via systemd. The socket file looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[Socket]</span>
</span></span><span class="line"><span class="cl"><span class="na">ListenFIFO</span><span class="o">=</span><span class="s">%t/zomboid.sock</span>
</span></span><span class="line"><span class="cl"><span class="na">SocketMode</span><span class="o">=</span><span class="s">0660</span>
</span></span><span class="line"><span class="cl"><span class="na">SocketUser</span><span class="o">=</span><span class="s">zomboid</span>
</span></span><span class="line"><span class="cl"><span class="na">SocketGroup</span><span class="o">=</span><span class="s">games</span>
</span></span><span class="line"><span class="cl"><span class="na">RemoveOnStop</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[Install]</span>
</span></span><span class="line"><span class="cl"><span class="na">WantedBy</span><span class="o">=</span><span class="s">sockets.target</span>
</span></span></code></pre></div><p>The corresponding options in the service file are:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[Unit]</span>
</span></span><span class="line"><span class="cl"><span class="na">Description</span><span class="o">=</span><span class="s">Zomboid Headless Server</span>
</span></span><span class="line"><span class="cl"><span class="na">After</span><span class="o">=</span><span class="s">network-online.target</span>
</span></span><span class="line"><span class="cl"><span class="na">Requires</span><span class="o">=</span><span class="s">zomboid.socket</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[Service]</span>
</span></span><span class="line"><span class="cl"><span class="na">WorkingDirectory</span><span class="o">=</span><span class="s">/srv/zomboid/game</span>
</span></span><span class="line"><span class="cl"><span class="na">ExecStart</span><span class="o">=</span><span class="s">/srv/zomboid/game/start-server.sh</span>
</span></span><span class="line"><span class="cl"><span class="na">Type</span><span class="o">=</span><span class="s">exec</span>
</span></span><span class="line"><span class="cl"><span class="na">User</span><span class="o">=</span><span class="s">zomboid</span>
</span></span><span class="line"><span class="cl"><span class="na">StandardInput</span><span class="o">=</span><span class="s">socket</span>
</span></span><span class="line"><span class="cl"><span class="na">StandardOutput</span><span class="o">=</span><span class="s">journal</span>
</span></span><span class="line"><span class="cl"><span class="na">StandardError</span><span class="o">=</span><span class="s">journal</span>
</span></span><span class="line"><span class="cl"><span class="na">Sockets</span><span class="o">=</span><span class="s">zomboid.socket</span>
</span></span><span class="line"><span class="cl"><span class="na">ExecStop</span><span class="o">=</span><span class="s">/bin/sh -c &#34;echo quit &gt; /run/zomboid.sock&#34;</span>
</span></span></code></pre></div><p>By modifying the <code>ExecStop=</code> option, we can make sure that the server is
stopped cleanly when using <code>systemctl stop zomboid.service</code>.</p>
<p>The developers have signalled (no pun intended) <a href="https://theindiestone.com/forums/index.php?/topic/63563-4178-multiplayer-zomboid-dedicated-server-does-not-handle-sigterm/#comment-376981">that they want to fix this
issue</a>,
which is very commendable. I hope a fix is implemented soon.</p>
<h3 id="logging-and-logfiles">Logging and logfiles<a href="#logging-and-logfiles" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h3>
<p>The strange decisions continue when it comes to logging. I did not expect the
application to honor all best practices, but
I did expect some kind of standards compliance, or at least configurability.</p>
<p>The application does 2 types of logging: it outputs messages to stdout. This
is reasonable, but the logging format itself is very strange:</p>
<pre tabindex="0"><code>[03-11-23 14:20:55.140] LOG  : Multiplayer , 1699017655140&gt; 1,021,965,239&gt; [MPStatistics] mem usage notification threshold=8,160,437,760.
[03-11-23 14:20:57.820] LOG  : Network     , 1699017657820&gt; 1,021,967,919&gt; [03-11-23 14:20:57.820] &gt; ZNet: SSteamSDK -&gt; SZombienet: OnPolicyResponse.
[03-11-23 14:20:57.821] LOG  : Network     , 1699017657821&gt; 1,021,967,920&gt; [03-11-23 14:20:57.821] &gt; ZNet: OnPolicyResponse.
[03-11-23 14:20:57.821] LOG  : Network     , 1699017657821&gt; 1,021,967,921&gt; [03-11-23 14:20:57.821] &gt; ZNet: SZombienet -&gt; SSteamSDK: BSecure.
[03-11-23 14:20:57.822] LOG  : Network     , 1699017657822&gt; 1,021,967,921&gt; [03-11-23 14:20:57.822] &gt; ZNet: Zomboid Server is VAC Secure.
</code></pre><p>I don&rsquo;t know much about logging facilities in Java, but this is horrible. It
does not adhere to the syslog standard, the date format is strange, the
default severity is called <code>LOG</code>, it uses fixed width fields, each line has
three timestamps in different formats (some lines even have four).</p>
<p>In my opinion, this logging system would be improved by just using <code>System.out.println()</code>.
At least then formatting could be handled by the logging daemon and messages
would be actually readable.</p>
<p>I know that logging is complicated topic, but this solution seems to use the
worst of both worlds.</p>
<p>The second problem is that the application also writes this information to
several log files in a custom folder (<code>Logs/</code>). There is no way to turn this
off or change the path. The logs have strange file names
(<code>03-11-23_14-20-04_DebugLog-server.txt</code>) and are rotated by the application
itself (!) using a strange algorithm: the application creates folders for each
day and log files are moved into it after each application restart. This makes
rotating the logs using something like <code>logrotate</code> supremely difficult, if not
entirely impossible.</p>
<p>For this reason, I decided to simply discard these log files. The messages are
saved in the systemd journal anyway, I simply do not need these files. The
first solution I thought of is using something like
<a href="https://github.com/abbbi/nullfsvfs">nullfsvfs</a> (eg: &ldquo;/dev/null as a
filesystem&rdquo;). This seemed very interesting, but ultimately overkill. I decided
to use the simpler, sledgehammer solution:</p>
<p>Deleting all files in <code>Logs/</code> every time the service is stopped.</p>
<p>This can be done via one line in the systemd service:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="na">ExecStopPost</span><span class="o">=</span><span class="s">/bin/sh -c &#34;rm -rf /srv/zomboid/Zomboid/Logs/*&#34;</span>
</span></span></code></pre></div><p>Logs that don&rsquo;t follow a single event stream and are not rotateable are
useless at best and a risk (disk space) at worst. I really hope the developers
think about implementing logging in a more standards-compliant and sensible
way. Adhering to the <a href="https://12factor.net/logs">twelve factor guidelines on
logging</a> should be a huge improvement. If this is
not possible, at least allow users to choose where they want logs to
go (eg: console, file, etc). (This would contradict the 12 Factor App
Guidelines, but it would at least be better than the status quo. Also logfile names
should not have a timestamp (that&rsquo;s what filesystem metadata
is for). Thirdly, don&rsquo;t try to rotate logs yourself. Let system administration
utilities (eg: logrotate, journald) handle this for you.</p>
<h3 id="complete-file">Complete file<a href="#complete-file" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h3>
<p>In conclusion, this is the complete systemd file for the service:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[Unit]</span>
</span></span><span class="line"><span class="cl"><span class="na">Description</span><span class="o">=</span><span class="s">Zomboid Headless Server</span>
</span></span><span class="line"><span class="cl"><span class="na">After</span><span class="o">=</span><span class="s">network-online.target</span>
</span></span><span class="line"><span class="cl"><span class="na">Requires</span><span class="o">=</span><span class="s">zomboid.socket</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[Service]</span>
</span></span><span class="line"><span class="cl"><span class="na">WorkingDirectory</span><span class="o">=</span><span class="s">/srv/zomboid/game</span>
</span></span><span class="line"><span class="cl"><span class="na">ExecStart</span><span class="o">=</span><span class="s">/srv/zomboid/game/start-server.sh</span>
</span></span><span class="line"><span class="cl"><span class="na">User</span><span class="o">=</span><span class="s">zomboid</span>
</span></span><span class="line"><span class="cl"><span class="na">StandardInput</span><span class="o">=</span><span class="s">socket</span>
</span></span><span class="line"><span class="cl"><span class="na">StandardOutput</span><span class="o">=</span><span class="s">journal</span>
</span></span><span class="line"><span class="cl"><span class="na">StandardError</span><span class="o">=</span><span class="s">journal</span>
</span></span><span class="line"><span class="cl"><span class="na">Type</span><span class="o">=</span><span class="s">exec</span>
</span></span><span class="line"><span class="cl"><span class="na">Sockets</span><span class="o">=</span><span class="s">zomboid.socket</span>
</span></span><span class="line"><span class="cl"><span class="na">ExecStop</span><span class="o">=</span><span class="s">/bin/sh -c &#34;echo quit &gt; /run/zomboid.sock&#34;</span>
</span></span><span class="line"><span class="cl"><span class="na">ExecStopPost</span><span class="o">=</span><span class="s">/bin/sh -c &#34;rm -rf /srv/zomboid/Zomboid/Logs/*&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># hardening</span>
</span></span><span class="line"><span class="cl"><span class="na">ProtectControlGroups</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">RestrictSUIDSGID</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">ProtectClock</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">ProtectHome</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">ProtectKernelLogs</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">ProtectKernelModules</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">ProtectProc</span><span class="o">=</span><span class="s">invisible</span>
</span></span><span class="line"><span class="cl"><span class="na">RemoveIPC</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">PrivateDevices</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">ProtectHostname</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">NoNewPrivileges</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">CapabilityBoundingSet</span><span class="o">=</span>
</span></span><span class="line"><span class="cl"><span class="na">RestrictNamespaces</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">SystemCallArchitectures</span><span class="o">=</span><span class="s">native</span>
</span></span><span class="line"><span class="cl"><span class="na">LockPersonality</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">RestrictRealtime</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">SystemCallErrorNumber</span><span class="o">=</span><span class="s">EPERM</span>
</span></span><span class="line"><span class="cl"><span class="na">UMask</span><span class="o">=</span><span class="s">0077</span>
</span></span><span class="line"><span class="cl"><span class="na">RestrictAddressFamilies</span><span class="o">=</span><span class="s">AF_INET AF_INET6</span>
</span></span><span class="line"><span class="cl"><span class="na">PrivateUsers</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">PrivateTmp</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">ProtectKernelTunables</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">MemoryDenyWriteExecute</span><span class="o">=</span><span class="s">false</span>
</span></span><span class="line"><span class="cl"><span class="na">SystemCallFilter</span><span class="o">=</span><span class="s">@system-service</span>
</span></span><span class="line"><span class="cl"><span class="na">SystemCallFilter</span><span class="o">=</span><span class="s">~@resources</span>
</span></span><span class="line"><span class="cl"><span class="na">SystemCallFilter</span><span class="o">=</span><span class="s">~@privileged</span>
</span></span><span class="line"><span class="cl"><span class="na">PrivateNetwork</span><span class="o">=</span><span class="s">false</span>
</span></span><span class="line"><span class="cl"><span class="na">ProtectSystem</span><span class="o">=</span><span class="s">strict</span>
</span></span><span class="line"><span class="cl"><span class="na">ReadWritePaths</span><span class="o">=</span><span class="s">/srv/zomboid/Zomboid</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[Install]</span>
</span></span><span class="line"><span class="cl"><span class="na">WantedBy</span><span class="o">=</span><span class="s">multi-user.target</span>
</span></span></code></pre></div><p>With the hardening options active, we can get it down to quite a nice score:</p>
<pre tabindex="0"><code>→ Overall exposure level for zomboid.service: 1.1 OK 🙂
</code></pre><h2 id="networking">Networking<a href="#networking" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<p>After starting the service first tries to open its ports on the router via
UPnP. Another anti-feature in my opinion: UPnP should be disabled by default
and left as a concious choice for the users that really want it (in my
opinion, you should never actually want/need UPnP, so it might even be better
to disable this &ldquo;feature&rdquo; completely).</p>
<p>Since my router does not implement UPnP, I disabled it and opened the requisite
ports in my firewall (16261 and 16262).</p>
<p>I did not want my server to appear in the games global server browser, so I decided to
create a DNS record that points to the server. I could then tell my friends
to use that name when they want to connect. Imagine my surprise when I tested
this, and the client still could not connect to the server. After some
digging I found the following:</p>
<p><code>ss -unlp | grep 1626</code></p>
<pre tabindex="0"><code>UNCONN 0      0                          0.0.0.0:16261      0.0.0.0:*    users:((&#34;ProjectZomboid6&#34;,pid=1660231,fd=40))
UNCONN 0      0                          0.0.0.0:16262      0.0.0.0:*    users:((&#34;ProjectZomboid6&#34;,pid=1660231,fd=42))
</code></pre><p>The application only supports IPv4. To remedy this, I created a custom record
that only resolves my IPv4 address.  With this in hand I tried again, to no
avail. Turns out, the client does not support name resolution at all.</p>
<p>At this point I gave up trying to find a <em>nice</em> solution and instead chose <em>a</em>
solution. I told my friends to do a <code>nslookup/dig</code> of my IPv4-only record and
that they should use the output of that in the client.</p>
<p>I would urge the developers to implement IPv6 and support name resolution. This
should really make the user experience better for servers that can&rsquo;t or don&rsquo;t
want to be in the global server browser.</p>
<h2 id="user-management">User management<a href="#user-management" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<p>User management is another strange beast. A specific server has local users
that are not connected to the steam users at all. These users have a username
and a password. There is also an optional server password. To limit access, we
can either configure a server password or configure whitelisting. If there is
a whitelist, the server administrator must <em>add all of the users and their
passwords beforehand</em>. As far as I can tell, there is no way for the user
to change their password later. Since this is a huge hassle, I decided to
simply create a server password and disable whitelisting.</p>
<h2 id="conclusion">Conclusion<a href="#conclusion" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<p>First of all, a big thank you to the developers of Project Zomboid. I very much
look forward to playing it. It looks like a nice game and I am glad that the
server is now working. This was way harder than it needed to be though, and I
wonder how people that have less technical knowledge than me could set this up
sensibly. I really hope the developers put some time and effort into these
things. These are fundamental problems, that fortunately should have easy
solutions.  By programming your <a href="https://www.shellcheck.net/">applications</a>
<a href="https://en.wikipedia.org/wiki/Signal_(IPC)">according</a>
<a href="https://datatracker.ietf.org/doc/html/rfc5424">to</a>
<a href="https://12factor.net/">relevant</a> <a href="https://systemd.io/">standards</a>, you can
save yourself a lot of hassle in the long run.</p>
]]></content></item><item><title>A Lyrical Analysis of Aïcha</title><link>https://blog.sergeantbiggs.net/posts/a-lyrical-analysis-of-aicha/</link><pubDate>Wed, 23 Aug 2023 18:25:29 +0200</pubDate><author>SergeantBiggs</author><guid>https://blog.sergeantbiggs.net/posts/a-lyrical-analysis-of-aicha/</guid><description>&amp;lt;no value&amp;gt;</description><content type="text/html" mode="escaped"><![CDATA[<p>This blog post is a fair bit out of the ordinary for me. It does not have a
tech focus at all, and might also be rather niche. It is a very specific topic
that I&rsquo;ve had in my mind for quite a while now. I was finally prompted into
writing it down because of a friend who complained that my blog has too much
tech-content (as if such a thing were even possible).</p>
<p>This post is about a song, more precisely two songs. It is about <em>Aïcha</em> by
Khaled and the English-language cover of that song by Outlandish. Although
the version by Outlandish is a cover, the meaning of the song is radically
transformed because of musical and lyrical choices. To understand what I mean
we first have to understand a few things about the original song and about its
genre, <a href="https://en.wikipedia.org/wiki/Ra%C3%AF">Raï</a>. I will also briefly
introduce Outlandish.</p>
<h2 id="raï-and-khaled">Raï and Khaled<a href="#ra%c3%af-and-khaled" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<p>Raï is a form of Algerian folk music that originates in
<a href="https://en.wikipedia.org/wiki/Oran">Oran</a>. Going into all of the historical
development of this genre and explaining all sociological aspects is something
that I don&rsquo;t have any competency for. For the sake of this article, a few
things are important. Raï is associated with a countercultural movement in
Algeria that often clashed with established morals and societal expectations.
The lyrics are often political and often critical of social problems. They also
contain elements that Algerian society at that time considered raunchy or
vulgar. Musically it is a synthesis of traditional Islamic, traditional secular
and western music. This creates a very distinct sound.</p>
<p>Khaled is the personification of modern Raï. He was particularly popular
in the 80s and 90s, and still enjoys success in France and Algeria.</p>
<p>Aïcha itself was written <a href="https://en.wikipedia.org/wiki/Jean-Jacques_Goldman">Jean-Jacques
Goldman</a>. There are two
versions, a French version and a bilingual version (French and Arabic). The
Arabic parts were written by Khaled himself.</p>
<h2 id="outlandish">Outlandish<a href="#outlandish" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<p><em>Outlandish</em> is a Danish hip-hop group. When the song was written, they
consisted of three members: Waqas Ali Qadri, Lenny Martinez and Isam Bachiri.</p>
<h2 id="analysis">Analysis<a href="#analysis" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<p>If you haven&rsquo;t already please take a moment to listen to both songs right now
and to read the lyrics.
After you&rsquo;ve done that, continue reading. I will start with a short musical comparison, and then get to the actual meat
of this post: a comparison of the lyrics.</p>
<p>The cover takes some musical elements from the original, but they are a
distinct genre. While the original is a Raï song, the cover is a mixture R&amp;B
and pop. Musically, I prefer the original but I don&rsquo;t really have the musical
talent to talk about interesting differences between the songs.</p>
<p>NB: For the sake of brevity, I won&rsquo;t translate the french parts of the song. If
you don&rsquo;t understand, either learn french or try one of the numerous automatic
translation providers.</p>
<p>Lyrically, the songs are radically different. To highlight this, let&rsquo;s take the time
to go through the lyrics of both of them. We&rsquo;ll start with the original.</p>
<h3 id="the-original">The Original<a href="#the-original" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h3>
<pre tabindex="0"><code>Comme si je n&#39;existais pas
Elle est passée à côté de moi
Sans un regard, reine de Saba
J&#39;ai dit Aïcha prends tout est pour toi
Voici les perles, les bijoux
Aussi l&#39;or autour de ton cou
Les fruits bien mûrs au goût de miel
Ma vie Aïcha si tu m&#39;aimes

J&#39;irai où ton souffle nous mène
Dans les pays d&#39;ivoire et d&#39;ébène
J&#39;effacerai tes larmes, tes peines
Rien n&#39;est trop beau pour une si belle
</code></pre><p>Here we have a narrator that describes our titular Aïcha, who seems to be quite
a special person. She is compared to the biblical and mythical <a href="https://en.wikipedia.org/wiki/Queen_of_Sheba">Queen of
Sheba</a>. This comparison is
compounded by references to ivory and ebony wood, which are products that are
produced in the places our queen is supposed to hail from. The narrator
promises her all kinds of expensive material gifts: pearls, gold, et cetera.</p>
<pre tabindex="0"><code>Je dirais les mots les poèmes
Je jouerais les musiques du ciel
Je prendrais les rayons du soleil
Pour éclairer tes yeux de rêves
</code></pre><p>In the second part of the song, the narrator now shifts his focus to immaterial
courtship. He will write her poems, sing her songs, even catch sunlight for
her. After this part we get a musical and lyrical shift, as the titular Aïcha
suddenly starts answering our narrator.</p>
<pre tabindex="0"><code>Elle a dit garde tes trésors
Moi je vaux mieux que tout ça
Des barreaux sont des barreaux même en or
Je veux les mêmes droits que toi
Et du respect pour chaque jour, moi je ne veux que l&#39;amour
</code></pre><p>This twist clearly illustrates the theme of the song. It is a song about love
and courtship, but also about freedom and the social status of women in MENA
countries. What is important to Aïcha is not being wooed with material or
immaterial gifts.
She plainly states she wants the same rights as men, in a
relationship but also in society in general. With this, the song manages to be
subversive and exemplifies the kind of social commentary that is typical of its
genre. The Aïcha in this song is strong-willed and tells us exactly what she
wants.</p>
<h3 id="the-cover">The Cover<a href="#the-cover" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h3>
<p>Without further ado, let us delve into the cover version. The first part is
already quite different from the original.</p>
<pre tabindex="0"><code>So sweet, so beautiful
Everyday like a queen on her throne
Don&#39;t nobody knows how she feels
Aïcha, lady one day it will be real (let&#39;s do it)
She moves, she moves like a breeze
I swear I can&#39;t get her out of my dreams
To have her shining right here by my side (shining like a star)
I&#39;d sacrifice all them tears in my eyes
</code></pre><p>Apart from leaving out the specific reference to the queen of Sheba, one thing
is immediately apparent:
the <strong>perspective</strong> of this song is different. We no longer have a narrator that
talks to Aïcha, but one that describes her.</p>
<pre tabindex="0"><code>She holds her child to her heart
Makes her feel like she is blessed from above
Falls asleep underneath her sweet tears
Her lullaby fades away with his fears
</code></pre><p>The second part starts deviating completely from the original. Gone are any
references to promises or courtship. Instead, Aïcha is here described as a
mother with a child, a far cry from the image of Aïcha in the
original. After that, we arrive at the same musical shift as in the
original, but with radically different imagery:</p>
<pre tabindex="0"><code>She needs somebody to lean on
Someone body, mind &amp; soul
To take her hand, to take her world
And show her the time of her life, so true
Throw the pain away for good
No more contemplating boo
No more contemplating boo
</code></pre><p>Gone is Aïchas rejection of courtship. Gone is the social commentary. Gone is
the
specific reference to women&rsquo;s rights. Also, markedly:</p>
<p>Gone is Aïchas voice.</p>
<p>She is not the one speaking. She has no active part in this song. Instead we
are being told by the narrator what she (supposedly) wants: someone to <strong>lean on</strong>,
someone to <strong>take her hand</strong>. Someone to <strong>show her the time of her life</strong>.
This Aïcha needs to be taken care of, for she certainly can not take care of
herself. This is a far cry from the powerful woman in the original, who
actively demands things from the narrator. Khaleds Aïcha is active. Outlandishs
Aïcha is entirely passive.</p>
<p>Outlandish manages to quite successfully subvert the central themes of the song: longing,
freedom and equality. The song is reduced to a much simpler theme: a desirable
but unhappy woman, who needs love to be happy.</p>
<p>The crowning jewel of the song can be found in the last verse. Here the
narrator tells us what is desirable about her.</p>
<pre tabindex="0"><code>Lord knows the way she feels
Everyday in his name she begins
To have her shining here by my side
I&#39;d sacrifice all them tears in my eyes
</code></pre><p>This verse can be seen as the ultimate conclusion of the song. What makes our
Aïcha most desirable? Her devotion to God of course! The song ends with an
explicit reference to God and the difference to the original could not be more
marked. We have gone from a secular song written by a french Jew and
performed by an Algerian Raï singer to something that could be found on an
evangelical hip-hop sampler album.</p>
<p>Not surprisingly all three members of Outlandish describe themselves as
religious.</p>
<p>To make something clear: I have nothing against religion, or religious music
for that matter. I consider myself a Christian and have even been known to
<a href="https://www.youtube.com/watch?v=_tdWkuyFI6c">listen to some Christian music from time to
time</a>. But this cover is
something else. It does not feel like a cover. It feels more and more like
a deliberate subversion of the themes of the original. It makes me genuinely
angry every time I think about it.</p>
<p>I hope you enjoyed my little analysis of this song. This thing has been on my
mind for quite a while and I&rsquo;m glad I finally got it out. If this wasn&rsquo;t your
cup of tea, I&rsquo;m pretty sure that the next article will be more technical.</p>
]]></content></item><item><title>Booting Arch Linux Like It's 2012; Secure Boot and TPM</title><link>https://blog.sergeantbiggs.net/posts/booting-arch-linux-like-its-2012/</link><pubDate>Wed, 12 Jul 2023 11:17:53 +0200</pubDate><author>SergeantBiggs</author><guid>https://blog.sergeantbiggs.net/posts/booting-arch-linux-like-its-2012/</guid><description>&amp;lt;no value&amp;gt;</description><content type="text/html" mode="escaped"><![CDATA[<p>I recently got a new job and with that came a new Laptop: a Dell XPS 13 Plus
9320. The first thing I did before installing the OS, is something that many
Linux users will be familiar with: I disabled secure boot. This has been a
tried-and-true ritual of mine. But as with all rituals, there comes a time when
we need to change them. At the same time, I wanted to modernise other parts of
my booting process, while still keeping it reasonably secure. To understand why
I did this, I will start by giving a short overview of how I do it &ldquo;currently&rdquo;
and then show what I did to improve the situation.</p>
<p>Normally, I encrypt my main root partition with LUKS while my boot partition
stays unencrypted. I do this because encrypting the boot partition has
downsides: it only supports LUKS 1 which would mean entering 2 passphrases.
Not encrypting the boot partition means being open to
<a href="https://en.wikipedia.org/wiki/Evil_maid_attack">evil maid attacks</a>. But even
encrypting the boot partition does not save one from this vector, because the
<a href="https://en.wikipedia.org/wiki/EFI_system_partition">ESP</a> stays unencrypted.
Secure boot solves this problem: it only allows booting operating systems that
were signed and makes sure that the &ldquo;boot chain&rdquo; is secure. This does not
prevent all attacks, but provides a reasonable hurdle for most use cases.</p>
<p>Configuring secure boot enables another convenience feature: unlocking the root
partition with a key from the TPM rather than a passphrase.
To do this, we need to configure the following components:</p>
<ul>
<li>systemd-boot</li>
<li>mkinitcpio</li>
<li>secure boot</li>
<li>systemd-cryptenroll</li>
</ul>
<h2 id="systemd-boot">systemd-boot<a href="#systemd-boot" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<p>NB: This part is not strictly
necessary, I think it would theoretically be possible to configure GRUB to do
the same thing, but it &ldquo;fits&rdquo; well into the rest of the setup.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">sudo bootctl install
</span></span></code></pre></div><p>After installing it, we need to add some configuration. First, we edit the
file <code>/boot/loader/loader.conf</code>.</p>
<pre tabindex="0"><code>timeout 0
console-mode max
default arch.conf
editor no
</code></pre><p>Then we create the default boot entry (<code>arch.conf</code>).</p>
<pre tabindex="0"><code>title   Arch Linux
linux   /vmlinuz-linux
initrd  /intel-ucode.img
initrd  /initramfs-linux.img
options root=/dev/mapper/root rw loglevel=3 quiet splash
</code></pre><p>To update our Bootloader every time the kernel or systemd is updated, we have
2 options. The first option is using <code>systemd-boot-update.service</code>. This
updates the bootloader on the next reboot. This option won&rsquo;t work for our setup,
since we need to generate and sign the boot loader files <em>before</em> we reboot. To solve this
issue, I installed <a href="https://aur.archlinux.org/packages/systemd-boot-pacman-hook">systemd-boot-pacman-hook (AUR)</a>. There are
other solutions for this problem, but this seemed the simplest. It also works
well with the workflow of <code>sbctl</code> that I will go into later in this article.</p>
<h2 id="mkinitcpio">mkinitcpio<a href="#mkinitcpio" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<p>After installing the bootloader, we need to make sure that our disk can be
decrypted. We do this by configuring the initramfs. I will be using the default
tool under Arch Linux
(<a href="https://wiki.archlinux.org/title/Mkinitcpio">mkinitcpio</a>). If you&rsquo;re using
<a href="https://wiki.archlinux.org/title/Dracut">dracut</a> the setup should be similar,
but I have not tested it.</p>
<p>There are 2 ways of decrypting a drive with mkinitcpio: the
<a href="https://wiki.archlinux.org/title/Dm-crypt/System_configuration#Using_encrypt_hook">encrypt</a>
or the
<a href="https://wiki.archlinux.org/title/Dm-crypt/System_configuration#Using_systemd-cryptsetup-generator">sd-encrypt</a>
hook. The <code>sd-encrypt</code> hook uses systemd, and it is the one I will be using. To
configure this hook, first we need to configure <code>/etc/crypttab.initramfs</code>. This
file gets added to the initramfs and contains the devices that should be
decrypted by <code>systemd-cryptsetup-generator</code>:</p>
<pre tabindex="0"><code>root	UUID=&lt;label&gt;	none	tmp2-device=auto
</code></pre><p>The last column specifies that we want this drive to be automatically unlocked
using a key stored in the TPM. If you want to use a passphrase, omit the
last two columns.</p>
<p>We also need to set the correct hooks. To do that, we edit
<code>/etc/mkinitcpio.conf</code> and change the HOOKS variable to the following:</p>
<pre tabindex="0"><code>HOOKS=(base systemd autodetect modconf kms keyboard sd-vconsole block sd-encrypt filesystems fsck)
</code></pre><h2 id="secure-boot">Secure boot<a href="#secure-boot" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<p>This is the most finicky party of the setup, and the exact way of doing this
depends on your hardware manufacturer/UEFI implementation. To use secure boot,
there are two strategies:
using your own keys, or using a signed bootloader. Most distros
take the second route. I will be implementing the first one.</p>
<p>Note: Doing this can brick your device. Please make sure you understand what
you are doing and don&rsquo;t blindly follow the instructions in this article.</p>
<p>It&rsquo;s a good idea to back up the variables, in case something goes wrong. These
can then be restored later, if your UEFI supports it.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="k">for</span> var in PK KEK db dbx <span class="p">;</span> <span class="k">do</span> efi-readvar -v <span class="nv">$var</span> -o old_<span class="si">${</span><span class="nv">var</span><span class="si">}</span>.esl <span class="p">;</span> <span class="k">done</span>
</span></span></code></pre></div><p>After that, we need to put the UEFI in &ldquo;Setup Mode&rdquo;. This mode is activated
when the Platform Key (PK) is deleted. After deleting the key and rebooting, we
can generate our own keys with <code>sbctl</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">sudo sbctl create-keys
</span></span></code></pre></div><p>After that, we can enroll these keys into the UEFI. The <code>-m</code> also enrolls
Microsofts keys. This is necessary on many motherboards, since they use these
keys to sign their firmware. Only leave this option out if you know what you
are doing.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">sudo sbctl enroll-keys -m
</span></span></code></pre></div><p>After that, we can sign our kernel image and boot loader.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">sudo sbctl sign -s /boot/vmlinuz-linux
</span></span><span class="line"><span class="cl">sudo sbctl sign -s /boot/EFI/systemd/systemd-bootx64.efi
</span></span></code></pre></div><p>Since I use <a href="https://wiki.archlinux.org/title/Fwupd">fwupd</a> I signed that one
as well.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">sudo sbctl sign -s /boot/EFI/Arch/fwupdx64.efi
</span></span></code></pre></div><p>After they are added once, the files will be automatically resigned every time
the kernel or systemd are updated. This is done via a pacman hook. For that
reason, we installed <code>systemd-boot-pacman-hook</code> earlier: this automatically
regenerates <code>systemd-bootx64.efi</code> when systemd-boot is updated. After that, it
will be signed by the pacman hook of <code>sbctl</code>.</p>
<h2 id="systemd-cryptenroll">systemd-cryptenroll<a href="#systemd-cryptenroll" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<p>Before we generate the key, we need to think about <em>when</em> we want systemd to use
the TPM. This is configured using so-called Platform Configuration Registers
(PCRs). One of these is the secure boot state (7). This means systemd will only
unlock the drive if secure boot is enabled and no secure boot errors have been
reported.
This is the default setting.
Some guides on the internet also use PCR 0
(firmware). To me personally, this does not make much sense. I&rsquo;m not an expert,
but I would think that we have to inherently trust the firmware anyway, since a
malicious firmware could theoretically also falsely report PCR 0. If anyone has
a good answer to this, feel free to contact me.</p>
<p>In the meantime, I&rsquo;ve decided to not use PCR 0 since it would be quite annoying
(rebinding every time the firmware is updated).</p>
<p>With that discussion out of the way, we can generate our key.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">sudo systemd-cryptenroll --tpm2-device<span class="o">=</span>auto /dev/sdX
</span></span></code></pre></div><p>If you&rsquo;re paranoid, you can add a PIN as well with <code>--tpm2-with-pin=true</code>.</p>
<p>It&rsquo;s also sensible to generate a recovery key and store it somewhere safe. This
can be used when systemd can not decrypt the drive from the TPM.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">sudo systemd-cryptenroll --recovery /dev/sdX
</span></span></code></pre></div><p>I&rsquo;m very happy with this setup. This problem has been in the back of my mind
for some years now, and I&rsquo;m glad I finally tried to solve it.
I will probably be using this setup on all of my future Linux machines.</p>
]]></content></item><item><title>Youtube Without Youtube; Watching Videos on the Command Line</title><link>https://blog.sergeantbiggs.net/posts/youtube-without-youtube-watching-videos-on-the-command-line/</link><pubDate>Wed, 07 Jun 2023 16:55:40 +0200</pubDate><author>SergeantBiggs</author><guid>https://blog.sergeantbiggs.net/posts/youtube-without-youtube-watching-videos-on-the-command-line/</guid><description>&amp;lt;no value&amp;gt;</description><content type="text/html" mode="escaped"><![CDATA[<p>I don&rsquo;t have a Google account. This is a &ldquo;problem&rdquo; that is sometimes surprisingly
easy to navigate, and sometimes surprisingly hard. At other times it is just
very annoying. One particular annoyance is Youtube. Because I don&rsquo;t have any
way to &ldquo;subscribe&rdquo; to channels, I just end up using a mixture of the
algorithm and manually searching specific channels using the search bar. This
works, but is quite annoying and time-consuming. It also does not really allow
me to &ldquo;prioritize&rdquo; videos, and I&rsquo;ve had some times where I &ldquo;missed&rdquo; a video
that I wanted to watch earlier. One additional caveat is that I like to watch
videos with <a href="https://mpv.io/">mpv</a>, because it is an excellent player, has good
CLI-Integration, sensible keybindings and support for a lot of Codecs. It also
allows me to queue up multiple videos using
<a href="https://github.com/mpv-player/mpv/blob/master/TOOLS/umpv">umpv</a>. So
ideally, my solution will allow me to continue doing this.</p>
<p>The first solution that came to mind were alternative Youtube frontends. I
found a <a href="https://github.com/mendel5/alternative-front-ends#youtube">list with different
options</a> that I had
a look at. Most of these solutions support accounts. That would mean I could
create an account on their public instance, and then use that instance to watch
the videos. This seemed like a nice idea at first, but in such cases I&rsquo;m more
at ease with self-hosting. Most of these services support that though. I
started diving into the documentation of these different services and the more
I did, the less I became interested. In general these services are very
resource-heavy, requiring about 4G of RAM. They are also
frequently buggy. One service claims that to function: &ldquo;[the application] must
be restarted often, at least once a day, ideally every hour.&rdquo; That doesn&rsquo;t
inspire much confidence. Another service only runs as a docker container, which
is something I try to avoid as much as possible.</p>
<p>At this point I realised I was falling into a rabbit hole, and getting
nowhere. At this point, I asked some of my friends and one of them pointed me
towards this:</p>
<pre tabindex="0"><code>https://www.youtube.com/feeds/videos.xml?channel_id=UCyhnYIvIKK_--PiJXCMKxQQ
</code></pre><p>If we open that link, we get an Atom feed. These feeds can be plugged into an
RSS reader, which gives a great overview over all the channels that I&rsquo;m
interested in. There&rsquo;s just one tiny hitch: we need the channel ID. This is not
the same thing as the name of the channel and isn&rsquo;t &ldquo;shown&rdquo; normally. Because I
didn&rsquo;t need to find the ID of many channels I just ended up <a href="https://commentpicker.com/youtube-channel-id.php">using a web tool to
get the IDs</a>. This worked
pretty well. Apparently, there are other ways, as outlined in this <a href="https://stackoverflow.com/questions/14366648/how-can-i-get-a-channel-id-from-youtube">SO
discussion</a>.</p>
<p>After gathering all channel IDs, we just have to plug them into an RSS reader.
Since I hadn&rsquo;t used one for quite a while, I took this opportunity to find a
new (preferably TUI-based) one. I started my search with the excellent
<a href="https://wiki.archlinux.org/title/List_of_applications">List of applications on the Arch
Wiki</a> and found
<a href="https://codezen.org/canto-ng/">canto</a>. From their website:</p>
<p>&ldquo;Canto is an Atom/RSS feed reader for the console that is meant to be quick,
concise, and colorful. It’s meant to provide a minimal, yet information packed
interface. No navigating menus. No dense blocks of unreadable white text. An
interface with almost infinite customization and extensibility using the
excellent Python programming language.&rdquo;</p>
<p>That sums it up pretty well. I won&rsquo;t go through all of the features Canto has,
because most of them are pretty standard for an RSS/Atom reader, but I want to
highlight the specific features that make it ideal for this use case. Canto
allows us to set a default browser to open links, and also has a built-in
reader, but its killer feature
is its so-called &ldquo;smart link plugin&rdquo;. This plugin
makes it possible to open specific file types or URLs with specific programs.
This means I can use canto to open the Youtube entries with umpv, and all
other entries with my normal browser. To make this possible, we just need to
copy the plugin into our configuration directory, and change some settings:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">cp /usr/lib/canto/plugins/smartlink.py ~/.config/canto/plugins/
</span></span><span class="line"><span class="cl">vim ~/.config/canto/plugins
</span></span></code></pre></div><p>To change the settings, we just change the <code>HANDLERS</code> variable.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">HANDLERS</span> <span class="o">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="s2">&#34;match-file&#34;</span> <span class="p">:</span> <span class="s2">&#34;image data&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;handler&#34;</span> <span class="p">:</span> <span class="s2">&#34;feh&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="s2">&#34;match-file&#34;</span> <span class="p">:</span> <span class="s2">&#34;PDF&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;handler&#34;</span> <span class="p">:</span> <span class="s2">&#34;evince&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="s2">&#34;match-url&#34;</span> <span class="p">:</span> <span class="s2">&#34;youtube.com&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;handler&#34;</span><span class="p">:</span> <span class="s2">&#34;umpv&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">]</span>
</span></span></code></pre></div><p>Now we can press <code>f</code> on an entry to open it with
<a href="https://github.com/mpv-player/mpv/blob/master/TOOLS/umpv">umpv</a>, which adds it
to our mpv queue. If we press <code>g</code>, the entry is opened in the browser we
configured.</p>
<p>I&rsquo;ve been using this setup for a few days now, and I&rsquo;m very happy with it. It
is exactly what I wanted: lightweight, simple, uses mpv for playback and
terminal-based.</p>
]]></content></item><item><title>Hardening Admin Access With Nginx; Part 2</title><link>https://blog.sergeantbiggs.net/posts/hardening-admin-access-with-nginx-part-2/</link><pubDate>Wed, 26 Oct 2022 22:39:16 +0200</pubDate><author>SergeantBiggs</author><guid>https://blog.sergeantbiggs.net/posts/hardening-admin-access-with-nginx-part-2/</guid><description>&amp;lt;no value&amp;gt;</description><content type="text/html" mode="escaped"><![CDATA[<p><a href="https://blog.sergeantbiggs.net/posts/hardening-admin-access-with-nginx/">In the first post of this series</a>
I wrote about web applications that use a POST request with
<code>application/x-www-form-urlencoded</code> to send their credentials to the server.
Now, how about web applications that use something else to accomplish this? I
have one web application that sends the username and password as JSON, and
natively Nginx can not deal with that. As a solution, I decided to use
<a href="https://openresty.org/">OpenResty</a>. OpenResty is a Lua web application server
based on Nginx. I&rsquo;ve been using it for a little bit now, and I&rsquo;m quite happy
with it.</p>
<p>So, now we have to power of Lua at our disposal, how can we deal with this
problem?</p>
<p>Our web application sends a request in the following format:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;Username&#34;</span><span class="p">:</span> <span class="s2">&#34;admin&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;Pw&#34;</span><span class="p">:</span> <span class="s2">&#34;secretpassword&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>To extract the information, we use
<a href="https://github.com/bungle/lua-resty-reqargs">lua-resty-reqargs</a>. This takes
the request, and returns three values (<code>get</code>, <code>post</code>, and <code>files</code>). These are
presented as lua tables. We can then look at the values inside the <code>post</code>
table to get our <code>Username</code> value. If we find a certain string inside our
variable, we send the client a 403.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-lua" data-lang="lua"><span class="line"><span class="cl"><span class="kd">local</span> <span class="n">get</span><span class="p">,</span> <span class="n">post</span><span class="p">,</span> <span class="n">files</span> <span class="o">=</span> <span class="n">require</span> <span class="s2">&#34;resty.reqargs&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">ngx.status</span> <span class="o">=</span> <span class="n">ngx.HTTP_OK</span>
</span></span><span class="line"><span class="cl"><span class="kd">local</span> <span class="n">user_normalised</span> <span class="o">=</span> <span class="n">post.Username</span><span class="p">:</span><span class="n">lower</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">if</span> <span class="n">string.find</span><span class="p">(</span><span class="n">user_normalised</span><span class="p">,</span> <span class="s2">&#34;admin&#34;</span><span class="p">)</span> <span class="kr">then</span>
</span></span><span class="line"><span class="cl">    <span class="n">ngx.status</span> <span class="o">=</span> <span class="mi">403</span>
</span></span><span class="line"><span class="cl">    <span class="n">ngx.exit</span><span class="p">(</span><span class="n">ngx.HTTP_FORBIDDEN</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="kr">end</span>
</span></span></code></pre></div><p>Our Nginx config has the same location block, with the &ldquo;jump&rdquo; to our
<code>@with_admin</code> pseudo-location.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="cl"><span class="k">location</span> <span class="s">/authenticate</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kn">error_page</span> <span class="mi">403</span> <span class="p">=</span> <span class="s">@with_admin</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kn">access_by_lua_file</span> <span class="s">conf/auth/application.lua</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kn">proxy_pass</span> <span class="s">http://application</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">location</span> <span class="s">@with_admin</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kn">allow</span> <span class="n">192.168.1.0</span><span class="s">/24</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kn">allow</span> <span class="n">192.168.10.0</span><span class="s">/24</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kn">deny</span> <span class="s">all</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kn">proxy_pass</span> <span class="s">http://application</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Pretty easy, huh? I look forward to all the additional features OpenResty has
to offer.</p>
]]></content></item><item><title>Hardening Admin Access With Nginx</title><link>https://blog.sergeantbiggs.net/posts/hardening-admin-access-with-nginx/</link><pubDate>Tue, 18 Oct 2022 19:26:55 +0200</pubDate><author>SergeantBiggs</author><guid>https://blog.sergeantbiggs.net/posts/hardening-admin-access-with-nginx/</guid><description>&amp;lt;no value&amp;gt;</description><content type="text/html" mode="escaped"><![CDATA[<p>I run some web applications on my server, and a lot of these applications have
admin interfaces. Since I don&rsquo;t need to have admin access frequently, I want
these interfaces to only be accessible from my LAN, not from the Internet. This
needs different approaches, because each web application is different. In
this post, I want to showcase some of these approaches. First we&rsquo;ll show
a simple example. After that, we will move on to more complex ones.</p>
<p>The simplest example is a web application that exposes its admin interface on a
single URI. This is easy, because we can just define that URI as a
location block, and then put the specific security options in that block.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="cl"><span class="k">upstream</span> <span class="s">application</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kn">server</span> <span class="s">unix://var/run/application.socket</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">server</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kn">server_name</span> <span class="s">application.example.com</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kn">location</span> <span class="s">/admin/</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kn">allow</span> <span class="n">192.168.1.0</span><span class="s">/24</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kn">deny</span> <span class="s">all</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kn">proxy_pass</span> <span class="s">http://application</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kn">location</span> <span class="s">/</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kn">proxy_pass</span> <span class="s">http://application</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>This works very well if your application only exposes admin access on a certain
endpoint. Most applications don&rsquo;t do this. Frequently they will
have separate login and admin panel URLs. So even if you protect the admin
panel, login as an administrative user is still possible.</p>
<p>To solve this problem, we first have to create a <code>location</code> block for the login
endpoint. In
this location block we disallow logging in from the internet for administrative
users. To figure out how to do that, we have to understand the login process.</p>
<p>Most web applications will use a POST request with
<code>application/x-www-form-urlencoded</code>. In this case, the body of the HTTP message
is a string and contains all information as key-value pairs, separated by
ampersands (&amp;). In the case of a login, this is something like:</p>
<pre tabindex="0"><code class="language-urlencode" data-lang="urlencode">userName=admin&amp;password=rosebud
</code></pre><p>We need the name of the key to parse it into Nginx. The best way of doing that
is with the developer tools of your <a href="https://www.mozilla.org/en-US/firefox/new/">favorite
browser</a>. Use the <code>Network</code> tab
while logging in, and just look at the request body of the relevant POST
request (this is also a good way to get the URL for your location block).</p>
<p>Nginx can&rsquo;t natively parse the bodies of these requests, but <a href="https://github.com/calio/form-input-nginx-module">there is a module
that can</a>. Please refer to
the GitHub page for installation instructions.</p>
<p>This module is super
easy to use. We just specify the key of the value we want to have, and the
module will save it in an Nginx variable.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="cl"><span class="k">set_form_input</span> <span class="nv">$userName</span><span class="p">;</span>
</span></span></code></pre></div><p>After we have parsed the correct variable, we just need to determine if someone
is trying to log on as our admin user. We can do that with an <code>if</code> statement.</p>
<p><strong>NB:</strong> <code>if</code> statements are notorious in Nginx as something you need to be
careful about: there is even a whole article about how <a href="https://www.nginx.com/resources/wiki/start/topics/depth/ifisevil/">if is
evil in location contexts</a>.
If you want to fully understand why I do what I do, please read that piece of
documentation.</p>
<p><strong>tldr; it&rsquo;s fine to use it in location contexts if there&rsquo;s no alternative
module, and if we only return to another location in the body of the
if statement.</strong></p>
<p>This is realised by defining our target location as an error page.
We then <code>return</code> that error. In my first version, my location block looked
like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="cl"><span class="k">location</span> <span class="s">/user/login</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kn">set_form_input</span> <span class="nv">$userName</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kn">error_page</span> <span class="mi">403</span> <span class="p">=</span> <span class="s">@with_admin</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kn">if</span> <span class="s">(</span><span class="nv">$user_name</span> <span class="p">=</span> <span class="s">admin)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kn">return</span> <span class="mi">403</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="kn">proxy_pass</span> <span class="s">http://application</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>This location blog has a problem though: can you figure out what it is?</p>
<p>The problem is that this will allow an attacker to put some whitespace
around the string. This will not match our string, and since most web
applications strip whitespace in login fields, the login will still work.</p>
<p>Thanks to <a href="https://twitter.com/jbesendorf">Besen</a> for spotting this problem!</p>
<p>It&rsquo;s better to use substring matching. This will stop an attacker from using
any weird characters before or after the string, that might be stripped by the
application itself:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="cl"><span class="k">location</span> <span class="s">/user/login</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kn">set_form_input</span> <span class="nv">$userName</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kn">error_page</span> <span class="mi">403</span> <span class="p">=</span> <span class="s">@with_admin</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kn">if</span> <span class="s">(</span><span class="nv">$user_name</span> <span class="p">~</span> <span class="sr">admin)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kn">return</span> <span class="mi">403</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="kn">proxy_pass</span> <span class="s">http://application</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>After that we just need to define a location block that our <code>return</code> statement
will jump to. In that block we define our security settings, and pass the
credentials on to our proxied application.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="cl"><span class="k">location</span> <span class="s">@with_admin</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kn">allow</span> <span class="n">192.168.1.0</span><span class="s">/24</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kn">deny</span> <span class="s">all</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kn">proxy_pass</span> <span class="s">http://application</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>As a recap, the full configuration will look something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="cl"><span class="k">upstream</span> <span class="s">application</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kn">server</span> <span class="s">unix://var/run/application.socket</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">server</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kn">server_name</span> <span class="s">application.example.com</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kn">location</span> <span class="s">/admin/</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kn">allow</span> <span class="n">192.168.1.0</span><span class="s">/24</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kn">deny</span> <span class="s">all</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kn">proxy_pass</span> <span class="s">http://gitea</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kn">location</span> <span class="s">/user/login</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kn">set_form_input</span> <span class="nv">$userName</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="kn">error_page</span> <span class="mi">403</span> <span class="p">=</span> <span class="s">@with_admin</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kn">if</span> <span class="s">(</span><span class="nv">$user_name</span> <span class="p">~</span> <span class="sr">admin)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="kn">return</span> <span class="mi">403</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="kn">proxy_pass</span> <span class="s">http://application</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kn">location</span> <span class="s">@with_admin</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kn">allow</span> <span class="n">192.168.1.0</span><span class="s">/24</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kn">deny</span> <span class="s">all</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kn">proxy_pass</span> <span class="s">http://application</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kn">location</span> <span class="s">/</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kn">proxy_pass</span> <span class="s">http://application</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>In that way, we protect our admin interface and only allow logging in as the
admin user from a LAN.</p>
<p>This will not work for all web applications. Some applications use <code>JSON</code> to
communicate with their backend. As far as I know Nginx does not have a native
way to deal with this. To deal with this, we will use
<a href="https://openresty.org/en/">OpenResty</a>. This is an application that turns Nginx
into a lua application server. I will write about this topic in a future blog
post, so stay tuned if this is something that interests you!</p>
]]></content></item><item><title>Simple Launcher for Sway</title><link>https://blog.sergeantbiggs.net/posts/simple-launcher-for-sway/</link><pubDate>Sun, 02 Oct 2022 15:36:05 +0200</pubDate><author>SergeantBiggs</author><guid>https://blog.sergeantbiggs.net/posts/simple-launcher-for-sway/</guid><description>&amp;lt;no value&amp;gt;</description><content type="text/html" mode="escaped"><![CDATA[<p>I wanted to share something that has been in my i3/sway config for quite a
while. I don&rsquo;t know where I got it from exactly, but I&rsquo;ve found it quite useful
over the years. It uses binding modes to create a simple program launcher.</p>
<p>Binding modes deactivate all normal bindings when switching to them. Only the
bindings defined in the binding mode are active.</p>
<pre tabindex="0"><code>set $mode_launcher &#34;Start: [f]irefox [c]hat [k]eepassxc [q]uit&#34;

mode $mode_launcher {
    bindsym f exec firefox
    bindsym c exec ~/.config/sway/scripts/social.sh
    bindsym k exec keepassxc

    bindsym Return mode &#34;default&#34;
    bindsym Escape mode &#34;default&#34;
    bindsym $mod+o mode &#34;default&#34;
    bindsym q mode &#34;default&#34;
}

bindsym $mod+o mode $mode_launcher
</code></pre><p>After going into the mode with <code>$mod+0</code>, we can start the applications with the
relevant keys. The mode can be exited using <code>Return</code>, <code>Esc</code>, <code>$mod+o</code> or <code>q</code>.</p>
]]></content></item><item><title>Path-Based Activation of systemd Units</title><link>https://blog.sergeantbiggs.net/posts/systemd-path-based-activation/</link><pubDate>Sun, 25 Sep 2022 15:51:13 +0200</pubDate><author>SergeantBiggs</author><guid>https://blog.sergeantbiggs.net/posts/systemd-path-based-activation/</guid><description>&amp;lt;no value&amp;gt;</description><content type="text/html" mode="escaped"><![CDATA[<p>I recently learned about a really nice feature of systemd:</p>
<p><a href="https://man.archlinux.org/man/systemd.path.5">Path-based activation of units</a>.</p>
<p>This feature allows systemd to monitor files or paths, and start a unit when the
path exists, or something in it is changed. It has different options:</p>
<ul>
<li>PathExists: triggers if path exists.</li>
<li>PathExistsGlob: triggers if a file that matches a glob exists.</li>
<li>PathModified: triggers if path is changed (every write).</li>
<li>PathChanged: triggers if path is changed (not every write, only when file is
closed).</li>
<li>DirectoryNotEmpty: triggers if directory contains at least one file.</li>
</ul>
<p>This is done (in typical systemd
fashion), by creating a .path file that corresponds to a specific service file.</p>
<p>I&rsquo;ve been using it for some of my units that have their data storage on an
external mount point, that due to encryption, is only mounted manually after
booting the machine.</p>
<p>One service that uses this is my Jellyfin instance. So, to make sure Jellyfin
only starts after the volume is mounted (eg. the path exists), we can do the
following thing:</p>
<p>First, we create a .path file:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">systemctl edit --full --force jellyfin.path
</span></span></code></pre></div><p>In that path file, we include the following lines:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[Path]</span>
</span></span><span class="line"><span class="cl"><span class="na">PathExists</span><span class="o">=</span><span class="s">/media/zfsnas/cryptset/smb/shared</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[Install]</span>
</span></span><span class="line"><span class="cl"><span class="na">WantedBy</span><span class="o">=</span><span class="s">multi-user.target</span>
</span></span></code></pre></div><p>This is pretty much the simplest form of creating a .path unit. See the
<a href="https://man.archlinux.org/man/systemd.path.5">manpage</a> for more options.</p>
<p>The important part is the <code>WantedBy=</code> in the install section. This (like with
all units) allows us to enable the units, and will then start them when it
enters multi-user mode.</p>
<p>We can then enable the .path file, and disable the regular .service file:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">systemctl disable jellyfin.service
</span></span><span class="line"><span class="cl">systemctl <span class="nb">enable</span> jellyfin.path
</span></span></code></pre></div><p>After that, jellyfin.service will wait for the specified path to exist before
starting. I really like this feature, and I can already think of a few things
that this would be handy for (monitoring files and directories, automating
backups to external drives, etc).</p>
<p>I&rsquo;ll keep you posted if I do anything interesting with it!</p>
]]></content></item><item><title>Overlay Bar for Sway</title><link>https://blog.sergeantbiggs.net/posts/overlay-bar-for-sway/</link><pubDate>Wed, 21 Sep 2022 15:49:04 +0200</pubDate><author>SergeantBiggs</author><guid>https://blog.sergeantbiggs.net/posts/overlay-bar-for-sway/</guid><description>&amp;lt;no value&amp;gt;</description><content type="text/html" mode="escaped"><![CDATA[<p>I recently found out about a cool software that I hadn&rsquo;t heard of until now. It
is right up my alley, and I wanted to write this short post to gush about it
for a bit.</p>
<p>The program is called <a href="https://github.com/francma/wob">Wayland Overlay Bar
(wob)</a>. It can be used to create simple
progress/status bars for programs. The nice thing about it, is that it just
takes an integer on a named pipe, and displays the value of that integer as a
graphical bar.</p>
<p>Installation and configuration is very simple. After installing the program,
you can create a named pipe, and connect that pipe to the stdin of wob:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mkfifo /tmp/wobpipe
</span></span><span class="line"><span class="cl">tail -f /tmp/wobpipe <span class="p">|</span> wob
</span></span></code></pre></div><p>For convenience, wob also provides a systemd socket file. The command below
creates a wob pipe under <code>$XDG_RUNTIME_DIR/wob.socket</code> that is accessible to
the current user.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">systemctl --user <span class="nb">enable</span> wob.socket --now
</span></span></code></pre></div><p>I mainly use it for volume and brightness control. To make it work in sway,
only a slight modification of my commands was necessary:</p>
<pre tabindex="0"><code class="language-i3" data-lang="i3">set $WOBSOCK $XDG_RUNTIME_DIR/wob.sock

bindsym XF86AudioLowerVolume exec pamixer --decrease 5 &amp;&amp; pamixer --get-volume &gt; $WOBSOCK
bindsym XF86AudioRaiseVolume exec pamixer --increase 5 &amp;&amp; pamixer --get-volume &gt; $WOBSOCK
bindsym XF86MonBrightnessDown exec xbacklight -dec 5 &amp;&amp; xbacklight -get &gt; $WOBSOCK
bindsym XF86MonBrightnessUp exec xbacklight -inc 5 &amp;&amp; xbacklight -get &gt; $WOBSOCK
</code></pre><p>I love the simplicity and flexibility of this program. I really see myself
using this often in the future.</p>
]]></content></item><item><title>Period Tracking in a Post-Roe World</title><link>https://blog.sergeantbiggs.net/posts/period-tracking-in-a-post-roe-world/</link><pubDate>Sat, 25 Jun 2022 13:17:12 +0200</pubDate><author>SergeantBiggs</author><guid>https://blog.sergeantbiggs.net/posts/period-tracking-in-a-post-roe-world/</guid><description>&amp;lt;no value&amp;gt;</description><content type="text/html" mode="escaped"><![CDATA[<p>The absolutely despicable decision from the United States Supreme Court has
prompted many on Twitter to share their opinions on how to be &ldquo;safe&rdquo; online.
Rightful anger is turning into an absolutely ridiculous free-for-all, as
semi-qualified people are regurgitating their semi-literate takes unto the World
Wide Web. There&rsquo;s a lot of fear-mongering and false information going around
what kind of online risks people are facing. Often these threads are short and
contain frantic unspecific information that just fuels FUD.</p>
<p>In this article I&rsquo;ll try to give some
information that is actually based on reality. It is always
difficult to give &ldquo;generic&rdquo; advice, because security advice should always be
tailored to a specific situation. I will try to give some advice that will fit
in this situation, and add caveats and further information when necessary.
First, we&rsquo;ll talk about some ways
you can anonymise your &ldquo;research&rdquo; about abortion, then we&rsquo;ll talk about period
tracking and what apps I would personally recommend.</p>
<p><strong>NB:</strong> I can only judge these apps according to their security/privacy. I don&rsquo;t
purport to know much about their usability.</p>
<p>Also, I will try to be as non-technical as possible in this article. This means
that this article will oversimplify several concepts, to hopefully make them clear to
non-technical users. I might follow
this article up with some more technical analysis of period tracking apps.</p>
<p>I will also link several articles from the EFFs excellent <a href="https://ssd.eff.org/en">Surveillance
Self-Defense</a> program.</p>
<h2 id="caveat-app-stores-and-mobile-platforms">Caveat: app stores and mobile platforms<a href="#caveat-app-stores-and-mobile-platforms" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<p>One big caveat for this article are app stores that are primarily found on
Android and iOS. Please keep in mind that on the <a href="https://play.google.com/">Play Store</a> and the <a href="https://www.apple.com/app-store/">App Store</a>
you are <em>always logged in with your account</em>. So there will be record of you
downloading and installing the app. Under Android you can use an alternative app
store called <a href="https://f-droid.org/">F-Droid</a>. This allows you to download and
install applications without a login. I don&rsquo;t use iOS so I don&rsquo;t know this for
sure, but if I understand the Apple ecosystem correctly, something like this
doesn&rsquo;t exist for that platform.</p>
<p>The other caveat is that mobile platforms will save a backup of your apps and
their data in the cloud (Google Drive/iCloud). For iCloud, you can <a href="https://support.apple.com/en-us/HT207689">disable using
iCloud for individual Apps</a>. For
Android this depends on your android version/phone manufacturer.</p>
<p>Think about these things if you decide to use certain apps, like a period
tracker.</p>
<h2 id="researching-topics">Researching topics<a href="#researching-topics" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<p>On the internet, you will find two types of advice on how
to anonymise your browsing. One will suggest using a VPN, the other using Tor.
Both of these tools have their own advantages and disadvantages, and each have
to be used correctly to be effective. I personally prefer Tor, since VPNs have
some very big downsides that aren&rsquo;t immediately apparent to non-technical users.
I still mention them here, because they are often the first type of tool people
hear about, and I want to caution against them.</p>
<p>Please also note that any anonymising way falls flat if you identify yourself
via another way. This should be a no-brainer, but if you log into a website via a
VPN or Tor, you are no longer &ldquo;anonymous&rdquo; to that website.</p>
<h3 id="vpn">VPN<a href="#vpn" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h3>
<p>VPNs are a technology that can be used for different purposes. In our case, a VPN
anonymises your traffic to
your ISP. <a href="https://blog.cloudflare.com/dns-encryption-explained/">Because of the way the internet
works</a>, your ISP can see
all websites
you visit, but not what you do on the websites itself.
They see that you visited google.com, but not what you search. They will
see that you visited youtube.com, but not what videos you watch, etc.</p>
<p>If this is a concern for you, you can use a VPN. This will make your ISP unable
to see which websites you visit. But this will just &ldquo;shift&rdquo; the traffic to your
VPN provider, which essentially (for these purposes) <em>becomes</em> your ISP. This
means you need to <em>trust your VPN provider as much as your ISP, if not more</em>.
This means you really need to research which VPN you want to use. In general,
the way I understand the situation in the US, your data will not be safe from
the government because courts can get to that data via subpoena. So you will
want to find a VPN provider that doesn&rsquo;t save any data. But even if your
provider says they don&rsquo;t save any data, there is no way to know that they are
not lying. Because if they have
data, they <em>will</em> have to give it away. Another way is to use a provider where
jurisdiction by US courts does not extend. This is difficult to gauge.</p>
<p>One advantage a VPN has over Tor, is that it tunnels your complete traffic, not
only your web browsing.</p>
<p>In general I would not recommend a VPN, unless you know what you are doing and
you fall in the category I describe above.</p>
<p>For more information, you can read the EFFs guide on <a href="https://ssd.eff.org/en/module/choosing-vpn-thats-right-you">choosing the right VPN</a>.</p>
<h3 id="tor">Tor<a href="#tor" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h3>
<p>This is a protocol and a browser that completely anonymises your traffic. It is
impossible for anyone to uniquely identify the websites you visit, and it is
also impossible for the websites to identify you. Tor can be downloaded on the
website of the <a href="https://www.torproject.org/download/">Tor Project</a>. I would
recommend using the Tor Browser. In this application, the protocol is bundled
with a special browser that also has several different anonymising settings. Tor
is different from a VPN in that it encrypts your connection in multiple layers,
and then sends it along multiple other computers before it reaches the website
you are opening. This means your traffic is fully anonymised (there are some
things to be considered still, these are described in the <a href="https://support.torproject.org/faq/">Tor FAQ</a>).</p>
<p>One caveat about Tor is that it can be quite slow. It bounces your
connection around several other computers, so you will have quite high latency
(&ldquo;ping&rdquo;) and slower download speeds. This means that you will &ldquo;reach&rdquo; websites
slower and they will also &ldquo;render&rdquo; slower on your device. Another is that
it only works for that Browser. Other traffic (from programs on your computer)
will not be anonymous.</p>
<p>Here are some guides on how to install Tor Browser:</p>
<ul>
<li><a href="https://ssd.eff.org/en/module/how-use-tor-windows">Windows</a></li>
<li><a href="https://ssd.eff.org/en/module/how-use-tor-macos">MacOS</a></li>
<li><a href="https://support.torproject.org/tormobile/tormobile-7/">Android</a></li>
<li><a href="https://onionbrowser.com/">iOS</a></li>
</ul>
<p>For mobile platforms also make sure you understand the
<a href="#caveat-app-stores-and-mobile-platforms">caveats</a>.</p>
<h2 id="period-tracking">Period tracking<a href="#period-tracking" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<p>I&rsquo;ve seen advice ranging from &ldquo;delete all of your period trackers&rdquo; to &ldquo;this
specific app is perfectly safe and you should definitely use it&rdquo;.</p>
<p>The first advice (delete all of your apps) is of course very safe. It&rsquo;s also
absolutely bonkers, and it doesn&rsquo;t help users at all. In my opinion, if you want
to blurt that hot take into the public sphere, you might as well not say
anything at all.</p>
<p>Now that we&rsquo;ve dealt with that, some apps. As you can guess, I&rsquo;m a bit out of my
depth with this topic. I&rsquo;m not a health expert, and I can&rsquo;t meaningfully test
these apps. There seem to be two &ldquo;big&rdquo; apps that are currently circulating. I&rsquo;ll
write my opinion for each one. The first one (Clue) seems to be the more popular one,
and the second one (Drip) seems to be the more privacy-focused one.</p>
<h3 id="clue">Clue<a href="#clue" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h3>
<p><a href="https://helloclue.com/">Clue</a> is a commercial period and fertility tracking app
from Berlin. They are currently heavily marketing their app to a US audience via
<a href="https://twitter.com/clue/status/1521859643055685636">Twitter posts</a> and <a href="https://helloclue.com/articles/abortion/clue-s-response-to-roe-vs-wade">press
releases</a>.
In these press releases they especially keep hammering home that they are a
European company that falls under GDPR. GDPR is a big topic that I can&rsquo;t address
in this article, but suffice to say it is a quite strict data protection
regulation that sets rules how EU companies have to handle data from EU
residents. It sets a lot of restrictions and some rights that consumers have.
There are two important things to know about this. The first one is:</p>
<p><strong>GDPR does not apply to non-EU residents and citizens.</strong></p>
<p>While Clue probably does not treat &ldquo;American&rdquo; data different from &ldquo;EU&rdquo; data,
because that would just mean more work, they are perfectly able to do so. In
practice this caveat likely does not matter. There is another problem that is
likely more important:</p>
<p><strong>GDPR has exceptions for law enforcement.</strong></p>
<p>In Germany (where Clue is based), courts can request documents and information.
Companies then have to provide this information. As far as I understand, this
also applies to US law enforcement, because of Mutual Legal Assistance Treaties.
In general, Germany does not require that something is illegal in Germany for
them to serve document requests through these treaties.</p>
<p>Clue is currently misleading about this topic. They claim that they will only
serve <a href="https://twitter.com/clue/status/1522153112127082499">EU and German
authorities</a>. This is
<em>technically</em> correct, but does not matter in practice. If i understand the
process correctly, US law enforcement will contact
German law enforcement, that will then contact the company. My information about
this process is purely theoretical. I&rsquo;m not a lawyer, so I don&rsquo;t know if this
actually happens, or will ever happen. I just want to shine a light on the fact
that it is not as easy as &ldquo;we are in the EU and GDPR is very good&rdquo;. GDPR does
not protect you as an non-resident and non-citizen of the EU. GDPR also does not
protect you from criminal and civil liability!</p>
<p>All in all, this does not mean you should absolutely not use Clue. There are
still some good arguments for using it over US-based applications. Just think
about the consequences of using it, and try to think about your specific threat
model.</p>
<h3 id="drip">Drip<a href="#drip" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h3>
<p><a href="https://bloodyhealth.gitlab.io/">Drip</a> is a privacy-focused and open source
application. Open source means that the source code for the application can be
read and changed by anyone. <a href="https://bloodyhealth.gitlab.io/privacy-policy.html">Their privacy policy is refreshingly
short</a>. This is because they
<em>don&rsquo;t collect any data</em>. All data is kept locally on the device, and can be
deleted from inside the app. This means that they <em>can&rsquo;t</em> give any data to law
enforcement, even if there is a subpoena. (An example of this
strategy can be seen in the cases where <a href="https://signal.org/bigbrother/central-california-grand-jury/">Signal was
subpoenaed</a>) The
app is funded by Mozilla and the German
government (through Prototype Fund).</p>
<p>The app has some problems that may or may not be a deal breaker:</p>
<ul>
<li>The newest version is only available via Google Play</li>
</ul>
<p>They also provide instructions on their website to manually install the latest
version, so this problem can be partly circumvented.</p>
<ul>
<li>There is no iOS version currently</li>
</ul>
<p>The development team is working on an iOS version, but it is not available at
the moment. Even if they create an iOS version, there will not a be way to
download this version anonymously (see
<a href="#caveat-app-stores-and-mobile-platforms">caveats</a> for more information).</p>
<p>An additional deal breaker for an American audience might be that this app does not
currently allow you to enter Temperature in Fahrenheit. There have been some
feature requests for this, but there is currently no one working on this feature
(since the team seems to be busy with releasing an iOS version).</p>
<h2 id="conclusion">Conclusion<a href="#conclusion" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<p>The internet contains a lot of information. Some of it is good, but a lot of it
is bad, especially when people work themselves up to a frenzy. I hope that the
advice in this article will be useful to people. Good luck, and stay safe out
there!</p>
]]></content></item><item><title>Deploying an ISO to Nextcloud</title><link>https://blog.sergeantbiggs.net/posts/deploying-an-iso-to-nextcloud/</link><pubDate>Mon, 20 Jun 2022 11:10:54 +0200</pubDate><author>SergeantBiggs</author><guid>https://blog.sergeantbiggs.net/posts/deploying-an-iso-to-nextcloud/</guid><description>&amp;lt;no value&amp;gt;</description><content type="text/html" mode="escaped"><![CDATA[<p>I recently wrote an <a href="/posts/making-a-custom-archlinux-based-live-system/">article</a>
on building a custom ArchLinux-based live distro with Drone. This setup has worked quite well for
me, and it saved me a lot of time in the long run. There&rsquo;s just one thing that
bothers me about it: after it is built, the ISO is copied to an open directory.
I can then download the ISO from there and flash it onto a USB. I wanted to
make sure that the ISO is actually &ldquo;deployed&rdquo; on all of my devices after it&rsquo;s
built, so I don&rsquo;t have to download it manually. This also removes the
possibility of there being multiple versions on multiple machines, and saves me
the hassle of always having to remember to download it.</p>
<p>I decided the best way of doing this would be via NextCloud. I have a NextCloud
server, which syncs important files between my devices. This would be the
perfect tool for &ldquo;deploying&rdquo; the new ISO to all of the devices that it is
needed on.</p>
<p>Nextcloud has an <a href="https://docs.nextcloud.com/server/latest/developer_manual/client_apis/WebDAV/basic.html">API for their WebDAV
implementation</a>.
This allows us to use WebDav operations to download, upload, and change files
on the server. This is pretty much ideal for our use case. Since WebDAV is an
extension of HTTP, I can use my favourite API client (curl + bash).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/usr/bin/env bash
</span></span></span><span class="line"><span class="cl"><span class="cp"></span><span class="nv">USERNAME</span><span class="o">=</span><span class="s2">&#34;clouduser&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">FILE</span><span class="o">=</span><span class="s2">&#34;spart.iso&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">curl -u <span class="s2">&#34;</span><span class="si">${</span><span class="nv">USERNAME</span><span class="si">}</span><span class="s2">:</span><span class="si">${</span><span class="nv">PASSWORD</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    https://cloud.example.com/remote.php/dav/files/clouduser/ISO/<span class="s2">&#34;</span><span class="nv">$FILE</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    -T <span class="s2">&#34;</span><span class="nv">$BUILD_DIR</span><span class="s2">/out/</span><span class="nv">$FILE</span><span class="s2">&#34;</span>
</span></span></code></pre></div><p>Our client will need a password. To provide this, we can create an
Application Password in NextCloud. After generating it, we will use Drone to
provide the password to our script. We don&rsquo;t want to store our password in
plain text, so we can use drones <a href="https://docs.drone.io/secret/repository/">secret
management</a>. Secrets are defined per
repository under <em>settings -&gt; secrets</em>. After adding our secret, we can use it
in our pipeline.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">pipeline</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">exec</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">default</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">steps</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">upload iso to nextcloud</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">commands</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">./extra/upload/upload-to-nextcloud.sh</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BUILD_DIR</span><span class="p">:</span><span class="w"> </span><span class="l">/media/fast/cryptset/builds/spart</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">PASSWORD</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">from_secret</span><span class="p">:</span><span class="w"> </span><span class="l">nc_password</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">trigger</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">branch</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">main</span><span class="w">
</span></span></span></code></pre></div><p>Drone continues to be a very useful tool, and I look forward to
doing more stuff with it. I hope you enjoyed reading this article!</p>
]]></content></item><item><title>Automated Headless Unity Builds</title><link>https://blog.sergeantbiggs.net/posts/automated-headless-unity-builds/</link><pubDate>Mon, 11 Apr 2022 12:34:25 +0200</pubDate><author>SergeantBiggs</author><guid>https://blog.sergeantbiggs.net/posts/automated-headless-unity-builds/</guid><description>&amp;lt;no value&amp;gt;</description><content type="text/html" mode="escaped"><![CDATA[<p>I was recently developing some games with some friends of mine in
<a href="https://unity.com/">Unity</a>. This was a multiplayer game that needed to be deployed
to my server. I was in charge of the server and I was very annoyed with
the fact that I always had to build it on my local machine, and copy it over to
the server to run it. I was thinking of a way to automate it, using the Gitea +
Drone setup that I&rsquo;m running on my server.</p>
<p>Unity has some support for CLI workflows, but it is a bit limited, and
difficult to work with. Since my server runs headlessly, I will try to run the
application completely headless as well.</p>
<p>The first problem was getting the correct Unity version. Unity is quite picky
about its versions, and I wanted to make sure I got the same minor release.
Since I&rsquo;m on Arch,
there are 2 different ways of getting the version. I could use the <a href="https://aur.archlinux.org/packages/unity-editor">AUR
package</a> and change the
PKGBUILD to the version I want. Or I could use the <a href="https://unity.com/unity-hub">Unity Hub</a>. This
would also enable me to install different versions, and manage the modules for
each installed version. I decided to use the unity
hub, because of the higher flexibility. It can be installed with this <a href="https://aur.archlinux.org/packages/unityhub">AUR package</a>.</p>
<h2 id="x-problems">X problems<a href="#x-problems" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<p>The first problem that presented itself is when I tried to run Unity Hub in
headless mode. It just errors out with the following message:</p>
<pre tabindex="0"><code>(unityhub-bin:192687): Gtk-WARNING **: 13:06:59.647: cannot open display:
</code></pre><p>The problem is that Unity Hub tries to initialize an X display even if it
doesn&rsquo;t actually output any graphics. To fix this, we can provide it with a
<em>virtual X server</em>. One option is to use
<a href="https://en.wikipedia.org/wiki/Xvfb">Xvfb</a>. This provides a virtual display
server that performs all graphical operations in virtual memory without showing
any screen output. With this we can &ldquo;trick&rdquo; unityhub into thinking we have a
graphical display. Arch provides this package with <code>xorg-server-xvfb</code>. After
installing it, we can use the convenience script called <code>xvfb-run</code>:</p>
<pre tabindex="0"><code>xvfb-run unityhub --headless help
</code></pre><p>After that, we can install the Unity version we need (2021.2.0f1 in our case).
Because this is not one of the &ldquo;recommended&rdquo; versions that Unity Hub includes
by default, we also need to specify the changeset. I got the specific changeset
from the <a href="https://unity3d.com/get-unity/download/archive">Unity archive</a>. I
just looked at the URL for the windows Unity editor downloads and used the
changeset from there. In my case this is 4bf1ec4b23c9. We also install the
module to create linux builds. (<code>--childmodules</code>
automatically installs al child modules of the selected modules)
This makes the full command the following:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">xvfb-run unityhub --headless install --version 2021.2.0f1 --changeset 4bf1ec4b23c9 --module linux-il2cpp --childmodules
</span></span></code></pre></div><h2 id="activation">Activation<a href="#activation" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<p>After installing we need to activate our Unity license. This is a bit
complicated, and the exact setup depends highly on the specific use case. In my
case, I&rsquo;m using the free, non-commercial licenses that are tied to a Unity
account. So I can&rsquo;t just specify a serial number to activate my license (that
only works for pro licenses). The other option is to specify your account
details on the command line. Since I&rsquo;m running this whole process in a build
pipeline, I don&rsquo;t want to do this.
So I decided to follow the steps for
<a href="https://docs.unity3d.com/Manual/ManualActivationGuide.html#alf-commandline">offline activation</a>.
In the first step, we need to generate a license activation file for our
specific editor version. Unity Hub installs the editor into the users home
directory at <code>$HOME/Unity/Hub/Editor/2021.2.0f1/Editor/Unity</code></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">Unity -batchmode -nographics -createManualActivationFile
</span></span></code></pre></div><p>This will create a <code>.alf</code> file in the current directory. We then need to
download this file to a computer with a web browser. In the web browser we can
then request a Unity license file at the following
<a href="https://license.unity3d.com/manual">URL</a>. After copying the license file to
our server again, we can activate it with the following command:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">Unity -batchmode -nographics -manualLicenseFile &lt;file&gt;
</span></span></code></pre></div><p>After activating Unity, we can start trying to build our application. This took
quite some trial and error, since the <a href="https://docs.unity3d.com/Manual/CommandLineArguments.html">Unity
CLI</a> is limited in
its functionality, and not particularly well documented. There are some options
we always need to add if we want to do a headless build:</p>
<ul>
<li><code>-batchmode</code>: runs all unity workflows sequentially</li>
<li><code>-nographics</code>: does not initialize any graphics (eg: the editor window)</li>
<li><code>-accept-apiupdate</code>: Make sure that the APIUpdater runs</li>
<li><code>-quit</code>: quit after running all batch mode workflows</li>
</ul>
<p>Unity also has some generic build arguments and targets:</p>
<ul>
<li>Standalone</li>
<li>Win</li>
<li>Win64</li>
<li>OSXUniversal</li>
<li>Linux64</li>
<li>iOS</li>
<li>Android</li>
<li>WebGL</li>
<li>WindowsStoreApps</li>
<li>tvOS</li>
</ul>
<p>This doesn&rsquo;t allow us to control more specific build options (subtargets,
scenes, etc). To do this, we need to write a C#-Script that defines the
options. This script needs to be in a specific folder in your project folder
(<code>Assets/Editor</code>). In that script you can specify your needed build options. My
script looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">System.Collections</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">System.Collections.Generic</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">UnityEditor</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">UnityEngine</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">public</span> <span class="kd">static</span> <span class="k">class</span> <span class="nc">BuildSystem</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">static</span> <span class="kt">string</span><span class="p">[]</span> <span class="n">sceneList</span> <span class="p">=</span> <span class="p">{</span> <span class="s">&#34;Assets/Scenes/SampleScene.unity&#34;</span> <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">static</span> <span class="k">void</span> <span class="n">BuildLinuxHeadless</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">report</span> <span class="p">=</span> <span class="n">BuildPipeline</span><span class="p">.</span><span class="n">BuildPlayer</span><span class="p">(</span><span class="k">new</span> <span class="n">BuildPlayerOptions</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">locationPathName</span> <span class="p">=</span> <span class="s">&#34;../target/server&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">scenes</span> <span class="p">=</span> <span class="n">sceneList</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">target</span> <span class="p">=</span> <span class="n">BuildTarget</span><span class="p">.</span><span class="n">StandaloneLinux64</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">subtarget</span> <span class="p">=</span> <span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="n">StandaloneBuildSubtarget</span><span class="p">.</span><span class="n">Server</span>
</span></span><span class="line"><span class="cl">        <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">Debug</span><span class="p">.</span><span class="n">Log</span><span class="p">(</span><span class="s">$&#34;Build result: {report.summary.result}, {report.summary.totalErrors} errors&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">report</span><span class="p">.</span><span class="n">summary</span><span class="p">.</span><span class="n">totalErrors</span> <span class="p">&gt;</span> <span class="m">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">EditorApplication</span><span class="p">.</span><span class="n">Exit</span><span class="p">(</span><span class="m">1</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>After that, we can execute the script with <code>-executeMethod</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">Unity -batchmode -nographics -projectPath &lt;project_path&gt; -executeMethod BuildSystem.BuildLinuxHeadless -accept-apiupdate -quit
</span></span></code></pre></div><p>The build should spit out a working player after that. If you want to customise
build options, just adjust the script accordingly.</p>
<h2 id="pipeline">Pipeline<a href="#pipeline" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<p>This is an example Drone pipeline based on this script, and a systemd service
file that starts the server.</p>
<p>game-server.service</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[Unit]</span>
</span></span><span class="line"><span class="cl"><span class="na">Description</span><span class="o">=</span><span class="s">game Server</span>
</span></span><span class="line"><span class="cl"><span class="na">After</span><span class="o">=</span><span class="s">network-online.target</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[Service]</span>
</span></span><span class="line"><span class="cl"><span class="na">ExecStart</span><span class="o">=</span><span class="s">/srv/user/game/game-server</span>
</span></span><span class="line"><span class="cl"><span class="na">Type</span><span class="o">=</span><span class="s">exec</span>
</span></span><span class="line"><span class="cl"><span class="na">Environment</span><span class="o">=</span><span class="s">&#34;TERM=xterm&#34;</span>
</span></span><span class="line"><span class="cl"><span class="na">WorkingDirectory</span><span class="o">=</span><span class="s">/srv/user/game/</span>
</span></span><span class="line"><span class="cl"><span class="na">User</span><span class="o">=</span><span class="s">user</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[Install]</span>
</span></span><span class="line"><span class="cl"><span class="na">WantedBy</span><span class="o">=</span><span class="s">default.target</span>
</span></span></code></pre></div><p>.drone.yml</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">pipeline</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">exec</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">default</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">steps</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">copy build files</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">commands</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">install -m 750 -d /var/lib/drone-runner-exec/builds/game</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">install -m 750 -d /var/lib/drone-runner-exec/builds/game/src</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">install -m 750 -d /var/lib/drone-runner-exec/builds/game/target</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">cp -r game_project/* /var/lib/drone-runner-exec/builds/game/src</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">build</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">commands</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">export PATH=$PATH:/var/lib/drone-runner-exec/Unity/Hub/Editor/2021.2.0f1/Editor</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">export HOME=/var/lib/drone-runner-exec</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">export XDG_CACHE_HOME=$HOME/.cache</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">Unity -batchmode -nographics -projectPath $PROJECT_PATH -executeMethod BuildSystem.BuildLinuxHeadless -accept-apiupdate -quit &amp;&gt; $HOME/builds/game/build.log</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">PROJECT_PATH</span><span class="p">:</span><span class="w"> </span><span class="l">/var/lib/drone-runner-exec/builds/game/src</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">TARGET_PATH</span><span class="p">:</span><span class="w"> </span><span class="l">/var/lib/drone-runner-exec/builds/game/target</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">stop server</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">commands</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">sudo systemctl stop game-server.service</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">copy server files</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">commands</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">install -m 750 -g builders -d &#34;$DEPLOY_PATH&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">install -m 750 -g builders -d &#34;$DEPLOY_PATH/game-server_Data&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">install -m 750 -g builders &#34;$TARGET_PATH/game-server&#34; &#34;$DEPLOY_PATH&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">install -m 750 -g builders &#34;$TARGET_PATH/UnityPlayer.so&#34; &#34;$DEPLOY_PATH&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">cp -r $TARGET_PATH/game-server_Data/* &#34;$DEPLOY_PATH/game-server_Data/&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">TARGET_PATH</span><span class="p">:</span><span class="w"> </span><span class="l">/var/lib/drone-runner-exec/builds/game/target</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">DEPLOY_PATH</span><span class="p">:</span><span class="w"> </span><span class="l">/srv/user/game</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">copy service file</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">commands</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">sudo install -m 644 game-server.service /etc/systemd/system/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">reload systemd daemon</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">commands</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">sudo systemctl daemon-reload</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">start service</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">commands</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">sudo systemctl start game-server.service</span><span class="w">
</span></span></span></code></pre></div><p>Some things to consider about the drone pipeline:</p>
<p>The unity editor needs certain variables set to build correctly. Notably the
following:</p>
<ul>
<li>$HOME</li>
<li>$XDG_CACHE_HOME</li>
</ul>
<p>Also, Unity is <em>very</em> verbose when building. In fact, it is so verbose that the
web console of my drone server couldn&rsquo;t handle it. I decided to write the
output to a file. That way, I can at least see the log of the last build and
why it went wrong. Of course, you can always make this part more complicated,
add some kind of versioned logging facility. But I&rsquo;ll tackle that when I need
such a functionality.</p>
<p>I hope you enjoyed reading this article. Now go out there and create your own
Unity build pipelines!</p>
]]></content></item><item><title>Making a Custom ArchLinux-based Live System</title><link>https://blog.sergeantbiggs.net/posts/making-a-custom-archlinux-based-live-system/</link><pubDate>Thu, 07 Apr 2022 09:37:51 +0200</pubDate><author>SergeantBiggs</author><guid>https://blog.sergeantbiggs.net/posts/making-a-custom-archlinux-based-live-system/</guid><description>&amp;lt;no value&amp;gt;</description><content type="text/html" mode="escaped"><![CDATA[<p>As some of you might know, I work as a system administrator. Currently, most of
my job consists of rather typical &ldquo;Client Management&rdquo; support. I go out and fix
peoples computers. This is something that I also do in my spare time, of
course. One of my favorite tools to do that are live systems. They are
very handy for all kinds of troubleshooting and problem solving. I use them
for:</p>
<ul>
<li>simple data rescue</li>
<li>forensics</li>
<li>creating backups</li>
<li>partitioning</li>
<li>unlocking computers</li>
</ul>
<p>Obviously, they have many more use cases.</p>
<p>I&rsquo;ve been such a big fan of them, that I have quite some collection of live
distros and ISOs. This also shows my biggest problem: there are a lot of
different options to choose from, and none of them do <em>everything</em> I want.
A lot of them are infrequently updated, have very specific usages or are just a
bit weird. There are some exceptions to this rule (looking at you, <a href="https://partedmagic.com/">Parted
Magic</a>), but they do charge a subscription fee to use
the software. So I decided to look into doing this myself.</p>
<p>I had been experimenting with
<a href="https://wiki.archlinux.org/title/Archiso">Archiso</a> for a while to create
custom live systems. I even created some for previous employers. But the
problem I was having was essentially the same as my collection of other ISOs:
They were &ldquo;lying around&rdquo; everywhere. I had no centralized way of
building/deploying them. With an ever growing collection of ISOs an ever
growing mountain of flash drives emerged.</p>
<p>I decided to solve this problem by creating a single distro that I could use
for <em>everything</em>. If I needed a new tool, I would just add it. I combined this
with a <a href="https://www.amazon.de/Verbatim-Dual-USB-Stick-Type-49966/dp/B0749XGV86/">flash drive that I keep on my
key chain</a>.
I decided to do this with <a href="https:/wiki.archlinux.org/title/Archiso">Archiso</a>
again, since I have some experience with it, and I like the build system. The
two things that really tie everything together though, are
<a href="https://gitea.io">Gitea</a> (A self-hosted Git forge)
and <a href="https://drone.io">Drone</a> (a self-hosted CI platform). This allows me to
automate the building process.</p>
<p><strong>NB:</strong> Setting up Archiso is out of the scope of this article. Please see this
<a href="https://wiki.archlinux.org/title/Archiso">excellent article</a> on the Arch Wiki
to get started.</p>
<h2 id="building-the-iso">Building the ISO<a href="#building-the-iso" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<p>Normally, we would just build the ISO locally with <code>mkarchiso</code>. With
<a href="https://drone.io">drone</a>, we can automate this process. I will show my drone
file as an example, but drone has a lot more options. Check out the
<a href="https://docs.drone.io/">documentation</a> if I whet your appetite.</p>
<p>The drone workflow consists of one or more pipelines that each contain steps.
Each step has a name, and one or more associated commands. Let&rsquo;s go through the
steps one by one.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">pipeline</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">exec</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">default</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">steps</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">create chroot</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">commands</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">mkdir /var/lib/drone-runner-exec/buildroot/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">mkarchroot /var/lib/drone-runner-exec/buildroot/root base-devel</span><span class="w">
</span></span></span></code></pre></div><p>This command creates a chroot that we use to build our AUR packages in. For
Archiso, we need to manually build AUR packages and add them to a local
repository.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">download aur packages</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">commands</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">auracle download nwipe</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">auracle download unixbench</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">auracle download stress-ng</span><span class="w">
</span></span></span></code></pre></div><p>Here we download our aur packages using the excellent <a href="https://github.com/falconindy/auracle">auracle</a></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">build stress-ng</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">commands</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">cd stress-ng</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">makechrootpkg -c -r /var/lib/drone-runner-exec/buildroot/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">build unixbench</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">commands</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">cd unixbench</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">makechrootpkg -c -r /var/lib/drone-runner-exec/buildroot/ -- --syncdeps</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">build nwipe</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">commands</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">cd nwipe</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">makechrootpkg -c -r /var/lib/drone-runner-exec/buildroot/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">build owper</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">commands</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">cd extra/owper/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">makechrootpkg -c -r /var/lib/drone-runner-exec/buildroot/ -- --syncdeps</span><span class="w">
</span></span></span></code></pre></div><p>After that, we build the packages in a <a href="https://wiki.archlinux.org/title/DeveloperWiki:Building_in_a_clean_chroot">clean chroot</a>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">add packages to repo</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">commands</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">find . -name &#39;*pkg.tar.zst&#39; -exec cp &#39;{}&#39; /var/lib/drone-runner-exec/builds/spart/aur_repo \;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">cd /var/lib/drone-runner-exec/builds/spart/aur_repo/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">repo-add localaur.db.tar.gz *.pkg.tar.zst</span><span class="w">
</span></span></span></code></pre></div><p>We add the packages to our local repo that pacman can use.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">build iso</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">commands</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">sudo mkarchiso -v -w &#34;/var/lib/drone-runner-exec/builds/spart/work&#34; -o &#34;/var/lib/drone-runner-exec/builds/spart/out&#34; -A SPART -L SPART -P SergeantBiggs .</span><span class="w">
</span></span></span></code></pre></div><p>Building the ISO.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">chown iso</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">commands</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">sudo chown -R drone-runner-exec:builders &#34;/var/lib/drone-runner-exec/builds/spart/out/&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">move iso</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">commands</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">mv /var/lib/drone-runner-exec/builds/spart/out/*.iso /var/www/open.sgnt.link/iso/spart.iso</span><span class="w">
</span></span></span></code></pre></div><p>After that, we move the ISO to a web server. We can then download it once it is
finished.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">delete work directory, repo and chroot</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">commands</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">sudo rm -rf /var/lib/drone-runner-exec/builds/spart/work/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">sudo rm -rf /var/lib/drone-runner-exec/buildroot/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">rm -rf /var/lib/drone-runner-exec/builds/spart/aur_repo/*</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">when</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">status</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">failure</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">success</span><span class="w">
</span></span></span></code></pre></div><p>After everything is finished, we delete the work directory and the chroot. The
&ldquo;when&rdquo; part makes sure that this command is always executed, even if the build
fails.</p>
<p>Here is the complete file, for reference:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">pipeline</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">exec</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">default</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">steps</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">create chroot</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">commands</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">mkdir /var/lib/drone-runner-exec/buildroot/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">mkarchroot /var/lib/drone-runner-exec/buildroot/root base-devel</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">download aur packages</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">commands</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">auracle download nwipe</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">auracle download unixbench</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">auracle download stress-ng</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">build stress-ng</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">commands</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">cd stress-ng</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">makechrootpkg -c -r /var/lib/drone-runner-exec/buildroot/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">build unixbench</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">commands</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">cd unixbench</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">makechrootpkg -c -r /var/lib/drone-runner-exec/buildroot/ -- --syncdeps</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">build nwipe</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">commands</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">cd nwipe</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">makechrootpkg -c -r /var/lib/drone-runner-exec/buildroot/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">build owper</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">commands</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">cd extra/owper/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">makechrootpkg -c -r /var/lib/drone-runner-exec/buildroot/ -- --syncdeps</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">add packages to repo</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">commands</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">find . -name &#39;*pkg.tar.zst&#39; -exec cp &#39;{}&#39; /var/lib/drone-runner-exec/builds/spart/aur_repo \;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">cd /var/lib/drone-runner-exec/builds/spart/aur_repo/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">repo-add localaur.db.tar.gz *.pkg.tar.zst</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">build iso</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">commands</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">sudo mkarchiso -v -w &#34;/var/lib/drone-runner-exec/builds/spart/work&#34; -o &#34;/var/lib/drone-runner-exec/builds/spart/out&#34; -A SPART -L SPART -P SergeantBiggs .</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">chown iso</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">commands</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">sudo chown -R drone-runner-exec:builders &#34;/var/lib/drone-runner-exec/builds/spart/out/&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">move iso</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">commands</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">mv /var/lib/drone-runner-exec/builds/spart/out/*.iso /var/www/open.sgnt.link/iso/spart.iso</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">delete work directory, repo and chroot</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">commands</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">sudo rm -rf /var/lib/drone-runner-exec/builds/spart/work/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">sudo rm -rf /var/lib/drone-runner-exec/buildroot/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">rm -rf /var/lib/drone-runner-exec/builds/spart/aur_repo/*</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">when</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">status</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">failure</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">success</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">trigger</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">branch</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">main</span><span class="w">
</span></span></span></code></pre></div><p>And that&rsquo;s that. I really love this setup. It enables me to add new features
(packages, settings, etc) to my live system and build the ISO automatically.
Afterwards, I can just download the image and copy it to my flash drive.</p>
<p>I hope you enjoyed reading this article!</p>
]]></content></item><item><title>File Sharing with Qemu and Virt-Manager</title><link>https://blog.sergeantbiggs.net/posts/file-sharing-with-qemu-and-virt-manager/</link><pubDate>Wed, 09 Mar 2022 14:52:49 +0100</pubDate><author>SergeantBiggs</author><guid>https://blog.sergeantbiggs.net/posts/file-sharing-with-qemu-and-virt-manager/</guid><description>&amp;lt;no value&amp;gt;</description><content type="text/html" mode="escaped"><![CDATA[<p>I&rsquo;ve been using qemu and libvirt to create and manage virtual
machines under Linux. I&rsquo;m using <a href="https://virt-manager.org/">Virtual Machine
Manager</a> to graphically manage the machines. I&rsquo;ve
been really impressed with qemu + KVM performanace, and Virt-Manager provides a
very nice and easy to use interface. There is just one thing lacking, in
comparison to &ldquo;traditional&rdquo; desktop virtualization software (eg.
<a href="https://www.virtualbox.org/">Virtualbox</a>): there is no pre-configured way to
share files between the host and the guest. In contrast, Virtualbox provides a really easy
way to transfer files. As long as the agent is installed you can just &ldquo;copy
and paste&rdquo; them over, or drag them into the VM from the host (and vice
versa). In this article, I&rsquo;ll try to configure an easy way to share files
between the host and the VM. My host is an ArchLinux system, and the two guests
are Fedora 35 and Windows 10. The steps in this article
should be generic enough to &ldquo;just work&rdquo; on most other distros, but YMMV.</p>
<p><strong>NB</strong>: This is intended as an easy way to share files for classic &ldquo;desktop&rdquo;
virtualization settings. It&rsquo;s not intended for servers, where there are
multiple users on the host or the guests.</p>
<p>We&rsquo;ll be using the same method for both guests, because it works on Linux and
on Windows. Instead of the &ldquo;traditional&rdquo; way (sharing
over the network with SMB, NFS, or 9p), we&rsquo;ll we using a file system
developed by libvirt called
<a href="https://libvirt.org/kbase/virtiofs.html">virtiofs</a>. Virtiofs was developed for
precisely this purpose (&ldquo;Unlike existing approaches, it is designed to offer
local file system semantics and performance.&rdquo;). This makes file sharing
easier to configure, since we don&rsquo;t need to set up a server. It also should
offer very good performance.</p>
<h2 id="host">Host<a href="#host" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<p>To set this up, we first need a folder on the host.
This can be done in several ways. I decided to just create a folder in
<code>/mnt</code>, and change its ownership to my user:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo mkdir /mnt/vfs_share
</span></span><span class="line"><span class="cl">sudo chown foo:foo /mnt/vfs_share
</span></span></code></pre></div><p>Then we need to add a virtiofs device.</p>
<p>Virtiofs needs shared memory to work.
This can be enabled
in the hardware configuration window. Navigate to <em>Hardware -&gt; Memory</em> and
select <em>Enable shared memory</em>.</p>
<p>After that, open virt-manager and navigate to the hardware settings of the
virtual machine. Click on <em>Add Hardware</em>.</p>


    <figure><img src="https://blog.sergeantbiggs.net/img/virt-manager_1.png"alt="The virt-manager hardware window. The left pane contains the different categories. The right pane contains the setting for the specific category. Beneath the categore pane, there is a button called &#39;Add Hardware&#39;"/><figcaption><p>Virtual Machine Manager hardware configuration window.</p></figcaption></figure>
<p>A dialog box should pop up. In the pane on the left, click on <em>Filesystem</em></p>


    <figure><img src="https://blog.sergeantbiggs.net/img/virt-manager_2.png"alt="The &#39;Add Hardware dialog&#39;. The left pane contains a list of hardware devices that can be added. The option &#39;Filesystem&#39; is selected."/><figcaption><p>The &lsquo;Add Hardware&rsquo; dialog window</p></figcaption></figure>
<p>Enter the necessary settings.</p>


    <figure><img src="https://blog.sergeantbiggs.net/img/virt-manager_3.png"alt="The same window, with the apprioprate settings."/><figcaption><p>Filesystem options</p></figcaption></figure>
<p>The settings are as follows:</p>
<p>For the <em>Driver</em>, we keep the default (virtiofs). The source path is the path
on our host filesystem. The target path is a bit of a misnomer. It&rsquo;s not
actually a path, just an identifier that we use as a mount point in our
guest file system. This will become clear when we mount the file system in the
guest later.</p>
<p>If you want a more detailed overview of these options and some
alternatives, check
out the libvirt knowledge base on
<a href="https://libvirt.org/kbase/virtiofs.html">virtiofs</a></p>
<h2 id="guest">Guest<a href="#guest" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<h3 id="linux">Linux<a href="#linux" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h3>
<p>After that, it&rsquo;s time to configure the guest. First we create a folder in our
users home directory that we will mount the share into.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mkdir ~/share
</span></span></code></pre></div><p>After that, we can mount the share with:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo mount -t virtiofs share /home/foo/share
</span></span></code></pre></div><p>If we want to make this permanent, we can just add an entry
to <code>/etc/fstab</code></p>
<pre tabindex="0"><code>share   /home/foo/share       virtiofs        defaults        0       0
</code></pre><p>After rebooting, the share should be mounted! If your user on the host and the
guest are the same, you don&rsquo;t even need to worry about any other permissions.
Otherwise, change the folder with <code>chmod</code> to suit your needs.</p>
<h3 id="windows-10">Windows 10<a href="#windows-10" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h3>
<p>Windows 10 can also use virtiofs, but it needs some additional work. First, we
need to install 2 pieces of software:</p>
<ul>
<li><a href="https://winfsp.dev/">WinFSP</a></li>
<li>Virtio drivers</li>
</ul>
<p>This installation can be done in two ways: manually or with chocolatey.</p>
<h4 id="chocolatey">Chocolatey<a href="#chocolatey" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h4>
<p>If you already have <a href="https://chocolatey.org/">Chocolatey</a> installed, it&rsquo;s as
simple as:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="cl"><span class="n">choco</span> <span class="n">install</span> <span class="nb">virtio-drivers</span> <span class="n">winfsp</span>
</span></span></code></pre></div><p>If you don&rsquo;t have Chocolatey installed, I highly recommend that you check it
out. It&rsquo;s a package manager for Windows that integrates quite well into the
system. It makes maintaining software on Windows a lot easier! Installation
instructions are <a href="https://chocolatey.org/install#individual">here</a>.</p>
<p>During the installation of the virtio-drivers it will pop up a dialog box
asking if you trust the drivers. Confirm the dialog box to install them.</p>
<h4 id="manual-install">Manual install<a href="#manual-install" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h4>
<p>Download WinFSP <a href="https://winfsp.dev/rel/">here</a> and install it. The default
options are fine if you just want to use the drivers.</p>
<p>After that, you need to download the latest ISO from the following
<a href="https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/latest-virtio/virtio-win.iso">archive</a>.
After downloading, mount the ISO. Then open <em>Device Manager</em>. Select the
appropriate device. It should appear under <em>Other devices</em> as a <em>Mass storage
controller</em></p>


    <figure><img src="https://blog.sergeantbiggs.net/img/vfs-windows_01.png"alt="A view of the device manager."/><figcaption><p>Device Manager</p></figcaption></figure>
<p>Right-click on the device and select <em>Update driver</em>. From that menu, select
<em>Browse my computer for drivers</em>. Browse to the location of the ISO, and select
the directory of the drivers.</p>
<h4 id="configuration">Configuration<a href="#configuration" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h4>
<p>After that, it&rsquo;s a good idea to create a service for the drivers, so they are
automatically loaded during system startup.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="cl"><span class="n">sc</span><span class="p">.</span><span class="py">exe</span> <span class="n">create</span> <span class="n">VirtioFsSvc</span> <span class="n">binpath</span><span class="p">=</span><span class="s2">&#34;C:\ProgramData\Chocolatey\bin\virtiofs.exe&#34;</span> <span class="n">start</span><span class="p">=</span><span class="n">auto</span> <span class="n">depend</span><span class="p">=</span><span class="s2">&#34;WinFsp.Launcher/VirtioFsDrv&#34;</span> <span class="n">DisplayName</span><span class="p">=</span><span class="s2">&#34;Virtio FS Service&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">sc</span><span class="p">.</span><span class="py">exe</span> <span class="nb">start </span><span class="n">VirtioFsSvc</span>
</span></span></code></pre></div><p>If you&rsquo;ve installed manually, find the location of the .exe and change the
binpath.</p>
<h2 id="benchmarks">Benchmarks<a href="#benchmarks" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<p>All Benchmarks were done on a Dell Latitude 5401 with the following specs:</p>
<table>
  <thead>
      <tr>
          <th>Name</th>
          <th>Value</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>CPU</td>
          <td>Intel i5-9400H (8) @ 4.300GHz</td>
      </tr>
      <tr>
          <td>GPU</td>
          <td>Intel CoffeeLake-H GT2 [UHD Graphics 630]</td>
      </tr>
      <tr>
          <td>Memory</td>
          <td>16 GB DDR4</td>
      </tr>
      <tr>
          <td>Storage</td>
          <td>512 GB NVMe</td>
      </tr>
  </tbody>
</table>
<h3 id="linux-1">Linux<a href="#linux-1" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h3>
<p>For the benchmark, I simply copied over a 10 GB file with <code>cp</code>, measuring
it with <code>time</code>. That gave me the following result:</p>
<pre tabindex="0"><code>real    0m12.532s
user    0m0.002s
sys     0m5.785s
</code></pre><p>I also transferred the same file with <code>rsync</code>, to get a progress bar. That way
I could get a feel for the approximate speed of the transfer. The transfer
speed was around 250-300 MB/s consistently.</p>
<h3 id="windows">Windows<a href="#windows" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h3>
<p>On Windows, I also copied the file in 2 ways. First using the windows File
transfer dialog. I got up speeds of about 550-700 MB/s. After that, I tried
the same using
<a href="https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/robocopy">robocopy</a>
(A Microsoft tool for robust file copying).</p>
<pre tabindex="0"><code>-------------------------------------------------------------------------------
   ROBOCOPY     ::     Robust File Copy for Windows
-------------------------------------------------------------------------------

  Started : Friday, 11 March 2022 10:23:20
   Source : C:\Users\foo\Downloads\
     Dest = Z:\

    Files : *.*

  Options : *.* /DCOPY:DA /COPY:DAT /R:1000000 /W:30

------------------------------------------------------------------------------

                           1    C:\Users\foo\Downloads
100%        New File              10.0 g        10gig.img

------------------------------------------------------------------------------

               Total    Copied   Skipped  Mismatch    FAILED    Extras
    Dirs :         1         0         1         0         0         0
   Files :         1         1         0         0         0         2
   Bytes :  10.000 g  10.000 g         0         0         0   797.0 k
   Times :   0:00:18   0:00:18                       0:00:00   0:00:00


   Speed :           566020993 Bytes/sec.
   Speed :           32387.981 MegaBytes/min.
   Ended : Friday, 11 March 2022 10:23:39
</code></pre><p>Here we end up with roughly the same speed as the windows file dialog.</p>
<h2 id="conclusion">Conclusion<a href="#conclusion" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<p>I&rsquo;m very pleased with these results. The process of setting it up is quite easy
(although not as &ldquo;seamless&rdquo; as the solutions Virtualbox and VMWare provide).
The performance seems fantastic, at first glance. For now, I&rsquo;m very happy
with it.
I will be testing this further. If I hit any
snags or discover something else that&rsquo;s interesting, I&rsquo;ll let you know.</p>
]]></content></item><item><title>Hardening Applications with systemd</title><link>https://blog.sergeantbiggs.net/posts/hardening-applications-with-systemd/</link><pubDate>Sat, 05 Mar 2022 13:46:22 +0100</pubDate><author>SergeantBiggs</author><guid>https://blog.sergeantbiggs.net/posts/hardening-applications-with-systemd/</guid><description>&amp;lt;no value&amp;gt;</description><content type="text/html" mode="escaped"><![CDATA[<p>Because we live in the day and age where the <a href="https://versioduo.com/">new</a> <a href="https://0pointer.net">gods</a> have taken over Linux,
it&rsquo;s a good idea to familiarize ourselves with their rituals. Some of them
might seem strange to us, but some of them are actually very nice features. One
of the features I really like about systemd are the built-in hardening
capabilities.</p>
<p>The built-in options for hardening are quite extensive, and can best be
compared to something like <a href="https://firejail.wordpress.com/">firejail</a>. They
both have similar capabilities, but firejail focuses more on desktop
applications, whereas systemd hardening applies to systemd units. The hardening
options are configured in the units service file, in the <code>[Service]</code>
section.</p>
<p>Let&rsquo;s look at the different options with an example:
a service file I set up a while ago for a
Discord bot. The bot is a self-hosted version of
<a href="https://github.com/eritislami/evobot">evobot</a>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="c1"># /etc/systemd/system/boombot.service</span>
</span></span><span class="line"><span class="cl"><span class="k">[Unit]</span>
</span></span><span class="line"><span class="cl"><span class="na">Description</span><span class="o">=</span><span class="s">Music Bot</span>
</span></span><span class="line"><span class="cl"><span class="na">After</span><span class="o">=</span><span class="s">network-online.target</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[Service]</span>
</span></span><span class="line"><span class="cl"><span class="na">ExecStart</span><span class="o">=</span><span class="s">/usr/bin/node /srv/bot/DiscordBots/BoomBot/index.js</span>
</span></span><span class="line"><span class="cl"><span class="na">Type</span><span class="o">=</span><span class="s">simple</span>
</span></span><span class="line"><span class="cl"><span class="na">WorkingDirectory</span><span class="o">=</span><span class="s">/srv/bot/DiscordBots/BoomBot</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Hardening</span>
</span></span><span class="line"><span class="cl"><span class="na">PrivateDevices</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">PrivateTmp</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">ProtectControlGroups</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">ProtectSystem</span><span class="o">=</span><span class="s">full</span>
</span></span><span class="line"><span class="cl"><span class="na">ProtectKernelTunables</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">RestrictSUIDSGID</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">User</span><span class="o">=</span><span class="s">bot</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[Install]</span>
</span></span><span class="line"><span class="cl"><span class="na">WantedBy</span><span class="o">=</span><span class="s">multi-user.target</span>
</span></span></code></pre></div><p>As you can see, I already set up some basic hardening options, but we can
certainly do better than that.</p>
<p>In hardening systemd units, two resources are particularly useful. The first
one is <code>systemd-analyze security &lt;name&gt;.service</code>. This tool gives an overview
of a lot of useful hardening options, whether they are turned on or not, and a
score based on how many options are active. The score is based on a weighted
system, where hardening options are given some points that are subtracted if
they are activated. The scale goes from 10 (worst) to 0 (best).</p>
<p>The other
resource is the man page for
<a href="https://man.archlinux.org/man/systemd.exec.5">systemd.exec(5)</a>. It includes
all possible hardening options (in the section <code>SANDBOXING</code>). Most options are described in great detail. This enables us to make informed
choices about our hardening.</p>
<p>To start, let&rsquo;s have a look at the output of <code>systemd-analyze security boombot.service</code>.</p>
<pre tabindex="0"><code>  NAME                                                        DESCRIPTION                                                             EXPOSURE
✗ RemoveIPC=                                                  Service user may leave SysV IPC objects around                               0.1
✗ RootDirectory=/RootImage=                                   Service runs within the host&#39;s root directory                                0.1
✓ User=/DynamicUser=                                          Service runs under a static non-root user identity
✗ CapabilityBoundingSet=~CAP_SYS_TIME                         Service processes may change the system clock                                0.2
✗ NoNewPrivileges=                                            Service processes may acquire new privileges                                 0.2
✓ AmbientCapabilities=                                        Service process does not receive ambient capabilities
✗ ProtectClock=                                               Service may write to the hardware clock or system clock                      0.2
✗ CapabilityBoundingSet=~CAP_SYS_PACCT                        Service may use acct()                                                       0.1
✗ CapabilityBoundingSet=~CAP_KILL                             Service may send UNIX signals to arbitrary processes                         0.1
✗ ProtectKernelLogs=                                          Service may read from or write to the kernel log ring buffer                 0.2
✗ CapabilityBoundingSet=~CAP_WAKE_ALARM                       Service may program timers that wake up the system                           0.1
✗ CapabilityBoundingSet=~CAP_(DAC_*|FOWNER|IPC_OWNER)         Service may override UNIX file/IPC permission checks                         0.2
✗ CapabilityBoundingSet=~CAP_LINUX_IMMUTABLE                  Service may mark files immutable                                             0.1
✗ CapabilityBoundingSet=~CAP_IPC_LOCK                         Service may lock memory into RAM                                             0.1
✗ ProtectKernelModules=                                       Service may load or read kernel modules                                      0.2
✗ CapabilityBoundingSet=~CAP_SYS_MODULE                       Service may load kernel modules                                              0.2
✗ CapabilityBoundingSet=~CAP_SYS_TTY_CONFIG                   Service may issue vhangup()                                                  0.1
✗ CapabilityBoundingSet=~CAP_SYS_BOOT                         Service may issue reboot()                                                   0.1
✗ CapabilityBoundingSet=~CAP_SYS_CHROOT                       Service may issue chroot()                                                   0.1
✗ SystemCallArchitectures=                                    Service may execute system calls with all ABIs                               0.2
✗ CapabilityBoundingSet=~CAP_BLOCK_SUSPEND                    Service may establish wake locks                                             0.1
✗ MemoryDenyWriteExecute=                                     Service may create writable executable memory mappings                       0.1
✗ RestrictNamespaces=~user                                    Service may create user namespaces                                           0.3
✗ RestrictNamespaces=~pid                                     Service may create process namespaces                                        0.1
✗ RestrictNamespaces=~net                                     Service may create network namespaces                                        0.1
✗ RestrictNamespaces=~uts                                     Service may create hostname namespaces                                       0.1
✗ RestrictNamespaces=~mnt                                     Service may create file system namespaces                                    0.1
✗ CapabilityBoundingSet=~CAP_LEASE                            Service may create file leases                                               0.1
✗ RestrictNamespaces=~cgroup                                  Service may create cgroup namespaces                                         0.1
✗ RestrictNamespaces=~ipc                                     Service may create IPC namespaces                                            0.1
✗ ProtectHostname=                                            Service may change system host/domainname                                    0.1
✗ CapabilityBoundingSet=~CAP_(CHOWN|FSETID|SETFCAP)           Service may change file ownership/access mode/capabilities unrestricted      0.2
✗ CapabilityBoundingSet=~CAP_SET(UID|GID|PCAP)                Service may change UID/GID identities/capabilities                           0.3
✗ LockPersonality=                                            Service may change ABI personality                                           0.1
✗ RestrictAddressFamilies=~AF_PACKET                          Service may allocate packet sockets                                          0.2
✗ RestrictAddressFamilies=~AF_NETLINK                         Service may allocate netlink sockets                                         0.1
✗ RestrictAddressFamilies=~AF_UNIX                            Service may allocate local sockets                                           0.1
✗ RestrictAddressFamilies=~…                                  Service may allocate exotic sockets                                          0.3
✗ RestrictAddressFamilies=~AF_(INET|INET6)                    Service may allocate Internet sockets                                        0.3
✗ CapabilityBoundingSet=~CAP_MAC_*                            Service may adjust SMACK MAC                                                 0.1
✗ RestrictRealtime=                                           Service may acquire realtime scheduling                                      0.1
✗ ProtectSystem=                                              Service has very limited write access to the OS file hierarchy               0.1
✗ CapabilityBoundingSet=~CAP_SYS_PTRACE                       Service has ptrace() debugging abilities                                     0.3
✗ CapabilityBoundingSet=~CAP_SYS_(NICE|RESOURCE)              Service has privileges to change resource use parameters                     0.1
✓ SupplementaryGroups=                                        Service has no supplementary groups
✓ CapabilityBoundingSet=~CAP_SYS_RAWIO                        Service has no raw I/O access
✓ PrivateTmp=                                                 Service has no access to other software&#39;s temporary files
✓ PrivateDevices=                                             Service has no access to hardware devices
✗ CapabilityBoundingSet=~CAP_NET_ADMIN                        Service has network configuration privileges                                 0.2
✗ ProtectProc=                                                Service has full access to process tree (/proc hidepid=)                     0.2
✗ ProcSubset=                                                 Service has full access to non-process /proc files (/proc subset=)           0.1
✗ ProtectHome=                                                Service has full access to home directories                                  0.2
✗ CapabilityBoundingSet=~CAP_NET_(BIND_SERVICE|BROADCAST|RAW) Service has elevated networking privileges                                   0.1
✗ CapabilityBoundingSet=~CAP_AUDIT_*                          Service has audit subsystem access                                           0.1
✗ CapabilityBoundingSet=~CAP_SYS_ADMIN                        Service has administrator privileges                                         0.3
✗ PrivateNetwork=                                             Service has access to the host&#39;s network                                     0.5
✗ PrivateUsers=                                               Service has access to other users                                            0.2
✗ CapabilityBoundingSet=~CAP_SYSLOG                           Service has access to kernel logging                                         0.1
✓ DeviceAllow=                                                Service has a minimal device ACL
✓ KeyringMode=                                                Service doesn&#39;t share key material with other services
✓ Delegate=                                                   Service does not maintain its own delegated control group subtree
✗ SystemCallFilter=~@clock                                    Service does not filter system calls                                         0.2
✗ SystemCallFilter=~@cpu-emulation                            Service does not filter system calls                                         0.1
✗ SystemCallFilter=~@debug                                    Service does not filter system calls                                         0.2
✗ SystemCallFilter=~@module                                   Service does not filter system calls                                         0.2
✗ SystemCallFilter=~@mount                                    Service does not filter system calls                                         0.2
✗ SystemCallFilter=~@obsolete                                 Service does not filter system calls                                         0.1
✗ SystemCallFilter=~@privileged                               Service does not filter system calls                                         0.2
✗ SystemCallFilter=~@raw-io                                   Service does not filter system calls                                         0.2
✗ SystemCallFilter=~@reboot                                   Service does not filter system calls                                         0.2
✗ SystemCallFilter=~@resources                                Service does not filter system calls                                         0.2
✗ SystemCallFilter=~@swap                                     Service does not filter system calls                                         0.2
✗ IPAddressDeny=                                              Service does not define an IP address allow list                             0.2
✓ NotifyAccess=                                               Service child processes cannot alter service state
✓ ProtectControlGroups=                                       Service cannot modify the control group file system
✓ PrivateMounts=                                              Service cannot install system mounts
✓ CapabilityBoundingSet=~CAP_MKNOD                            Service cannot create device nodes
✓ ProtectKernelTunables=                                      Service cannot alter kernel tunables (/proc/sys, …)
✓ RestrictSUIDSGID=                                           SUID/SGID file creation by service is restricted
✗ UMask=                                                      Files created by service are world-readable by default                       0.1

→ Overall exposure level for boombot.service: 7.6 EXPOSED 🙁
</code></pre><p>As you can see in the last line, systemd is not very happy with our hardening
options. Let&rsquo;s turn that frown upside down!</p>
<p>The first group of &ldquo;options&rdquo; I want to tackle is denying access to the file
system. We already had <code>ProtectSystem=</code> set to &ldquo;full&rdquo;. With this, only certain
parts of the OS are mounted read-only for the service (/usr, /boot, /etc). If
we set it to strict, this extends to the <strong>complete file hierarchy</strong>. If we want
to allow the service to write to certain paths, we can specify them manually
with <code>ReadWritePaths=</code>. Since this bot doesn&rsquo;t store any data, let&rsquo;s set it to
strict, and see if it still works. There are also other ways to restrict access
to the file system. Let&rsquo;s enable the following settings:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="na">ProtectSystem</span><span class="o">=</span><span class="s">strict		# Disable write to entire file hierarchy</span>
</span></span><span class="line"><span class="cl"><span class="na">PrivateDevices</span><span class="o">=</span><span class="s">true		# Only allow access to pseudo-devices (eg: null, random, zero) in separate namespace	</span>
</span></span><span class="line"><span class="cl"><span class="na">PrivateTmp</span><span class="o">=</span><span class="s">true			# Mount /tmp in own namespace</span>
</span></span><span class="line"><span class="cl"><span class="na">ProtectKernelLogs</span><span class="o">=</span><span class="s">true		# Disable access to Kernel Logs</span>
</span></span><span class="line"><span class="cl"><span class="na">ProtectProc</span><span class="o">=</span><span class="s">invisible		# Disable access to information about other processes</span>
</span></span><span class="line"><span class="cl"><span class="na">PrivateUsers</span><span class="o">=</span><span class="s">true		# Disable access to other users on system</span>
</span></span><span class="line"><span class="cl"><span class="na">ProtectHome</span><span class="o">=</span><span class="s">true		# Disable access to /home</span>
</span></span><span class="line"><span class="cl"><span class="na">UMask</span><span class="o">=</span><span class="s">0077			# set umask</span>
</span></span></code></pre></div><p>Let&rsquo;s have a look at how far we&rsquo;ve come with these options:</p>
<pre tabindex="0"><code>→ Overall exposure level for boombot.service: 6.7 MEDIUM 😐
</code></pre><p>That&rsquo;s already a lot better! Let&rsquo;s see if we can improve it. The next step is
restricting access to the system.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="na">RestrictNamespaces</span><span class="o">=</span><span class="s">true         		# Disable creating namespaces</span>
</span></span><span class="line"><span class="cl"><span class="na">LockPersonality</span><span class="o">=</span><span class="s">true            		# Locks personality system call</span>
</span></span><span class="line"><span class="cl"><span class="na">NoNewPrivileges</span><span class="o">=</span><span class="s">true            		# Service may not acquire new privileges</span>
</span></span><span class="line"><span class="cl"><span class="na">ProtectKernelModules</span><span class="o">=</span><span class="s">true       		# Service may not load kernel modules</span>
</span></span><span class="line"><span class="cl"><span class="na">SystemCallArchitectures</span><span class="o">=</span><span class="s">native  		# Only allow native system calls</span>
</span></span><span class="line"><span class="cl"><span class="na">ProtectHostname</span><span class="o">=</span><span class="s">true            		# Service may not change host name</span>
</span></span><span class="line"><span class="cl"><span class="na">RestrictAddressFamilies</span><span class="o">=</span><span class="s">AF_INET AF_INET6        # Service may only use IP address families</span>
</span></span><span class="line"><span class="cl"><span class="na">RestrictRealtime</span><span class="o">=</span><span class="s">true				# Disable realtime privileges</span>
</span></span><span class="line"><span class="cl"><span class="na">ProtectControlGroups</span><span class="o">=</span><span class="s">true			# Disable access to cgroups</span>
</span></span><span class="line"><span class="cl"><span class="na">ProtectKernelTunables</span><span class="o">=</span><span class="s">true			# Disable write access to kernel variables</span>
</span></span><span class="line"><span class="cl"><span class="na">RestrictSUIDSGID</span><span class="o">=</span><span class="s">true				# Disable setting suid or sgid bits</span>
</span></span><span class="line"><span class="cl"><span class="na">ProtectClock</span><span class="o">=</span><span class="s">true                               # Disable changing system clock</span>
</span></span></code></pre></div><pre tabindex="0"><code>→ Overall exposure level for boombot.service: 4.7 OK 🙂
</code></pre><p>We have our first smile! Let&rsquo;s keep going!</p>
<p>The next 2 options are very powerfull, but it is a bit harder to decide how to
set them. They are <code>CapabilityBoundingSet</code> and
<code>SystemCallFilter</code>.</p>
<p>The first option restricts (as its name suggests) capabilities.
These are &ldquo;privileges&rdquo; that a process has. Examples of these capabilities
include:</p>
<ul>
<li>Read/write access to the Kernel audit log</li>
<li>Access to privileged BPF operations</li>
<li>Block system suspend</li>
<li>chown arbitrary files</li>
<li>kill arbitrary processes</li>
<li>change MAC address</li>
<li>Bind a socket to a port less than 1024</li>
<li>and many more</li>
</ul>
<p>Many of these capabilities are not needed for normal processes, and they can
be especially dangerous because they often ignore permissions (eg: <code>CAP_CHOWN</code>
ignores filesystem permissions). Let&rsquo;s disable all of them.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="na">CapabilityBoundingSet</span><span class="o">=</span>
</span></span></code></pre></div><p>You can also apply a whitelist, or invert the selection with <code>~</code> (eg:
<code>~CAP_SYS_PTRACE</code>).</p>
<pre tabindex="0"><code>→ Overall exposure level for boombot.service: 2.8 OK 🙂
</code></pre><p>Now for the hardest part (in my opinion): <code>SystemCallFilter</code>. This is a very
powerful tool, as it allows you to restrict the system calls that the process
can call. This isn&rsquo;t easy to configure, and might need frequent updates, as the
process gains new features.</p>
<p>To make this process easier, systemd includes some &ldquo;groupings&rdquo;. These are
prepended with an &lsquo;@&rsquo;, and include several groups of system calls. Examples of
these include</p>
<ul>
<li>@swap</li>
<li>@timer</li>
<li>@reboot</li>
<li>@raw-io</li>
<li>etc.</li>
</ul>
<p>For simplicity, we&rsquo;ll use a special grouping, <code>@system-service</code>. This includes
a &ldquo;set of system calls used by common services, excluding any special purpose
calls&rdquo;. The man page recommends this as a starting point for custom lists. We
also deny 2 other groups that <code>systemd-analyze security</code> recommends:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="na">SystemCallFilter</span><span class="o">=</span><span class="s">@system-service</span>
</span></span><span class="line"><span class="cl"><span class="na">SystemCallFilter</span><span class="o">=</span><span class="s">~@privileged @resources # System calls related to super-user</span>
</span></span></code></pre></div><p>After setting these options, we arrive at our final assessment:</p>
<pre tabindex="0"><code>→ Overall exposure level for boombot.service: 1.1 OK 🙂
</code></pre><p>That seems like a pretty nice verdict! Our process is now locked down quite
well. Of course, there are other things that can still be done, like setting a
more specific system call filter, but this is a good start. I&rsquo;ll include the
full service file here, so you can have a look at it and use it as a
template for your own unit files.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="c1"># /etc/systemd/system/boombot.service</span>
</span></span><span class="line"><span class="cl"><span class="k">[Unit]</span>
</span></span><span class="line"><span class="cl"><span class="na">Description</span><span class="o">=</span><span class="s">Music Bot</span>
</span></span><span class="line"><span class="cl"><span class="na">After</span><span class="o">=</span><span class="s">network-online.target</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[Service]</span>
</span></span><span class="line"><span class="cl"><span class="na">ExecStart</span><span class="o">=</span><span class="s">/usr/bin/node /srv/bot/DiscordBots/BoomBot/index.js</span>
</span></span><span class="line"><span class="cl"><span class="na">Type</span><span class="o">=</span><span class="s">simple</span>
</span></span><span class="line"><span class="cl"><span class="na">WorkingDirectory</span><span class="o">=</span><span class="s">/srv/bot/DiscordBots/BoomBot</span>
</span></span><span class="line"><span class="cl"><span class="na">User</span><span class="o">=</span><span class="s">bot</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Hardening</span>
</span></span><span class="line"><span class="cl"><span class="c1">## File System</span>
</span></span><span class="line"><span class="cl"><span class="na">ProtectSystem</span><span class="o">=</span><span class="s">strict</span>
</span></span><span class="line"><span class="cl"><span class="na">PrivateDevices</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">PrivateTmp</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">ProtectKernelLogs</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">ProtectProc</span><span class="o">=</span><span class="s">invisible</span>
</span></span><span class="line"><span class="cl"><span class="na">PrivateUsers</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">ProtectHome</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">UMask</span><span class="o">=</span><span class="s">0077</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">## System</span>
</span></span><span class="line"><span class="cl"><span class="na">RestrictNamespaces</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">LockPersonality</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">NoNewPrivileges</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">ProtectKernelModules</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">SystemCallArchitectures</span><span class="o">=</span><span class="s">native</span>
</span></span><span class="line"><span class="cl"><span class="na">ProtectHostname</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">RestrictAddressFamilies</span><span class="o">=</span><span class="s">AF_INET AF_INET6</span>
</span></span><span class="line"><span class="cl"><span class="na">RestrictRealtime</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">ProtectControlGroups</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">ProtectKernelTunables</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">RestrictSUIDSGID</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">ProtectClock</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">RemoveIPC</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">## Capabilities and syscalls</span>
</span></span><span class="line"><span class="cl"><span class="na">CapabilityBoundingSet</span><span class="o">=</span>
</span></span><span class="line"><span class="cl"><span class="na">SystemCallFilter</span><span class="o">=</span><span class="s">@system-service</span>
</span></span><span class="line"><span class="cl"><span class="na">SystemCallFilter</span><span class="o">=</span><span class="s">~@privileged @resources</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[Install]</span>
</span></span><span class="line"><span class="cl"><span class="na">WantedBy</span><span class="o">=</span><span class="s">multi-user.target</span>
</span></span></code></pre></div><p>I hope you enjoyed reading this article, and I hope you get inspired to have a
closer look at your services and lock them down!</p>
]]></content></item><item><title>Hello World (again)</title><link>https://blog.sergeantbiggs.net/posts/hello-world/</link><pubDate>Thu, 03 Mar 2022 12:44:32 +0100</pubDate><author>SergeantBiggs</author><guid>https://blog.sergeantbiggs.net/posts/hello-world/</guid><description>&amp;lt;no value&amp;gt;</description><content type="text/html" mode="escaped"><![CDATA[<p>If you&rsquo;re a regular reader of my blog, you might notice something different. I
am no longer using <a href="https://wordpress.org/">WordPress</a>. Instead I&rsquo;m using <a href="https://gohugo.io/">Hugo</a>, a static site generator. I might write a
future blog post on my thoughts in using it in general
and the stumbling blocks (or maybe lack thereof) I&rsquo;ve encountered.</p>
<p>I will be migrating the posts from the WordPress instance. This might take a
few weeks. I thought about using a tool to convert the pages automatically,
but I&rsquo;ve decided against it. A tool can&rsquo;t convert the pages cleanly, and I
don&rsquo;t have that many blog posts anyway (one example of laziness actually having
benefits ^^). Also, this will give me a good opportunity to get to know Hugos
ins and outs, so it&rsquo;s a good exercise nonetheless. I&rsquo;m not sure whether I want
to backdate the new posts or not. On the one hand I like the idea of starting
with a &ldquo;clean slate&rdquo;, but on the other hand I don&rsquo;t want to lose the information.</p>
<p>One thing that will definitely be lost, is the comment section. Since I never
got many comments (aside from very friendly bots reminding me about the great
benefits of Viagra and Cialis pills), I don&rsquo;t think I lost anything.
Maybe I will bring back the comment section at some point, but for now I&rsquo;m not
planning on it. If you <em>really</em> want to tell me something, just write me an
email!</p>
]]></content></item><item><title>Credential Management With Systemd</title><link>https://blog.sergeantbiggs.net/posts/credential-management-with-systemd/</link><pubDate>Tue, 11 Jan 2022 14:14:46 +0100</pubDate><author>SergeantBiggs</author><guid>https://blog.sergeantbiggs.net/posts/credential-management-with-systemd/</guid><description>&amp;lt;no value&amp;gt;</description><content type="text/html" mode="escaped"><![CDATA[<p>Systemd introduced a credential management system in version 247. It can be
used to pass sensitive information from the system to the unit. It&rsquo;s primary
usage is passing configuration data, passwords, private keys, etc. The
functionality is further explained in <code>systemd.exec (5)</code>.</p>
<p>When I was browsing <a href="https://lwn.net">LWN</a> a few days ago, I noticed something in the <a href="https://github.com/systemd/systemd/releases/tag/v250">release
notes</a> for the new systemd v250:</p>
<pre tabindex="0"><code>   *  Support for encrypted and authenticated credentials has been added.
      This extends the credential logic introduced with v247 to support
      non-interactive symmetric encryption and authentication, based on a
      key that is stored on the /var/ file system or in the TPM2 chip (if
      available), or the combination of both (by default if a TPM2 chip
      exists the combination is used, otherwise the /var/ key only). The
      credentials are automatically decrypted at the moment a service is
      started, and are made accessible to the service itself in unencrypted
      form. A new tool &#39;systemd-creds&#39; encrypts credentials for this
      purpose, and two new service file settings LoadCredentialEncrypted=
      and SetCredentialEncrypted= configure such credentials.

      This feature is useful to store sensitive material such as SSL
      certificates, passwords and similar securely at rest and only decrypt
      them when needed, and in a way that is tied to the local OS
      installation or hardware.
</code></pre><p>This looks like quite a nice feature. Let&rsquo;s try it out!</p>
<p>Like the release notes say, systemd-creds will use a TPM2 chip if it is
available. If it isn&rsquo;t available, it will just use the secret in
<code>/var/lib/systemd/credential.secret</code>. So let&rsquo;s first check if we have a TPM
enabled. This can by reading the value of the following file:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">cat /sys/class/tpm/tpm0/tpm_version_major
</span></span></code></pre></div><p>If TPM 2.0 is available, the output of the command will be &ldquo;2&rdquo;.</p>
<p>First we need to encrypt our old credentials. This can be done with
<code>systemd-creds</code>. This command takes an input file and an output file. The input
file is our old unencrypted credential file, the output file will be the
encrypted one:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">systemd-creds encrypt secrets/old/token_unencrypted secrets/token
</span></span></code></pre></div><p>After doing that, we just need to change the setting in our <code>.service</code> file (this
is an example file for a bot that I&rsquo;m running on my server):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[Unit]</span>
</span></span><span class="line"><span class="cl"><span class="na">Description</span><span class="o">=</span><span class="s">My Service</span>
</span></span><span class="line"><span class="cl"><span class="na">After</span><span class="o">=</span><span class="s">network-online.target</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[Service]</span>
</span></span><span class="line"><span class="cl"><span class="na">ExecStart</span><span class="o">=</span><span class="s">/usr/bin/important-service</span>
</span></span><span class="line"><span class="cl"><span class="na">Type</span><span class="o">=</span><span class="s">exec</span>
</span></span><span class="line"><span class="cl"><span class="na">user</span><span class="o">=</span><span class="s">foo</span>
</span></span><span class="line"><span class="cl"><span class="na">WorkingDirectory</span><span class="o">=</span><span class="s">/home/foo</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Hardening</span>
</span></span><span class="line"><span class="cl"><span class="na">PrivateDevices</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">ProtectControlGroups</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">ProtectSystem</span><span class="o">=</span><span class="s">full</span>
</span></span><span class="line"><span class="cl"><span class="na">ProtectKernelTunables</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">RestrictSUIDSGID</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">PrivateTmp</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">PrivateUsers</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">ProtectClock</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">ProtectHome</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">ProtectKernelLogs</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">ProtectKernelModules</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">ProtectProc</span><span class="o">=</span><span class="s">noaccess</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Everything is RO, we can only write to ReadWritePaths</span>
</span></span><span class="line"><span class="cl"><span class="na">ProtectSystem</span><span class="o">=</span><span class="s">strict</span>
</span></span><span class="line"><span class="cl"><span class="na">RemoveIPC</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">UMask</span><span class="o">=</span><span class="s">0077</span>
</span></span><span class="line"><span class="cl"><span class="na">ProtectHostname</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">NoNewPrivileges</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="na">CapabilityBoundingSet</span><span class="o">=</span>
</span></span><span class="line"><span class="cl"><span class="na">RestrictNamespaces</span><span class="o">=</span><span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">RestrictAddressFamilies</span><span class="o">=</span><span class="s">AF_INET AF_INET6</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Secrets</span>
</span></span><span class="line"><span class="cl"><span class="na">LoadCredentialEncrypted</span><span class="o">=</span><span class="s">token:/home/foo/secrets/token</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[Install]</span>
</span></span><span class="line"><span class="cl"><span class="na">WantedBy</span><span class="o">=</span><span class="s">multi-user.target</span>
</span></span></code></pre></div><p>NB: the ID of the token (the name in front of the colon). Needs to be the same
as the file name of the secret.</p>
<p>After that, everything should work fine, right? Not really. When I started the
unit for the first time, it immediately crashed. Can you guess what the problem
was? I&rsquo;ll even give you the error logs from the unit:</p>
<pre tabindex="0"><code>Jan 11 14:19:57 examplehost service[13245]: ERROR:tcti:src/tss2-tcti/tcti-device.c:442:Tss2_Tcti_Device_Init() Failed to open specified TCTI device file /dev/tpmrm0: Operation not permitted
Jan 11 14:19:57 examplehost service[13245]: ERROR:tcti:src/tss2-tcti/tctildr-dl.c:154:tcti_from_file() Could not initialize TCTI file: libtss2-tcti-device.so.0
Jan 11 14:19:57 examplehost service[13245]: ERROR:tcti:src/tss2-tcti/tcti-device.c:442:Tss2_Tcti_Device_Init() Failed to open specified TCTI device file /dev/tpm0: Operation not permitted
Jan 11 14:19:57 examplehost service[13245]: ERROR:tcti:src/tss2-tcti/tctildr-dl.c:154:tcti_from_file() Could not initialize TCTI file: libtss2-tcti-device.so.0
Jan 11 14:19:57 examplehost service[13245]: WARNING:tcti:src/util/io.c:252:socket_connect() Failed to connect to host 127.0.0.1, port 2321: errno 111: Connection refused
Jan 11 14:19:57 examplehost service[13245]: ERROR:tcti:src/tss2-tcti/tcti-swtpm.c:591:Tss2_Tcti_Swtpm_Init() Cannot connect to swtpm TPM socket
Jan 11 14:19:57 examplehost service[13245]: ERROR:tcti:src/tss2-tcti/tctildr-dl.c:154:tcti_from_file() Could not initialize TCTI file: libtss2-tcti-swtpm.so.0
Jan 11 14:19:57 examplehost service[13245]: WARNING:tcti:src/util/io.c:252:socket_connect() Failed to connect to host 127.0.0.1, port 2321: errno 111: Connection refused
Jan 11 14:19:57 examplehost service[13245]: ERROR:tcti:src/tss2-tcti/tctildr-dl.c:154:tcti_from_file() Could not initialize TCTI file: libtss2-tcti-mssim.so.0
Jan 11 14:19:57 examplehost service[13245]: ERROR:tcti:src/tss2-tcti/tctildr-dl.c:254:tctildr_get_default() No standard TCTI could be loaded
Jan 11 14:19:57 examplehost service[13245]: ERROR:tcti:src/tss2-tcti/tctildr.c:428:Tss2_TctiLdr_Initialize_Ex() Failed to instantiate TCTI
Jan 11 14:19:57 examplehost service[13245]: ERROR:esys:src/tss2-esys/esys_context.c:69:Esys_Initialize() Initialize default tcti. ErrorCode (0x000a000a)
Jan 11 14:19:57 examplehost systemd[13245]: Failed to initialize TPM context: tcti:IO failure
Jan 11 14:19:57 examplehost systemd[13244]: sergeantbot-staging.service: Failed to set up credentials: Protocol error
Jan 11 14:19:57 examplehost systemd[13244]: sergeantbot-staging.service: Failed at step CREDENTIALS spawning /srv/bot/prod/service: Protocol error
</code></pre><p>The problem was with one of my hardening options. Specifically with
<code>PrivateDevices=true</code>. If the option is set systemd only gives access to a very
limited subset of pseudo-devices (null, zero, pty). So we need to do this
differently. One possibility would be to disable this option. But I&rsquo;m not a big
fan of that. Another is to use the <code>DevicePolicy</code> option. There we can specify a
device policy. There are 2 options: strict or closed. Closed allows access to
pseudo-devices, and strict disallows all access. Since having access to certain
pseudo-devices is not a problem, we&rsquo;ll set this to closed for now. Then we can
allow specific devices with the <code>DeviceAllow</code> option. Just look for the device
that throws the error in the logs (tpm0 and tpmrm0 in my case), and allow it
explicitly:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="na">DevicePolicy</span><span class="o">=</span><span class="s">closed</span>
</span></span><span class="line"><span class="cl"><span class="na">DeviceAllow</span><span class="o">=</span><span class="s">/dev/tpm0</span>
</span></span><span class="line"><span class="cl"><span class="na">DeviceAllow</span><span class="o">=</span><span class="s">/dev/tpmrm0</span>
</span></span></code></pre></div><p>After that, restart the service and everything should work fine.</p>
<p>That concludes our quick dive into systemd credentials. I really like this feature
and I think it is a very good way of passing secrets to a service (definitely
better than using <a href="https://12factor.net/config">environment variables</a>!)</p>
<p>I hope you enjoyed the article!</p>
]]></content></item><item><title>Easier Postgres User Management</title><link>https://blog.sergeantbiggs.net/posts/easier-postgres-user-management/</link><pubDate>Fri, 31 Dec 2021 13:52:50 +0100</pubDate><author>SergeantBiggs</author><guid>https://blog.sergeantbiggs.net/posts/easier-postgres-user-management/</guid><description>&amp;lt;no value&amp;gt;</description><content type="text/html" mode="escaped"><![CDATA[<p>I have a Postgres instance that runs on my main server, and provides database
services to all of my apps. Like everything, this has grown over the years. So
I thought be time for some cleaning.</p>
<p>When I configured the services, I did it inconsistently because I didn&rsquo;t really
understand the different authentication options. Most of the time I would just
create a username and password, contact the server over TCP/IP, and call it a
day. This meant tracking users + passwords in external password managers, and
writing long passwords in configuration files. I knew about postgresql sockets
but never really used them. That is, until I discovered peer authentication.
This allows postgres to authenticate users locally. You can then specify which
users have access to which databases in <code>pga_hba.conf</code>. This allows you to easily
add new users and give them permissions for databases, without having to fuck
around with passwords. You can configure it by adding the specific users and
databases that you want to activate peer authentication for.</p>
<p>For example: if you configure a gitlab instance, and want to give the gitlab
user access to the gitlab database, add the following to <code>pg_hba.conf</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="c1"># TYPE  DATABASE        USER            ADDRESS                 METHOD</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># &#34;local&#34; is for Unix domain socket connections only</span>
</span></span><span class="line"><span class="cl"><span class="na">local   all             postgres                                peer</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># All other users that can access the socket</span>
</span></span><span class="line"><span class="cl"><span class="na">local   foo             foo                                     peer</span>
</span></span><span class="line"><span class="cl"><span class="na">local   bar             bar                                     peer</span>
</span></span><span class="line"><span class="cl"><span class="na">local   gitlab          gitlab                                  peer</span>
</span></span></code></pre></div><p>This provides an easy way to ensure only specific users are allowed to access
specific databases. It has the added benefit of being faster and more reliable,
since it uses a Unix socket. I also removed the network authentication for
localhost, and disabled listening on a network socket, since that is no longer
needed.</p>
<p><code>/var/lib/postgres/data/postgresql.conf</code></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="na">listen_addresses</span> <span class="o">=</span> <span class="s">&#39;&#39;</span>
</span></span></code></pre></div>]]></content></item><item><title>Installing and Configuring SSH Under Windows</title><link>https://blog.sergeantbiggs.net/posts/installing-and-configuring-ssh-under-windows/</link><pubDate>Tue, 28 Sep 2021 13:26:10 +0100</pubDate><author>SergeantBiggs</author><guid>https://blog.sergeantbiggs.net/posts/installing-and-configuring-ssh-under-windows/</guid><description>&amp;lt;no value&amp;gt;</description><content type="text/html" mode="escaped"><![CDATA[<p>Some friends of mine want to join the <a href="/posts/setting-up-valheim">Valheim server I set up a while
ago</a>, and
they asked me to explain to them how to create an SSH key under Windows to
control the server (I set up a system where people can start, stop and query
the status of the server. More about that
<a href="/posts/setting-up-valheim-part-2">here</a>).</p>
<p>Most of the tutorials I found on the internet were quite old (suggesting to RSA
with 1024 bits!). So I thought of writing a quick tutorial that uses settings
that are a bit more modern. I&rsquo;ll be describing 2 methods. One with
<a href="https://www.chiark.greenend.org.uk/~sgtatham/putty/">PuTTY</a>, the
other one with the built-in <a href="https://www.openssh.com/">BSD OpenSSH</a> client under Windows 10.</p>
<h1 id="built-in-openssh">Built-in OpenSSH<a href="#built-in-openssh" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h1>
<p>This is the recommended way of doing things, and it&rsquo;s not fundamentally
different from configuring SSH on *NIX platforms. First, open a PowerShell
prompt. In that window, type:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-powershell" data-lang="powershell"><span class="line"><span class="cl"><span class="nb">ssh-keygen</span> <span class="n">-t</span> <span class="n">ed25519</span>
</span></span></code></pre></div><p><code>-t ed25519</code> tells OpenSSH to create an
<a href="https://en.wikipedia.org/wiki/EdDSA#Ed25519">ed25519</a> key. This is a form of elliptic
curve DSA. It is very fast and also more secure than &ldquo;small&rdquo; (1024-2048 bit)
RSA keys. I would recommend this algorithm, although you can also use standard
NIST ECDSA.</p>
<p>OpenSSH should start a dialog. If you&rsquo;ve never created an SSH-Key, you can just
hit enter to choose the default path. It&rsquo;s not important where you save the
key, just make sure it&rsquo;s in a place only your user can access (the default
location is fine for that). After you enter the path, it will ask you for a
passphrase. It&rsquo;s recommended to choose a passphrase, although it&rsquo;s not strictly
necessary if you just use the key for this functionality, since it doesn&rsquo;t
really have high stakes. If you plan on using the key for other things, I would
recommend choosing a passphrase.</p>
<p>After that, go into the path were it saved the key, and
open the public key file (in my case id_ed25519.pub). Then send the text string
to your sysadmin. He will enter that key in his authorized_keys file.</p>
<h1 id="putty">PuTTY<a href="#putty" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h1>
<p>This is the &ldquo;old-school&rdquo; way of doing things. If you&rsquo;re running Windows 10, I
would recommend you use the built-in OpenSSH client.</p>
<p>First you need to
<a href="https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html">download</a> and install PuTTY. After installing it, we first
need to generate a key. After that, we&rsquo;ll create a profile for each command.
That means we can just select and open the profile to execute our commands. To
generate a key, you need to execute a program called PuTTYgen. The correct
options are shown in the following screenshot. We want an ed25519 key, so
choose EdDSA under Type of key to generate, and make sure ed25519 is selected
as the type.</p>


    <figure><img src="https://blog.sergeantbiggs.net/img/win_putty_1.png"alt="Dialog for PuTTYGen. Make sure EdDSA and Ed25519 are selected."/></figure>
<p>After that, click generate. Generation should be very fast on a modern
computer. After generating you will see the following:</p>


    <figure><img src="https://blog.sergeantbiggs.net/img/win_putty_2.png"alt="The dialog, after generating the key."/></figure>
<p>You can just copy the public key out of the box at the top. Send that to your
sysadmin. Afterwards, you can choose a passphrase and klick on <code>save private key</code>. Choose an appropriate location (c:\User\username.ssh is a good place).
You can also save the public key, but that one can also be generated from the
private key later, so you don&rsquo;t need to do it at this time.</p>
<p>After that, open up PuTTY. Enter the appropriate hostname into the dialog (ask
your sysadmin for the correct FQDN). After that, we need to do 2 things. First
we need to manually specify the SSH key. For that, go into Connection -&gt; SSH -&gt;
Auth. In the last option, we can specify the private key file. Just hit browse,
and select the private key.</p>


    <figure><img src="https://blog.sergeantbiggs.net/img/win_putty_3.png"alt="The PuTTY &#39;auth&#39; tab&#39;."/></figure>
<p>Because PuTTY tries to connect to an interactive SSH immediately (which isn&rsquo;t
allowed for security reasons), we need to configure some additional things. The
best way to do this is to create 3 sessions for each of our 3 commands. To
associate a command with a specific connection, we go to Connection -&gt; SSH.
There we can specify a remote command.</p>


    <figure><img src="https://blog.sergeantbiggs.net/img/win_putty_4.png"alt="The SSH tab. Here &#39;status&#39; is specified as the remote command"/></figure>
<p>After that, go back to Session and define a session name under <code>Saved Sessions</code>.
After that, save the session.</p>


    <figure><img src="https://blog.sergeantbiggs.net/img/win_putty_5.png"alt="PuTTY session dialog"/></figure>
<p>Repeat the process for the other commands. After that, you can just open putty,
load the session and click on Open to execute the command. Now everything
should work. Good luck and have fun with Valheim!</p>
]]></content></item><item><title>Power Cable Modding</title><link>https://blog.sergeantbiggs.net/posts/power-cable-modding/</link><pubDate>Fri, 16 Jul 2021 12:18:47 +0100</pubDate><author>SergeantBiggs</author><guid>https://blog.sergeantbiggs.net/posts/power-cable-modding/</guid><description>&amp;lt;no value&amp;gt;</description><content type="text/html" mode="escaped"><![CDATA[<p>The NAS in my living room has a very nice case: The <a href="https://www.fractal-design.com/products/cases/node/node-304/black/">Node
304</a> by <a href="https://www.fractal-design.com/">Fractal
Design</a>. It&rsquo;s very modular and compact (Mini-ITX), and you can mount put up to
six [sic!] 3.5 inch HDDs. It just has one big annoyance with the power supply.</p>
<p>The supply is mounted internally, and then connected to a plug that is mounted
to the panel. This cable uses a right-angle-connector. The problem with this
is, I didn&rsquo;t think of the fact that my power supply mounts the other way. So it
needs a connector that is angled towards the other side. Finding a right-angle
connector wasn&rsquo;t that difficult, but I didn&rsquo;t find one with a panel-mountable socket
on the other side. So, for a long time I just left the cable
dangling out of the back. I decided to rectify this a few days ago.</p>
<p>My first idea was to zombify both cables I had by cutting them up and then
soldering them back together. If this would be a low-voltage project, I
wouldn&rsquo;t think twice about it. But I&rsquo;ve always felt a bit uneasy with high
voltage. And I wasn&rsquo;t sure if it&rsquo;s OK to just solder in that case. So I asked
around in my social circles if anyone knew an electrician. My good friend, <del>the
Broom</del> <a href="https://github.com/besendorf">Besen</a> told me: &ldquo;I&rsquo;m not an electrician, but when I worked on construction
sites I always did it that way&rdquo;. Very reassuring. So, I decided to listen to
his advice. We decided the best way would be to crimp the cable onto a
panel-mounted plug.</p>
<p>So that&rsquo;s what I did.</p>


    <figure><img src="https://blog.sergeantbiggs.net/img/IMG_20210716_135343.jpg"alt="A power supply. A cable with a right-angled plug is inserted into it"/><figcaption><p>The cable I&rsquo;m going to cut</p></figcaption></figure>


    <figure><img src="https://blog.sergeantbiggs.net/img/IMG_20210716_135402-1.jpg"alt="A panel mount, screwed into the back of the case"/><figcaption><p>Attaching the panel-mounted plug</p></figcaption></figure>


    <figure><img src="https://blog.sergeantbiggs.net/img/IMG_20210716_135802.jpg"alt="A cable, a wire cutter, and a cutter knife on a desk"/><figcaption><p>Cutting the cable</p></figcaption></figure>


    <figure><img src="https://blog.sergeantbiggs.net/img/IMG_20210716_140025.jpg"alt="The same cable, with the outer isolation removed"/><figcaption><p>Removing isolation</p></figcaption></figure>


    <figure><img src="https://blog.sergeantbiggs.net/img/IMG_20210716_140143.jpg"alt="The cable, with one plug crimped onto the live wire"/><figcaption><p>Crimping the plug</p></figcaption></figure>


    <figure><img src="https://blog.sergeantbiggs.net/img/IMG_20210716_140707.jpg"alt="All 3 plugs crimped onto the cable"/><figcaption><p>All plugs crimped</p></figcaption></figure>


    <figure><img src="https://blog.sergeantbiggs.net/img/IMG_20210716_141729-1.jpg"alt="The cable is attached to the socket"/></figure>


    <figure><img src="https://blog.sergeantbiggs.net/img/IMG_20210716_141608.jpg"alt="The cable is attached to the socket of the power supply and to the socket for the other cable"/><figcaption><p>Attaching the plugs</p></figcaption></figure>


    <figure><img src="https://blog.sergeantbiggs.net/img/IMG_20210716_141613.jpg"alt="Close-Up of previous picture, showing the socket with the crimped wires"/><figcaption><p>Close-Up</p></figcaption></figure>


    <figure class="left" ><img src="https://blog.sergeantbiggs.net/img/IMG_20210716_141720.jpg"alt="The cable, with a CE sticker on it"/><figcaption><p>What is that sticker doing there?</p></figcaption></figure>


    <figure class="right" ><img src="https://blog.sergeantbiggs.net/img/IMG_20210716_141752.jpg"alt="The cable, with the CE sticker removed"/><figcaption><p>Didn&rsquo;t feel right leaving it on :D</p></figcaption></figure>


    <figure><img src="https://blog.sergeantbiggs.net/img/IMG_20210716_143634.jpg"alt="The case with a warning label on it. The warning label says &#39;DO NOT OPEN&#39;"/></figure>


    <figure><img src="https://blog.sergeantbiggs.net/img/IMG_20210716_143639.jpg"alt="Another warning label. This one says: NOT CE, and includes a skull and crossbones"/><figcaption><p>I also made some warning labels to stop casual snooping, since live is now exposed inside the case even when the computer is turned off.</p></figcaption></figure>
<p>I know this is a bit different from my other projects, but I hope you liked
reading it!</p>
]]></content></item><item><title>NAS-Upgrade; How I stopped worrying and learned to love ZFS</title><link>https://blog.sergeantbiggs.net/posts/nas-zfs-upgrade/</link><pubDate>Fri, 02 Jul 2021 11:01:25 +0100</pubDate><author>SergeantBiggs</author><guid>https://blog.sergeantbiggs.net/posts/nas-zfs-upgrade/</guid><description>&amp;lt;no value&amp;gt;</description><content type="text/html" mode="escaped"><![CDATA[<p>I have a Server in my living room that runs Arch Linux. I originally set up
this server to provide a NAS. The NAS serves as a backup solution for the
clients in my network, and also stores my media collection.</p>
<p>When I initially set it up, I had just one (Desktop!) HDD: A Seagate Barracuda
with 8TB of storage. For the filesystem, I chose Btrfs at the time because I
heard some good things about it, and looking through the features it seemed to
do what I wanted. But the longer I used it, the more problems creeped up. It
doesn&rsquo;t have native filesystem encryption, so I had to use LUKS. It
supports Quotas, but doesn&rsquo;t have a good way of displaying how much storage
a specific subvolume/snapshot actually uses. It supports snapshots, <a href="https://btrfs.wiki.kernel.org/index.php/Quota_support#Known_issues">but not
too many of them</a>.
I also thought that subvolumes were very neat, so I
created a lot of them to give each &ldquo;application&rdquo; a unique path that I tried
to cram into the FHS-Philosophy. This caused more trouble than it solved
though, because now I had a lot of different paths that I had to write
down. I also made a bash script to mount all of these different subvolumes,
and had to frequently look inside this script to keep all of the paths and
subvolumes together. And with every Service I added, I needed to update
this mounting script. This wasn&rsquo;t really hard or complicated, but very
annoying.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/bin/bash
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="nv">DISK1</span><span class="o">=</span><span class="s1">&#39;/dev/sde1&#39;</span>
</span></span><span class="line"><span class="cl"><span class="nv">CNAME</span><span class="o">=</span><span class="s1">&#39;HDD_1&#39;</span>
</span></span><span class="line"><span class="cl"><span class="nv">ROOT_PATH</span><span class="o">=</span><span class="s1">&#39;/mnt&#39;</span>
</span></span><span class="line"><span class="cl"><span class="nv">JELLYFIN</span><span class="o">=</span><span class="s2">&#34;jellyfin&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">NAS_ROOT</span><span class="o">=</span><span class="s2">&#34;/media/smbnas/&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">BACKUP_ROOT</span><span class="o">=</span><span class="s2">&#34;backup&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;Opening disk&#34;</span>
</span></span><span class="line"><span class="cl">sudo cryptsetup open <span class="s2">&#34;</span><span class="nv">$DISK1</span><span class="s2">&#34;</span> <span class="s2">&#34;</span><span class="nv">$CNAME</span><span class="s2">&#34;</span> <span class="o">||</span> <span class="nb">echo</span> <span class="s1">&#39;failed&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;Mounting btrfs root&#34;</span>
</span></span><span class="line"><span class="cl">sudo mount <span class="s2">&#34;/dev/mapper/</span><span class="nv">$CNAME</span><span class="s2">&#34;</span> <span class="s2">&#34;</span><span class="nv">$ROOT_PATH</span><span class="s2">/</span><span class="nv">$CNAME</span><span class="s2">&#34;</span> -o <span class="nv">compress</span><span class="o">=</span>zstd,autodefrag
</span></span><span class="line"><span class="cl"><span class="o">||</span> <span class="nb">echo</span> <span class="s1">&#39;failed&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;Mounting btrfs subvol &#39;jellyfin&#39;&#34;</span>
</span></span><span class="line"><span class="cl">sudo mount <span class="s2">&#34;/dev/mapper/</span><span class="nv">$CNAME</span><span class="s2">&#34;</span> <span class="s2">&#34;</span><span class="nv">$NAS_ROOT</span><span class="s2">/</span><span class="nv">$JELLYFIN</span><span class="s2">&#34;</span> -o
</span></span><span class="line"><span class="cl"><span class="nv">compress</span><span class="o">=</span>zstd,autodefrag,subvol<span class="o">=</span><span class="s2">&#34;/</span><span class="nv">$JELLYFIN</span><span class="s2">&#34;</span> <span class="o">||</span> <span class="nb">echo</span> <span class="s1">&#39;failed&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;Mounting btrfs subvol &#39;backup/biggs&#39;&#34;</span>
</span></span><span class="line"><span class="cl">mount <span class="s2">&#34;/dev/mapper/</span><span class="nv">$CNAME</span><span class="s2">&#34;</span> <span class="s2">&#34;</span><span class="nv">$NAS_ROOT</span><span class="s2">/</span><span class="nv">$BACKUP_ROOT</span><span class="s2">/biggs&#34;</span> -o
</span></span><span class="line"><span class="cl"><span class="nv">compress</span><span class="o">=</span>zstd,autodefrag,subvol<span class="o">=</span><span class="s2">&#34;/</span><span class="nv">$BACKUP_ROOT</span><span class="s2">/biggs&#34;</span> <span class="o">||</span> <span class="nb">echo</span> <span class="s1">&#39;failed&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;Mounting nextcloud subvol&#34;</span>
</span></span><span class="line"><span class="cl">mount <span class="s2">&#34;/dev/mapper/</span><span class="nv">$CNAME</span><span class="s2">&#34;</span> <span class="s2">&#34;/var/nextcloud&#34;</span> -o
</span></span><span class="line"><span class="cl"><span class="nv">compress</span><span class="o">=</span>zstd,autodefrag,subvol<span class="o">=</span><span class="s2">&#34;/nextcloud&#34;</span> <span class="o">||</span> <span class="nb">echo</span> <span class="s1">&#39;failed&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;Mounting XXX subvol&#34;</span>
</span></span><span class="line"><span class="cl">mount <span class="s2">&#34;/dev/mapper/</span><span class="nv">$CNAME</span><span class="s2">&#34;</span> <span class="s2">&#34;/var/www/XXX.sergeantbiggs.net&#34;</span> -o
</span></span><span class="line"><span class="cl"><span class="nv">compress</span><span class="o">=</span>zstd,autodefrag,subvol<span class="o">=</span><span class="s2">&#34;/tiktok&#34;</span> <span class="o">||</span> <span class="nb">echo</span> <span class="s2">&#34;failed&#34;</span>
</span></span></code></pre></div><p>The script I used. It&rsquo;s a <del>bit of a</del> mess.</p>
<p>I also created another script to automate snapshot creation. This was to
protect the files on my SMB shares. If you delete a file from an SMB share on
the client side, it&rsquo;s gone. With this, I would have a chance to restore things
if they were accidentally deleted. This made listing the subvolumes a complete
shitshow, because the list was completetely cluttered with the snapshots.
So a lot of Btrfs tools were just not working very well in my situation and
I had to rely on scripts, both self-written and third-party.</p>
<h1 id="the-move">The Move<a href="#the-move" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h1>
<p>That&rsquo;s why I decided to move to ZFS. In this article, I will take you on the
journey I faced to do this. I decided to this at the same time that I did a
major hardware upgrade. Up to that point I just had one Desktop HDD. I decided
to replace it with 3 WD Reds, each 8 TB in size. I will be running these in
RAIDZ, an improved version of RAID5 that removes the write hole (a general
problem in RAID 5, something Btrfs hasn&rsquo;t been able to solve yet). It is also
generally very efficient.</p>
<p>So, how will we go about this whole thing? There are 2 options:</p>
<p>Option 1 would be to replace all the Btrfs subvolumes with the ZFS equivalent
(Datasets) and mount them at the exact same locations. The advantage would be
that I don&rsquo;t have to reconfigure the applications that use the storage. The
disadvantage would be that my services would be down while the data transfers
(and for over 5 TB, that would take a while).</p>
<p>Option 2 would be to mount both volumes, transfer the data and reconfigure the
applications after the transfer is complete. The advantage here would that I
would just have very short interruptions in service.</p>
<p>I decided to go for option 2. This has the other advantage that it forces me to
organise the data in a different way, which is hopefully more sensible than
scores of subvolumes that mount to different folders somewhere in the
hierarchy.</p>
<p>First I had to decide how I wanted to install ZFS. If you don&rsquo;t know about this
problem, here&rsquo;s a short explainer.
<a href="https://github.com/openzfs/zfs/blob/master/LICENSE">ZFS</a> uses a free software license, the <a href="https://en.wikipedia.org/wiki/Common_Development_and_Distribution_License">CDDL</a>
This is a so-called copyleft license (a license which restricts using the code
for proprietary software). One problem with this is that it is incompatible
with the <a href="https://gpl.stern.sergeantbiggs.net/">GPL</a>. This means ZFS can&rsquo;t be distributed with the Linux kernel.
There is a third-party kernel module, called
<a href="https://zfsonlinux.org/">zfsonlinux</a>, which we will be
using. For Arch Linux, there are a few options of getting this kernel
module. The <strong>recommended</strong> way of getting it is by installing a <a href="https://aur.archlinux.org/packages/zfs-linux/">patched kernel
that includes the module</a>. The advantage is that this is the easiest
solution, and installation/upgrade (which, for all intents and purposes, is
always the same in Arch!) is very fast and not any different to upgrade a
normal kernel. The disadvantage is that I have to wait for the maintainers
to release new versions of the modified kernel. This can sometimes take
months. Since I want to keep this server very up to date (and close to
upstream) for security reasons, I didn&rsquo;t like this option very much. The
other option would be to use DKMS. This recompiles the module into the
kernel every time there is a kernel update. Although the update takes
slightly longer, this has the advantage of not having to wait for the
maintainers to release a new patched kernel. The DKMS version is installed
with the following command: <code>pacman -S zfs-dkms linux-headers</code>. After
installing, we can start to configure ZFS.</p>
<h1 id="zfs-configuration">ZFS Configuration<a href="#zfs-configuration" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h1>
<p>First we create a pool for our hard drives. Zfs-on-linux recommends using
device ids when creating pools that are smaller than 10 devices. The device ids
on Arch Linux can be found by showing the contents of /dev/disk/by-id.</p>
<pre tabindex="0"><code>lrwxrwxrwx 1 root root  9 Jun 26 13:53 ata-Intenso_SSD_Sata_III_AA000000000000016985 -&gt; ../../sda
lrwxrwxrwx 1 root root 10 Jun 26 13:53 ata-Intenso_SSD_Sata_III_AA000000000000016985-part1 -&gt; ../../sda1
lrwxrwxrwx 1 root root 10 Jun 26 13:53 ata-Intenso_SSD_Sata_III_AA000000000000016985-part2 -&gt; ../../sda2
lrwxrwxrwx 1 root root 10 Jun 26 13:53 ata-Intenso_SSD_Sata_III_AA000000000000016985-part3 -&gt; ../../sda3
lrwxrwxrwx 1 root root  9 Jun 26 13:53 ata-WDC_WD80EFBX-68AZZN0_VRG0M7DK -&gt; ../../sdc
lrwxrwxrwx 1 root root  9 Jun 26 13:53 ata-WDC_WD80EFBX-68AZZN0_VRG8G6LK -&gt; ../../sdb
lrwxrwxrwx 1 root root  9 Jun 26 13:53 ata-WDC_WD80EFBX-68AZZN0_VRG8ZJNK -&gt; ../../sdd
</code></pre><p>In my case, this is /dev/sdb, /dev/sdc, and /dev/sdd. Using the device ids has
the advantage that Linux changes the &ldquo;classic&rdquo; identifiers (sd[a-z]) if the
boot order is different. So adding more disks, or just a USB drive, can change
these. This is obviously something we want to avoid. So, to create our pool we
use the following command:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">zpool create -f -m /media/ zfsnas raidz ata-WDC_WD80EFBX-68AZZN0_VRG0M7DK ata-WDC_WD80EFBX-68AZZN0_VRG8G6LK ata-WDC_WD80EFBX-68AZZN0_VRG8ZJNK
</span></span></code></pre></div><p>If the command is successful, there should be no output. We can check the
status of our pool with zpool status. The output should look like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">  pool: zfsnas
</span></span><span class="line"><span class="cl"> state: ONLINE
</span></span><span class="line"><span class="cl">config:
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        NAME                                   STATE     READ WRITE CKSUM
</span></span><span class="line"><span class="cl">        zfsnas                                 ONLINE       <span class="m">0</span>     <span class="m">0</span>     <span class="m">0</span>
</span></span><span class="line"><span class="cl">          raidz1-0                             ONLINE       <span class="m">0</span>     <span class="m">0</span>     <span class="m">0</span>
</span></span><span class="line"><span class="cl">            ata-WDC_WD80EFBX-68AZZN0_VRG0M7DK  ONLINE       <span class="m">0</span>     <span class="m">0</span>     <span class="m">0</span>
</span></span><span class="line"><span class="cl">            ata-WDC_WD80EFBX-68AZZN0_VRG8G6LK  ONLINE       <span class="m">0</span>     <span class="m">0</span>     <span class="m">0</span>
</span></span><span class="line"><span class="cl">            ata-WDC_WD80EFBX-68AZZN0_VRG8ZJNK  ONLINE       <span class="m">0</span>     <span class="m">0</span>     <span class="m">0</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">errors: No known data errors
</span></span></code></pre></div><p>So the pools are automatically imported on boot we need to enable 2 systemd services.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">systemctl <span class="nb">enable</span> zfs-import-cache
</span></span><span class="line"><span class="cl">systemctl <span class="nb">enable</span> zfs-import.target
</span></span></code></pre></div><p>After that, we want to create datasets. These look like folders, but allow us
to use other features, like mounting them under a different path and setting
quotas. We create these datasets with <code>zfs create &lt;nameofzpool&gt;/&lt;nameofdataset&gt;</code>.
In our case we also want to enable encryption. So the command looks like this</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">zfs create -o <span class="nv">encryption</span><span class="o">=</span>on -o <span class="nv">keyformat</span><span class="o">=</span>passphrase zfsnas/cryptset
</span></span></code></pre></div><p>ZFS asks us for a passphrase, which we then need to enter twice. After that we
can start to create datasets for our different services. In my case, I have one
dataset for my Nextcloud server, one for my SMB NAS, and one for the backups of
my different clients. We also set quotas for each dataset.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">zfs create zfsnas/cryptset/smb
</span></span><span class="line"><span class="cl">zfs create zfsnas/cryptset/nextcloud
</span></span><span class="line"><span class="cl">zfs create zfsnas/cryptset/backup
</span></span><span class="line"><span class="cl">zfs <span class="nb">set</span> <span class="nv">quota</span><span class="o">=</span>8TB zfsnas/cryptset/smb
</span></span><span class="line"><span class="cl">zfs <span class="nb">set</span> <span class="nv">quota</span><span class="o">=</span>200GB zfsnas/cryptset/nextcloud
</span></span><span class="line"><span class="cl">zfs <span class="nb">set</span> <span class="nv">quota</span><span class="o">=</span>4TB zfsnas/cryptset/backup
</span></span></code></pre></div><p>After we have prepared the datasets, we transfer everything from the old disk
to the new array. I&rsquo;m doing this with rsync.</p>
<h1 id="reconfiguring-services">Reconfiguring Services<a href="#reconfiguring-services" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h1>
<p>After transferring everything, we need to reconfigure our services. The one I
was most afraid of was nextcloud. There is this <a href="https://help.nextcloud.com/t/howto-change-move-data-directory-after-installation/17170">help
article</a> that begins with
this scary looking disclaimer:</p>
<p><strong>First of all: Changing data directory after installation is not officially
supported. Consider re-installing Nextcloud with new data directory, if you did
not use it too much/added users/created shares/tags/comments etc.</strong></p>
<p>That doesn&rsquo;t inspire much confidence. The problem is that NC stores information
about all files in its database. So this database to be either manually updated
(error-prone) or rebuilt (losing basically all metadata like shares, comments,
etc). I decided to do the second one, since I&rsquo;m not a DBA by any stretch of the
imagination, and I didn&rsquo;t have many shares. If someone gets their data
access cut off, they&rsquo;ll complain anyway. Then I&rsquo;ll be informed and can create a
new share.</p>
<p>The first thing I did was change the location of the data directory. To do
this, we need to change <code>/etc/webapps/nextcloud/config.php</code>. It contains a
variable called <em>datadirectory</em>. After I changed this variable, I ran <code>occ maintenance:repair</code> for good measure. To my surprise, this was all that I
needed. All files (and share links) were there. For Samba, I just changed the
config (smb.conf) to the new location.</p>
<p>After that, everything worked. I&rsquo;m looking forward to the new NAS, and I hope I
will lose some of the hassle I had with Btrfs.</p>
]]></content></item><item><title>Valheim Part 2; Keeping it up to date</title><link>https://blog.sergeantbiggs.net/posts/setting-up-valheim-part-2/</link><pubDate>Wed, 03 Mar 2021 09:36:10 +0100</pubDate><author>SergeantBiggs</author><guid>https://blog.sergeantbiggs.net/posts/setting-up-valheim-part-2/</guid><description>&amp;lt;no value&amp;gt;</description><content type="text/html" mode="escaped"><![CDATA[<p>I recently posted an article about setting up a Valheim server. After
everything was set up, I was faced with a problem that comes from the game
being under active Development:</p>
<p>Very frequent updates.</p>
<p>My friends (and I) soon grew weary of constant reminders to update the server
so I set up a way to do it automatically. First, the organisational problem.
When can I restart the server safely, and when does it disturb the least amount
of users?</p>
<p>Since I&rsquo;m a big believer in democracy I decided to use a doodle. Well, not
doodle specifically but the <a href="https://www.dfn.de/dienstleistungen/dfnterminplaner/">DFN
Terminplaner</a>, an implementation of <a href="https://framadate.org/">Framadate</a>.
Maybe I&rsquo;ll host it on my own server some day? If I do, dear reader, I&rsquo;ll be
sure to tell you about it in a future blog post. But for now, back to the poll.
After polling my friends, the results were pretty clear:</p>


    <figure><img src="https://blog.sergeantbiggs.net/img/results_poll_valheim.png"alt="The results of a poll. All polled users chose a time between 09:00 and 16:00."/><figcaption><p>Direct quote: &lsquo;11:00 would be perfect, because that&rsquo;s when we usually go to sleep.&rsquo;</p></figcaption></figure>
<p>Not quite the typical maintenance time for servers, right? Alas, I don&rsquo;t really
care about that. If they are happy with it, I&rsquo;m happy with it. So I settled on
12:00 to make sure they are sleeping (should work in 99% of cases).</p>
<p>Now I need a way to automatically run the update command.</p>
<p>The &ldquo;classic&rdquo; Linux/Unix way would be to use <a href="https://en.wikipedia.org/wiki/Cron">cron</a>. But why use something boring
that was invented for <a href="https://en.wikipedia.org/wiki/Version_7_Unix">V7 Unix</a> (almost 42 years ago!)? systemd can do something
similar, with some
<a href="https://wiki.archlinux.org/index.php/Systemd/Timers#Caveats">caveats</a>. So let&rsquo;s just do that. We will all have to bow to
Poettering and Sievers some day, even if we lose our <a href="https://youtube.com/watch?v=RG9rE1pcMcU">home
directories</a> in the
process. The <a href="https://en.wikipedia.org/wiki/Unix_philosophy">old</a> <a href="https://en.wikipedia.org/wiki/Everything_is_a_file">gods</a> <a href="https://en.wikipedia.org/wiki/Ken_Thompson">are</a> <a href="https://en.wikipedia.org/wiki/Dennis_Ritchie">dead</a>. The new ones are just as terrifying. They could
only be stopped by a combined uprising of devuan (remember those guys?) and
gentoo users. Or maybe that one guy that approached me during a Linux install
party because he wanted to use <a href="https://www.eduroam.org/">eduroam</a> on his NixOS machine without using
wpa_supplicant.</p>
<p>Anyway.</p>
<p>I decided to use <a href="https://en.wikipedia.org/wiki/Version_7_Unix">systemd timers</a>.</p>
<p>With this approach we need two systemd units. A service that runs the program
and a timer that specifies the time.</p>
<p>update-valheim.service:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[Unit]</span>
</span></span><span class="line"><span class="cl"><span class="na">Description</span><span class="o">=</span><span class="s">Update Valheim server</span>
</span></span><span class="line"><span class="cl"><span class="na">After</span><span class="o">=</span><span class="s">network-online.target</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[Service]</span>
</span></span><span class="line"><span class="cl"><span class="na">WorkingDirectory</span><span class="o">=</span><span class="s">/home/steam/Valheim/</span>
</span></span><span class="line"><span class="cl"><span class="na">ExecStart</span><span class="o">=</span><span class="s">/home/steam/update_valheim.sh</span>
</span></span><span class="line"><span class="cl"><span class="na">Type</span><span class="o">=</span><span class="s">oneshot</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[Install]</span>
</span></span><span class="line"><span class="cl"><span class="na">WantedBy</span><span class="o">=</span><span class="s">multi-user.target</span>
</span></span></code></pre></div><p>update-valheim.timer:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[Unit]</span>
</span></span><span class="line"><span class="cl"><span class="na">Description</span><span class="o">=</span><span class="s">Valheim updater timer</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[Timer]</span>
</span></span><span class="line"><span class="cl"><span class="na">OnCalendar</span><span class="o">=</span><span class="s">*-*-* 12:00:00</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[Install]</span>
</span></span><span class="line"><span class="cl"><span class="na">WantedBy</span><span class="o">=</span><span class="s">timers.target</span>
</span></span></code></pre></div><p>The line <code>OnCalendar=*-*-* 12:00:00</code> specifies that we want to execute it every
day at 12:00.After that, we just start/enable the timer</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">systemctl --user start update-valheim.timer
</span></span><span class="line"><span class="cl">systemctl --user <span class="nb">enable</span> update-valheim.timer
</span></span></code></pre></div><p>The script to actually update the server has the following content:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/bin/bash
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="nv">OUTPUT</span><span class="o">=</span><span class="k">$(</span>steamcmd +login anonymous +force_install_dir /home/steam/Valheim +app_update <span class="m">8966</span> <span class="m">60</span> +quit <span class="p">|</span> tail -n 1<span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># If app is out of date, restart Service</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> ! <span class="o">[[</span> <span class="s2">&#34;</span><span class="nv">$OUTPUT</span><span class="s2">&#34;</span> <span class="o">==</span> <span class="s2">&#34;Success! App &#39;896660&#39; already up to date.&#34;</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">        <span class="nb">echo</span> <span class="s2">&#34;Restarting valheim&#34;</span>
</span></span><span class="line"><span class="cl">        systemctl --user restart valheim.service
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span></code></pre></div><p>If steamCMD detects that the app is already up to date it will output <code>Success! App '896660' already up to date</code>.</p>
<p>We don&rsquo;t restart the server, if steamCMD didn&rsquo;t change any of the files. If it
did, we restart the service with systemd. And that&rsquo;s that. A pretty simple
solution for a pretty annoying problem. Hopefully it works. If it doesn&rsquo;t I
will of course update you, dear reader!</p>
]]></content></item><item><title>Setting Up Valheim</title><link>https://blog.sergeantbiggs.net/posts/setting-up-valheim/</link><pubDate>Mon, 15 Feb 2021 08:09:52 +0100</pubDate><author>SergeantBiggs</author><guid>https://blog.sergeantbiggs.net/posts/setting-up-valheim/</guid><description>&amp;lt;no value&amp;gt;</description><content type="text/html" mode="escaped"><![CDATA[<p>I recently started getting into <a href="https://www.valheimgame.com/">Valheim</a>. I&rsquo;ve been playing it cooperatively
with some good friends of mine, and soon after starting a world we decided we
needed a dedicated server. We&rsquo;re four people with very different schedules (I
work, they&rsquo;re at uni) so it really was a no-brainer.</p>
<p>Setting up your own server is pretty easy, although there were some pitfalls.
To document those, and also to show how I did it differently (because of course
I did), I&rsquo;m writing this blog post.</p>
<p>The first thing you need to do is to install <a href="https://developer.valvesoftware.com/wiki/SteamCMD">SteamCMD</a>.
This is a CLI-client for
Steam that you need to install the dedicated server. After you install it, it&rsquo;s
a good idea to create a separate user for your games:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">useradd -m steam
</span></span></code></pre></div><p>I added an directory in the home folder of the steam user called Valheim. To
install it to that folder, you need to enter the following command</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">steamcmd +login anonymous +force_install_dir /home/steam/Valheim +app_update <span class="m">896660</span> +exit
</span></span></code></pre></div><p>You can get the ID (896660) from <a href="https://steamdb.info/search/?a=app&amp;q=server">this steam
database</a>. Just search for Valheim,
and it should come up. You can also use the command above to update your
server.</p>
<p>Most guides recommend you use the Bash script inside the folder to start the
server.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">templdpath</span><span class="o">=</span><span class="nv">$LD_LIBRARY_PATH</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">LD_LIBRARY_PATH</span><span class="o">=</span>./linux64:<span class="nv">$LD_LIBRARY_PATH</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">SteamAppId</span><span class="o">=</span><span class="m">892970</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;Starting server PRESS CTRL-C to exit&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Tip: Make a local copy of this script to avoid it being overwritten by steam.</span>
</span></span><span class="line"><span class="cl"><span class="c1"># NOTE: Minimum password length is 5 characters &amp; Password cant be in the server name.</span>
</span></span><span class="line"><span class="cl"><span class="c1"># NOTE: You need to make sure the ports 2456-2458 is being forwarded to your server through your local router &amp; firewall.</span>
</span></span><span class="line"><span class="cl">./valheim_server.x86_64 -name <span class="s2">&#34;server_name&#34;</span> -port <span class="m">2456</span> -world <span class="s2">&#34;world_name&#34;</span> -password <span class="s2">&#34;your_password&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">LD_LIBRARY_PATH</span><span class="o">=</span><span class="nv">$templdpath</span>
</span></span></code></pre></div><p>I decided against it, because we might as well use a systemd service file.
After all, that&rsquo;s what they&rsquo;re designed for! The systemd service file should
look a little like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[Unit]</span>
</span></span><span class="line"><span class="cl"><span class="na">Description</span><span class="o">=</span><span class="s">Valheim Headless Server</span>
</span></span><span class="line"><span class="cl"><span class="na">After</span><span class="o">=</span><span class="s">network-online.target</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[Service]</span>
</span></span><span class="line"><span class="cl"><span class="na">WorkingDirectory</span><span class="o">=</span><span class="s">/home/steam/Valheim/</span>
</span></span><span class="line"><span class="cl"><span class="na">ExecStart</span><span class="o">=</span><span class="s">/home/steam/Valheim/valheim_server.x86_64 -name &#34;server_name&#34; -port 2456 -world &#34;world_name&#34; -password &#34;your_password&#34;</span>
</span></span><span class="line"><span class="cl"><span class="na">KillSignal</span><span class="o">=</span><span class="s">SIGINT</span>
</span></span><span class="line"><span class="cl"><span class="na">Environment</span><span class="o">=</span><span class="s">&#34;LD_LIBRARY_PATH=./linux64:$LD_LIBRARY_PATH&#34;</span>
</span></span><span class="line"><span class="cl"><span class="na">Environment</span><span class="o">=</span><span class="s">&#34;SteamAppId=892970&#34;</span>
</span></span><span class="line"><span class="cl"><span class="na">Environment</span><span class="o">=</span><span class="s">&#34;TERM=xterm&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[Install]</span>
</span></span><span class="line"><span class="cl"><span class="na">WantedBy</span><span class="o">=</span><span class="s">multi-user.target</span>
</span></span></code></pre></div><p>One important thing to note is the line <code>Environment=&quot;TERM=xterm&quot;</code>. For some
reason, unity games need TERM set to xterm. Otherwise
the game doesn&rsquo;t work. Since I&rsquo;m using tmux (with TERM set to tmux-256color), I
have to set it to xterm. I also set the other Environment variables that were
in the Bash script.</p>
<p>One thing you need to do is change your firewall to allow ports 2456-2458. If
you use nftables it should look like this.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="cl"><span class="k">net</span> <span class="s">filter</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kn">chain</span> <span class="s">input</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kn">type</span> <span class="s">filter</span> <span class="s">hook</span> <span class="s">input</span> <span class="s">priority</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kn">policy</span> <span class="s">drop</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kn">iif</span> <span class="s">lo</span> <span class="s">accept</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># Early dropping of invalid packets:
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="s">ct</span> <span class="s">state</span> <span class="s">invalid</span> <span class="s">drop</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># Accept traffic originating from us
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="s">ct</span> <span class="s">state</span> <span class="s">established,related</span> <span class="s">accept</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># Valheim
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>        <span class="s">tcp</span> <span class="s">dport</span> <span class="mi">2456</span><span class="s">-2458</span> <span class="s">accept</span>
</span></span><span class="line"><span class="cl">        <span class="s">udp</span> <span class="s">dport</span> <span class="mi">2456</span><span class="s">-2458</span> <span class="s">accept</span>
</span></span><span class="line"><span class="cl">    <span class="err">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="s">chain</span> <span class="s">output</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kn">type</span> <span class="s">filter</span> <span class="s">hook</span> <span class="s">output</span> <span class="s">priority</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kn">policy</span> <span class="s">accept</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kn">oif</span> <span class="s">lo</span> <span class="s">accept</span>
</span></span><span class="line"><span class="cl">    <span class="err">}</span>
</span></span><span class="line"><span class="cl"><span class="err">}</span>
</span></span></code></pre></div><p>If you had a previous world you need to copy it over. On windows, it can be
found in <code>C:\Users\user\AppData\LocalLow\IronGate\Valheim\worlds</code>. In that folder
you should have two files. You need to copy those two files into
<code>~/.config/unity3d/IronGate/Valheim/worlds</code>. If you don&rsquo;t have that folder, you
can just run the binary once to create it.</p>
<p>After that, you&rsquo;re technically ready to go. I was faced with one more problem
though: the Server uses about 30% of my total CPU resources, even when no one
is connected! This is a needless waste of my resources, since my machine runs
24/7. So I decided I would only start it when people actually wanted to play.
My electric bill will thank me.</p>
<p>But when I relayed this decision to my friends, the wailing and lamentations
began. &ldquo;How will we be able to play it whenever we want? We can&rsquo;t live with
this! We need a way to start the server ourselves!!&rdquo;. My assurances of a swift
reaction when contacted via Discord, fell on deaf ears. So I wracked my brain.
How can I make sure my (mischievous) friends can start (and stop!) the server
themselves, without giving them shell access to my machine?</p>
<p>My solution consisted of two components</p>
<ul>
<li><a href="https://wiki.archlinux.org/index.php/Systemd/User">systemd/User</a></li>
<li>SSH Keys + the authorized_keys file</li>
</ul>
<p>Systemd/User is a way of creating systemd units that can be controlled by the
user. They are placed in the users home directory (~/.config/systemd/user/).
They can then be controlled by passing the <code>--user</code> flag when running systemd
commands. This allowed me to circumvent giving them root/sudo access, which is
necessary for normal systemd operation.</p>
<p><strong>NB</strong>: If you want to use systemd/User configuration, you have to configure
<em>lingering</em>. this makes sure that the service is not stopped after the user has
logged out:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">loginctl enable-linger steam
</span></span></code></pre></div><p>After that I needed to find a way for them to execute commands without being
able to log in. And SSH has a way of doing that! In the authorized_keys file
(which contains the public keys for authentication) you can set a custom
command that will be executed when they connect via SSH. So for each user (key)
you get the following line in authorized_keys.</p>
<pre tabindex="0"><code># Limited users (can only start, stop, and query status of valheim server)
command=&#34;./manage_valheim.sh $SSH_ORIGINAL_COMMAND&#34; ssh-rsa KEY_GOES_HERE
</code></pre><p>The variable $SSH_ORIGINAL_COMMAND contains the command they enter on the
command line when using SSH. This command can be either start, stop, or status.
The command is parsed with manage_valheim.sh, a Bash script.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/bin/bash
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="nv">SCTL</span><span class="o">=</span><span class="s2">&#34;systemctl --user&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">function</span> usage<span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">	<span class="nb">echo</span> <span class="s2">&#34;Usage: start stop status&#34;</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">case</span> <span class="nv">$1</span> in
</span></span><span class="line"><span class="cl">	start<span class="o">)</span>
</span></span><span class="line"><span class="cl">		<span class="nv">$SCTL</span> start valheim.service
</span></span><span class="line"><span class="cl">		<span class="p">;;</span>
</span></span><span class="line"><span class="cl">	stop<span class="o">)</span>
</span></span><span class="line"><span class="cl">		<span class="nv">$SCTL</span> stop valheim.service
</span></span><span class="line"><span class="cl">		<span class="p">;;</span>
</span></span><span class="line"><span class="cl">	status<span class="o">)</span>
</span></span><span class="line"><span class="cl">		<span class="nv">$SCTL</span> status valheim.service
</span></span><span class="line"><span class="cl">		<span class="p">;;</span>
</span></span><span class="line"><span class="cl">	*<span class="o">)</span>
</span></span><span class="line"><span class="cl">		<span class="nb">echo</span> <span class="s2">&#34;Command not recognized!&#34;</span>
</span></span><span class="line"><span class="cl">		usage
</span></span><span class="line"><span class="cl">		<span class="p">;;</span>
</span></span><span class="line"><span class="cl"><span class="k">esac</span>
</span></span></code></pre></div><p>The script parses user input and then starts the appropriate action (start,
stop, or status). This way, the users that have public keys in authorized_keys
can &ldquo;manage&rdquo; the server without having complete shell access.</p>
<p>Now my friends are happy, because they have an easy, user friendly way of
managing the server. I&rsquo;m happy because they don&rsquo;t have any more access than
they need. And, perhaps most importantly, my electric bill is happy.</p>
<p>Thanks for the read, I hope you enjoyed it. And I hope it will help you in
setting up your own Valheim server. And one last thing: Play Valheim! It&rsquo;s a
great game, shout-out to the Developer IronGate Studio. You guys did a great
job!</p>
]]></content></item><item><title>Using Monitoring for Great Good</title><link>https://blog.sergeantbiggs.net/posts/using-monitoring-for-great-good/</link><pubDate>Fri, 15 Jan 2021 20:00:39 +0100</pubDate><author>SergeantBiggs</author><guid>https://blog.sergeantbiggs.net/posts/using-monitoring-for-great-good/</guid><description>&amp;lt;no value&amp;gt;</description><content type="text/html" mode="escaped"><![CDATA[<p>I was recently reminded of the importance of a good monitoring solution. For
the main server in my home network I use <a href="https://www.netdata.cloud/">Netdata</a>,
an open-source monitoring
tool. I&rsquo;ve been very happy with it from the beginning. It&rsquo;s easy to install,
configure, and maintain. It shows a ton of information. And it has a very handy
feature:</p>
<p><em>A <a href="https://learn.netdata.cloud/docs/agent/health">health monitoring</a> system with built in <a href="https://learn.netdata.cloud/docs/agent/health/notifications">notifications</a>.</em></p>
<p>I&rsquo;m using a notification system that uses Email and also a Telegram bot. The
bot is especially handy, since I get the notifications pretty much
instantly when an alarm is raised, and I receive notifications on the go. One
day I received some puzzling alarms.</p>


    <figure><img src="https://blog.sergeantbiggs.net/img/netdata_alarms.png"alt="Screenshot of 3 Telegram messages. The first one (warning) says the ratio of dropped packets is 0.29%. The second one warns about the disk backlog being 2112 ms. The third is an error, and says that the ratio of dropped packets has gone to 11%."/><figcaption><p>The alarms in question</p></figcaption></figure>
<p>These three messages in succession were strange. Normally I get some packet
loss and disk backlog on the system. I use it as a NAS, and this can happen
when transferring large files. But the last message is really strange. <strong>11%</strong>
packet loss? That&rsquo;s <em>huge</em>! So I started wondering what it was. I logged into the
Netdata web interface. The CPU graph immediately caught my eye.</p>


    <figure><img src="https://blog.sergeantbiggs.net/img/spike.png"alt="A netdata CPU graph. The graph has very obvious spikes that happen every 10-20 seconds."/><figcaption><p>Netdata CPU graph</p></figcaption></figure>
<p>What were these strange spikes? I thought the best Idea would be to just login
to the server and check it locally. So I fired up <a href="https://htop.dev/">htop</a> (still the best task
manager) and just waited for the spike. Unfortunately, since the spikes are
very short, I wasn&rsquo;t able to take a screenshot. But the culprit was soon found.
It was Factorio!</p>
<p>Factorio is a survival/management game and I occasionally play it with some
friends of mine, hence the server.</p>
<p>After I found out that Factorio was the culprit, I looked into it a bit
further. With <code>journalctl -u -f factorio.service</code> I looked at the live log of
the service.</p>
<pre tabindex="0"><code class="language-log" data-lang="log">31 04:01:15 stern systemd[1]: factorio.service: Scheduled restart job, restart counter is at 3474.
Dec 31 04:01:15 stern systemd[1]: Stopped Factorio game headless server.
Dec 31 04:01:15 stern systemd[1]: Started Factorio game headless server.
Dec 31 04:01:15 stern factorio[100820]:    0.000 2020-12-31 04:01:15; Factorio 1.0.0 (build 54889, linux64, headless)
Dec 31 04:01:15 stern factorio[100820]:    0.000 Operating system: Linux
Dec 31 04:01:15 stern factorio[100820]:    0.000 Program arguments: &#34;/usr/bin/factorio&#34; &#34;--server-settings&#34; &#34;/etc/factorio/server-settings.json&#34; &#34;--use-server-whitelist&#34; &#34;--server-whitelist&#34; &#34;/etc/factorio/server-whitelist.json&#34; &#34;--server-adminlist&#34; &#34;/etc/factorio/server-adminlist.json&#34; &#34;--start-server-load-latest&#34;
Dec 31 04:01:15 stern factorio[100820]:    0.000 Read data path: /usr/share/factorio
Dec 31 04:01:15 stern factorio[100820]:    0.000 Write data path: /var/lib/factorio/.factorio [82445/117495MB]
Dec 31 04:01:15 stern factorio[100820]:    0.000 Binaries path: /usr
Dec 31 04:01:15 stern factorio[100820]:    0.010 System info: [CPU: Intel(R) Celeron(R) J4105 CPU @ 1.50GHz, 4 cores, RAM: 7607 MB]
Dec 31 04:01:15 stern factorio[100820]:    0.010 Environment: DISPLAY=&lt;unset&gt; WAYLAND_DISPLAY=&lt;unset&gt; DESKTOP_SESSION=&lt;unset&gt; XDG_SESSION_DESKTOP=&lt;unset&gt; XDG_CURRENT_DESKTOP=&lt;unset&gt; __GL_FSAA_MODE=&lt;unset&gt; __GL_LOG_MAX_ANISO=&lt;unset&gt; __GL_SYNC_TO_VBLANK=&lt;unset&gt; __GL_SORT_FBCONFIGS=&lt;unset&gt; __GL_YIELD=&lt;unset&gt;
Dec 31 04:01:15 stern factorio[100820]:    0.010 Running in headless mode
Dec 31 04:01:15 stern factorio[100820]:    0.014 Loading mod core 0.0.0 (data.lua)
Dec 31 04:01:15 stern factorio[100820]:    0.122 Loading mod base 1.0.0 (data.lua)
Dec 31 04:01:16 stern factorio[100820]:    0.691 Loading mod base 1.0.0 (data-updates.lua)
Dec 31 04:01:16 stern factorio[100820]:    0.974 Checksum for core: 2630831588
Dec 31 04:01:16 stern factorio[100820]:    0.974 Checksum of base: 3509992273
Dec 31 04:01:17 stern factorio[100820]:    1.347 Prototype list checksum: 3301461508
Dec 31 04:01:17 stern factorio[100820]:    1.444 Info PlayerData.cpp:68: Local player-data.json available, timestamp 1609383665
Dec 31 04:01:17 stern factorio[100820]:    1.444 Info PlayerData.cpp:75: Cloud player-data.json unavailable
Dec 31 04:01:17 stern factorio[100820]:    1.447 Factorio initialised
Dec 31 04:01:17 stern factorio[100820]:    1.448 Info HttpSharedState.cpp:54: Downloading https://auth.factorio.com/api-login?api_version=4
Dec 31 04:01:17 stern factorio[100820]:    2.217 Info AuthServerConnector.cpp:41: Auth server returned error: {&#34;data&#34;:{},&#34;error&#34;:&#34;login-insufficient-membership&#34;,&#34;message&#34;:&#34;Your account doesn&#39;t own the game.  Please buy it to log in.  If you are playing through Steam, please check your Steam account is linked to your Factorio account&#34;}
Dec 31 04:01:17 stern factorio[100820]:    2.217 Error CommandLineMultiplayer.cpp:345: Hosting multiplayer game failed: Your account doesn&#39;t own the game. Please buy it to log in.
Dec 31 04:01:17 stern factorio[100820]:    2.223 Info ServerMultiplayerManager.cpp:136: Quitting multiplayer connection.
Dec 31 04:01:17 stern factorio[100820]:    2.223 Info ServerMultiplayerManager.cpp:769: updateTick(4294967295) changing state from(Ready) to(Closed)
Dec 31 04:01:18 stern factorio[100820]:    2.288 Goodbye
</code></pre><p>Note the restart counter, which is currently at <strong>3474!</strong> The log had millions of
entries like this, and the service was restarting every few seconds. Apparently
it was doing this for weeks/months now. The reason was that my Steam account
wasn&rsquo;t linked to my Factorio account anymore. After relinking, it worked
again.</p>
<p>So, what did we learn from this experience. First of all, look at your systemd
units, and how they are configured. The problem here was two-fold. First off,
the <a href="https://aur.archlinux.org/packages/factorio-headless/">AUR package for factorio-headless</a> isn&rsquo;t&hellip; ideal. It&rsquo;s been out of date for
years and does really weird things. The systemd unit it ships makes no sense in
a lot of ways. IMHO, shipping a systemd unit for something like this makes no
sense anyway. Anyone who sets up a Factorio server will likely have a specific
way of configuring it. When I first installed the AUR package I had to change a
lot to the systemd unit to make it work in my situation. I&rsquo;ll include the unit
below, maybe you can spot the mistake.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[Unit]</span>
</span></span><span class="line"><span class="cl"><span class="na">Description</span><span class="o">=</span><span class="s">Factorio game headless server</span>
</span></span><span class="line"><span class="cl"><span class="na">Documentation</span><span class="o">=</span><span class="s">http://www.factorio.com/</span>
</span></span><span class="line"><span class="cl"><span class="na">After</span><span class="o">=</span><span class="s">network.target</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[Service]</span>
</span></span><span class="line"><span class="cl"><span class="na">User</span><span class="o">=</span><span class="s">factorio</span>
</span></span><span class="line"><span class="cl"><span class="na">Group</span><span class="o">=</span><span class="s">factorio</span>
</span></span><span class="line"><span class="cl"><span class="na">EnvironmentFile</span><span class="o">=</span><span class="s">/etc/conf.d/factorio</span>
</span></span><span class="line"><span class="cl"><span class="na">WorkingDirectory</span><span class="o">=</span><span class="s">/var/lib/factorio</span>
</span></span><span class="line"><span class="cl"><span class="na">ExecStart</span><span class="o">=</span><span class="s">/usr/bin/factorio --server-settings /etc/factorio/server-settings.json --use-server-whitelist --server-whitelist /etc/factorio/server-whitelist.json --server-adminlist /etc/factorio/server-adminlist.json --start-server-load-latest</span>
</span></span><span class="line"><span class="cl"><span class="na">TimeoutStopSec</span><span class="o">=</span><span class="s">30</span>
</span></span><span class="line"><span class="cl"><span class="na">KillSignal</span><span class="o">=</span><span class="s">SIGINT</span>
</span></span><span class="line"><span class="cl"><span class="na">RestartSec</span><span class="o">=</span><span class="s">10</span>
</span></span><span class="line"><span class="cl"><span class="na">Restart</span><span class="o">=</span><span class="s">on-failure</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[Install]</span>
</span></span><span class="line"><span class="cl"><span class="na">WantedBy</span><span class="o">=</span><span class="s">multi-user.target</span>
</span></span></code></pre></div><p>So, did you catch it? It&rsquo;s line 15, <code>Restart=on-failure</code>. I&rsquo;ve never been a big
fan of autorestarting units, but for something like Factorio it makes no sense
at all. It&rsquo;s not a critical service. And if the service fails, I want to know
why, and I don&rsquo;t want it to just autorestart over and over. Which brings me to
the second problem:</p>
<p>I didn&rsquo;t notice. I just installed the service, configured it until it worked
and didn&rsquo;t give it much thought afterwards. This really goes to show the
benefit of knowing how systemd works and writing your own units, instead of
just copying other peoples work, who may or may not know what they are doing.
Even as I am writing this article, I noticed another mistake in the service
file.</p>
<p>The line <code>After=network.target</code> should be <code>After=network-online.target</code>. <a href="https://www.freedesktop.org/wiki/Software/systemd/NetworkTarget/">Here&rsquo;s
why</a>.</p>
<p>To cite the page:</p>
<p>&ldquo;<code>network.target</code> has very little meaning during start-up. It only indicates that
the network management stack is up after it has been reached. Whether any
network interfaces are already configured when it is reached is undefined. Its
primary purpose is for ordering things properly at shutdown.&rdquo;</p>
<p><code>network-online.target</code> makes sure the network is actually up, and it is the one
you want when you use a service that needs access to the internet (e.g. a
Factorio server).</p>
<p>In this experience, I learned a few things:</p>
<ul>
<li>Look at your systemd units (ideally configure them manually for things like
this)</li>
<li>Monitoring tools are nice, Netdata is very nice</li>
<li>If your monitoring tools have alarms, configure them so you get them
somewhere where you can actually see them</li>
</ul>
<p>I have to add that I don&rsquo;t know if the error was actually connected to
Factorio, since the service had this problem for a few weeks/months. But the
error is what made me curious, and made me take a closer look.</p>
<p>I hope you enjoyed this little piece. May it help you in your future
endeavors!</p>
]]></content></item><item><title>Printer Modification Part 2; Raspberry Pies solve everything</title><link>https://blog.sergeantbiggs.net/posts/printer-modification-part-2/</link><pubDate>Sun, 09 Aug 2020 18:37:55 +0100</pubDate><author>SergeantBiggs</author><guid>https://blog.sergeantbiggs.net/posts/printer-modification-part-2/</guid><description>&amp;lt;no value&amp;gt;</description><content type="text/html" mode="escaped"><![CDATA[<p>I&rsquo;ve never had a raspberry pi, because I&rsquo;ve never seen the point of owning one.
Most &ldquo;common&rdquo; use cases didn&rsquo;t make much sense to me. Especially since I&rsquo;ve
always had &ldquo;home servers&rdquo;, so much of the classical Raspi stuff never seemed
necessary to me. When I need some network service (pihole, NAS, DNS, Firewall,
Web servers, etc.) I very much prefer to just let it run on a device with
decent specs.</p>
<p>That said, I very much like the Pi Zero, especially the Zero W. It&rsquo;s very
small, very versatile and very very cheap. It&rsquo;s especially handy if you want to
do things with microelectronics. And it&rsquo;s exactly what I need for the second
function of my DeskJet: the scanning function.</p>
<p>Getting a fully functioning scanning device has been a journey. One of epic
proportions. One of heartbreak and failure, but ultimately of success.</p>
<p>The story begins with the fact that
<a href="https://en.wikipedia.org/wiki/Scanner_Access_Now_Easy">SANE</a>
doesn&rsquo;t really have a windows client.
Yes, there are deprecated pieces of software floating around SourceForge (who
remembers those guys, eh?), but I really don&rsquo;t like the idea of using one of
those. Especially since I will then be dependent on this software for years. So
I started writing my own scanning &ldquo;interface&rdquo;. I decided to go with a HTTP
server, since it is platform-independent. It also allows me to add new features
and write my own &ldquo;API&rdquo;. The server has a PHP script which just executes a
command that scans the image. I would not recommend this approach, because it
has a lot of security problems, but it works quite well for me. I&rsquo;m not too
worried about this for my own setup, since the server is only exposed to the
LAN and is protected with a password. I <strong>don&rsquo;t</strong> recommend this for any kind of
production setup though.</p>
<p>The finished scan is saved on an SMB share. The clients in the network can
access this share. The share is read-only, so users can&rsquo;t delete files of other
users. So the share doesn&rsquo;t get filled with thousands of documents, the files
in the folder will be periodically deleted. This is done with a simple Bash
script that is periodically executed with a cron job.</p>
<p>For this setup you will need: a web server + a PHP implementation. I&rsquo;m using
<a href="https://wiki.archlinux.org/index.php/Nginx#PHP_implementation">NGINX and PHP-FPM</a>.
You also need some network share for the scanned files.</p>
<p>First we install php-fpm and nginx and configure them accordingly. I&rsquo;ll include
examples of the configuration files.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="cl"><span class="k">server</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="kn">server_name</span> <span class="s">scan.example.lan</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="kn">listen</span> <span class="mi">443</span> <span class="s">ssl</span> <span class="s">http2</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="kn">ssl_certificate</span> <span class="s">/etc/ssl/cert.pem</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="kn">ssl_certificate_key</span> <span class="s">/etc/ssl/key.pem</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="kn">auth_basic</span> <span class="s">&#34;Scan</span> <span class="s">Web</span> <span class="s">Interface&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="kn">auth_basic_user_file</span> <span class="s">scan_authfile</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="kn">root</span> <span class="s">/var/www/scan.example.lan</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="kn">location</span> <span class="s">/</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="kn">index</span>               <span class="s">index.html</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="kn">location</span> <span class="p">~</span> <span class="sr">\.php$</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="c1"># 404
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>		<span class="kn">try_files</span> <span class="nv">$fastcgi_script_name</span> <span class="p">=</span><span class="mi">404</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="kn">include</span> <span class="s">fastcgi_params</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="kn">fastcgi_pass</span>                        <span class="s">unix:/run/php-fpm/php-fpm.sock</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">		<span class="kn">fastcgi_index</span>                       <span class="s">index.php</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">		<span class="kn">fastcgi_buffers</span>                     <span class="mi">8</span> <span class="mi">16k</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">		<span class="kn">fastcgi_buffer_size</span>                 <span class="mi">32k</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="kn">fastcgi_param</span> <span class="s">DOCUMENT_ROOT</span>         <span class="nv">$realpath_root</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">		<span class="kn">fastcgi_param</span> <span class="s">SCRIPT_FILENAME</span>       <span class="nv">$realpath_root$fastcgi_script_name</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span></code></pre></div><p>The web interface itself is as simple as possible, because web development is
not my forté. Look at the HTML and PHP as a minimalistic example to get you
started:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="cp">&lt;!DOCTYPE html&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">html</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">head</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">meta</span> <span class="na">charset</span><span class="o">=</span><span class="s">&#34;UTF-8&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">title</span><span class="p">&gt;</span>Best scanning page ever<span class="p">&lt;/</span><span class="nt">title</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;</span><span class="nt">link</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;/styles.css&#34;</span> <span class="na">rel</span><span class="o">=</span><span class="s">&#34;stylesheet&#34;</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;text/css&#34;</span> <span class="na">media</span><span class="o">=</span><span class="s">&#34;all&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">head</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">body</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">         <span class="p">&lt;</span><span class="nt">form</span> <span class="na">action</span><span class="o">=</span><span class="s">&#34;index.php&#34;</span> <span class="na">method</span><span class="o">=</span><span class="s">&#34;post&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">            <span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="o">=</span><span class="s">&#34;submit&#34;</span> <span class="na">name</span><span class="o">=</span><span class="s">&#34;scan&#34;</span> <span class="na">value</span><span class="o">=</span><span class="s">&#34;Click to scan&#34;</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">&lt;/</span><span class="nt">form</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">body</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">html</span><span class="p">&gt;</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="o">&lt;?</span><span class="nx">php</span>
</span></span><span class="line"><span class="cl"><span class="nv">$datetime</span> <span class="o">=</span> <span class="nx">date</span><span class="p">(</span><span class="s2">&#34;Y-m-d_His&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nx">exec</span><span class="p">(</span><span class="s2">&#34;scanimage -o /media/smb/scan/</span><span class="si">${</span><span class="nv">datetime</span><span class="si">}</span><span class="s2">.jpg --format=jpeg --resolution 300&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="k">echo</span> <span class="s2">&#34;Scan done&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="cp">?&gt;</span><span class="err">
</span></span></span></code></pre></div><p>The share definition in smb.conf:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[scan]</span>
</span></span><span class="line"><span class="cl"><span class="na">comment</span> <span class="o">=</span> <span class="s">Scan Share</span>
</span></span><span class="line"><span class="cl"><span class="na">path</span> <span class="o">=</span> <span class="s">/media/smb/scan/</span>
</span></span><span class="line"><span class="cl"><span class="na">valid users</span> <span class="o">=</span> <span class="s">@users</span>
</span></span><span class="line"><span class="cl"><span class="na">public</span> <span class="o">=</span> <span class="s">no</span>
</span></span><span class="line"><span class="cl"><span class="na">writable</span> <span class="o">=</span> <span class="s">no</span>
</span></span><span class="line"><span class="cl"><span class="na">printable</span> <span class="o">=</span> <span class="s">no</span>
</span></span></code></pre></div><p>The bash script that periodically deletes the folder. Because the name of the
file is the date, it is relatively trivial to delete all files that are older
than a certain time.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/bin/bash
</span></span></span><span class="line"><span class="cl"><span class="cp"></span><span class="nb">shopt</span> -s nullglob
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">SCAN_FOLDER</span><span class="o">=</span>/media/smb/scan/
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">function</span> error_exit<span class="o">()</span> <span class="o">{</span> <span class="nb">echo</span> <span class="s2">&#34;Error, exiting!&#34;</span><span class="p">;</span> <span class="nb">exit</span> 1<span class="p">;</span> <span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">function</span> delete_snapshots<span class="o">()</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl"><span class="nv">CUTOFF</span><span class="o">=</span><span class="s2">&#34;</span><span class="nv">$1</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> scan_file in *<span class="p">;</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="nv">snapshot_date</span><span class="o">=</span><span class="k">$(</span><span class="nb">echo</span> <span class="s2">&#34;</span><span class="nv">$scan_file</span><span class="s2">&#34;</span> <span class="p">|</span> awk -F <span class="s1">&#39;_&#39;</span> <span class="s1">&#39;{ print $1 }&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># If date is invalid, exit</span>
</span></span><span class="line"><span class="cl">    date --date <span class="s2">&#34;</span><span class="nv">$snapshot_date</span><span class="s2">&#34;</span> <span class="p">&amp;</span>&gt; /dev/null <span class="o">||</span> error_exit
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="o">[[</span> <span class="k">$(</span>date --date <span class="s2">&#34;</span><span class="nv">$snapshot_date</span><span class="s2">&#34;</span> +%s<span class="k">)</span> -lt <span class="k">$(</span>date --date <span class="s2">&#34;</span><span class="nv">$CUTOFF</span><span class="s2">&#34;</span> +%s<span class="k">)</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">        rm <span class="s2">&#34;</span><span class="nv">$scan_file</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="nb">echo</span> <span class="s2">&#34;Deleted file: </span><span class="nv">$scan_file</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">fi</span>
</span></span><span class="line"><span class="cl"><span class="k">done</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> <span class="s2">&#34;</span><span class="nv">$SCAN_FOLDER</span><span class="s2">&#34;</span> <span class="o">||</span> error_exit
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Delete files that are older than one day</span>
</span></span><span class="line"><span class="cl">delete_snapshots <span class="s2">&#34;1 day ago&#34;</span>
</span></span></code></pre></div><p>Some settings for php-fpm need to be changed. This is necessary, because
php-fpm includes is hardened by default. One of these is the fact
that it can&rsquo;t access physical devices on the system (the files in <code>/dev/</code>).
Instead it only gets access to a set of virtualized pseudo-devices (e.g.
<code>/dev/random</code>). This is a very sensible default because in 99% of cases you
really don&rsquo;t need that access when running a web server. But in our case we
need access to the scanner. To change these defaults, we need to change the
systemd service file for php-fpm.</p>
<p>To do this run <code>systemctl edit php-fpm.service</code> and add the following lines:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[Service]</span>
</span></span><span class="line"><span class="cl"><span class="na">PrivateDevices</span><span class="o">=</span><span class="s">false</span>
</span></span></code></pre></div><p>We also need to add the user <code>http</code> (or whatever user runs your nginx instance)
to the <code>scanner</code> group so the server has the necessary permissions to scan:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">usermod -aG scanner http
</span></span></code></pre></div><p>If you&rsquo;re wondering how the Raspberry Pi plays into all of this, don&rsquo;t despair
dear reader! It will all become apparent now.
The problem with this setup is that the scanner is in the living room. So every
time I want to scan a document, I have to:</p>
<ol>
<li>Walk to the scanner</li>
<li>Put a page in the scanner</li>
<li>Walk back to my computer</li>
<li>Navigate to the web interface</li>
<li>Click on Scan</li>
<li>Repeat for every page</li>
</ol>
<p>This is highly annoying, and I wanted a simpler solution. One that enables me
to scan while standing next to the scanner. Then I can just scan all pages and
copy them from the share when I&rsquo;m done. The first thing that sprang to mind was
to write an App/Shortcut for my Smartphone. While this would work well, there
are some things I don&rsquo;t like about this solution. I often don&rsquo;t have my phone
in my pocket when I&rsquo;m home, because I don&rsquo;t need it. I would have to take the
phone out, navigate to the page, input the credentials, etc. The ideal solution
would be a button on/next to the scanner. I thought about a lot of solutions.
At first I wanted a button that is connected over USB. But I didn&rsquo;t really know
how to deal with authentication so I dropped the idea.</p>
<p>After giving it some thought, I decided to go to the master of bizarre
solutions for difficult problems: my good friend
<a href="https://twitter.com/just_radow">radow</a>. They once unironically
suggested to implement a print server by setting up an email server, which then
prints the attachments of the emails it receives. This &ldquo;implementation&rdquo; was
posited as a solution to the problem &ldquo;I need a networked printer for me and my
3 roommates&rdquo;. We also once filed down the flat blade on a DVI-I plug, so it
would fit into a DVI-D socket.</p>
<p>Spoiler alert: it did <strong>not</strong> work.</p>
<p>After some deliberation we came to the conclusion that my best bet would be
some kind of networked solution, since I already have the HTTP interface. So I
settled on using a raspberry pi. That way I can solder a button to the GPIO
pins. When the button is pressed a script is executed that initiates a scan.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="ch">#!/usr/bin/env python3</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">RPi.GPIO</span> <span class="k">as</span> <span class="nn">GPIO</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">requests</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">time</span> <span class="kn">import</span> <span class="n">sleep</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">PIN_BUTTON</span> <span class="o">=</span> <span class="mi">18</span>
</span></span><span class="line"><span class="cl"><span class="n">GPIO</span><span class="o">.</span><span class="n">setmode</span><span class="p">(</span><span class="n">GPIO</span><span class="o">.</span><span class="n">BCM</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">GPIO</span><span class="o">.</span><span class="n">setup</span><span class="p">(</span><span class="n">PIN_BUTTON</span><span class="p">,</span> <span class="n">GPIO</span><span class="o">.</span><span class="n">IN</span><span class="p">,</span> <span class="n">pull_up_down</span><span class="o">=</span><span class="n">GPIO</span><span class="o">.</span><span class="n">PUD_DOWN</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">GPIO</span><span class="o">.</span><span class="n">wait_for_edge</span><span class="p">(</span><span class="n">PIN_BUTTON</span><span class="p">,</span> <span class="n">GPIO</span><span class="o">.</span><span class="n">RISING</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">requests</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="s1">&#39;https://scan.example.lan/index.php&#39;</span><span class="p">,</span> <span class="n">auth</span><span class="o">=</span><span class="p">(</span><span class="s1">&#39;user&#39;</span><span class="p">,</span> <span class="s1">&#39;password&#39;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="n">sleep</span><span class="p">(</span><span class="mf">0.5</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">except</span> <span class="ne">KeyboardInterrupt</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">GPIO</span><span class="o">.</span><span class="n">cleanup</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">()</span>
</span></span></code></pre></div><p>To deal with power I decided to use a mountable micro usb socket that extends
to a usb plug. We can then just plug in another micro usb cable into the back
of the printer.</p>


    <figure class="figure-left" ><img src="https://blog.sergeantbiggs.net/img/usb-mount.jpeg"alt="Micro USB cable to socket, mountable"/><figcaption><p>The cable I will be using</p></figcaption></figure>
<p>I don’t have any kind of dremeling tool, so I just decided to drill a round
hole into the plastic and mount it that way. It is an ugly solution, but it
works.</p>


    <figure><img src="https://blog.sergeantbiggs.net/img/IMG_20200807_152306.jpg"alt="The back of the printer. The usb socket is mounted with two screws."/><figcaption><p>Back of the printer</p></figcaption></figure>


    <figure><img src="https://blog.sergeantbiggs.net/img/IMG_20200806_154608.jpg"alt="A desk with a wire cutter, a spool of solder, a button and a lighter"/><figcaption><p>The button I&rsquo;ll be using</p></figcaption></figure>


    <figure><img src="https://blog.sergeantbiggs.net/img/IMG_20200806_154355.jpg"alt="A raspberry pi with a button soldered onto the GPIO pins"/><figcaption><p>Et voilá, the button is soldered</p></figcaption></figure>


    <figure><img src="https://blog.sergeantbiggs.net/img/IMG_20200807_152715-1.jpg"alt="An open printer, with a raspberry pi crammed into it"/><figcaption><p>&lsquo;Mounting&rsquo; the Pi</p></figcaption></figure>


    <figure><img src="https://blog.sergeantbiggs.net/img/IMG_20200807_153009-1.jpg"alt="The top of the printer. A hole is drilled, button is next to it."/><figcaption><p>Drilling a hole into the top and mount the button in there.</p></figcaption></figure>


    <figure><img src="https://blog.sergeantbiggs.net/img/IMG_20200807_171101.jpg"alt="An open printer, with a raspberry pi crammed into it"/><figcaption><p>The result</p></figcaption></figure>
<p>So that&rsquo;s the project finished. It took quite a while, and was very frustrating at times, but it all worked out in the end. I&rsquo;m very happy with the results.</p>
]]></content></item><item><title>Printer Modification Part 1; making an enterprise-grade Printer from a €50 DeskJet</title><link>https://blog.sergeantbiggs.net/posts/printer-modification-part-1/</link><pubDate>Thu, 06 Aug 2020 17:14:30 +0100</pubDate><author>SergeantBiggs</author><guid>https://blog.sergeantbiggs.net/posts/printer-modification-part-1/</guid><description>&amp;lt;no value&amp;gt;</description><content type="text/html" mode="escaped"><![CDATA[<p>This is part 1 one of what will probably be a multipart series. I bought this
DeskJet 3750 a few weeks ago. It can print and scan using a USB connection and
over Wifi.</p>


    <figure class="left" ><img src="https://blog.sergeantbiggs.net/img/deskjet.jpg"alt="A HP DeskJet 3750"/><figcaption><p>The printer in question. Apparently it&rsquo;s the smallest all-in-one ever made!! Great! That&rsquo;s going to make modding an absolute nightmare! Who the fuck would ever come up with such a stupid idea?</p></figcaption></figure>
<p>Generally I don&rsquo;t like to stick random Wifi devices in my LAN, and certainly
not proprietary devices that will probably never have their firmware updated.
But I don&rsquo;t want to drag this thing out of a closet every time I want to print
something.</p>
<p>Luckily I have a solution for that! I have a small server sitting in my living
room. It&rsquo;s running <a href="https://archlinux.org">Arch Linux</a> (Of course, what else?). So if I can connect that
printer to the Server via USB, I can then send print jobs to the server. So
let&rsquo;s get started doing that.</p>
<p>If you want to follow along, here&rsquo;s what you need to have at home</p>
<ul>
<li>A HP DeskJet 3750</li>
<li>A headless server, running Arch Linux (not technically necessary, but I will
judge you if you use any other Distro)</li>
<li>A USB Cable (A to B)</li>
</ul>
<p>So, have you got all that stuff? Great, let&rsquo;s begin.</p>
<p>We will be using a combination of Samba,
<a href="https://en.wikipedia.org/wiki/CUPS">CUPS</a> and Nginx. We use hplip for the
printer drivers. First we install the packages.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pacman -S cups samba nginx hplip
</span></span></code></pre></div><p>If you use Arch Linux, don&rsquo;t forget to start and enable the
services.</p>
<p>Let&rsquo;s dive into the configuration files!</p>
<p>For CUPS, we can just leave the configuration file as is. We will be running
the web server on localhost, and then access it using NGINX. Why, you ask?
Don&rsquo;t despair, dear reader. This will become apparent in just a few seconds.</p>
<p>I&rsquo;ll include the NGINX configuration file. Maybe you can already guess it?</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="cl"><span class="k">server</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kn">server_name</span> <span class="s">cups.example.lan</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kn">listen</span> <span class="mi">443</span> <span class="s">ssl</span> <span class="s">http2</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kn">ssl_certificate</span> <span class="s">/etc/ssl/cert.pem</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kn">ssl_certificate_key</span> <span class="s">/etc/ssl/key.pem</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kn">auth_basic</span> <span class="s">&#34;CUPS</span> <span class="s">Admin</span> <span class="s">Interface&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kn">auth_basic_user_file</span> <span class="s">cups_authfile</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kn">location</span> <span class="s">/</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kn">proxy_set_header</span> <span class="s">X-Forwarded-Host</span> <span class="nv">$host</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kn">proxy_set_header</span> <span class="s">X-Forwarded-Server</span> <span class="nv">$host</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kn">proxy_set_header</span> <span class="s">X-Forwarded-For</span> <span class="nv">$proxy_add_x_forwarded_for</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kn">proxy_pass</span> <span class="s">http://localhost:631</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kn">proxy_http_version</span> <span class="mi">1</span><span class="s">.1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kn">proxy_pass_request_headers</span> <span class="no">on</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kn">proxy_set_header</span> <span class="s">Connection</span> <span class="s">&#34;keep-alive&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kn">proxy_store</span> <span class="no">off</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Yes reader, you guessed it correctly! If we run the CUPS web interface behind
NGINX, we can protect it with a password. This is a handy way to protect web
interfaces with passwords, and to unify your web interface configuration.</p>
<p>So, after we add CUPS behind NGINX, we can add the printer. I won&rsquo;t show this
in this post, because it is ridiculously easy to do.</p>
<p>After we&rsquo;ve added the printer in cups, we can add it to Samba. Then we can use
the printer from any Windows device in the network.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[DJ-Printer]</span>
</span></span><span class="line"><span class="cl"><span class="na">printing</span> <span class="o">=</span> <span class="s">CUPS</span>
</span></span><span class="line"><span class="cl"><span class="na">comment</span> <span class="o">=</span> <span class="s">HP DeskJet 3750 InkJet Printer</span>
</span></span><span class="line"><span class="cl"><span class="na">printer</span> <span class="o">=</span> <span class="s">HP_DeskJet_3700_series</span>
</span></span><span class="line"><span class="cl"><span class="na">path</span> <span class="o">=</span> <span class="s">/var/spool/samba</span>
</span></span><span class="line"><span class="cl"><span class="na">browseable</span> <span class="o">=</span> <span class="s">no</span>
</span></span><span class="line"><span class="cl"><span class="na">guest ok</span> <span class="o">=</span> <span class="s">no</span>
</span></span><span class="line"><span class="cl"><span class="na">writable</span> <span class="o">=</span> <span class="s">no</span>
</span></span><span class="line"><span class="cl"><span class="na">printable</span> <span class="o">=</span> <span class="s">yes</span>
</span></span><span class="line"><span class="cl"><span class="na">user client driver</span> <span class="o">=</span> <span class="s">yes</span>
</span></span><span class="line"><span class="cl"><span class="na">valid users</span> <span class="o">=</span> <span class="s">@users</span>
</span></span></code></pre></div><p>Now we just need to install the drivers on windows. In these cases I always go
for the &ldquo;basic&rdquo; or &ldquo;professional&rdquo; drivers, since they don&rsquo;t have as much
bloatware. The drivers for this particular printer can be found <a href="https://support.hp.com/us-en/drivers/selfservice/hp-deskjet-3700-all-in-one-printer-series/8954253">here</a></p>
<p>After installing the drivers, we can add the printer. The easiest
way to do this, is to go to Devices and Printers -&gt; add a printer. In the next
screen, click on <code>The printer that I want isn't listed</code></p>


    <figure class="left" ><img src="https://blog.sergeantbiggs.net/img/add_printer.png"alt="the windows printer selection dialog. A border is drawn around &#39;The printer that I want isn&#39;t listed&#39;"/><figcaption><p>Printer selection dialog</p></figcaption></figure>
<p>After that click on <code>Select a shared printer by name</code>, and enter the name of
the printer in the form <code>\\hostname\printer_name</code>. The printer name is the &ldquo;share
name&rdquo; in the Samba configuration file (the one between square brackets). So in
our example it would be <code>\\hostname\DJ-Printer</code>. In the next step, Windows will
ask you to choose a driver. Just navigate the list and select the correct
manufacturer and driver.</p>
<p>And now we have a fully working network printer!</p>
<p>In the next part of this miniseries, I will talk about the scan function. We
will be adding a scan share, and the ability to scan directly from the printer.
This will involve some hardware mods, since the printer doesn&rsquo;t have a scan
button. Stay tuned!</p>
]]></content></item></channel></rss>