<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  <title>Sebastian Wiesner</title>
  <subtitle>System engineer for satellite mission planning. Rust, Arch, Linux, Gnome.</subtitle>
  <link href="https://swsnr.de/feed.xml" rel="self" />
  <link href="https://swsnr.de/" />
  <updated>2026-01-02T00:00:00Z</updated>
  <id>https://swsnr.de/</id>
  <author>
    <name>Sebastian Wiesner</name>
  </author>
  <entry>
    <title>Proton Pass as 1Password alternative?</title>
    <link href="https://swsnr.de/proton-pass-as-1password-alternative/" />
    <updated>2026-01-02T00:00:00Z</updated>
    <id>https://swsnr.de/proton-pass-as-1password-alternative/</id>
    <content type="html">&lt;h1&gt;Proton Pass as 1Password alternative?&lt;/h1&gt;
&lt;p&gt;In the days before Christmas &lt;a href=&quot;https://1password.com/&quot;&gt;1Password&lt;/a&gt; had a &lt;a href=&quot;https://www.1password.community/discussions/developers/1password-chrome-extension-is-incorrectly-manipulating--blocks/165639&quot;&gt;pretty stupid issue&lt;/a&gt; which broke syntax highlighting on all sites using PrismJS.
As my 1Password subscription also runs out soon, this nudged me to look at alternatives.&lt;/p&gt;
&lt;p&gt;I&#39;d love to use a European provider, or perhaps even self-host my own solution, and I can&#39;t help but think that that 1Password issue is a symptom of sloppy engineering.
And while I don&#39;t have specific doubts about 1Password&#39;s security standards, it&#39;s still sort of concerning that 1Password apparently just dumps random npm.js dependencies in its extension, which has all my credentials.
The NPM ecosystem doesn&#39;t have a stellar security track record after all.&lt;/p&gt;
&lt;p&gt;So I took a look at &lt;a href=&quot;https://proton.me/pass&quot;&gt;Proton Pass&lt;/a&gt;.
Unfortunately it didn&#39;t make the cut, and can&#39;t (yet) replace 1Password for me.
But that&#39;s probably just because I&#39;ve been using 1Password for a long time (I think it&#39;s almost ten years by now), and gotten used to many of 1Password&#39;s features.
Had I started fresh, I&#39;d quite likely kept using Proton Pass, as it&#39;s a pretty good password manager, and the rest of the Proton product suite is pretty cool too.
I&#39;ll likely take another look at Proton Pass in a year or two, and since it&#39;s pretty much impossible to find good in-depth technical comparisons of different password managers, I figured I&#39;d keep note of my testing here.&lt;/p&gt;
&lt;p&gt;I think I&#39;ll also look at self-hosting Bitwarden with vaultwarden, but since that&#39;s a bit more effort to set up, I haven&#39;t had the time yet.
Maybe I&#39;ll have another blog post about this soonish.&lt;/p&gt;
&lt;h2 id=&quot;what-i-like&quot; tabindex=&quot;-1&quot;&gt;What I like &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/proton-pass-as-1password-alternative/#what-i-like&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Proton, the company behind Proton Pass, is a strictly European provider with its own servers, i.e. not AWS or Azure like apparently everyone else.
Since we can&#39;t really trust overseas anymore these days, that&#39;s a major point for me.
I also appreciate that Proton&#39;s major shareholder is a non-profit foundation, and any kind of venture capital.&lt;/p&gt;
&lt;p&gt;Proton also has great customer service which responded in less than a day even around Christmas and New Year holidays, and was really helpful and supportive.&lt;/p&gt;
&lt;p&gt;Proton Pass itself has native clients for all platforms I care for (Linux, Windows, iOS, Android).
The apps look pretty good, and are straight-forward to use.
It also features CLI which, despite still being labelled as Beta, appears pretty complete and solid, and already includes SSH support.
It can run as SSH agent pre-loaded with all SSH keys from select vaults, and it can also load SSH keys from select vaults into a running SSH agent.
It&#39;s pretty easy to script, and while I didn&#39;t test all of it, it looks like it provides comprehensive access to all features of Proton Pass.&lt;/p&gt;
&lt;p&gt;And last but not least its family plan also considerably cheaper than 1Password, and can be combined with subscriptions for other proton products&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://swsnr.de/proton-pass-as-1password-alternative/#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h2 id=&quot;what-i-miss&quot; tabindex=&quot;-1&quot;&gt;What I miss &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/proton-pass-as-1password-alternative/#what-i-miss&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;importing-data-from-1password&quot; tabindex=&quot;-1&quot;&gt;Importing data from 1password &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/proton-pass-as-1password-alternative/#importing-data-from-1password&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I started with an import of all my 1Password vaults, and quickly noticed that the import leaves quite a bit to be desired.&lt;/p&gt;
&lt;p&gt;Items with lots of custom fields and sections sometimes get mangled during import; the imported items have duplicated fields, and sometimes sections with empty titles.
The latter is particularly annoying: Proton Pass doesn&#39;t actually allow sections with empty titles, so when you edit the item later on the &amp;quot;Save&amp;quot; button is disabled, with no clear indication or error message.
Only a tiny red error icon in empty space points out a missing section title, and it took me a while to figure out that I had to put in section titles to be able to save my edits to the item.&lt;/p&gt;
&lt;p&gt;But worse, the import actually &lt;strong&gt;lost some data&lt;/strong&gt; in the process.
For a start, none of my passkeys made it over, thought I&#39;m in fact no sure whether 1Password actually exports these.
But it definitely does export plain &amp;quot;Document&amp;quot; entries, and these got lost too:
Proton Pass imported these as empty items, with correct title, but &lt;strong&gt;no attachment&lt;/strong&gt;.
In other words, it lost the actual relevant content of the item, and &lt;strong&gt;without any warning&lt;/strong&gt; in fact. Since we keep quite a lot of such documents in our shared vaults (notably copies of important legal documents such as birth certificates, deeds of possession, etc.), that was a major disappointment.&lt;/p&gt;
&lt;h3 id=&quot;tags&quot; tabindex=&quot;-1&quot;&gt;Tags &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/proton-pass-as-1password-alternative/#tags&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The import also lost all my tags, simply because Proton Pass doesn&#39;t actually support any tags, which was another major disappointment.
I use tags a lot to keep track of groups of items across vaults.
For instance, I have tags which track any kind of account that needs a paid subscription, i.e. which costs money, or any kind of account that has purchased digital content attached (e.g. Steam or PSN for games, or Adobe for some Ebooks, etc.).
I also have tags which track what data an account stores, notably addresses and payment data such as bank accounts or credit cards.&lt;/p&gt;
&lt;p&gt;I&#39;d really hate to loose these tags, because they turned out to be very helpful a couple of times:
when I changed my bank account a few years ago, having a complete list of every paid account that has my credit card or bank account number was really convenient.
As another example, we&#39;re about to relocate soon, so I&#39;m really happy that by means of these tags I have a complete list of every important account that has my postal address.&lt;/p&gt;
&lt;h3 id=&quot;per-device-registration&quot; tabindex=&quot;-1&quot;&gt;Per-device registration &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/proton-pass-as-1password-alternative/#per-device-registration&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Proton Pass secures the vault data with only a single master password.
That&#39;s no concern if you actually pick a strong master password, but alas, non-technical people (e.g. all my family save me) have quite a different understanding of what&#39;s a strong password.
As such, I very much appreciate that 1password has two distinct secrets to protect vaults:
a strong random secret key which is kept offline (we have printed copies in our strongbox at home) and only needed once every time a new client is registered, and the master password which routinely unlocks the vault in everyday use of the desktop client and apps.
The second random secret is an effective companion to somewhat weaker passwords, and makes brute force attacks against the password impossible.&lt;/p&gt;
&lt;p&gt;I know that it probably makes not much of a difference effectively, since even a weaker password combined with the usual server-side countermeasures against brute forcing (contemporary PBKDFs, rate-limiting, etc.) should hold up, but I still feel a lot safer and sleep better knowing that with 1Password it&#39;s factually impossible to gain access to vault items by brute-forcing passwords.&lt;/p&gt;
&lt;h3 id=&quot;other-nuisances&quot; tabindex=&quot;-1&quot;&gt;Other nuisances &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/proton-pass-as-1password-alternative/#other-nuisances&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Besides these three major points, there are also a few minor nuisances.&lt;/p&gt;
&lt;p&gt;Proton Pass won&#39;t let you add custom icons to entries&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://swsnr.de/proton-pass-as-1password-alternative/#fn2&quot; id=&quot;fnref2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;; I&#39;ve got a lot of custom non-login entries in your vault, so my entire vault just had the generic grey pliers icon all over the place.
I might just as well not have any icons at all.&lt;/p&gt;
&lt;p&gt;Proton Pass doesn&#39;t support plain Documents similar to 1Password; to store document I&#39;d have to create an empty item with a single attachment which is a bit clumsy.&lt;/p&gt;
&lt;p&gt;The browser extensions and the CLI aren&#39;t linked to the desktop app like with 1Password, where unlocking the desktop app automatically unlocks the CLI and browser extensions as well.
So depending on my current tasks I ended up repeatedly unlocking Proton Pass three or more times.&lt;/p&gt;
&lt;p&gt;The mobile app has a biometric unlock, and the browser extension can unlock with a short PIN, but neither seems to request the actual password again periodically.
Probably not that relevant for security, but having to type the actual master password every once in a while is also a good way to make sure people don&#39;t forget it.&lt;/p&gt;
&lt;p&gt;The desktop app downloads don&#39;t appear to be cryptographically signed, which is probably not too relevant either, but still feels like a strange oversight for a security-minded app.&lt;/p&gt;
&lt;h2 id=&quot;summary&quot; tabindex=&quot;-1&quot;&gt;Summary &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/proton-pass-as-1password-alternative/#summary&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I&#39;d really like to use Proton Pass, but I definitely miss a better import from 1Password, tags to organize my vault items, and a two-level security with explicit client registration.&lt;/p&gt;
&lt;p&gt;These three issues rule out 1Password for me; all the other things above are mostly minor nuisances which I could easily get used to.
I might have used Proton Pass nonetheless in combination with the whole Proton suite, if only Proton Drive had a proper Linux client.&lt;/p&gt;
&lt;p&gt;Proton Pass is a good password manager in and by itself, the CLI is great, and it has bonus points for being sort of not-for-profit and Europe-based.
Let&#39;s see how things evolve; perhaps I&#39;ll take another look at Proton Pass in a year or two.&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep&quot;&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Incidentally, if Proton Drive had a convenient Linux desktop client, I might actually have subscribed to the whole Proton Suite and kept using Proton Pass despite its shortcomings, because the Proton Suite as a whole also has some pretty convincing features on its own.
But alas, Proton Drive has no Linux client yet, and all this &lt;code&gt;rclone mount&lt;/code&gt; and &lt;code&gt;rclone bisync&lt;/code&gt; business is not really great. &lt;a href=&quot;https://swsnr.de/proton-pass-as-1password-alternative/#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn2&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Curiously Bitwarden doesn&#39;t appear to support custom icons either, while still fetching Favicons from websites.
I really can&#39;t understand why.
If you&#39;re already storing (fav)icons along with entries, and showing them in place of standard icons, how hard can it be to just have a small image picker to edit these icons? &lt;a href=&quot;https://swsnr.de/proton-pass-as-1password-alternative/#fnref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  <entry>
    <title>forgejo-runner image with mkosi</title>
    <link href="https://swsnr.de/forgejo-runner-image-with-mkosi/" />
    <updated>2025-11-30T00:00:00Z</updated>
    <id>https://swsnr.de/forgejo-runner-image-with-mkosi/</id>
    <content type="html">&lt;h1&gt;forgejo-runner image with mkosi&lt;/h1&gt;
&lt;p&gt;Codeberg doesn&#39;t have much CI capacity so I bring my own action runners to run workflows in my Codeberg repositories.
I use &lt;code&gt;mkosi&lt;/code&gt; to build a generic system image containing the runner as well as all dependencies I need for CI, which I can then deploy on any system (e.g. spare laptops at home, hosted cloud VMs, etc.) that uses systemd.
This post shows the image definition.&lt;/p&gt;
&lt;h2 id=&quot;base-image&quot; tabindex=&quot;-1&quot;&gt;Base image &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/forgejo-runner-image-with-mkosi/#base-image&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The base image is pretty straight-forward.
We build a discoverable disk image based on Archlinux, configure timezone, locale, hostname, and keymap, and install a basic set of packages:&lt;/p&gt;
&lt;pre class=&quot;language-ini&quot;&gt;&lt;code class=&quot;language-ini&quot;&gt;&lt;span class=&quot;token section&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token section-name selector&quot;&gt;Output&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;ImageId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;forgejo-runner&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;Format&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;disk&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;CompressOutput&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;true&lt;/span&gt;

&lt;span class=&quot;token section&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token section-name selector&quot;&gt;Distribution&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;Distribution&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;arch&lt;/span&gt;

&lt;span class=&quot;token section&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token section-name selector&quot;&gt;Content&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;Bootable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;false&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;Timezone&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;UTC&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;Keymap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;us&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;Locale&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;C.UTF-8&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;Hostname&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;forgejo-runner-????-????&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;Packages&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# Base packages&lt;/span&gt;
    base
    base-devel
    &lt;span class=&quot;token comment&quot;&gt;# pacman-offline to auto-update the runner system&lt;/span&gt;
    pacman-offline
    &lt;span class=&quot;token comment&quot;&gt;# The forgejo runner daemon itself&lt;/span&gt;
    forgejo-runner
    &lt;span class=&quot;token comment&quot;&gt;# node 24, as required by actions&lt;/span&gt;
    nodejs-lts-krypton
    &lt;span class=&quot;token comment&quot;&gt;# Git, obviously&lt;/span&gt;
    git
    &lt;span class=&quot;token comment&quot;&gt;# Extra dependencies for the actual CI jobs&lt;/span&gt;
    rustup
    npm
    &lt;span class=&quot;token comment&quot;&gt;# ... whatever you need&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We also need a preset file to enable the pacman offline timer for auto-updates as well as the forgejo runner service.
We also disable homed while at it, because we don&#39;t need user management in a single-purpose disk image.
The preset file goes into the default extra tree below &lt;code&gt;mkosi.extra&lt;/code&gt; where mkosi automatically picks it up and includes it in the image.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ cat mkosi.extra/usr/local/lib/systemd/system-preset/10-forgejo-runner.preset
disable systemd-homed.service
disable systemd-homed-activate.service
enable forgejo-runner.service
enable pacman-offline-prepare.timer
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;disk-layout&quot; tabindex=&quot;-1&quot;&gt;Disk layout &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/forgejo-runner-image-with-mkosi/#disk-layout&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;By default, &lt;code&gt;mkosi&lt;/code&gt; tries to minimize the size of all partitions in the disk image.
However, we do need a large &lt;code&gt;/var&lt;/code&gt; for toolchains, caches, build outputs, etc., so we adjust the partitions created in the disk image with two repart configuration files:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;mkosi.repart/10-root.conf&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-ini&quot;&gt;&lt;code class=&quot;language-ini&quot;&gt;&lt;span class=&quot;token section&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token section-name selector&quot;&gt;Partition&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;root&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;Format&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;btrfs&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;CopyFiles&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;/&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;Minimize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;guess&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;mkosi.repart/20-var.conf&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-ini&quot;&gt;&lt;code class=&quot;language-ini&quot;&gt;&lt;span class=&quot;token section&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token section-name selector&quot;&gt;Partition&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;var&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;CopyFiles&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;/var:/&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;Format&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;btrfs&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;SizeMinBytes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;10G&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;SizeMaxBytes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;10G&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;runner-configuration&quot; tabindex=&quot;-1&quot;&gt;Runner configuration &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/forgejo-runner-image-with-mkosi/#runner-configuration&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Next we initialize a default configuration for the forgejo-runner:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ mkdir mkosi.extra/etc/forgejo-runner
$ curl https://code.forgejo.org/forgejo/runner/raw/branch/main/internal/pkg/config/config.example.yaml &#92;
   &amp;gt; mkosi.extra/etc/forgejo-runner/config.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The defaults are good;
we just need to adapt them slightly to remove some example values, reduce the job timeout, and pick some more reasonable directories:&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;diff --git a/mkosi.extra/etc/forgejo-runner/config.yaml b/mkosi.extra/etc/forgejo-runner/config.yaml
index 5addac4..420c43d 100644
&lt;span class=&quot;token coord&quot;&gt;--- a/mkosi.extra/etc/forgejo-runner/config.yaml&lt;/span&gt;
&lt;span class=&quot;token coord&quot;&gt;+++ b/mkosi.extra/etc/forgejo-runner/config.yaml&lt;/span&gt;
@@ -25,15 +25,13 @@ runner:
&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;  capacity: 1
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;  # Extra environment variables to run jobs.
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;  envs:
&lt;/span&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;    A_TEST_ENV_NAME_1: a_test_env_value_1
&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;    A_TEST_ENV_NAME_2: a_test_env_value_2
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;  # Extra environment variables to run jobs from a file.
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;  # It will be ignored if it&#39;s empty or the file doesn&#39;t exist.
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;  env_file: .env
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;  # The timeout for a job to be finished.
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;  # Please note that the Forgejo instance also has a timeout (3h by default) for the job.
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;  # So the job could be stopped by the Forgejo instance if it&#39;s timeout is shorter than this.
&lt;/span&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;  timeout: 3h
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;  timeout: 1h
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;  # The timeout for the runner to wait for running jobs to finish when
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;  # shutting down because a TERM or INT signal has been received.  Any
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;  # running jobs that haven&#39;t finished after this timeout will be
&lt;/span&gt;@@ -92,7 +90,7 @@ cache:
&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;  #
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;  # If empty, the cache data will be stored in $HOME/.cache/actcache.
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;  #
&lt;/span&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;  dir: &quot;&quot;
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;  dir: &quot;/var/lib/forgejo-runner/cache&quot;
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;  #
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;  #######################################################################
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;  #
&lt;/span&gt;@@ -191,4 +189,4 @@ container:
&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;host:
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;  # The parent directory of a job&#39;s working directory.
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;  # If it&#39;s empty, $HOME/.cache/act/ will be used.
&lt;/span&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;  workdir_parent:
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;  workdir_parent: /var/lib/forgejo-runner/act
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;register-runner-from-credentials&quot; tabindex=&quot;-1&quot;&gt;Register runner from credentials &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/forgejo-runner-image-with-mkosi/#register-runner-from-credentials&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We also need to register the runner with Codeberg (or any other Forgejo instance).
We could do this by hand: open a shell in the image and run &lt;code&gt;forgejo-runner register&lt;/code&gt;.
However, it&#39;s a lot more convenient to do this automatically when booting the image.
Systemd has a concept of &lt;a href=&quot;https://systemd.io/CREDENTIALS/&quot;&gt;credentials&lt;/a&gt; which we can use to inject registration data into the image when booting.
Inside the image we can then automatically register the runner if it&#39;s not registered yet.&lt;/p&gt;
&lt;p&gt;For this purpose we add a small one-shot service &lt;code&gt;mkosi.extra/usr/local/lib/systemd/system/forgejo-runner-register.service&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-ini&quot;&gt;&lt;code class=&quot;language-ini&quot;&gt;&lt;span class=&quot;token section&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token section-name selector&quot;&gt;Unit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;Description&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;Forgejo Runner Registration&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;After&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;network-online.target&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;Wants&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;network-online.target&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;ConditionPathExists&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;!/var/lib/forgejo-runner/.runner&lt;/span&gt;

&lt;span class=&quot;token key attr-name&quot;&gt;ConditionCredential&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;forgejo.runner.name&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;ConditionCredential&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;forgejo.runner.instance&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;ConditionCredential&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;forgejo.runner.labels&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;ConditionCredential&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;forgejo.runner.token&lt;/span&gt;

&lt;span class=&quot;token section&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token section-name selector&quot;&gt;Service&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;oneshot&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;RemainAfterExit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;ExecStart&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;/usr/local/bin/forgejo-runner-register-from-credentials&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;forgejo-runner&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;WorkingDirectory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;/var/lib/forgejo-runner&lt;/span&gt;

&lt;span class=&quot;token key attr-name&quot;&gt;ImportCredential&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;forgejo.runner.*&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This service only runs when the runner is not registered (&lt;code&gt;ConditionPathExists&lt;/code&gt;) and all secrets for registration are available (&lt;code&gt;ConditionCredential&lt;/code&gt;).
It imports all registration credentials from the service manager and calls a small script at &lt;code&gt;mkosi.extra/usr/local/bin/forgejo-runner-register-from-credentials&lt;/code&gt; which simply calls&lt;code&gt;forgejo-runner register&lt;/code&gt; with the credential values:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token shebang important&quot;&gt;#!/usr/bin/bash&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-Euo&lt;/span&gt; pipefail
&lt;span class=&quot;token builtin class-name&quot;&gt;exec&lt;/span&gt; /usr/bin/forgejo-runner register &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
    --no-interactive &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
    &lt;span class=&quot;token parameter variable&quot;&gt;--name&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;systemd-creds &lt;span class=&quot;token function&quot;&gt;cat&lt;/span&gt; forgejo.runner.name&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
    &lt;span class=&quot;token parameter variable&quot;&gt;--instance&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;systemd-creds &lt;span class=&quot;token function&quot;&gt;cat&lt;/span&gt; forgejo.runner.instance&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
    &lt;span class=&quot;token parameter variable&quot;&gt;--labels&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;systemd-creds &lt;span class=&quot;token function&quot;&gt;cat&lt;/span&gt; forgejo.runner.labels&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;
    &lt;span class=&quot;token parameter variable&quot;&gt;--token&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;systemd-creds &lt;span class=&quot;token function&quot;&gt;cat&lt;/span&gt; forgejo.runner.token&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we arrange for this service to be started before the actual &lt;code&gt;forgejo-runner&lt;/code&gt; with a small&lt;/p&gt;
&lt;pre class=&quot;language-ini&quot;&gt;&lt;code class=&quot;language-ini&quot;&gt;&lt;span class=&quot;token section&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token section-name selector&quot;&gt;Unit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;After&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;forgejo-runner-register.service&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;Requires&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;forgejo-runner-register.service&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;ConditionFileNotEmpty&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;/var/lib/forgejo-runner/.runner&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;ConditionFileNotEmpty&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;/etc/forgejo-runner/config.yaml&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;build-and-launch&quot; tabindex=&quot;-1&quot;&gt;Build and launch &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/forgejo-runner-image-with-mkosi/#build-and-launch&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We build the image with &lt;code&gt;mkosi&lt;/code&gt; and import the resulting &lt;code&gt;forgejo-runner.raw.zst&lt;/code&gt; as a machine image:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ importctl import-raw --class=machine &#92;
   forgejo-runner.raw.zst codeberg-runner
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we &lt;a href=&quot;https://docs.codeberg.org/ci/actions/#obtaining-a-registration-token&quot;&gt;obtain a registration token&lt;/a&gt; and boot the image manually to pass credentials to register the runner:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ systemd-nspawn --boot --link-journal=try-guest -U &#92;
   --machine codeberg-runner &#92;
   --set-credential=forgejo.runner.instance:https://codeberg.org &#92;
   --set-credential=forgejo.runner.name:foo-runner &#92;
   --set-credential=forgejo.runner.labels:foo:host &#92;
   --set-credential=forgejo.runner.token:THE_TOKEN
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From now on we can use &lt;code&gt;machinectl&lt;/code&gt; to boot the runner, e.g. &lt;code&gt;machinectl start codeberg-runner&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Launching and registering a new runner is now a matter of &lt;code&gt;importctl&lt;/code&gt;, &lt;code&gt;systemd-nspawn&lt;/code&gt;, and &lt;code&gt;machinectl&lt;/code&gt;, and I can launch a new runner within minutes.
We can now spin up an e.g. Alma Linux VM on Hetzner cloud and get a runner up and running within a few minutes, all by hand, even without heavy automation by e.g. terraform, and dispose of it again when the runner is not needed any more which makes for a simple and cost-effective way to do CI on Codeberg.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Build Arch packages with git sources on OBS</title>
    <link href="https://swsnr.de/arch-git-packages-on-obs/" />
    <updated>2025-11-06T00:00:00Z</updated>
    <id>https://swsnr.de/arch-git-packages-on-obs/</id>
    <content type="html">&lt;h1&gt;Build Arch packages with git sources on OBS&lt;/h1&gt;
&lt;p&gt;After the &lt;a href=&quot;https://arstechnica.com/security/2024/04/what-we-know-about-the-xz-utils-backdoor-that-almost-infected-the-world/&quot;&gt;infamous xz backdoor incident&lt;/a&gt; there&#39;s been a general move towards building packages directly from Git sources for the sake of transparency, instead of using upstream provided
tarballs.&lt;/p&gt;
&lt;p&gt;Now, I like to build my Arch packages on the public &lt;a href=&quot;https://build.opensuse.org/&quot;&gt;openSUSE Build service&lt;/a&gt; (OBS) generously sponsored by SUSE, but building packages from Git sources poses a bit of a challenge on OBS: the public instance does not permit network access during builds and requires all sources to be present upfront along with the &lt;code&gt;PKGBUILD&lt;/code&gt;. This prevents &lt;code&gt;makepkg&lt;/code&gt; from fetching the git sources during build. However, with a small trick we can simply commit a “snapshot” of the source repo along with the &lt;code&gt;PKGBUILD&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We&#39;ll build the small &lt;a href=&quot;https://github.com/leahneukirchen/wcal&quot;&gt;wcal&lt;/a&gt; utility.
We start with a straight-forward &lt;code&gt;PKGBUILD&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ osc mkpac wcal
$ cd wcal
$ cat &amp;gt;PKGBUILD &amp;lt;&amp;lt;EOF
pkgname=wcal
pkgver=0.1
pkgrel=1
pkgdesc=&#39;ISO weekly calendar&#39;
license=(&#39;CC0-1.0&#39;)
arch=(&amp;quot;x86_64&amp;quot;)
url=&#39;https://github.com/leahneukirchen/wcal&#39;
makedepends=(&#39;git&#39;)
source=(&amp;quot;git+${url}.git#tag=v${pkgver}&amp;quot;)

build() {
    make -C &amp;quot;${pkgname}&amp;quot; CFLAGS=&amp;quot;${CFLAGS}&amp;quot; LDFLAGS=&amp;quot;${LDFLAGS}&amp;quot; PREFIX=/usr all
}

package() {
    make -C &amp;quot;${pkgname}&amp;quot; PREFIX=/usr DESTDIR=&amp;quot;${pkgdir}/&amp;quot; install
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When we run &lt;code&gt;makepkg --geninteg&lt;/code&gt; to add the &lt;code&gt;sha256sums&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ makepkg --geninteg &amp;gt;&amp;gt; PKGBUILD
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then &lt;code&gt;makepkg&lt;/code&gt; clones the source URL as a bare Git repository along with the &lt;code&gt;PKGBUILD&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ls
PKGBUILD  src  wcal
$ ls wcal
config  description  HEAD  hooks  info  objects  packed-refs  refs
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can just add this bare repository to the package repo:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ osc add wcal
wcal is a directory, do you want to archive it for submission? (y/n) y
91 blocks
A    wcal.obscpio
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This creates an archive containing the bare repository, which we can commit along with the &lt;code&gt;PKGBUILD&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ osc addremove
$ osc st
A    PKGBUILD
A    wcal.obscpio
$ osc ci -m &#39;wcal 0.1&#39;
Sending meta data...
Done.
Sending    wcal
Sending    wcal/PKGBUILD
Sending    wcal/wcal.obscpio
Transmitting file data ..
Committed revision 1.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The archive gets uploaded along with the &lt;code&gt;PKGBUILD&lt;/code&gt;, and OBS automatically extracts it before running &lt;code&gt;makepkg&lt;/code&gt; which then picks up the pre-existing bare repository and uses it as source directory.
It still tries to update the repository which fails as the build as no network; this causes a spurious warning in the OBS build logs:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[   26s] ==&amp;gt; Retrieving sources...
[   26s]   -&amp;gt; Updating wcal git repo...
[   26s] fatal: unable to access &#39;https://github.com/leahneukirchen/wcal.git/&#39;: Could not resolve host: github.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, we can happily ignore this warning; the package builds successfully.&lt;/p&gt;
&lt;p&gt;When updating the package after updating the &lt;code&gt;pkgver&lt;/code&gt; in &lt;code&gt;PKGBUILD&lt;/code&gt; we run &lt;code&gt;makepkg --geninteg&lt;/code&gt; again to update the checksums in the &lt;code&gt;PKGBUILD&lt;/code&gt;.
This updates the bare repository which we then &lt;code&gt;osc add&lt;/code&gt; again; osc warns that the &lt;code&gt;.cpio&lt;/code&gt; archive is already under version control but updates it nonetheless.&lt;/p&gt;
&lt;p&gt;While not as convenient as using OBS source services to have upstream tarballs fetched automatically it&#39;s worth the added build transparency.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Notes on building Arch packages on openSUSE Build Service</title>
    <link href="https://swsnr.de/notes-on-building-arch-packages-on-opensuse-build-service/" />
    <updated>2025-07-30T00:00:00Z</updated>
    <id>https://swsnr.de/notes-on-building-arch-packages-on-opensuse-build-service/</id>
    <content type="html">&lt;h1&gt;Notes on building Arch packages on openSUSE Build Service&lt;/h1&gt;
&lt;p&gt;I use &lt;a href=&quot;https://build.opensuse.org&quot;&gt;openSUSE Build Service&lt;/a&gt; (OBS) to build &lt;a href=&quot;https://build.opensuse.org/project/show/home:swsnr&quot;&gt;most of my packages for Archlinux&lt;/a&gt;, sourced from AUR or written by myself, except for a few proprietary packages which OBS does not permit to be built on its infrastructure. There are some caveats, tho, and this post keeps a few notes to work around these.&lt;/p&gt;
&lt;h2 id=&quot;set-packager&quot; tabindex=&quot;-1&quot;&gt;Set PACKAGER &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/notes-on-building-arch-packages-on-opensuse-build-service/#set-packager&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;OBS uses a &lt;a href=&quot;https://github.com/openSUSE/obs-build/blob/68dfd74d879e4819b202013168b9d8b64049ee5f/build-recipe-arch#L39&quot;&gt;generated &lt;code&gt;PACKAGER&lt;/code&gt; value&lt;/a&gt; which effectively identifies the build worker, for instance, &lt;code&gt;i04-ch1b &amp;lt;abuild@i04-ch1b&amp;gt;&lt;/code&gt;. There are no direct means to change this, but there&#39;s a workaround, albeit a slightly hacky one:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a package (say, &lt;code&gt;makepkg-packager&lt;/code&gt;) which installs a profile snippet containing &lt;code&gt;export PACKAGER=&amp;quot;Jane Doe (OBS) &amp;lt;jdoe@example.com&amp;gt;&amp;quot;&lt;/code&gt; at &lt;code&gt;/etc/profile.d/obs-packager.sh&lt;/code&gt; (&lt;a href=&quot;https://build.opensuse.org/projects/home:swsnr:supplemental/packages/swsnr-base-devel/files/PKGBUILD?expand=1&quot;&gt;example&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;Add the package to &lt;code&gt;Requires: makepkg-packager&lt;/code&gt; in the project configuration, via the web interface or &lt;code&gt;osc meta prjconf -e&lt;/code&gt; (&lt;a href=&quot;https://build.opensuse.org/projects/home:swsnr/prjconf&quot;&gt;example&lt;/a&gt;).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This effectively sets the &lt;code&gt;$PACKAGER&lt;/code&gt; environment variable for the build process of every package in the project, and luckily the Arch build recipe explicitly sources the whole &lt;code&gt;/etc/profile&lt;/code&gt; before invoking &lt;code&gt;makepkg&lt;/code&gt;. A makepkg drop-in for &lt;code&gt;/etc/makepkg.conf.d&lt;/code&gt; won&#39;t work, because the Arch OBS build recipe explicitly assembles a custom makepkg file.&lt;/p&gt;
&lt;p&gt;The package can be moved to a separate subproject (e.g. &amp;quot;supplemental&amp;quot; or &amp;quot;internal&amp;quot;) linked to the main project; this will enable builds from the main project to use the package, but the package won&#39;t appear in the repository of the main project.&lt;/p&gt;
&lt;h2 id=&quot;disable-automated-rebuilds&quot; tabindex=&quot;-1&quot;&gt;Disable automated rebuilds &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/notes-on-building-arch-packages-on-opensuse-build-service/#disable-automated-rebuilds&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;OBS automatically rebuilds packages if dependencies change, but this doesn&#39;t work well for Archlinux packages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Arch packages get updated frequently, which causes excessive rebuilds for packages at the leafs of the dependency tree (e.g. anything depending on gnome-shell will get rebuilt almost daily).&lt;/li&gt;
&lt;li&gt;OBS&#39; logic to bump the build number on every automatic rebuild only works for RPM and Debian packages, but &lt;a href=&quot;https://github.com/openSUSE/obs-build/issues/984&quot;&gt;does not support Arch&#39;s &lt;code&gt;PKGBUILD&lt;/code&gt; format&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Automatic rebuilds can be disabled by adding the attribute &lt;code&gt;rebuild=&amp;quot;local&amp;quot;&lt;/code&gt; to the &lt;code&gt;repository&lt;/code&gt; element in the project metadata, via the web interface or &lt;code&gt;osc meta prj -e&lt;/code&gt; (&lt;a href=&quot;https://build.opensuse.org/projects/home:swsnr/meta&quot;&gt;example&lt;/a&gt;):&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;  &amp;lt;title&gt;Internal dependencies&amp;lt;/title&gt;
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;  &amp;lt;description&gt;&amp;lt;/description&gt;
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;  &amp;lt;person userid=&quot;swsnr&quot; role=&quot;maintainer&quot;/&gt;
&lt;/span&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;  &amp;lt;repository name=&quot;Arch&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;  &amp;lt;repository name=&quot;Arch&quot; rebuild=&quot;local&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;    &amp;lt;path project=&quot;Arch:Extra&quot; repository=&quot;standard&quot;/&gt;
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;    &amp;lt;arch&gt;x86_64&amp;lt;/arch&gt;
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;  &amp;lt;/repository&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;resolve-multiple-providers-for-dependencies&quot; tabindex=&quot;-1&quot;&gt;Resolve multiple providers for dependencies &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/notes-on-building-arch-packages-on-opensuse-build-service/#resolve-multiple-providers-for-dependencies&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;OBS does not resolve multiple &lt;code&gt;provides&lt;/code&gt; automatically. This affects e.g. Rust, which is provided both by &lt;code&gt;rust&lt;/code&gt; and &lt;code&gt;rustup&lt;/code&gt;; OBS fails to resolve this and needs to be told explicitly which package to prefer via the &lt;code&gt;Prefer&lt;/code&gt; setting in the project configuration (&lt;a href=&quot;https://build.opensuse.org/projects/home:swsnr/prjconf&quot;&gt;example&lt;/a&gt;).&lt;/p&gt;
&lt;h2 id=&quot;dealing-with-offline-builds&quot; tabindex=&quot;-1&quot;&gt;Dealing with offline builds &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/notes-on-building-arch-packages-on-opensuse-build-service/#dealing-with-offline-builds&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;OBS builds packages without network access, so all sources must be available upfront in the package repository. For standard packages with archive sources (i.e. source tarballs) the &lt;code&gt;download_files&lt;/code&gt; &lt;a href=&quot;https://openbuildservice.org/help/manuals/obs-user-guide/cha-obs-source-services&quot;&gt;source service&lt;/a&gt; makes OBS automatically download and commit the source archives on every commit. It can be enabled with the following content in the &lt;code&gt; _service&lt;/code&gt; file (&lt;a href=&quot;https://build.opensuse.org/projects/home:swsnr/packages/pcsc-cyberjack/files/_service?expand=1&quot;&gt;example&lt;/a&gt;):&lt;/p&gt;
&lt;pre class=&quot;language-xml&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;services&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;service&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;download_files&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;services&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For packages which fetch extra dependencies during build (e.g. NPM, Rust, Go, Maven, etc.) these dependencies need to be vendored upfront and added as an explicit source. The details depend on the package manager being used; for Cargo for instance I usually use &lt;code&gt;cargo vendor&lt;/code&gt;, then tar the resulting &lt;code&gt;vendor/&lt;/code&gt; directory and commit it as separate source (&lt;a href=&quot;https://build.opensuse.org/package/show/home:swsnr/cargo-vet&quot;&gt;example&lt;/a&gt;). For go it&#39;s similar (&lt;a href=&quot;https://build.opensuse.org/package/show/home:swsnr/wsl2-ssh-agent&quot;&gt;example&lt;/a&gt;). Generally, it&#39;s easy for anything with first-class support for vendoring (e.g. Rust, Go) or which just puts dependencies into a directory in the source tree (e.g. npm). For stuff which doesn&#39;t support vendoring and puts dependency artifacts in some other location, it can be quite some work (e.g. Maven, Haskell).&lt;/p&gt;
&lt;p&gt;In any case, any &lt;code&gt;PKGBUILD&lt;/code&gt; from the AUR which expects to be able to download dependencies during build won&#39;t work on OBS; it needs to be adapted, or in some cases entirely rewritten, to account for offline builds.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Simple Secure Boot in Fedora</title>
    <link href="https://swsnr.de/simple-secure-boot-in-fedora/" />
    <updated>2024-04-13T00:00:00Z</updated>
    <id>https://swsnr.de/simple-secure-boot-in-fedora/</id>
    <content type="html">&lt;h1&gt;Simple Secure Boot in Fedora&lt;/h1&gt;
&lt;p&gt;Fedora doesn&#39;t use a proper secure boot setup: It doesn&#39;t use unified kernel images and still leaves unsigned initrd files around. Generally, it still seems to consider secure boot support just as means to boot and install on secure-boot locked machines with Microsoft&#39;s keys, instead of a proper security tool a user should make use of to really own their own machines.&lt;/p&gt;
&lt;p&gt;In this article we&#39;ll explore how we can setup secure boot with custom keys. The result works pretty well, and uses no fancy trickery or weird hacks, but is probably still firmly outside of what Fedora supports and will perhaps break with future Fedora releases.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; I use Fedora 40 in this article; it may or may not apply to other Fedora 40 versions, both earlier and later. As far as I understand Fedora is actively working on systemd-boot support, UKIs, and secure boot; the whole area is pretty much a moving target and may change a lot in between Fedora releases.&lt;/p&gt;
&lt;h2 id=&quot;prerequisites&quot; tabindex=&quot;-1&quot;&gt;Prerequisites &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/simple-secure-boot-in-fedora/#prerequisites&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Before we start let&#39;s install necessary tooling:&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;# dnf install systemd-boot-unsigned systemd-ukify sbsigntools&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We install systemd-boot as our bootloader to replace grub, systemd-ukify to build unified kernel images (UKIs) for signing, and sbsigntools to sign EFI binaries for secure boot.&lt;/p&gt;
&lt;p&gt;Let&#39;s also put &lt;a href=&quot;https://github.com/Foxboron/sbctl&quot;&gt;sbctl&lt;/a&gt; in &lt;code&gt;~/bin&lt;/code&gt; to generate and enroll keys, and to use &lt;code&gt;sbctl verify&lt;/code&gt; to check our signatures:&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;$ curl -LsS https://github.com/Foxboron/sbctl/releases/download/0.13/sbctl-0.13-linux-amd64.tar.gz  | tar xz
$ mkdir -p ~/bin
$ install -m755 sbctl/sbctl ~/bin/sbctl&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, we confirm that secure boot is in setup mode:&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;# bootctl status | grep &#39;Secure Boot&#39;
systemd-boot not installed in ESP.
   Secure Boot: disabled (setup)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If secure boot is not in setup mode we need to clear the platform key (PK) from the firmware first, via the firmware interface (e.g. &lt;code&gt;systemctl reboot --firmware-setup&lt;/code&gt;).&lt;/p&gt;
&lt;h2 id=&quot;replace-grub-with-systemd-boot&quot; tabindex=&quot;-1&quot;&gt;Replace grub with systemd-boot &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/simple-secure-boot-in-fedora/#replace-grub-with-systemd-boot&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As a first step we replace the default grub bootloader with systemd-boot because the latter is considerably simpler to set up and can boot from UKIs without further configuration.&lt;/p&gt;
&lt;p&gt;As systemd-boot does not need a separate boot partition we first unmount the boot partition and mount the EFI system partition to the standard &lt;code&gt;/efi/&lt;/code&gt; mountpoint:&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;# umount /boot/efi
# umount /boot
# mkdir -Zm755 /efi/
# sed -i &#39;s_/boot/efi_/efi_&#39; /etc/fstab
# sed -i &#39;/&#92;/boot/d&#39; /etc/fstab
# systemctl daemon-reload
# mount -a&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we copy systemd-boot to the ESP, uninstall grub and remove its files from the ESP, and enable a utility service which keeps the systemd-boot up to date on the ESP when the corresponding package gets updated:&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;# bootctl install
Created &quot;/efi/EFI/systemd&quot;.
Created &quot;/efi/loader&quot;.
Created &quot;/efi/loader/entries&quot;.
Created &quot;/efi/EFI/Linux&quot;.
Copied &quot;/usr/lib/systemd/boot/efi/systemd-bootx64.efi&quot; to &quot;/efi/EFI/systemd/systemd-bootx64.efi&quot;.
Copied &quot;/usr/lib/systemd/boot/efi/systemd-bootx64.efi&quot; to &quot;/efi/EFI/BOOT/BOOTX64.EFI&quot;.
Random seed file /efi/loader/random-seed successfully written (32 bytes).
Successfully initialized system token in EFI variable with 32 bytes.
Created EFI boot entry &quot;Linux Boot Manager&quot;.
# dnf remove &#39;grub*&#39; --setopt protected_packages=
[…]
# rm -rf /efi/EFI/fedora
# systemctl enable systemd-boot-update.service
Created symlink /etc/systemd/system/sysinit.target.wants/systemd-boot-update.service → /usr/lib/systemd/system/systemd-boot-update.service.&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;enable-ukis&quot; tabindex=&quot;-1&quot;&gt;Enable UKIs &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/simple-secure-boot-in-fedora/#enable-ukis&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Next, we&#39;ll reconfigure Fedora to build a unified kernel image instead the traditional pair of a kernel image and initramfs. This creates a single bootable binary to sign for secure boot, instead of leaving an unsigned initramfs around.&lt;/p&gt;
&lt;p&gt;Unfortunately, as of 2024-04-12 Fedora 40 &lt;a href=&quot;https://src.fedoraproject.org/rpms/dracut/blob/f40/f/0001-feat-kernel-install-do-nothing-when-KERNEL_INSTALL_I.patch&quot;&gt;patches dracut&#39;s kernel install plugin&lt;/a&gt; and breaks its UKI support in doing so. We obtain the original plugin and overwrite the one included in the package:&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;$ curl https://raw.githubusercontent.com/dracut-ng/dracut-ng/main/install.d/50-dracut.install &gt; /tmp/50-dracut.install
$ sudo install -m755 -t /etc/kernel/install.d/ /tmp/50-dracut.install&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we can enable UKIs, by creating &lt;code&gt;/etc/kernel/install.conf&lt;/code&gt; with these contents:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token assign-left variable&quot;&gt;layout&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;uki
&lt;span class=&quot;token assign-left variable&quot;&gt;initrd_generator&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;dracut
&lt;span class=&quot;token assign-left variable&quot;&gt;uki_generator&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;ukify&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This configuration file changes the kernel installation layout to UKIs, chooses dracut has the initrd generator, and uses &lt;code&gt;ukify&lt;/code&gt; to combine the kernel and the initrd to a UKI. While we could use &lt;code&gt;dracut&lt;/code&gt; for the latter step as well, in my experiments it failed to take the kernel command line from &lt;code&gt;/etc/kernel/cmdline&lt;/code&gt; into account. Besides, &lt;code&gt;ukify&lt;/code&gt; has a handy &lt;code&gt;ukify inspect&lt;/code&gt; command to inspect the contents of a UKI image, which helps us verify that we build a good image.&lt;/p&gt;
&lt;p&gt;We also disable the dracut rescue image because the rescue image setup in Fedora does not yet support UKIs (trying to run the rescue image hook in a UKI setup fails), by creating &lt;code&gt;/etc/dracut.conf.d/50-no-rescue-image.conf&lt;/code&gt; with the following contents:&lt;/p&gt;
&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token assign-left variable&quot;&gt;dracut_rescue_image&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;no&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;generate-secure-boot-keys&quot; tabindex=&quot;-1&quot;&gt;Generate secure boot keys &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/simple-secure-boot-in-fedora/#generate-secure-boot-keys&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For secure boot we first need a set of secure boot keys which we generate with &lt;code&gt;sbctl&lt;/code&gt; (there are probably also tools for this in Fedora, but &lt;code&gt;sbctl&lt;/code&gt; just makes this very easy):&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;$ sudo ~/bin/sbctl create-keys
Created Owner UUID baa10157-a096-4bba-9b4d-5ae3f2b84895
Creating secure boot keys...✓
Secure boot keys created!
$ sudo ~/bin/sbctl status
Installed:	✓ sbctl is installed
Owner GUID:	baa10157-a096-4bba-9b4d-5ae3f2b84895
Setup Mode:	✗ Enabled
Secure Boot:	✗ Disabled
Vendor Keys:	none&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;include-the-fedora-secure-boot-ca&quot; tabindex=&quot;-1&quot;&gt;Include the Fedora Secure Boot CA &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/simple-secure-boot-in-fedora/#include-the-fedora-secure-boot-ca&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We also add the Secure Boot Signer CA certificate from Fedora to the secure boot DB which enables us to boot binaries signed by Fedora without having to re-sign them ourselves. Specifically, Fedora&#39;s &lt;code&gt;fwupd-efi&lt;/code&gt; package comes with a signed EFI binary for fwupd, so firmware updates will just work without further ado if we include the Fedora certificate.&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;$ sudo dnf install openssl
$ curl https://src.fedoraproject.org/rpms/shim-unsigned-x64/blob/f40/f/fedora-ca-20200709.cer | openssl x509 -inform der -out fedora-ca-20200709.pem
[…]
$ sudo install -m600 -Dt /usr/share/secureboot/keys/custom/db/ fedora-ca-20200709.pem
$ sudo dnf remove openssl&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;sign-the-bootloader&quot; tabindex=&quot;-1&quot;&gt;Sign the bootloader &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/simple-secure-boot-in-fedora/#sign-the-bootloader&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now we can use our keys to sign the boot loader, and then install the signed binary:&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;# /usr/bin/sbsign --key /usr/share/secureboot/keys/db/db.key --cert /usr/share/secureboot/keys/db/db.pem /usr/lib/systemd/boot/efi/systemd-bootx64.efi
Signing Unsigned original image
# bootctl install --no-variables
Copied &quot;/usr/lib/systemd/boot/efi/systemd-bootx64.efi.signed&quot; to &quot;/efi/EFI/systemd/systemd-bootx64.efi&quot;.
Copied &quot;/usr/lib/systemd/boot/efi/systemd-bootx64.efi.signed&quot; to &quot;/efi/EFI/BOOT/BOOTX64.EFI&quot;.
Random seed file /efi/loader/random-seed successfully refreshed (32 bytes).&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To keep the signed binary up to date when the package gets updated we amend the &lt;code&gt;systemd-boot-update.service&lt;/code&gt; we enabled above to update the signed binary before updating the ESP. To this end we use &lt;code&gt;systemctl edit systemd-boot-update.service --drop-in=10-sbsign&lt;/code&gt; to create a new drop-in with the following contents:&lt;/p&gt;
&lt;pre class=&quot;language-ini&quot;&gt;&lt;code class=&quot;language-ini&quot;&gt;&lt;span class=&quot;token section&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token section-name selector&quot;&gt;Service&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;ExecStartPre&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;/usr/bin/sbsign --key /usr/share/secureboot/keys/db/db.key --cert /usr/share/secureboot/keys/db/db.pem /usr/lib/systemd/boot/efi/systemd-bootx64.efi&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;sign-ukis&quot; tabindex=&quot;-1&quot;&gt;Sign UKIs &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/simple-secure-boot-in-fedora/#sign-ukis&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Next we configure ukify to sign the generated UKIs, by creating &lt;code&gt;/etc/kernel/uki.conf&lt;/code&gt; with the following contents:&lt;/p&gt;
&lt;pre class=&quot;language-ini&quot;&gt;&lt;code class=&quot;language-ini&quot;&gt;&lt;span class=&quot;token section&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token section-name selector&quot;&gt;UKI&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;SecureBootPrivateKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;/usr/share/secureboot/keys/db/db.key&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;SecureBootCertificate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;/usr/share/secureboot/keys/db/db.pem&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we re-install all kernels to install them as signed UKIs on the ESP:&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;root@fedora-40-test:/home/test# kernel-install add-all --verbose
Loading /etc/kernel/install.conf…
layout=uki set via /etc/kernel/install.conf
INITRD_GENERATOR (dracut) set via /etc/kernel/install.conf.
UKI_GENERATOR (ukify) set via /etc/kernel/install.conf.
Loaded /etc/kernel/install.conf.
[…]
Skipping overridden file &#39;/usr/lib/kernel/install.d/50-dracut.install&#39;.
[…]
dracut: *** Creating initramfs image file &#39;/tmp/kernel-install.staging.ooX3PH/initrd&#39; done ***
[…]
KERNEL_INSTALL_LAYOUT and KERNEL_INSTALL_UKI_GENERATOR are good
Using config file: /etc/kernel/uki.conf
+ sbverify --list /usr/lib/modules/6.8.4-300.fc40.x86_64/vmlinuz
+ sbsign --key /usr/share/secureboot/keys/db/db.key --cert /usr/share/secureboot/keys/db/db.pem /tmp/ukij9govtve --output /tmp/kernel-install.staging.ooX3PH/uki.efi
Signing Unsigned original image
Wrote signed /tmp/kernel-install.staging.ooX3PH/uki.efi
[…]
Installing /tmp/kernel-install.staging.ooX3PH/uki.efi as /efi/EFI/Linux/951d91d766c544f2820cb103358d4de2-6.8.4-300.fc40.x86_64.efi
[…]
Installed 1 kernel(s).&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;enroll-secure-boot-keys&quot; tabindex=&quot;-1&quot;&gt;Enroll secure boot keys &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/simple-secure-boot-in-fedora/#enroll-secure-boot-keys&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now we can verify that everything is signed:&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;$ sudo ~/bin/sbctl verify
Verifying file database and EFI images in /efi...
✗ /efi/EFI/BOOT/BOOTIA32.EFI is not signed
✓ /efi/EFI/BOOT/BOOTX64.EFI is signed
✗ /efi/EFI/BOOT/fbia32.efi is not signed
✗ /efi/EFI/BOOT/fbx64.efi is not signed
✓ /efi/EFI/Linux/951d91d766c544f2820cb103358d4de2-6.8.4-300.fc40.x86_64.efi is signed
✓ /efi/EFI/systemd/systemd-bootx64.efi is signed&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As we can see, all relevant files (the boot loader as well as the kernel image) are signed now (the unsigned files &lt;code&gt;BOOTIA32.EFI&lt;/code&gt;, &lt;code&gt;fbia32.efi&lt;/code&gt;, and &lt;code&gt;fbx64.efi&lt;/code&gt; are left-overs from grub; we can remove these).&lt;/p&gt;
&lt;p&gt;Finally, we&#39;re ready to enroll our secure boot keys and leave secure boot setup mode:&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;$ sudo ~/bin/sbctl enroll-keys --custom
Enrolling keys to EFI variables...
With custom keys...✓
Enrolled keys to the EFI variables!&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that &lt;code&gt;sbctl enroll-keys&lt;/code&gt; may abort and very seriously warn you in case it fails to verify the absence of signed option ROMs on your machine. The warning text describes the available options; in case you happen to see it, &lt;strong&gt;do read it and take it seriously&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;We&#39;ve now left setup mode, so let&#39;s reboot:&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;# bootctl status | grep &#39;Secure Boot&#39;
   Secure Boot: disabled
# systemctl reboot&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After a reboot secure boot is completely enabled:&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;$ sudo bootctl | grep &#39;Secure Boot&#39;
   Secure Boot: enabled (user)
$ sudo ~/bin/sbctl status
Installed:	✓ sbctl is installed
Owner GUID:	baa10157-a096-4bba-9b4d-5ae3f2b84895
Setup Mode:	✓ Disabled
Secure Boot:	✓ Enabled
Vendor Keys:	custom&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;configure-fwupd&quot; tabindex=&quot;-1&quot;&gt;Configure fwupd &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/simple-secure-boot-in-fedora/#configure-fwupd&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now that everything works, we should finally configure the firmware updater &lt;code&gt;fwupd&lt;/code&gt; for our setup. Normally, &lt;code&gt;fwupd&lt;/code&gt; tries to load its EFI capsule (the executable it boots into the install the firmware update on a reboot) through shim to support conventional secure boot setups on systems using Microsoft&#39;s keys. However, we don&#39;t have shim installed; instead, in our setup we can directly use the signed EFI binary (which is why we included the Fedora CA above). To convince fwupd to not use shim, we update &lt;code&gt;/etc/fwupd/fwupd.conf&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-ini&quot;&gt;&lt;code class=&quot;language-ini&quot;&gt;&lt;span class=&quot;token section&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token section-name selector&quot;&gt;fwupd&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# use `man 5 fwupd.conf` for documentation&lt;/span&gt;

&lt;span class=&quot;token section&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token section-name selector&quot;&gt;uefi_capsule&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;EnableGrubChainLoad&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;false&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;DisableShimForSecureBoot&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;true&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/simple-secure-boot-in-fedora/#conclusion&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;After a few iterations the whole process came out to be a lot simpler than I anticipated, and I now have fully working useful secure boot on my Fedora system. I&#39;m positively surprised on how well Fedora follows systemd upstream without too much of custom tooling and patchery (looking at you, Debian…).&lt;/p&gt;
&lt;p&gt;The whole secure boot dance also became a lot simpler in the past years. A few years ago on Arch, &lt;code&gt;sbctl&lt;/code&gt; changed the game for secure boot (see &lt;a href=&quot;https://swsnr.de/install-arch-with-secure-boot-tpm2-based-luks-encryption-and-systemd-homed/&quot;&gt;Install Arch with Secure boot, TPM2-based LUKS encryption, and systemd-homed &lt;/a&gt;, but these days systemd supports this a lot better with &lt;code&gt;ukify&lt;/code&gt;, and the remaining gaps are easily filled with good old sbsigntools, to a point that I no longer need sbctl for routine signing of EFI binaries and can solely rely on Fedora packages for this purpose.&lt;/p&gt;
&lt;p&gt;That said, I did come across a few oddities in Fedora&#39;s packages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The dracut package patches out UKI support from the kernel-install hook; however this looks like a faulty patch, and probably gets resolved soon. The whole dracut situation is somehwat moving currently with a new upstream emerging after years of stagnation and non-cooperation on the original upstream.&lt;/li&gt;
&lt;li&gt;There is a signed fwupd EFI binary, but no signed systemd-boot binary?&lt;/li&gt;
&lt;li&gt;The signed fwupd binary is part of &lt;code&gt;fwupd-efi&lt;/code&gt; along with the unsigned binary, but for shim the signed and unsigned binaries are split into different packages (and &lt;code&gt;systemd-boot-unsigned&lt;/code&gt; suggests this is also planned for systemd-boot)?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But I guess these will resolve soon; the dracut situation will calm down over time, and Fedora actively works to improve UKI support.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Arch Linux rescue image with mkosi</title>
    <link href="https://swsnr.de/archlinux-rescue-image-with-mkosi/" />
    <updated>2024-03-24T00:00:00Z</updated>
    <id>https://swsnr.de/archlinux-rescue-image-with-mkosi/</id>
    <content type="html">&lt;h1&gt;Arch Linux rescue image with mkosi&lt;/h1&gt;
&lt;p&gt;In this post we&#39;ll build a small single-file Arch Linux rescue image for EFI systems.&lt;/p&gt;
&lt;p&gt;We will end up with a single EFI executable of about 400 MiB (further optimization can get it down to about 200MiB) which embeds a fully-fledged Arch Linux system. We can then put this image on the EFI partition and sign it for secure boot, which gives us a rescue single-file rescue system to boot into in case the main Arch installation does not boot anymore. From that rescue system we can then &lt;code&gt;chroot&lt;/code&gt; into the main installation to repair it.&lt;/p&gt;
&lt;p&gt;The image will include metadata which enables &lt;code&gt;systemd-boot&lt;/code&gt; to automatically discover the image and add it to its menu when we place in &lt;code&gt;/efi/EFI/Linux&lt;/code&gt; (the bootloader specification calls these &amp;quot;type 2 entries&amp;quot;, see &lt;a href=&quot;https://uapi-group.org/specifications/specs/boot_loader_specification/#type-2-efi-unified-kernel-images&quot;&gt;Type #2 EFI Unified Kernel Images&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;You can find my personal version of the image built in this post at &lt;a href=&quot;https://codeberg.org/swsnr/rescue-image&quot;&gt;https://codeberg.org/swsnr/rescue-image&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I got the idea from a recent post by the mkosi maintainer at &lt;a href=&quot;https://0pointer.net/blog/a-re-introduction-to-mkosi-a-tool-for-generating-os-images.html&quot;&gt;https://0pointer.net/blog/a-re-introduction-to-mkosi-a-tool-for-generating-os-images.html&lt;/a&gt; which I recommend reading for more information about &lt;code&gt;mkosi&lt;/code&gt;. The post calls what we&#39;re building here a Unified System Image (USI).&lt;/p&gt;
&lt;h2 id=&quot;prerequisites&quot; tabindex=&quot;-1&quot;&gt;Prerequisites &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/archlinux-rescue-image-with-mkosi/#prerequisites&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We&#39;ll need a few packages:&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;# pacman -S mkosi
# pacman -S --asdeps systemd-ukify&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;mkosi&lt;/code&gt; builds the image, and uses &lt;code&gt;systemd-ukify&lt;/code&gt; to build a UKI.&lt;/p&gt;
&lt;h2 id=&quot;configure-mkosi&quot; tabindex=&quot;-1&quot;&gt;Configure mkosi &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/archlinux-rescue-image-with-mkosi/#configure-mkosi&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We configure the image in &lt;code&gt;mkosi.conf&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-ini&quot;&gt;&lt;code class=&quot;language-ini&quot;&gt;&lt;span class=&quot;token section&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token section-name selector&quot;&gt;Distribution&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;Distribution&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;arch&lt;/span&gt;

&lt;span class=&quot;token section&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token section-name selector&quot;&gt;Output&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;ImageId&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;archlinux-rescue&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;Format&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;uki&lt;/span&gt;

&lt;span class=&quot;token section&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token section-name selector&quot;&gt;Content&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;Hostname&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;archlinux-rescue&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;Bootloader&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;none&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;Bootable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token value attr-value&quot;&gt;false&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;Packages&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;
    base
    intel-ucode
    linux
    linux-firmware
    wireless-regdb
    iwd
    nano
    less
    mandoc
    man-pages
    arch-install-scripts&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;select Arch as base distribution for the image,&lt;/li&gt;
&lt;li&gt;configure the identifier of the image which also sets the output filenames,&lt;/li&gt;
&lt;li&gt;enable the UKI output format,&lt;/li&gt;
&lt;li&gt;set a hostname for the image,&lt;/li&gt;
&lt;li&gt;and disable bootloader installation (otherwise &lt;code&gt;mkosi&lt;/code&gt; would install a somewhat superfluous EFI partition inside the UKI).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We also tell &lt;code&gt;mkosi&lt;/code&gt; to install some essential packages into the image.
We add&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;base and a kernel,&lt;/li&gt;
&lt;li&gt;firmware binaries, which are essential to boot on any modern hardware,&lt;/li&gt;
&lt;li&gt;the regulatory database, which is required for wireless connections,&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;iwd&lt;/code&gt; daemon to configure wireless interfaces and connect to wireless stations,&lt;/li&gt;
&lt;li&gt;the simple &lt;code&gt;nano&lt;/code&gt; text editor,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;less&lt;/code&gt; as pager, and &lt;code&gt;mandoc&lt;/code&gt; for the &lt;code&gt;man&lt;/code&gt; command (&lt;code&gt;mandoc&lt;/code&gt; is somewhat smaller than &lt;code&gt;man-db&lt;/code&gt;), and&lt;/li&gt;
&lt;li&gt;&lt;code&gt;arch-install-scripts&lt;/code&gt; for the &lt;code&gt;arch-chroot&lt;/code&gt; command.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;define-the-systemd-preset&quot; tabindex=&quot;-1&quot;&gt;Define the systemd preset &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/archlinux-rescue-image-with-mkosi/#define-the-systemd-preset&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We customize the systemd preset to enable the &lt;code&gt;iwd&lt;/code&gt; daemon automatically, and disable a few standard systemd services which have no use in a single-user rescue image:&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;new file mode 100644
index 0000000..4f00b6f
&lt;span class=&quot;token coord&quot;&gt;--- /dev/null&lt;/span&gt;
&lt;span class=&quot;token coord&quot;&gt;+++ b/mkosi.extra/etc/systemd/system-preset/10-rescue-image.preset&lt;/span&gt;
&lt;span class=&quot;token coord&quot;&gt;@@ -0,0 +1,4 @@&lt;/span&gt;
&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;disable systemd-homed.service
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;disable systemd-userdbd.socket
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;disable systemd-boot-update.service
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;enable iwd.service
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;mkosi&lt;/code&gt; automatically copies the file system tree in the &lt;code&gt;mkosi.extra&lt;/code&gt; directory to the image. Hence, our preset file will end up in &lt;code&gt;/etc/systemd/system-preset/10-rescue-image.preset&lt;/code&gt; inside the image, where &lt;code&gt;mkosi&lt;/code&gt; will pick it up from when it applies the preset as one of its last steps in the build process.&lt;/p&gt;
&lt;h2 id=&quot;configure-networking&quot; tabindex=&quot;-1&quot;&gt;Configure networking &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/archlinux-rescue-image-with-mkosi/#configure-networking&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In addition to our custom preset file the default systemd preset applies, which enables &lt;code&gt;systemd-resolved&lt;/code&gt; and &lt;code&gt;systemd-networkd&lt;/code&gt; and thus provides us with a small yet capable network management stack in the rescue system. However, we still need to configure this stack a bit for proper networking when booting the image on real hardware.&lt;/p&gt;
&lt;p&gt;We first put &lt;code&gt;systemd-resolved&lt;/code&gt; into the recommended &lt;code&gt;stub&lt;/code&gt; mode for the &lt;code&gt;resolv.conf&lt;/code&gt; file, to make sure all DNS resolution goes through resolved, by placing the &lt;code&gt;resolv.conf&lt;/code&gt; symlink to &lt;code&gt;/run/systemd/resolve/stub-resolv.conf&lt;/code&gt; into the &lt;code&gt;mkosi.extra&lt;/code&gt; directory (the diff actually describes a symlink in Git):&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;new file mode 120000
index 0000000..3639662
&lt;span class=&quot;token coord&quot;&gt;--- /dev/null&lt;/span&gt;
&lt;span class=&quot;token coord&quot;&gt;+++ b/mkosi.extra/etc/resolv.conf&lt;/span&gt;
&lt;span class=&quot;token coord&quot;&gt;@@ -0,0 +1 @@&lt;/span&gt;
&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;/run/systemd/resolve/stub-resolv.conf
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From there &lt;code&gt;mkosi&lt;/code&gt; copies the symlink itself to the image literally.&lt;/p&gt;
&lt;p&gt;By default, &lt;code&gt;systemd-networkd&lt;/code&gt; only manages virtual network interfaces of containers and virtual machines, which implies that we automatically have a network connection when testing the image with &lt;code&gt;mkosi qemu&lt;/code&gt;. On real hardware however we have real Ethernet and wireless interfaces which we also want &lt;code&gt;systemd-networkd&lt;/code&gt; to handle in our rescue image:&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;new file mode 100644
index 0000000..09fdddf
&lt;span class=&quot;token coord&quot;&gt;--- /dev/null&lt;/span&gt;
&lt;span class=&quot;token coord&quot;&gt;+++ b/mkosi.extra/etc/systemd/network/80-wifi-station.network&lt;/span&gt;
&lt;span class=&quot;token coord&quot;&gt;@@ -0,0 +1,8 @@&lt;/span&gt;
&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;# SPDX-License-Identifier: MIT-0
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;[Match]
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;Type=wlan
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;WLANInterfaceType=station
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;[Network]
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;DHCP=yes
&lt;/span&gt;new file mode 100644
index 0000000..0896e7a
&lt;span class=&quot;token coord&quot;&gt;--- /dev/null&lt;/span&gt;
&lt;span class=&quot;token coord&quot;&gt;+++ b/mkosi.extra/etc/systemd/network/89-ethernet.network&lt;/span&gt;
&lt;span class=&quot;token coord&quot;&gt;@@ -0,0 +1,9 @@&lt;/span&gt;
&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;# SPDX-License-Identifier: MIT-0
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;# Enable DHCPv4 and DHCPv6 on all physical ethernet links
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;[Match]
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;Kind=!*
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;Type=ether
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;[Network]
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;DHCP=yes
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;configure-pacman&quot; tabindex=&quot;-1&quot;&gt;Configure pacman &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/archlinux-rescue-image-with-mkosi/#configure-pacman&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;With networking set up we can now configure pacman. This enables us to install additional software while booted in the rescue image, to handle any kind of recovery task:&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;new file mode 100644
index 0000000..3bd39de
&lt;span class=&quot;token coord&quot;&gt;--- /dev/null&lt;/span&gt;
&lt;span class=&quot;token coord&quot;&gt;+++ b/mkosi.extra/etc/pacman.d/mirrorlist&lt;/span&gt;
&lt;span class=&quot;token coord&quot;&gt;@@ -0,0 +1,2 @@&lt;/span&gt;
&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;Server = https://geo.mirror.pkgbuild.com/$repo/os/$arch
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;Server = https://mirror.rackspace.com/archlinux/$repo/os/$arch
&lt;/span&gt;new file mode 100755
index 0000000..b4db6db
&lt;span class=&quot;token coord&quot;&gt;--- /dev/null&lt;/span&gt;
&lt;span class=&quot;token coord&quot;&gt;+++ b/mkosi.postinst.chroot&lt;/span&gt;
&lt;span class=&quot;token coord&quot;&gt;@@ -0,0 +1,6 @@&lt;/span&gt;
&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;#!/usr/bin/env bash
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;set -euo pipefail
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;echo &quot;Populating pacman keyring&quot;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;pacman-key --init
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;pacman-key --populate
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We add a &lt;code&gt;mirrorlist&lt;/code&gt; using Arch&#39;s worldwide geolocating mirrors, to avoid the hassle of setting up a mirror list for the occasional package installation during recovery.&lt;/p&gt;
&lt;p&gt;We also initialize the &lt;code&gt;pacman&lt;/code&gt; key ring within the image in a post-installation script. &lt;code&gt;mkosi&lt;/code&gt; runs this &lt;code&gt;postinst&lt;/code&gt; script after package installation, but before configuration and image cleanup. The &lt;code&gt;.chroot&lt;/code&gt; extension tells &lt;code&gt;mkosi&lt;/code&gt; to run the script while &lt;code&gt;chroot&lt;/code&gt;ed into the image.&lt;/p&gt;
&lt;h2 id=&quot;initialize-manpage-database&quot; tabindex=&quot;-1&quot;&gt;Initialize manpage database &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/archlinux-rescue-image-with-mkosi/#initialize-manpage-database&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In this post-installation script we also initialize the manpage database now, as &lt;code&gt;mandoc&lt;/code&gt; does not do this automatically after package installation:&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;diff --git a/mkosi.postinst.chroot b/mkosi.postinst.chroot
index b4db6db..03ece97 100755
&lt;span class=&quot;token coord&quot;&gt;--- a/mkosi.postinst.chroot&lt;/span&gt;
&lt;span class=&quot;token coord&quot;&gt;+++ b/mkosi.postinst.chroot&lt;/span&gt;
@@ -4,3 +4,6 @@ set -euo pipefail
echo &quot;Populating pacman keyring&quot;
pacman-key --init
pacman-key --populate
&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;echo &quot;Updating manpage database&quot;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;makewhatis /usr/share/man
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we can use &lt;code&gt;man&lt;/code&gt; in the image to read documentation.&lt;/p&gt;
&lt;h2 id=&quot;set-os-metadata&quot; tabindex=&quot;-1&quot;&gt;Set OS metadata &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/archlinux-rescue-image-with-mkosi/#set-os-metadata&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A UKI image contains release metadata which systemd-boot uses to create a menu entry for the UKI. So far, our image contains the default release metadata of Arch Linux, and thus appears as a regular Arch system in the systemd-boot menu. We add a custom &lt;code&gt;/etc/os-release&lt;/code&gt; file to change the identification of the rescue image:&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;new file mode 100644
index 0000000..4c47e89
&lt;span class=&quot;token coord&quot;&gt;--- /dev/null&lt;/span&gt;
&lt;span class=&quot;token coord&quot;&gt;+++ b/mkosi.extra/etc/os-release&lt;/span&gt;
&lt;span class=&quot;token coord&quot;&gt;@@ -0,0 +1,6 @@&lt;/span&gt;
&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;NAME=&quot;Arch Linux&quot;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;ID=arch
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;VARIANT=&quot;Rescue Image&quot;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;VARIANT_ID=rescue
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;BUILD_ID=rolling
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;ANSI_COLOR=&quot;38;2;23;147;209&quot;
&lt;/span&gt;new file mode 100755
index 0000000..abe3a3e
&lt;span class=&quot;token coord&quot;&gt;--- /dev/null&lt;/span&gt;
&lt;span class=&quot;token coord&quot;&gt;+++ b/mkosi.finalize&lt;/span&gt;
&lt;span class=&quot;token coord&quot;&gt;@@ -0,0 +1,6 @@&lt;/span&gt;
&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;#!/usr/bin/env bash
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;set -euo pipefail
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;echo &quot;Finalizing /etc/os-release&quot;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;source &quot;${BUILDROOT}/etc/os-release&quot;
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;echo &quot;PRETTY_NAME=&#92;&quot;${NAME} (${VARIANT} ${IMAGE_VERSION:-n/a})&#92;&quot;&quot; &gt;&gt; &quot;${BUILDROOT}/etc/os-release&quot;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We add a custom &lt;code&gt;/etc/os-release&lt;/code&gt; to the image with the &lt;code&gt;mkosi.extra&lt;/code&gt; tree, and then use a &lt;code&gt;finalize&lt;/code&gt; script, which &lt;code&gt;mkosi&lt;/code&gt; runs at the very end of the build process, to generate the &lt;code&gt;$PRETTY_NAME&lt;/code&gt;, which systemd-boot uses as the menu label. By generating &lt;code&gt;$PRETTY_NAME&lt;/code&gt; dynamically we can include the &lt;code&gt;$IMAGE_VERSION&lt;/code&gt; in the name, which &lt;code&gt;mkosi&lt;/code&gt; sets from the &lt;code&gt;--image-version&lt;/code&gt; argument.&lt;/p&gt;
&lt;p&gt;This allows us to build the image with e.g. &lt;code&gt;--image-version=$(git rev-parse --short=10 HEAD)-$(date --utc +%Y%m%d%H%M)&lt;/code&gt; to have the git hash and timestamp appear in the menu name, to quickly see how old the rescue image is.&lt;/p&gt;
&lt;h2 id=&quot;set-a-root-password&quot; tabindex=&quot;-1&quot;&gt;Set a root password &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/archlinux-rescue-image-with-mkosi/#set-a-root-password&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To prevent unauthorized access to the rescue image we set a root password.
&lt;code&gt;mkosi&lt;/code&gt; reads a plain text or hashed password from the &lt;code&gt;mkosi.rootpw&lt;/code&gt; file.
We can use &lt;code&gt;openssl passwd&lt;/code&gt; to generate a hashed password.&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;$ touch mkosi.rootpw
$ chmod 600 mkosi.rootpw
$ echo -n hashed: &gt;&gt;mkosi.rootpw
$ openssl passwd -6 &gt;&gt;mkosi.rootpw
Password:
Verifying - Password:&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For testing, we can use &lt;code&gt;mkosi -f --autologin qemu&lt;/code&gt; to start a VM without having to type the root password.&lt;/p&gt;
&lt;h2 id=&quot;build-the-image&quot; tabindex=&quot;-1&quot;&gt;Build the image &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/archlinux-rescue-image-with-mkosi/#build-the-image&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We&#39;re now ready to build the image, but before we do so we create two additional directories for &lt;code&gt;mkosi&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;$ mkdir mkosi.cache mkosi.output&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;mkosi&lt;/code&gt; writes the generated image to the &lt;code&gt;mkosi.output&lt;/code&gt; directory if it exists.
We create this directory to move the generated images out of the way, and make it easier to &lt;code&gt;gitignore&lt;/code&gt; the build artifacts.&lt;/p&gt;
&lt;p&gt;If &lt;code&gt;mkosi.cache&lt;/code&gt; exists &lt;code&gt;mkosi&lt;/code&gt; will use it for the package manager cache, and re-use the cached package artifacts for subsequent rebuilds which reduces strain on the Arch mirrors a bit and speeds up subsequent builds of the image.&lt;/p&gt;
&lt;p&gt;Now, let&#39;s build the image:&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;$ mkosi -f --image-version=$(date --utc +%Y%m%d%H%M%S)
[…]
‣  […]/mkosi.output/archlinux-rescue_20240128082906.efi size is 560.4M, consumes 560.4M.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The image builds successfully. It&#39;s fairly large though. To test it with &lt;code&gt;qemu&lt;/code&gt; we need to give the VM more memory than the default 2GB &lt;code&gt;mkosi&lt;/code&gt; gives to it:&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;$ mkosi -f --autologin --qemu-mem=6G qemu
[…]
Arch Linux 6.7.1-arch1-1 (ttyS0)

archlinux-rescue login: root (automatic login)

Last login: […] on tty1
[root@archlinux-rescue ~]#&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Before we install our image, let&#39;s check the metadata:&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;$ ukify inspect mkosi.output/archlinux-rescue_20240128082906.efi
[…]
.osrel:
  size: 227 bytes
  sha256: 94d2621c5426c1041d463f64c97a41d5d927599d648d4c09438f93f5243b4eb6
  text:
    NAME=&quot;Arch Linux&quot;
    ID=arch
    VARIANT=&quot;Rescue Image&quot;
    VARIANT_ID=rescue
    BUILD_ID=rolling
    ANSI_COLOR=&quot;38;2;23;147;209&quot;
    IMAGE_ID=&quot;archlinux-rescue&quot;
    IMAGE_VERSION=&quot;20240128082906&quot;
    PRETTY_NAME=&quot;Arch Linux (Rescue Image 20240128082906)&quot;
[…]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Our metadata is there; &lt;code&gt;systemd-boot&lt;/code&gt; will show this image under the above &lt;code&gt;PRETTY_NAME&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If size is no concern, i.e. if the EFI system partition or the XBOOTLDR partition are sufficiently large to hold this image, we can now install it to the EFI partition and (optionally) sign it for secure boot:&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;# install -m644 mkosi.output/archlinux-rescue_20240128082906.efi
&gt; /efi/EFI/Linux/archlinux-rescue.efi
# sbctl sign /efi/EFI/Linux/archlinux-rescue.efi&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can however also shrink the image by removing files we do not need.&lt;/p&gt;
&lt;h2 id=&quot;shrink-the-image&quot; tabindex=&quot;-1&quot;&gt;Shrink the image &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/archlinux-rescue-image-with-mkosi/#shrink-the-image&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Let&#39;s first try a few simple cleanups:&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;index 08d7d01..40ee5a1 100644
&lt;span class=&quot;token coord&quot;&gt;--- a/mkosi.conf&lt;/span&gt;
&lt;span class=&quot;token coord&quot;&gt;+++ b/mkosi.conf&lt;/span&gt;
@@ -21,3 +21,9 @@ Packages=
&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   less
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   man-pages
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;   mandoc
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;RemoveFiles=
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    /usr/include/
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    /usr/share/include
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    /usr/share/pkgconfig
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    /usr/lib/**/*.a
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    /usr/share/locale
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We remove all headers and &lt;code&gt;pkg-config&lt;/code&gt; files, and all static libraries. These files are for compiling programs, and we&#39;re quite unlikely to do that in a rescue image. We also remove all locales, because we use &lt;code&gt;C.UTF-8&lt;/code&gt; in the image instead of localized messages.&lt;/p&gt;
&lt;p&gt;This gets the image down by a bit:&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;$ mkosi -f
[…]
‣  […]/mkosi.output/archlinux-rescue.efi size is 528.7M, consumes 528.7M.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To make a real difference, however, we need to shrink the biggest contributors to the size of the image: Kernel modules and firmware. &lt;code&gt;mkosi&lt;/code&gt; has builtin support for shrinking the kernel module tree along with firmware:&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;KernelModulesExclude=.*
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;KernelModulesIncludeHost=true
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;KernelModulesInclude=
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    fs/
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    hid/
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    input/
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    usb/
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    dm-.*
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    crypto/
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    tpm/
&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;    virtio
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We first default to excluding all kernel modules: &lt;code&gt;mkosi&lt;/code&gt; does not touch kernel modules by default, so excluding all modules is necessary to make the subsequent include rules take effect.&lt;/p&gt;
&lt;p&gt;With these include rules we first include all kernel modules currently used on the host system: We&#39;d like to use the image to boot into our hardware, so the set of loaded modules is a good baseline. Then we include a couple of additional driver hierarchies, including all filesystems, all HID, input, and USB devices (to make sure we can plug a USB disk with e.g. an NTFS file system, even if none was plugged into the running system so far). We also include device mapper drivers and the crypto hierarchy to support all kinds of encrypted disks, optionally TPM locked. Finally, we also include all virtio drivers, to still be able to test the image in qemu.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;mkosi&lt;/code&gt; then removes all modules not matching any of these includes, and also removes all firmware binaries not used by any retained modules.&lt;/p&gt;
&lt;p&gt;The resulting image will likely not work on other hardware, but it became a lot smaller:&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;$ mkosi -f
[…]
‣  […]/mkosi.output/archlinux-rescue.efi size is 244.2M, consumes 244.2M.&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;conclusion&quot; tabindex=&quot;-1&quot;&gt;Conclusion &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/archlinux-rescue-image-with-mkosi/#conclusion&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I used to keep a rescue image based on GRML around (see &lt;a href=&quot;https://wiki.archlinux.org/title/Systemd-boot#Grml_on_ESP&quot;&gt;GRML on ESP&lt;/a&gt;), but stopped doing so once I had a working secure boot setup: GRML ships its initrd and its squash disk image separately, and there were no easy means to combine them into a single image for signing, or sign each part separately.&lt;/p&gt;
&lt;p&gt;With mkosi, I finally have a viable alternative which supports secure boot, and allows me to leave my Arch ISO thumb drive at home.&lt;/p&gt;
&lt;h2 id=&quot;further-reading&quot; tabindex=&quot;-1&quot;&gt;Further reading &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/archlinux-rescue-image-with-mkosi/#further-reading&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://overhead.neocities.org/blog/build-usi-mkosi/&quot;&gt;Building USIs with mkosi&lt;/a&gt; has a bit more background information, and some helpful tips for more thorough cleanup to shrink the image.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Build Arch Linux packages on OBS</title>
    <link href="https://swsnr.de/build-arch-linux-packages-on-obs/" />
    <updated>2024-02-22T00:00:00Z</updated>
    <id>https://swsnr.de/build-arch-linux-packages-on-obs/</id>
    <content type="html">&lt;h1&gt;Build Arch Linux packages on OBS&lt;/h1&gt;
&lt;p&gt;Suse generously sponsors a public build service for packages at &lt;a href=&quot;https://build.opensuse.org/&quot;&gt;https://build.opensuse.org/&lt;/a&gt;.
Let&#39;s try to use it to build Arch packages into a personal repository.&lt;/p&gt;
&lt;h2 id=&quot;install-osc&quot; tabindex=&quot;-1&quot;&gt;Install osc &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/build-arch-linux-packages-on-obs/#install-osc&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The build service works best with a local command line tool called &lt;code&gt;osc&lt;/code&gt;.
Conveniently, the build service itself offers an &lt;a href=&quot;https://build.opensuse.org/project/repository_state/openSUSE:Tools/Arch&quot;&gt;Arch repository with binary packages for &lt;code&gt;osc&lt;/code&gt;&lt;/a&gt; which we can just add to &lt;code&gt;pacman.conf&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-ini&quot;&gt;&lt;code class=&quot;language-ini&quot;&gt;&lt;span class=&quot;token section&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token section-name selector&quot;&gt;openSUSE_Tools_Arch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;Server&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token value attr-value&quot;&gt;https://download.opensuse.org/repositories/openSUSE:/Tools/Arch/$arch/&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We also need to import and trust the &lt;a href=&quot;https://build.opensuse.org/projects/openSUSE:Tools/signing_keys&quot;&gt;signing key&lt;/a&gt; for this repo:&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;# xh &#39;https://build.opensuse.org/projects/openSUSE:Tools/signing_keys/download?kind=gpg&#39; | pacman-key add -
# pacman-key --lsign-key FCADAFC81273B9E7F184F2B0826659A9013E5B65&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we can install &lt;code&gt;osc&lt;/code&gt; and also &lt;code&gt;python-keyring&lt;/code&gt; to let &lt;code&gt;osc&lt;/code&gt; store our password in GNOME keyring:&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;# pacman -S osc python-keyring&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;configure-repository&quot; tabindex=&quot;-1&quot;&gt;Configure repository &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/build-arch-linux-packages-on-obs/#configure-repository&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now we can add an Arch repository to our home project on OBS, by navigating to the &amp;quot;Repositories&amp;quot; tab and checking the &amp;quot;Arch Extra&amp;quot; variant under &amp;quot;Arch distributions&amp;quot;.&lt;/p&gt;
&lt;h2 id=&quot;checkout-the-home-project&quot; tabindex=&quot;-1&quot;&gt;Checkout the home project &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/build-arch-linux-packages-on-obs/#checkout-the-home-project&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For simplicity, we&#39;ll just go with the &amp;quot;Home&amp;quot; project which every build service user gets.
To start, we check out our home project locally with &lt;code&gt;osc&lt;/code&gt; (you&#39;ll want to replace &lt;code&gt;swsnr&lt;/code&gt; with your OBS username):&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;$ osc checkout home:swsnr

Your user account / password are not configured yet.
You will be asked for them below, and they will be stored in
/home/basti/.config/osc/oscrc for future use.

Creating osc configuration file /home/basti/.config/osc/oscrc ...
Username [api.opensuse.org]: swsnr
Password [swsnr@api.opensuse.org]:

NUM NAME              DESCRIPTION
1   Secret Service    Store password in Secret Service (GNOME Keyring backend) [secure, persistent]
2   Transient         Do not store the password and always ask for it [secure, in-memory]
3   Obfuscated config Store the password in obfuscated form in the osc config file [insecure, persistent]
4   Config            Store the password in plain text in the osc config file [insecure, persistent]
Select credentials manager [default=1]: 1
done
A    home:swsnr&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As it&#39;s the first time we&#39;re using &lt;code&gt;osc&lt;/code&gt; it prompts for authentication.&lt;/p&gt;
&lt;h2 id=&quot;create-our-first-package&quot; tabindex=&quot;-1&quot;&gt;Create our first package &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/build-arch-linux-packages-on-obs/#create-our-first-package&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now we can add a new package.
For simplicity, we&#39;ll package my favorite font Vollkorn, because it&#39;s dead simple and doesn&#39;t involve any complex build system.&lt;/p&gt;
&lt;p&gt;First, let&#39;s create the package and import the &lt;code&gt;PKGBUILD&lt;/code&gt; from AUR:&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;$ osc mkpac otf-vollkorn
A    otf-vollkorn
$ cd otf-vollkorn/
$ xh &#39;https://aur.archlinux.org/cgit/aur.git/plain/PKGBUILD?h=otf-vollkorn&#39; &gt; PKGBUILD
$ osc add PKGBUILD
A    PKGBUILD&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&#39;s first edit this &lt;code&gt;PKGBUILD&lt;/code&gt; to remove the reference to the changelog which we didn&#39;t include, and change the license to a proper SPDX license identifier:&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;diff --git i/PKGBUILD w/PKGBUILD
index 38de0bc..5cfdd48 100644
&lt;span class=&quot;token coord&quot;&gt;--- i/PKGBUILD&lt;/span&gt;
&lt;span class=&quot;token coord&quot;&gt;+++ w/PKGBUILD&lt;/span&gt;
&lt;span class=&quot;token coord&quot;&gt;@@ -4,10 +4,9 @@&lt;/span&gt;
&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;pkgname=otf-vollkorn
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;pkgdesc=&quot;Vollkorn typeface by Friedrich Althausen (OpenType)&quot;
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;url=&#39;http://vollkorn-typeface.com/&#39;
&lt;/span&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;license=(&#39;OFL&#39;)
&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;license=(&#39;OFL-1.1&#39;)
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;pkgver=4.105
&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;pkgrel=2
&lt;/span&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;changelog=ChangeLog.${pkgname}
&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;arch=(&#39;any&#39;)
&lt;/span&gt;
&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;source=(http://vollkorn-typeface.com/download/vollkorn-${pkgver//./-}.zip)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next we&#39;ll fetch, verify, and commit the font source tarball, because the build service builds packages with network access blocked completely, so all sources need to be committed upfront:&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;$ makepkg --verifysource
==&gt; Making package: otf-vollkorn 4.105-2 (…)
==&gt; Retrieving sources...
  -&gt; Downloading vollkorn-4-105.zip...
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 10.5M  100 10.5M    0     0  6466k      0  0:00:01  0:00:01 --:--:-- 6471k
==&gt; Validating source files with sha256sums...
    vollkorn-4-105.zip ... Passed
$ osc add vollkorn-4-105.zip
A    vollkorn-4-105.zip&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we can commit the package:&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;$ osc commit -m &#39;Upload otf-vollkorn&#39;
Sending meta data...
Done.
Sending    otf-vollkorn
Sending    otf-vollkorn/PKGBUILD
Sending    otf-vollkorn/vollkorn-4-105.zip
Transmitting file data ..
Committed revision 1.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This transfers the package to the build service, which then starts building it.
The &lt;a href=&quot;https://build.opensuse.org/package/show/home:swsnr/otf-vollkorn&quot;&gt;package page&lt;/a&gt; shows the status of the build process, and provides access to the build log.&lt;/p&gt;
&lt;p&gt;After the package was built, it moves to the repository and becomes available for download.
Note that the build services queues all these operations, so depending on where your package ends up in the queue and how busy the service is, all of these steps may take a while, so even a fast build might take about an hour to finally end up in the repository.&lt;/p&gt;
&lt;h2 id=&quot;use-the-package&quot; tabindex=&quot;-1&quot;&gt;Use the package &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/build-arch-linux-packages-on-obs/#use-the-package&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To use the package we first need to import the signing keys from the project page, e.g. &lt;a href=&quot;https://build.opensuse.org/projects/home:swsnr/signing_keys&quot;&gt;https://build.opensuse.org/projects/home:swsnr/signing_keys&lt;/a&gt; for my home project.&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;# xh &#39;https://build.opensuse.org/projects/home:swsnr/signing_keys/download?kind=gpg&#39; | pacman-key add -
# pacman-key --lsign 42D80446DC5C2B66D69DF5B6C1A96AD497928E88&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Obviously, you&#39;ll want to use the key of your own project here.&lt;/p&gt;
&lt;p&gt;We also need to tell pacman about the repository by adding it to &lt;code&gt;pacman.conf&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-ini&quot;&gt;&lt;code class=&quot;language-ini&quot;&gt;&lt;span class=&quot;token section&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token section-name selector&quot;&gt;home_swsnr_Arch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token key attr-name&quot;&gt;Server&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token value attr-value&quot;&gt;https://download.opensuse.org/repositories/home:/swsnr/Arch/$arch/&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we can finally install our first package from our own OBS Arch repository:&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;# pacman -Syu
:: Synchronising package databases...
 home_swsnr_Arch                            […]
:: Starting full system upgrade...
 there is nothing to do
# pacman -S otf-vollkorn
resolving dependencies...
looking for conflicting packages...

Package (1)                   New Version  Net Change

home_swsnr_Arch/otf-vollkorn  4.105-2        5,26 MiB

Total Installed Size:  5,26 MiB

:: Proceed with installation? [Y/n]
[…]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Success 🎉&lt;/p&gt;
&lt;h2 id=&quot;final-words&quot; tabindex=&quot;-1&quot;&gt;Final words &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/build-arch-linux-packages-on-obs/#final-words&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Using OBS to build Arch packages turned out to be surprisingly simple, but there&#39;s a caveat.&lt;/p&gt;
&lt;p&gt;The build services builds all packages strictly without network access, something that few &lt;code&gt;PKGBUILD&lt;/code&gt;s are prepared for as Arch doesn&#39;t have similar restrictions.
For traditional C sources that&#39;s not much of an issue, but most modern languages like Go, Rust, ECMAScript, etc. have their own dependency manager and routinely try to pull packages from the internet as part of building.
Getting &lt;code&gt;PKGBUILD&lt;/code&gt;s for packages written in these languages to build on OBS requires some contortions.  However, I&#39;ve managed to get Rust software build on OBS with small modifications to &lt;code&gt;PKGBUILD&lt;/code&gt;, but that&#39;s for another post.&lt;/p&gt;
&lt;p&gt;Generally, you can&#39;t expect OBS to just build arbitrary AUR &lt;code&gt;PKGBUILD&lt;/code&gt;s; there are other issues, such as OBS failing to resolve alternative dependencies.&lt;/p&gt;
&lt;p&gt;But so far I&#39;ve overcome most of these issues, and certainly plan to use OBS more to build packages for my Arch machines.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Install Arch with Secure boot, TPM2-based LUKS encryption, and systemd-homed</title>
    <link href="https://swsnr.de/install-arch-with-secure-boot-tpm2-based-luks-encryption-and-systemd-homed/" />
    <updated>2022-01-06T00:00:00Z</updated>
    <id>https://swsnr.de/install-arch-with-secure-boot-tpm2-based-luks-encryption-and-systemd-homed/</id>
    <content type="html">&lt;h1&gt;Install Arch with Secure boot, TPM2-based LUKS encryption, and systemd-homed&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; I no longer use &lt;code&gt;dracut&lt;/code&gt;, and the corresponding part of this blog post no longer reflects my setup.&lt;/p&gt;
&lt;p&gt;This article describes my Arch Linux setup which combines Secure Boot with custom keys, TPM2-based full disk encryption and systemd-homed into a fully encrypted and authenticated, yet convenient Linux system.&lt;/p&gt;
&lt;p&gt;This setup draws inspiration from &lt;a href=&quot;https://0pointer.net/blog/authenticated-boot-and-disk-encryption-on-linux.html&quot;&gt;Authenticated Boot and Disk Encryption on Linux&lt;/a&gt; and &lt;a href=&quot;https://0pointer.net/blog/unlocking-luks2-volumes-with-tpm2-fido2-pkcs11-security-hardware-on-systemd-248.html&quot;&gt;Unlocking LUKS2 volumes with TPM2, FIDO2, PKCS#11 Security Hardware on systemd 248&lt;/a&gt; by Lennart Poettering.&lt;/p&gt;
&lt;h2 id=&quot;what-this-setup-does&quot; tabindex=&quot;-1&quot;&gt;What this setup does &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/install-arch-with-secure-boot-tpm2-based-luks-encryption-and-systemd-homed/#what-this-setup-does&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Authenticate the boot loader, kernel, initramfs, microcode with Secure Boot, using my own custom keys. Nothing can boot which wasn’t signed by my keys.&lt;/li&gt;
&lt;li&gt;Everything is either authenticated with my keys (kernel, initramfs, microcode) or encrypted (system partition).&lt;/li&gt;
&lt;li&gt;Encrypt the system partition, and unlock it automatically if the boot process was authenticated, by means of a TPM2 key bound to the secure boot state.&lt;/li&gt;
&lt;li&gt;Give every user their own dedicated encrypted home directory, which gets unlocked at login and locked again at logout.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;what-it-doesn%E2%80%99t&quot; tabindex=&quot;-1&quot;&gt;What it doesn’t &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/install-arch-with-secure-boot-tpm2-based-luks-encryption-and-systemd-homed/#what-it-doesn%E2%80%99t&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Show an ugly LUKS password prompt at boot (even with Plymouth it’s not really pretty).&lt;/li&gt;
&lt;li&gt;Leave some parts unencrypted and unauthenticated (conventional installations often fail to consider the initramfs).&lt;/li&gt;
&lt;li&gt;Ask me twice for my password, once at boot to unlock the disk and then again at login.&lt;/li&gt;
&lt;li&gt;Encrypt data of all users with a shared key.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;tools-used&quot; tabindex=&quot;-1&quot;&gt;Tools used &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/install-arch-with-secure-boot-tpm2-based-luks-encryption-and-systemd-homed/#tools-used&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Archlinux&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;systemd&lt;/strong&gt; &amp;gt;= 250&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;systemd-boot&lt;/strong&gt; as bootloader&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;systemd&lt;/strong&gt; in initramfs to automatically discover and mount the root filesystem&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;dracut&lt;/strong&gt; to generate the initramfs and build signed UEFI binaries&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;sbctl&lt;/strong&gt; to create and enroll Secure Boot keys, and sign binaries&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;systemd-homed&lt;/strong&gt; to manage user accounts with per-user encrypted home directories&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;systemd-cryptenroll&lt;/strong&gt; to add TPM2 and recovery keys tokens to a LUKS partition&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;the-setup&quot; tabindex=&quot;-1&quot;&gt;The setup &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/install-arch-with-secure-boot-tpm2-based-luks-encryption-and-systemd-homed/#the-setup&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id=&quot;install-the-system&quot; tabindex=&quot;-1&quot;&gt;Install the system &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/install-arch-with-secure-boot-tpm2-based-luks-encryption-and-systemd-homed/#install-the-system&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;We follow the &lt;a href=&quot;https://wiki.archlinux.org/title/installation_guide&quot;&gt;Installation Guide&lt;/a&gt; up to and including section &lt;a href=&quot;https://wiki.archlinux.org/title/installation_guide#Update_the_system_clock&quot;&gt;“Update the system clock”&lt;/a&gt;. Then we partition the disk (&lt;code&gt;/dev/nvme0n1&lt;/code&gt; in our case); we need an EFI system partition of about 500MB and a root partition spanning the rest of the disk. The EFI partition must be unencrypted and have a FAT filesystem; for the root file system we choose btrfs on top of an encrypted partition.&lt;/p&gt;
&lt;p&gt;First we partition the disk and reload the partition table; we take care to specify proper partition types (-t option) so that systemd can automatically discover and mount our filesystems without further configuration in &lt;code&gt;/etc/crypttab&lt;/code&gt; or &lt;code&gt;/etc/fstab&lt;/code&gt; (see &lt;a href=&quot;https://systemd.io/DISCOVERABLE_PARTITIONS/&quot;&gt;Discoverable Partitions Specification (DPS)&lt;/a&gt;):&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;$ target_device=/dev/nvme0n1
$ sgdisk -Z &quot;$target_device&quot;
$ sgdisk -n1:0:+550M -t1:ef00 -c1:EFISYSTEM -N2 -t2:8304 -c2:linux &quot;$target_device&quot;
$ sleep 3
$ partprobe -s &quot;$target_device&quot;
$ sleep 3&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we setup the encrypted partition for the root file system. We get asked for an encryption password where we pick a very simple encryption password (even “password” is good enough for now, really) to save some typing during installation, as we’ll later replace the password with TPM2 key and a random recovery key:&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;$ cryptsetup luksFormat --type luks2 /dev/disk/by-partlabel/linux
$ cryptsetup luksOpen /dev/disk/by-partlabel/linux root
$ root_device=/dev/mapper/root&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we create the filesystems:&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;$ mkfs.fat -F32 -n EFISYSTEM /dev/disk/by-partlabel/EFISYSTEM
$ mkfs.btrfs -f -L linux &quot;$root_device&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we can mount the filesystems and create some basic btrfs subvolumes:&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;$ mount &quot;$root_device&quot; /mnt
$ mkdir /mnt/efi
$ mount /dev/disk/by-partlabel/EFISYSTEM /mnt/efi
$ for subvol in var var/log var/cache var/tmp srv home; do btrfs subvolume create &quot;/mnt/$subvol&quot;; done&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we’re ready to bootstrap Arch Linux: We generate a mirrorlist and install essential packages:&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;$ reflector --save /etc/pacman.d/mirrorlist --protocol https --latest 5 --sort age
$ pacstrap /mnt base linux linux-firmware intel-ucode btrfs-progs dracut neovim&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This takes a while to download and installation all packages; afterwards we configure some essential settings. Choose locale settings and the &lt;code&gt;$new_hostname&lt;/code&gt; according to your personal preferences.&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;$ ln -sf /usr/share/zoneinfo/Europe/Berlin /mnt/etc/localtime
$ sed -i -e &#39;/^#en_GB.UTF-8/s/^#//&#39; /mnt/etc/locale.gen
$ echo &#39;LANG=en_GB.UTF-8&#39; &gt;/mnt/etc/locale.conf
$ echo &#39;KEYMAP=us&#39; &gt;/mnt/etc/vconsole.conf
$ echo &quot;$new_hostname&quot; &gt;/mnt/etc/hostname&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we enter the new system and finish configuration by generating locales, enabling a few essential services and setting a root password:&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;$ arch-chroot /mnt
$ locale-gen
$ systemctl enable systemd-homed
$ systemctl enable systemd-timesyncd
$ passwd root&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Still in chroot we now build unified EFI kernel images (including initrd and kernel) for booting and install the systemd-boot boot loader:&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;$ pacman -S --noconfirm --asdeps binutils elfutils
$ dracut -f --uefi --regenerate-all
$ bootctl install&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We do &lt;em&gt;not&lt;/em&gt; need to create &lt;code&gt;/etc/fstab&lt;/code&gt; or &lt;code&gt;/etc/crypttab&lt;/code&gt;; as we assigned the appropriate types to each partition and installed systemd-boot a systemd-based initramfs can automatically determine the disk the system was booted from, and discover all relevant partitions. It can then use superblock information to automatically open encrypted LUKS devices and mount file systems.&lt;/p&gt;
&lt;p&gt;At this point we also need to take care to install everything we need for network configuration after reboot. For desktop systems I prefer network manager because it integrates well into Gnome:&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;$ pacman -S networkmanager&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We have finished the basic setup from the live disk now; let’s leave chroot and reboot:&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;$ exit
$ reboot&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After reboot we can complete the system installation, by adding a desktop environment, applications, command line tools, etc.&lt;/p&gt;
&lt;p&gt;I like to automate this, and have two bash scripts in my &lt;a href=&quot;https://github.com/swsnr/dotfiles&quot;&gt;dotfiles&lt;/a&gt;, one for boostrapping a new system from a live disk (&lt;a href=&quot;https://github.com/swsnr/dotfiles/blob/6a25c4e0620068ecc6360fcbe1587eb14b622ac2/arch/bootstrap-from-iso.bash&quot;&gt;&lt;code&gt;arch/bootstrap-from-iso.bash&lt;/code&gt;&lt;/a&gt;) and another one for installing everything after the initial bootstrapping (&lt;a href=&quot;https://github.com/swsnr/dotfiles/blob/6a25c4e0620068ecc6360fcbe1587eb14b622ac2/arch/install.bash&quot;&gt;&lt;code&gt;arch/install.bash&lt;/code&gt;&lt;/a&gt;).&lt;/p&gt;
&lt;h3 id=&quot;create-homed-user&quot; tabindex=&quot;-1&quot;&gt;Create homed user &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/install-arch-with-secure-boot-tpm2-based-luks-encryption-and-systemd-homed/#create-homed-user&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;With the installation finished we create our user account with &lt;code&gt;homectl&lt;/code&gt;; let’s name it &lt;code&gt;foo&lt;/code&gt; for the purpose of this article. First we should disable copy on write for &lt;code&gt;/home&lt;/code&gt;, because this file system feature doesn’t work well with large files frequently updated in place, such as disk images of virtual machines or loopback files as created by systemd-homed:&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;$ chattr +C /home/&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We now create the &lt;code&gt;foo&lt;/code&gt; user with an encrypted home directory backed by LUKS and btrfs:&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;$ homectl create foo --storage luks --fs-type btrfs&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By default systemd assigns 85% of the available disk space to the user account, and will balance available space among all user accounts (based on a weight we can configure with &lt;code&gt;—rebalance-weight&lt;/code&gt;). On a single user system we may prefer to set an explicit quota for the user account:&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;$ homectl resize foo 50G&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can also add some additional metadata to the user account:&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;$ homectl update foo --real-name &#39;Foo&#39; --email-address foo@example.org --language en_GB.UTF-8 --member-of wheel&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;man homectl&lt;/code&gt; provides a complete list of flags; in particular it also offers support for various kinds of security tokens (e.g. FIDO2) for user authentication, provides plenty of means for resource accounting (e.g. memory consumption) for the user account, and supports different kinds of password policies.&lt;/p&gt;
&lt;p&gt;Finally we may run into systemd issues with home areas on btrfs (see below); if login fails with a “Operation on home failed: Not enough disk space for home” message we need to enable LUKS discard:&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;$ homectl update foo --luks-discard=true&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This flag is not safe (heed the warning in &lt;code&gt;man homectl&lt;/code&gt;), but until systemd improves its behaviour on btrfs we have no choice unfortunately.&lt;/p&gt;
&lt;h3 id=&quot;setup-secure-boot&quot; tabindex=&quot;-1&quot;&gt;Setup secure boot &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/install-arch-with-secure-boot-tpm2-based-luks-encryption-and-systemd-homed/#setup-secure-boot&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;First let’s check the secure boot state. We must be in Setup Mode in order to enroll our own keys:&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;$ sbctl status
Installed:	✓ sbctl is installed
Owner GUID:	REDACTED
Setup Mode:	✗ Enabled
Secure Boot:	✗ Disabled&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To enable secure boot we need some keys which we generate with &lt;code&gt;sbctl&lt;/code&gt;. For historical reasons sbctl creates these keys in &lt;code&gt;/usr/share/secureboot&lt;/code&gt; but plans exists to change this to a more appropriate place (see &lt;a href=&quot;https://github.com/Foxboron/sbctl/issues/57&quot;&gt;Github issue 57&lt;/a&gt;).&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;$ sbctl create-keys&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we tell dracut how to sign the UEFI binaries it builds and rebuild our kernel images to get them signed:&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;$ cat &gt; /etc/dracut.conf.d/50-secure-boot.conf &lt;&lt;EOF uefi_secureboot_cert=&quot;/usr/share/secureboot/keys/db/db.pem&quot; uefi_secureboot_key=&quot;/usr/share/secureboot/keys/db/db.key&quot; EOF=&quot;&quot; $=&quot;&quot; dracut=&quot;&quot; -f=&quot;&quot; --uefi=&quot;&quot; --regenerate-all&lt;=&quot;&quot; code=&quot;&quot;&gt;&lt;/EOF&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next we need to sign the bootloader. With &lt;code&gt;-s&lt;/code&gt; we ask &lt;code&gt;sbctl&lt;/code&gt; to remember this file in its database which later lets us check signatures with &lt;code&gt;sbctl verify&lt;/code&gt; and automatically update all signatures with &lt;code&gt;sbctl sign-all&lt;/code&gt;. The &lt;code&gt;sbctl&lt;/code&gt; package includes a pacman hook which automatically updates signatures when an EFI binary on &lt;code&gt;/efi&lt;/code&gt; or in &lt;code&gt;/usr/lib&lt;/code&gt; changed. Note that we do not sign the boot loader on &lt;code&gt;/efi&lt;/code&gt; but instead place a signed copy in &lt;code&gt;/usr/lib&lt;/code&gt;. Starting with systemd 250 &lt;code&gt;bootctl&lt;/code&gt; will pick up the signed copy when updating the boot loader. Hence we reinstall the bootloader afterwards to put the signed copy on &lt;code&gt;/efi&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;$ sbctl sign -s -o /usr/lib/systemd/boot/efi/systemd-bootx64.efi.signed /usr/lib/systemd/boot/efi/systemd-bootx64.efi
$ bootctl install&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We should also do the same for the firmware update to enable seamless firmware updates under secure boot. Again we use &lt;code&gt;-s&lt;/code&gt; to remember this file in the &lt;code&gt;sbtctl&lt;/code&gt; database:&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;$ sbctl sign -s -o /usr/lib/fwupd/efi/fwupdx64.efi.signed /usr/lib/fwupd/efi/fwupdx64.efi&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now let’s verify that we have all signatures in place and enroll keys if everything’s properly signed:&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;$ sbctl verify
Verifying file database and EFI images in /efi...
✓ /usr/lib/fwupd/efi/fwupdx64.efi.signed is signed
✓ /usr/lib/systemd/boot/efi/systemd-bootx64.efi.signed is signed
✓ /efi/EFI/BOOT/BOOTX64.EFI is signed
✓ /efi/EFI/Linux/linux-5.15.12-arch1-1-19ea0ebee1ea4de086128ce1a8e2197b-rolling.efi is signed
✓ /efi/EFI/systemd/systemd-bootx64.efi is signed
$ sbctl enroll-keys&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After a reboot we can check the secure boot state again; we’ll see that setup mode is now disabled, secure boot is on, and everything was properly enrolled:&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;$ reboot
$ sbctl status
Installed:	✓ sbctl is installed
Owner GUID:	REDACTED
Setup Mode:	✓ Disabled
Secure Boot:	✓ Enabled&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;enroll-tpm2-keys&quot; tabindex=&quot;-1&quot;&gt;Enroll TPM2 keys &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/install-arch-with-secure-boot-tpm2-based-luks-encryption-and-systemd-homed/#enroll-tpm2-keys&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;With the boot process secured we can now configure automatic unlocking of the root filesystem, by binding a LUKS key to the TPM.&lt;/p&gt;
&lt;p&gt;We enable the tpm2-tss module in the Dracut configuration, install the dependencies of this dracut module, and regenerate our UEFI kernel images (which will again be signed for secure boot):&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;$ cat &gt; /etc/dracut.conf.d/50-tpm2.conf &lt;&lt;EOF add_dracutmodules+=&quot; tpm2-tss &quot; EOF=&quot;&quot; $=&quot;&quot; pacman=&quot;&quot; -S=&quot;&quot; tpm2-tools=&quot;&quot; dracut=&quot;&quot; -f=&quot;&quot; --uefi=&quot;&quot; --regenerate-all&lt;=&quot;&quot; code=&quot;&quot;&gt;&lt;/EOF&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we can enroll a TPM2 token (bound to the secure boot measurement in PCR 7) and a recovery key to our root filesystem. This prompts for an existing passphrase each time. Store the recovery key at a safe place &lt;em&gt;outside&lt;/em&gt; of this disk, to have it at hand if TPM2 unlocking ever breaks.&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;$ systemd-cryptenroll /dev/gpt-auto-root-luks --recovery-key
$ systemd-cryptenroll /dev/gpt-auto-root-luks --tpm2-device=auto&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now reboot and enjoy: The boot process goes straight all the way to the login manager and never shows a LUKS password prompt. The root filesystem is still reasonably secure: The TPM2 key becomes invalid if the secure boot state changes (e.g. new keys are enrolled, or secure boot is disabled), and cannot be recovered if the disk is removed from the system. Consequently only a kernel signed and authenticated with your own secure boot keys can unlock the root disk automatically.&lt;/p&gt;
&lt;p&gt;Finally we can wipe the password slots if you like (&lt;strong&gt;make sure to have a recovery key at this point&lt;/strong&gt;):&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;$ systemd-cryptenroll /dev/gpt-auto-root-luks --wipe-slot=password&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you cannot use secure boot for some reason you can alternatively bind the TPM2 token to a combination of firmware state and configuration and the exact boot chain (up to and including the specific kernel that was started), by specifing the PCR registers 0-5:&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;$ systemd-cryptenroll /dev/gpt-auto-root-luks --tpm2-device=auto --tpm2-pcrs 0+1+2+3+4+5&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This only permits the current kernel and its specific boot chain (e.g bootloader used) to unlock the root filesystem automatically. However this means that we need to reboot and then wipe and re-enroll the TPM2 token after every rebuild of the kernel image… which happens quite often in fact: Dracut updates or configuration changes, kernel updates, systemd updates (due to the EFI shim provided by systemd), bootloader updates, bootloader configuration changes, etc.&lt;/p&gt;
&lt;p&gt;Hence I generally recommend to use secure boot if possible in any way.&lt;/p&gt;
&lt;h2 id=&quot;issues-with-this-setup&quot; tabindex=&quot;-1&quot;&gt;Issues with this setup &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/install-arch-with-secure-boot-tpm2-based-luks-encryption-and-systemd-homed/#issues-with-this-setup&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;While I am happy with this setup it still has a few drawbacks and issues.&lt;/p&gt;
&lt;h3 id=&quot;double-encryption&quot; tabindex=&quot;-1&quot;&gt;Double encryption &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/install-arch-with-secure-boot-tpm2-based-luks-encryption-and-systemd-homed/#double-encryption&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In this setup home directories get encrypted twice, once by homed and then again by the underlying LUKS device. This wastes a bunch of CPU cycles and likely impacts performance a lot, though I haven’t measured the impact and it’s not so bad as to be noticeable in my day-to-day work.&lt;/p&gt;
&lt;p&gt;We could optimize this by putting &lt;code&gt;/home/&lt;/code&gt; on a separate partition backed by dm-integrity to authenticate the filesystem (omitting dm-integrity and using a plain file system leaves an attack vector, because linux cannot securely mount untrusted file systems). This setup requires at least systemd 250 or newer, because earlier versions do not support dm-integrity well. With systemd 250 we can setup a HMAC-based integrity device, put the HMAC key on the rootfs (e.g. &lt;code&gt;/etc/keys/home.key&lt;/code&gt;) and register the home partition in &lt;code&gt;/etc/integritytab&lt;/code&gt; with that key, and then mount it via &lt;code&gt;/etc/fstab&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;However, this has a few issues on its own, because dm-integrity has a few design issues and is and nowhere near LUKS/dm-crypt:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;There’s no key management like LUKS for dm-crypt, meaning we can’t use passphrases or TPM2 keys for dm-integrity devices; instead we need a key file somewhere on disk.&lt;/li&gt;
&lt;li&gt;Unlike LUKS/dm-crypt devices dm-integrity devices aren’t self-describing, because the superblock for dm-integrity doesn’t even contain the algorithm used (see &amp;lt;&lt;a href=&quot;https://github.com/systemd/systemd/pull/20902&quot;&gt;https://github.com/systemd/systemd/pull/20902&lt;/a&gt;). We cannot mount a dm-integrity device without some extra configuration, and worse, getting the configuration wrong can silently corrupt the device.&lt;/li&gt;
&lt;li&gt;For these reasons, &lt;a href=&quot;https://systemd.io/DISCOVERABLE_PARTITIONS/&quot;&gt;DPS&lt;/a&gt; cannot and does not support dm-integrity partitions, so we need to configure the whole home partition mount, from dm-integrity up to &lt;code&gt;/etc/fstab&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;tooling-issues&quot; tabindex=&quot;-1&quot;&gt;Tooling issues &lt;a class=&quot;header-anchor&quot; href=&quot;https://swsnr.de/install-arch-with-secure-boot-tpm2-based-luks-encryption-and-systemd-homed/#tooling-issues&quot; aria-hidden=&quot;true&quot;&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There are also multiple issues with current tooling that require some more or less safe workarounds:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;At the time of writing systemd-home has issues with resizing LUKS home areas on btrfs filesystems, apparently due to &lt;code&gt;fallocate()&lt;/code&gt; idiosyncrasies in btrfs. This issue prevents users from logging in, see systemd issues &lt;a href=&quot;https://github.com/systemd/systemd/issues/19398&quot;&gt;19398&lt;/a&gt; and &lt;a href=&quot;https://github.com/systemd/systemd/issues/20960&quot;&gt;20960&lt;/a&gt;, an &lt;a href=&quot;https://bbs.archlinux.org/viewtopic.php?id=258382&quot;&gt;Arch forums post&lt;/a&gt;, and a &lt;a href=&quot;https://lists.freedesktop.org/archives/systemd-devel/2020-August/045092.html&quot;&gt;mail on the systemd-devel list&lt;/a&gt;. A workaround is to enable online discard for the user, but this flag is unsafe because it allows overcommitting disk space, which results in IO errors that the kernel and application are not well prepared to handle (which comes down to potential data loss). Keep frequent backups if you need this.&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Compose emojis</title>
    <link href="https://swsnr.de/compose-emojis/" />
    <updated>2021-11-06T00:00:00Z</updated>
    <id>https://swsnr.de/compose-emojis/</id>
    <content type="html">&lt;h1&gt;Compose emojis&lt;/h1&gt;
&lt;p&gt;On Linux you can define custom sequences for the Compose key. You just need to create a &lt;code&gt;~/.XCompose&lt;/code&gt; file and can start to define new sequences for e.g. emojis:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;include &amp;quot;%S/en_US.UTF-8/Compose&amp;quot;

&amp;lt;Multi_key&amp;gt; &amp;lt;period&amp;gt; &amp;lt;p&amp;gt; &amp;lt;r&amp;gt; &amp;lt;a&amp;gt; &amp;lt;y&amp;gt; : &amp;quot;🙏&amp;quot;
&amp;lt;Multi_key&amp;gt; &amp;lt;period&amp;gt; &amp;lt;less&amp;gt; &amp;lt;3&amp;gt; &amp;lt;parenright&amp;gt; : &amp;quot;😍&amp;quot;
&amp;lt;Multi_key&amp;gt; &amp;lt;period&amp;gt; &amp;lt;less&amp;gt; &amp;lt;3&amp;gt; &amp;lt;period&amp;gt; : &amp;quot;❤️&amp;quot;
&amp;lt;Multi_key&amp;gt; &amp;lt;period&amp;gt; &amp;lt;less&amp;gt; &amp;lt;3&amp;gt; &amp;lt;asterisk&amp;gt; : &amp;quot;😘&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;man 5 Compose&lt;/code&gt; documents the format, though Gtk doesn’t seem to support all of it: It doesn’t handle includes apparently, and always seems to include its own hard-coded list of compose sequences.&lt;/p&gt;
&lt;p&gt;There is &lt;a href=&quot;https://gist.github.com/natema/136d4c7a4f3c0ea448b3b2f768831a43&quot;&gt;a nice Gist with some sequences&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>The mysterious disapperance of Docker images</title>
    <link href="https://swsnr.de/the-mysterious-disapperance-of-docker-images/" />
    <updated>2021-08-09T00:00:00Z</updated>
    <id>https://swsnr.de/the-mysterious-disapperance-of-docker-images/</id>
    <content type="html">&lt;h1&gt;The mysterious disapperance of Docker images&lt;/h1&gt;
&lt;p&gt;A node hosts a Gitlab runner and a small k3s cluster which runs a few services as regular kubernetes deployments. A CI job pinned to that runner builds Docker images for these services services, updates the image of the corresponding deployments, and starts a few system and acceptance tests. The CI job does not push those images to the in-house registry; to avoid polluting the registry with hundreds of images it just builds locally.&lt;/p&gt;
&lt;p&gt;Each test then scales each deployment to zero replicas to effectively stop all services, clears the system’s underlying database, and scales the service deployments back to a small number of replicas sufficient for testing.&lt;/p&gt;
&lt;p&gt;The whole thing runs fine until one day the replicas randomly fail to start.&lt;/p&gt;
&lt;p&gt;The first symptom is that the tests time out while waiting for replicas to start. The services remain unreachable after system reset. Inspecting the deployment reveals that the Docker image doesn’t exist:&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;$ kubectl describe pod foo-68fbd8c7c5-8grqd
Name:         foo-68fbd8c7c5-8grqd
[…]
Events:
  Type     Reason     Age                   From               Message
  ----     ------     ----                  ----               -------
  Normal   Scheduled  13m                   default-scheduler  Successfully assigned default/foo-68fbd8c7c5-8grqd to node.example.com
  Normal   BackOff    11m (x6 over 13m)     kubelet            Back-off pulling image &quot;registry.example.com/foo/foo:1.2.3-64-affa5225&quot;
  Normal   Pulling    11m (x4 over 13m)     kubelet            Pulling image &quot;registry.example.com/foo/foo:1.2.3-64-affa5225&quot;
  Warning  Failed     11m (x4 over 13m)     kubelet            Error: ErrImagePull
  Warning  Failed     11m (x4 over 13m)     kubelet            Failed to pull image &quot;registry.example.com/foo/foo:1.2.3-64-affa5225&quot;: rpc error: code = Unknown desc = Error response from daemon: manifest for registry.example.com/foo/foo:1.2.3-64-affa5225 not found: manifest unknown: manifest unknown
  Warning  Failed     3m11s (x43 over 13m)  kubelet            Error: ImagePullBackOff&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Docker doesn’t find the image in the registry because it was never pushed, but it shouldn’t even try to ask the registry: The image was built locally and should already exist on the node. So why is it gone? Perhaps a docker build failure?&lt;/p&gt;
&lt;p&gt;However a &lt;code&gt;docker image ls&lt;/code&gt; thrown into the pipeline before the test reveals that the image builds fine; in fact it’s there minutes before the test starts and then randomly disappears while the tests are running. Clearly something’s deleting images from the Docker daemon.&lt;/p&gt;
&lt;p&gt;Some careful search for the right keywords (“docker images disappear” wasn’t good enough) reveals a &lt;a href=&quot;https://stackoverflow.com/questions/58348036/docker-images-disappearing-over-time&quot;&gt;Stack Overflow answer&lt;/a&gt; pointing to &lt;a href=&quot;https://kubernetes.io/docs/concepts/architecture/garbage-collection/#containers-images&quot;&gt;Kubernetes’ own garbage collection&lt;/a&gt; which runs &lt;em&gt;every minute&lt;/em&gt;, and, under disk space pressure, removes unused Docker images. And indeed, on the affected node kubelet wanted to delete even more:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[…] node.example.com k3s[…]: E0809 […] Image garbage collection failed multiple times in a row: wanted to free 311382016 bytes, but freed 573253440 bytes space with errors in image deletion
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Why does kubelet feel pressured to delete images? And why just about 500MiB? That’s not a lot, by Docker’s standards.&lt;/p&gt;
&lt;p&gt;Turns out, the system had a very very small /var/lib/docker:&lt;/p&gt;
&lt;pre class=&quot;language-console&quot;&gt;&lt;code class=&quot;language-console&quot;&gt;$ dh -h
Filesystem      Size  Used Avail Use% Mounted on
[…]
/dev/sda6       7.0G  2.3G  4.8G  33% /var&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Mystery solved: That’s nowhere near enough to satisfy docker’s demand for space, and explains why kubelet feels so much pressure to delete images in almost every build.&lt;/p&gt;
&lt;p&gt;As a stop-gap measure all images were pruned; later the day &lt;code&gt;/var/lib/docker&lt;/code&gt; was given much more space. The tests haven’t failed since.&lt;/p&gt;
</content>
  </entry>
</feed>