<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
    <channel>
      <title>Sc0tt</title>
      <link>https://blog.s99.sh</link>
      <description></description>
      <generator>Zola</generator>
      <language>en</language>
      <atom:link href="https://blog.s99.sh/rss.xml" rel="self" type="application/rss+xml"/>
      <lastBuildDate>Sat, 15 Aug 2026 00:00:00 +0000</lastBuildDate>
      <item>
          <title>Mostly Harmless: A Hitchhiker&#x27;s Guide to Nintendo Emulation and the Linux Hackers Who Won&#x27;t Let It Die</title>
          <pubDate>Sat, 15 Aug 2026 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://blog.s99.sh/blog/nintendnix/</link>
          <guid>https://blog.s99.sh/blog/nintendnix/</guid>
          <description xml:base="https://blog.s99.sh/blog/nintendnix/">&lt;p&gt;At 15:04:49 on Monday the 12th of May 2025, a volunteer named Peder Bergebakken
Sundt deleted a package called &lt;code&gt;torzu&lt;&#x2F;code&gt; from nixpkgs. Torzu was a fork of a fork
of yuzu, the Nintendo Switch emulator. It had been in the repository for four and
a half months. The commit message is two words: &lt;code&gt;torzu: drop&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Eighteen seconds later, at 15:05:07, he committed this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;aliases: re-add entries for yuzu and citra&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Not &lt;em&gt;add&lt;&#x2F;em&gt;. &lt;strong&gt;Re-add.&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Fourteen months earlier, yuzu and Citra had been erased from the internet in a
legal settlement. When nixpkgs removed them, it left behind small markers — one
line each, saying where the packages went and why. Sometime in the intervening
year, a routine cleanup had swept those markers away. And while he was down there
burying torzu, Peder noticed the older graves had been levelled.&lt;&#x2F;p&gt;
&lt;p&gt;So he carved them again. All eight of them, character for character, including
the inconsistent capitalisation in the originals.&lt;&#x2F;p&gt;
&lt;p&gt;I want to tell you about the two years of legal machinery that made that commit
necessary. But mostly I want to tell you about the eighteen seconds — because in
those eighteen seconds is the entire argument for free software, and I don&#x27;t
think I&#x27;ve ever seen it stated more plainly by anyone, in any manifesto,
anywhere.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;mostly-harmless&quot;&gt;Mostly harmless&lt;a class=&quot;post-anchor&quot; href=&quot;#mostly-harmless&quot; aria-label=&quot;Anchor link for: mostly-harmless&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;In &lt;em&gt;The Hitchhiker&#x27;s Guide to the Galaxy&lt;&#x2F;em&gt;, the Guide&#x27;s entry for the planet Earth
— the whole planet, four billion years of it, everyone who ever lived — reads, in
its entirety:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Mostly harmless.&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;That joke has stayed with me for twenty years because of how it works: the horror
isn&#x27;t that the entry is wrong. It&#x27;s that it&#x27;s &lt;em&gt;accurate&lt;&#x2F;em&gt;, and it&#x27;s two words, and
that&#x27;s all the room the universe allocated.&lt;&#x2F;p&gt;
&lt;p&gt;Nixpkgs has an entry like that for every piece of software it has ever buried.&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s the mechanic, because everything below depends on it. When a package
leaves nixpkgs, the maintainers can&#x27;t simply delete the files. Thousands of
people have &lt;code&gt;yuzu&lt;&#x2F;code&gt; written in a config file somewhere, and Nix is an evaluated
language — if the name just vanished, they&#x27;d get a bare &lt;em&gt;&quot;attribute &#x27;yuzu&#x27;
missing&quot;&lt;&#x2F;em&gt; and nothing else. So nixpkgs keeps a file, &lt;code&gt;pkgs&#x2F;top-level&#x2F;aliases.nix&lt;&#x2F;code&gt;,
which is somewhere between a redirect table and a cemetery. An entry can point at
a new name:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;nix&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-name z-nix z-source&quot;&gt;citra = citra-nightly;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; # added 2022-05-17&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Or, when there&#x27;s nothing left to point at, it becomes a &lt;code&gt;throw&lt;&#x2F;code&gt; — an attribute
whose only remaining purpose is to fail loudly and explain itself:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;nix&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-name z-nix z-source&quot;&gt;notepadqq =&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function&quot;&gt; throw&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;&amp;#39;notepadqq&amp;#39; has been removed due to upstream stopping maintenance in 2023.&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; # Added 2026-02-10&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That&#x27;s it. &lt;strong&gt;A &lt;code&gt;throw&lt;&#x2F;code&gt; in &lt;code&gt;aliases.nix&lt;&#x2F;code&gt; is a headstone.&lt;&#x2F;strong&gt; The software is gone,
but the name persists, carrying one sentence about what happened to it, written
by whoever buried it, addressed to a stranger in the future whose config finally
breaks.&lt;&#x2F;p&gt;
&lt;p&gt;I counted. As of today that file is 3,016 lines long and contains &lt;strong&gt;1,980 of
these epitaphs.&lt;&#x2F;strong&gt; It is, almost certainly by accident, one of the largest
machine-readable memorials to dead software that exists anywhere.&lt;&#x2F;p&gt;
&lt;p&gt;And when you read them in bulk, you notice they nearly all say the same handful
of things:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;nix&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-name z-nix z-source&quot;&gt;norouter =&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function&quot;&gt; throw&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;norouter has been removed because it has been marked as broken since at least November 2024.&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-name z-nix z-source&quot;&gt;notary =&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function&quot;&gt; throw&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;&amp;#39;notary&amp;#39; has been removed due to being archived upstream. Consider using &amp;#39;notation&amp;#39; instead.&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-name z-nix z-source&quot;&gt;notes-up =&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function&quot;&gt; throw&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;&amp;#39;notes-up&amp;#39; has been removed as it was unmaintained and depends on deprecated webkitgtk_4_0&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-name z-nix z-source&quot;&gt;nufraw =&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function&quot;&gt; throw&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;&amp;#39;nufraw&amp;#39; has been removed as the UI depended on the deprecated GTK2 engine.&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Unmaintained. Broken. Archived. Abandoned. Depends on something deprecated.&lt;&#x2F;p&gt;
&lt;p&gt;These are software&#x27;s natural causes of death — entropy, boredom, the slow
retirement of a dependency, a maintainer who got a new job or a kid or simply
stopped caring. Nineteen hundred and eighty gravestones, and the overwhelming
majority died the way everything dies: because eventually nobody was left to hold
it up.&lt;&#x2F;p&gt;
&lt;p&gt;Which is why the ones in this story stand out so violently. In a cemetery where
almost everyone died of old age, a small cluster of stones say something else
entirely.&lt;&#x2F;p&gt;
&lt;p&gt;They say that something came and &lt;em&gt;took&lt;&#x2F;em&gt; them.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-quiet-decade&quot;&gt;The quiet decade&lt;a class=&quot;post-anchor&quot; href=&quot;#the-quiet-decade&quot; aria-label=&quot;Anchor link for: the-quiet-decade&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Before any of that, there were years and years of nothing happening, which is the
part everyone forgets and the part that matters most.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2014-09-02  73439c605320  add dolphinEmu and dolphinEmuMaster&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Cray Elliott adds Dolphin — the GameCube and Wii emulator — in September 2014.
Remember this commit. It&#x27;s going to be the most important one in the piece, and
you won&#x27;t see why until the end.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2017-07-27  bc52bf2cfe3d  citra: init at 2017-07-26&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Nikolay Amiantov adds Citra, the 3DS emulator. The project didn&#x27;t cut releases,
so the &quot;version&quot; is just the date somebody checked it out.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2020-04-02  c36ab0c67853  yuzu: init at 482&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2020-07-31  2e2f87db13e0  ryujinx: init at 1.0.5160&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Four months apart in 2020, both Switch emulators arrive, added by the same person
— a maintainer signing as &quot;Ivar&quot; and &quot;IvarWithoutBones,&quot; who appears to have
simply been the one who cared about this corner of the world.&lt;&#x2F;p&gt;
&lt;p&gt;And then, for years: maintenance. Hundreds of commits of the most profoundly
boring kind.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2022-03-21  yuzu-{ea,mainline}: {2557,953} -&amp;gt; {2690,992}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2022-05-12  yuzu-{ea,mainline}: {2690,992} -&amp;gt; {2725,1014}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2023-07-16  yuzu-ea: 3702 -&amp;gt; 3783, yuzu-mainline: 1475 -&amp;gt; 1513&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Version bumps. Hash updates. Somebody fixing the Wayland build. Somebody adding
udev rules so controllers work. Somebody noticing the package pulled in a copy of
ffmpeg it didn&#x27;t need.&lt;&#x2F;p&gt;
&lt;p&gt;In April 2023, K900 — by then yuzu&#x27;s de facto caretaker in nixpkgs — writes this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2023-04-28  f08af5a3d41e  yuzu: reanimate, clean up, switch to qt6&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;em&gt;Reanimate.&lt;&#x2F;em&gt; The package had gone stale and somebody brought it back. It&#x27;s a
throwaway verb in a routine commit, and it is the only time in this entire
history that anyone uses that word about something that actually returned.&lt;&#x2F;p&gt;
&lt;p&gt;This is what a healthy free software ecosystem looks like from the inside. It
isn&#x27;t dramatic. Nobody is manning barricades. It&#x27;s a handful of people, scattered
across several countries, who have quietly taken on a small permanent obligation
to strangers: &lt;em&gt;this will keep working.&lt;&#x2F;em&gt; Not because anyone pays them. Because
they said they would, in the only way that counts, which is by continuing to do
it every couple of weeks for years.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-last-normal-days&quot;&gt;The last normal days&lt;a class=&quot;post-anchor&quot; href=&quot;#the-last-normal-days&quot; aria-label=&quot;Anchor link for: the-last-normal-days&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Nintendo of America filed suit against Tropic Haze LLC, the developers of yuzu,
on Monday the 26th of February 2024.&lt;&#x2F;p&gt;
&lt;p&gt;Here is what nixpkgs did on Tuesday:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2024-02-27 16:06:27  6b24129a4f67  yuzuPackages.compat-list: unstable-2024-02-14 -&amp;gt; unstable-2024-02-26&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2024-02-27 16:21:46  db7b9caf4914  yuzuPackages.early-access: 4141 -&amp;gt; 4174&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2024-02-27 17:48:21  efc44c29ab49  yuzuPackages.mainline: 1715 -&amp;gt; 1727&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2024-02-27 22:37:40  b5c956262d95  Merge pull request #291878 from K900&#x2F;fresh-fruit&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Three routine version bumps and a merge, on a branch K900 had named
&lt;code&gt;fresh-fruit&lt;&#x2F;code&gt;, the day after the lawsuit. Nothing acknowledges anything. It&#x27;s a
Tuesday, there were new builds, he packaged them.&lt;&#x2F;p&gt;
&lt;p&gt;The next day, somebody else shows up to do careful work:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2024-02-28  46fbd894a87c  Yuzu: Only compile with -msse4.1 if on x64&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Rowan Goemans, fixing a compiler flag that was breaking the build on ARM. It&#x27;s
part of a larger effort to make yuzu work on &lt;code&gt;aarch64&lt;&#x2F;code&gt; — people wanted to run it
on handhelds and single-board machines. Meticulous, unglamorous portability work,
the kind that benefits a minority of users and takes a disproportionate amount of
care.&lt;&#x2F;p&gt;
&lt;p&gt;K900 merged it on the 1st of March.&lt;&#x2F;p&gt;
&lt;p&gt;The package had seventy-two hours left.&lt;&#x2F;p&gt;
&lt;p&gt;I keep returning to those three days, and I&#x27;ve decided the reason is this: they
are the last moments in this story when the people involved were still simply
&lt;em&gt;building something&lt;&#x2F;em&gt;. Not defending it. Not mirroring it. Not arguing about it.
Just making a Switch emulator compile correctly on a processor architecture most
of its users didn&#x27;t own, because a good patch had arrived and that&#x27;s what you do
with a good patch.&lt;&#x2F;p&gt;
&lt;p&gt;Nobody wrote &quot;this may be moot now.&quot; The commit log had no idea what was coming.
It almost never does.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;two-removals-and-one-word&quot;&gt;Two removals, and one word&lt;a class=&quot;post-anchor&quot; href=&quot;#two-removals-and-one-word&quot; aria-label=&quot;Anchor link for: two-removals-and-one-word&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;On Monday the 4th of March 2024, yuzu&#x27;s developers settled for $2.4 million,
agreed never to work on Nintendo emulation again, and pulled everything offline.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2024-03-04 20:39:38  ef72e8b3a9b9  yuzuPackages: remove&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Soner Sayakci, on a branch called &lt;code&gt;remove-yuzu&lt;&#x2F;code&gt;. Eight package files deleted, 343
lines gone: mainline, early-access, the compatibility list, the timezone
database helper, both update scripts. And in &lt;code&gt;aliases.nix&lt;&#x2F;code&gt;, four living redirects
become five headstones:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;diff&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-markup z-deleted z-diff&quot;&gt;  yuzu-ea = yuzuPackages.early-access; # Added 2022-08-18&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-markup z-deleted z-diff&quot;&gt;  yuzu-early-access = yuzuPackages.early-access; # Added 2023-12-29&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-markup z-deleted z-diff&quot;&gt;  yuzu = yuzuPackages.mainline; # Added 2021-01-25&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-markup z-deleted z-diff&quot;&gt;  yuzu-mainline = yuzuPackages.mainline; # Added 2023-12-29&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;+&lt;&#x2F;span&gt;&lt;span class=&quot;z-markup z-inserted z-diff&quot;&gt;  yuzu-ea = throw &amp;quot;yuzu-ea has been removed from nixpkgs, as it has been deleted upstream&amp;quot;; # Added 2024-03-04&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;+&lt;&#x2F;span&gt;&lt;span class=&quot;z-markup z-inserted z-diff&quot;&gt;  yuzu-early-access = throw &amp;quot;yuzu-early-access has been removed from nixpkgs, as it has been deleted upstream&amp;quot;; # Added 2024-03-04&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;+&lt;&#x2F;span&gt;&lt;span class=&quot;z-markup z-inserted z-diff&quot;&gt;  yuzu = throw &amp;quot;yuzu has been removed from nixpkgs, as it has been deleted upstream&amp;quot;; # Added 2024-03-04&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;+&lt;&#x2F;span&gt;&lt;span class=&quot;z-markup z-inserted z-diff&quot;&gt;  yuzu-mainline = throw &amp;quot;yuzu-mainline has been removed from nixpkgs, as it has been deleted upstream&amp;quot;; # Added 2024-03-04&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;+&lt;&#x2F;span&gt;&lt;span class=&quot;z-markup z-inserted z-diff&quot;&gt;  yuzuPackages = throw &amp;quot;yuzuPackages has been removed from nixpkgs, as it has been deleted upstream&amp;quot;; # Added 2024-03-04&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Look at the removed lines. &lt;code&gt;# Added 2021-01-25&lt;&#x2F;code&gt;. That alias had been pointing at
a real, living package for three years and two months. That&#x27;s how long the name
meant something.&lt;&#x2F;p&gt;
&lt;p&gt;The reason given is &lt;strong&gt;&quot;deleted upstream.&quot;&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The following evening the same person came back to bury Citra, which died in the
same settlement because it shared a team and a codebase:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2024-03-05 20:18:05  9b90f685e58c  citra: remove package&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And tucked into that commit, alongside the three new Citra epitaphs, is this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;diff&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-markup z-deleted z-diff&quot;&gt;  yuzu = throw &amp;quot;yuzu has been removed from nixpkgs, as it has been deleted upstream&amp;quot;; # Added 2024-03-04&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;+&lt;&#x2F;span&gt;&lt;span class=&quot;z-markup z-inserted z-diff&quot;&gt;  yuzu = throw &amp;quot;yuzu has been removed from nixpkgs, as it has been taken down upstream&amp;quot;; # Added 2024-03-04&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;He went back and changed the wording on all five. Nothing else moved — same
names, same dates, same trailing comments. One phrase:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;deleted → taken down.&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ve thought about this diff more than is reasonable. I don&#x27;t know what was in
his head and I&#x27;m not going to pretend I do. But those two phrases are not
synonyms, and the difference between them is precisely what everybody was
arguing about that week.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;Deleted upstream&lt;&#x2F;em&gt; is intransitive and blameless. It&#x27;s the same register as the
other nineteen hundred stones in that file — a project that stopped, a repo that
went quiet, software dying of natural causes. It puts yuzu in the same category
as &lt;code&gt;nufraw&lt;&#x2F;code&gt;, which died because nobody wanted to maintain a GTK2 interface.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;Taken down&lt;&#x2F;em&gt; has an agent. Something was &lt;strong&gt;done&lt;&#x2F;strong&gt; to it, by &lt;strong&gt;someone&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Twenty-four hours after writing the gentler version, with a second emulator to
bury and presumably a day to think about it, a volunteer sysadmin went back into
a 3,000-line file and corrected the historical record — so that the error message
some stranger sees in 2031 will be true.&lt;&#x2F;p&gt;
&lt;p&gt;Nobody asked him to do that. Nobody would ever have noticed if he hadn&#x27;t. There
is no metric anywhere that rewards it. He did it because the record was slightly
wrong and he was the one who knew.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-actually-killed-them&quot;&gt;What actually killed them&lt;a class=&quot;post-anchor&quot; href=&quot;#what-actually-killed-them&quot; aria-label=&quot;Anchor link for: what-actually-killed-them&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Here&#x27;s the part that changed how I think about this whole story, and I need to be
precise about it, because the precision is the point.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Not one of these projects was ever found liable of anything by any court.&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Not one. yuzu settled before any judge ruled on anything. Ryujinx never even got
a lawsuit — in October 2024 its developer received a private offer and accepted
it, and no one outside that conversation knows what was in it. The thirteen
repositories that vanished in February 2026 came down on filed forms. In the
entire two-year demolition of open-source Nintendo emulation, there was no
verdict, no finding of fact, no adversarial hearing, no precedent set.&lt;&#x2F;p&gt;
&lt;p&gt;There was no &lt;em&gt;law&lt;&#x2F;em&gt; here in the sense most people mean the word. There was
leverage.&lt;&#x2F;p&gt;
&lt;p&gt;And the leverage isn&#x27;t copyright, which is the thing everyone assumes. Copying a
game is infringement, obviously — but writing an emulator isn&#x27;t, and that was
settled decades ago in &lt;em&gt;Sega v. Accolade&lt;&#x2F;em&gt; and &lt;em&gt;Sony v. Connectix&lt;&#x2F;em&gt;. Clean-room
reverse engineering is legal. It has been legal my entire adult life. Nintendo
has never once argued otherwise in front of a judge, because they would lose.&lt;&#x2F;p&gt;
&lt;p&gt;The lever is §1201 of the Digital Millennium Copyright Act — the
anti-circumvention provision. And §1201 doesn&#x27;t regulate copying. It regulates
&lt;strong&gt;tools&lt;&#x2F;strong&gt;. It makes it unlawful to distribute technology that circumvents an
access control, and — this is the part that should worry you regardless of how
you feel about Nintendo — you can be liable under it &lt;em&gt;whether or not any
infringement ever occurs.&lt;&#x2F;em&gt; The act it punishes is publication. The thing it
removes from the world is not a copy. It&#x27;s a piece of source code.&lt;&#x2F;p&gt;
&lt;p&gt;If that sounds familiar, it should. This is the same statute, and very nearly the
same fight, as &lt;em&gt;Universal v. Corley&lt;&#x2F;em&gt; — the first circuit-level test of §1201 —
in which a federal court ordered &lt;strong&gt;2600&lt;&#x2F;strong&gt; magazine to stop publishing DeCSS, a
few hundred lines of C that let you play a DVD you owned on a Linux machine. Not
to stop pirating anything. To stop &lt;em&gt;publishing the source code&lt;&#x2F;em&gt;. The injunction
barred Corley from posting it and from knowingly linking to anyone who had.&lt;&#x2F;p&gt;
&lt;p&gt;The response is one of the founding legends of the culture this post is about.
People printed DeCSS on t-shirts. Somebody set it to music. Seth Schoen wrote the
algorithm out as a 465-stanza haiku — explicitly to make the argument that source
code is speech, and to force a court that wanted to ban it to explain why banning
a poem was different. Phil Carmody found a prime number whose digits encoded the
gzipped source, so that a &lt;em&gt;prime number&lt;&#x2F;em&gt; became contraband. Jon Lech Johansen,
the Norwegian teenager who co-wrote the thing, was prosecuted at home and
acquitted — twice. Within weeks there were mirrors on every continent, and the
code has never been unavailable for a single day since.&lt;&#x2F;p&gt;
&lt;p&gt;They did not win in court. Corley lost, and lost the appeal; the Second Circuit
affirmed that §1201 was constitutional even applied to publishing source. What
they won was the only victory that was ever actually on offer: &lt;strong&gt;they made the
code impossible to erase.&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;That is the tradition these commits belong to. Not a legal tradition — a
practical one. When the law can remove something from the world by filing a form,
the only durable answer is to make the something exist in too many places, held
by too many unaffiliated people, to be worth the forms.&lt;&#x2F;p&gt;
&lt;p&gt;Which brings us to a man with an archive.org link and an apology.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;ryujinx-dies-slowly&quot;&gt;Ryujinx dies slowly&lt;a class=&quot;post-anchor&quot; href=&quot;#ryujinx-dies-slowly&quot; aria-label=&quot;Anchor link for: ryujinx-dies-slowly&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;yuzu died in a single commit. Ryujinx took twenty-one months to finish dying, and
the manner of it is completely different — and, I think, more moving.&lt;&#x2F;p&gt;
&lt;p&gt;On the 1st of October 2024, Nintendo contacted gdkchan, Ryujinx&#x27;s developer, and
offered him an agreement. He took it. Within a day the GitHub organisation and
every asset in it was gone. No filing, no figure, no terms, no court. A phone
call, and six years of work left the internet.&lt;&#x2F;p&gt;
&lt;p&gt;This created an immediate and mundane problem for nixpkgs, of the kind that
lawsuits don&#x27;t usually cause: the package&#x27;s &lt;code&gt;src&lt;&#x2F;code&gt; pointed at a repository that no
longer existed. Ryujinx no longer &lt;em&gt;built&lt;&#x2F;em&gt;. Not for anyone wanting to pirate
anything — for anyone on NixOS who already had it in their configuration and ran
an update.&lt;&#x2F;p&gt;
&lt;p&gt;Four days later:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2024-10-05 17:53:29  59a5a09b670a  ryujinx: archive.org tarball mirror&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The body of that commit is my favourite artifact in this entire history:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Should fix source builds by relying on our beloved archive.org to host a
SHA256-matching tarball generated from a mirror of the repo.&lt;&#x2F;p&gt;
&lt;p&gt;I have only &lt;code&gt;nix repl&lt;&#x2F;code&gt;&#x27;d the fetchTarball expression for its consistency
check, as I do not currently have the time nor storage to build the package
locally, sorry about that.&lt;&#x2F;p&gt;
&lt;p&gt;We could also generate archives to address [issue #345410] and the 24.05
branch.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Nabile Rahmani. &lt;em&gt;Our beloved archive.org.&lt;&#x2F;em&gt; And then, instantly, the apology — he
couldn&#x27;t build it locally, he didn&#x27;t have the disk space, he&#x27;s sorry, and here is
exactly what he verified and exactly what he didn&#x27;t.&lt;&#x2F;p&gt;
&lt;p&gt;I find that almost unbearably decent. A corporation had just deleted a project
from the internet, and this man&#x27;s response was to go find a copy, wire it back
up, and &lt;em&gt;apologise for the limitations of his hard drive.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The diff is a small monument:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;diff&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-markup z-deleted z-diff&quot;&gt;  src = fetchFromGitHub {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-markup z-deleted z-diff&quot;&gt;    owner = &amp;quot;Ryujinx&amp;quot;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-markup z-deleted z-diff&quot;&gt;    repo = &amp;quot;Ryujinx&amp;quot;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-markup z-deleted z-diff&quot;&gt;    rev = &amp;quot;5dbba07e33e83c9047dcbb701c9655edbbe89086&amp;quot;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;+&lt;&#x2F;span&gt;&lt;span class=&quot;z-markup z-inserted z-diff&quot;&gt;  src = fetchzip {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;+&lt;&#x2F;span&gt;&lt;span class=&quot;z-markup z-inserted z-diff&quot;&gt;    url = &amp;quot;https:&#x2F;&#x2F;archive.org&#x2F;download&#x2F;ryujinx-5dbba-07e-33e-83c-9047dcbb-701c-9655edbbe-89086.tar&#x2F;Ryujinx-5dbba07e33e83c9047dcbb701c9655edbbe89086.tar.gz&amp;quot;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;     hash = &amp;quot;sha256-UeJ3KE5e5H9crqroAxjmxYTf&#x2F;Z4cbj41a6&#x2F;1HW2nLcA=&amp;quot;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;   };&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;The hash didn&#x27;t change.&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Sit with that for a second, because it&#x27;s the most quietly radical thing in this
post. Nix builds are content-addressed: the definition doesn&#x27;t say &lt;em&gt;where&lt;&#x2F;em&gt; the
source lives, it says &lt;em&gt;what the source is&lt;&#x2F;em&gt;, as a cryptographic fingerprint. The
project was erased from the internet, and the build recipe barely noticed,
because nixpkgs already knew exactly what it was looking for and any
bit-identical copy from anywhere on Earth would satisfy it.&lt;&#x2F;p&gt;
&lt;p&gt;You cannot take something down from a system like that. You can only make it
slightly harder to find. The architecture itself — designed for reproducibility,
for boring engineering reasons, years before any of this — turns out to be
censorship-resistant as a side effect of being &lt;em&gt;honest about what it wants&lt;&#x2F;em&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Three hours later he backported the same fix to the stable branch, so people on
&lt;code&gt;release-24.05&lt;&#x2F;code&gt; wouldn&#x27;t be stranded either. Nobody was going to thank him for
that either.&lt;&#x2F;p&gt;
&lt;p&gt;Then the long tail, which is somehow sadder than the removal:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2024-10-14  3b0e6194fb38  ryujinx: remove absolute path in desktop entry&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Two weeks after the project ceased to exist, somebody tidied up its &lt;code&gt;.desktop&lt;&#x2F;code&gt;
file. The little menu entry with the icon. For a program whose upstream had been
gone a fortnight.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2024-11-05  a321e2707588  ryujinx-greemdev: init at 1.2.76&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;A fork appears — a developer called greemdev had picked up the code — and gets
packaged five weeks after the death.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2024-12-16  f1c84c20f7f3  ryujinx: drop update script&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That one is the actual funeral. The update script is the machinery that
periodically checks upstream for a new release. Deleting it is the moment
somebody concluded, in writing, that there would never be another version. The
package still built fine. It just stopped waiting for news.&lt;&#x2F;p&gt;
&lt;p&gt;The fork got a real name in January —&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2025-01-20  255128254232  ryujinx-greemdev: rename to ryubing&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;— and only in the summer, twenty-one months after the phone call, did anyone
finally remove the original:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2025-07-30  1c1083100bd2  ryujinx: remove&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;nix&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-name z-nix z-source&quot;&gt;ryujinx =&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function&quot;&gt; throw&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;&amp;#39;ryujinx&amp;#39; has been replaced by &amp;#39;ryubing&amp;#39; as the new upstream&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; # Added 2025-07-30&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Read that against yuzu&#x27;s headstone. No takedown. No deletion. No blame. &lt;em&gt;Replaced
by, as the new upstream.&lt;&#x2F;em&gt; Even in the epitaph, it&#x27;s a handoff — one set of hands
to another.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-sweep&quot;&gt;The sweep&lt;a class=&quot;post-anchor&quot; href=&quot;#the-sweep&quot; aria-label=&quot;Anchor link for: the-sweep&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;After yuzu&#x27;s settlement the codebase forked in every direction at once, and the
names are their own form of testimony: Suyu — say it aloud, &lt;em&gt;sue-you&lt;&#x2F;em&gt; — and
Sudachi, Torzu, Nuzu, Uzuy, Sumi, Citron. People were angry, and the anger came
out as puns, which is the most reliable indicator of a healthy subculture I know.&lt;&#x2F;p&gt;
&lt;p&gt;nixpkgs picked up two of them, both over Christmas:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2024-12-25  f8289f040130  suyu: init at 0.0.4&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2024-12-26  f0a75c8509dc  torzu: init at unstable 12.15.24&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Somebody packaged a Nintendo Switch emulator on Christmas Day.&lt;&#x2F;p&gt;
&lt;p&gt;Torzu then got months of genuine care — a maintainer went through it properly,
switching it from vendored libraries to system ones, unbreaking &lt;code&gt;aarch64&lt;&#x2F;code&gt;,
cleaning up the ffmpeg handling. Four separate commits on a single day in January
2025, all of them tidying. This is a fork of a fork of a dead emulator, and
somebody was still doing the boring, correct, invisible thing with it.&lt;&#x2F;p&gt;
&lt;p&gt;And then the 12th of May:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2025-05-12 15:04:49  6b4920b3da9a  torzu: drop&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2025-05-12 15:05:07  e0828b8ecd96  aliases: re-add entries for yuzu and citra&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2025-05-12 19:10:58  41e4ba4b5582  suyu: drop&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Two different maintainers, four hours apart, burying both forks. And the epitaphs
have changed again:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;nix&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-name z-nix z-source&quot;&gt;torzu =&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function&quot;&gt; throw&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;torzu has been removed from nixpkgs, as it is subject to a DMCA takedown&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; # Added 2025-05-12&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-name z-nix z-source&quot;&gt;suyu =&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function&quot;&gt; throw&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;suyu has been removed from nixpkgs, as it is subject to a DMCA takedown&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; # Added 2025-05-12&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Written independently, hours apart, by two people who as far as I can tell never
coordinated — and they arrived at the same sentence. By the second round of
deaths, nixpkgs had stopped reaching for euphemisms altogether and started naming
the instrument.&lt;&#x2F;p&gt;
&lt;p&gt;But the commit I care about is still the middle one. The eighteen-second one.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;diff&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;+&lt;&#x2F;span&gt;&lt;span class=&quot;z-markup z-inserted z-diff&quot;&gt;  citra = throw &amp;quot;citra has been removed from nixpkgs, as it has been taken down upstream&amp;quot;; # added 2024-03-04&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;+&lt;&#x2F;span&gt;&lt;span class=&quot;z-markup z-inserted z-diff&quot;&gt;  citra-nightly = throw &amp;quot;citra-nightly has been removed from nixpkgs, as it has been taken down upstream&amp;quot;; # added 2024-03-04&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;+&lt;&#x2F;span&gt;&lt;span class=&quot;z-markup z-inserted z-diff&quot;&gt;  citra-canary = throw &amp;quot;citra-canary has been removed from nixpkgs, as it has been taken down upstream&amp;quot;; # added 2024-03-04&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;...&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;+&lt;&#x2F;span&gt;&lt;span class=&quot;z-markup z-inserted z-diff&quot;&gt;  yuzu-ea = throw &amp;quot;yuzu-ea has been removed from nixpkgs, as it has been taken down upstream&amp;quot;; # Added 2024-03-04&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;+&lt;&#x2F;span&gt;&lt;span class=&quot;z-markup z-inserted z-diff&quot;&gt;  yuzu-early-access = throw &amp;quot;yuzu-early-access has been removed from nixpkgs, as it has been taken down upstream&amp;quot;; # Added 2024-03-04&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;+&lt;&#x2F;span&gt;&lt;span class=&quot;z-markup z-inserted z-diff&quot;&gt;  yuzu = throw &amp;quot;yuzu has been removed from nixpkgs, as it has been taken down upstream&amp;quot;; # Added 2024-03-04&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;+&lt;&#x2F;span&gt;&lt;span class=&quot;z-markup z-inserted z-diff&quot;&gt;  yuzu-mainline = throw &amp;quot;yuzu-mainline has been removed from nixpkgs, as it has been taken down upstream&amp;quot;; # Added 2024-03-04&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;+&lt;&#x2F;span&gt;&lt;span class=&quot;z-markup z-inserted z-diff&quot;&gt;  yuzuPackages = throw &amp;quot;yuzuPackages has been removed from nixpkgs, as it has been taken down upstream&amp;quot;; # Added 2024-03-04&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Eight headstones, restored exactly — the original &lt;code&gt;# Added 2024-03-04&lt;&#x2F;code&gt; comments
preserved, the lowercase &lt;code&gt;# added&lt;&#x2F;code&gt; on the Citra lines preserved, the whole thing
put back the way it had been rather than tidied into consistency. He didn&#x27;t
rewrite history. He restored it.&lt;&#x2F;p&gt;
&lt;p&gt;Mechanically it&#x27;s housekeeping. A refactor dropped entries it shouldn&#x27;t have, and
he put them back so old configurations would keep producing useful errors. Five
minutes of work.&lt;&#x2F;p&gt;
&lt;p&gt;But consider what it actually is.&lt;&#x2F;p&gt;
&lt;p&gt;A corporation spent $2.4 million and two years making a piece of software cease
to exist. And fourteen months after it stopped existing, an unpaid volunteer in
Norway, working on something else entirely, noticed that the &lt;em&gt;record of its
death&lt;&#x2F;em&gt; had been accidentally erased — and stopped what he was doing to write it
back.&lt;&#x2F;p&gt;
&lt;p&gt;Not the software. The software was gone. The &lt;strong&gt;memory&lt;&#x2F;strong&gt; of it. The eight lines
whose only function in the universe is to tell some future stranger &lt;em&gt;there was
something here, and this is what happened to it.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;That&#x27;s not nostalgia. That&#x27;s not even really about emulators. That is a person
deciding, unprompted and unpaid, that erasure is a thing worth resisting on
principle, at whatever scale you happen to have access to — and then doing the
five minutes of work available to him.&lt;&#x2F;p&gt;
&lt;p&gt;Most political convictions never get closer to reality than an opinion. This one
took eighteen seconds and shipped.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-one-clean-handoff&quot;&gt;The one clean handoff&lt;a class=&quot;post-anchor&quot; href=&quot;#the-one-clean-handoff&quot; aria-label=&quot;Anchor link for: the-one-clean-handoff&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;While all this was happening, the 3DS branch of the family did something nobody
else in the story managed: it reorganised, deliberately, and survived.&lt;&#x2F;p&gt;
&lt;p&gt;Citra died in March 2024 alongside yuzu. Two months later, a fork was packaged:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2024-05-22  60c881713318  lime3ds: init at 2114&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Lime3DS got a normal year — version bumps, a boost compatibility fix, a build
repair. Then upstream, Lime3DS and a separate long-running Citra fork looked at
each other, decided that two half-staffed forks were worse than one properly
staffed project, and merged. And nixpkgs recorded the whole transition in a
single commit:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2025-03-22  fd2881f93fac  lime3ds: remove; azahar: init at 2120.1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;One package out, its successor in, same commit, same author. No &lt;code&gt;throw&lt;&#x2F;code&gt; about
takedowns — because nothing was taken. Just a community deciding to consolidate,
and the packaging walking across with it.&lt;&#x2F;p&gt;
&lt;p&gt;Azahar is still there. It got a version bump last month.&lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s the only death in this history that was actually a graduation, and it
happened because the people involved were free to merge their work — which they
were, because it was all under a licence that let them.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;citron-fourteen-days&quot;&gt;Citron: fourteen days&lt;a class=&quot;post-anchor&quot; href=&quot;#citron-fourteen-days&quot; aria-label=&quot;Anchor link for: citron-fourteen-days&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;On the 5th of February 2026, a new contributor added another descendant:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2026-02-05  0422085da960  citron-emu: init at version 0.12.25&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Emre Cebi. A 197-line package expression — the full careful treatment, every
system library wired up by hand, &lt;code&gt;fetchFromGitea&lt;&#x2F;code&gt; because Citron had already fled
GitHub.&lt;&#x2F;p&gt;
&lt;p&gt;On the 13th of February 2026, Nintendo filed DMCA takedowns against thirteen
Switch emulator repositories on GitHub in a single day. Active projects, dead
projects, archived projects. One company, one afternoon, one form each.&lt;&#x2F;p&gt;
&lt;p&gt;On the 19th of February 2026:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2026-02-19  d4e880822a10  citron-emu: remove&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Emre Cebi. The same person. &lt;strong&gt;Fourteen days.&lt;&#x2F;strong&gt; He wrote the package and he was
the one who deleted it, and there were nine working days in between.&lt;&#x2F;p&gt;
&lt;p&gt;The epitaph:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;nix&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-name z-nix z-source&quot;&gt;citron-emu =&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function&quot;&gt; throw&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;citron-emu was discontinued in february 2026&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment&quot;&gt; # added 2026-02-19&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Lowercase &lt;em&gt;february&lt;&#x2F;em&gt;. Typed by somebody who wasn&#x27;t composing anything for
posterity — just closing out a package he&#x27;d spent a couple of evenings on, two
weeks earlier, when it still had a future.&lt;&#x2F;p&gt;
&lt;p&gt;Thirteen repositories in one day, and no court was involved in any of it. That&#x27;s
the part I can&#x27;t get past. Not a hearing, not a judgment, not a finding. A
sufficiently large company can now file a stack of forms on a Friday and remove
thirteen pieces of software from the world&#x27;s largest code host before the
weekend, and the only thing standing between that and permanence is whether
enough unaffiliated strangers happened to keep copies.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-s-still-standing&quot;&gt;What&#x27;s still standing&lt;a class=&quot;post-anchor&quot; href=&quot;#what-s-still-standing&quot; aria-label=&quot;Anchor link for: what-s-still-standing&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;It would be a tidy ending if that were the end. It isn&#x27;t, and the ways it isn&#x27;t
are the whole reason I wrote this.&lt;&#x2F;p&gt;
&lt;p&gt;Three weeks before Citron arrived, someone had packaged another one:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2026-01-29  8c40c044b6b6  eden: init at 0.1.1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Eden went through the exact same February sweep that killed Citron. Its
maintainers filed counter-notices, moved their infrastructure, and kept shipping.
It is in nixpkgs right now. The most recent commit touching it is four days old:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2026-08-11  12c5a31da8d2  eden: pin httplib input&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Somebody pinning a dependency to keep a build reproducible. The single most
ordinary commit imaginable — which is exactly the point. The correct response to
&quot;they will try to erase this&quot; turns out not to be a manifesto. It&#x27;s a version
pin, filed on a Tuesday, by someone who intends to still be here next month.&lt;&#x2F;p&gt;
&lt;p&gt;Ryubing is still standing too, and in May it did something that made me put my
coffee down:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2026-05-08  5b511c170c0d  ryubing: migrate to forgejo&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;diff&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-markup z-deleted z-diff&quot;&gt;  src = fetchFromGitLab {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;+&lt;&#x2F;span&gt;&lt;span class=&quot;z-markup z-inserted z-diff&quot;&gt;  src = fetchFromForgejo {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;     domain = &amp;quot;git.ryujinx.app&amp;quot;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-markup z-deleted z-diff&quot;&gt;    owner = &amp;quot;Ryubing&amp;quot;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-markup z-deleted z-diff&quot;&gt;    repo = &amp;quot;Ryujinx&amp;quot;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;+&lt;&#x2F;span&gt;&lt;span class=&quot;z-markup z-inserted z-diff&quot;&gt;    owner = &amp;quot;projects&amp;quot;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;+&lt;&#x2F;span&gt;&lt;span class=&quot;z-markup z-inserted z-diff&quot;&gt;    repo = &amp;quot;Ryubing&amp;quot;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;There is an entire diaspora encoded in which fetcher function these packages
call.&lt;&#x2F;p&gt;
&lt;p&gt;They all started on &lt;code&gt;fetchFromGitHub&lt;&#x2F;code&gt;. Ryujinx fell back to &lt;code&gt;fetchzip&lt;&#x2F;code&gt; against
archive.org when GitHub stopped being an option. Citron used &lt;code&gt;fetchFromGitea&lt;&#x2F;code&gt;.
Ryubing went to &lt;code&gt;fetchFromGitLab&lt;&#x2F;code&gt; on a self-hosted box and then to
&lt;code&gt;fetchFromForgejo&lt;&#x2F;code&gt; when that box changed software. Eden fetches from
&lt;code&gt;git.eden-emu.dev&lt;&#x2F;code&gt; — its own machine, its own domain, its own terms.&lt;&#x2F;p&gt;
&lt;p&gt;Read the fetcher calls in chronological order and you get a map of where everyone
fled, and what they fled &lt;em&gt;to&lt;&#x2F;em&gt;: off the centralised platform, onto their own
hardware, running software they control.&lt;&#x2F;p&gt;
&lt;p&gt;That migration is not a defeat. That is the free software movement&#x27;s oldest
argument being proven in real time, at cost, by people who would much rather have
been left alone. A single DMCA to a single American company removed thirteen
projects in an afternoon — because thirteen projects were all standing on the
same floor. The answer, the one people have been making since before most of
these maintainers were born, is federation: your own server, your own domain,
software you can fork, licences nobody can revoke. It&#x27;s more work. It&#x27;s less
convenient. It is the only arrangement that cannot be undone by a form.&lt;&#x2F;p&gt;
&lt;p&gt;And nixpkgs followed every single one of them to their new address. That&#x27;s what a
distribution &lt;em&gt;is&lt;&#x2F;em&gt;, when you strip away everything else: a standing promise to
keep knowing where the source lives, no matter how many times it has to move.&lt;&#x2F;p&gt;
&lt;p&gt;Now — the control group.&lt;&#x2F;p&gt;
&lt;p&gt;Dolphin, added in 2014 in the very first commit of this post, has never been
removed. Never aliased, never mirrored, never threatened into silence. It got a
version bump in June. So did &lt;code&gt;cemu&lt;&#x2F;code&gt;, the Wii U emulator. &lt;code&gt;melonds&lt;&#x2F;code&gt; has had nine
version bumps this year alone. &lt;code&gt;mgba&lt;&#x2F;code&gt;, &lt;code&gt;ares&lt;&#x2F;code&gt;, &lt;code&gt;snes9x&lt;&#x2F;code&gt;, &lt;code&gt;desmume&lt;&#x2F;code&gt;,
&lt;code&gt;mupen64plus&lt;&#x2F;code&gt; — all present, all boring, all completely fine. Nintendo&#x27;s lawyers
have been extraordinarily busy for two years and not one commit has touched any
of them.&lt;&#x2F;p&gt;
&lt;p&gt;Because it was never about emulation. Every project that died in this story
required the user to supply cryptographic keys extracted from a physical console,
and that — §1201, the tool, the circumvention — is the entire hook. The projects
that don&#x27;t need keys have sat in the same repository, undisturbed, for over a
decade.&lt;&#x2F;p&gt;
&lt;p&gt;You can read that distinction directly off the commit log. It is much, much
clearer there than in any reporting I found, and it is the strongest possible
evidence that what happened here was not the enforcement of copyright. It was the
selective removal of software that a company found inconvenient, using a statute
that never required it to prove harm.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-name-in-the-licence-field&quot;&gt;The name in the licence field&lt;a class=&quot;post-anchor&quot; href=&quot;#the-name-in-the-licence-field&quot; aria-label=&quot;Anchor link for: the-name-in-the-licence-field&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;One last commit. If you open Eden&#x27;s package expression today — the living one,
the one updated four days ago — and scroll down to the licence list, you&#x27;ll find
this:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;nix&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;  # Primary&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-name z-nix&quot;&gt;  gpl3Plus&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;  # Build system&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-name z-nix&quot;&gt;  lgpl3Plus&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;  # Dynarmic and Yuzu code&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-name z-nix&quot;&gt;  gpl2Plus&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-name z-nix&quot;&gt;  bsd0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Dynarmic and Yuzu code.&lt;&#x2F;strong&gt;&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Nintendo got the repositories. They got the domains, the Patreon accounts, the
Discord servers, the developers&#x27; signatures on an agreement never to do this
again, and $2.4 million.&lt;&#x2F;p&gt;
&lt;p&gt;And the name is still sitting there, in the licence field of a package that
somebody updated on Tuesday, because the code was GPL and &lt;strong&gt;attribution is not
optional under the GPL.&lt;&#x2F;strong&gt; It&#x27;s not sentiment. It&#x27;s not defiance. It is a
&lt;em&gt;legal requirement&lt;&#x2F;em&gt;, and it runs in the opposite direction from every other legal
requirement in this story.&lt;&#x2F;p&gt;
&lt;p&gt;That&#x27;s the thing I want you to take away, and it took me a week of reading commit
logs to see it.&lt;&#x2F;p&gt;
&lt;p&gt;Copyright was the weapon. Section 1201 was the weapon. The same body of law that
let a company delete six years of work with a phone call is the law that makes it
&lt;em&gt;mandatory&lt;&#x2F;em&gt; to keep saying yuzu&#x27;s name — in every fork, in every downstream
package, in every distribution that carries any descendant of that code, forever,
as a condition of the licence.&lt;&#x2F;p&gt;
&lt;p&gt;Richard Stallman&#x27;s insight in 1989 was that you could take copyright — an
instrument designed to let owners restrict — and invert it, so that the same
enforcement machinery guaranteed freedom instead. Copyleft. It has been called a
hack, and it is one, and this is what it looks like when it works forty years
later on a battlefield nobody anticipated:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;They bought silence, and copyleft made silence illegal.&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;coda&quot;&gt;Coda&lt;a class=&quot;post-anchor&quot; href=&quot;#coda&quot; aria-label=&quot;Anchor link for: coda&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;None of the people in this story are activists. I don&#x27;t think any of them would
use that word about themselves, and I suspect several would be embarrassed by
this post.&lt;&#x2F;p&gt;
&lt;p&gt;They&#x27;re people who took on a small, permanent, unglamorous obligation to
strangers — &lt;em&gt;this will keep working&lt;&#x2F;em&gt; — and then kept discharging it under
conditions nobody signed up for. Bumping a version the day after a lawsuit.
Fixing an ARM compiler flag on a package with seventy-two hours to live. Tidying
the desktop entry of a project that no longer existed. Hunting a tarball across
archive.org and apologising for not having the disk space to test it. Backporting
the fix to stable three hours later so the people on the slow channel wouldn&#x27;t be
stranded either. Packaging an emulator on Christmas Day. Going back the next
evening to change one word, so the record would be true.&lt;&#x2F;p&gt;
&lt;p&gt;And, fourteen months after a death, noticing that the gravestones had gone
missing, and putting them back.&lt;&#x2F;p&gt;
&lt;p&gt;That&#x27;s the whole movement, really. Not the licences, though the licences are what
make it enforceable. Not the manifestos, though somebody had to write them.
Underneath all of it, free software is just an enormous number of people, most of
whom will never meet, each doing the small amount of work in front of them on the
shared understanding that &lt;strong&gt;things worth having are worth keeping, and keeping is
a thing you do repeatedly, forever, or not at all.&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;You can take down a repository. You can buy a settlement, or make a phone call
that ends a project in an afternoon, or file thirteen forms before the weekend
and remove thirteen years of work from the largest code host on Earth. All of
that has been done, and it worked, and there is no court anywhere that has said
otherwise.&lt;&#x2F;p&gt;
&lt;p&gt;But you cannot serve a takedown on everyone who kept a copy, because nobody knows
who they are, and that is the entire design. You cannot DMCA a hash that already
describes the thing you deleted. You cannot revoke a licence that requires your
victim&#x27;s name to be spoken by everyone who inherits their work.&lt;&#x2F;p&gt;
&lt;p&gt;And you cannot stop a volunteer, on a Monday afternoon in May, from spending
eighteen seconds making sure the dead are still named.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;2025-05-12 15:05:07  aliases: re-add entries for yuzu and citra&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Don&#x27;t panic. Keep a copy.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;&lt;em&gt;Every commit hash, date, and quotation in this post was read out of a local
nixpkgs clone with &lt;code&gt;git log&lt;&#x2F;code&gt; and &lt;code&gt;git show&lt;&#x2F;code&gt;. Nothing here came from a press
release. If you want to walk it yourself, &lt;code&gt;git log --all -i --grep=yuzu&lt;&#x2F;code&gt; is a
good place to start — and &lt;code&gt;git log -p -- pkgs&#x2F;top-level&#x2F;aliases.nix&lt;&#x2F;code&gt; is a
genuinely underrated way to spend an evening among the headstones.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>There Are 2 Hard Things in Computer Science, and Sean Bean Said None of Them</title>
          <pubDate>Sat, 15 Aug 2026 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://blog.s99.sh/blog/two-hard-things/</link>
          <guid>https://blog.s99.sh/blog/two-hard-things/</guid>
          <description xml:base="https://blog.s99.sh/blog/two-hard-things/">&lt;p&gt;I had the quote stuck in my head all week and could not place it. What I had
was roughly this:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;There are only 2 problems in programming. 1. Naming things. 2. Cache
invalidation. 3. Counting.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;And I was fairly sure it had been read to me in a grave British voice, by Sean
Bean, in Civilization V.&lt;&#x2F;p&gt;
&lt;p&gt;Every part of that is wrong, and the ways in which it is wrong turn out to be
more interesting than the quote.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-actual-line&quot;&gt;The actual line&lt;a class=&quot;post-anchor&quot; href=&quot;#the-actual-line&quot; aria-label=&quot;Anchor link for: the-actual-line&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;blockquote&gt;
&lt;p&gt;There are only two hard things in Computer Science: cache invalidation and
naming things.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Two things. Not three. The man who said it was &lt;strong&gt;Phil Karlton&lt;&#x2F;strong&gt;, and the reason
you have never seen a citation is that there isn&#x27;t one — he said it out loud, to
people, and then he died.&lt;&#x2F;p&gt;
&lt;p&gt;Karlton is not a household name but his résumé is the back half of the twentieth
century in computing: Xerox, Digital, Silicon Graphics, and finally Netscape,
where &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.karlton.org&#x2F;karlton&#x2F;&quot;&gt;his own home page&lt;&#x2F;a&gt; gives his title as
&lt;strong&gt;Principal Curmudgeon&lt;&#x2F;strong&gt;. X11. OpenGL. Debuggers. Mail user agents. He was in the
room for SSL 3.0 — you can still read him arguing about the transport layer on
the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;lists.w3.org&#x2F;Archives&#x2F;Public&#x2F;ietf-tls&#x2F;1996AprJun&#x2F;0125.html&quot;&gt;IETF TLS list in 1996&lt;&#x2F;a&gt;.
Every HTTPS connection you have ever made is downstream of meetings this man sat
in.&lt;&#x2F;p&gt;
&lt;p&gt;On 20 June 1997 he and his wife Jan were killed in a car accident while on
holiday in Italy. Their son David &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.karlton.org&#x2F;&quot;&gt;runs the site&lt;&#x2F;a&gt; that
is now the closest thing the quote has to a primary source.&lt;&#x2F;p&gt;
&lt;p&gt;So the most-repeated sentence in software engineering has no paper. It is
hearsay, all the way down.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-chain-of-custody&quot;&gt;The chain of custody&lt;a class=&quot;post-anchor&quot; href=&quot;#the-chain-of-custody&quot; aria-label=&quot;Anchor link for: the-chain-of-custody&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;It is genuinely worth laying out how thin the evidence is, because the quote is
treated as scripture:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;~1970, Carnegie Mellon.&lt;&#x2F;strong&gt; A commenter named Tom Bajzek says he heard Phil say
it before an intramural game, back when they both belonged to the Friday
Evening Volleyball Association. This is a guy on a blog, in 2017, recalling a
remark from forty-seven years earlier.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;~1996–97.&lt;&#x2F;strong&gt; Tim Bray hears it from Karlton at Netscape and later writes it
down on his blog — the earliest instance anyone has found online. Bray&#x27;s own
summary of his role is about as modest as it gets: &lt;em&gt;Phil said it, I reported
it, someone else added on the off-by-one.&lt;&#x2F;em&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;December 2005.&lt;&#x2F;strong&gt; Kellan Elliott-McCrea publishes a post whose title is just
the quote, which is roughly the moment it stops being a thing Netscape people
say and starts being a thing everyone says.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;July 2009.&lt;&#x2F;strong&gt; Martin Fowler gives it a
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;martinfowler.com&#x2F;bliki&#x2F;TwoHardThings.html&quot;&gt;bliki entry&lt;&#x2F;a&gt;, which is
canonisation.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;December 2017.&lt;&#x2F;strong&gt; David Karlton writes
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.karlton.org&#x2F;2017&#x2F;12&#x2F;naming-things-hard&#x2F;&quot;&gt;&lt;em&gt;Naming things is hard&lt;&#x2F;em&gt;&lt;&#x2F;a&gt;,
confirming his dad threw the line around more than once and that he&#x27;s fairly
confident it was original to him.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;That&#x27;s it. That&#x27;s the archive. The best corroboration for the volleyball story
is that Karlton&#x27;s own Netscape page, written by him, lists volleyball among his
hobbies — beer, single malts, rock climbing, roller hockey, volleyball. The
detail checks out even though the quote doesn&#x27;t.&lt;&#x2F;p&gt;
&lt;p&gt;There is something perfect about this. The industry&#x27;s favourite line about how
hard it is to &lt;em&gt;name&lt;&#x2F;em&gt; things has spent thirty years unable to reliably name its
own author.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-third-one&quot;&gt;The third one&lt;a class=&quot;post-anchor&quot; href=&quot;#the-third-one&quot; aria-label=&quot;Anchor link for: the-third-one&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;The version I had in my head — the one with the counting joke — is not Karlton&#x27;s
and was never meant to be mistaken for it. On New Year&#x27;s Day 2010, Leon
Bambrick — &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;secretgeek.net&#x2F;&quot;&gt;@secretGeek&lt;&#x2F;a&gt; — tweeted:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;There are 2 hard problems in computer science: cache invalidation, naming
things, and off-by-1 errors.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;The joke is not the third item. The joke is the &lt;strong&gt;2&lt;&#x2F;strong&gt;. The sentence promises two
problems and delivers three, so the list demonstrates the bug it is describing
while you are still reading it. It is a self-executing punchline, and it is
better than the original — which is why it has almost entirely replaced it in
casual use, and why my memory kept the shape of it while dropping every fact
attached.&lt;&#x2F;p&gt;
&lt;p&gt;My reconstruction had even mangled &lt;em&gt;that&lt;&#x2F;em&gt;: I remembered the payload (&quot;counting&quot;)
rather than the mechanism (&quot;off-by-one&quot;), which is what you get when a brain
compresses a joke down to its gist and throws away the executable part.&lt;&#x2F;p&gt;
&lt;p&gt;The format turned out to be extremely fertile. Mathias Verraes did the
distributed-systems port, and it may be the best of the lot:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;There are only two hard problems in distributed systems: 2. Exactly-once
delivery. 1. Guaranteed order of messages. 2. Exactly-once delivery.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Out-of-order &lt;em&gt;and&lt;&#x2F;em&gt; delivered twice. The sentence is the failure mode.&lt;&#x2F;p&gt;
&lt;p&gt;Fowler&#x27;s page also collects Phillip Scott Bowden&#x27;s — &lt;em&gt;there&#x27;s two hard problems
in computer science: we only have one joke and it&#x27;s not funny&lt;&#x2F;em&gt; — and Nat Pryce&#x27;s
observation that the sheer volume of variants is itself evidence that programming
may not be as easy as everyone claims.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;now-sean-bean&quot;&gt;Now, Sean Bean&lt;a class=&quot;post-anchor&quot; href=&quot;#now-sean-bean&quot; aria-label=&quot;Anchor link for: now-sean-bean&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Here is where my memory was most confidently wrong.&lt;&#x2F;p&gt;
&lt;p&gt;Civilization has read historical quotes at you since the beginning, and the
narrator is different in each game:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Game&lt;&#x2F;th&gt;&lt;th&gt;Narrator&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Civilization IV&lt;&#x2F;td&gt;&lt;td&gt;Leonard Nimoy&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Civilization V&lt;&#x2F;td&gt;&lt;td&gt;W. Morgan Sheppard&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Civilization VI&lt;&#x2F;td&gt;&lt;td&gt;Sean Bean&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Civilization VII&lt;&#x2F;td&gt;&lt;td&gt;Gwendoline Christie&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;Sean Bean is &lt;strong&gt;Civ VI&lt;&#x2F;strong&gt;. Civ V — the one I was sure about — was W. Morgan
Sheppard, the English character actor with the gravel in his voice, who did the
tech quotes and the intro and died in 2019. Civ VII, in 2025, went to Gwendoline
Christie, the first woman to narrate the series and the second Game of Thrones
alumnus in a row, which is a hiring pattern I respect.&lt;&#x2F;p&gt;
&lt;p&gt;And the Karlton quote is in none of them. As far as I can tell it has never
appeared in a Civilization game. What Sean Bean actually reads you when you
finish &lt;strong&gt;Computers&lt;&#x2F;strong&gt; in Civ VI is:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;To err is human, but to really foul things up you need a computer.
— &lt;em&gt;Paul R. Ehrlich&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;The good thing about computers is that they do what you tell them to do. The
bad news is that they do what you tell them to do.
— &lt;em&gt;Ted Nelson&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Same register. Same cadence. Same wry-aphorism-about-machines slot in the
sentence. If you were going to falsely remember a programming joke as having
been narrated to you over a tech-tree fanfare, these are exactly the neighbours
that would make it feel true.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-the-misremembering-is-on-topic&quot;&gt;Why the misremembering is on-topic&lt;a class=&quot;post-anchor&quot; href=&quot;#why-the-misremembering-is-on-topic&quot; aria-label=&quot;Anchor link for: why-the-misremembering-is-on-topic&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Two decades of Civilization trained a generation of people who work with
computers to receive pithy aphorisms in a serious British voice with strings
under it. The Karlton line fits that slot so precisely — right length, right
irony, right subject — that my brain filed it there and, when asked for a source,
returned the one it had lying around.&lt;&#x2F;p&gt;
&lt;p&gt;Which is, and I&#x27;m aware of how neat this is, a cache invalidation bug. The
aphorism was cached. Its provenance was evicted. Nothing signalled that the entry
had gone stale, so the lookup succeeded and returned confident garbage: &lt;em&gt;Sean
Bean, Civ 5&lt;&#x2F;em&gt;. No error. No miss. Just a plausible value served warm from a bad
line.&lt;&#x2F;p&gt;
&lt;p&gt;Human memory is a cache with an excellent hit rate, no TTL, and no invalidation
protocol whatsoever. It will hand you a wrong answer with exactly the same
subjective confidence as a right one, which is the whole reason the original
observation is any good: the hard problems aren&#x27;t the ones that announce
themselves by crashing.&lt;&#x2F;p&gt;
&lt;p&gt;So, corrected and with numbering intact:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;There are only two hard things in Computer Science: cache invalidation and
naming things.
— &lt;strong&gt;Phil Karlton&lt;&#x2F;strong&gt;, out loud, sometime between 1970 and 1997, to somebody who
was paying attention&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Nobody read it to you. It doesn&#x27;t need the voice.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;&lt;strong&gt;Sources:&lt;&#x2F;strong&gt; &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;martinfowler.com&#x2F;bliki&#x2F;TwoHardThings.html&quot;&gt;Martin Fowler, &lt;em&gt;Two Hard Things&lt;&#x2F;em&gt;&lt;&#x2F;a&gt; ·
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.karlton.org&#x2F;2017&#x2F;12&#x2F;naming-things-hard&#x2F;&quot;&gt;David Karlton, &lt;em&gt;Naming things is hard&lt;&#x2F;em&gt;&lt;&#x2F;a&gt; ·
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.karlton.org&#x2F;&quot;&gt;karlton.org&lt;&#x2F;a&gt; ·
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.karlton.org&#x2F;karlton&#x2F;&quot;&gt;Phil Karlton&#x27;s Netscape home page&lt;&#x2F;a&gt; ·
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;laughingmeme.org&#x2F;2005&#x2F;12&#x2F;23&#x2F;there-are-only-two-hard-things-in-computer-science-cache-invalidation-and-naming-things&#x2F;&quot;&gt;Kellan Elliott-McCrea (2005)&lt;&#x2F;a&gt; ·
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.pcgamer.com&#x2F;sean-bean-civ-6&#x2F;&quot;&gt;PC Gamer on Sean Bean and Civ 6&lt;&#x2F;a&gt; ·
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.pcgamer.com&#x2F;games&#x2F;strategy&#x2F;civilization-7-s-new-narrator-is-game-of-thrones-star-gwendoline-christie&#x2F;&quot;&gt;PC Gamer on Gwendoline Christie and Civ 7&lt;&#x2F;a&gt; ·
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;civilization.fandom.com&#x2F;wiki&#x2F;Computers_(Civ6)&quot;&gt;Civilization Wiki: Computers (Civ6)&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>I Put Claude Code in a Cgroup Jail Because It Kept Eating My Workstation</title>
          <pubDate>Fri, 31 Jul 2026 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://blog.s99.sh/blog/caging-claude-code/</link>
          <guid>https://blog.s99.sh/blog/caging-claude-code/</guid>
          <description xml:base="https://blog.s99.sh/blog/caging-claude-code/">&lt;p&gt;For two weeks, heracles — my main box, 128 GB of RAM, the machine I drive the
whole fleet from — kept hanging solid and rebooting itself out from under me.&lt;&#x2F;p&gt;
&lt;p&gt;Not a crash you get to watch. A &lt;em&gt;wedge&lt;&#x2F;em&gt;. One second I&#x27;m working across a dozen
terminals; the next the pointer won&#x27;t move, the fans spin up like a jet on the
apron, and every input I give the machine vanishes into a tarpit. Thirty seconds
of that and the hardware watchdog gives up, yanks the reset line, and I&#x27;m looking
at a login greeter with every tmux session, every browser tab, every train of
thought I had going — &lt;em&gt;gone&lt;&#x2F;em&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;It happened on the 13th. It happened again on the 26th. It happened on the 29th
while I sat there and watched it die. Losing a day&#x27;s context to a silent reboot
is a special kind of rage, the kind that makes you want to physically fight a
computer. So I stopped being angry at the symptom and went to find out what was
actually killing my machine.&lt;&#x2F;p&gt;
&lt;p&gt;This is that story. It has a villain, and the villain is JavaScript. Obviously
it&#x27;s JavaScript.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-autopsy&quot;&gt;The autopsy&lt;a class=&quot;post-anchor&quot; href=&quot;#the-autopsy&quot; aria-label=&quot;Anchor link for: the-autopsy&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Here&#x27;s the thing about a machine that wedges hard: its own logs are useless,
because the journal never gets flushed. heracles&#x27; on-disk journal just &lt;em&gt;ends&lt;&#x2F;em&gt;,
mid-line, at the moment of death. So years ago I set up netconsole — the kernel
screaming its dying words over UDP to a different box on the network before it
loses the ability to do anything at all. ares catches heracles&#x27; last breath.&lt;&#x2F;p&gt;
&lt;p&gt;And heracles&#x27; last words, every single time, were the same:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;oom-kill:constraint=CONSTRAINT_NONE,...,global_oom&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;A global out-of-memory. The kernel ran out of RAM, invoked the OOM killer, and
the OOM killer — in its infinite wisdom — started executing &lt;em&gt;whatever&lt;&#x2F;em&gt;. Not the
thing eating the memory. Just... things. wireplumber. My emacs daemon. The
accessibility bus. Tiny 30-megabyte desktop services, shot one after another,
each death freeing nothing, while the actual gluttons sat there untouched and the
box spiraled into a reclaim stall until the watchdog put it out of its misery.&lt;&#x2F;p&gt;
&lt;p&gt;So: what filled 128 gigabytes? I ranked the processes from the OOM dump. The
entire top of the list was one thing, over and over:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;7.36 GB  .claude-unwrapped&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;7.35 GB  .claude-unwrapped&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;7.35 GB  .claude-unwrapped&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   ...   (twenty of them, five to seven gigs each)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Twenty-odd Claude Code sessions, north of 100 GB combined. My AI assistant had
eaten my workstation.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;bun-pool-5-invoked-oom-killer&quot;&gt;&quot;Bun Pool 5 invoked oom-killer&quot;&lt;a class=&quot;post-anchor&quot; href=&quot;#bun-pool-5-invoked-oom-killer&quot; aria-label=&quot;Anchor link for: bun-pool-5-invoked-oom-killer&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Now, a detail that made me tilt my head. The thread that actually tripped the
final allocation was named &lt;code&gt;Bun Pool 5&lt;&#x2F;code&gt;. Bun. As in the JavaScript runtime.&lt;&#x2F;p&gt;
&lt;p&gt;I want to be precise about what I learned next, because it reframes everything.
Claude Code is not a Node script. It&#x27;s not a little CLI that shells out. Claude
Code, as shipped, is a &lt;strong&gt;257-megabyte single-file executable&lt;&#x2F;strong&gt; — the entire Bun
runtime (v1.4.0) with JavaScriptCore baked in, plus the whole application bundle,
&lt;code&gt;bun build --compile&lt;&#x2F;code&gt;&#x27;d into one enormous ELF binary. &lt;code&gt;Bun Pool 5&lt;&#x2F;code&gt; wasn&#x27;t some
side process. It was Claude Code itself. Those worker threads &lt;em&gt;are&lt;&#x2F;em&gt; the program.&lt;&#x2F;p&gt;
&lt;p&gt;Which means every one of those 7-gigabyte lines was a single chat session&#x27;s
JavaScript heap.&lt;&#x2F;p&gt;
&lt;p&gt;Seven gigabytes. For a terminal program. That mostly sits there waiting for me to
type.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-ratchet&quot;&gt;The ratchet&lt;a class=&quot;post-anchor&quot; href=&quot;#the-ratchet&quot; aria-label=&quot;Anchor link for: the-ratchet&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;It gets worse, and this is the part that made me put my head on the desk.&lt;&#x2F;p&gt;
&lt;p&gt;A few days later — after I&#x27;d started building the fix — I was mid-session,
sitting at a prompt Claude had drawn for me, deciding how to answer. Hadn&#x27;t typed
anything in a while. And the session got killed. So I went and looked at what it
had been holding at the moment of death. systemd logs it right there on the way
out:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;claude-1654491.scope: Consumed 25min CPU over 3h 18min wall clock time, 21.5G memory peak.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;Twenty-one and a half gigabytes.&lt;&#x2F;strong&gt; Sitting idle. At a menu. For three hours.
That one session was 83% of all the Claude memory on the box. A fresh session
starts at about 400 megabytes. This thing had ratcheted to &lt;em&gt;fifty times&lt;&#x2F;em&gt; that and
then just... held it, doing nothing, while I stared at a multiple-choice question.&lt;&#x2F;p&gt;
&lt;p&gt;And &quot;ratcheted&quot; is exactly the right word, because this is the specific technical
sin, and I want to name it precisely instead of just yelling &quot;JavaScript bad.&quot;&lt;&#x2F;p&gt;
&lt;p&gt;JavaScriptCore — like V8, like every one of these runtimes — allocates its heap
in a big arena. When garbage collection runs, it frees your dead objects &lt;em&gt;inside&lt;&#x2F;em&gt;
that arena. It reuses the space. What it does &lt;strong&gt;not&lt;&#x2F;strong&gt; do, essentially ever, is
give the pages back to the operating system. So the arena is a one-way ratchet.
The instant a session briefly needs 21 GB — you had it read a big file, or it
captured a huge build log into its context, or you resumed a long conversation
and it deserialized the whole transcript back into memory — the arena grows to
fit. And then it stays that big. Forever. For the entire life of the process. The
memory is &quot;free&quot; as far as the JS heap is concerned, and completely unavailable
as far as the rest of the box is concerned.&lt;&#x2F;p&gt;
&lt;p&gt;This is not a bug in Claude Code, exactly. It&#x27;s the water the entire ecosystem
swims in. It&#x27;s what you get when an industry decides that the correct runtime for
a systems tool — a thing that runs on my machine, in my resource budget, next to
my real work — is a browser engine with a garbage collector that treats &quot;give
memory back&quot; as somebody else&#x27;s problem. Ship it as a 257 MB binary, let the heap
grow without bound, and if the user&#x27;s workstation falls over under ten concurrent
copies, well, buy more RAM. I &lt;em&gt;have&lt;&#x2F;em&gt; 128 gigabytes. It wasn&#x27;t enough, because the
software was written by people for whom &quot;it works on my machine&quot; is the entire
quality bar.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;m not going to fix their memory management. I can&#x27;t; it&#x27;s compiled into a
quarter-gigabyte blob and there isn&#x27;t even a heap-limit flag exposed. But I run
NixOS, and I don&#x27;t have to &lt;em&gt;trust&lt;&#x2F;em&gt; software. I can put it in a cage.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;building-the-cage&quot;&gt;Building the cage&lt;a class=&quot;post-anchor&quot; href=&quot;#building-the-cage&quot; aria-label=&quot;Anchor link for: building-the-cage&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Here&#x27;s the good news, and the reason I&#x27;ll die on the NixOS hill: my entire fleet
is one declarative repo. Every machine, every service, every dotfile, and — as of
this saga — the exact resource jail every &lt;code&gt;claude&lt;&#x2F;code&gt; process runs inside. I don&#x27;t
SSH into a box and tweak things. I change the source and rebuild.&lt;&#x2F;p&gt;
&lt;p&gt;The first thing I tried was &lt;strong&gt;wrong&lt;&#x2F;strong&gt;, and I&#x27;m including it because pretending you
got it right on the first go is how blogs lie to you. I added zram — compressed
swap in RAM — figuring a swap cushion would let pressure build gracefully instead
of slamming into a wall. Reader, it made the next crash &lt;em&gt;worse&lt;&#x2F;em&gt;. When your
problem is a hot working set of live JavaScript heaps, compressing them into a
different part of the same RAM buys you nothing and costs you CPU. At the next
death, 56 GB of physical memory was consumed by zram&#x27;s own compressed pages,
&lt;em&gt;on top of&lt;&#x2F;em&gt; 60 GB of resident heaps, and the box thrashed itself to death faster
than before. I ripped it back out. Wrong tool. Moving on.&lt;&#x2F;p&gt;
&lt;p&gt;The fix that actually worked has three parts, and they&#x27;re all just cgroups —
the Linux kernel&#x27;s resource-accounting jails — wired up declaratively.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;One: every Claude session runs in its own systemd scope with a hard memory
cap.&lt;&#x2F;strong&gt; The wrapper launches &lt;code&gt;claude&lt;&#x2F;code&gt; inside a transient &lt;code&gt;--user&lt;&#x2F;code&gt; scope with
&lt;code&gt;MemoryMax=12G&lt;&#x2F;code&gt;. A single session that ratchets past 12 gigs now hits the wall of
its &lt;em&gt;own&lt;&#x2F;em&gt; cgroup and gets OOM-killed &lt;em&gt;inside its own jail&lt;&#x2F;em&gt; — the kernel kills that
one process tree and nothing else. The bloated session executes itself. Twelve
gigs is still obscene for a chat client, but it&#x27;s a number that lets ten of them
coexist on this box, and a session that blows through it was going to hurt me
anyway.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Two: all of those scopes live under one parent &lt;code&gt;claude.slice&lt;&#x2F;code&gt; with an aggregate
cap&lt;&#x2F;strong&gt; — &lt;code&gt;MemoryMax&lt;&#x2F;code&gt; at 66% of RAM. This is the one that actually addresses my
real failure mode, which was never &lt;em&gt;one&lt;&#x2F;em&gt; runaway. It was twenty reasonable-looking
sessions summing past 128 GB with no single one looking guilty. Now, all Claudes
&lt;em&gt;combined&lt;&#x2F;em&gt; are bounded. When they collectively push the ceiling, a kill fires
&lt;em&gt;inside the slice&lt;&#x2F;em&gt; — a Claude dies, never the compositor.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Three: I un-inverted the OOM priorities.&lt;&#x2F;strong&gt; This was an embarrassing bug of my
own making. I&#x27;d biased &lt;code&gt;claude&lt;&#x2F;code&gt; toward being the preferred kill victim... but I&#x27;d
set the bias too low. It turns out logind session services (wireplumber, emacs,
the a11y bus) carry a higher OOM-priority by default than the timid value I&#x27;d
picked, so the kernel would rather shoot a 30 MB audio daemon than a 7 GB Claude.
That&#x27;s &lt;em&gt;exactly backwards&lt;&#x2F;em&gt;, and it&#x27;s why the desktop kept dying instead of the
thing at fault. Fixed the numbers so a fat Claude is always first against the
wall.&lt;&#x2F;p&gt;
&lt;p&gt;Belt and suspenders on top: systemd-oomd watching memory-pressure (PSI) on the
user slice and killing the worst offender &lt;em&gt;from userspace&lt;&#x2F;em&gt; before the kernel&#x27;s
blunt global killer ever wakes up, plus earlyoom as a dumb backstop.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;did-it-work&quot;&gt;Did it work?&lt;a class=&quot;post-anchor&quot; href=&quot;#did-it-work&quot; aria-label=&quot;Anchor link for: did-it-work&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;The night I finished it, that idle 21.5 GB session I told you about got killed.
And here is the entire point of two weeks of work, in the logs:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;kernel global-OOM events this boot:   0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;systemd-oomd kill actions this boot:   1   -&amp;gt;  claude-1654491.scope&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;claude sessions still alive:           8&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;box reboots:                           0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;One&lt;&#x2F;strong&gt; session died. The one holding 21.5 gigabytes for no reason. The other
eight kept running. My desktop didn&#x27;t even flicker. The machine that had
hard-rebooted three times in two weeks took the exact same kind of memory
pressure and handled it as a routine event — killed one recoverable chat session,
which I brought right back with &lt;code&gt;claude --resume&lt;&#x2F;code&gt;, and stayed up. Uptime held.
Nothing got kicked to a greeter. I didn&#x27;t lose a day.&lt;&#x2F;p&gt;
&lt;p&gt;That&#x27;s the difference between &quot;the OS is a suggestion&quot; and &quot;the OS is in charge.&quot;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-part-where-i-make-it-a-graph&quot;&gt;The part where I make it a graph&lt;a class=&quot;post-anchor&quot; href=&quot;#the-part-where-i-make-it-a-graph&quot; aria-label=&quot;Anchor link for: the-part-where-i-make-it-a-graph&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;The last thing that bugged me: I only found the 21.5 GB session &lt;em&gt;after&lt;&#x2F;em&gt; it died,
in a scope-exit line. I was blind to the ratchet while it was happening. So I
wired Claude&#x27;s cgroups into the Telegraf → InfluxDB → Grafana stack that already
runs my fleet — every session&#x27;s live memory, its high-water mark, its throttling
events, sampled every 15 seconds, graphed next to everything else. Plus a little
journal-scraper that turns every session teardown into an event so I can see
&lt;em&gt;how&lt;&#x2F;em&gt; each one ended — clean exit, self-OOM&#x27;d on its own cap, or reaped by oomd —
and backfills the peak history from those same scope-exit lines.&lt;&#x2F;p&gt;
&lt;p&gt;Now I can watch the ratchet climb in real time. When some future Claude session
starts marching toward 12 gigs while sitting at a prompt, I&#x27;ll see the line go up
and to the right, and I&#x27;ll know exactly which JavaScript heap to be furious at.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-lesson&quot;&gt;The lesson&lt;a class=&quot;post-anchor&quot; href=&quot;#the-lesson&quot; aria-label=&quot;Anchor link for: the-lesson&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;You are not going to fix the memory management of software you didn&#x27;t write,
especially not when it&#x27;s shipped as a quarter-gigabyte compiled blob by an
ecosystem that considers releasing memory back to the OS a quaint premodern
concern. That fight is unwinnable and it&#x27;s not yours.&lt;&#x2F;p&gt;
&lt;p&gt;But you don&#x27;t have to win it. The kernel has had the tools to contain a greedy
process since forever — cgroups, memory limits, OOM scoring, PSI. The tragedy is
how few people wire them up, because doing it by hand is fiddly and doesn&#x27;t
survive a reboot. That&#x27;s the whole reason I build my machines the way I do:
declaratively, in one repo, where &quot;every &lt;code&gt;claude&lt;&#x2F;code&gt; runs in a 12-gig jail under a
slice capped at two-thirds of RAM, and here&#x27;s the exact OOM priority of every
service&quot; is fifteen lines of Nix that are &lt;em&gt;true on every boot forever.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I can&#x27;t make the JavaScript behave. But I can build a cage strong enough that it
doesn&#x27;t matter, and I can prove the cage holds, and I can graph the animal
throwing itself against the bars.&lt;&#x2F;p&gt;
&lt;p&gt;Now if you&#x27;ll excuse me, I have a chat session at 11 gigs and climbing.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Booting Back Out of a Bad Timeshift Snapshot (Pop!_OS + BTRFS)</title>
          <pubDate>Fri, 26 Jun 2026 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://blog.s99.sh/blog/btrfs-timeshift-recovery/</link>
          <guid>https://blog.s99.sh/blog/btrfs-timeshift-recovery/</guid>
          <description xml:base="https://blog.s99.sh/blog/btrfs-timeshift-recovery/">&lt;p&gt;I upgraded a Pop!_OS box from 22.04 LTS to 24.04 LTS, hit a snag, and reached
for Timeshift to &quot;restore&quot; an older snapshot. It booted — into the &lt;em&gt;old&lt;&#x2F;em&gt; system
— and then every subsequent boot dropped me into the &lt;strong&gt;systemd emergency
shell&lt;&#x2F;strong&gt;. &lt;code&gt;systemd-modules-load.service&lt;&#x2F;code&gt; was failing with &lt;em&gt;&quot;Failed to look up
module alias … Function not implemented&quot;&lt;&#x2F;em&gt; (ENOSYS), &lt;code&gt;vfat&lt;&#x2F;code&gt; wouldn&#x27;t load, and so
&lt;code&gt;&#x2F;boot&#x2F;efi&lt;&#x2F;code&gt; couldn&#x27;t mount. One illness, several symptoms.&lt;&#x2F;p&gt;
&lt;p&gt;The root cause: Timeshift&#x27;s BTRFS restore doesn&#x27;t roll back in place — it
&lt;em&gt;swaps subvolumes&lt;&#x2F;em&gt;, so the old snapshot became my live root (&lt;code&gt;@&lt;&#x2F;code&gt;). The booted
system&#x27;s &lt;code&gt;&#x2F;lib&#x2F;modules&lt;&#x2F;code&gt; no longer matched the running kernel, which is why module
loading died and the FAT EFI partition couldn&#x27;t be mounted.&lt;&#x2F;p&gt;
&lt;p&gt;Below is the full recovery, &lt;strong&gt;in fish-shell syntax&lt;&#x2F;strong&gt;, from the emergency shell
back to a clean 24.04 boot.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Shell note:&lt;&#x2F;strong&gt; the systemd emergency shell is usually &lt;code&gt;bash&lt;&#x2F;code&gt;&#x2F;&lt;code&gt;sh&lt;&#x2F;code&gt;. To use
these exact commands, start fish first with &lt;code&gt;fish&lt;&#x2F;code&gt; (if the binary is present).
The only fish-specific bits are variable handling (&lt;code&gt;set NAME value&lt;&#x2F;code&gt; instead of
&lt;code&gt;NAME=value&lt;&#x2F;code&gt;, and command substitution with &lt;code&gt;(...)&lt;&#x2F;code&gt; instead of &lt;code&gt;$(...)&lt;&#x2F;code&gt;);
everything else is identical to bash.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;how-btrfs-chooses-what-to-boot&quot;&gt;How BTRFS &quot;chooses&quot; what to boot&lt;a class=&quot;post-anchor&quot; href=&quot;#how-btrfs-chooses-what-to-boot&quot; aria-label=&quot;Anchor link for: how-btrfs-chooses-what-to-boot&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;BTRFS doesn&#x27;t choose — the bootloader and mount options do:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;The GRUB kernel line carries &lt;code&gt;root=UUID=&amp;lt;fs-uuid&amp;gt; ... rootflags=subvol=@&lt;&#x2F;code&gt;.
That &lt;code&gt;subvol=@&lt;&#x2F;code&gt; selects which subvolume becomes &lt;code&gt;&#x2F;&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;With no &lt;code&gt;subvol=&lt;&#x2F;code&gt;, BTRFS falls back to the filesystem&#x27;s &lt;strong&gt;default subvolume&lt;&#x2F;strong&gt;
(&lt;code&gt;btrfs subvolume get-default&lt;&#x2F;code&gt;, normally subvolid 5 = the top level).&lt;&#x2F;li&gt;
&lt;li&gt;After root mounts, the &lt;code&gt;&#x2F;etc&#x2F;fstab&lt;&#x2F;code&gt; &lt;em&gt;inside that subvolume&lt;&#x2F;em&gt; re-asserts
&lt;code&gt;subvol=@&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;On Ubuntu&#x2F;Pop the live system is the subvolume named &lt;strong&gt;&lt;code&gt;@&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; (home is &lt;code&gt;@home&lt;&#x2F;code&gt;).
The fix is to put your good 24.04 system back at &lt;code&gt;@&lt;&#x2F;code&gt; so the existing &lt;code&gt;subvol=@&lt;&#x2F;code&gt;
boot entry lands on the right system. Timeshift snapshots are &lt;strong&gt;read-only&lt;&#x2F;strong&gt;, so
you can&#x27;t boot one directly — you create a fresh &lt;em&gt;writable&lt;&#x2F;em&gt; &lt;code&gt;@&lt;&#x2F;code&gt; from the
snapshot.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;step-1-identify-the-btrfs-device&quot;&gt;Step 1 — Identify the BTRFS device&lt;a class=&quot;post-anchor&quot; href=&quot;#step-1-identify-the-btrfs-device&quot; aria-label=&quot;Anchor link for: step-1-identify-the-btrfs-device&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;fish&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-function&quot;&gt;btrfs&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; filesystem show&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-function&quot;&gt;lsblk&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; -f&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Note the device for your root filesystem (e.g. &lt;code&gt;&#x2F;dev&#x2F;nvme0n1p3&lt;&#x2F;code&gt;). If it&#x27;s
LUKS-encrypted, unlock it first:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;fish&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-function&quot;&gt;cryptsetup&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; luksOpen &#x2F;dev&#x2F;nvme0n1pX cryptroot   &lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;# then use &#x2F;dev&#x2F;mapper&#x2F;cryptroot&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;step-2-mount-the-top-level-subvolume-subvolid-5&quot;&gt;Step 2 — Mount the top-level subvolume (subvolid=5)&lt;a class=&quot;post-anchor&quot; href=&quot;#step-2-mount-the-top-level-subvolume-subvolid-5&quot; aria-label=&quot;Anchor link for: step-2-mount-the-top-level-subvolume-subvolid-5&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;This exposes every subvolume, not just the currently-booted one.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;fish&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-function&quot;&gt;mkdir&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; -p &#x2F;mnt&#x2F;top&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-function&quot;&gt;mount&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; -o subvolid=5 &#x2F;dev&#x2F;nvme0n1p3 &#x2F;mnt&#x2F;top&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-function&quot;&gt;ls&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; -la &#x2F;mnt&#x2F;top&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;step-3-list-all-subvolumes&quot;&gt;Step 3 — List all subvolumes&lt;a class=&quot;post-anchor&quot; href=&quot;#step-3-list-all-subvolumes&quot; aria-label=&quot;Anchor link for: step-3-list-all-subvolumes&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;fish&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-function&quot;&gt;btrfs&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; subvolume list &#x2F;mnt&#x2F;top&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You&#x27;ll see your live &lt;code&gt;@&lt;&#x2F;code&gt; and &lt;code&gt;@home&lt;&#x2F;code&gt;, plus Timeshift snapshots under
&lt;code&gt;timeshift-btrfs&#x2F;snapshots&#x2F;&amp;lt;timestamp&amp;gt;&#x2F;@&lt;&#x2F;code&gt; and &lt;code&gt;...&#x2F;@home&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;step-4-pick-the-snapshot-and-verify-it-s-the-24-04-system&quot;&gt;Step 4 — Pick the snapshot and VERIFY it&#x27;s the 24.04 system&lt;a class=&quot;post-anchor&quot; href=&quot;#step-4-pick-the-snapshot-and-verify-it-s-the-24-04-system&quot; aria-label=&quot;Anchor link for: step-4-pick-the-snapshot-and-verify-it-s-the-24-04-system&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Don&#x27;t trust names — check the contents before changing anything. In fish, set the
variable with &lt;code&gt;set&lt;&#x2F;code&gt; and use &lt;code&gt;(...)&lt;&#x2F;code&gt; for any command substitution:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;fish&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-function&quot;&gt;ls&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; &#x2F;mnt&#x2F;top&#x2F;timeshift-btrfs&#x2F;snapshots&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-function&quot;&gt;set&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; SNAP &#x2F;mnt&#x2F;top&#x2F;timeshift-btrfs&#x2F;snapshots&#x2F;2026-06-19_XX-XX-XX   &lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;# your real timestamp&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-function&quot;&gt;grep&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-punctuation z-definition z-variable&quot;&gt; VERSION= $SNAP&#x2F;@&#x2F;etc&#x2F;os-release      &lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;# expect VERSION=&amp;quot;24.04...&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-function&quot;&gt;ls&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-source&quot;&gt; $SNAP&#x2F;@&#x2F;lib&#x2F;modules                    &lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;# expect a 24.x kernel directory&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Or loop over every candidate subvolume at once (fish &lt;code&gt;for ... end&lt;&#x2F;code&gt;):&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;fish&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;for&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; s &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword&quot;&gt;in&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; &#x2F;mnt&#x2F;top&#x2F;@&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;*&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-function&quot;&gt;    echo&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;=== &lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-variable&quot;&gt;$s&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-punctuation z-definition z-string&quot;&gt; ===&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-function&quot;&gt;    grep&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-punctuation z-definition z-variable&quot;&gt; VERSION= $s&#x2F;etc&#x2F;os-release&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-function&quot;&gt;    ls&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-source&quot;&gt; $s&#x2F;lib&#x2F;modules&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;end&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Proceed only once you&#x27;ve confirmed which subvolume is the 24.04 system.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;step-5-move-the-broken-subvolumes-aside-rename-never-delete&quot;&gt;Step 5 — Move the broken subvolumes aside (rename, never delete)&lt;a class=&quot;post-anchor&quot; href=&quot;#step-5-move-the-broken-subvolumes-aside-rename-never-delete&quot; aria-label=&quot;Anchor link for: step-5-move-the-broken-subvolumes-aside-rename-never-delete&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;fish&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-function&quot;&gt;cd&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; &#x2F;mnt&#x2F;top&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-function&quot;&gt;mv&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; @      @broken-2204&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-function&quot;&gt;mv&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; @home  @home-broken-2204&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Keeping them (renamed) makes this whole operation reversible.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;step-6-create-new-writable-subvolumes-from-the-snapshot&quot;&gt;Step 6 — Create new WRITABLE subvolumes from the snapshot&lt;a class=&quot;post-anchor&quot; href=&quot;#step-6-create-new-writable-subvolumes-from-the-snapshot&quot; aria-label=&quot;Anchor link for: step-6-create-new-writable-subvolumes-from-the-snapshot&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;fish&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-function&quot;&gt;btrfs&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-punctuation z-definition z-variable&quot;&gt; subvolume snapshot $SNAP&#x2F;@      &#x2F;mnt&#x2F;top&#x2F;@&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-function&quot;&gt;btrfs&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-punctuation z-definition z-variable&quot;&gt; subvolume snapshot $SNAP&#x2F;@home  &#x2F;mnt&#x2F;top&#x2F;@home&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;No &lt;code&gt;-r&lt;&#x2F;code&gt; flag → these are read-write, which is what a root filesystem requires.
The original snapshots stay untouched as backups.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note on &lt;code&gt;@home&lt;&#x2F;code&gt;:&lt;&#x2F;strong&gt; only restore it if the snapshot&#x27;s home is the version you
want. If you&#x27;ve created nothing in &lt;code&gt;&#x2F;home&lt;&#x2F;code&gt; since the breakage, restoring it is
fine. If unsure, skip the &lt;code&gt;@home&lt;&#x2F;code&gt; lines and keep your current &lt;code&gt;@home&lt;&#x2F;code&gt; — the
system will still boot.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;step-7-sanity-check-the-mount-options&quot;&gt;Step 7 — Sanity-check the mount options&lt;a class=&quot;post-anchor&quot; href=&quot;#step-7-sanity-check-the-mount-options&quot; aria-label=&quot;Anchor link for: step-7-sanity-check-the-mount-options&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;The new &lt;code&gt;@&lt;&#x2F;code&gt; came from a system that already used &lt;code&gt;subvol=@&lt;&#x2F;code&gt;, so its &lt;code&gt;&#x2F;etc&#x2F;fstab&lt;&#x2F;code&gt;
and the GRUB entry should already agree. Quick confirm:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;fish&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-function&quot;&gt;grep&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; -E&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;#39;subvol&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; &#x2F;mnt&#x2F;top&#x2F;@&#x2F;etc&#x2F;fstab&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;step-8-reboot&quot;&gt;Step 8 — Reboot&lt;a class=&quot;post-anchor&quot; href=&quot;#step-8-reboot&quot; aria-label=&quot;Anchor link for: step-8-reboot&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;fish&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-function&quot;&gt;cd&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; &#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-function&quot;&gt;umount&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; &#x2F;mnt&#x2F;top&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-function&quot;&gt;reboot&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;GRUB still points at &lt;code&gt;subvol=@&lt;&#x2F;code&gt;, but &lt;code&gt;@&lt;&#x2F;code&gt; is now your real 24.04 root with a
kernel that matches its &lt;code&gt;&#x2F;lib&#x2F;modules&lt;&#x2F;code&gt;. Module loading works → &lt;code&gt;vfat&lt;&#x2F;code&gt; loads →
&lt;code&gt;&#x2F;boot&#x2F;efi&lt;&#x2F;code&gt; mounts → boot completes.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;step-9-confirm-success-and-clean-up&quot;&gt;Step 9 — Confirm success and clean up&lt;a class=&quot;post-anchor&quot; href=&quot;#step-9-confirm-success-and-clean-up&quot; aria-label=&quot;Anchor link for: step-9-confirm-success-and-clean-up&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;After a clean graphical&#x2F;login boot:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;fish&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-function&quot;&gt;grep&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; VERSION &#x2F;etc&#x2F;os-release     &lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;# 24.04&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-function&quot;&gt;systemctl&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; --failed&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;               # should be empty&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-function&quot;&gt;findmnt&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; &#x2F;boot&#x2F;efi                &lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;# mounted&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-function&quot;&gt;uname&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; -r; &lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function&quot;&gt;ls&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; &#x2F;lib&#x2F;modules        &lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;# kernel matches a modules dir&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Once you&#x27;re confident, delete the leftovers:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;fish&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-function&quot;&gt;sudo&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; btrfs subvolume delete &#x2F;@broken-2204&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-support z-function&quot;&gt;sudo&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; btrfs subvolume delete &#x2F;@home-broken-2204&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;(Mount subvolid=5 again if you need the top-level path to reach them.)&lt;&#x2F;p&gt;
&lt;h2 id=&quot;troubleshooting&quot;&gt;Troubleshooting&lt;a class=&quot;post-anchor&quot; href=&quot;#troubleshooting&quot; aria-label=&quot;Anchor link for: troubleshooting&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Drops right back to emergency:&lt;&#x2F;strong&gt; a unit is still failing. Run
&lt;code&gt;systemctl --failed&lt;&#x2F;code&gt; and &lt;code&gt;journalctl -b -p err&lt;&#x2F;code&gt;. A failed &lt;code&gt;*.mount&lt;&#x2F;code&gt; usually
means a stale UUID&#x2F;PARTUUID in &lt;code&gt;&#x2F;etc&#x2F;fstab&lt;&#x2F;code&gt; — compare against &lt;code&gt;blkid&lt;&#x2F;code&gt;. Add
&lt;code&gt;nofail&lt;&#x2F;code&gt; to non-critical mounts (like &lt;code&gt;&#x2F;boot&#x2F;efi&lt;&#x2F;code&gt;, &lt;code&gt;&#x2F;recovery&lt;&#x2F;code&gt;) so a missing
mount can&#x27;t abort the whole boot.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&quot;Function not implemented&quot; on modprobe:&lt;&#x2F;strong&gt; confirms a kernel&#x2F;modules mismatch
— i.e. you&#x27;re still on the wrong subvolume. Re-check Step 4.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;GRUB itself is broken after the swap:&lt;&#x2F;strong&gt; boot a live USB, mount the top-level,
&lt;code&gt;mount --bind&lt;&#x2F;code&gt; &#x2F;dev &#x2F;proc &#x2F;sys, chroot into &lt;code&gt;@&lt;&#x2F;code&gt;, then
&lt;code&gt;update-grub &amp;amp;&amp;amp; grub-install&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Never run &lt;code&gt;btrfs check --repair&lt;&#x2F;code&gt; first&lt;&#x2F;strong&gt; — use &lt;code&gt;btrfs check --readonly&lt;&#x2F;code&gt; to
diagnose; &lt;code&gt;--repair&lt;&#x2F;code&gt; can worsen damage.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;&lt;em&gt;Recovery complete — you&#x27;re back on 24.04.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Two Family Trees: systemd Targets and Slices</title>
          <pubDate>Thu, 25 Jun 2026 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://blog.s99.sh/blog/systemd-targets-vs-slices/</link>
          <guid>https://blog.s99.sh/blog/systemd-targets-vs-slices/</guid>
          <description xml:base="https://blog.s99.sh/blog/systemd-targets-vs-slices/">&lt;p&gt;I went looking for a way to &lt;em&gt;group&lt;&#x2F;em&gt; a handful of my own systemd units — my
sync timers, a mail fetcher, a couple of personal daemons — so I could see them
and operate on them as a set instead of squinting at the full &lt;code&gt;systemctl&lt;&#x2F;code&gt;
output. Two answers kept coming up: &lt;strong&gt;targets&lt;&#x2F;strong&gt; and &lt;strong&gt;slices&lt;&#x2F;strong&gt;. They both
&quot;group units.&quot; They both show up in the same listings. And for an embarrassing
minute I couldn&#x27;t have told you the difference between them.&lt;&#x2F;p&gt;
&lt;p&gt;So I went and read the history. It turns out the reason they feel like
overlapping tools is an illusion of proximity: they live next to each other in
&lt;code&gt;systemctl&lt;&#x2F;code&gt; today, but they were &lt;em&gt;born in different decades, at different
companies, to solve completely unrelated problems.&lt;&#x2F;em&gt; Targets are the descendants
of &lt;strong&gt;init runlevels&lt;&#x2F;strong&gt;. Slices are the descendants of &lt;strong&gt;control groups&lt;&#x2F;strong&gt;. They
only look like siblings because systemd dresses every unit type in the same
uniform.&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s the one sentence to hold onto before we dig in:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A target is a node in a dependency graph. A slice is a node in a cgroup
tree.&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;One answers &lt;em&gt;&quot;what should be running, and in what order?&quot;&lt;&#x2F;em&gt; The other answers
&lt;em&gt;&quot;whose resources are these, and what are the limits?&quot;&lt;&#x2F;em&gt; Those are orthogonal
questions, and once you see that, the whole model snaps into focus.&lt;&#x2F;p&gt;
&lt;p&gt;Let me trace each family tree.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;family-tree-1-runlevels-targets&quot;&gt;Family tree #1: runlevels → targets&lt;a class=&quot;post-anchor&quot; href=&quot;#family-tree-1-runlevels-targets&quot; aria-label=&quot;Anchor link for: family-tree-1-runlevels-targets&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;the-1980s-a-runlevel-is-a-state&quot;&gt;The 1980s: a runlevel is a &lt;em&gt;state&lt;&#x2F;em&gt;&lt;a class=&quot;post-anchor&quot; href=&quot;#the-1980s-a-runlevel-is-a-state&quot; aria-label=&quot;Anchor link for: the-1980s-a-runlevel-is-a-state&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;The story starts with &lt;strong&gt;SysV init&lt;&#x2F;strong&gt;, the init system that came out of AT&amp;amp;T&#x27;s
UNIX System III in the early 1980s and carried into System V (hence &quot;SysV&quot;). Its
central concept was the &lt;strong&gt;runlevel&lt;&#x2F;strong&gt;: a named configuration of the machine — a
&lt;em&gt;state&lt;&#x2F;em&gt; you transition the system &lt;em&gt;into&lt;&#x2F;em&gt;, in which only a selected group of
processes is allowed to run. The mapping of &quot;what runs at which level&quot; lived in
&lt;code&gt;&#x2F;etc&#x2F;inittab&lt;&#x2F;code&gt;, and init&#x27;s entire job was to drag the machine into the requested
state and keep the right processes alive.&lt;&#x2F;p&gt;
&lt;p&gt;There were eight of them, 0–6 plus &lt;code&gt;S&lt;&#x2F;code&gt;, and the ones at the edges were
universal:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;0   halt &#x2F; poweroff&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;1   single-user (maintenance)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;S   single-user (≈ 1)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;6   reboot&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The interesting part is the middle, &lt;strong&gt;2–5&lt;&#x2F;strong&gt;, because that&#x27;s where the distros
quietly disagreed — a fact that bites people to this day:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Red Hat &#x2F; Fedora:&lt;&#x2F;strong&gt; runlevel &lt;strong&gt;3&lt;&#x2F;strong&gt; was full multi-user with networking but a
&lt;em&gt;text&lt;&#x2F;em&gt; console; runlevel &lt;strong&gt;5&lt;&#x2F;strong&gt; added the graphical X11 login. The famous
&quot;boot to a GUI vs boot to a console&quot; toggle was literally &lt;code&gt;3&lt;&#x2F;code&gt; vs &lt;code&gt;5&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Debian &#x2F; Ubuntu:&lt;&#x2F;strong&gt; runlevels &lt;strong&gt;2 through 5 were all identical&lt;&#x2F;strong&gt; — full
multi-user mode, no text-vs-graphical distinction at all.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The mental model that matters: a runlevel is a &lt;strong&gt;state machine position&lt;&#x2F;strong&gt;. You
are &lt;em&gt;at&lt;&#x2F;em&gt; a runlevel; you &lt;em&gt;switch to&lt;&#x2F;em&gt; another. It&#x27;s about the global condition of
the box, and which bundle of services that condition implies.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;the-init-wars-2005-2010&quot;&gt;The init wars (2005–2010)&lt;a class=&quot;post-anchor&quot; href=&quot;#the-init-wars-2005-2010&quot; aria-label=&quot;Anchor link for: the-init-wars-2005-2010&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;SysV init had an Achilles&#x27; heel: it ran service scripts &lt;strong&gt;sequentially&lt;&#x2F;strong&gt;, in a
numbered order, each one blocking the next. On a machine with dozens of
services, boot was a slow conga line. Two projects attacked this before systemd
existed:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;launchd&lt;&#x2F;strong&gt; (Apple, 2005, shipped in Mac OS X 10.4 &quot;Tiger&quot;). One daemon to
replace init, cron, inetd, and friends — and crucially, it started services
&lt;strong&gt;on demand via socket activation&lt;&#x2F;strong&gt; rather than eagerly in a fixed order.
Apple relicensed it under Apache 2.0 in 2006 to encourage adoption elsewhere.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Upstart&lt;&#x2F;strong&gt; (Canonical, 2006, by Scott James Remnant, first in Ubuntu 6.10).
An &lt;strong&gt;event-based&lt;&#x2F;strong&gt; init: services reacted to emitted events (&quot;a disk
appeared&quot;, &quot;a filesystem mounted&quot;) instead of marching through runlevel
scripts.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Both were saying the same thing in different dialects: &lt;em&gt;the rigid, sequential
runlevel model is the wrong abstraction.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;h3 id=&quot;april-30-2010-rethinking-pid-1&quot;&gt;April 30, 2010: &quot;Rethinking PID 1&quot;&lt;a class=&quot;post-anchor&quot; href=&quot;#april-30-2010-rethinking-pid-1&quot; aria-label=&quot;Anchor link for: april-30-2010-rethinking-pid-1&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;Then Lennart Poettering (at Red Hat), with design work from Kay Sievers (then at
Novell), published an essay titled
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;0pointer.de&#x2F;blog&#x2F;projects&#x2F;systemd.html&quot;&gt;&lt;strong&gt;Rethinking PID 1&lt;&#x2F;strong&gt;&lt;&#x2F;a&gt; and
announced systemd. The pitch: aggressive &lt;strong&gt;parallelization&lt;&#x2F;strong&gt; of boot, &lt;strong&gt;socket
activation&lt;&#x2F;strong&gt; pushed to its limit (create all the listening sockets first, hand
them to services, let everything start at once without hand-written ordering),
&lt;strong&gt;D-Bus activation&lt;&#x2F;strong&gt;, &lt;strong&gt;cgroups to track processes&lt;&#x2F;strong&gt; (more on that in the second
family tree), and — the relevant bit here — &lt;strong&gt;targets to replace runlevels.&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;A &lt;strong&gt;target&lt;&#x2F;strong&gt; is systemd&#x27;s answer to &quot;what is a runlevel, really?&quot; Stripped of
the state-machine framing, a runlevel was just &lt;em&gt;a name for a set of services
that should be up together.&lt;&#x2F;em&gt; So systemd made that explicit. From the
&lt;code&gt;systemd.target(5)&lt;&#x2F;code&gt; man page:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Target units are used to &lt;strong&gt;group units&lt;&#x2F;strong&gt; and to set &lt;strong&gt;synchronization points&lt;&#x2F;strong&gt;
for ordering dependencies with other unit files. […] Target units do not offer
any additional functionality on top of the generic functionality provided by
units. They merely group units. […] Target units provide a more flexible
replacement for SysV runlevels in the classic SysV init system.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Read that twice, because it&#x27;s the whole thing: a target &lt;strong&gt;has no processes of
its own&lt;&#x2F;strong&gt;, &lt;strong&gt;does nothing on its own&lt;&#x2F;strong&gt;, and exists purely as a &lt;em&gt;named node in
the dependency graph&lt;&#x2F;em&gt; that other units can point at. The old runlevels are still
there as compatibility aliases — and the mapping is hard-coded right in
systemd&#x27;s build files (&lt;code&gt;units&#x2F;meson.build&lt;&#x2F;code&gt;):&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;runlevel0.target → poweroff.target&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;runlevel1.target → rescue.target&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;runlevel2&#x2F;3&#x2F;4.target → multi-user.target&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;runlevel5.target → graphical.target&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;runlevel6.target → reboot.target&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;systemd&#x27;s own FAQ doesn&#x27;t mince words: &lt;em&gt;&quot;The concept of runlevels is obsolete. A
set of target units are exposed that carry similar semantics, e.g. runlevel 5 -&amp;gt;
graphical.target.&quot;&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Targets also do the job runlevels never could: they&#x27;re &lt;strong&gt;synchronization
points&lt;&#x2F;strong&gt; in a real dependency graph. Boot is a chain of them —
&lt;code&gt;sysinit.target&lt;&#x2F;code&gt; → &lt;code&gt;basic.target&lt;&#x2F;code&gt; → &lt;code&gt;multi-user.target&lt;&#x2F;code&gt; → &lt;code&gt;graphical.target&lt;&#x2F;code&gt; —
and units hook themselves in with &lt;code&gt;WantedBy=&lt;&#x2F;code&gt;, &lt;code&gt;Wants=&lt;&#x2F;code&gt;, &lt;code&gt;Requires=&lt;&#x2F;code&gt;, &lt;code&gt;After=&lt;&#x2F;code&gt;,
&lt;code&gt;Before=&lt;&#x2F;code&gt;. That last point matters for later: &lt;strong&gt;a single unit can be &lt;code&gt;WantedBy&lt;&#x2F;code&gt;
many targets at once.&lt;&#x2F;strong&gt; Membership in the dependency graph is many-to-many.&lt;&#x2F;p&gt;
&lt;p&gt;You can see the graph directly:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;systemctl&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; list-units --type=target&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;          # what&amp;#39;s active&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;systemctl&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; list-dependencies graphical.target&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt; # the tree under a target&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;So: &lt;strong&gt;runlevels → targets.&lt;&#x2F;strong&gt; The lineage is &lt;em&gt;init, boot, state, ordering.&lt;&#x2F;em&gt;
Nothing about resources.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;family-tree-2-cgroups-slices&quot;&gt;Family tree #2: cgroups → slices&lt;a class=&quot;post-anchor&quot; href=&quot;#family-tree-2-cgroups-slices&quot; aria-label=&quot;Anchor link for: family-tree-2-cgroups-slices&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Now rewind to a completely different room.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;2006-2008-google-needs-to-box-in-processes&quot;&gt;2006–2008: Google needs to box in processes&lt;a class=&quot;post-anchor&quot; href=&quot;#2006-2008-google-needs-to-box-in-processes&quot; aria-label=&quot;Anchor link for: 2006-2008-google-needs-to-box-in-processes&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;Around 2006, engineers at Google — &lt;strong&gt;Paul Menage&lt;&#x2F;strong&gt; and &lt;strong&gt;Rohit Seth&lt;&#x2F;strong&gt; — were
wrestling with enormous shared Linux clusters and built a kernel feature they
called &lt;strong&gt;&quot;process containers.&quot;&lt;&#x2F;strong&gt; The goal had nothing to do with booting: it was
about &lt;strong&gt;hierarchically grouping processes&lt;&#x2F;strong&gt; so you could &lt;strong&gt;account for,
&lt;strong&gt;limit&lt;&#x2F;strong&gt;, prioritize, and isolate&lt;&#x2F;strong&gt; their resource usage — CPU, memory, block
I&#x2F;O, and so on, each handled by a per-resource &quot;controller.&quot;&lt;&#x2F;p&gt;
&lt;p&gt;To avoid collision with the loaded word &quot;container,&quot; it was renamed &lt;strong&gt;control
groups&lt;&#x2F;strong&gt; — &lt;strong&gt;cgroups&lt;&#x2F;strong&gt; — in late 2007, and &lt;strong&gt;merged into the Linux kernel in
2.6.24 (January 2008).&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This is a &lt;em&gt;kernel resource-management&lt;&#x2F;em&gt; primitive. It knows nothing about
services, runlevels, or boot order. It just lets you draw a box around a set of
processes and say &quot;this box gets at most 2 GB of RAM and 10% of one CPU.&quot;&lt;&#x2F;p&gt;
&lt;h3 id=&quot;why-systemd-cared-processes-that-won-t-stay-caught&quot;&gt;Why systemd cared: processes that won&#x27;t stay caught&lt;a class=&quot;post-anchor&quot; href=&quot;#why-systemd-cared-processes-that-won-t-stay-caught&quot; aria-label=&quot;Anchor link for: why-systemd-cared-processes-that-won-t-stay-caught&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;Here&#x27;s the bridge between the two family trees. Traditional Unix daemons
&lt;strong&gt;double-fork&lt;&#x2F;strong&gt; to &quot;daemonize&quot; — detach from their parent and re-parent to PID&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;That&#x27;s deliberate, and it&#x27;s a disaster for a service manager: once a daemon
double-forks, the thing that launched it has &lt;strong&gt;lost track of it&lt;&#x2F;strong&gt;. PID-based
supervision is racy and unreliable — PIDs get recycled, children scatter and
re-parent to init, and you can never be sure you&#x27;ve found (or killed) &lt;em&gt;all&lt;&#x2F;em&gt; of a
service&#x27;s processes.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;cgroups solved this cleanly, and it&#x27;s exactly why &quot;Rethinking PID 1&quot; leaned on
them. Put every service in &lt;strong&gt;its own cgroup&lt;&#x2F;strong&gt;, and the kernel maintains a
&lt;strong&gt;reliable, race-free record of precisely which processes belong to that
service&lt;&#x2F;strong&gt;, no matter how many times they fork. Poettering put it bluntly:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Unless it is privileged and has access to the cgroup file system it cannot
escape its group.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;That gives you accurate accounting &lt;em&gt;and&lt;&#x2F;em&gt; the ability to reliably kill an entire
service — every last worker — by acting on the group.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;july-3-2013-v205-systemd-grows-slices-and-scopes&quot;&gt;July 3, 2013 (v205): systemd grows slices and scopes&lt;a class=&quot;post-anchor&quot; href=&quot;#july-3-2013-v205-systemd-grows-slices-and-scopes&quot; aria-label=&quot;Anchor link for: july-3-2013-v205-systemd-grows-slices-and-scopes&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;For systemd&#x27;s first few years it &lt;em&gt;used&lt;&#x2F;em&gt; cgroups internally for this tracking,
but there was a problem: lots of programs (systemd, logind, libvirt, your shell)
were all writing to the cgroup tree at once, stepping on each other. So in
&lt;strong&gt;version 205&lt;&#x2F;strong&gt; (released 2013-07-03), systemd introduced two new unit types and
a new rule. From the NEWS file:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Two new unit types have been introduced: &lt;strong&gt;Scope units&lt;&#x2F;strong&gt; are very similar to
service units, however, are created out of pre-existing processes […] By using
scope units it is possible for system services and applications to group their
own child processes […] &lt;strong&gt;Slice units&lt;&#x2F;strong&gt; may be used to partition system
resources in an hierarchical fashion and then assign other units to them. By
default there are now three slices: &lt;code&gt;system.slice&lt;&#x2F;code&gt; (for all system services),
&lt;code&gt;user.slice&lt;&#x2F;code&gt; (for all user sessions), &lt;code&gt;machine.slice&lt;&#x2F;code&gt; (for VMs and
containers).&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;And the reason they appeared at all:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Slices and scopes have been introduced primarily in context of the work to
move cgroup handling to a &lt;strong&gt;single-writer scheme&lt;&#x2F;strong&gt;, where only PID 1
creates&#x2F;removes&#x2F;manages cgroups.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;That &quot;single-writer&quot; rule — &lt;strong&gt;only PID 1 touches the cgroup tree&lt;&#x2F;strong&gt; — is why
modern systemd is the sole owner of cgroups on a Linux box. Everything else
asks systemd.&lt;&#x2F;p&gt;
&lt;p&gt;So what &lt;em&gt;is&lt;&#x2F;em&gt; a slice? From &lt;code&gt;systemd.slice(5)&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;A slice unit is a concept for &lt;strong&gt;hierarchically managing resources of a group
of processes&lt;&#x2F;strong&gt;. This management is performed by &lt;strong&gt;creating a node in the Linux
Control Group (cgroup) tree&lt;&#x2F;strong&gt;. Units that manage processes (primarily scope
and service units) may be assigned to a specific slice. For each slice,
certain resource limits may be set that apply to all processes of all units
contained in that slice.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;A slice &lt;strong&gt;is&lt;&#x2F;strong&gt; a cgroup directory. Its &lt;em&gt;name&lt;&#x2F;em&gt; encodes its path in the tree:
&lt;code&gt;-.slice&lt;&#x2F;code&gt; is the root, and &lt;code&gt;foo-bar.slice&lt;&#x2F;code&gt; sits inside &lt;code&gt;foo.slice&lt;&#x2F;code&gt; inside
&lt;code&gt;-.slice&lt;&#x2F;code&gt;. The default layout every systemd box boots with:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-.slice                 the root&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;├── system.slice        all system services&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;├── user.slice          all user sessions (each user gets user-UID.slice)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;└── machine.slice       VMs and containers (via systemd-machined)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The third unit type from v205, the &lt;strong&gt;scope&lt;&#x2F;strong&gt;, is the odd cousin: like a service,
but for processes systemd &lt;em&gt;didn&#x27;t&lt;&#x2F;em&gt; fork itself — login sessions, VMs, anything
externally launched. Scopes and services are the &lt;strong&gt;leaves&lt;&#x2F;strong&gt; that hold actual
processes; slices are the &lt;strong&gt;branches&lt;&#x2F;strong&gt; that hold them.&lt;&#x2F;p&gt;
&lt;p&gt;And that leaf&#x2F;branch split isn&#x27;t a metaphor — it&#x27;s a kernel rule. systemd&#x27;s
&lt;code&gt;CONTROL_GROUP_INTERFACE.md&lt;&#x2F;code&gt; spells it out:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Service, scope and slice units &lt;strong&gt;directly map to objects in the cgroup tree&lt;&#x2F;strong&gt;.
[…] Slices &lt;strong&gt;do not contain processes themselves&lt;&#x2F;strong&gt;, but the services and
scopes contained in them do.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Under cgroup v2&#x27;s &quot;no processes in inner nodes&quot; rule, an inner node can&#x27;t hold
processes directly — so slices (inner nodes) never contain processes, only their
service&#x2F;scope children (leaf nodes) do. Crucially, a unit is placed in &lt;strong&gt;exactly
one&lt;&#x2F;strong&gt; slice: the &lt;code&gt;Slice=&lt;&#x2F;code&gt; directive is a single value, defaulting to
&lt;code&gt;system.slice&lt;&#x2F;code&gt;. Membership in the cgroup tree is &lt;strong&gt;one-to-one&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;the-cgroup-v2-detour&quot;&gt;The cgroup v2 detour&lt;a class=&quot;post-anchor&quot; href=&quot;#the-cgroup-v2-detour&quot; aria-label=&quot;Anchor link for: the-cgroup-v2-detour&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;One more thread, because it explains why old guides feel inconsistent. The
original cgroups (v1) gave each controller its &lt;em&gt;own independent hierarchy&lt;&#x2F;em&gt; —
the cpu tree, the memory tree, the io tree were separate and could disagree
about which group a process was in. It was a mess that couldn&#x27;t be fixed without
breaking the interface. &lt;strong&gt;Tejun Heo&lt;&#x2F;strong&gt; led the redesign — &lt;strong&gt;cgroup v2&lt;&#x2F;strong&gt;, the
&quot;unified hierarchy,&quot; where every controller shares one tree and a process
belongs to exactly one cgroup. It landed as stable in &lt;strong&gt;Linux 4.5 (March
2016).&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;systemd&#x27;s migration tracks that maturing:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;v233 (2017)  hybrid v1+v2 default, with knobs to opt into unified&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;v243 (2019)  build-time default flips to &amp;quot;unified&amp;quot; (pure cgroup v2)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;v258 (2025)  cgroup v1 support removed entirely — v2 always mounted&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;So if you&#x27;re on a current system, your slices are nodes in a single unified
cgroup v2 tree. The &quot;exactly one slice&quot; cleanliness is partly &lt;em&gt;because&lt;&#x2F;em&gt; of
Tejun&#x27;s redesign.&lt;&#x2F;p&gt;
&lt;p&gt;So: &lt;strong&gt;cgroups → slices.&lt;&#x2F;strong&gt; The lineage is &lt;em&gt;resources, accounting, limits,
containment.&lt;&#x2F;em&gt; Nothing about boot order.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-nuance-why-they-aren-t-siblings&quot;&gt;The nuance: why they aren&#x27;t siblings&lt;a class=&quot;post-anchor&quot; href=&quot;#the-nuance-why-they-aren-t-siblings&quot; aria-label=&quot;Anchor link for: the-nuance-why-they-aren-t-siblings&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Lay the two family trees side by side and the &quot;overlap&quot; evaporates:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;&#x2F;th&gt;&lt;th&gt;&lt;strong&gt;Target&lt;&#x2F;strong&gt;&lt;&#x2F;th&gt;&lt;th&gt;&lt;strong&gt;Slice&lt;&#x2F;strong&gt;&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Ancestor&lt;&#x2F;td&gt;&lt;td&gt;SysV init runlevels (early 1980s)&lt;&#x2F;td&gt;&lt;td&gt;Linux cgroups (Google, 2006 → kernel 2.6.24, 2008)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Arrived in systemd&lt;&#x2F;td&gt;&lt;td&gt;day one (2010)&lt;&#x2F;td&gt;&lt;td&gt;v205 (2013)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;What it &lt;em&gt;is&lt;&#x2F;em&gt;&lt;&#x2F;td&gt;&lt;td&gt;a node in the &lt;strong&gt;dependency graph&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;a node in the &lt;strong&gt;cgroup tree&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Question it answers&lt;&#x2F;td&gt;&lt;td&gt;&lt;em&gt;what should run, and in what order?&lt;&#x2F;em&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;em&gt;whose resources, and what limits?&lt;&#x2F;em&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Has processes?&lt;&#x2F;td&gt;&lt;td&gt;no — pure synchronization point&lt;&#x2F;td&gt;&lt;td&gt;not directly; its leaf children (services&#x2F;scopes) do&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Kernel object?&lt;&#x2F;td&gt;&lt;td&gt;no, it&#x27;s bookkeeping&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;yes&lt;&#x2F;strong&gt;, a real cgroup directory&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;A unit relates to it via&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;WantedBy=&lt;&#x2F;code&gt;, &lt;code&gt;Wants=&lt;&#x2F;code&gt;, &lt;code&gt;Requires=&lt;&#x2F;code&gt;, &lt;code&gt;After=&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;Slice=&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Cardinality&lt;&#x2F;td&gt;&lt;td&gt;a unit can be in &lt;strong&gt;many&lt;&#x2F;strong&gt; targets&lt;&#x2F;td&gt;&lt;td&gt;a unit is in &lt;strong&gt;exactly one&lt;&#x2F;strong&gt; slice&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;The knobs it carries&lt;&#x2F;td&gt;&lt;td&gt;none (it does nothing itself)&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;CPUWeight=&lt;&#x2F;code&gt;, &lt;code&gt;MemoryMax=&lt;&#x2F;code&gt;, &lt;code&gt;IOWeight=&lt;&#x2F;code&gt;, &lt;code&gt;TasksMax=&lt;&#x2F;code&gt;…&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;The deep point is &lt;strong&gt;orthogonality.&lt;&#x2F;strong&gt; Every service is simultaneously at two
coordinates:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Somewhere in the dependency graph&lt;&#x2F;strong&gt; — pulled in by some targets, ordered
before&#x2F;after other units. This is the &lt;em&gt;time &#x2F; causality&lt;&#x2F;em&gt; axis. Targets live
here.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Somewhere in the cgroup tree&lt;&#x2F;strong&gt; — inside exactly one slice, subject to its
resource limits. This is the &lt;em&gt;resource &#x2F; containment&lt;&#x2F;em&gt; axis. Slices live here.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;These axes don&#x27;t compete; they&#x27;re perpendicular. Asking &quot;should I use a target
or a slice?&quot; is a bit like asking &quot;should I describe this meeting by its time or
its room?&quot; Usually the honest answer is &lt;em&gt;both, for different reasons.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;so-which-one-do-i-actually-want&quot;&gt;So which one do I actually want?&lt;a class=&quot;post-anchor&quot; href=&quot;#so-which-one-do-i-actually-want&quot; aria-label=&quot;Anchor link for: so-which-one-do-i-actually-want&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Back to my original itch — &quot;let me see and operate on my own set of units as a
group.&quot; Now I can answer it properly, because it depends on &lt;em&gt;which axis&lt;&#x2F;em&gt; I care
about.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;If I want to bring them up&#x2F;down together, order them, or depend on them as a
set&lt;&#x2F;strong&gt; → that&#x27;s the dependency-graph axis → &lt;strong&gt;a target.&lt;&#x2F;strong&gt; It&#x27;s the
runlevel-flavored answer: &quot;here is a named bundle; activate it.&quot;&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;ini&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;# ~&#x2F;.config&#x2F;systemd&#x2F;user&#x2F;personal.target&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-section z-group-title z-ini&quot;&gt;Unit&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword z-other z-definition z-ini&quot;&gt;Description&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;My personal services&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;ini&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;# in each of my units:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-section z-group-title z-ini&quot;&gt;Install&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword z-other z-definition z-ini&quot;&gt;WantedBy&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;personal.target&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;systemctl&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; --user list-dependencies personal.target&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;  # see the whole set&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;systemctl&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; --user start personal.target&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;               # bring the set up&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;If I want to cap or account their combined CPU&#x2F;memory, or view them all as one
resource group&lt;&#x2F;strong&gt; → that&#x27;s the cgroup-tree axis → &lt;strong&gt;a slice.&lt;&#x2F;strong&gt; It&#x27;s the
cgroup-flavored answer, and &lt;code&gt;status&lt;&#x2F;code&gt; on a slice gives you a live tree of
everything inside it plus its resource usage:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;ini&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;# ~&#x2F;.config&#x2F;systemd&#x2F;user&#x2F;personal.slice&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-section z-group-title z-ini&quot;&gt;Slice&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword z-other z-definition z-ini&quot;&gt;MemoryMax&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;2G&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword z-other z-definition z-ini&quot;&gt;CPUWeight&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;50&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;ini&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;# in each of my units:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-section z-group-title z-ini&quot;&gt;Service&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword z-other z-definition z-ini&quot;&gt;Slice&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;personal.slice&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;systemctl&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; --user status personal.slice&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;  # the cgroup tree + live usage&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;systemd-cgls&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; --user&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;                      # the whole user cgroup tree&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And the punchline that took me the whole history to appreciate: &lt;strong&gt;you can use
both, and it isn&#x27;t redundant.&lt;&#x2F;strong&gt; A &lt;code&gt;personal.target&lt;&#x2F;code&gt; that pulls the group up &lt;em&gt;and&lt;&#x2F;em&gt;
a &lt;code&gt;personal.slice&lt;&#x2F;code&gt; that boxes its resources are describing the same services
along two independent axes. The target says &lt;em&gt;when&lt;&#x2F;em&gt; they run; the slice says &lt;em&gt;how
much&lt;&#x2F;em&gt; they get.&lt;&#x2F;p&gt;
&lt;p&gt;(If all I really want is to &lt;em&gt;list&lt;&#x2F;em&gt; mine and never operate on them as a unit, the
cheapest trick of all is a shared name prefix — &lt;code&gt;systemctl --user list-units &#x27;personal-*&#x27;&lt;&#x2F;code&gt;, since &lt;code&gt;systemctl&lt;&#x2F;code&gt; accepts globs. No new unit types
required. But the moment I want grouped &lt;em&gt;behavior&lt;&#x2F;em&gt; — ordering or limits — I reach
for the matching family tree.)&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-takeaway&quot;&gt;The takeaway&lt;a class=&quot;post-anchor&quot; href=&quot;#the-takeaway&quot; aria-label=&quot;Anchor link for: the-takeaway&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;systemd looks like one monolithic thing, but a lot of it is really &lt;strong&gt;two old
Unix ideas wearing the same &lt;code&gt;systemctl&lt;&#x2F;code&gt; uniform&lt;&#x2F;strong&gt;: the init state machine that
goes back to AT&amp;amp;T System V, and the kernel resource controller that came out of
Google&#x27;s clusters. Targets are the grandchild of the runlevel. Slices are the
grandchild of the cgroup. They share a command-line front-end and absolutely
nothing else in their DNA.&lt;&#x2F;p&gt;
&lt;p&gt;Once you can glance at a unit type and ask &lt;em&gt;&quot;which family tree is this from —
the dependency graph, or the cgroup tree?&quot;&lt;&#x2F;em&gt;, the rest of systemd stops feeling
arbitrary. It&#x27;s just two lineages, finally living under one roof.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h3 id=&quot;sources-further-reading&quot;&gt;Sources &amp;amp; further reading&lt;a class=&quot;post-anchor&quot; href=&quot;#sources-further-reading&quot; aria-label=&quot;Anchor link for: sources-further-reading&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;ul&gt;
&lt;li&gt;Lennart Poettering, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;0pointer.de&#x2F;blog&#x2F;projects&#x2F;systemd.html&quot;&gt;&lt;strong&gt;Rethinking PID 1&lt;&#x2F;strong&gt;&lt;&#x2F;a&gt; (2010-04-30) — the founding essay.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;systemd.target(5)&lt;&#x2F;code&gt;, &lt;code&gt;systemd.slice(5)&lt;&#x2F;code&gt;, &lt;code&gt;systemd.scope(5)&lt;&#x2F;code&gt;, &lt;code&gt;systemd.special(7)&lt;&#x2F;code&gt; — the canonical definitions quoted above.&lt;&#x2F;li&gt;
&lt;li&gt;systemd &lt;code&gt;NEWS&lt;&#x2F;code&gt; — v205 (slices&#x2F;scopes + single-writer), v243 (unified default), v258 (v1 removed).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;docs.kernel.org&#x2F;admin-guide&#x2F;cgroup-v2.html&quot;&gt;Control Group v2&lt;&#x2F;a&gt; — the kernel&#x27;s own cgroup v2 documentation.&lt;&#x2F;li&gt;
&lt;li&gt;LWN, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;lwn.net&#x2F;Articles&#x2F;1008721&#x2F;&quot;&gt;14 years of systemd&lt;&#x2F;a&gt; — a retrospective.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</description>
      </item>
      <item>
          <title>Getting SASSy: Stealing Login Patterns from Codeberg and Sourcehut</title>
          <pubDate>Wed, 03 Jun 2026 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://blog.s99.sh/blog/sassy/</link>
          <guid>https://blog.s99.sh/blog/sassy/</guid>
          <description xml:base="https://blog.s99.sh/blog/sassy/">&lt;p&gt;I&#x27;m building a SaaS. I want it to be as FOSS, Linux-native, and self-hosted as I
can make it — which means at some point I have to stop and solve the least
glamorous, most important problem in the whole thing: &lt;strong&gt;letting users log in.&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Authentication is one of those areas where the wrong instinct is to invent
something. The right instinct is to go read how people who already solved it —
in the open, in production, for years — chose to do it. I picked two: &lt;strong&gt;Codeberg&lt;&#x2F;strong&gt;,
which runs &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;forgejo.org&quot;&gt;Forgejo&lt;&#x2F;a&gt; (a fork of Gitea), and
&lt;strong&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;sourcehut.org&quot;&gt;Sourcehut&lt;&#x2F;a&gt;&lt;&#x2F;strong&gt; (&lt;code&gt;sr.ht&lt;&#x2F;code&gt;). Both are fully open source.
Both are self-hostable. Both have been running real accounts for real people for
a long time. And because they&#x27;re open, I can read exactly how they do it instead
of guessing.&lt;&#x2F;p&gt;
&lt;p&gt;I came away with two questions. This post is built around them.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;how-do-codeberg-and-sourcehut-handle-this-problem&quot;&gt;&quot;How do Codeberg and Sourcehut handle this problem?&quot;&lt;a class=&quot;post-anchor&quot; href=&quot;#how-do-codeberg-and-sourcehut-handle-this-problem&quot; aria-label=&quot;Anchor link for: how-do-codeberg-and-sourcehut-handle-this-problem&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;The most useful thing I learned up front is that these two sit at &lt;strong&gt;opposite ends
of one spectrum&lt;&#x2F;strong&gt;, and which one you copy depends entirely on whether you ever
plan to split your app into multiple services.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Sourcehut treats identity as a service.&lt;&#x2F;strong&gt; &lt;code&gt;meta.sr.ht&lt;&#x2F;code&gt; is a standalone
account &#x2F; billing &#x2F; security hub. Every other service — &lt;code&gt;git.sr.ht&lt;&#x2F;code&gt;,
&lt;code&gt;builds.sr.ht&lt;&#x2F;code&gt;, &lt;code&gt;lists.sr.ht&lt;&#x2F;code&gt; — is an &lt;strong&gt;OAuth2 client of meta&lt;&#x2F;strong&gt;. Auth is a
network boundary you cross.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Forgejo treats identity as a module.&lt;&#x2F;strong&gt; One Go process owns login, sessions,
tokens, and 2FA directly, but it delegates credential checking through a common
&lt;strong&gt;auth-source interface&lt;&#x2F;strong&gt; (local password, LDAP, OIDC, PAM…). Auth is a plugin
point inside one binary.&lt;&#x2F;p&gt;
&lt;p&gt;If you&#x27;re one app today — like I am — Forgejo&#x27;s model is the closer fit. But the
&lt;em&gt;discipline&lt;&#x2F;em&gt; worth stealing is Sourcehut&#x27;s: treat identity as the source of
truth, behind an API, so you can extract it into its own service later without a
rewrite.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;sourcehut-in-detail&quot;&gt;Sourcehut, in detail&lt;a class=&quot;post-anchor&quot; href=&quot;#sourcehut-in-detail&quot; aria-label=&quot;Anchor link for: sourcehut-in-detail&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;code&gt;meta.sr.ht-api&lt;&#x2F;code&gt; (Go, GraphQL) owns all auth state. The Python web frontend holds
&lt;em&gt;no credentials&lt;&#x2F;em&gt; — it just issues GraphQL queries. The UI is a client of the auth
API, not a co-owner of the auth database. That separation is the whole point.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Login:&lt;&#x2F;strong&gt; password + &lt;strong&gt;TOTP&lt;&#x2F;strong&gt; 2FA. No WebAuthn. Public keys (SSH, PGP) are
stored centrally in meta and consumed by the other services.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;OAuth2&lt;&#x2F;strong&gt; is RFC 6749-conformant and deliberately hardened:
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Confidential clients only&lt;&#x2F;strong&gt; — public clients are &lt;em&gt;prohibited&lt;&#x2F;em&gt;. A native or
CLI app must run a server component, or use a personal access token.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;redirect_uri&lt;&#x2F;code&gt; is &lt;strong&gt;not accepted at runtime&lt;&#x2F;strong&gt; — only the pre-registered URI
is used. That kills a whole class of redirect attacks.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;scope&lt;&#x2F;code&gt; is &lt;strong&gt;mandatory&lt;&#x2F;strong&gt; (stricter than the spec). Scopes look like
&lt;code&gt;meta.sr.ht&#x2F;PROFILE:RO meta.sr.ht&#x2F;SSH_KEYS:RW&lt;&#x2F;code&gt; — &lt;code&gt;service&#x2F;SCOPE:access&lt;&#x2F;code&gt;,
where access is &lt;code&gt;RO&lt;&#x2F;code&gt; or &lt;code&gt;RW&lt;&#x2F;code&gt;, enforced per GraphQL resolver via an &lt;code&gt;@access&lt;&#x2F;code&gt;
directive.&lt;&#x2F;li&gt;
&lt;li&gt;Authorization codes are 16 hex chars, 5-minute TTL. Client secret is 64-byte
base64.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;The token format is the clever bit.&lt;&#x2F;strong&gt; An access &#x2F; personal token is a
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;baremessages.org&#x2F;&quot;&gt;BARE&lt;&#x2F;a&gt;-encoded payload — username, client ID,
authorized scopes, expiry — that is &lt;strong&gt;HMAC-signed&lt;&#x2F;strong&gt; and base64&#x27;d. So the token
is &lt;em&gt;stateless&lt;&#x2F;em&gt;: the server validates the signature without a database hit. The
grant rides inside the token. Revocation is layered on top — a SHA hash of the
token in &lt;strong&gt;Redis&lt;&#x2F;strong&gt; for instant lookup, plus a DB expiry timestamp as the
durable fallback if the cache is lost.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Service-to-service&lt;&#x2F;strong&gt; uses an &lt;code&gt;@internal&lt;&#x2F;code&gt; auth type — single-use tokens
encrypted with Sourcehut&#x27;s internal private keys — which unlocks resolvers not
exposed to the public. That&#x27;s how &lt;code&gt;git.sr.ht&lt;&#x2F;code&gt; trusts a request that claims to
come from &lt;code&gt;meta&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h3 id=&quot;forgejo-codeberg-in-detail&quot;&gt;Forgejo &#x2F; Codeberg, in detail&lt;a class=&quot;post-anchor&quot; href=&quot;#forgejo-codeberg-in-detail&quot; aria-label=&quot;Anchor link for: forgejo-codeberg-in-detail&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Password hashing:&lt;&#x2F;strong&gt; &lt;code&gt;PASSWORD_HASH_ALGO&lt;&#x2F;code&gt;, default &lt;strong&gt;PBKDF2-HMAC-SHA256 at
~310k–320k iterations&lt;&#x2F;strong&gt;; also argon2id, scrypt, bcrypt. Configurable length and
complexity, plus an optional &lt;strong&gt;Have-I-Been-Pwned check&lt;&#x2F;strong&gt; (&lt;code&gt;PASSWORD_CHECK_PWN&lt;&#x2F;code&gt;)
that rejects known-breached passwords at signup. Cheap, high-signal win.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Sessions:&lt;&#x2F;strong&gt; cookie + middleware. A &lt;code&gt;webAuth()&lt;&#x2F;code&gt; → &lt;code&gt;AuthShared()&lt;&#x2F;code&gt; flow walks an
&lt;strong&gt;authentication priority chain&lt;&#x2F;strong&gt; (try each method in order), then stashes the
user in request context, gated on email confirmation &#x2F; login-prohibition &#x2F;
forced-password-change. Session store is pluggable: memory &#x2F; db &#x2F; &lt;strong&gt;Redis&lt;&#x2F;strong&gt; &#x2F;
memcached.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;API tokens&lt;&#x2F;strong&gt; are &lt;strong&gt;hashed in the DB with the same algorithm as passwords&lt;&#x2F;strong&gt; —
never stored in plaintext — and &lt;strong&gt;scoped&lt;&#x2F;strong&gt; by permission, with an LRU cache
(default 20) of recently-validated hashes so it doesn&#x27;t re-run PBKDF2 on every
request. A nice perf&#x2F;security balance.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;2FA:&lt;&#x2F;strong&gt; TOTP &lt;em&gt;and&lt;&#x2F;em&gt; WebAuthn&#x2F;FIDO2 passkeys, including passwordless login. For
basic-auth API calls with 2FA on, the code goes in an &lt;code&gt;X-Gitea-OTP:&lt;&#x2F;code&gt; header.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;The auth-source abstraction&lt;&#x2F;strong&gt; is the real lesson: one interface, many
backends — local password, OAuth2&#x2F;OIDC client, LDAP (service-account search
&lt;em&gt;or&lt;&#x2F;em&gt; simple DN-template bind), PAM, SMTP, SPNEGO&#x2F;Kerberos, reverse-proxy header
trust. Forgejo can also &lt;em&gt;be&lt;&#x2F;em&gt; an OAuth2&#x2F;OIDC provider for other apps.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;where-is-it-all-stored-a-sql-table-unix&quot;&gt;&quot;Where is it all stored? A SQL table? Unix?&quot;&lt;a class=&quot;post-anchor&quot; href=&quot;#where-is-it-all-stored-a-sql-table-unix&quot; aria-label=&quot;Anchor link for: where-is-it-all-stored-a-sql-table-unix&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;This was my second question, and it&#x27;s the one that actually changed how I&#x27;m going
to build. Because the naïve mental model — &quot;a Linux server, users, surely there
are Unix accounts somewhere&quot; — is &lt;strong&gt;wrong&lt;&#x2F;strong&gt;, and deliberately so.&lt;&#x2F;p&gt;
&lt;p&gt;The trick is to separate two things people conflate:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Where the identity &lt;em&gt;record&lt;&#x2F;em&gt; lives&lt;&#x2F;strong&gt; → always a &lt;strong&gt;SQL row.&lt;&#x2F;strong&gt; Never
&lt;code&gt;&#x2F;etc&#x2F;passwd&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;What &lt;em&gt;verifies the secret&lt;&#x2F;em&gt;&lt;&#x2F;strong&gt; → usually a hash in that same SQL row, but
&lt;em&gt;optionally&lt;&#x2F;em&gt; delegated to Unix (&lt;code&gt;&#x2F;etc&#x2F;shadow&lt;&#x2F;code&gt;) or an external directory
(LDAP&#x2F;OIDC).&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;That second point is the entire reason the auth-source abstraction exists.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;sourcehut-everything-in-postgresql&quot;&gt;Sourcehut: everything in PostgreSQL&lt;a class=&quot;post-anchor&quot; href=&quot;#sourcehut-everything-in-postgresql&quot; aria-label=&quot;Anchor link for: sourcehut-everything-in-postgresql&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;Owned by &lt;code&gt;meta.sr.ht&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Concern&lt;&#x2F;th&gt;&lt;th&gt;Where it lives&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Identification&lt;&#x2F;strong&gt; (who you are)&lt;&#x2F;td&gt;&lt;td&gt;Postgres &lt;code&gt;user&lt;&#x2F;code&gt; table: &lt;code&gt;id, created, updated, username, email, user_type, url, location, bio&lt;&#x2F;code&gt;. Single source of truth; other services have their own DBs but defer to meta.&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Authentication&lt;&#x2F;strong&gt; (proving it)&lt;&#x2F;td&gt;&lt;td&gt;Password hash + TOTP secret in meta&#x27;s Postgres. SSH &lt;strong&gt;public keys in Postgres&lt;&#x2F;strong&gt;, cached in Redis.&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Authorization&lt;&#x2F;strong&gt; (what you can do)&lt;&#x2F;td&gt;&lt;td&gt;A &lt;strong&gt;stateless HMAC-signed token&lt;&#x2F;strong&gt; carrying username&#x2F;client&#x2F;scopes&#x2F;expiry. Revocation via Redis (SHA of token) + Postgres expiry.&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Unix accounts&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;None per user.&lt;&#x2F;strong&gt; One shared system user &lt;em&gt;per service&lt;&#x2F;em&gt; — you SSH as &lt;code&gt;git@&lt;&#x2F;code&gt;, &lt;code&gt;hg@&lt;&#x2F;code&gt;, &lt;code&gt;builds@&lt;&#x2F;code&gt;.&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;h3 id=&quot;forgejo-everything-in-a-configurable-sql-db&quot;&gt;Forgejo: everything in a configurable SQL DB&lt;a class=&quot;post-anchor&quot; href=&quot;#forgejo-everything-in-a-configurable-sql-db&quot; aria-label=&quot;Anchor link for: forgejo-everything-in-a-configurable-sql-db&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;SQLite &#x2F; MySQL &#x2F; PostgreSQL, your choice:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Concern&lt;&#x2F;th&gt;&lt;th&gt;Where it lives&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Identification&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;user&lt;&#x2F;code&gt; table.&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Authentication&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;Hashed password in the &lt;code&gt;user&lt;&#x2F;code&gt; row; 2FA secrets + WebAuthn credentials in their own tables; SSH keys in &lt;code&gt;public_key&lt;&#x2F;code&gt;; API tokens &lt;strong&gt;hashed&lt;&#x2F;strong&gt; in &lt;code&gt;access_token&lt;&#x2F;code&gt;.&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Authorization&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;Token scopes stored with the token; repo&#x2F;org&#x2F;team permissions in SQL. Sessions in memory &#x2F; db &#x2F; Redis.&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Unix accounts&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;None per user.&lt;&#x2F;strong&gt; A single &lt;code&gt;git&lt;&#x2F;code&gt; &lt;code&gt;RUN_USER&lt;&#x2F;code&gt;.&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;h3 id=&quot;so-where-does-unix-actually-come-in&quot;&gt;So where does Unix actually come in?&lt;a class=&quot;post-anchor&quot; href=&quot;#so-where-does-unix-actually-come-in&quot; aria-label=&quot;Anchor link for: so-where-does-unix-actually-come-in&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;Two places, both optional, both Forgejo:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;PAM&lt;&#x2F;strong&gt; verifies the password against &lt;code&gt;&#x2F;etc&#x2F;shadow&lt;&#x2F;code&gt; (real Unix accounts) — but
&lt;strong&gt;the identity row still lives in SQL.&lt;&#x2F;strong&gt; PAM only checks the secret. (It also
has a footgun: an uploaded SSH key can bypass the PAM check entirely.)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;LDAP &#x2F; OIDC&lt;&#x2F;strong&gt; verify against an external directory — but Forgejo still
&lt;strong&gt;mirrors a &lt;code&gt;user&lt;&#x2F;code&gt; row into SQL&lt;&#x2F;strong&gt; so it has something to hang repos and
permissions off of.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Even when the &lt;em&gt;verifier&lt;&#x2F;em&gt; is Unix or LDAP, the &lt;em&gt;system of record&lt;&#x2F;em&gt; is SQL. That&#x27;s
the rule.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;the-ssh-trick-worth-stealing&quot;&gt;The SSH trick worth stealing&lt;a class=&quot;post-anchor&quot; href=&quot;#the-ssh-trick-worth-stealing&quot; aria-label=&quot;Anchor link for: the-ssh-trick-worth-stealing&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;Neither forge runs &lt;code&gt;useradd&lt;&#x2F;code&gt; per customer. So how does &lt;code&gt;git push&lt;&#x2F;code&gt; over SSH know
who you are with one shared system user?&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;sshd&lt;&#x2F;code&gt;&#x27;s &lt;code&gt;AuthorizedKeysCommand&lt;&#x2F;code&gt;. Instead of reading &lt;code&gt;~&#x2F;.ssh&#x2F;authorized_keys&lt;&#x2F;code&gt;,
OpenSSH runs a program, hands it the public key being offered, and that program
looks the key up &lt;strong&gt;in the database&lt;&#x2F;strong&gt; and resolves it to a user.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Sourcehut&lt;&#x2F;strong&gt; runs &lt;code&gt;gitsrht-dispatch&lt;&#x2F;code&gt;: it checks Redis, then Postgres, then
&lt;code&gt;meta&lt;&#x2F;code&gt;, caching the result back down the chain for the next push.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Forgejo&lt;&#x2F;strong&gt; either generates a managed &lt;code&gt;authorized_keys&lt;&#x2F;code&gt; with a forced-command
per key, or uses &lt;code&gt;AuthorizedKeysCommand&lt;&#x2F;code&gt; (&lt;code&gt;gitea keys …&lt;&#x2F;code&gt;) to do the same lookup.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;So OpenSSH&#x27;s key-auth mechanism gets &lt;em&gt;reused&lt;&#x2F;em&gt;, but the &quot;account&quot; a key resolves
to is a &lt;strong&gt;database row, not a Unix user&lt;&#x2F;strong&gt;. One system user, a key→user lookup,
and you never touch &lt;code&gt;&#x2F;etc&#x2F;passwd&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-i-m-taking-into-my-own-build&quot;&gt;What I&#x27;m taking into my own build&lt;a class=&quot;post-anchor&quot; href=&quot;#what-i-m-taking-into-my-own-build&quot; aria-label=&quot;Anchor link for: what-i-m-taking-into-my-own-build&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Both forges converged on the same shape, so that&#x27;s the shape I&#x27;m copying:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;One SQL &lt;code&gt;users&lt;&#x2F;code&gt; table is the source of truth&lt;&#x2F;strong&gt; for identity. Unix accounts
don&#x27;t enter into it.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Make the credential verifier pluggable&lt;&#x2F;strong&gt; behind that table: local hash now;
LDAP&#x2F;OIDC later when an enterprise customer asks for SSO — without touching the
identity model.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Authorization travels separately&lt;&#x2F;strong&gt; — either DB-stored scoped tokens
(Forgejo: simpler) or stateless signed tokens with a revocation list
(Sourcehut: fewer DB hits). I&#x27;ll probably start with the former.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Ship TOTP early, WebAuthn&#x2F;passkeys later.&lt;&#x2F;strong&gt; That&#x27;s the order both landed on.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Add the HIBP breached-password check.&lt;&#x2F;strong&gt; Free, and it stops the dumbest
account takeovers.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;If I ever do SSH&#x2F;key access,&lt;&#x2F;strong&gt; copy the &lt;code&gt;AuthorizedKeysCommand&lt;&#x2F;code&gt; dispatch
trick. One shared system user, key→DB-user lookup. Never &lt;code&gt;useradd&lt;&#x2F;code&gt; per
customer.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The headline, if you skipped to the bottom: &lt;strong&gt;it&#x27;s SQL all the way down for
identity and the canonical credentials, and the avoidance of per-user Unix
accounts is a feature, not an oversight.&lt;&#x2F;strong&gt; The Linux box hosts the service; it
does not host the users.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;&lt;em&gt;Sources I read while writing this: the
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;sourcehut.org&#x2F;blog&#x2F;2020-09-25-api-2-updates&#x2F;&quot;&gt;Sourcehut API 2.0 dev log&lt;&#x2F;a&gt;,
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;man.sr.ht&#x2F;meta.sr.ht&#x2F;oauth.md&quot;&gt;OAuth 2.0 via meta.sr.ht&lt;&#x2F;a&gt;,
&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;sourcehut.org&#x2F;blog&#x2F;2019-11-22-what-happens-on-git-push&#x2F;&quot;&gt;What happens when you push to git.sr.ht&lt;&#x2F;a&gt;,
Drew DeVault&#x27;s &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;drewdevault.com&#x2F;blog&#x2F;Interactive-SSH-programs&#x2F;&quot;&gt;Building interactive SSH applications&lt;&#x2F;a&gt;,
the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;forgejo.org&#x2F;docs&#x2F;latest&#x2F;user&#x2F;authentication&#x2F;&quot;&gt;Forgejo authentication docs&lt;&#x2F;a&gt;,
the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;docs.gitea.com&#x2F;administration&#x2F;config-cheat-sheet&quot;&gt;Gitea config cheat sheet&lt;&#x2F;a&gt;,
and the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;deepwiki.com&#x2F;go-gitea&#x2F;gitea&#x2F;7.2-ssh-and-gpg-key-management&quot;&gt;Gitea SSH &amp;amp; key management writeup&lt;&#x2F;a&gt;.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Mouse settings explained: DPI, report rate, and receiver modes</title>
          <pubDate>Sun, 17 May 2026 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://blog.s99.sh/blog/mouse-settings-explained/</link>
          <guid>https://blog.s99.sh/blog/mouse-settings-explained/</guid>
          <description xml:base="https://blog.s99.sh/blog/mouse-settings-explained/">&lt;p&gt;Modern gaming mice expose a few settings that are easy to misunderstand
because the labels are short and the marketing around them is often
imprecise. This post uses ZOWIE&#x27;s current  -DW  wireless mice as the
example:
EC-DW, FK2-DW, ZA13-DW, S2-DW, and U2-DW. The exact shape differs by model,
but
the relevant controls and settings are the same.&lt;&#x2F;p&gt;
&lt;figure class=&quot;inline-svg&quot; role=&quot;img&quot; aria-label=&quot;ZOWIE S2-DW box contents: mouse, enhanced receiver, USB receiver, and cable&quot;&gt;
&lt;svg xmlns=&quot;http:&#x2F;&#x2F;www.w3.org&#x2F;2000&#x2F;svg&quot; viewBox=&quot;0 0 1068.000000 1068.000000&quot; preserveAspectRatio=&quot;xMidYMid meet&quot; role=&quot;img&quot;&gt;
&lt;g transform=&quot;translate(0.000000,1068.000000) scale(0.100000,-0.100000)&quot; fill=&quot;var(--accent-color, #d2003d)&quot; stroke=&quot;none&quot;&gt;
&lt;path d=&quot;M1788 10073 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z
M2098 10073 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z M1943 9843
c9 -2 23 -2 30 0 6 3 -1 5 -18 5 -16 0 -22 -2 -12 -5z M2074 9620 c0 -74 1
-105 3 -67 2 37 2 97 0 135 -2 37 -3 6 -3 -68z M2592 9550 c0 -19 2 -27 5 -17
2 9 2 25 0 35 -3 9 -5 1 -5 -18z M2632 9490 c0 -19 2 -27 5 -17 2 9 2 25 0 35
-3 9 -5 1 -5 -18z M1312 9245 c0 -16 2 -22 5 -12 2 9 2 23 0 30 -3 6 -5 -1 -5
-18z M1271 9184 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z M2582
9165 c0 -16 2 -22 5 -12 2 9 2 23 0 30 -3 6 -5 -1 -5 -18z M2622 9105 c0 -16
2 -22 5 -12 2 9 2 23 0 30 -3 6 -5 -1 -5 -18z M1322 8875 c0 -16 2 -22 5 -12
2 9 2 23 0 30 -3 6 -5 -1 -5 -18z M5213 8893 c26 -2 68 -2 95 0 26 2 4 3 -48
3 -52 0 -74 -1 -47 -3z M1282 8825 c0 -16 2 -22 5 -12 2 9 2 23 0 30 -3 6 -5
-1 -5 -18z M2611 8754 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z
M2681 8104 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z M2642 8070
c0 -14 2 -19 5 -12 2 6 2 18 0 25 -3 6 -5 1 -5 -13z M2682 8030 c0 -14 2 -19
5 -12 2 6 2 18 0 25 -3 6 -5 1 -5 -13z M5128 7913 c155 -2 409 -2 565 0 155 1
28 2 -283 2 -311 0 -438 -1 -282 -2z M5343 7735 c0 -27 2 -38 4 -22 2 15 2 37
0 50 -2 12 -4 0 -4 -28z M1937 7623 c18 -2 50 -2 70 0 21 2 7 4 -32 4 -38 0
-55 -2 -38 -4z M8748 7623 c84 -2 219 -2 300 0 81 1 12 3 -153 3 -165 0 -231
-2 -147 -3z M5383 7573 c15 -2 39 -2 55 0 15 2 2 4 -28 4 -30 0 -43 -2 -27 -4z
M2454 7518 l-19 -23 23 19 c21 18 27 26 19 26 -2 0 -12 -10 -23 -22z M6523
7055 c0 -27 2 -38 4 -22 2 15 2 37 0 50 -2 12 -4 0 -4 -28z M4619 7043 c-13
-16 -12 -17 4 -4 9 7 17 15 17 17 0 8 -8 3 -21 -13z M5108 7003 c7 -3 16 -2
19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z M5348 7003 c7 -3 16 -2 19 1 4 3 -2
6 -13 5 -11 0 -14 -3 -6 -6z M5808 7003 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11
0 -14 -3 -6 -6z M5173 6905 c0 -33 2 -45 4 -27 2 18 2 45 0 60 -2 15 -4 0 -4
-33z M4235 6939 c-4 -6 -5 -12 -2 -15 2 -3 7 2 10 11 7 17 1 20 -8 4z M5433
6900 c0 -30 2 -43 4 -27 2 15 2 39 0 55 -2 15 -4 2 -4 -28z M6522 6930 c0 -14
2 -19 5 -12 2 6 2 18 0 25 -3 6 -5 1 -5 -13z M5563 6923 c9 -2 25 -2 35 0 9 3
1 5 -18 5 -19 0 -27 -2 -17 -5z M1711 6854 c0 -11 3 -14 6 -6 3 7 2 16 -1 19
-3 4 -6 -2 -5 -13z M8604 6485 c0 -66 1 -92 3 -57 2 34 2 88 0 120 -2 31 -3 3
-3 -63z M5504 6495 c0 -49 1 -71 3 -48 2 23 2 64 0 90 -2 26 -3 8 -3 -42z
M5071 6504 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z M8911 6444
c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z M8913 6335 c0 -22 2 -30
4 -17 2 12 2 30 0 40 -3 9 -5 -1 -4 -23z M5363 5563 c9 -2 25 -2 35 0 9 3 1 5
-18 5 -19 0 -27 -2 -17 -5z M4942 5195 c0 -16 2 -22 5 -12 2 9 2 23 0 30 -3 6
-5 -1 -5 -18z M5328 5163 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z
M1654 4840 c0 -168 2 -236 3 -152 2 83 2 221 0 305 -1 83 -3 15 -3 -153z
M1763 4830 c0 -30 2 -43 4 -27 2 15 2 39 0 55 -2 15 -4 2 -4 -28z M1882 4830
c0 -19 2 -27 5 -17 2 9 2 25 0 35 -3 9 -5 1 -5 -18z M5533 4715 c0 -22 2 -30
4 -17 2 12 2 30 0 40 -3 9 -5 -1 -4 -23z M9603 4585 c0 -27 2 -38 4 -22 2 15
2 37 0 50 -2 12 -4 0 -4 -28z M5368 4513 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11
0 -14 -3 -6 -6z M1822 4503 c70 -2 187 -2 260 0 73 1 16 3 -127 3 -143 0 -203
-2 -133 -3z M9603 4445 c0 -22 2 -30 4 -17 2 12 2 30 0 40 -3 9 -5 -1 -4 -23z
M5358 4453 c12 -2 30 -2 40 0 9 3 -1 5 -23 4 -22 0 -30 -2 -17 -4z M5428 4413
c12 -2 32 -2 45 0 12 2 2 4 -23 4 -25 0 -35 -2 -22 -4z M5683 4413 c9 -2 23
-2 30 0 6 3 -1 5 -18 5 -16 0 -22 -2 -12 -5z M1354 3575 c0 -60 1 -84 3 -52 2
32 2 81 0 110 -2 29 -3 3 -3 -58z M9312 3650 c0 -19 2 -27 5 -17 2 9 2 25 0
35 -3 9 -5 1 -5 -18z M8524 3560 c0 -63 1 -89 3 -57 2 31 2 83 0 115 -2 31 -3
5 -3 -58z M2484 3540 c0 -52 1 -74 3 -47 2 26 2 68 0 95 -2 26 -3 4 -3 -48z
M1403 3583 c15 -2 37 -2 50 0 12 2 0 4 -28 4 -27 0 -38 -2 -22 -4z M2751 3544
c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z M5943 3500 c0 -36 2 -50
4 -32 2 17 2 47 0 65 -2 17 -4 3 -4 -33z M9311 3524 c0 -11 3 -14 6 -6 3 7 2
16 -1 19 -3 4 -6 -2 -5 -13z M1613 2693 c32 -2 81 -2 110 0 29 2 3 3 -58 3
-60 0 -84 -1 -52 -3z M2153 2643 c31 -2 83 -2 115 0 31 2 5 3 -58 3 -63 0 -89
-1 -57 -3z M2584 2065 c0 -110 2 -156 3 -103 2 53 2 143 0 200 -1 57 -3 13 -3
-97z M2148 1833 c23 -2 61 -2 85 0 23 2 4 4 -43 4 -47 0 -66 -2 -42 -4z M2308
1813 c23 -2 61 -2 85 0 23 2 4 4 -43 4 -47 0 -66 -2 -42 -4z M1633 1763 c20
-2 54 -2 75 0 20 2 3 4 -38 4 -41 0 -58 -2 -37 -4z M2353 1703 c26 -2 67 -2
90 0 23 2 2 3 -48 3 -49 0 -68 -1 -42 -3z M1603 1300 c0 -41 2 -58 4 -37 2 20
2 54 0 75 -2 20 -4 3 -4 -38z M2483 760 c0 -30 2 -43 4 -27 2 15 2 39 0 55 -2
15 -4 2 -4 -28z M2318 693 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6
-6z&quot;&#x2F;&gt;
&lt;&#x2F;g&gt;
&lt;g transform=&quot;translate(0.000000,1068.000000) scale(0.100000,-0.100000)&quot; fill=&quot;var(--text-color, currentColor)&quot; stroke=&quot;none&quot;&gt;
&lt;path d=&quot;M1672 10099 c-258 -59 -405 -233 -406 -483 -1 -44 3 -234 9 -421 17
-604 15 -681 -27 -860 -17 -71 -22 -124 -22 -255 l-1 -165 38 -100 c51 -133
114 -231 199 -309 84 -79 134 -109 242 -146 316 -110 671 4 844 270 61 95 117
229 129 312 14 98 4 319 -17 374 -5 13 -12 45 -16 71 -3 27 -8 51 -10 54 -2 3
-7 34 -11 70 -9 78 -9 179 1 544 17 611 17 618 -5 696 -40 145 -102 222 -239
297 -35 20 -104 41 -180 57 -103 21 -424 17 -528 -6z m518 -33 c14 -3 39 -8
55 -12 97 -19 243 -119 288 -196 64 -111 67 -155 52 -683 -16 -595 -14 -661
25 -832 20 -85 41 -269 35 -300 -2 -10 -6 -40 -9 -68 -15 -155 -128 -363 -256
-470 -159 -133 -431 -180 -645 -110 -109 35 -187 84 -271 170 -74 76 -129 172
-175 305 -39 111 -34 323 10 495 28 111 29 168 20 980 l-4 390 27 63 c43 99
111 167 218 219 105 49 188 61 415 58 105 -2 201 -5 215 -9z M1892 9831 c-51
-27 -52 -34 -50 -213 3 -162 4 -167 27 -187 14 -12 35 -24 48 -27 l23 -6 0
-404 c0 -263 3 -404 10 -404 7 0 10 141 10 404 l0 404 28 5 c16 3 39 15 53 25
23 19 24 26 29 178 5 170 -1 200 -44 224 -33 17 -102 18 -134 1z m135 -33 c23
-23 24 -30 25 -171 0 -168 -7 -191 -66 -202 -42 -8 -74 1 -103 28 -22 19 -23
28 -23 170 l0 149 28 26 c36 36 103 36 139 0z M1860 7795 c0 -12 15 -15 75
-15 63 0 76 -3 85 -19 13 -25 13 -27 -6 -45 -9 -10 -33 -16 -60 -16 -24 0 -44
-4 -44 -10 0 -5 24 -10 53 -10 63 0 87 19 87 68 0 43 -38 62 -125 62 -51 0
-65 -3 -65 -15z M1890 7747 c-33 -17 -40 -78 -12 -103 24 -20 172 -26 172 -7
0 8 -25 13 -76 15 -60 2 -79 7 -88 21 -19 31 4 52 63 55 35 2 51 7 48 15 -5
17 -77 20 -107 4z M4825 8893 c-11 -3 -28 -12 -39 -22 -45 -39 -46 -54 -46
-559 0 -264 3 -494 7 -512 4 -20 23 -50 48 -74 23 -23 51 -57 63 -77 33 -54
100 -112 167 -144 54 -25 82 -35 150 -50 11 -3 31 -8 45 -11 44 -11 323 -13
385 -3 194 32 353 133 381 243 3 15 16 31 28 36 11 4 32 20 46 35 l25 27 3
503 c2 422 0 510 -12 539 -28 67 0 64 -646 69 -322 3 -594 3 -605 0z m1185
-69 c30 -30 32 -70 5 -102 l-23 -27 -579 0 c-557 0 -580 1 -598 19 -29 28 -26
89 5 116 23 20 28 20 597 18 566 -3 573 -3 593 -24z m-1184 -139 c43 -22 1149
-22 1178 0 11 8 22 15 25 15 3 0 5 -206 4 -457 -3 -418 -4 -459 -20 -470 -29
-22 -33 -15 -33 65 l0 77 -567 -3 -568 -2 3 -80 c4 -85 -3 -95 -38 -60 -19 19
-20 33 -20 475 0 250 2 455 4 455 2 0 17 -7 32 -15z m1123 -836 c-20 -64 -101
-142 -191 -183 -177 -80 -430 -92 -615 -30 -90 30 -136 56 -192 108 -40 38
-80 104 -81 133 0 10 118 13 543 15 298 2 543 2 545 1 2 0 -2 -20 -9 -44z m8
-84 c-9 -60 -31 -102 -81 -149 -68 -64 -175 -111 -306 -134 -47 -9 -254 -8
-310 0 -160 24 -321 113 -368 203 -16 31 -33 115 -23 115 3 0 17 -16 31 -34
28 -38 123 -106 185 -131 119 -50 318 -71 475 -50 153 20 351 125 381 202 14
38 22 26 16 -22z M5336 7798 c-9 -12 -16 -43 -16 -68 0 -25 7 -56 16 -68 13
-19 24 -22 89 -22 93 0 105 10 105 89 0 79 -15 91 -108 91 -62 0 -73 -3 -86
-22z m172 -56 c4 -72 -6 -82 -84 -82 -72 0 -84 11 -84 77 0 56 13 64 95 61
l70 -3 3 -53z M5384 7736 c-4 -9 -4 -20 0 -23 3 -4 12 -1 18 5 9 9 9 15 0 24
-9 9 -13 7 -18 -6z M5440 7730 c0 -13 5 -20 13 -17 6 2 12 10 12 17 0 7 -6 15
-12 18 -8 2 -13 -5 -13 -18z M8555 8875 l-25 -24 0 -615 0 -615 26 -20 c26
-20 38 -21 339 -21 301 0 313 1 339 21 l26 20 0 615 0 615 -25 24 -24 25 -316
0 -316 0 -24 -25z m646 -25 c18 -10 19 -28 19 -605 0 -526 -2 -595 -16 -609
-13 -14 -55 -16 -309 -16 -254 0 -296 2 -309 16 -14 14 -16 83 -16 609 0 555
1 595 18 604 23 14 587 15 613 1z M8767 8314 c-16 -17 11 -24 89 -24 95 0 124
-10 124 -43 0 -42 -21 -56 -84 -59 -36 -2 -61 -8 -64 -15 -5 -16 103 -18 141
-4 24 9 40 37 42 71 1 25 -27 68 -49 74 -29 8 -191 7 -199 0z M8823 8253 c-56
-11 -73 -103 -26 -140 24 -20 40 -23 121 -23 75 0 92 3 92 15 0 12 -16 15 -88
15 -95 0 -122 12 -122 57 0 32 25 46 85 46 56 0 79 12 54 28 -14 9 -76 10
-116 2z M5265 6970 c0 -95 3 -130 12 -130 8 0 13 23 15 66 3 60 6 69 31 88 26
19 30 19 55 5 25 -14 27 -20 30 -87 2 -48 7 -72 15 -72 17 0 12 149 -5 167
-19 19 -73 23 -103 7 l-25 -13 0 50 c0 32 -4 49 -12 49 -10 0 -13 -33 -13
-130z M6530 7057 c0 -53 -5 -59 -36 -42 -31 16 -82 10 -106 -11 -9 -9 -20 -32
-24 -51 -14 -75 48 -135 115 -112 17 6 31 15 31 21 0 5 5 6 10 3 6 -4 8 -11 5
-16 -4 -5 1 -9 9 -9 14 0 16 19 16 130 0 80 -4 130 -10 130 -5 0 -10 -19 -10
-43z m-44 -60 c12 -4 28 -22 35 -39 34 -82 -93 -143 -130 -62 -16 35 -14 58 7
81 26 30 44 34 88 20z M4266 7008 c-36 -46 -66 -89 -66 -95 0 -9 21 -13 70
-13 l70 0 0 -30 c0 -21 5 -30 16 -30 11 0 15 8 12 28 -2 22 1 28 20 30 30 4
29 32 -2 32 -19 0 -22 4 -18 25 3 18 0 25 -12 25 -10 0 -16 -9 -16 -24 0 -22
-5 -24 -52 -28 -29 -2 -55 -3 -57 -2 -1 2 24 35 58 75 34 40 61 76 61 81 0 22
-24 1 -84 -74z M4460 6965 c0 -104 2 -125 15 -125 10 0 15 10 15 33 0 23 8 41
25 57 l26 24 52 -58 c29 -33 58 -57 64 -55 7 2 -8 26 -33 54 -69 77 -69 74
-13 135 76 83 48 73 -41 -15 l-80 -80 0 78 c0 62 -3 77 -15 77 -13 0 -15 -21
-15 -125z M4837 7013 c-32 -16 -47 -42 -47 -80 0 -76 99 -126 157 -80 24 18 2
27 -28 12 -25 -13 -34 -14 -56 -3 -14 7 -33 23 -41 36 l-14 22 81 0 c75 0 81
1 81 21 0 63 -74 103 -133 72z m95 -33 c10 -12 18 -26 18 -31 0 -5 -29 -9 -65
-9 -71 0 -83 13 -45 48 26 24 69 20 92 -8z M5090 7022 c-8 -3 -19 -10 -23 -16
-6 -8 -10 -7 -14 2 -14 34 -23 3 -23 -78 0 -53 4 -90 10 -90 6 0 10 27 10 60
0 51 4 65 25 85 31 32 50 31 81 0 23 -23 25 -33 22 -85 -2 -43 1 -60 10 -60
20 0 16 130 -6 157 -17 22 -62 34 -92 25z M5527 7013 c-33 -17 -35 -28 -4 -19
48 15 70 13 89 -6 29 -29 17 -42 -43 -48 -47 -4 -56 -9 -68 -34 -12 -26 -11
-30 8 -49 24 -25 87 -29 102 -6 8 11 12 11 16 2 12 -32 23 -7 23 56 0 51 -5
74 -18 91 -21 27 -69 33 -105 13z m87 -89 c40 -15 -5 -76 -50 -68 -37 7 -44
12 -44 34 0 32 52 50 94 34z M5773 7013 c-18 -9 -29 -11 -31 -4 -2 6 -8 11
-13 11 -5 0 -9 -41 -9 -91 0 -68 3 -90 13 -86 7 2 12 23 13 53 1 67 10 89 40
102 33 16 37 15 62 -8 18 -17 22 -32 22 -85 0 -37 4 -65 10 -65 6 0 10 30 10
69 0 51 -5 74 -18 91 -21 26 -63 32 -99 13z M5992 7014 c-39 -16 -58 -69 -42
-118 8 -26 20 -38 51 -50 37 -15 43 -15 75 0 21 10 31 21 26 26 -5 5 -20 3
-36 -6 -75 -39 -138 64 -73 120 26 23 57 23 85 1 19 -15 37 6 19 21 -21 18
-71 21 -105 6z M6180 7003 c-51 -42 -48 -114 6 -149 32 -22 91 -22 112 0 23
23 10 28 -26 10 -27 -13 -36 -13 -58 -2 -15 7 -34 23 -42 36 l-14 22 79 0 c44
0 82 3 85 6 12 11 -9 58 -33 77 -38 28 -76 28 -109 0z m111 -34 c6 -11 9 -23
6 -25 -3 -3 -34 -4 -71 -2 l-65 3 12 25 c22 45 93 44 118 -1z M8544 7004 c-7
-27 0 -149 11 -175 8 -21 58 -49 90 -49 41 0 82 26 94 61 6 18 11 67 11 110 0
65 -3 79 -16 79 -13 0 -15 -13 -14 -77 3 -108 3 -110 -22 -133 -28 -26 -68
-26 -102 1 -25 19 -26 25 -26 115 0 91 -12 123 -26 68z M8832 7007 c-43 -46
-22 -85 58 -108 75 -21 97 -50 62 -82 -23 -21 -68 -22 -106 -2 -37 20 -48 9
-17 -16 42 -34 143 -19 157 23 3 11 3 30 0 43 -6 22 -25 32 -133 70 -20 7 -15
53 6 65 24 12 71 13 91 1 20 -13 34 -2 19 15 -7 9 -31 14 -63 14 -42 0 -57 -5
-74 -23z M9050 6905 l0 -125 79 0 c64 0 83 4 105 21 32 25 34 61 5 90 -19 19
-20 22 -4 39 12 13 15 28 11 44 -12 47 -31 56 -117 56 l-79 0 0 -125z m161 84
c18 -18 19 -23 6 -42 -12 -18 -26 -23 -76 -25 l-61 -3 0 46 0 45 55 0 c42 0
60 -5 76 -21z m-1 -102 c16 -8 25 -21 25 -37 0 -32 -36 -50 -102 -50 l-53 0 0
50 0 50 53 0 c28 0 63 -6 77 -13z M1624 7003 c-6 -10 -26 -45 -44 -78 -56
-103 -55 -101 -77 -63 -11 18 -35 59 -53 91 -53 88 -61 79 -58 -65 3 -143 22
-165 25 -30 1 48 2 92 3 97 0 6 13 -12 29 -40 55 -99 66 -115 75 -112 5 2 30
38 55 80 l46 77 3 -100 c4 -149 22 -124 22 30 0 72 -3 130 -8 130 -4 0 -12 -8
-18 -17z M2421 6950 c-52 -12 -81 -92 -53 -142 18 -31 33 -39 80 -43 34 -3 84
27 69 41 -4 5 -11 3 -15 -3 -4 -6 -21 -13 -38 -17 -23 -5 -37 -2 -57 14 -48
38 -36 50 52 50 77 0 80 1 76 22 -11 56 -63 91 -114 78z m71 -37 c35 -32 23
-43 -47 -43 -69 0 -79 8 -48 42 20 22 70 23 95 1z M1745 6925 c-14 -13 -25
-30 -26 -37 -3 -30 3 -64 13 -84 32 -65 150 -43 169 31 10 34 -11 89 -39 104
-37 20 -91 13 -117 -14z m115 -17 c28 -30 26 -73 -6 -104 -19 -20 -31 -24 -52
-20 -56 14 -82 87 -45 128 23 25 77 24 103 -4z M1952 6874 c3 -46 10 -81 19
-89 21 -21 80 -25 106 -7 21 13 23 13 23 -3 0 -9 5 -15 11 -13 14 5 15 181 1
186 -5 1 -11 -28 -14 -69 -4 -79 -20 -101 -66 -93 -45 9 -52 22 -52 95 0 57
-3 69 -16 69 -14 0 -16 -11 -12 -76z M2185 6926 c-23 -34 -7 -59 47 -75 52
-16 69 -28 62 -46 -8 -20 -56 -28 -89 -15 -29 11 -45 6 -29 -10 17 -17 74 -20
109 -6 27 12 35 21 35 40 0 27 -41 56 -78 56 -25 0 -46 25 -38 46 5 13 16 16
56 12 28 -3 50 -2 50 2 0 12 -28 20 -71 20 -30 0 -42 -5 -54 -24z M5475 6661
c-3 -5 -1 -12 5 -16 12 -7 30 2 30 16 0 12 -27 12 -35 0z M8587 6613 c-4 -3
-7 -29 -7 -56 0 -48 -1 -49 -18 -34 -32 29 -99 18 -129 -22 -7 -9 -13 -32 -13
-52 0 -72 68 -117 130 -86 27 14 30 14 30 0 0 -8 5 -13 11 -11 6 2 10 50 10
132 0 127 -1 142 -14 129z m-27 -113 c43 -43 13 -130 -45 -130 -35 0 -75 42
-75 78 0 62 77 95 120 52z M4764 6585 c-23 -17 -38 -19 -28 -4 4 7 1 10 -7 7
-10 -3 -15 -28 -17 -91 -2 -73 0 -87 13 -87 12 0 15 13 15 63 1 66 12 90 48
101 12 4 20 12 17 17 -8 13 -18 11 -41 -6z M4883 6585 c-60 -26 -63 -133 -4
-165 30 -15 97 -12 111 5 17 21 7 29 -18 16 -32 -17 -70 -13 -92 9 -33 33 -24
40 48 40 58 0 91 7 95 21 4 15 -42 70 -66 79 -36 12 -35 12 -74 -5z m85 -25
c40 -37 29 -50 -43 -50 -72 0 -83 12 -45 50 26 26 60 25 88 0z M5092 6578
c-54 -34 -53 -117 3 -154 29 -19 102 -14 116 7 11 19 7 20 -27 7 -43 -16 -81
-3 -100 34 -15 30 -15 34 1 64 21 39 66 52 101 29 13 -8 25 -11 29 -5 3 5 -4
14 -15 20 -36 20 -75 19 -108 -2z M5280 6572 c-48 -48 -40 -120 17 -150 28
-16 86 -15 101 0 19 19 14 30 -7 18 -33 -17 -76 -12 -99 13 -12 13 -22 27 -22
33 0 5 35 9 77 9 l78 0 -3 30 c-4 39 -28 62 -74 70 -33 6 -41 3 -68 -23z m112
-24 c9 -13 14 -26 12 -30 -7 -11 -134 -10 -134 1 0 4 10 21 21 36 18 22 27 26
53 21 17 -4 39 -16 48 -28z M5804 6586 c-20 -9 -35 -25 -43 -46 -17 -49 -14
-65 18 -99 23 -24 37 -31 68 -31 39 0 84 22 71 35 -3 4 -15 2 -26 -4 -39 -21
-79 -13 -102 19 l-21 30 70 0 c96 0 101 3 88 47 -8 26 -20 40 -44 49 -39 17
-40 17 -79 0z m86 -26 c38 -38 27 -50 -45 -50 -58 0 -65 2 -65 20 0 20 42 49
73 50 9 0 26 -9 37 -20z M5990 6505 c0 -57 4 -95 10 -95 6 0 10 27 10 60 0 51
4 65 25 85 13 14 29 25 35 25 5 0 10 4 10 9 0 4 -11 6 -25 3 -14 -2 -25 -8
-25 -13 0 -5 -4 -9 -10 -9 -5 0 -10 7 -10 15 0 8 -4 15 -10 15 -6 0 -10 -38
-10 -95z M5480 6500 c0 -49 4 -90 9 -90 5 0 11 41 13 90 2 67 0 90 -9 90 -10
0 -13 -25 -13 -90z M5553 6583 c2 -5 11 -24 19 -43 9 -19 25 -56 37 -82 12
-27 27 -48 34 -48 7 0 27 31 44 69 39 87 47 111 35 111 -5 0 -25 -35 -44 -77
l-36 -78 -21 50 c-12 28 -27 62 -34 78 -7 15 -18 27 -25 27 -7 0 -11 -3 -9 -7z
M9140 6560 c0 -11 -7 -20 -15 -20 -8 0 -15 -4 -15 -10 0 -5 7 -10 15 -10 12 0
15 -14 15 -67 0 -38 4 -73 8 -79 10 -16 46 -26 66 -18 25 9 19 22 -8 17 -32
-6 -37 6 -39 82 -2 65 -2 65 26 65 15 0 27 5 27 10 0 6 -11 10 -25 10 -18 0
-25 5 -25 20 0 11 -7 20 -15 20 -8 0 -15 -9 -15 -20z M8231 6529 c-17 -7 -21
-13 -14 -20 8 -8 17 -8 31 0 44 23 92 8 92 -30 0 -12 -12 -18 -50 -21 -57 -6
-80 -21 -80 -54 0 -44 82 -73 110 -39 10 12 7 13 -22 9 -44 -7 -78 17 -62 43
8 12 26 19 59 21 47 3 48 3 42 -25 -7 -35 11 -73 24 -52 12 20 11 114 -2 143
-16 35 -74 47 -128 25z M8692 6529 c-13 -5 -20 -14 -17 -20 5 -8 10 -8 18 0
19 19 85 13 97 -8 18 -34 12 -41 -38 -41 -37 0 -54 -5 -67 -20 -26 -28 -26
-45 -4 -69 22 -23 59 -27 92 -10 16 9 22 9 25 0 2 -6 8 -11 13 -11 5 0 9 35 9
78 0 64 -4 82 -18 95 -20 18 -73 21 -110 6z m108 -106 c0 -26 -30 -53 -59 -53
-40 0 -55 14 -47 44 6 24 11 26 56 26 40 0 50 -3 50 -17z M8890 6409 c0 -99 3
-130 12 -127 8 3 12 19 10 41 -5 50 3 58 38 40 93 -46 177 78 102 150 -32 30
-72 35 -114 13 -24 -13 -28 -13 -28 0 0 8 -4 14 -10 14 -6 0 -10 -50 -10 -131z
m147 85 c20 -20 24 -31 19 -57 -8 -38 -22 -53 -58 -62 -22 -6 -33 -1 -58 23
-35 36 -37 46 -10 90 25 41 69 44 107 6z M9305 6528 c-33 -19 -44 -41 -45 -85
0 -34 5 -46 31 -67 34 -29 83 -34 113 -11 26 20 15 28 -19 15 -31 -12 -61 -6
-87 18 -37 32 -22 42 63 42 l81 0 -7 28 c-15 61 -77 90 -130 60z m87 -25 c35
-32 23 -43 -47 -43 -69 0 -78 7 -45 40 24 24 67 26 92 3z M9497 6533 c-4 -3
-7 -46 -7 -95 0 -72 3 -88 15 -88 12 0 15 14 15 70 0 70 0 71 35 91 19 12 35
23 35 25 0 11 -40 2 -54 -12 -15 -14 -16 -14 -16 0 0 17 -12 21 -23 9z M5252
5550 c-148 -31 -268 -116 -320 -228 -22 -46 -27 -71 -26 -127 1 -84 21 -133
69 -172 56 -47 108 -53 413 -53 299 1 321 4 388 55 104 79 97 264 -14 382
-127 135 -307 185 -510 143z m279 -51 c92 -30 130 -52 183 -104 62 -60 90
-122 91 -201 0 -77 -34 -133 -97 -163 -40 -19 -61 -20 -338 -18 -335 3 -349 5
-397 78 -22 33 -27 51 -26 102 0 34 7 76 14 92 45 99 139 178 254 212 99 30
228 31 316 2z M5257 5412 c-25 -27 -27 -33 -27 -125 0 -53 5 -107 11 -121 16
-35 55 -45 154 -43 118 4 126 12 128 138 1 52 -1 106 -6 121 -11 42 -53 58
-150 58 -78 0 -85 -2 -110 -28z m204 -22 c17 -9 19 -22 19 -104 0 -117 -4
-121 -111 -121 -43 0 -81 4 -86 8 -4 5 -9 49 -10 98 -3 116 7 129 103 129 36
0 74 -5 85 -10z M1658 4838 l-3 -303 -27 -3 -28 -3 0 -239 0 -239 350 -3 350
-3 0 243 c0 235 -1 242 -20 242 -20 0 -20 7 -20 305 l0 305 -300 0 -300 0 -2
-302z m560 -35 l-3 -298 -257 -3 -258 -2 0 300 0 300 260 0 260 0 -2 -297z
m42 -515 l0 -198 -310 0 -310 0 0 193 c0 107 2 196 4 198 2 2 142 4 310 4
l306 0 0 -197z M1768 4833 l3 -58 70 3 69 4 0 54 0 54 -72 0 -73 0 3 -57z
m120 0 l3 -33 -50 0 -51 0 0 36 0 35 48 -3 c45 -3 47 -4 50 -35z M2010 4835
l0 -55 70 0 70 0 0 55 0 55 -70 0 -70 0 0 -55z m120 0 l0 -35 -50 0 -50 0 0
35 0 35 50 0 50 0 0 -35z M5315 4896 c-80 -35 -117 -153 -84 -268 28 -102 153
-146 235 -83 52 40 65 74 67 176 1 75 -2 93 -19 117 -50 66 -128 89 -199 58z
m103 -37 c46 -16 72 -67 72 -140 0 -33 -6 -75 -14 -94 -25 -58 -100 -82 -152
-49 -48 32 -60 76 -55 208 1 26 68 86 95 86 13 0 37 -5 54 -11z M5325 4791
c-21 -24 -24 -110 -5 -146 26 -48 101 -40 121 12 16 41 6 121 -17 138 -28 21
-78 19 -99 -4z m95 -27 c13 -32 13 -80 0 -103 -13 -25 -35 -32 -65 -21 -23 9
-25 15 -25 68 0 65 11 82 52 82 21 0 31 -6 38 -26z M7937 4661 c-5 -20 -12
-21 -116 -21 l-111 0 0 -135 0 -135 115 0 c96 0 115 -3 115 -15 0 -13 33 -15
240 -15 181 0 249 3 273 14 43 17 85 68 93 111 l6 35 524 0 524 0 6 -25 c3
-14 4 -37 2 -50 l-5 -25 173 0 c109 0 174 4 174 10 0 6 28 10 65 10 l65 0 0
100 0 100 -59 0 c-33 0 -63 5 -66 10 -4 6 -74 10 -178 10 l-172 0 3 -50 4 -50
-531 0 -531 0 -11 28 c-13 34 -45 69 -84 90 -25 14 -70 18 -271 20 -237 3
-241 3 -247 -17z m534 -71 c40 -38 41 -41 37 -91 -4 -40 -12 -58 -37 -82 l-31
-32 -228 -3 -227 -3 -3 86 c-2 47 -1 104 3 125 l7 40 218 0 219 0 42 -40z
m-509 -85 l0 -90 -106 -3 -106 -3 0 96 0 96 106 -3 106 -3 0 -90z m1948 13 l0
-73 -130 3 -130 3 0 70 0 69 130 0 130 0 0 -72z m128 0 l3 -58 -56 0 -55 0 0
61 0 60 53 -3 52 -3 3 -57z M4982 4452 c-32 -5 -102 -74 -104 -103 -6 -97 13
-139 99 -225 75 -75 156 -120 268 -151 103 -28 191 -24 305 12 139 44 238 116
307 224 42 66 49 119 21 174 -36 71 -41 72 -483 72 -214 0 -400 -1 -413 -3z
m820 -47 c26 -10 50 -54 50 -90 0 -60 -78 -162 -169 -221 -178 -114 -357 -128
-532 -42 -88 44 -185 130 -216 190 -27 56 -15 126 27 150 37 21 792 33 840 13z
M2486 3713 c-12 -12 -6 -33 9 -33 8 0 15 6 15 14 0 17 -14 28 -24 19z M852
3597 c3 -118 13 -136 78 -144 54 -8 96 12 109 49 13 37 15 198 2 198 -5 0 -10
-42 -13 -94 -3 -75 -8 -99 -22 -115 -24 -27 -76 -27 -107 -2 -22 17 -24 29
-27 115 -2 55 -8 96 -14 96 -6 0 -9 -39 -6 -103z M1153 3690 c-27 -11 -42 -37
-35 -64 8 -33 21 -45 66 -57 79 -21 107 -56 69 -85 -22 -16 -87 -18 -115 -3
-23 12 -39 2 -18 -11 50 -32 145 -22 162 16 22 47 1 75 -72 98 -70 23 -87 47
-55 80 18 18 76 21 106 6 15 -8 19 -7 19 3 0 24 -83 35 -127 17z M1360 3695
c-1 -3 -1 -58 -1 -122 l0 -118 78 -3 c68 -2 81 0 101 18 29 27 29 74 1 94 -20
14 -20 16 -5 31 17 17 21 52 8 72 -13 20 -61 33 -123 33 -32 0 -59 -2 -59 -5z
m142 -27 c49 -40 23 -78 -60 -85 l-62 -6 0 52 0 51 53 0 c29 0 60 -6 69 -12z
m25 -124 c33 -45 9 -67 -76 -72 l-71 -4 0 44 c0 25 3 48 7 52 5 4 35 6 67 3
42 -3 63 -10 73 -23z M5590 3565 c0 -83 4 -135 10 -135 6 0 10 13 10 28 0 17
10 37 23 50 l23 22 34 -45 c31 -42 57 -61 68 -51 2 3 -14 26 -37 52 -22 26
-41 51 -41 55 0 5 17 24 38 44 22 21 31 35 22 35 -8 0 -39 -23 -68 -50 -30
-28 -55 -50 -58 -50 -2 0 -4 41 -4 90 0 53 -4 90 -10 90 -6 0 -10 -52 -10
-135z M9290 3565 c0 -83 4 -135 10 -135 6 0 10 7 10 15 0 19 0 19 39 -1 61
-30 131 12 131 80 0 30 -21 72 -44 88 -19 13 -83 8 -96 -7 -22 -27 -29 -16
-27 40 2 40 0 55 -10 55 -10 0 -13 -31 -13 -135z m147 9 c20 -20 24 -31 19
-57 -7 -36 -38 -67 -68 -67 -11 0 -33 14 -50 30 -24 25 -29 36 -23 58 16 65
75 83 122 36z M9540 3565 c0 -83 4 -135 10 -135 6 0 10 52 10 135 0 83 -4 135
-10 135 -6 0 -10 -52 -10 -135z M4332 3679 c-38 -11 -58 -45 -48 -83 5 -21 17
-29 68 -43 71 -22 96 -48 74 -79 -11 -15 -26 -19 -65 -18 -27 0 -57 6 -65 13
-13 11 -16 11 -16 -1 0 -20 43 -38 89 -38 47 0 94 38 89 72 -4 26 -61 68 -93
68 -13 0 -33 9 -46 19 -20 16 -22 23 -13 38 19 31 57 44 93 32 33 -12 52 -6
35 10 -15 15 -67 20 -102 10z M8333 3675 c-79 -34 -57 -113 37 -130 49 -9 70
-23 70 -44 0 -45 -68 -65 -122 -37 -15 8 -30 11 -33 6 -10 -17 29 -34 80 -34
66 -1 98 22 93 68 -3 33 -16 42 -100 71 -40 14 -48 21 -48 41 0 33 50 56 94
43 38 -11 46 -11 46 -1 0 5 -17 14 -39 20 -46 14 -39 14 -78 -3z M8020 3582
c0 -91 2 -100 24 -121 15 -14 42 -25 68 -28 38 -4 48 -1 76 25 32 30 32 31 32
126 0 53 -4 96 -9 96 -5 0 -11 -43 -13 -96 -4 -87 -6 -98 -29 -115 -34 -26
-75 -24 -104 6 -23 22 -25 32 -25 115 0 53 -4 90 -10 90 -6 0 -10 -39 -10 -98z
M8525 3555 l0 -125 60 0 c86 0 133 15 141 45 8 34 -3 75 -21 75 -19 0 -19 9 0
34 20 27 19 52 -5 76 -17 17 -33 20 -97 20 l-78 0 0 -125z m159 90 c17 -17 12
-43 -12 -62 -9 -7 -40 -13 -69 -13 l-53 0 0 45 0 45 59 0 c40 0 65 -5 75 -15z
m11 -115 c15 -17 16 -24 7 -45 -13 -28 -41 -38 -103 -33 l-44 3 -3 48 -3 47
64 0 c51 0 67 -4 82 -20z M6020 3642 c0 -12 -9 -23 -22 -27 l-23 -8 23 -4 c20
-4 22 -10 22 -73 0 -87 22 -114 74 -94 25 10 19 22 -8 17 -33 -7 -48 24 -44
90 3 48 5 52 31 55 15 2 27 7 27 12 0 5 -12 10 -27 12 -16 2 -28 9 -28 17 0 8
-6 16 -12 19 -8 2 -13 -5 -13 -16z M1728 3634 c-12 -11 -10 -184 2 -184 6 0
10 24 10 53 0 77 8 97 41 104 16 3 29 12 29 20 0 15 -33 10 -57 -9 -7 -6 -12
-4 -15 7 -1 9 -6 13 -10 9z M1894 3630 c-52 -21 -71 -102 -35 -149 24 -31 109
-42 130 -17 19 23 10 28 -25 14 -38 -14 -80 -4 -94 22 -18 33 -3 41 71 39 38
-1 72 1 75 4 11 11 -17 65 -42 82 -26 17 -48 18 -80 5z m76 -30 c38 -38 27
-51 -42 -48 -56 3 -63 5 -60 23 8 44 68 59 102 25z M2114 3630 c-12 -4 -31
-21 -43 -36 -41 -52 -10 -128 56 -140 35 -7 93 11 93 28 0 6 -8 7 -17 3 -71
-30 -123 -7 -123 55 0 64 56 98 106 65 33 -22 46 -9 15 16 -25 20 -53 23 -87
9z M2279 3611 c-62 -62 -21 -161 66 -161 46 0 87 22 66 35 -7 4 -18 2 -24 -4
-27 -27 -117 7 -117 45 0 13 15 15 78 13 l77 -2 -2 29 c-3 37 -45 74 -84 74
-20 0 -40 -10 -60 -29z m106 -16 c38 -37 32 -45 -39 -45 -76 0 -91 13 -52 47
35 30 61 29 91 -2z M2490 3545 c0 -57 4 -95 10 -95 6 0 10 38 10 95 0 57 -4
95 -10 95 -6 0 -10 -38 -10 -95z M2557 3633 c-3 -5 8 -35 24 -68 15 -33 33
-72 38 -87 13 -36 39 -36 52 -1 5 15 23 56 40 91 16 36 28 66 27 68 -12 11
-45 -44 -78 -132 -9 -23 -30 -26 -30 -3 0 14 -58 139 -64 139 -3 0 -7 -3 -9
-7z M2815 3631 c-47 -20 -69 -76 -51 -129 15 -44 101 -68 142 -38 24 18 13 31
-14 17 -46 -25 -112 2 -112 45 0 13 13 15 78 13 42 -1 76 1 75 4 -2 4 -3 16
-3 26 0 46 -68 82 -115 62z m65 -26 c18 -9 30 -24 30 -35 0 -18 -7 -20 -65
-20 -73 0 -84 14 -39 49 32 25 36 25 74 6z M2986 3624 c-3 -9 -6 -51 -6 -95 0
-64 3 -79 15 -79 12 0 15 14 15 64 0 65 14 96 45 96 8 0 16 4 20 9 10 17 -11
20 -39 5 -33 -16 -39 -17 -31 -5 3 5 1 12 -4 15 -5 3 -12 -1 -15 -10z M4511
3507 c-1 -62 1 -121 5 -130 11 -29 24 -8 24 38 l0 44 28 -15 c69 -36 133 6
132 89 0 18 -6 39 -13 48 -29 38 -103 52 -127 24 -15 -18 -31 -20 -27 -4 1 6
-3 13 -9 16 -8 3 -12 -30 -13 -110z m135 85 c17 -11 34 -48 34 -76 0 -29 -42
-66 -74 -66 -58 0 -89 87 -46 130 20 20 65 26 86 12z M4768 3609 c-28 -16 -22
-30 8 -19 36 14 60 12 78 -6 28 -27 20 -44 -21 -44 -76 0 -116 -48 -77 -91 20
-22 76 -26 93 -6 9 11 3 14 -30 13 -28 -1 -45 4 -52 16 -18 27 8 48 60 48 45
0 45 0 40 -29 -4 -17 0 -36 8 -47 13 -18 14 -18 20 -1 4 10 5 45 3 79 -4 48
-9 65 -27 79 -25 21 -76 24 -103 8z M4967 3613 c-9 -8 -9 -146 -1 -167 14 -35
24 -12 24 52 0 72 16 102 53 102 9 0 17 5 17 11 0 13 -53 2 -58 -12 -2 -5 -8
-9 -13 -9 -4 0 -6 7 -3 15 6 15 -7 21 -19 8z M5129 3605 c-43 -23 -56 -85 -29
-135 16 -29 82 -46 119 -30 32 13 45 37 14 25 -46 -17 -69 -16 -96 5 -49 38
-36 50 53 50 44 0 80 2 80 5 0 3 -6 21 -14 40 -23 54 -73 70 -127 40z m89 -21
c42 -29 28 -44 -43 -44 -69 0 -79 8 -48 42 20 22 61 23 91 2z M5422 3604 c-24
-17 -29 -56 -9 -72 6 -5 30 -13 52 -16 60 -10 72 -43 21 -61 -18 -6 -37 -5
-56 2 -17 6 -32 7 -35 3 -8 -13 63 -33 93 -25 37 9 52 24 52 51 0 28 -12 36
-70 49 -39 9 -45 14 -45 35 0 24 3 25 53 24 53 -2 69 9 30 20 -37 10 -62 7
-86 -10z M5818 3613 c-10 -2 -18 -11 -18 -20 0 -11 5 -13 18 -8 53 23 102 11
102 -24 0 -15 -10 -19 -50 -23 -57 -6 -80 -21 -80 -54 0 -34 41 -58 85 -50 19
4 35 12 35 19 0 7 6 3 13 -8 10 -17 13 -10 17 50 6 80 -3 111 -34 119 -24 6
-62 6 -88 -1z m102 -116 c0 -30 -39 -52 -75 -43 -61 15 -37 66 31 66 39 0 44
-2 44 -23z M6185 3608 c-50 -29 -61 -98 -22 -143 27 -32 92 -43 121 -20 27 21
16 26 -27 13 -33 -10 -41 -9 -65 9 -15 11 -28 28 -30 37 -3 14 8 16 77 16 75
0 81 1 81 21 0 11 -13 34 -29 50 -31 31 -70 37 -106 17z m75 -18 c11 -6 24
-19 30 -30 10 -19 7 -20 -60 -20 -74 0 -83 7 -50 40 22 22 51 26 80 10z M6350
3610 c0 -5 25 -10 55 -10 30 0 55 -2 55 -5 0 -3 -6 -13 -14 -22 -79 -97 -104
-131 -99 -137 10 -10 141 -7 148 4 3 5 -20 10 -56 12 l-62 3 61 76 c34 43 62
80 62 83 0 3 -34 6 -75 6 -43 0 -75 -4 -75 -10z M8913 3610 c-55 -22 -68 -109
-23 -154 24 -25 97 -29 123 -7 26 21 10 32 -21 14 -40 -23 -76 -11 -96 32 -15
30 -15 39 -4 62 18 39 61 52 99 31 33 -19 52 -5 23 17 -21 16 -68 18 -101 5z
M9087 3606 c-21 -16 -9 -29 13 -15 27 16 78 11 90 -10 17 -33 12 -38 -45 -43
-60 -5 -86 -26 -79 -63 8 -43 69 -59 112 -29 20 14 22 14 22 0 0 -9 5 -16 10
-16 6 0 10 33 10 78 0 64 -4 82 -18 95 -23 21 -90 22 -115 3z m113 -105 c0
-24 -36 -51 -66 -51 -28 0 -47 24 -39 50 5 17 15 20 56 20 42 0 49 -3 49 -19z
M9657 3609 c-11 -6 -25 -27 -33 -46 -36 -84 46 -160 131 -121 14 6 25 15 25
20 0 11 -28 10 -35 -2 -11 -18 -63 -11 -85 12 -37 40 -27 48 60 48 44 0 80 2
80 5 0 3 -7 21 -15 40 -20 49 -83 70 -128 44z m95 -26 c35 -32 23 -43 -47 -43
-71 0 -85 15 -43 44 29 21 67 20 90 -1z M1325 2687 c-13 -7 -15 -62 -15 -407
l1 -399 47 -3 47 -3 3 -42 3 -43 45 0 44 0 0 -40 0 -40 333 -2 c182 -1 426 -2
541 -2 l209 -1 2 413 2 412 -43 0 c-42 0 -43 0 -46 38 -3 36 -4 37 -47 42 -45
5 -45 5 -45 45 l-1 40 -533 0 c-293 0 -539 -3 -547 -8z m1020 -399 c0 -197 -3
-358 -7 -358 -5 0 -224 0 -488 0 l-480 0 0 355 0 355 368 3 c202 1 421 3 487
2 l120 0 0 -357z m95 -87 l0 -359 -102 -4 c-56 -2 -277 -2 -490 0 l-388 4 0
34 0 34 455 -2 455 -3 0 322 0 322 33 4 c17 2 33 5 35 5 1 1 2 -159 2 -357z
m93 267 c-2 -5 -3 -166 -3 -358 l0 -351 -469 1 c-258 1 -478 4 -490 7 -14 4
-21 13 -21 30 l0 24 455 -3 455 -3 0 333 0 333 38 -3 c20 -2 36 -6 35 -10z
M1831 2446 c-26 -31 20 -74 53 -50 16 12 18 18 8 39 -13 27 -43 33 -61 11z
M1837 2353 c-4 -3 -7 -60 -7 -125 l0 -118 30 0 30 0 0 125 c0 124 0 125 -23
125 -13 0 -27 -3 -30 -7z M1580 1301 c0 -141 15 -167 99 -176 35 -3 47 1 73
24 28 25 31 34 36 103 2 42 2 88 -2 103 -13 50 -26 13 -26 -74 0 -70 -4 -93
-18 -111 -24 -30 -78 -33 -109 -6 -27 24 -31 44 -32 144 -1 41 -5 72 -11 72
-6 0 -10 -33 -10 -79z M1862 1294 c-12 -8 -22 -23 -22 -33 0 -25 32 -51 62
-51 14 0 36 -8 49 -19 l23 -18 -26 -17 c-24 -16 -52 -16 -90 -1 -10 4 -18 3
-18 -3 0 -16 65 -34 96 -26 76 19 68 84 -13 99 -52 9 -65 22 -52 47 10 18 18
20 60 15 27 -3 49 -2 49 2 0 25 -85 28 -118 5z M2049 1281 c-32 -33 -37 -67
-14 -110 23 -46 101 -61 142 -28 23 17 4 26 -25 11 -45 -22 -88 -6 -105 39 -6
15 2 17 73 17 86 0 94 6 68 53 -29 56 -93 64 -139 18z m115 -13 c9 -12 16 -26
16 -30 0 -5 -31 -8 -70 -8 -38 0 -70 4 -70 10 0 5 10 18 22 30 30 28 82 27
102 -2z M2260 1220 c0 -53 4 -90 10 -90 6 0 10 28 10 63 0 67 17 97 53 97 9 0
17 5 17 10 0 13 -26 13 -51 0 -13 -7 -19 -7 -19 0 0 5 -4 10 -10 10 -6 0 -10
-37 -10 -90z M1100 904 c0 -57 0 -57 -20 -39 -26 24 -84 23 -110 0 -11 -10
-24 -32 -30 -51 -26 -77 63 -145 137 -106 16 9 22 9 26 -1 3 -9 8 -9 16 -1 17
15 11 246 -6 252 -10 3 -13 -12 -13 -54z m-40 -49 c54 -28 54 -100 1 -127 -30
-15 -37 -15 -62 -2 -51 26 -51 101 -1 128 34 19 28 19 62 1z M2706 921 c-3 -5
-3 -16 0 -25 4 -12 0 -16 -15 -16 -12 0 -21 -4 -21 -10 0 -5 8 -10 18 -10 16
0 18 -7 14 -57 -5 -68 20 -112 60 -107 13 1 27 3 31 3 5 1 5 6 2 11 -4 6 -10
8 -15 5 -5 -3 -19 -1 -30 5 -17 9 -20 21 -20 75 l0 65 30 0 c17 0 30 5 30 10
0 6 -13 10 -30 10 -23 0 -30 5 -30 19 0 22 -16 36 -24 22z M1240 881 c-31 -9
-68 -62 -65 -94 8 -73 79 -112 141 -79 34 18 47 40 47 83 0 65 -58 107 -123
90z m83 -43 c9 -11 17 -33 17 -49 0 -29 -29 -68 -52 -70 -57 -5 -88 20 -88 73
0 33 5 41 32 58 37 22 64 19 91 -12z M1463 875 c-73 -32 -82 -117 -16 -161 36
-24 88 -24 112 1 18 17 7 33 -12 17 -46 -38 -120 -4 -120 56 0 59 53 93 106
67 36 -18 37 -18 37 -4 0 5 -16 17 -35 25 -40 17 -32 17 -72 -1z M1930 882
c-8 -3 -23 -11 -32 -20 -16 -14 -18 -13 -18 1 0 10 -4 17 -10 17 -6 0 -10 -37
-10 -91 0 -68 3 -90 13 -87 7 3 14 31 17 72 5 71 15 86 61 86 39 0 49 -18 49
-92 0 -54 3 -68 15 -68 12 0 15 14 15 64 0 75 14 96 65 96 45 0 58 -25 53
-101 -2 -42 1 -59 10 -59 8 0 12 21 12 70 0 57 -4 74 -20 90 -11 11 -26 21
-32 21 -28 3 -54 -3 -76 -18 -21 -13 -25 -13 -35 0 -13 16 -54 26 -77 19z
M2299 882 c-69 -8 -97 -108 -46 -160 20 -20 35 -25 68 -24 42 2 69 15 69 34 0
7 -5 6 -13 -1 -41 -34 -127 -4 -127 45 0 2 34 4 75 4 85 0 96 9 71 58 -17 32
-55 50 -97 44z m64 -44 l32 -33 -69 -3 c-37 -2 -71 0 -74 3 -14 14 41 64 71
65 5 0 23 -15 40 -32z M2537 879 c-16 -4 -36 -14 -45 -21 -13 -11 -15 -11 -9
5 4 11 2 17 -8 17 -12 0 -15 -18 -15 -90 0 -65 3 -90 13 -90 8 0 12 19 12 63
0 73 16 97 64 97 47 0 61 -22 61 -96 0 -36 4 -64 10 -64 6 0 10 29 10 68 0 96
-27 129 -93 111z M2867 878 c-31 -11 -46 -50 -27 -73 7 -8 30 -18 52 -21 22
-4 46 -14 55 -22 14 -14 13 -17 -5 -33 -21 -16 -49 -18 -91 -4 -23 7 -30 -12
-8 -21 53 -20 124 -3 133 31 8 34 0 43 -61 61 -62 18 -69 24 -52 48 15 20 44
26 74 15 12 -5 25 -4 28 1 5 8 -14 17 -52 24 -10 2 -30 -1 -46 -6z M1620 810
c0 -57 4 -74 20 -90 11 -11 26 -21 33 -21 6 -1 19 -2 27 -3 8 -1 26 5 39 13
18 10 26 11 29 3 2 -7 8 -12 13 -12 5 0 9 41 9 90 0 75 -3 90 -16 90 -12 0
-15 -12 -13 -66 3 -75 -11 -97 -62 -97 -46 0 -59 23 -55 99 3 49 0 64 -10 64
-11 0 -14 -17 -14 -70z&quot;&#x2F;&gt;
&lt;&#x2F;g&gt;
&lt;&#x2F;svg&gt;

&lt;&#x2F;figure&gt;
&lt;p&gt;The important distinction is simple: the bottom slider selects the active
connection mode. It is not a recommendation for when to play, when to stop,
or where to put the mouse after a session.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-receiver-mode-slider&quot;&gt;The receiver mode slider&lt;a class=&quot;post-anchor&quot; href=&quot;#the-receiver-mode-slider&quot; aria-label=&quot;Anchor link for: the-receiver-mode-slider&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;blog.s99.sh&#x2F;blog&#x2F;mouse-settings-explained&#x2F;mouse-bottom.png&quot; alt=&quot;Bottom of the mouse showing the three-position slider and the two buttons&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The three-position slider on the bottom is a power and receiver selector:&lt;&#x2F;p&gt;
&lt;p&gt;• Enhanced wireless receiver mode — use this when the enhanced receiver is
the device plugged into the computer. This is the larger directional
receiver that also works as a pogo-pin charging dock.
• Off — use this when you want the mouse powered down, such as for travel or
storage.
• USB wireless receiver mode — use this when the small USB receiver&#x2F;dongle
is the device plugged into the computer.&lt;&#x2F;p&gt;
&lt;p&gt;Set the slider to match the receiver currently connected to the computer. If
the enhanced receiver is connected, use enhanced receiver mode. If the USB
dongle is connected, use USB receiver mode. If neither receiver is connected
and you do not want the mouse awake, turn it off.&lt;&#x2F;p&gt;
&lt;figure class=&quot;inline-svg&quot; role=&quot;img&quot; aria-label=&quot;Slider set to enhanced receiver mode&quot;&gt;
&lt;svg xmlns=&quot;http:&#x2F;&#x2F;www.w3.org&#x2F;2000&#x2F;svg&quot; viewBox=&quot;0 0 1068.000000 512.000000&quot; preserveAspectRatio=&quot;xMidYMid meet&quot; role=&quot;img&quot;&gt;
&lt;g transform=&quot;translate(0.000000,512.000000) scale(0.100000,-0.100000)&quot; fill=&quot;var(--accent-color, #d2003d)&quot; stroke=&quot;none&quot;&gt;
&lt;path d=&quot;M4018 4463 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z
M3668 4423 c12 -2 30 -2 40 0 9 3 -1 5 -23 4 -22 0 -30 -2 -17 -4z M3908 4423
c12 -2 32 -2 45 0 12 2 2 4 -23 4 -25 0 -35 -2 -22 -4z M4702 3670 c0 -19 2
-27 5 -17 2 9 2 25 0 35 -3 9 -5 1 -5 -18z M4742 3555 c0 -16 2 -22 5 -12 2 9
2 23 0 30 -3 6 -5 -1 -5 -18z M2911 3544 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3
4 -6 -2 -5 -13z M5690 3516 c-110 -30 -188 -94 -237 -194 l-28 -57 -3 -351 -3
-351 -352 -107 c-194 -59 -383 -117 -422 -129 -75 -22 -102 -37 -69 -37 11 0
46 -7 78 -15 33 -9 73 -17 90 -20 28 -4 496 -98 623 -125 l52 -11 3 -392 c3
-378 4 -394 25 -439 28 -62 106 -144 163 -173 89 -45 113 -46 1065 -43 l900 3
58 24 c81 32 166 117 198 196 l24 60 3 910 c2 651 0 927 -8 970 -23 117 -97
211 -208 263 l-57 27 -925 2 c-715 1 -935 -1 -970 -11z m1958 -70 c60 -33 101
-76 134 -139 l23 -42 3 -947 c2 -937 2 -947 -18 -1002 -26 -67 -87 -133 -155
-166 l-50 -25 -950 0 -950 0 -54 30 c-75 41 -127 104 -151 183 -18 59 -20 93
-20 441 l0 378 -302 61 c-167 34 -329 67 -361 73 -31 7 -56 13 -54 15 2 2 163
53 358 113 l354 109 5 358 c5 340 6 361 26 405 32 69 91 128 159 161 l60 28
945 -2 945 -3 53 -29z M6920 3141 l0 -91 -285 0 -285 0 0 -55 0 -55 285 0 285
0 0 -90 c0 -49 3 -90 7 -90 3 0 14 6 22 14 9 7 85 59 169 116 84 57 149 106
145 110 -9 8 -312 213 -330 224 -10 5 -13 -14 -13 -83z M6498 2553 c117 -2
306 -2 420 0 114 1 18 2 -213 2 -231 0 -324 -1 -207 -2z M6503 2533 c114 -2
300 -2 415 0 114 1 20 2 -208 2 -228 0 -322 -1 -207 -2z M6740 2216 c0 -2 8
-10 18 -17 15 -13 16 -12 3 4 -13 16 -21 21 -21 13z M6153 1893 c37 -2 96 -2
130 0 34 2 4 3 -68 3 -71 0 -99 -1 -62 -3z M6643 1810 c0 -41 2 -58 4 -37 2
20 2 54 0 75 -2 20 -4 3 -4 -38z M4419 3493 c-13 -16 -12 -17 4 -4 16 13 21
21 13 21 -2 0 -10 -8 -17 -17z M2872 3425 c0 -16 2 -22 5 -12 2 9 2 23 0 30
-3 6 -5 -1 -5 -18z M3468 3383 c56 -2 148 -2 205 0 56 1 10 3 -103 3 -113 0
-159 -2 -102 -3z M4692 3280 c0 -19 2 -27 5 -17 2 9 2 25 0 35 -3 9 -5 1 -5
-18z M4732 3175 c0 -16 2 -22 5 -12 2 9 2 23 0 30 -3 6 -5 -1 -5 -18z M2922
3115 c0 -16 2 -22 5 -12 2 9 2 23 0 30 -3 6 -5 -1 -5 -18z M4682 2910 c0 -19
2 -27 5 -17 2 9 2 25 0 35 -3 9 -5 1 -5 -18z M4722 2805 c0 -16 2 -22 5 -12 2
9 2 23 0 30 -3 6 -5 -1 -5 -18z M4064 2705 c0 -49 1 -71 3 -48 2 23 2 64 0 90
-2 26 -3 8 -3 -42z M2932 2745 c0 -16 2 -22 5 -12 2 9 2 23 0 30 -3 6 -5 -1
-5 -18z M2891 2634 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z
M4672 2545 c0 -16 2 -22 5 -12 2 9 2 23 0 30 -3 6 -5 -1 -5 -18z M4712 2440
c0 -19 2 -27 5 -17 2 9 2 25 0 35 -3 9 -5 1 -5 -18z M2842 1720 c0 -19 2 -27
5 -17 2 9 2 25 0 35 -3 9 -5 1 -5 -18z M2842 1655 c0 -16 2 -22 5 -12 2 9 2
23 0 30 -3 6 -5 -1 -5 -18z M3233 1593 c9 -2 25 -2 35 0 9 3 1 5 -18 5 -19 0
-27 -2 -17 -5z M3180 1175 c13 -14 26 -25 28 -25 3 0 -5 11 -18 25 -13 14 -26
25 -28 25 -3 0 5 -11 18 -25z M3155 950 c10 -11 20 -20 23 -20 3 0 -3 9 -13
20 -10 11 -20 20 -23 20 -3 0 3 -9 13 -20z M3758 893 c7 -3 16 -2 19 1 4 3 -2
6 -13 5 -11 0 -14 -3 -6 -6z M3848 893 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0
-14 -3 -6 -6z M3783 873 c9 -2 25 -2 35 0 9 3 1 5 -18 5 -19 0 -27 -2 -17 -5z&quot;&#x2F;&gt;
&lt;&#x2F;g&gt;
&lt;g transform=&quot;translate(0.000000,512.000000) scale(0.100000,-0.100000)&quot; fill=&quot;var(--text-color, currentColor)&quot; stroke=&quot;none&quot;&gt;
&lt;path d=&quot;M3530 4460 c-25 -4 -63 -11 -85 -14 -164 -26 -356 -135 -446 -254
-84 -110 -125 -238 -132 -402 -2 -58 13 -702 18 -800 10 -168 14 -628 6 -670
-5 -25 -12 -67 -15 -95 -10 -80 -17 -117 -28 -160 -30 -107 -43 -219 -43 -370
0 -149 6 -192 44 -310 101 -310 293 -534 551 -643 138 -58 228 -76 390 -76
167 -1 226 6 330 41 217 71 379 193 507 380 43 62 109 201 141 298 34 101 56
269 48 354 -3 31 -8 85 -12 121 -3 36 -13 97 -21 135 -8 39 -23 113 -32 165
-15 84 -20 95 -40 98 -20 3 -22 0 -17 -30 3 -18 8 -46 11 -63 3 -16 8 -48 11
-70 4 -22 10 -47 14 -55 4 -8 11 -35 14 -60 4 -25 10 -72 15 -105 13 -88 11
-290 -4 -365 -7 -36 -24 -96 -37 -135 -130 -362 -360 -574 -708 -652 -80 -17
-259 -25 -320 -13 -25 4 -63 11 -85 14 -122 19 -276 88 -385 174 -179 140
-341 434 -368 667 -14 120 -5 277 22 418 3 12 14 67 25 122 11 55 23 123 26
150 4 28 9 64 12 80 6 30 5 245 -2 430 -19 519 -25 1083 -12 1155 5 25 25 83
45 130 30 73 47 96 113 161 101 100 239 169 384 194 22 4 51 9 65 12 46 9 404
10 510 1 207 -16 394 -96 514 -222 52 -53 117 -178 131 -251 5 -22 10 -47 12
-55 6 -21 5 -375 -1 -570 -18 -572 -24 -965 -15 -974 3 -4 13 -4 22 0 13 5 16
19 14 67 -2 55 6 392 18 747 2 69 7 253 10 410 4 254 3 294 -14 365 -39 169
-113 284 -245 377 -96 68 -143 88 -296 129 -65 18 -113 21 -345 24 -148 1
-290 -1 -315 -5z M3625 4259 c-117 -24 -258 -94 -336 -166 -157 -146 -227
-369 -161 -514 29 -62 96 -130 162 -162 l55 -27 470 -2 470 -3 50 24 c101 50
159 116 188 217 37 127 -25 315 -149 450 -70 76 -193 146 -314 179 -97 27
-318 29 -435 4z m351 -9 c268 -44 458 -204 523 -441 46 -165 -15 -300 -170
-378 l-64 -32 -460 6 c-515 7 -496 4 -582 82 -152 138 -125 383 62 569 98 98
218 159 370 189 82 16 238 18 321 5z M3727 2998 c-24 -6 -57 -29 -87 -59 -61
-61 -74 -111 -68 -265 5 -125 22 -172 83 -219 100 -75 239 -73 329 7 63 55 77
98 77 238 1 141 -10 186 -55 232 -68 70 -171 95 -279 66z m202 -32 c87 -45
112 -102 113 -257 1 -129 -4 -150 -39 -197 -81 -108 -249 -122 -344 -30 -60
58 -69 89 -69 225 0 101 3 124 21 158 49 92 108 126 214 121 42 -1 84 -10 104
-20z M6231 2544 c-129 -38 -198 -153 -169 -283 10 -46 21 -65 63 -106 79 -79
73 -78 550 -83 511 -4 552 1 624 84 48 55 63 91 64 159 0 69 -25 126 -80 176
-63 58 -77 60 -567 62 -297 1 -461 -2 -485 -9z m979 -30 c83 -39 130 -111 130
-197 0 -86 -31 -139 -115 -193 -28 -18 -55 -19 -494 -22 -533 -3 -532 -3 -601
75 -50 57 -63 111 -46 196 13 68 100 144 181 158 22 4 234 6 470 5 415 -1 432
-2 475 -22z M6779 2445 c-45 -24 -63 -63 -64 -132 0 -59 2 -65 38 -100 l37
-38 163 -3 c100 -2 175 1 197 8 74 23 108 111 75 196 -29 77 -50 84 -250 84
-127 -1 -177 -4 -196 -15z M4127 2430 c-51 -32 -62 -58 -62 -146 0 -69 3 -85
25 -117 13 -20 35 -41 48 -46 13 -6 82 -11 155 -11 145 0 169 8 208 66 22 34
27 161 7 196 -41 69 -66 78 -220 77 -110 0 -138 -4 -161 -19z m314 -16 c17 -8
37 -30 45 -49 18 -45 18 -138 -1 -175 -28 -54 -47 -60 -195 -60 -127 1 -137 2
-161 24 -40 35 -49 59 -49 130 0 62 2 67 38 106 l39 40 127 0 c96 0 134 -4
157 -16z M6215 1892 c-88 -2 -132 -7 -137 -15 -13 -21 -9 -95 7 -110 9 -10 15
-42 17 -98 l3 -84 113 -3 112 -3 0 90 c0 70 3 90 15 95 11 4 15 21 15 65 0 33
-3 61 -7 63 -5 2 -66 2 -138 0z m83 -199 l-3 -68 -77 -3 -78 -3 0 71 0 70 80
0 81 0 -3 -67z M6170 1670 c0 -11 7 -20 15 -20 8 0 15 9 15 20 0 11 -7 20 -15
20 -8 0 -15 -9 -15 -20z M6234 1679 c-10 -17 13 -36 27 -22 12 12 4 33 -11 33
-5 0 -12 -5 -16 -11z M6647 1813 c1 -71 5 -89 17 -91 14 -3 16 10 16 87 0 80
-2 91 -17 91 -16 0 -18 -10 -16 -87z M7033 1893 c-10 -3 -13 -49 -13 -179 l0
-174 200 0 200 0 0 168 c0 117 -4 172 -12 180 -8 8 -63 12 -187 11 -97 0 -182
-3 -188 -6z M6568 1827 c-55 -45 -63 -123 -19 -183 56 -78 159 -81 221 -6 29
34 31 42 27 96 -3 51 -8 63 -35 87 -56 50 -67 30 -19 -32 31 -41 34 -67 12
-114 -24 -50 -79 -72 -130 -51 -40 17 -66 54 -69 96 -2 32 26 90 44 90 5 0 10
9 10 20 0 26 -9 25 -42 -3z M3196 1609 c-26 -7 -73 -36 -98 -61 -92 -91 -56
-264 83 -398 292 -282 646 -349 1012 -191 199 86 397 313 397 456 0 85 -58
163 -137 184 -42 11 -1218 20 -1257 10z m1242 -24 c37 -7 96 -55 118 -96 25
-50 18 -114 -22 -190 -40 -76 -143 -193 -209 -239 -149 -102 -326 -160 -495
-162 -153 -2 -230 13 -350 68 -91 43 -132 70 -233 153 -84 70 -134 137 -166
222 -26 68 -26 94 -1 142 27 50 54 75 101 95 36 15 96 17 632 15 325 -2 607
-5 625 -8z M6990 1465 l0 -35 230 0 230 0 0 35 0 35 -230 0 -230 0 0 -35z&quot;&#x2F;&gt;
&lt;&#x2F;g&gt;
&lt;&#x2F;svg&gt;

&lt;&#x2F;figure&gt;
&lt;figure class=&quot;inline-svg&quot; role=&quot;img&quot; aria-label=&quot;Slider set to USB receiver mode&quot;&gt;
&lt;svg xmlns=&quot;http:&#x2F;&#x2F;www.w3.org&#x2F;2000&#x2F;svg&quot; viewBox=&quot;0 0 1068.000000 512.000000&quot; preserveAspectRatio=&quot;xMidYMid meet&quot; role=&quot;img&quot;&gt;
&lt;g transform=&quot;translate(0.000000,512.000000) scale(0.100000,-0.100000)&quot; fill=&quot;var(--accent-color, #d2003d)&quot; stroke=&quot;none&quot;&gt;
&lt;path d=&quot;M3993 4233 c9 -2 23 -2 30 0 6 3 -1 5 -18 5 -16 0 -22 -2 -12 -5z
M3499 4043 c-13 -16 -12 -17 4 -4 16 13 21 21 13 21 -2 0 -10 -8 -17 -17z
M4942 3650 c0 -19 2 -27 5 -17 2 9 2 25 0 35 -3 9 -5 1 -5 -18z M3141 3524 c0
-11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z M5823 3441 c-70 -24 -129
-66 -164 -118 -62 -89 -63 -97 -66 -463 -3 -307 -5 -336 -21 -342 -20 -9 -636
-155 -673 -160 -13 -2 -28 -6 -34 -10 -5 -3 -29 -9 -53 -13 -24 -4 -41 -9 -39
-12 5 -4 131 -25 157 -25 20 0 643 -68 653 -71 4 -1 8 -158 7 -347 0 -212 4
-368 11 -405 22 -119 99 -215 206 -255 54 -20 68 -20 926 -18 l872 3 50 25
c62 30 125 94 156 157 l24 48 3 877 2 876 -21 58 c-28 73 -94 145 -168 181
l-56 28 -860 2 c-807 2 -863 1 -912 -16z m1809 -54 c64 -32 111 -80 142 -146
21 -46 21 -53 21 -916 l0 -870 -27 -51 c-30 -57 -90 -115 -145 -141 -35 -17
-96 -18 -903 -18 -861 0 -865 0 -911 21 -55 26 -111 79 -141 137 -23 41 -23
48 -26 455 -2 226 -6 412 -10 412 -29 -1 -600 65 -597 69 3 3 123 33 268 67
144 34 278 66 297 71 l35 9 5 360 c6 399 5 395 76 474 44 48 99 79 163 90 25
4 417 7 871 7 l825 -2 57 -28z M6385 3095 c-82 -57 -151 -107 -152 -112 -3 -8
295 -213 310 -213 4 0 7 36 7 80 l0 80 260 0 260 0 0 50 0 50 -262 2 -261 3 2
83 c1 45 -1 82 -6 81 -4 -1 -75 -47 -158 -104z M6338 2473 c45 -2 119 -2 165
0 45 1 8 3 -83 3 -91 0 -128 -2 -82 -3z M6338 2183 c45 -2 119 -2 165 0 45 1
8 3 -83 3 -91 0 -128 -2 -82 -3z M6783 1800 c0 -41 2 -58 4 -37 2 20 2 54 0
75 -2 20 -4 3 -4 -38z M6821 1824 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6
-2 -5 -13z M6865 1810 c10 -11 20 -20 23 -20 3 0 -3 9 -13 20 -10 11 -20 20
-23 20 -3 0 3 -9 13 -20z M6694 1788 l-19 -23 23 19 c21 18 27 26 19 26 -2 0
-12 -10 -23 -22z M6632 1710 c0 -14 2 -19 5 -12 2 6 2 18 0 25 -3 6 -5 1 -5
-13z M6661 1714 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z M7205
1660 l-130 -6 127 -2 c76 -1 129 2 133 8 3 6 5 9 3 8 -2 -1 -61 -5 -133 -8z
M7057 1623 c-3 -5 41 -7 96 -5 184 4 204 8 55 10 -81 1 -149 -1 -151 -5z
M6261 1594 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z M7133 1583
c43 -2 110 -2 150 0 40 1 5 3 -78 3 -82 0 -115 -2 -72 -3z M3102 3410 c0 -14
2 -19 5 -12 2 6 2 18 0 25 -3 6 -5 1 -5 -13z M4892 3365 c0 -16 2 -22 5 -12 2
9 2 23 0 30 -3 6 -5 -1 -5 -18z M4288 3373 c34 -2 88 -2 120 0 31 2 3 3 -63 3
-66 0 -92 -1 -57 -3z M4288 3353 c34 -2 88 -2 120 0 31 2 3 3 -63 3 -66 0 -92
-1 -57 -3z M4932 3255 c0 -16 2 -22 5 -12 2 9 2 23 0 30 -3 6 -5 -1 -5 -18z
M3152 3155 c0 -16 2 -22 5 -12 2 9 2 23 0 30 -3 6 -5 -1 -5 -18z M3112 3045
c0 -16 2 -22 5 -12 2 9 2 23 0 30 -3 6 -5 -1 -5 -18z M4881 3004 c0 -11 3 -14
6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z M4921 2894 c0 -11 3 -14 6 -6 3 7 2
16 -1 19 -3 4 -6 -2 -5 -13z M3162 2785 c0 -16 2 -22 5 -12 2 9 2 23 0 30 -3
6 -5 -1 -5 -18z M4273 2695 c0 -44 2 -61 4 -37 2 23 2 59 0 80 -2 20 -4 1 -4
-43z M4253 2690 c0 -36 2 -50 4 -32 2 17 2 47 0 65 -2 17 -4 3 -4 -33z M3122
2685 c0 -16 2 -22 5 -12 2 9 2 23 0 30 -3 6 -5 -1 -5 -18z M3172 2425 c0 -16
2 -22 5 -12 2 9 2 23 0 30 -3 6 -5 -1 -5 -18z M3033 1695 c0 -22 2 -30 4 -17
2 12 2 30 0 40 -3 9 -5 -1 -4 -23z M4308 1613 c34 -2 90 -2 125 0 34 2 6 3
-63 3 -69 0 -97 -1 -62 -3z M4518 1593 c23 -2 59 -2 80 0 20 2 1 4 -43 4 -44
0 -61 -2 -37 -4z M4008 913 c12 -2 30 -2 40 0 9 3 -1 5 -23 4 -22 0 -30 -2
-17 -4z M4008 893 c12 -2 30 -2 40 0 9 3 -1 5 -23 4 -22 0 -30 -2 -17 -4z
M4038 723 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z&quot;&#x2F;&gt;
&lt;&#x2F;g&gt;
&lt;g transform=&quot;translate(0.000000,512.000000) scale(0.100000,-0.100000)&quot; fill=&quot;var(--text-color, currentColor)&quot; stroke=&quot;none&quot;&gt;
&lt;path d=&quot;M3668 4399 c-236 -50 -425 -182 -502 -351 -57 -128 -61 -152 -61
-398 0 -124 4 -385 9 -580 16 -578 14 -751 -9 -871 -3 -13 -7 -41 -10 -64 -3
-22 -10 -53 -15 -70 -10 -32 -25 -119 -36 -200 -4 -27 -7 -104 -7 -170 0 -149
5 -180 47 -309 89 -270 270 -493 486 -598 388 -188 861 -116 1143 175 114 118
215 300 268 485 34 115 33 382 -1 563 -5 28 -12 54 -15 59 -7 11 -21 88 -30
160 -8 66 -10 70 -36 70 -16 0 -18 -5 -15 -27 3 -16 8 -50 11 -78 4 -27 13
-75 20 -105 32 -130 47 -251 48 -385 0 -77 -2 -151 -6 -165 -3 -14 -10 -46
-16 -72 -13 -58 -64 -186 -96 -243 -107 -193 -221 -309 -379 -388 -128 -64
-175 -79 -326 -103 -159 -26 -383 12 -545 92 -104 52 -135 75 -226 165 -125
125 -219 301 -273 513 -17 66 -20 297 -5 386 5 30 11 69 13 85 3 17 12 59 20
95 17 72 22 101 32 175 3 28 8 59 11 70 5 24 4 335 -2 500 -5 147 -14 499 -20
780 -5 239 5 310 60 430 39 86 129 183 220 237 72 43 217 94 300 105 33 4 175
8 315 8 210 -1 269 -4 335 -19 211 -48 384 -167 460 -315 9 -19 28 -70 41
-115 l24 -81 -5 -400 c-4 -220 -8 -431 -10 -470 -8 -178 -16 -605 -11 -614 3
-5 14 -7 24 -5 15 4 17 18 18 102 1 96 10 457 20 792 18 608 14 649 -67 815
-55 112 -146 196 -287 265 -101 49 -122 56 -257 80 -27 5 -167 8 -310 8 -213
0 -276 -4 -347 -19z M3958 4234 c-2 -1 -30 -5 -62 -9 -99 -11 -237 -65 -320
-124 -253 -181 -323 -487 -149 -653 21 -20 61 -48 89 -62 l52 -26 454 0 c251
0 464 2 474 6 155 53 247 177 239 325 -4 75 -12 102 -58 197 -77 160 -230 279
-422 327 -57 14 -286 29 -297 19z m245 -29 c188 -40 335 -132 418 -258 96
-147 118 -284 64 -403 -18 -39 -100 -115 -156 -146 l-44 -23 -455 3 c-521 4
-499 1 -596 99 -80 80 -102 171 -69 293 74 279 349 452 703 444 59 -1 120 -5
135 -9z M3923 2969 c-23 -11 -54 -33 -68 -47 -52 -51 -59 -78 -63 -220 -4
-121 -2 -136 18 -175 30 -58 58 -87 107 -109 98 -44 204 -31 276 32 65 58 78
99 78 246 0 110 -2 127 -25 171 -42 83 -97 115 -206 120 -61 3 -83 -1 -117
-18z m231 -29 c48 -33 87 -90 97 -142 12 -64 4 -218 -14 -258 -32 -72 -108
-123 -189 -126 -77 -3 -125 13 -174 61 -53 51 -64 90 -64 230 0 103 2 113 30
161 42 74 108 107 206 102 55 -3 79 -9 108 -28z M6173 2550 c-88 -42 -146
-132 -142 -225 4 -97 51 -164 146 -211 l48 -23 462 0 c444 -1 464 0 510 20 61
26 97 60 128 123 28 55 31 94 13 164 -16 66 -95 144 -161 160 -33 9 -180 12
-505 12 -430 -1 -459 -2 -499 -20z m1013 -14 c78 -34 135 -113 139 -195 5 -90
-49 -173 -135 -209 -49 -20 -66 -21 -496 -22 -315 0 -457 3 -485 11 -48 15
-112 68 -134 112 -25 48 -30 125 -11 169 23 53 72 105 120 128 39 18 68 19
506 19 365 0 472 -3 496 -13z M6415 2473 c-107 -2 -178 -7 -193 -15 -59 -30
-84 -137 -48 -206 31 -60 44 -63 253 -63 l188 0 32 35 c46 50 54 115 22 175
-38 71 -56 76 -254 74z M4329 2416 c-48 -33 -62 -70 -57 -156 7 -126 54 -160
223 -160 126 0 165 13 202 69 27 41 32 141 9 196 -26 62 -62 75 -211 75 -122
0 -133 -2 -166 -24z m343 -35 c29 -32 30 -37 26 -112 -3 -73 -5 -81 -36 -111
l-32 -33 -124 -2 c-114 -2 -127 0 -157 20 -19 13 -39 34 -46 47 -18 35 -16
126 3 166 29 61 43 65 197 62 l139 -3 30 -34z M6107 1893 c-4 -3 -7 -44 -7
-90 0 -65 3 -85 15 -89 12 -5 15 -28 15 -120 l0 -114 155 0 155 0 0 115 c0 92
3 115 14 115 8 0 17 7 20 16 11 28 6 150 -6 162 -13 13 -349 18 -361 5z m284
-187 c2 -2 3 -43 1 -90 l-3 -86 -105 0 -104 0 0 90 0 90 103 0 c57 0 106 -2
108 -4z M6223 1600 c1 -12 8 -20 19 -20 11 0 18 7 18 20 0 13 -7 20 -19 20
-14 0 -19 -6 -18 -20z M6310 1600 c0 -13 7 -20 20 -20 13 0 20 7 20 20 0 13
-7 20 -20 20 -13 0 -20 -7 -20 -20z M6755 1868 c-11 -43 -1 -153 13 -153 11 0
14 20 14 85 0 82 -14 118 -27 68z M7080 1878 c-1 -2 -2 -51 -3 -111 l-2 -108
130 3 130 3 3 85 c5 138 13 130 -133 130 -69 0 -125 -1 -125 -2z M6692 1825
c-30 -22 -56 -74 -54 -110 7 -108 108 -167 201 -117 37 20 71 73 71 110 0 34
-24 84 -54 111 -23 21 -26 22 -26 7 0 -10 11 -31 25 -48 35 -42 33 -88 -6
-133 -26 -30 -37 -35 -74 -35 -51 0 -89 26 -105 74 -12 37 -1 74 33 113 26 29
19 48 -11 28z M3391 1600 c-98 -45 -146 -149 -110 -237 38 -91 56 -121 102
-171 144 -152 335 -260 512 -287 45 -7 237 -7 257 0 7 2 31 7 53 10 54 8 114
29 215 78 94 45 131 70 204 137 85 79 169 223 168 288 -1 88 -63 172 -139 190
-21 5 -303 10 -628 11 -575 1 -591 1 -634 -19z m1284 -25 c92 -47 120 -143 70
-245 -85 -173 -238 -297 -457 -370 -198 -66 -402 -58 -592 25 -78 33 -230 146
-295 218 -82 90 -121 192 -101 261 13 45 48 88 95 118 29 18 55 19 635 16 583
-3 606 -4 645 -23z M7057 1613 c-20 -19 6 -23 146 -23 130 0 154 2 154 15 0
13 -23 15 -147 15 -81 0 -150 -3 -153 -7z&quot;&#x2F;&gt;
&lt;&#x2F;g&gt;
&lt;&#x2F;svg&gt;

&lt;&#x2F;figure&gt;
&lt;p&gt;The enhanced receiver can charge the mouse through the pogo pins, but that
does not make the slider a &quot;dock mode&quot; in the everyday sense. Charging is a
feature of the enhanced receiver; the slider&#x27;s job is to choose which
wireless receiver the mouse should talk to.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-two-bottom-buttons&quot;&gt;The two bottom buttons&lt;a class=&quot;post-anchor&quot; href=&quot;#the-two-bottom-buttons&quot; aria-label=&quot;Anchor link for: the-two-bottom-buttons&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;The two other controls are for DPI and report rate:&lt;&#x2F;p&gt;
&lt;p&gt;• DPI button — cycles through  400 ,  800 ,  1000 ,  1200 ,  1600 , and
3200  DPI.
• Report-rate button — cycles through  125 ,  500 ,  1000 ,  2000 , and
4000  Hz, depending on which receiver is in use.&lt;&#x2F;p&gt;
&lt;p&gt;ZOWIE&#x27;s documentation notes that available report rates vary by receiver. In
practical terms, the enhanced receiver is the one intended for the high
report-rate modes. The small USB receiver is the simpler portable option and
may not expose the same top-end settings.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-dpi-actually-changes&quot;&gt;What DPI actually changes&lt;a class=&quot;post-anchor&quot; href=&quot;#what-dpi-actually-changes&quot; aria-label=&quot;Anchor link for: what-dpi-actually-changes&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;DPI means &quot;dots per inch,&quot; although &quot;counts per inch&quot; is the more accurate
technical description for a mouse sensor. It is the number of movement
counts the mouse reports for each inch of physical movement.&lt;&#x2F;p&gt;
&lt;p&gt;At  400  DPI, moving the mouse one inch reports about 400 counts. At  1600
DPI, the same movement reports about 1600 counts. If every other sensitivity
setting stays the same, the cursor or crosshair moves four times as far.&lt;&#x2F;p&gt;
&lt;p&gt;DPI is not a quality rating. A higher number does not automatically mean
better tracking, better aim, or a more accurate sensor. It is mostly a
sensitivity input. The setting only makes sense together with the rest of
the chain: operating-system pointer speed, in-game sensitivity, raw-input
behavior, screen resolution, and mousepad space.&lt;&#x2F;p&gt;
&lt;p&gt;For games, the useful concept is effective sensitivity. Players often call
this  eDPI : mouse DPI multiplied by in-game sensitivity. This is useful
when
comparing settings inside the same game, but it does not transfer perfectly
across different games because their sensitivity scales are different.&lt;&#x2F;p&gt;
&lt;p&gt;A sensible starting point is  800  or  1600  DPI, then adjust in-game
sensitivity until large movements and small corrections both feel
controlled. For desktop use on a high-resolution display, a higher DPI can
be
convenient. For aim-heavy games, an extremely high total sensitivity usually
makes small corrections harder, even if the sensor itself is working
correctly.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-report-rate-changes&quot;&gt;What report rate changes&lt;a class=&quot;post-anchor&quot; href=&quot;#what-report-rate-changes&quot; aria-label=&quot;Anchor link for: what-report-rate-changes&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Report rate, often called polling rate, is how often the mouse sends
position updates to the computer.&lt;&#x2F;p&gt;
&lt;p&gt;•  125  Hz = one report every  8  ms
•  500  Hz = one report every  2  ms
•  1000  Hz = one report every  1  ms
•  2000  Hz = one report every  0.5  ms
•  4000  Hz = one report every  0.25  ms&lt;&#x2F;p&gt;
&lt;p&gt;A higher report rate reduces the maximum time between a physical movement
and the next mouse report. That can improve motion smoothness and reduce
input latency, especially on high-refresh-rate displays where the system can
present those updates more frequently.&lt;&#x2F;p&gt;
&lt;p&gt;The improvement is not linear in practice. Moving from  125  Hz to  1000  Hz
is obvious. Moving from  1000  Hz to  4000  Hz is much smaller and depends
on the rest of the system: monitor refresh rate, game engine, CPU load, USB
behavior, and whether the game is already frame-time limited.&lt;&#x2F;p&gt;
&lt;p&gt;ZOWIE lists the S2-DW battery life as  80  hours
at  1000  Hz,  30  hours at  2000  Hz, and  24  hours at  4000  Hz. Higher
report rates also create more USB&#x2F;input work for the system. On a fast
machine this is usually fine; on a CPU-limited system or a poorly behaving
game, dropping back to  1000  Hz can make sense.&lt;&#x2F;p&gt;
&lt;figure class=&quot;inline-svg&quot; role=&quot;img&quot; aria-label=&quot;Battery life by report rate&quot;&gt;
&lt;svg xmlns=&quot;http:&#x2F;&#x2F;www.w3.org&#x2F;2000&#x2F;svg&quot; viewBox=&quot;0 0 1068.000000 372.000000&quot; preserveAspectRatio=&quot;xMidYMid meet&quot; role=&quot;img&quot;&gt;
&lt;g transform=&quot;translate(0.000000,372.000000) scale(0.100000,-0.100000)&quot; fill=&quot;var(--accent-color, #d2003d)&quot; stroke=&quot;none&quot;&gt;
&lt;path d=&quot;M4913 2763 c81 -2 213 -2 295 0 81 1 14 3 -148 3 -162 0 -229 -2
-147 -3z M4675 2195 c0 -286 1 -402 2 -257 2 145 2 379 0 520 -1 141 -2 23 -2
-263z M5598 1993 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z M5892
1720 c0 -14 2 -19 5 -12 2 6 2 18 0 25 -3 6 -5 1 -5 -13z M5589 1697 c5 -5 21
-7 37 -5 27 3 26 4 -8 9 -22 3 -34 1 -29 -4z M5320 1016 c0 -2 7 -7 16 -10 8
-3 12 -2 9 4 -6 10 -25 14 -25 6z M6100 1012 c0 -4 9 -8 19 -8 11 0 17 4 15 8
-3 4 -12 8 -20 8 -8 0 -14 -4 -14 -8z M5314 840 c0 -91 2 -128 3 -82 2 45 2
119 0 165 -1 45 -3 8 -3 -83z M5242 835 c0 -16 2 -22 5 -12 2 9 2 23 0 30 -3
6 -5 -1 -5 -18z M5211 834 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5
-13z M5653 750 c0 -36 2 -50 4 -32 2 17 2 47 0 65 -2 17 -4 3 -4 -33z&quot;&#x2F;&gt;
&lt;&#x2F;g&gt;
&lt;g transform=&quot;translate(0.000000,372.000000) scale(0.100000,-0.100000)&quot; fill=&quot;var(--text-color, currentColor)&quot; stroke=&quot;none&quot;&gt;
&lt;path d=&quot;M4920 2865 l0 -55 -79 0 c-92 0 -124 -9 -146 -43 -13 -21 -15 -91
-15 -569 0 -300 3 -554 7 -564 21 -56 68 -66 299 -62 l192 3 23 -45 c71 -134
218 -225 387 -237 75 -6 79 -4 62 27 -8 16 -22 19 -73 19 -198 0 -382 184
-391 392 -5 124 30 221 116 314 74 81 175 128 284 131 344 11 554 -344 380
-640 -13 -23 -35 -52 -49 -65 l-25 -23 -31 42 -30 42 -16 -37 c-8 -21 -15 -41
-15 -46 0 -5 -11 -37 -25 -70 l-25 -61 106 5 c58 3 108 8 111 11 3 3 -6 19
-20 37 l-24 31 52 74 c67 92 85 146 92 260 5 78 2 101 -17 160 -13 38 -30 80
-39 94 -62 100 -157 174 -265 210 -59 19 -82 22 -160 17 -50 -4 -102 -9 -116
-12 l-25 -6 3 258 c3 342 -2 353 -165 353 l-83 0 0 55 0 55 -140 0 -140 0 0
-55z m464 -120 c13 -17 15 -65 16 -295 l0 -275 -35 -22 c-41 -27 -51 -28 -59
-8 -5 13 -39 15 -248 13 l-243 -3 -3 -87 -3 -88 191 0 191 0 -7 -22 c-14 -47
-18 -48 -201 -48 l-174 0 3 -87 3 -88 163 -3 c89 -1 162 -6 163 -10 1 -9 5
-33 14 -71 l7 -31 -199 0 c-164 0 -202 3 -221 16 l-22 15 0 537 c1 553 1 559
40 575 8 3 149 5 312 4 284 -2 298 -3 312 -22z M4817 2653 c-4 -3 -7 -44 -7
-89 l0 -83 240 -3 c132 -2 245 -1 250 0 6 2 10 43 10 93 l0 89 -243 0 c-134 0
-247 -3 -250 -7z M4817 2404 c-11 -11 -8 -162 2 -169 7 -4 374 -8 479 -5 10 0
12 21 10 88 l-3 87 -241 3 c-132 1 -243 -1 -247 -4z M5602 1992 c-22 -3 -23
-4 -20 -145 l3 -142 35 -5 c19 -3 88 -4 153 -2 108 3 117 5 117 23 0 18 -8 19
-130 19 l-129 0 -3 128 c-3 126 -3 127 -26 124z M5830 967 c-33 -78 -36 -220
-6 -307 14 -42 28 -66 39 -68 22 -4 21 -1 -5 49 -33 65 -36 217 -4 304 17 48
20 65 11 69 -8 3 -22 -15 -35 -47z M5318 845 c2 -139 5 -165 18 -165 12 0 15
14 13 78 0 42 2 85 6 95 15 38 100 50 128 19 13 -14 17 -39 17 -105 0 -76 2
-87 18 -87 15 0 17 9 12 97 -3 54 -9 103 -13 110 -15 24 -77 36 -118 24 -22
-7 -39 -17 -39 -23 0 -6 -3 -8 -6 -5 -4 4 -7 32 -8 64 -1 43 -5 58 -16 61 -13
2 -15 -20 -12 -163z M5980 985 c0 -27 -18 -33 -38 -13 -6 6 -15 8 -19 5 -4 -4
4 -15 17 -24 l23 -16 -29 -25 c-29 -24 -19 -27 29 -7 14 6 17 2 17 -24 0 -17
5 -31 10 -31 6 0 10 14 10 30 0 33 2 35 31 19 35 -18 43 -4 10 17 l-32 21 31
15 c19 9 27 18 21 22 -6 4 -19 2 -30 -4 -28 -14 -31 -13 -31 15 0 14 -4 25
-10 25 -5 0 -10 -11 -10 -25z M6110 1002 c0 -5 8 -24 17 -43 12 -24 18 -64 20
-137 2 -87 -1 -112 -21 -168 -21 -57 -22 -65 -7 -62 9 2 25 27 38 63 29 81 28
251 -3 310 -19 37 -44 58 -44 37z M4741 974 c-38 -31 -42 -81 -11 -109 18 -16
19 -20 5 -27 -24 -14 -46 -65 -39 -92 18 -71 146 -96 217 -42 10 8 21 32 24
53 5 35 2 42 -26 65 l-30 26 24 16 c45 30 28 97 -30 122 -53 22 -99 18 -134
-12z m143 -26 c39 -55 -2 -96 -91 -90 -51 4 -71 68 -31 96 35 25 103 21 122
-6z m6 -140 c27 -29 25 -54 -6 -85 -22 -22 -32 -25 -70 -20 -77 8 -109 62 -64
107 29 29 112 28 140 -2z M5058 982 c-41 -25 -67 -86 -64 -153 3 -70 30 -118
79 -142 32 -16 42 -17 74 -7 50 16 70 35 84 79 15 43 15 125 0 163 -25 65
-115 97 -173 60z m115 -32 c56 -44 52 -198 -6 -229 -38 -20 -97 -10 -119 20
-22 30 -31 119 -16 158 25 68 89 91 141 51z M5630 918 c-1 -2 -2 -56 -3 -121
-2 -98 0 -117 13 -117 12 0 14 12 12 62 -4 89 20 133 82 153 14 4 16 9 8 17
-8 8 -22 5 -51 -10 -40 -21 -41 -21 -41 -2 0 11 -4 20 -10 20 -5 0 -10 -1 -10
-2z&quot;&#x2F;&gt;
&lt;&#x2F;g&gt;
&lt;&#x2F;svg&gt;

&lt;&#x2F;figure&gt;
&lt;h2 id=&quot;practical-settings&quot;&gt;Practical settings&lt;a class=&quot;post-anchor&quot; href=&quot;#practical-settings&quot; aria-label=&quot;Anchor link for: practical-settings&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;For most people, this is the clean baseline:&lt;&#x2F;p&gt;
&lt;p&gt;• Use enhanced wireless receiver mode when the enhanced receiver is plugged
into the computer.
• Use USB wireless receiver mode when the small USB dongle is plugged into
the computer.
• Use Off when storing or transporting the mouse.
• Start at 800 or 1600 DPI, then tune sensitivity in the operating system or
game.
• Start at 1000 Hz report rate; try  2000  or  4000  Hz if you are on a high-
refresh display and the system remains stable.&lt;&#x2F;p&gt;
&lt;p&gt;• Dock the mouse to charge it as frequently as needed per the report rate settings&lt;&#x2F;p&gt;
&lt;figure class=&quot;inline-svg&quot; role=&quot;img&quot; aria-label=&quot;Mouse charging on the enhanced receiver via pogo pins&quot;&gt;
&lt;svg xmlns=&quot;http:&#x2F;&#x2F;www.w3.org&#x2F;2000&#x2F;svg&quot; viewBox=&quot;0 0 1068.000000 689.000000&quot; preserveAspectRatio=&quot;xMidYMid meet&quot; role=&quot;img&quot;&gt;
&lt;g transform=&quot;translate(0.000000,689.000000) scale(0.100000,-0.100000)&quot; fill=&quot;var(--accent-color, #d2003d)&quot; stroke=&quot;none&quot;&gt;
&lt;path d=&quot;M2728 6133 c29 -2 74 -2 100 0 26 2 2 3 -53 3 -55 0 -76 -1 -47 -3z
M5878 6133 c29 -2 74 -2 100 0 26 2 2 3 -53 3 -55 0 -76 -1 -47 -3z M4063
6123 c15 -2 39 -2 55 0 15 2 2 4 -28 4 -30 0 -43 -2 -27 -4z M7208 6123 c18
-2 45 -2 60 0 15 2 0 4 -33 4 -33 0 -45 -2 -27 -4z M3888 6083 c28 -2 76 -2
105 0 28 2 5 3 -53 3 -58 0 -81 -1 -52 -3z M7038 6083 c29 -2 74 -2 100 0 26
2 2 3 -53 3 -55 0 -76 -1 -47 -3z M2493 5965 c0 -22 2 -30 4 -17 2 12 2 30 0
40 -3 9 -5 -1 -4 -23z M4265 5325 c0 -368 1 -518 2 -332 2 186 2 488 0 670 -1
183 -2 31 -2 -338z M2495 5270 c0 -333 1 -469 2 -303 2 167 2 439 0 605 -1
167 -2 31 -2 -302z M4141 4764 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2
-5 -13z M6338 4683 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z
M6658 4683 c6 -2 18 -2 25 0 6 3 1 5 -13 5 -14 0 -19 -2 -12 -5z M6393 4643
c9 -2 25 -2 35 0 9 3 1 5 -18 5 -19 0 -27 -2 -17 -5z M6588 4643 c12 -2 30 -2
40 0 9 3 -1 5 -23 4 -22 0 -30 -2 -17 -4z M4122 4605 c0 -16 2 -22 5 -12 2 9
2 23 0 30 -3 6 -5 -1 -5 -18z M3493 4525 c0 -38 2 -53 4 -32 2 20 2 52 0 70
-2 17 -4 1 -4 -38z M5640 4556 c0 -2 8 -10 18 -17 15 -13 16 -12 3 4 -13 16
-21 21 -21 13z M3307 4536 c-3 -8 1 -20 9 -27 12 -10 18 -9 31 3 8 9 12 21 9
27 -10 16 -43 14 -49 -3z M3394 4535 c-4 -9 -2 -21 4 -27 15 -15 44 -1 40 19
-4 23 -36 29 -44 8z M7874 4145 c0 -154 2 -216 3 -137 2 79 2 205 0 280 -1 75
-3 11 -3 -143z M6483 4373 c9 -2 25 -2 35 0 9 3 1 5 -18 5 -19 0 -27 -2 -17
-5z M3418 4313 c7 -3 16 -2 19 1 4 3 -2 6 -13 5 -11 0 -14 -3 -6 -6z M7990
4212 c-30 -54 -56 -100 -58 -104 -2 -5 12 -8 32 -8 20 0 36 -3 36 -7 0 -5 -9
-41 -19 -80 -10 -40 -15 -73 -12 -73 6 1 133 184 139 201 2 5 -15 9 -37 9 -46
0 -45 -2 -31 89 6 35 10 65 8 66 -2 2 -28 -40 -58 -93z M6404 4140 c0 -74 1
-105 3 -67 2 37 2 97 0 135 -2 37 -3 6 -3 -68z M3318 4133 c7 -3 16 -2 19 1 4
3 -2 6 -13 5 -11 0 -14 -3 -6 -6z M3378 4133 c7 -3 16 -2 19 1 4 3 -2 6 -13 5
-11 0 -14 -3 -6 -6z M3300 4047 c-30 -45 -56 -85 -58 -89 -2 -5 17 -8 42 -8
l46 0 0 -151 0 -150 28 3 27 3 3 148 3 147 45 0 45 0 -61 90 c-33 50 -62 90
-63 90 -1 0 -27 -37 -57 -83z M5802 3905 c0 -16 2 -22 5 -12 2 9 2 23 0 30 -3
6 -5 -1 -5 -18z M7202 3900 c0 -19 2 -27 5 -17 2 9 2 25 0 35 -3 9 -5 1 -5
-18z M5762 3840 c0 -14 2 -19 5 -12 2 6 2 18 0 25 -3 6 -5 1 -5 -13z M7242
3790 c0 -19 2 -27 5 -17 2 9 2 25 0 35 -3 9 -5 1 -5 -18z M3198 3573 c6 -2 18
-2 25 0 6 3 1 5 -13 5 -14 0 -19 -2 -12 -5z M7192 3525 c0 -16 2 -22 5 -12 2
9 2 23 0 30 -3 6 -5 -1 -5 -18z M3333 3533 c15 -2 39 -2 55 0 15 2 2 4 -28 4
-30 0 -43 -2 -27 -4z M5812 3440 c0 -19 2 -27 5 -17 2 9 2 25 0 35 -3 9 -5 1
-5 -18z M7232 3425 c0 -16 2 -22 5 -12 2 9 2 23 0 30 -3 6 -5 -1 -5 -18z
M3955 3400 c10 -11 20 -20 23 -20 3 0 -3 9 -13 20 -10 11 -20 20 -23 20 -3 0
3 -9 13 -20z M5771 3314 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z
M3343 3263 c9 -2 25 -2 35 0 9 3 1 5 -18 5 -19 0 -27 -2 -17 -5z M3348 3243
c12 -2 30 -2 40 0 9 3 -1 5 -23 4 -22 0 -30 -2 -17 -4z M7181 3174 c0 -11 3
-14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z M5821 3114 c0 -11 3 -14 6 -6 3 7
2 16 -1 19 -3 4 -6 -2 -5 -13z M5781 3064 c0 -11 3 -14 6 -6 3 7 2 16 -1 19
-3 4 -6 -2 -5 -13z M2651 3054 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2
-5 -13z M4103 3015 c0 -22 2 -30 4 -17 2 12 2 30 0 40 -3 9 -5 -1 -4 -23z
M2652 2980 c0 -19 2 -27 5 -17 2 9 2 25 0 35 -3 9 -5 1 -5 -18z M2613 2810 c0
-25 2 -35 4 -22 2 12 2 32 0 45 -2 12 -4 2 -4 -23z M4052 2720 c0 -19 2 -27 5
-17 2 9 2 25 0 35 -3 9 -5 1 -5 -18z M4092 2615 c0 -16 2 -22 5 -12 2 9 2 23
0 30 -3 6 -5 -1 -5 -18z M2662 2390 c0 -14 2 -19 5 -12 2 6 2 18 0 25 -3 6 -5
1 -5 -13z M2621 2344 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z
M4041 2294 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z M4081 2254
c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z M2671 2044 c0 -11 3 -14
6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z M5980 1936 c0 -2 8 -10 18 -17 15 -13
16 -12 3 4 -13 16 -21 21 -21 13z M4152 1430 c0 -14 2 -19 5 -12 2 6 2 18 0
25 -3 6 -5 1 -5 -13z M4151 1344 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2
-5 -13z M3347 943 c18 -2 50 -2 70 0 21 2 7 4 -32 4 -38 0 -55 -2 -38 -4z
M2770 826 c0 -2 8 -10 18 -17 15 -13 16 -12 3 4 -13 16 -21 21 -21 13z M3343
613 c9 -2 23 -2 30 0 6 3 -1 5 -18 5 -16 0 -22 -2 -12 -5z&quot;&#x2F;&gt;
&lt;&#x2F;g&gt;
&lt;g transform=&quot;translate(0.000000,689.000000) scale(0.100000,-0.100000)&quot; fill=&quot;var(--text-color, currentColor)&quot; stroke=&quot;none&quot;&gt;
&lt;path d=&quot;M2562 6129 c-18 -4 -45 -17 -59 -30 -54 -52 -53 -36 -53 -779 l1
-686 22 -42 c12 -23 39 -53 58 -66 20 -14 46 -44 58 -67 34 -64 67 -104 124
-147 96 -73 204 -121 321 -143 28 -5 62 -11 75 -14 166 -32 462 -22 621 21
199 54 343 152 397 270 15 33 36 60 56 73 18 11 43 38 55 60 l22 39 0 707 c0
790 4 746 -71 783 -34 17 -77 19 -581 21 -298 1 -649 3 -778 4 -129 2 -250 0
-268 -4z m1629 -77 c23 -26 29 -42 29 -78 0 -39 -5 -51 -31 -75 l-31 -29 -810
2 -810 3 -24 30 c-28 35 -30 73 -8 121 32 68 -15 65 863 61 l793 -3 29 -32z
m-1681 -172 c0 -28 89 -31 871 -28 742 3 798 4 816 21 11 9 21 17 24 17 8 0
-6 -1275 -14 -1290 -5 -8 -21 -25 -36 -37 l-27 -21 4 83 c2 46 1 100 -2 119
l-7 36 -784 0 -785 0 0 -117 0 -116 -24 22 c-57 54 -55 33 -56 704 0 398 4
617 10 617 6 0 10 -4 10 -10z m1610 -1147 c0 -82 -125 -224 -250 -285 -156
-76 -308 -110 -495 -112 -205 -1 -328 21 -490 89 -142 60 -271 193 -289 298
l-7 37 766 0 765 0 0 -27z m-1472 -159 c119 -159 434 -268 742 -259 63 2 135
7 160 10 95 15 237 58 314 95 91 45 206 142 235 199 l19 36 7 -27 c5 -18 -1
-53 -14 -97 -24 -78 -78 -154 -144 -202 -64 -46 -182 -99 -256 -114 -36 -8
-79 -17 -96 -21 -51 -11 -396 -13 -468 -3 -101 15 -232 55 -314 95 -138 69
-244 209 -242 320 0 42 2 46 12 30 7 -10 27 -38 45 -62z M3262 4634 c-20 -14
-22 -24 -22 -97 0 -122 5 -127 133 -127 134 1 141 6 145 108 3 67 1 81 -18
105 -21 26 -25 27 -119 27 -71 0 -102 -4 -119 -16z m218 -24 c16 -16 20 -33
20 -82 -1 -92 -8 -98 -122 -98 -60 0 -98 4 -106 12 -15 15 -16 142 -2 169 9
17 22 19 100 19 77 0 93 -3 110 -20z M5704 6124 c-42 -14 -71 -39 -90 -76 -20
-40 -21 -1392 0 -1441 7 -18 33 -51 58 -72 25 -22 53 -56 63 -75 9 -19 28 -49
41 -67 l25 -32 -18 -53 c-16 -46 -18 -90 -18 -323 0 -148 4 -412 9 -585 5
-173 9 -324 9 -335 1 -18 -5 -66 -18 -163 -3 -20 -16 -77 -27 -127 -18 -74
-22 -121 -22 -265 0 -168 1 -178 28 -250 38 -101 99 -222 138 -273 150 -196
369 -301 623 -300 106 1 111 1 220 28 189 47 358 179 460 361 51 90 104 239
114 324 14 108 0 290 -29 385 -5 17 -11 46 -14 65 -14 105 -19 137 -23 155 -4
18 -3 250 2 400 1 33 5 202 9 375 5 173 9 326 9 340 2 50 -14 159 -27 192 -13
32 -12 36 24 98 21 36 61 88 89 115 28 28 55 63 61 78 6 18 10 274 10 726 l0
698 -23 34 c-45 67 -30 65 -630 67 -298 2 -649 4 -780 5 -166 2 -249 -1 -273
-9z m1637 -72 c24 -26 29 -41 29 -81 0 -43 -4 -53 -31 -75 l-31 -26 -805 0
-805 0 -30 31 c-29 28 -30 34 -25 82 5 41 13 58 38 80 l31 29 800 -4 800 -4
29 -32z m-836 -202 c780 0 801 1 833 20 l32 20 0 -626 c0 -681 1 -666 -56
-707 l-25 -19 2 119 1 118 -786 3 -786 2 0 -117 0 -118 -37 38 -38 37 -3 635
-2 635 32 -20 c32 -19 51 -20 833 -20z m760 -1127 c-9 -51 -39 -106 -82 -154
-56 -62 -68 -65 -106 -29 -32 30 -139 90 -187 104 -127 38 -161 41 -400 40
-162 0 -233 -4 -288 -17 -83 -18 -197 -73 -260 -124 -40 -33 -44 -34 -66 -19
-54 35 -119 133 -131 199 l-7 37 767 0 767 0 -7 -37z m-1421 -201 l36 -32 -26
-40 c-15 -22 -29 -40 -33 -40 -9 0 -47 62 -65 105 -9 21 -16 60 -16 89 l0 51
34 -51 c19 -27 50 -64 70 -82z m895 113 c121 -15 246 -71 324 -143 60 -56 85
-96 118 -182 22 -61 24 -78 24 -255 0 -104 -4 -338 -9 -520 -12 -428 -12 -500
-4 -560 9 -55 33 -186 42 -225 15 -63 21 -135 21 -260 -1 -128 -2 -140 -34
-225 -84 -226 -186 -354 -360 -453 -29 -17 -151 -63 -151 -58 0 2 -22 -2 -48
-9 -73 -19 -226 -19 -313 0 -272 60 -440 211 -549 494 -54 139 -61 322 -21
515 41 192 52 283 47 366 -7 102 -20 735 -20 940 0 176 2 188 27 253 50 129
109 193 236 257 90 45 135 57 241 68 89 9 353 7 429 -3z m530 -55 c-8 -71 -51
-170 -73 -170 -6 1 -23 19 -38 40 l-28 40 40 36 c21 19 52 55 67 80 15 24 31
44 34 44 3 0 2 -32 -2 -70z M6487 4370 c-33 -4 -56 -14 -75 -33 -26 -27 -27
-30 -30 -170 -4 -181 4 -211 71 -245 l47 -25 0 -423 c0 -276 3 -424 10 -424 7
0 10 150 10 430 l0 430 23 0 c29 0 64 24 81 58 17 33 22 279 7 330 -9 30 -43
71 -60 73 -3 0 -13 2 -21 3 -8 1 -36 -1 -63 -4z m80 -26 c45 -9 55 -52 51
-215 -4 -175 -10 -188 -96 -197 -99 -9 -122 31 -122 207 0 199 29 234 167 205z
M6405 2210 c-3 -5 27 -10 76 -12 71 -3 84 -6 96 -25 22 -33 -12 -63 -73 -63
-31 0 -44 -4 -42 -12 6 -18 109 -10 131 9 23 21 22 75 -1 96 -14 13 -38 17
-100 17 -44 0 -83 -4 -87 -10z M6444 2160 c-41 -17 -51 -74 -18 -107 20 -20
33 -23 104 -23 61 0 81 3 77 13 -2 8 -28 12 -76 13 -87 2 -112 18 -93 58 9 21
19 25 62 28 27 2 51 8 53 13 3 13 -81 17 -109 5z M7960 4500 c0 -29 -1 -30
-49 -30 -84 0 -81 11 -81 -328 0 -253 2 -301 16 -320 15 -21 21 -22 175 -22
146 0 161 2 179 20 19 19 20 33 20 313 0 344 2 337 -85 337 l-54 0 6 30 6 30
-67 0 -66 0 0 -30z m210 -360 l0 -290 -144 0 c-122 0 -145 2 -150 16 -8 21 -8
549 1 557 3 4 71 7 150 7 l143 0 0 -290z M3113 3565 c-160 -28 -264 -77 -353
-164 -65 -64 -112 -148 -134 -242 -16 -67 -16 -281 1 -899 4 -146 7 -283 7
-305 0 -62 -20 -196 -39 -270 -21 -76 -26 -119 -30 -265 -3 -111 -1 -148 16
-210 58 -223 186 -415 346 -517 59 -38 180 -88 233 -97 105 -18 133 -21 215
-20 147 2 324 54 430 128 167 117 312 354 342 561 17 112 3 322 -27 420 -5 17
-12 48 -15 70 -3 22 -10 65 -15 95 -9 53 -7 336 6 775 13 464 9 531 -39 634
-67 147 -208 253 -397 298 -86 21 -444 26 -547 8z m377 -35 c181 -13 193 -16
307 -72 130 -63 210 -160 249 -302 11 -41 13 -119 7 -411 -3 -198 -7 -385 -8
-415 -7 -179 -7 -422 -1 -465 4 -27 16 -95 27 -149 33 -164 34 -175 34 -336
-1 -155 -1 -155 -38 -247 -64 -161 -104 -224 -206 -323 -143 -140 -376 -215
-586 -188 -176 22 -297 76 -412 183 -112 103 -178 216 -238 404 -31 97 -25
303 15 481 29 130 35 183 34 305 0 131 -14 806 -19 954 -4 134 8 209 49 297
82 175 268 277 513 283 48 1 90 4 92 7 6 6 36 5 181 -6z M3343 3260 c-36 -4
-60 -13 -75 -28 -22 -21 -23 -32 -26 -193 -3 -167 -3 -172 20 -196 13 -14 38
-32 56 -39 l32 -14 0 -425 c0 -277 3 -425 10 -425 7 0 10 149 10 428 l0 428
37 10 c26 6 45 20 61 44 22 33 23 41 20 190 -3 136 -5 158 -23 181 -26 35 -59
45 -122 39z m94 -45 l34 -34 -3 -157 -3 -156 -29 -25 c-26 -22 -37 -25 -83
-21 -85 7 -87 12 -91 199 -4 173 3 199 51 219 48 19 88 11 124 -25z M3260
1100 c0 -6 28 -10 63 -10 79 0 105 -7 112 -31 12 -37 -11 -58 -68 -61 -65 -4
-68 -22 -4 -26 41 -3 51 1 77 26 32 32 33 61 4 93 -14 16 -32 19 -101 19 -49
0 -83 -4 -83 -10z M3281 1039 c-25 -27 -27 -66 -5 -97 14 -20 24 -22 100 -22
60 0 84 3 84 13 0 8 -16 11 -55 9 -71 -5 -102 3 -115 28 -19 36 3 59 60 62 67
4 69 22 3 26 -43 2 -55 -1 -72 -19z&quot;&#x2F;&gt;
&lt;&#x2F;g&gt;
&lt;&#x2F;svg&gt;

&lt;&#x2F;figure&gt;
&lt;h2 id=&quot;references&quot;&gt;References&lt;a class=&quot;post-anchor&quot; href=&quot;#references&quot; aria-label=&quot;Anchor link for: references&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;• ZOWIE S2-DW specifications: https:&#x2F;&#x2F;zowie.benq.com&#x2F;en-us&#x2F;mouse&#x2F;s2-dw.html
• ZOWIE DPI and polling-rate FAQ: https:&#x2F;&#x2F;zowie.benq.com&#x2F;en-
us&#x2F;support&#x2F;faq&#x2F;product&#x2F;application&#x2F;zowiegear-faq-kn-00025.html
• ZOWIE S2-DW quick start guide: https:&#x2F;&#x2F;zowie.benq.com&#x2F;en-us&#x2F;qsg&#x2F;s2-
dw&#x2F;guide.html&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Why Your OnePlus Says VOOC When It Isn&#x27;t: USB-PD, PPS, and the SuperVOOC Architecture</title>
          <pubDate>Thu, 30 Apr 2026 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://blog.s99.sh/blog/oneplus-usbpowerdelivery-vooc/</link>
          <guid>https://blog.s99.sh/blog/oneplus-usbpowerdelivery-vooc/</guid>
          <description xml:base="https://blog.s99.sh/blog/oneplus-usbpowerdelivery-vooc/">&lt;h1 id=&quot;why-your-oneplus-reports-vooc-when-it-isn-t&quot;&gt;Why Your OnePlus Reports VOOC When It Isn&#x27;t&lt;a class=&quot;post-anchor&quot; href=&quot;#why-your-oneplus-reports-vooc-when-it-isn-t&quot; aria-label=&quot;Anchor link for: why-your-oneplus-reports-vooc-when-it-isn-t&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h1&gt;
&lt;p&gt;A OnePlus phone connected to a 100W third-party GaN charger displays a &quot;VOOC&quot; fast-charge indicator in OxygenOS. The indicator is misleading. The phone is negotiating USB Power Delivery with PPS and drawing approximately a quarter of what its battery hardware will accept. OxygenOS labels any negotiated rate above an internal threshold with the family name, regardless of the protocol actually in use.&lt;&#x2F;p&gt;
&lt;p&gt;This post covers what is on the CC pin during that negotiation, why third-party chargers cap where they do, and what would have to change for the same phone to charge at its rated peak. The relevant pieces are dual-cell battery topology, charge pumps versus buck converters, and a cable-side ID resistor that gates OPPO&#x27;s high-current path.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;usb-power-delivery&quot;&gt;USB Power Delivery&lt;a class=&quot;post-anchor&quot; href=&quot;#usb-power-delivery&quot; aria-label=&quot;Anchor link for: usb-power-delivery&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;img src=&quot;.&#x2F;logo-usb-pd.svg&quot; alt=&quot;USB-IF Type-C Charging trident&quot; width=&quot;120&quot; align=&quot;right&quot; &#x2F;&gt;
&lt;p&gt;USB-PD is the open standard maintained by the USB-IF for negotiating power over USB-C. The model is straightforward: the source advertises a list of &lt;strong&gt;Power Data Objects&lt;&#x2F;strong&gt; (PDOs) — fixed voltage&#x2F;current pairs such as &lt;code&gt;5V&#x2F;3A&lt;&#x2F;code&gt;, &lt;code&gt;9V&#x2F;3A&lt;&#x2F;code&gt;, &lt;code&gt;15V&#x2F;3A&lt;&#x2F;code&gt;, &lt;code&gt;20V&#x2F;5A&lt;&#x2F;code&gt; — and the sink selects one. Voltage steps are coarse and discrete. Conversion happens device-side: the phone receives 9V at the port and its internal buck IC steps that down to ~4.4V for the cell. Conversion losses dissipate as heat inside the phone.&lt;&#x2F;p&gt;
&lt;p&gt;The spec has gone through several revisions. Each adds capability without removing the older fixed-PDO model.&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Spec&lt;&#x2F;th&gt;&lt;th&gt;Year&lt;&#x2F;th&gt;&lt;th&gt;Max power&lt;&#x2F;th&gt;&lt;th&gt;Voltages&lt;&#x2F;th&gt;&lt;th&gt;Key addition&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;USB-PD 1.0&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;2012&lt;&#x2F;td&gt;&lt;td&gt;100W&lt;&#x2F;td&gt;&lt;td&gt;5&#x2F;12&#x2F;20V profiles&lt;&#x2F;td&gt;&lt;td&gt;First spec; ran over USB-A&#x2F;B VBUS. Largely skipped.&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;USB-PD 2.0&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;2014&lt;&#x2F;td&gt;&lt;td&gt;100W&lt;&#x2F;td&gt;&lt;td&gt;5V, 9V, 15V, 20V (fixed PDOs)&lt;&#x2F;td&gt;&lt;td&gt;Rebuilt around USB-C. The &quot;classic&quot; PD everyone knows.&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;USB-PD 3.0&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;2015–2017&lt;&#x2F;td&gt;&lt;td&gt;100W&lt;&#x2F;td&gt;&lt;td&gt;Same fixed PDOs&lt;&#x2F;td&gt;&lt;td&gt;Fast Role Swap, extended messages, battery status, firmware update over PD.&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;USB-PD 3.0 + PPS&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;2017&lt;&#x2F;td&gt;&lt;td&gt;100W&lt;&#x2F;td&gt;&lt;td&gt;3.3–21V in &lt;strong&gt;20mV&lt;&#x2F;strong&gt; steps, &lt;strong&gt;50mA&lt;&#x2F;strong&gt; current steps&lt;&#x2F;td&gt;&lt;td&gt;Programmable Power Supply — chargers track battery voltage.&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;USB-PD 3.1 SPR&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;2021&lt;&#x2F;td&gt;&lt;td&gt;100W&lt;&#x2F;td&gt;&lt;td&gt;Same as 3.0 (≤20V)&lt;&#x2F;td&gt;&lt;td&gt;&quot;Standard Power Range.&quot; Cleanups, better extended messages.&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;USB-PD 3.1 EPR&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;2021&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;240W&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;Adds 28V, 36V, 48V fixed PDOs&lt;&#x2F;td&gt;&lt;td&gt;&quot;Extended Power Range.&quot; Requires EPR-rated e-marked cable (5A @ 50V).&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;USB-PD 3.1 EPR + AVS&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;2021&lt;&#x2F;td&gt;&lt;td&gt;240W&lt;&#x2F;td&gt;&lt;td&gt;15–48V &lt;strong&gt;adjustable&lt;&#x2F;strong&gt; (100mV steps)&lt;&#x2F;td&gt;&lt;td&gt;Adjustable Voltage Supply — PPS&#x27;s bigger sibling for the EPR range.&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;USB-PD 3.2&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;2023&lt;&#x2F;td&gt;&lt;td&gt;240W&lt;&#x2F;td&gt;&lt;td&gt;Same envelope as 3.1&lt;&#x2F;td&gt;&lt;td&gt;Tightens AVS, finer current granularity, multi-port budget signaling.&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;Reading a charger&#x27;s spec label:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&quot;PD 65W&quot;&lt;&#x2F;strong&gt; with no PPS: fixed 20V&#x2F;3.25A. Adequate for laptops, suboptimal for modern phones because it forces a high-ratio buck.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&quot;PD 65W PPS&quot;&lt;&#x2F;strong&gt;: at least one PPS PDO, typically &lt;code&gt;3.3–11V @ 5A&lt;&#x2F;code&gt; or &lt;code&gt;3.3–21V @ 3A&lt;&#x2F;code&gt;. This is what a 2024+ phone targets.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&quot;PD 3.1 &#x2F; EPR 140W+&quot;&lt;&#x2F;strong&gt;: includes 28V or 36V fixed PDOs. Apple&#x27;s 140W MacBook Pro charger and the higher-tier Anker &#x2F; UGREEN units fall here. Delivery above 100W requires an EPR-rated cable.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&quot;AVS&quot;&lt;&#x2F;strong&gt; on the label: PD 3.1 EPR with adjustable voltage. Currently limited to enthusiast GaN units.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;pps-specifically&quot;&gt;PPS specifically&lt;a class=&quot;post-anchor&quot; href=&quot;#pps-specifically&quot; aria-label=&quot;Anchor link for: pps-specifically&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;PPS is the section of PD 3.0 most relevant to phones. The sink requests voltage in &lt;strong&gt;20mV&lt;&#x2F;strong&gt; increments and current in &lt;strong&gt;50mA&lt;&#x2F;strong&gt; increments and adjusts continuously. Rather than &quot;9V&#x2F;3A, device copes,&quot; the phone requests &quot;8.74V at 4.20A,&quot; then a few hundred ms later &quot;8.78V at 4.18A,&quot; and the source tracks.&lt;&#x2F;p&gt;
&lt;p&gt;This matters for thermal behavior. The phone&#x27;s charge IC has the least conversion work when the input voltage is close to twice the cell voltage. A buck or charge pump operates most efficiently at small, predictable conversion ratios. With fixed PD the input is always 9V or 15V regardless of state-of-charge; with PPS the input slides as the cell fills, holding the conversion stage near peak efficiency throughout the curve.&lt;&#x2F;p&gt;
&lt;p&gt;PPS adoption on Android is broad: Samsung Super Fast Charging 2.0 (45W), Pixel fast charging, and every modern OnePlus when paired with a non-OPPO charger all use PPS. iPhones do not request PPS; they negotiate fixed 9V or 15V PDOs and let the internal IC handle the rest.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;the-oppo-family-dash-warp-vooc-supervooc&quot;&gt;The OPPO family: Dash, Warp, VOOC, SuperVOOC&lt;a class=&quot;post-anchor&quot; href=&quot;#the-oppo-family-dash-warp-vooc-supervooc&quot; aria-label=&quot;Anchor link for: the-oppo-family-dash-warp-vooc-supervooc&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;OPPO&#x27;s proprietary fast-charge family began with VOOC in 2014. OnePlus rebranded the protocol twice — first as Dash, then as Warp — before consolidating to VOOC and SuperVOOC after the 2021 OnePlus&#x2F;OPPO merger. The underlying technology in each generation is the same lineage; the renames are organizational.&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Name&lt;&#x2F;th&gt;&lt;th&gt;Era&lt;&#x2F;th&gt;&lt;th&gt;Typical wire spec&lt;&#x2F;th&gt;&lt;th&gt;Architectural trick&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;VOOC 1.0&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;2014, OPPO&lt;&#x2F;td&gt;&lt;td&gt;5V &#x2F; 4A (20W)&lt;&#x2F;td&gt;&lt;td&gt;Low voltage, high current. Charger does the heavy regulation.&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Dash Charge&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;2016–2018, OnePlus&lt;&#x2F;td&gt;&lt;td&gt;5V &#x2F; 4A (20W)&lt;&#x2F;td&gt;&lt;td&gt;OnePlus rebrand of VOOC. Same tech.&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Warp Charge 30&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;2018, OnePlus&lt;&#x2F;td&gt;&lt;td&gt;5V &#x2F; 6A (30W)&lt;&#x2F;td&gt;&lt;td&gt;More current at the same voltage.&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Warp Charge 65&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;2020, OnePlus&lt;&#x2F;td&gt;&lt;td&gt;10V &#x2F; 6.5A (65W)&lt;&#x2F;td&gt;&lt;td&gt;OnePlus rebrand of SuperVOOC 2.0.&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;VOOC 4.0&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;2019, OPPO&lt;&#x2F;td&gt;&lt;td&gt;5V &#x2F; 6A (30W)&lt;&#x2F;td&gt;&lt;td&gt;Same tier as Warp 30.&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;SuperVOOC 1.0&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;2018, OPPO&lt;&#x2F;td&gt;&lt;td&gt;10V &#x2F; 5A (50W)&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;Dual-cell series battery&lt;&#x2F;strong&gt;. Charge pump in the phone.&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;SuperVOOC 2.0&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;2020&lt;&#x2F;td&gt;&lt;td&gt;10V &#x2F; 6.5A (65W)&lt;&#x2F;td&gt;&lt;td&gt;—&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;SuperVOOC 80W&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;2021&lt;&#x2F;td&gt;&lt;td&gt;10V &#x2F; 8A&lt;&#x2F;td&gt;&lt;td&gt;—&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;SuperVOOC 100W&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;2022&lt;&#x2F;td&gt;&lt;td&gt;10V &#x2F; 10A&lt;&#x2F;td&gt;&lt;td&gt;—&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;SuperVOOC 150W&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;2022&lt;&#x2F;td&gt;&lt;td&gt;20V &#x2F; 7.5A&lt;&#x2F;td&gt;&lt;td&gt;Higher voltage, deeper charge-pump ratio.&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;SuperVOOC 240W&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;2022&lt;&#x2F;td&gt;&lt;td&gt;24V &#x2F; 10A&lt;&#x2F;td&gt;&lt;td&gt;Reference design. Production phones top out near 100W.&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;Everything from SuperVOOC 1.0 onward is architecturally distinct from PD and from the earlier Dash&#x2F;Warp&#x2F;VOOC line. The next section covers why.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;what-distinguishes-supervooc&quot;&gt;What distinguishes SuperVOOC&lt;a class=&quot;post-anchor&quot; href=&quot;#what-distinguishes-supervooc&quot; aria-label=&quot;Anchor link for: what-distinguishes-supervooc&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Four mechanisms, which compound.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;1-dual-cell-series-battery&quot;&gt;1. Dual-cell series battery&lt;a class=&quot;post-anchor&quot; href=&quot;#1-dual-cell-series-battery&quot; aria-label=&quot;Anchor link for: 1-dual-cell-series-battery&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;A SuperVOOC phone uses &lt;strong&gt;two battery cells wired in series&lt;&#x2F;strong&gt;. A OnePlus 12, for example, ships two ~2700 mAh cells whose terminals are connected &lt;code&gt;(+)–[cell A]–(–)&#x2F;(+)–[cell B]–(–)&lt;&#x2F;code&gt;, giving a pack with ~8.7V nominal instead of the ~4.35V of a conventional single-cell phone. Same energy capacity. Double the pack voltage. Half the pack current at any given charging power.&lt;&#x2F;p&gt;
&lt;p&gt;Resistive losses in the cells, busbars, protection IC, and connecting traces scale as I². Halving the current quarters the I²R dissipation. A single-cell phone absorbing 100W must push ~22A through its cells; a dual-cell phone absorbs the same 100W at ~11A. The 4× heat reduction is a direct consequence of Ohm&#x27;s law on a load whose resistance cannot be designed away.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;2-charge-pump-rather-than-buck-converter&quot;&gt;2. Charge pump rather than buck converter&lt;a class=&quot;post-anchor&quot; href=&quot;#2-charge-pump-rather-than-buck-converter&quot; aria-label=&quot;Anchor link for: 2-charge-pump-rather-than-buck-converter&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;A typical phone charge IC is a synchronous buck converter — a pair of MOSFETs and an inductor switching at a few hundred kHz to step the input voltage down. Buck IC efficiency under heavy load is 88–93%. The remaining 7–12% becomes heat next to the battery.&lt;&#x2F;p&gt;
&lt;p&gt;A &lt;strong&gt;charge pump&lt;&#x2F;strong&gt; performs the same function with a different topology: capacitors instead of an inductor, with a fixed integer ratio (2:1, 4:1) rather than an arbitrary one. The constraint pays off — a 2:1 charge pump halves the input voltage and doubles the current at 97–98% efficiency. The cost is the fixed ratio: the input voltage must be approximately 2× or 4× what the cells require.&lt;&#x2F;p&gt;
&lt;p&gt;SuperVOOC combines the two. The charger delivers 10V at 8–10A; the phone&#x27;s 2:1 charge pump halves that to 5V at 16–20A; this flows into the dual-cell pack at ~8.7V, with each cell seeing ~4.35V at half the pack current. The arithmetic only closes when input voltage, charge-pump ratio, and pack topology are co-designed.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;3-charger-side-regulation-via-mcu-to-mcu-handshake&quot;&gt;3. Charger-side regulation via MCU-to-MCU handshake&lt;a class=&quot;post-anchor&quot; href=&quot;#3-charger-side-regulation-via-mcu-to-mcu-handshake&quot; aria-label=&quot;Anchor link for: 3-charger-side-regulation-via-mcu-to-mcu-handshake&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;Under USB-PD the device runs the charge curve. The phone reads cell voltage and temperature, decides what it wants, requests it from the charger, and converts the input internally. The charger is a voltage source that complies.&lt;&#x2F;p&gt;
&lt;p&gt;SuperVOOC moves the regulation loop into the charger. The charger contains its own MCU. At plug-in, charger MCU and phone MCU exchange a proprietary handshake over the CC pin (or D+&#x2F;D- on earlier revisions). After the handshake, the phone streams real-time cell voltage and temperature telemetry to the charger and the charger adjusts its output accordingly — fine-grained, high-rate, and outside the USB-PD spec.&lt;&#x2F;p&gt;
&lt;p&gt;The consequence is that more conversion happens inside the charger and less inside the phone. The charger runs warm; the phone often does not. This inverts the thermal profile of a generic PD charger.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;4-cable-id-resistor&quot;&gt;4. Cable ID resistor&lt;a class=&quot;post-anchor&quot; href=&quot;#4-cable-id-resistor&quot; aria-label=&quot;Anchor link for: 4-cable-id-resistor&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;USB-PD specifies an e-marker chip in cables rated above 3A. SuperVOOC piggybacks on the same mechanism with a different signal: a specific resistor value to ground on a particular pin tells the charger that the cable is rated for 8A &#x2F; 10A &#x2F; 12.5A continuous and that high-current mode is permitted. Without that resistor the charger will not deliver above the PD-PPS fallback rate, regardless of what the phone requests.&lt;&#x2F;p&gt;
&lt;p&gt;This is why a SuperVOOC charger paired with a generic USB-C cable charges a OnePlus at PD-PPS speeds: the charger does not recognize an unidentified cable. The reverse — an OPPO&#x2F;OnePlus cable into a generic charger — is also a no-op, because the generic charger has no MCU on the other end.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;what-happens-when-a-oneplus-plugs-into-an-anker&quot;&gt;What happens when a OnePlus plugs into an Anker&lt;a class=&quot;post-anchor&quot; href=&quot;#what-happens-when-a-oneplus-plugs-into-an-anker&quot; aria-label=&quot;Anchor link for: what-happens-when-a-oneplus-plugs-into-an-anker&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Concrete numbers for the four chargers in question.&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;&#x2F;th&gt;&lt;th&gt;&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;img src=&quot;https:&#x2F;&#x2F;blog.s99.sh&#x2F;blog&#x2F;oneplus-usbpowerdelivery-vooc&#x2F;iniu-100w.jpg&quot; alt=&quot;INIU 100W 3-port&quot; &#x2F;&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;img src=&quot;https:&#x2F;&#x2F;blog.s99.sh&#x2F;blog&#x2F;oneplus-usbpowerdelivery-vooc&#x2F;anker-prime-67w.jpg&quot; alt=&quot;Anker Prime 67W 3-port&quot; &#x2F;&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;INIU 100W 3-port GaN&lt;&#x2F;strong&gt; (B0DJ8C3MZD)&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;Anker Prime 67W 3-port&lt;&#x2F;strong&gt; (B0C4YTB56H)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;img src=&quot;https:&#x2F;&#x2F;blog.s99.sh&#x2F;blog&#x2F;oneplus-usbpowerdelivery-vooc&#x2F;anker-prime-100w.jpg&quot; alt=&quot;Anker Prime 100W 3-port&quot; &#x2F;&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;img src=&quot;https:&#x2F;&#x2F;blog.s99.sh&#x2F;blog&#x2F;oneplus-usbpowerdelivery-vooc&#x2F;anker-prime-250w-station.jpg&quot; alt=&quot;Anker Prime 250W 6-port station&quot; &#x2F;&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Anker Prime 100W 3-port&lt;&#x2F;strong&gt; (B0C4DGBHY2)&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;Anker Prime 250W 6-port station&lt;&#x2F;strong&gt; (B0CYLL8Y89)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;All four advertise PD 3.0 + PPS. The first three are single-unit GaN; the last is a desktop power station, which on its higher-power port also supports PD 3.1 EPR (28V) for laptops. None implements VOOC, SuperVOOC, or any other proprietary protocol — generic chargers cannot, lacking both the MCU and the proprietary firmware.&lt;&#x2F;p&gt;
&lt;p&gt;When a OnePlus plugs into any of them, the negotiation proceeds as follows:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Source capabilities.&lt;&#x2F;strong&gt; The charger advertises its PDO list, including PPS ranges such as &lt;code&gt;3.3–11V @ 5A&lt;&#x2F;code&gt; and &lt;code&gt;3.3–21V @ 3.25A&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Phone selects PPS.&lt;&#x2F;strong&gt; The OnePlus selects the PPS PDO whose voltage range covers &lt;code&gt;~2 × battery voltage&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Phone requests V&#x2F;I.&lt;&#x2F;strong&gt; It requests, e.g., &lt;code&gt;8.94V @ 3.00A&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Charger complies; the phone&#x27;s charge pump halves the 9V to ~4.5V at ~6A into the cell.&lt;&#x2F;strong&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Loop.&lt;&#x2F;strong&gt; The phone re-issues an adjusted V&#x2F;I request every few hundred milliseconds.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;The constraint is at step 3. &lt;strong&gt;OnePlus phones cap their PPS current request at ~3A&lt;&#x2F;strong&gt;, regardless of what the charger offers. A 100W single-port unit with a &lt;code&gt;3.3–11V @ 5A&lt;&#x2F;code&gt; PPS PDO still receives a 3A request from the phone. OPPO&#x2F;OnePlus reserves the high-current path for the proprietary protocol.&lt;&#x2F;p&gt;
&lt;p&gt;Measured draw at the USB-C connector with a power meter on a OnePlus 12-class phone:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Battery state&lt;&#x2F;th&gt;&lt;th&gt;Phone&#x27;s PPS request&lt;&#x2F;th&gt;&lt;th&gt;Wattage at the port&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;0–10% (warm-up)&lt;&#x2F;td&gt;&lt;td&gt;~5V × 2–3A&lt;&#x2F;td&gt;&lt;td&gt;10–15W&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;10–60% (bulk CC)&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;~8.9–9.0V × 3.0A&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;~27W&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;60–80% (transition)&lt;&#x2F;td&gt;&lt;td&gt;~9V × 2–2.5A&lt;&#x2F;td&gt;&lt;td&gt;18–22W&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;80–95% (CV taper)&lt;&#x2F;td&gt;&lt;td&gt;~8.7V × 1–1.5A&lt;&#x2F;td&gt;&lt;td&gt;8–13W&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;95–100% (trickle)&lt;&#x2F;td&gt;&lt;td&gt;~5V × 0.5A&lt;&#x2F;td&gt;&lt;td&gt;2–3W&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;Bulk-stage draw at the connector reads approximately &lt;strong&gt;27W&lt;&#x2F;strong&gt;, sustained for 15–25 minutes before the taper.&lt;&#x2F;p&gt;
&lt;p&gt;The voltage choice is not arbitrary. The 2:1 charge pump in front of the single cell wants its input at approximately twice the cell voltage. A cell at 4.0–4.4V during bulk → request 8.0–8.9V at the port. The 3A figure is the phone-side cap.&lt;&#x2F;p&gt;
&lt;p&gt;Removing PPS — connecting the same phone to a fixed-PDO PD charger with no PPS PDOs — falls back to &lt;code&gt;9V&#x2F;2A = 18W&lt;&#x2F;code&gt; on a fixed rail. This is slower and warmer because the conversion stage no longer has voltage tracking and the charge pump cannot remain at its efficiency sweet spot across the curve.&lt;&#x2F;p&gt;
&lt;p&gt;Among the four chargers shown:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;INIU 100W&lt;&#x2F;strong&gt;, &lt;strong&gt;Anker 67W&lt;&#x2F;strong&gt;, and &lt;strong&gt;Anker 100W&lt;&#x2F;strong&gt; deliver the same ~27W bulk to the phone. The additional wattage on the 100W units serves laptops and other devices.&lt;&#x2F;li&gt;
&lt;li&gt;The &lt;strong&gt;Anker Prime 250W station&lt;&#x2F;strong&gt; also delivers ~27W to the phone. Its advantage is sustaining that rate while simultaneously running a 100W laptop on another port without throttling either.&lt;&#x2F;li&gt;
&lt;li&gt;None triggers the SuperVOOC handshake. The &quot;VOOC&quot; indicator in OxygenOS labels any charge above an internal wattage threshold; it does not confirm the protocol.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;pd-pps-versus-supervooc-head-to-head&quot;&gt;PD-PPS versus SuperVOOC, head to head&lt;a class=&quot;post-anchor&quot; href=&quot;#pd-pps-versus-supervooc-head-to-head&quot; aria-label=&quot;Anchor link for: pd-pps-versus-supervooc-head-to-head&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Same OnePlus 12-class phone, same 5400 mAh dual-cell pack, two different protocols on the wire.&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Stage&lt;&#x2F;th&gt;&lt;th&gt;PD-PPS (Anker &#x2F; INIU)&lt;&#x2F;th&gt;&lt;th&gt;SuperVOOC 80W brick&lt;&#x2F;th&gt;&lt;th&gt;SuperVOOC 100W brick&lt;&#x2F;th&gt;&lt;th&gt;SuperVOOC 150W brick&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Negotiation&lt;&#x2F;td&gt;&lt;td&gt;USB-PD messages on CC&lt;&#x2F;td&gt;&lt;td&gt;Proprietary handshake on CC&lt;&#x2F;td&gt;&lt;td&gt;same&lt;&#x2F;td&gt;&lt;td&gt;same&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Bulk voltage&lt;&#x2F;td&gt;&lt;td&gt;~9V&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;~10V&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;~10V&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;~20V&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Bulk current&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;3A&lt;&#x2F;strong&gt; (phone-capped)&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;8A&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;10A&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;7.5A&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Bulk wattage&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;~27W&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;~80W&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;~100W&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;~150W&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Cable requirement&lt;&#x2F;td&gt;&lt;td&gt;any USB-C&lt;&#x2F;td&gt;&lt;td&gt;OPPO&#x2F;OnePlus cable with ID resistor&lt;&#x2F;td&gt;&lt;td&gt;same&lt;&#x2F;td&gt;&lt;td&gt;same&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Battery topology assumed&lt;&#x2F;td&gt;&lt;td&gt;dual-cell&lt;&#x2F;td&gt;&lt;td&gt;dual-cell&lt;&#x2F;td&gt;&lt;td&gt;dual-cell&lt;&#x2F;td&gt;&lt;td&gt;dual-cell&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;0→100% time (5400 mAh)&lt;&#x2F;td&gt;&lt;td&gt;75–90 min&lt;&#x2F;td&gt;&lt;td&gt;30–35 min&lt;&#x2F;td&gt;&lt;td&gt;&lt;strong&gt;~26 min&lt;&#x2F;strong&gt;&lt;&#x2F;td&gt;&lt;td&gt;~20 min&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Heat felt at&lt;&#x2F;td&gt;&lt;td&gt;the phone&lt;&#x2F;td&gt;&lt;td&gt;the brick&lt;&#x2F;td&gt;&lt;td&gt;the brick&lt;&#x2F;td&gt;&lt;td&gt;the brick&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;The 0→100% numbers do not scale linearly with peak wattage because the taper begins earlier at higher-power protocols. Cells absorb the peak rate only below ~70% state-of-charge; above that the curves converge as the battery itself becomes the bottleneck.&lt;&#x2F;p&gt;
&lt;p&gt;PD-PPS delivers approximately &lt;strong&gt;27% of the phone&#x27;s charging capacity&lt;&#x2F;strong&gt;. The remaining 73% sits behind a handshake that no third-party charger implements.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;a class=&quot;post-anchor&quot; href=&quot;#summary&quot; aria-label=&quot;Anchor link for: summary&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;SuperVOOC speeds require three components, all of them:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;The OPPO &#x2F; OnePlus charger with the SuperVOOC MCU at the matching wattage.&lt;&#x2F;li&gt;
&lt;li&gt;The OPPO &#x2F; OnePlus cable with the ID resistor.&lt;&#x2F;li&gt;
&lt;li&gt;A SuperVOOC-capable phone — a flagship OnePlus from 2020 onward with the dual-cell pack and charge pump.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Missing any one falls the system back to USB-PD with PPS. The phone still charges, at approximately one-quarter of its peak rate, with the thermal load shifted from the charger into the phone, and a 0→100% time of roughly 90 minutes rather than 30.&lt;&#x2F;p&gt;
&lt;p&gt;The &quot;VOOC&quot; indicator in OxygenOS reports a wattage threshold, not a protocol. Generic 27W PD-PPS clears that threshold; so does 100W SuperVOOC. For ground-truth on the wire, a USB-C power meter — the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.chargerlab.com&#x2F;&quot;&gt;ChargerLAB Power-Z KM003C&lt;&#x2F;a&gt; is the common enthusiast tool — reads the advertised PDO list and live V&#x2F;A draw directly.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>boot.initrd.secrets</title>
          <pubDate>Wed, 29 Apr 2026 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://blog.s99.sh/blog/boot-initrd-secrets/</link>
          <guid>https://blog.s99.sh/blog/boot-initrd-secrets/</guid>
          <description xml:base="https://blog.s99.sh/blog/boot-initrd-secrets/">&lt;h1 id=&quot;boot-initrd-secrets&quot;&gt;boot.initrd.secrets&lt;a class=&quot;post-anchor&quot; href=&quot;#boot-initrd-secrets&quot; aria-label=&quot;Anchor link for: boot-initrd-secrets&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h1&gt;
&lt;h2 id=&quot;synopsis&quot;&gt;Synopsis&lt;a class=&quot;post-anchor&quot; href=&quot;#synopsis&quot; aria-label=&quot;Anchor link for: synopsis&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;nix&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-name z-nix&quot;&gt;boot&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-name z-nix&quot;&gt;initrd&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-name z-nix z-source&quot;&gt;secrets =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;  &amp;quot;&#x2F;etc&#x2F;ssh&#x2F;ssh_host_ed25519_key&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-unquoted z-path z-nix&quot;&gt; &#x2F;etc&#x2F;secrets&#x2F;initrd&#x2F;ssh_host_ed25519_key&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;  &amp;quot;&#x2F;etc&#x2F;cryptroot.key&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-unquoted z-path z-nix&quot;&gt; &#x2F;etc&#x2F;secrets&#x2F;initrd&#x2F;cryptroot.key&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;}&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;description&quot;&gt;Description&lt;a class=&quot;post-anchor&quot; href=&quot;#description&quot; aria-label=&quot;Anchor link for: description&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;code&gt;boot.initrd.secrets&lt;&#x2F;code&gt; copies files into the initrd so they are available before the root filesystem is mounted. Typical contents include LUKS keyfiles, SSH host keys for initrd SSH, and VPN configurations.&lt;&#x2F;p&gt;
&lt;p&gt;The option type is &lt;code&gt;attrsOf (nullOr path)&lt;&#x2F;code&gt;. Attribute names are destination paths inside the initrd. Values are source paths on the host, or &lt;code&gt;null&lt;&#x2F;code&gt; to use the attribute name as both source and destination:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;nix&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-name z-nix&quot;&gt;boot&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-name z-nix&quot;&gt;initrd&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-name z-nix&quot;&gt;secrets&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;&#x2F;etc&#x2F;ssh&#x2F;ssh_host_ed25519_key&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-language&quot;&gt; null&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-comment&quot;&gt;# copies &#x2F;etc&#x2F;ssh&#x2F;ssh_host_ed25519_key to &#x2F;etc&#x2F;ssh&#x2F;ssh_host_ed25519_key inside the initrd&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The option is defined in &lt;code&gt;nixos&#x2F;modules&#x2F;system&#x2F;boot&#x2F;stage-1.nix&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;implementation&quot;&gt;Implementation&lt;a class=&quot;post-anchor&quot; href=&quot;#implementation&quot; aria-label=&quot;Anchor link for: implementation&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;There are two code paths. Which one runs depends on the value of &lt;code&gt;boot.loader.supportsInitrdSecrets&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;append-path-supportsinitrdsecrets-true&quot;&gt;Append path (&lt;code&gt;supportsInitrdSecrets = true&lt;&#x2F;code&gt;)&lt;a class=&quot;post-anchor&quot; href=&quot;#append-path-supportsinitrdsecrets-true&quot; aria-label=&quot;Anchor link for: append-path-supportsinitrdsecrets-true&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;GRUB, systemd-boot, and Limine all set &lt;code&gt;supportsInitrdSecrets = true&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;In this path, the initrd is built as a normal Nix derivation without any secrets. At bootloader install time (&lt;code&gt;nixos-rebuild switch&lt;&#x2F;code&gt;), the generated script &lt;code&gt;append-initrd-secrets&lt;&#x2F;code&gt; constructs a second CPIO archive containing the secrets and appends it to the initrd file on the boot partition.&lt;&#x2F;p&gt;
&lt;p&gt;The Linux kernel treats concatenated CPIO archives as a single initramfs — it unpacks them sequentially. This is a documented property of the initramfs format.&lt;&#x2F;p&gt;
&lt;p&gt;Secrets handled this way never enter the Nix store. They reside on the boot partition only.&lt;&#x2F;p&gt;
&lt;p&gt;The script is built in &lt;code&gt;stage-1.nix&lt;&#x2F;code&gt; and stored at &lt;code&gt;config.system.build.initialRamdiskSecretAppender&lt;&#x2F;code&gt;. Bootloader installers call it: GRUB via &lt;code&gt;install-grub.pl&lt;&#x2F;code&gt;, systemd-boot via &lt;code&gt;systemd-boot-builder.py&lt;&#x2F;code&gt;. The path to the script is also recorded in &lt;code&gt;bootspec.json&lt;&#x2F;code&gt; under the &lt;code&gt;initrdSecrets&lt;&#x2F;code&gt; key for use by external boot managers.&lt;&#x2F;p&gt;
&lt;p&gt;Failure to create secrets for the current generation is fatal. Failure for older generations produces a warning and continues — this is expected after removing or renaming a secret source file.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;inline-path-supportsinitrdsecrets-false&quot;&gt;Inline path (&lt;code&gt;supportsInitrdSecrets = false&lt;&#x2F;code&gt;)&lt;a class=&quot;post-anchor&quot; href=&quot;#inline-path-supportsinitrdsecrets-false&quot; aria-label=&quot;Anchor link for: inline-path-supportsinitrdsecrets-false&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;When the bootloader does not support appending, secrets are copied into the initrd derivation at build time:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;mkdir&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; -p&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; $(&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;dirname&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-normal z-shell&quot;&gt;$out&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&#x2F;secrets&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-variable z-other z-normal z-shell&quot;&gt;${dest}&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;cp&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; -Lr&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-source&quot;&gt; ${source&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;#39;} &amp;quot;$out&#x2F;secrets&#x2F;${dest}&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This places secrets in the Nix store. The store is world-readable. An assertion enforces that all values are unquoted Nix paths or store paths in this case, and the build emits:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note that this will result in all secrets being stored world-readable in the Nix store!&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;The &lt;code&gt;path&lt;&#x2F;code&gt; type on the option (rather than &lt;code&gt;str&lt;&#x2F;code&gt;) exists to support this fallback.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;append-initrd-secrets&quot;&gt;append-initrd-secrets&lt;a class=&quot;post-anchor&quot; href=&quot;#append-initrd-secrets&quot; aria-label=&quot;Anchor link for: append-initrd-secrets&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;The script performs the following steps:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Exit immediately if &lt;code&gt;boot.initrd.secrets&lt;&#x2F;code&gt; is empty.&lt;&#x2F;li&gt;
&lt;li&gt;Create a temporary directory.&lt;&#x2F;li&gt;
&lt;li&gt;For each secret, copy the source file to &lt;code&gt;$tmp&#x2F;.initrd-secrets&#x2F;$dest&lt;&#x2F;code&gt;, preserving attributes (&lt;code&gt;cp -a&lt;&#x2F;code&gt;).&lt;&#x2F;li&gt;
&lt;li&gt;Set all timestamps to the Unix epoch (&lt;code&gt;touch -amt 197001010000&lt;&#x2F;code&gt;) for reproducibility.&lt;&#x2F;li&gt;
&lt;li&gt;Sort the file list (&lt;code&gt;sort -z&lt;&#x2F;code&gt;) for deterministic ordering.&lt;&#x2F;li&gt;
&lt;li&gt;Generate a CPIO newc archive owned by root:root (&lt;code&gt;cpio -H newc -R +0:+0 --reproducible&lt;&#x2F;code&gt;).&lt;&#x2F;li&gt;
&lt;li&gt;Compress with the same compressor used for the main initrd.&lt;&#x2F;li&gt;
&lt;li&gt;Append (&lt;code&gt;&amp;gt;&amp;gt;&lt;&#x2F;code&gt;) to the target initrd file.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Simplified:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;tmp&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;$(&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;mktemp&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; -d&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-source&quot;&gt; ${TMPDIR&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;:-&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-punctuation z-definition z-variable&quot;&gt;tmp}&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&#x2F;initrd-secrets.XXXXXXXXXX&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;mkdir&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; -p&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; $(&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;dirname&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-normal z-shell&quot;&gt;$tmp&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&#x2F;.initrd-secrets&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-variable z-other z-normal z-shell&quot;&gt;${dest}&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;cp&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; -a&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-source&quot;&gt; ${source}&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-normal z-shell&quot;&gt;$tmp&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt;&#x2F;.initrd-secrets&#x2F;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-variable z-variable z-other z-normal z-shell&quot;&gt;${dest}&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-builtin&quot;&gt;cd&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-normal z-shell&quot;&gt;$tmp&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character z-escape&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;  &amp;amp;&amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt; find&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; . -mindepth&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; |&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt; xargs&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; touch -amt&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 197001010000&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character z-escape&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;  &amp;amp;&amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt; find&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; . -mindepth&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 1&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; -print0&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; |&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt; sort&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; -z&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character z-escape&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;  |&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt; cpio&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; --quiet -o -H newc -R +0:+0 --reproducible --null&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character z-escape&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;  |&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt; zstd&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; &amp;gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter&quot;&gt;$1&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The compressor and its arguments are taken from &lt;code&gt;config.boot.initrd.compressor&lt;&#x2F;code&gt; and &lt;code&gt;config.boot.initrd.compressorArgs&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;systemd-initrd-integration&quot;&gt;systemd initrd integration&lt;a class=&quot;post-anchor&quot; href=&quot;#systemd-initrd-integration&quot; aria-label=&quot;Anchor link for: systemd-initrd-integration&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;When &lt;code&gt;boot.initrd.systemd.enable = true&lt;&#x2F;code&gt;, secrets are not placed at their final paths directly. They are staged under &lt;code&gt;&#x2F;.initrd-secrets&#x2F;&lt;&#x2F;code&gt; inside the initrd and copied into place by the &lt;code&gt;initrd-nixos-copy-secrets&lt;&#x2F;code&gt; service.&lt;&#x2F;p&gt;
&lt;p&gt;This service is defined in &lt;code&gt;nixos&#x2F;modules&#x2F;system&#x2F;boot&#x2F;systemd&#x2F;initrd-secrets.nix&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;nix&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-name z-nix&quot;&gt;boot&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-name z-nix&quot;&gt;initrd&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-name z-nix&quot;&gt;systemd&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-name z-nix&quot;&gt;services&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-name z-nix z-source&quot;&gt;initrd-nixos-copy-secrets =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-other z-attribute-name z-multipart z-nix&quot;&gt;  description&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;Copy secrets into place&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-other z-attribute-name z-multipart z-nix&quot;&gt;  wantedBy&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;sysinit.target&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; ];&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-other z-attribute-name z-multipart z-nix&quot;&gt;  before&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;cryptsetup-pre.target&amp;quot; &amp;quot;shutdown.target&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; ];&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-other z-attribute-name z-multipart z-nix&quot;&gt;  conflicts&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;shutdown.target&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; ];&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-other z-attribute-name z-multipart z-nix&quot;&gt;  unitConfig&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-other z-attribute-name z-multipart z-nix&quot;&gt;DefaultDependencies&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-language&quot;&gt; false&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-other z-attribute-name z-multipart z-nix&quot;&gt;  script&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt; &amp;#39;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-string&quot;&gt;    for secret in $(cd &#x2F;.initrd-secrets; find . -type f -o -type l); do&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-string&quot;&gt;      mkdir -p &amp;quot;$(dirname &amp;quot;&#x2F;$secret&amp;quot;)&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-string&quot;&gt;      cp &amp;quot;&#x2F;.initrd-secrets&#x2F;$secret&amp;quot; &amp;quot;&#x2F;$secret&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-string&quot;&gt;    done&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string&quot;&gt;  &amp;#39;&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-other z-attribute-name z-multipart z-nix&quot;&gt;  serviceConfig&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-other z-attribute-name z-multipart z-nix&quot;&gt;    Type&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;oneshot&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-other z-attribute-name z-multipart z-nix&quot;&gt;    RemainAfterExit&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-language&quot;&gt; true&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;  };&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;}&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The indirection exists because the initrd mounts a tmpfs on &lt;code&gt;&#x2F;run&lt;&#x2F;code&gt;. Secrets targeting paths under &lt;code&gt;&#x2F;run&lt;&#x2F;code&gt; would be shadowed by this mount. Staging them in &lt;code&gt;&#x2F;.initrd-secrets&#x2F;&lt;&#x2F;code&gt; and copying after mount setup avoids this.&lt;&#x2F;p&gt;
&lt;p&gt;The service runs before &lt;code&gt;cryptsetup-pre.target&lt;&#x2F;code&gt;, so secrets are available before LUKS devices attempt to unlock.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;consumers&quot;&gt;Consumers&lt;a class=&quot;post-anchor&quot; href=&quot;#consumers&quot; aria-label=&quot;Anchor link for: consumers&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Several NixOS modules set &lt;code&gt;boot.initrd.secrets&lt;&#x2F;code&gt; internally.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;boot-initrd-network-ssh&quot;&gt;boot.initrd.network.ssh&lt;a class=&quot;post-anchor&quot; href=&quot;#boot-initrd-network-ssh&quot; aria-label=&quot;Anchor link for: boot-initrd-network-ssh&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;nix&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-name z-nix&quot;&gt;boot&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-name z-nix&quot;&gt;initrd&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-name z-nix&quot;&gt;network&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-name z-nix z-source&quot;&gt;ssh =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-other z-attribute-name z-multipart z-nix&quot;&gt;  enable&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-language&quot;&gt; true&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-other z-attribute-name z-multipart z-nix&quot;&gt;  hostKeys&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; [&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-unquoted z-path z-nix&quot;&gt; &#x2F;etc&#x2F;secrets&#x2F;initrd&#x2F;ssh_host_ed25519_key&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; ];&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;}&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Generates:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;nix&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-name z-nix&quot;&gt;boot&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-name z-nix&quot;&gt;initrd&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-name z-nix&quot;&gt;secrets&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;&#x2F;etc&#x2F;ssh&#x2F;ssh_host_ed25519_key&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; =&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-string z-unquoted z-path z-nix&quot;&gt;  &#x2F;etc&#x2F;secrets&#x2F;initrd&#x2F;ssh_host_ed25519_key&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The module warns against reusing regular host keys. With the append path, the initrd SSH key resides on the unencrypted boot partition.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;boot-initrd-luks-devices-keyfile&quot;&gt;boot.initrd.luks.devices.*.keyFile&lt;a class=&quot;post-anchor&quot; href=&quot;#boot-initrd-luks-devices-keyfile&quot; aria-label=&quot;Anchor link for: boot-initrd-luks-devices-keyfile&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;nix&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-name z-nix&quot;&gt;boot&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-name z-nix&quot;&gt;initrd&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-name z-nix&quot;&gt;secrets&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;&#x2F;etc&#x2F;cryptroot.key&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-unquoted z-path z-nix&quot;&gt; &#x2F;etc&#x2F;secrets&#x2F;initrd&#x2F;cryptroot.key&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-name z-nix&quot;&gt;boot&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-name z-nix&quot;&gt;initrd&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-name z-nix&quot;&gt;luks&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-name z-nix&quot;&gt;devices&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-name z-nix z-source&quot;&gt;cryptroot =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-other z-attribute-name z-multipart z-nix&quot;&gt;  device&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;&#x2F;dev&#x2F;disk&#x2F;by-uuid&#x2F;...&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-other z-attribute-name z-multipart z-nix&quot;&gt;  keyFile&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt; &amp;quot;&#x2F;etc&#x2F;cryptroot.key&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;}&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;boot-initrd-network-openvpn&quot;&gt;boot.initrd.network.openvpn&lt;a class=&quot;post-anchor&quot; href=&quot;#boot-initrd-network-openvpn&quot; aria-label=&quot;Anchor link for: boot-initrd-network-openvpn&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;nix&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-name z-nix&quot;&gt;boot&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-name z-nix&quot;&gt;initrd&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-name z-nix&quot;&gt;network&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-name z-nix z-source&quot;&gt;openvpn =&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-other z-attribute-name z-multipart z-nix&quot;&gt;  enable&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-language&quot;&gt; true&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-other z-attribute-name z-multipart z-nix&quot;&gt;  configuration&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-unquoted z-path z-nix&quot;&gt; &#x2F;etc&#x2F;secrets&#x2F;initrd&#x2F;vpn.ovpn&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;}&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Generates:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;nix&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-name z-nix&quot;&gt;boot&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-name z-nix&quot;&gt;initrd&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-name z-nix&quot;&gt;secrets&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-string&quot;&gt;&amp;quot;&#x2F;etc&#x2F;initrd.ovpn&amp;quot;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-unquoted z-path z-nix&quot;&gt; &#x2F;etc&#x2F;secrets&#x2F;initrd&#x2F;vpn.ovpn&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;security&quot;&gt;Security&lt;a class=&quot;post-anchor&quot; href=&quot;#security&quot; aria-label=&quot;Anchor link for: security&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;Append path.&lt;&#x2F;strong&gt; Secrets are on the boot partition, owned by root, not world-readable. They are not encrypted. The security posture is equivalent to the rest of an unencrypted &lt;code&gt;&#x2F;boot&lt;&#x2F;code&gt; — kernel and initrd are equally exposed. Attacks against the boot partition (replacement, modification) are addressed by Secure Boot and measured boot (TPM), not by this module.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Inline path.&lt;&#x2F;strong&gt; Secrets are in &lt;code&gt;&#x2F;nix&#x2F;store&lt;&#x2F;code&gt; and world-readable. Do not use this path for sensitive material.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Garbage collection.&lt;&#x2F;strong&gt; Old NixOS generations retain their initrd files with appended secrets. When a secret source file is removed, &lt;code&gt;nixos-rebuild switch&lt;&#x2F;code&gt; warns but continues for old generations. The old initrd (with the old secret) remains on &lt;code&gt;&#x2F;boot&lt;&#x2F;code&gt; until the generation is deleted with &lt;code&gt;nix-collect-garbage -d&lt;&#x2F;code&gt; or &lt;code&gt;nix-env --delete-generations&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;files&quot;&gt;Files&lt;a class=&quot;post-anchor&quot; href=&quot;#files&quot; aria-label=&quot;Anchor link for: files&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;File&lt;&#x2F;th&gt;&lt;th&gt;Role&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;nixos&#x2F;modules&#x2F;system&#x2F;boot&#x2F;stage-1.nix&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;Option definition, &lt;code&gt;append-initrd-secrets&lt;&#x2F;code&gt; script, inline fallback, assertion&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;nixos&#x2F;modules&#x2F;system&#x2F;boot&#x2F;systemd&#x2F;initrd-secrets.nix&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;systemd initrd copy service&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;nixos&#x2F;modules&#x2F;system&#x2F;boot&#x2F;loader&#x2F;grub&#x2F;grub.nix&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;GRUB sets &lt;code&gt;supportsInitrdSecrets = true&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;nixos&#x2F;modules&#x2F;system&#x2F;boot&#x2F;loader&#x2F;systemd-boot&#x2F;systemd-boot.nix&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;systemd-boot sets &lt;code&gt;supportsInitrdSecrets = true&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;nixos&#x2F;modules&#x2F;system&#x2F;boot&#x2F;loader&#x2F;grub&#x2F;install-grub.pl&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;Calls &lt;code&gt;append-initrd-secrets&lt;&#x2F;code&gt; per generation&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;nixos&#x2F;modules&#x2F;system&#x2F;boot&#x2F;loader&#x2F;systemd-boot&#x2F;systemd-boot-builder.py&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;Calls &lt;code&gt;append-initrd-secrets&lt;&#x2F;code&gt; per generation&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;nixos&#x2F;modules&#x2F;system&#x2F;boot&#x2F;bootspec.nix&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;Records &lt;code&gt;initrdSecrets&lt;&#x2F;code&gt; path in bootspec.json&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
</description>
      </item>
      <item>
          <title>My Recommended Software Stack for Leaving Gmail and Google Workspace</title>
          <pubDate>Thu, 23 Apr 2026 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://blog.s99.sh/blog/gsuite-migration/</link>
          <guid>https://blog.s99.sh/blog/gsuite-migration/</guid>
          <description xml:base="https://blog.s99.sh/blog/gsuite-migration/">&lt;h1 id=&quot;my-recommended-software-stack-for-leaving-gmail-and-google-workspace&quot;&gt;My Recommended Software Stack for Leaving Gmail and Google Workspace&lt;a class=&quot;post-anchor&quot; href=&quot;#my-recommended-software-stack-for-leaving-gmail-and-google-workspace&quot; aria-label=&quot;Anchor link for: my-recommended-software-stack-for-leaving-gmail-and-google-workspace&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h1&gt;
&lt;p&gt;A lot of people want a simpler, more private, less Google-dependent setup without giving up the tools they rely on. Here’s the stack I use.&lt;&#x2F;p&gt;
&lt;p&gt;The philosophy: pick the best option per category, keep your computer at the center of your data. Favor local over cloud, speed over latency. Unix philosophy — do one thing well.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;what-this-is-not&quot;&gt;What this is NOT&lt;a class=&quot;post-anchor&quot; href=&quot;#what-this-is-not&quot; aria-label=&quot;Anchor link for: what-this-is-not&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;A guide for self-hosters. If that’s you, see &lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#selfHosters&quot;&gt;1&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;email-and-calendar&quot;&gt;Email and Calendar&lt;a class=&quot;post-anchor&quot; href=&quot;#email-and-calendar&quot; aria-label=&quot;Anchor link for: email-and-calendar&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;Use:&lt;&#x2F;strong&gt; Fastmail &lt;img src=&quot;logos&#x2F;fastmail.svg&quot; alt=&quot;Fastmail&quot; width=&quot;32&quot; height=&quot;32&quot; style=&quot;height:32px;width:auto;max-height:32px;max-width:none;display:inline-block;vertical-align:-6px;margin:0 .2em;&quot;&#x2F;&gt;
 — fast, clean, focused on doing email well. Calendar works well enough.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Don’t use:&lt;&#x2F;strong&gt; Proton &lt;img src=&quot;logos&#x2F;proton.svg&quot; alt=&quot;Proton&quot; width=&quot;32&quot; height=&quot;32&quot; style=&quot;height:32px;width:auto;max-height:32px;max-width:none;display:inline-block;vertical-align:-6px;margin:0 .2em;&quot;&#x2F;&gt;
 — was a customer for years. Expensive, slow, web-only, bloated.&lt;&#x2F;p&gt;
&lt;p&gt;Fastmail’s web app is faster and supports IETF standards, so you can drop the browser for a native desktop app.&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#protocols&quot;&gt;2&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;&lt;&#x2F;p&gt;
&lt;h3 id=&quot;important&quot;&gt;!Important!&lt;a class=&quot;post-anchor&quot; href=&quot;#important&quot; aria-label=&quot;Anchor link for: important&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;strong&gt;Domain Registrar:&lt;&#x2F;strong&gt; Porkbun &lt;img src=&quot;logos&#x2F;porkbun.svg&quot; alt=&quot;Porkbun&quot; width=&quot;32&quot; height=&quot;32&quot; style=&quot;height:32px;width:auto;max-height:32px;max-width:none;display:inline-block;vertical-align:-6px;margin:0 .2em;&quot;&#x2F;&gt;
&lt;&#x2F;p&gt;
&lt;p&gt;Use your own domain. Switching providers becomes trivial — point MX and TXT records somewhere new and nobody notices. Your address is yours, not the vendor’s. Porkbun is cheap, simple, and supports WHOIS privacy.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;password-manager&quot;&gt;Password Manager&lt;a class=&quot;post-anchor&quot; href=&quot;#password-manager&quot; aria-label=&quot;Anchor link for: password-manager&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;Use:&lt;&#x2F;strong&gt; 1Password &lt;img src=&quot;logos&#x2F;1password.svg&quot; alt=&quot;1Password&quot; width=&quot;32&quot; height=&quot;32&quot; style=&quot;height:32px;width:auto;max-height:32px;max-width:none;display:inline-block;vertical-align:-6px;margin:0 .2em;&quot;&#x2F;&gt;
 — pretty good UX, cross-device, just don&#x27;t use passkeys&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#passkeys&quot;&gt;3&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;. And then in the future if you want to migrate you can export all your TOTP secrets and data. It makes for a pretty good general purpose database too. Has native apps for all platforms.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;file-storage&quot;&gt;File Storage&lt;a class=&quot;post-anchor&quot; href=&quot;#file-storage&quot; aria-label=&quot;Anchor link for: file-storage&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;Don’t use:&lt;&#x2F;strong&gt; Dropbox &lt;img src=&quot;logos&#x2F;dropbox.svg&quot; alt=&quot;Dropbox&quot; width=&quot;32&quot; height=&quot;32&quot; style=&quot;height:32px;width:auto;max-height:32px;max-width:none;display:inline-block;vertical-align:-6px;margin:0 .2em;&quot;&#x2F;&gt;
 — billing is a labyrinth, they won’t delete your account, only cancel the next subscription. Prisoners, not customers.&lt;&#x2F;p&gt;
&lt;p&gt;Storage should be local first. For sharing on your network, NFS or SMB&#x2F;CIFS does the job.&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#microsoftTryToNameSomethingChallenge&quot;&gt;4&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;External Transfers:&lt;&#x2F;strong&gt; Filemail &lt;img src=&quot;logos&#x2F;filemail.svg&quot; alt=&quot;Filemail&quot; width=&quot;32&quot; height=&quot;32&quot; style=&quot;height:32px;width:auto;max-height:32px;max-width:none;display:inline-block;vertical-align:-6px;margin:0 .2em;&quot;&#x2F;&gt;
 — simple, end-to-end encrypted, simple. You already have internet and storage; you don’t need the cloud to rent your files back to you.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;video-meetings&quot;&gt;Video Meetings&lt;a class=&quot;post-anchor&quot; href=&quot;#video-meetings&quot; aria-label=&quot;Anchor link for: video-meetings&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;Use:&lt;&#x2F;strong&gt; Signal &lt;img src=&quot;logos&#x2F;signal.svg&quot; alt=&quot;Signal&quot; width=&quot;32&quot; height=&quot;32&quot; style=&quot;height:32px;width:auto;max-height:32px;max-width:none;display:inline-block;vertical-align:-6px;margin:0 .2em;&quot;&#x2F;&gt;
, Zoom &lt;img src=&quot;logos&#x2F;zoom.svg&quot; alt=&quot;Zoom&quot; width=&quot;32&quot; height=&quot;32&quot; style=&quot;height:32px;width:auto;max-height:32px;max-width:none;display:inline-block;vertical-align:-6px;margin:0 .2em;&quot;&#x2F;&gt;
&lt;&#x2F;p&gt;
&lt;p&gt;Signal is peer-to-peer, end-to-end encrypted, and simple — but only reaches other Signal users. Use it where you can; fall back to Zoom for everyone else.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;writing-and-notes&quot;&gt;Writing and Notes&lt;a class=&quot;post-anchor&quot; href=&quot;#writing-and-notes&quot; aria-label=&quot;Anchor link for: writing-and-notes&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;Use:&lt;&#x2F;strong&gt; Obsidian &lt;img src=&quot;logos&#x2F;obsidian.svg&quot; alt=&quot;Obsidian&quot; width=&quot;32&quot; height=&quot;32&quot; style=&quot;height:32px;width:auto;max-height:32px;max-width:none;display:inline-block;vertical-align:-6px;margin:0 .2em;&quot;&#x2F;&gt;
 + Markdown — portable, durable, not tied to any vendor. Use &lt;code&gt;.docx&lt;&#x2F;code&gt; when compatibility wins.&lt;&#x2F;p&gt;
&lt;p&gt;Native editors beat web ones:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Linux&lt;&#x2F;th&gt;&lt;th&gt;macOS&lt;&#x2F;th&gt;&lt;th&gt;Windows&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;LibreOffice &lt;img src=&quot;logos&#x2F;libreoffice.svg&quot; alt=&quot;LibreOffice&quot; width=&quot;32&quot; height=&quot;32&quot; style=&quot;height:32px;width:auto;max-height:32px;max-width:none;display:inline-block;vertical-align:-6px;margin:0 .2em;&quot;&#x2F;&gt;
, OnlyOffice &lt;img src=&quot;logos&#x2F;onlyoffice.svg&quot; alt=&quot;OnlyOffice&quot; width=&quot;32&quot; height=&quot;32&quot; style=&quot;height:32px;width:auto;max-height:32px;max-width:none;display:inline-block;vertical-align:-6px;margin:0 .2em;&quot;&#x2F;&gt;
&lt;&#x2F;td&gt;&lt;td&gt;Pages, Word &lt;img src=&quot;logos&#x2F;word.svg&quot; alt=&quot;Microsoft Word&quot; width=&quot;32&quot; height=&quot;32&quot; style=&quot;height:32px;width:auto;max-height:32px;max-width:none;display:inline-block;vertical-align:-6px;margin:0 .2em;&quot;&#x2F;&gt;
&lt;&#x2F;td&gt;&lt;td&gt;Word &lt;img src=&quot;logos&#x2F;word.svg&quot; alt=&quot;Microsoft Word&quot; width=&quot;32&quot; height=&quot;32&quot; style=&quot;height:32px;width:auto;max-height:32px;max-width:none;display:inline-block;vertical-align:-6px;margin:0 .2em;&quot;&#x2F;&gt;
, LibreOffice &lt;img src=&quot;logos&#x2F;libreoffice.svg&quot; alt=&quot;LibreOffice&quot; width=&quot;32&quot; height=&quot;32&quot; style=&quot;height:32px;width:auto;max-height:32px;max-width:none;display:inline-block;vertical-align:-6px;margin:0 .2em;&quot;&#x2F;&gt;
&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;h3 id=&quot;spreadsheets&quot;&gt;Spreadsheets&lt;a class=&quot;post-anchor&quot; href=&quot;#spreadsheets&quot; aria-label=&quot;Anchor link for: spreadsheets&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;strong&gt;Use:&lt;&#x2F;strong&gt; Grist &lt;img src=&quot;logos&#x2F;grist.svg&quot; alt=&quot;Grist&quot; width=&quot;32&quot; height=&quot;32&quot; style=&quot;height:32px;width:auto;max-height:32px;max-width:none;display:inline-block;vertical-align:-6px;margin:0 .2em;&quot;&#x2F;&gt;
 — treats data like a database, not a grid. Typed columns, Python formulas, lightweight apps on top.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;chat&quot;&gt;Chat&lt;a class=&quot;post-anchor&quot; href=&quot;#chat&quot; aria-label=&quot;Anchor link for: chat&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;Use:&lt;&#x2F;strong&gt; Signal &lt;img src=&quot;logos&#x2F;signal.svg&quot; alt=&quot;Signal&quot; width=&quot;32&quot; height=&quot;32&quot; style=&quot;height:32px;width:auto;max-height:32px;max-width:none;display:inline-block;vertical-align:-6px;margin:0 .2em;&quot;&#x2F;&gt;
, Slack &lt;img src=&quot;logos&#x2F;slack.svg&quot; alt=&quot;Slack&quot; width=&quot;32&quot; height=&quot;32&quot; style=&quot;height:32px;width:auto;max-height:32px;max-width:none;display:inline-block;vertical-align:-6px;margin:0 .2em;&quot;&#x2F;&gt;
&lt;&#x2F;p&gt;
&lt;p&gt;Signal for direct messages. Slack for teams. I’d rather recommend a peer-to-peer, channel-based alternative — but unless you like IRC, there isn’t one.&lt;sup class=&quot;footnote-reference&quot;&gt;&lt;a href=&quot;#chat&quot;&gt;5&lt;&#x2F;a&gt;&lt;&#x2F;sup&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;tl-dr&quot;&gt;TL;DR&lt;a class=&quot;post-anchor&quot; href=&quot;#tl-dr&quot; aria-label=&quot;Anchor link for: tl-dr&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Email &#x2F; Calendar:&lt;&#x2F;strong&gt; Fastmail &lt;img src=&quot;logos&#x2F;fastmail.svg&quot; alt=&quot;Fastmail&quot; width=&quot;32&quot; height=&quot;32&quot; style=&quot;height:32px;width:auto;max-height:32px;max-width:none;display:inline-block;vertical-align:-6px;margin:0 .2em;&quot;&#x2F;&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Domain:&lt;&#x2F;strong&gt; Porkbun &lt;img src=&quot;logos&#x2F;porkbun.svg&quot; alt=&quot;Porkbun&quot; width=&quot;32&quot; height=&quot;32&quot; style=&quot;height:32px;width:auto;max-height:32px;max-width:none;display:inline-block;vertical-align:-6px;margin:0 .2em;&quot;&#x2F;&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Passwords:&lt;&#x2F;strong&gt; 1Password &lt;img src=&quot;logos&#x2F;1password.svg&quot; alt=&quot;1Password&quot; width=&quot;32&quot; height=&quot;32&quot; style=&quot;height:32px;width:auto;max-height:32px;max-width:none;display:inline-block;vertical-align:-6px;margin:0 .2em;&quot;&#x2F;&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;File storage:&lt;&#x2F;strong&gt; NFS &#x2F; SMB locally, Filemail &lt;img src=&quot;logos&#x2F;filemail.svg&quot; alt=&quot;Filemail&quot; width=&quot;32&quot; height=&quot;32&quot; style=&quot;height:32px;width:auto;max-height:32px;max-width:none;display:inline-block;vertical-align:-6px;margin:0 .2em;&quot;&#x2F;&gt;
 for external&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Video:&lt;&#x2F;strong&gt; Signal &lt;img src=&quot;logos&#x2F;signal.svg&quot; alt=&quot;Signal&quot; width=&quot;32&quot; height=&quot;32&quot; style=&quot;height:32px;width:auto;max-height:32px;max-width:none;display:inline-block;vertical-align:-6px;margin:0 .2em;&quot;&#x2F;&gt;
, Zoom &lt;img src=&quot;logos&#x2F;zoom.svg&quot; alt=&quot;Zoom&quot; width=&quot;32&quot; height=&quot;32&quot; style=&quot;height:32px;width:auto;max-height:32px;max-width:none;display:inline-block;vertical-align:-6px;margin:0 .2em;&quot;&#x2F;&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Writing:&lt;&#x2F;strong&gt; Obsidian &lt;img src=&quot;logos&#x2F;obsidian.svg&quot; alt=&quot;Obsidian&quot; width=&quot;32&quot; height=&quot;32&quot; style=&quot;height:32px;width:auto;max-height:32px;max-width:none;display:inline-block;vertical-align:-6px;margin:0 .2em;&quot;&#x2F;&gt;
, Markdown, &lt;code&gt;.docx&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Spreadsheets:&lt;&#x2F;strong&gt; Grist &lt;img src=&quot;logos&#x2F;grist.svg&quot; alt=&quot;Grist&quot; width=&quot;32&quot; height=&quot;32&quot; style=&quot;height:32px;width:auto;max-height:32px;max-width:none;display:inline-block;vertical-align:-6px;margin:0 .2em;&quot;&#x2F;&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Chat:&lt;&#x2F;strong&gt; Signal &lt;img src=&quot;logos&#x2F;signal.svg&quot; alt=&quot;Signal&quot; width=&quot;32&quot; height=&quot;32&quot; style=&quot;height:32px;width:auto;max-height:32px;max-width:none;display:inline-block;vertical-align:-6px;margin:0 .2em;&quot;&#x2F;&gt;
, Slack &lt;img src=&quot;logos&#x2F;slack.svg&quot; alt=&quot;Slack&quot; width=&quot;32&quot; height=&quot;32&quot; style=&quot;height:32px;width:auto;max-height:32px;max-width:none;display:inline-block;vertical-align:-6px;margin:0 .2em;&quot;&#x2F;&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;index&quot;&gt;Index&lt;a class=&quot;post-anchor&quot; href=&quot;#index&quot; aria-label=&quot;Anchor link for: index&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Logo&lt;&#x2F;th&gt;&lt;th&gt;Product&lt;&#x2F;th&gt;&lt;th&gt;Category&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;img src=&quot;logos&#x2F;fastmail.svg&quot; alt=&quot;Fastmail&quot; width=&quot;32&quot; height=&quot;32&quot; style=&quot;height:32px;width:auto;max-height:32px;max-width:none;display:inline-block;vertical-align:-6px;margin:0 .2em;&quot;&#x2F;&gt;
&lt;&#x2F;td&gt;&lt;td&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.fastmail.com&quot;&gt;Fastmail&lt;&#x2F;a&gt;&lt;&#x2F;td&gt;&lt;td&gt;Email &amp;amp; Calendar&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;img src=&quot;logos&#x2F;proton.svg&quot; alt=&quot;Proton&quot; width=&quot;32&quot; height=&quot;32&quot; style=&quot;height:32px;width:auto;max-height:32px;max-width:none;display:inline-block;vertical-align:-6px;margin:0 .2em;&quot;&#x2F;&gt;
&lt;&#x2F;td&gt;&lt;td&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;proton.me&quot;&gt;Proton&lt;&#x2F;a&gt;&lt;&#x2F;td&gt;&lt;td&gt;Email&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;img src=&quot;logos&#x2F;porkbun.svg&quot; alt=&quot;Porkbun&quot; width=&quot;32&quot; height=&quot;32&quot; style=&quot;height:32px;width:auto;max-height:32px;max-width:none;display:inline-block;vertical-align:-6px;margin:0 .2em;&quot;&#x2F;&gt;
&lt;&#x2F;td&gt;&lt;td&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;porkbun.com&quot;&gt;Porkbun&lt;&#x2F;a&gt;&lt;&#x2F;td&gt;&lt;td&gt;Domain Registrar&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;img src=&quot;logos&#x2F;1password.svg&quot; alt=&quot;1Password&quot; width=&quot;32&quot; height=&quot;32&quot; style=&quot;height:32px;width:auto;max-height:32px;max-width:none;display:inline-block;vertical-align:-6px;margin:0 .2em;&quot;&#x2F;&gt;
&lt;&#x2F;td&gt;&lt;td&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;1password.com&quot;&gt;1Password&lt;&#x2F;a&gt;&lt;&#x2F;td&gt;&lt;td&gt;Password Manager&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;img src=&quot;logos&#x2F;dropbox.svg&quot; alt=&quot;Dropbox&quot; width=&quot;32&quot; height=&quot;32&quot; style=&quot;height:32px;width:auto;max-height:32px;max-width:none;display:inline-block;vertical-align:-6px;margin:0 .2em;&quot;&#x2F;&gt;
&lt;&#x2F;td&gt;&lt;td&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.dropbox.com&quot;&gt;Dropbox&lt;&#x2F;a&gt;&lt;&#x2F;td&gt;&lt;td&gt;File Storage&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;img src=&quot;logos&#x2F;filemail.svg&quot; alt=&quot;Filemail&quot; width=&quot;32&quot; height=&quot;32&quot; style=&quot;height:32px;width:auto;max-height:32px;max-width:none;display:inline-block;vertical-align:-6px;margin:0 .2em;&quot;&#x2F;&gt;
&lt;&#x2F;td&gt;&lt;td&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.filemail.com&quot;&gt;Filemail&lt;&#x2F;a&gt;&lt;&#x2F;td&gt;&lt;td&gt;File Transfer&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;img src=&quot;logos&#x2F;signal.svg&quot; alt=&quot;Signal&quot; width=&quot;32&quot; height=&quot;32&quot; style=&quot;height:32px;width:auto;max-height:32px;max-width:none;display:inline-block;vertical-align:-6px;margin:0 .2em;&quot;&#x2F;&gt;
&lt;&#x2F;td&gt;&lt;td&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;signal.org&quot;&gt;Signal&lt;&#x2F;a&gt;&lt;&#x2F;td&gt;&lt;td&gt;Chat &amp;amp; Video&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;img src=&quot;logos&#x2F;zoom.svg&quot; alt=&quot;Zoom&quot; width=&quot;32&quot; height=&quot;32&quot; style=&quot;height:32px;width:auto;max-height:32px;max-width:none;display:inline-block;vertical-align:-6px;margin:0 .2em;&quot;&#x2F;&gt;
&lt;&#x2F;td&gt;&lt;td&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;zoom.us&quot;&gt;Zoom&lt;&#x2F;a&gt;&lt;&#x2F;td&gt;&lt;td&gt;Video Meetings&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;img src=&quot;logos&#x2F;obsidian.svg&quot; alt=&quot;Obsidian&quot; width=&quot;32&quot; height=&quot;32&quot; style=&quot;height:32px;width:auto;max-height:32px;max-width:none;display:inline-block;vertical-align:-6px;margin:0 .2em;&quot;&#x2F;&gt;
&lt;&#x2F;td&gt;&lt;td&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;obsidian.md&quot;&gt;Obsidian&lt;&#x2F;a&gt;&lt;&#x2F;td&gt;&lt;td&gt;Writing &amp;amp; Notes&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;img src=&quot;logos&#x2F;libreoffice.svg&quot; alt=&quot;LibreOffice&quot; width=&quot;32&quot; height=&quot;32&quot; style=&quot;height:32px;width:auto;max-height:32px;max-width:none;display:inline-block;vertical-align:-6px;margin:0 .2em;&quot;&#x2F;&gt;
&lt;&#x2F;td&gt;&lt;td&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.libreoffice.org&quot;&gt;LibreOffice&lt;&#x2F;a&gt;&lt;&#x2F;td&gt;&lt;td&gt;Office Suite&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;img src=&quot;logos&#x2F;onlyoffice.svg&quot; alt=&quot;OnlyOffice&quot; width=&quot;32&quot; height=&quot;32&quot; style=&quot;height:32px;width:auto;max-height:32px;max-width:none;display:inline-block;vertical-align:-6px;margin:0 .2em;&quot;&#x2F;&gt;
&lt;&#x2F;td&gt;&lt;td&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.onlyoffice.com&quot;&gt;OnlyOffice&lt;&#x2F;a&gt;&lt;&#x2F;td&gt;&lt;td&gt;Office Suite&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;img src=&quot;logos&#x2F;word.svg&quot; alt=&quot;Microsoft Word&quot; width=&quot;32&quot; height=&quot;32&quot; style=&quot;height:32px;width:auto;max-height:32px;max-width:none;display:inline-block;vertical-align:-6px;margin:0 .2em;&quot;&#x2F;&gt;
&lt;&#x2F;td&gt;&lt;td&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.microsoft.com&#x2F;microsoft-365&#x2F;word&quot;&gt;Microsoft Word&lt;&#x2F;a&gt;&lt;&#x2F;td&gt;&lt;td&gt;Word Processor&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;img src=&quot;logos&#x2F;grist.svg&quot; alt=&quot;Grist&quot; width=&quot;32&quot; height=&quot;32&quot; style=&quot;height:32px;width:auto;max-height:32px;max-width:none;display:inline-block;vertical-align:-6px;margin:0 .2em;&quot;&#x2F;&gt;
&lt;&#x2F;td&gt;&lt;td&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.getgrist.com&quot;&gt;Grist&lt;&#x2F;a&gt;&lt;&#x2F;td&gt;&lt;td&gt;Spreadsheets&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;img src=&quot;logos&#x2F;slack.svg&quot; alt=&quot;Slack&quot; width=&quot;32&quot; height=&quot;32&quot; style=&quot;height:32px;width:auto;max-height:32px;max-width:none;display:inline-block;vertical-align:-6px;margin:0 .2em;&quot;&#x2F;&gt;
&lt;&#x2F;td&gt;&lt;td&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;slack.com&quot;&gt;Slack&lt;&#x2F;a&gt;&lt;&#x2F;td&gt;&lt;td&gt;Chat&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;hr &#x2F;&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;selfHosters&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;1&lt;&#x2F;sup&gt;
&lt;p&gt;I&#x27;m working on some new projects to serve these gaps. See my git repos.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;protocols&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;2&lt;&#x2F;sup&gt;
&lt;p&gt;The standards-protocol point is bigger than just native desktop software. It’s about having authority over your own data. That’s my biggest gripe with Proton and Gmail. You should be able to use your data the way you want — whether that’s through a desktop app or a CLI tool. Don’t settle for less, unless a better option simply doesn’t exist yet.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;passkeys&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;3&lt;&#x2F;sup&gt;
&lt;p&gt;They purposely ruined them by making them unexportable.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;microsoftTryToNameSomethingChallenge&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;4&lt;&#x2F;sup&gt;
&lt;p&gt;microsoft Try To Name Something Challenge&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
&lt;div class=&quot;footnote-definition&quot; id=&quot;chat&quot;&gt;&lt;sup class=&quot;footnote-definition-label&quot;&gt;5&lt;&#x2F;sup&gt;
&lt;p&gt;Don’t email me about Matrix. Do email me about IRC.&lt;&#x2F;p&gt;
&lt;&#x2F;div&gt;
</description>
      </item>
      <item>
          <title>About Me</title>
          <pubDate>Sun, 19 Apr 2026 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://blog.s99.sh/about-me/</link>
          <guid>https://blog.s99.sh/about-me/</guid>
          <description xml:base="https://blog.s99.sh/about-me/">&lt;p&gt;Hi, I&#x27;m Scott&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;m a Senior Developer and Open Source project maintainer of electricEye.&lt;&#x2F;p&gt;
&lt;p&gt;I currently live in Texas with my partner and our cat🐈.&lt;&#x2F;p&gt;
&lt;p&gt;I use my site as a method of blogging about my learnings, as well as sharing information about projects I have previously, or am currently, working on in my spare time.&lt;&#x2F;p&gt;
&lt;p&gt;I specialize in Internet Protocol versions 4 and 6, software development, operations, system administration, and 🔥blazingly🔥 fast code&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;m a Linux user, a big advocate for the Open Source Software Movement, and the libre (free as in freedom) movement and I host my own services rather than handing it off to someone else&#x27;s platform.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Lambdo&#x27;s and Dont&#x27;s</title>
          <pubDate>Sun, 19 Apr 2026 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://blog.s99.sh/blog/lambdagony/</link>
          <guid>https://blog.s99.sh/blog/lambdagony/</guid>
          <description xml:base="https://blog.s99.sh/blog/lambdagony/">&lt;h1 id=&quot;lambdagony-you-don-t-need-a-lambda-you-need-a-socket&quot;&gt;Lambdagony: You Don&#x27;t Need a Lambda, You Need a Socket&lt;a class=&quot;post-anchor&quot; href=&quot;#lambdagony-you-don-t-need-a-lambda-you-need-a-socket&quot; aria-label=&quot;Anchor link for: lambdagony-you-don-t-need-a-lambda-you-need-a-socket&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h1&gt;
&lt;p&gt;You know that Lambda function you wrote? The one that receives an HTTP request, does some small thing, and returns a response? The one with 200 lines of Terraform around it, an API Gateway in front of it, a CloudWatch log group you always forget how to query, a 3-second cold start that appears at the worst possible time, and a deployment pipeline held together with YAML and prayers?&lt;&#x2F;p&gt;
&lt;p&gt;What if I told you the exact same workload could be a 12-line systemd unit on a box you already have?&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-problem-with-lambda&quot;&gt;The Problem with Lambda&lt;a class=&quot;post-anchor&quot; href=&quot;#the-problem-with-lambda&quot; aria-label=&quot;Anchor link for: the-problem-with-lambda&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Lambda is great for what Lambda is great for. The issue is that people reach for it when they don&#x27;t need it. You don&#x27;t have &quot;thousands of concurrent requests with unpredictable spiky traffic.&quot; You have a webhook that fires twice an hour from your git forge and a cron job that runs nightly. You are not Netflix.&lt;&#x2F;p&gt;
&lt;p&gt;But you&#x27;ve been told serverless is the future, so now you have:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;A Lambda function&lt;&#x2F;li&gt;
&lt;li&gt;An API Gateway (with its own fun config)&lt;&#x2F;li&gt;
&lt;li&gt;An IAM role with a policy document&lt;&#x2F;li&gt;
&lt;li&gt;A CloudWatch log group&lt;&#x2F;li&gt;
&lt;li&gt;A deployment artifact (zip or container image)&lt;&#x2F;li&gt;
&lt;li&gt;Some way to get that artifact there (CI, SAM, CDK, Terraform, clicking around in the console and hoping you remember what you did)&lt;&#x2F;li&gt;
&lt;li&gt;Cold starts&lt;&#x2F;li&gt;
&lt;li&gt;A 15 minute execution timeout&lt;&#x2F;li&gt;
&lt;li&gt;No local state, no filesystem (well, 512 MB of &lt;code&gt;&#x2F;tmp&lt;&#x2F;code&gt;), no persistent connections&lt;&#x2F;li&gt;
&lt;li&gt;A bill that&#x27;s small but irritating to reason about&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;All for something that accepts a JSON payload, writes a row to a database, and returns &lt;code&gt;{&quot;ok&quot;: true}&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-systemd-gives-you-for-free&quot;&gt;What systemd Gives You for Free&lt;a class=&quot;post-anchor&quot; href=&quot;#what-systemd-gives-you-for-free&quot; aria-label=&quot;Anchor link for: what-systemd-gives-you-for-free&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;systemd has a feature called &lt;strong&gt;socket activation&lt;&#x2F;strong&gt;. It&#x27;s been around since 2010. The idea is dead simple: systemd listens on a port. When a connection arrives, it starts your service and hands over the socket. When your service is idle, it can exit, and systemd goes back to listening.&lt;&#x2F;p&gt;
&lt;p&gt;Sound familiar? It&#x27;s Lambda. It&#x27;s literally Lambda. Except:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;No cold start that you can&#x27;t control (your binary is local, on an SSD)&lt;&#x2F;li&gt;
&lt;li&gt;No 15-minute timeout&lt;&#x2F;li&gt;
&lt;li&gt;Full filesystem access&lt;&#x2F;li&gt;
&lt;li&gt;Persistent state if you want it&lt;&#x2F;li&gt;
&lt;li&gt;Logs go to the journal, queryable with &lt;code&gt;journalctl -u yourservice&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Deployment is &lt;code&gt;scp&lt;&#x2F;code&gt; and &lt;code&gt;systemctl daemon-reload&lt;&#x2F;code&gt; (or NixOS rebuild, if you&#x27;re civilized)&lt;&#x2F;li&gt;
&lt;li&gt;It&#x27;s free. The box is already running.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;how-it-works&quot;&gt;How It Works&lt;a class=&quot;post-anchor&quot; href=&quot;#how-it-works&quot; aria-label=&quot;Anchor link for: how-it-works&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;You need two unit files. That&#x27;s it.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;the-socket-unit&quot;&gt;The Socket Unit&lt;a class=&quot;post-anchor&quot; href=&quot;#the-socket-unit&quot; aria-label=&quot;Anchor link for: the-socket-unit&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;ini&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;# &#x2F;etc&#x2F;systemd&#x2F;system&#x2F;myapi.socket&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-section z-group-title z-ini&quot;&gt;Unit&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword z-other z-definition z-ini&quot;&gt;Description&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;My API Socket&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-section z-group-title z-ini&quot;&gt;Socket&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword z-other z-definition z-ini&quot;&gt;ListenStream&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;8080&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-section z-group-title z-ini&quot;&gt;Install&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword z-other z-definition z-ini&quot;&gt;WantedBy&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;sockets.target&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This tells systemd: listen on port 8080. When someone connects, start the corresponding &lt;code&gt;.service&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;the-service-unit&quot;&gt;The Service Unit&lt;a class=&quot;post-anchor&quot; href=&quot;#the-service-unit&quot; aria-label=&quot;Anchor link for: the-service-unit&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;ini&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;# &#x2F;etc&#x2F;systemd&#x2F;system&#x2F;myapi.service&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-section z-group-title z-ini&quot;&gt;Unit&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword z-other z-definition z-ini&quot;&gt;Description&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;My API Service&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword z-other z-definition z-ini&quot;&gt;Requires&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;myapi.socket&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-section z-group-title z-ini&quot;&gt;Service&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword z-other z-definition z-ini&quot;&gt;Type&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;notify&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword z-other z-definition z-ini&quot;&gt;ExecStart&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;&#x2F;usr&#x2F;local&#x2F;bin&#x2F;myapi&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword z-other z-definition z-ini&quot;&gt;NonBlocking&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;true&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;# Hardening, because we&amp;#39;re not animals&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword z-other z-definition z-ini&quot;&gt;DynamicUser&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;yes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword z-other z-definition z-ini&quot;&gt;ProtectSystem&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;strict&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword z-other z-definition z-ini&quot;&gt;ProtectHome&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;yes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword z-other z-definition z-ini&quot;&gt;PrivateTmp&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;yes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword z-other z-definition z-ini&quot;&gt;NoNewPrivileges&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;yes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Your binary receives the already-open socket as file descriptor 3 (or via the &lt;code&gt;LISTEN_FDS&lt;&#x2F;code&gt; protocol — sd_listen_fds(3) if you want to be proper about it). Most languages have a library for this. In Go:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;listeners&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; _&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; :=&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; activation&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;Listeners&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;()&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;http&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;Serve&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;listeners&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt;0&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;],&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; myHandler&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;In Python:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;python&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword&quot;&gt;import&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; socket&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; os&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;fd&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric&quot;&gt; 3&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;  # or use the sd-daemon library&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-source&quot;&gt;sock&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator&quot;&gt; =&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt; socket&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;fromfd&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;fd&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt; socket&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;AF_INET&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt; socket&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;SOCK_STREAM&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;In Rust there&#x27;s &lt;code&gt;listenfd&lt;&#x2F;code&gt;. In C there&#x27;s &lt;code&gt;sd-daemon&lt;&#x2F;code&gt;. You get the idea.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;enable-and-start&quot;&gt;Enable and Start&lt;a class=&quot;post-anchor&quot; href=&quot;#enable-and-start&quot; aria-label=&quot;Anchor link for: enable-and-start&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;systemctl&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; enable --now myapi.socket&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That&#x27;s it. Port 8080 is now listening. Your service starts on first connection.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;but-what-about-scaling&quot;&gt;&quot;But What About Scaling?&quot;&lt;a class=&quot;post-anchor&quot; href=&quot;#but-what-about-scaling&quot; aria-label=&quot;Anchor link for: but-what-about-scaling&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;If you genuinely need to handle thousands of concurrent requests with autoscaling across regions, yes, use a managed service. Nobody is arguing you should run Twitter on socket-activated systemd units.&lt;&#x2F;p&gt;
&lt;p&gt;But be honest with yourself: most of the things people put in Lambdas are internal tools, webhooks, simple APIs, and cron jobs. These are workloads that a $5&#x2F;month VPS handles without breaking a sweat. The &quot;scale&quot; argument is a thought-terminating cliche that prevents people from choosing the simple option.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;but-what-about-high-availability&quot;&gt;&quot;But What About High Availability?&quot;&lt;a class=&quot;post-anchor&quot; href=&quot;#but-what-about-high-availability&quot; aria-label=&quot;Anchor link for: but-what-about-high-availability&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;You already have a server. Is it running? Then your service is available. If it&#x27;s not running, your Lambda isn&#x27;t going to help you either because the database it talks to is probably on the same infrastructure that&#x27;s down.&lt;&#x2F;p&gt;
&lt;p&gt;If you genuinely need HA, run two boxes and put a load balancer in front. Still simpler than Lambda + API Gateway + custom domain + ACM certificate + Route53 record.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;but-lambda-scales-to-zero&quot;&gt;&quot;But Lambda Scales to Zero!&quot;&lt;a class=&quot;post-anchor&quot; href=&quot;#but-lambda-scales-to-zero&quot; aria-label=&quot;Anchor link for: but-lambda-scales-to-zero&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;So does a socket-activated service. That&#x27;s the whole point. When nobody is connecting, your process isn&#x27;t running. The socket is open, held by PID 1, using approximately zero resources.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;bonus-systemd-timers-instead-of-cloudwatch-events-eventbridge&quot;&gt;Bonus: systemd Timers Instead of CloudWatch Events &#x2F; EventBridge&lt;a class=&quot;post-anchor&quot; href=&quot;#bonus-systemd-timers-instead-of-cloudwatch-events-eventbridge&quot; aria-label=&quot;Anchor link for: bonus-systemd-timers-instead-of-cloudwatch-events-eventbridge&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;That cron-triggered Lambda? It&#x27;s a systemd timer:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;ini&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;# &#x2F;etc&#x2F;systemd&#x2F;system&#x2F;nightly-job.timer&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-section z-group-title z-ini&quot;&gt;Unit&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword z-other z-definition z-ini&quot;&gt;Description&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;Run nightly job&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-section z-group-title z-ini&quot;&gt;Timer&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword z-other z-definition z-ini&quot;&gt;OnCalendar&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;*-*-* 03:00:00&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword z-other z-definition z-ini&quot;&gt;Persistent&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;true&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-section z-group-title z-ini&quot;&gt;Install&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword z-other z-definition z-ini&quot;&gt;WantedBy&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;timers.target&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;ini&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-comment&quot;&gt;# &#x2F;etc&#x2F;systemd&#x2F;system&#x2F;nightly-job.service&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-section z-group-title z-ini&quot;&gt;Unit&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword z-other z-definition z-ini&quot;&gt;Description&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;Nightly job&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-punctuation&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-section z-group-title z-ini&quot;&gt;Service&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword z-other z-definition z-ini&quot;&gt;Type&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;oneshot&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword z-other z-definition z-ini&quot;&gt;ExecStart&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;&#x2F;usr&#x2F;local&#x2F;bin&#x2F;nightly-job&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-keyword z-other z-definition z-ini&quot;&gt;DynamicUser&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-source&quot;&gt;yes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-entity z-name z-function&quot;&gt;systemctl&lt;&#x2F;span&gt;&lt;span class=&quot;z-string&quot;&gt; enable --now nightly-job.timer&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Want to see when it last ran? &lt;code&gt;systemctl status nightly-job.timer&lt;&#x2F;code&gt;. Want logs? &lt;code&gt;journalctl -u nightly-job&lt;&#x2F;code&gt;. Want to run it manually right now? &lt;code&gt;systemctl start nightly-job&lt;&#x2F;code&gt;. No clicking through 4 AWS console pages.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;when-to-actually-use-lambda&quot;&gt;When to Actually Use Lambda&lt;a class=&quot;post-anchor&quot; href=&quot;#when-to-actually-use-lambda&quot; aria-label=&quot;Anchor link for: when-to-actually-use-lambda&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;You have genuinely spiky, unpredictable traffic that spans orders of magnitude&lt;&#x2F;li&gt;
&lt;li&gt;You don&#x27;t want to manage any infrastructure at all (but then you&#x27;re managing AWS infrastructure, which is its own beast)&lt;&#x2F;li&gt;
&lt;li&gt;You&#x27;re already deep in the AWS ecosystem and the integration points save you real time&lt;&#x2F;li&gt;
&lt;li&gt;You need to run in multiple regions with minimal effort&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;when-to-use-a-socket-activated-service-instead&quot;&gt;When to Use a Socket-Activated Service Instead&lt;a class=&quot;post-anchor&quot; href=&quot;#when-to-use-a-socket-activated-service-instead&quot; aria-label=&quot;Anchor link for: when-to-use-a-socket-activated-service-instead&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;You have a server&lt;&#x2F;li&gt;
&lt;li&gt;Your traffic is low to moderate&lt;&#x2F;li&gt;
&lt;li&gt;You want to actually understand your infrastructure&lt;&#x2F;li&gt;
&lt;li&gt;You&#x27;re tired of 200 lines of IaC for a 50-line program&lt;&#x2F;li&gt;
&lt;li&gt;You value the ability to &lt;code&gt;ssh&lt;&#x2F;code&gt; in and just look at things&lt;&#x2F;li&gt;
&lt;li&gt;You don&#x27;t want to learn a new debugging&#x2F;logging&#x2F;deployment paradigm for what is fundamentally &quot;a program that listens on a port&quot;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;the-punchline&quot;&gt;The Punchline&lt;a class=&quot;post-anchor&quot; href=&quot;#the-punchline&quot; aria-label=&quot;Anchor link for: the-punchline&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;The cloud providers have done an incredible job of making people forget that computers are good at running programs. A modern Linux box with systemd can do socket activation, process supervision, resource limits (cgroups), sandboxing (namespaces, seccomp), logging, and scheduled tasks. It&#x27;s been able to do all of this for over a decade.&lt;&#x2F;p&gt;
&lt;p&gt;Lambda is a product. Socket activation is a feature of your operating system. One of them has a marketing budget.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>eyeFS: A Linux Kernel Filesystem for Multi-Camera H.265 Recording</title>
          <pubDate>Thu, 09 Apr 2026 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://blog.s99.sh/blog/eyefs/</link>
          <guid>https://blog.s99.sh/blog/eyefs/</guid>
          <description xml:base="https://blog.s99.sh/blog/eyefs/">&lt;p&gt;&lt;strong&gt;Working title:&lt;&#x2F;strong&gt; &lt;code&gt;eyeFS&lt;&#x2F;code&gt;
&lt;strong&gt;Status:&lt;&#x2F;strong&gt; design document&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;1-abstract&quot;&gt;1. Abstract&lt;a class=&quot;post-anchor&quot; href=&quot;#1-abstract&quot; aria-label=&quot;Anchor link for: 1-abstract&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;eyeFS is a Linux kernel filesystem that turns a dedicated block device into a circular log of H.265 chunks, supporting an arbitrary number of concurrent video feeds added and removed at runtime. The device is operated as a byte-addressable ring; chunks are packed back-to-back with no gaps and no container format; each chunk is an independently decodable H.265 Annex B bitstream. Multiple feeds share a single write head — a zipper merge across cameras — so that retention is uniform across feeds and the ring has one authoritative log-time axis. The userspace surface is a standard mount point: chunks appear as files under &lt;code&gt;&#x2F;mnt&#x2F;eye&#x2F;cameras&#x2F;&amp;lt;id&amp;gt;&#x2F;chunks&#x2F;&lt;&#x2F;code&gt;, each camera has its own tail-able &lt;code&gt;current&lt;&#x2F;code&gt; file at &lt;code&gt;&#x2F;mnt&#x2F;eye&#x2F;cameras&#x2F;&amp;lt;id&amp;gt;&#x2F;current&lt;&#x2F;code&gt; exposing the chunk-in-progress for that feed, and runtime control lives in sysfs. Crash recovery rescans the device for self-describing chunk markers.&lt;&#x2F;p&gt;
&lt;p&gt;This document covers the on-disk layout, the multi-feed architecture, the VFS and sysfs interfaces, the write and read paths, crash consistency, and the userspace&#x2F;kernel split.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;2-motivation-compressed&quot;&gt;2. Motivation (compressed)&lt;a class=&quot;post-anchor&quot; href=&quot;#2-motivation-compressed&quot; aria-label=&quot;Anchor link for: 2-motivation-compressed&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;A surveillance workload is a single-writer, append-only, delete-oldest-first log of self-describing H.265 units. A conventional stack (filesystem + container + file rotation) solves problems the workload doesn&#x27;t have, at a cost of 5–15% media waste, fragmentation, and bounded-space accounting headaches. The cleanest representation is a raw block device operated as a circular log of keyframe-aligned chunks.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;3-why-in-kernel&quot;&gt;3. Why in-kernel&lt;a class=&quot;post-anchor&quot; href=&quot;#3-why-in-kernel&quot; aria-label=&quot;Anchor link for: 3-why-in-kernel&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;The userspace design delivers the log but pushes every reader through an RPC. The kernel design delivers the log &lt;em&gt;and&lt;&#x2F;em&gt; exposes it as a first-class filesystem, which unlocks properties a daemon cannot provide:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Page cache and zero-copy reads.&lt;&#x2F;strong&gt; Playback is &lt;code&gt;sendfile(2)&lt;&#x2F;code&gt; &#x2F; &lt;code&gt;splice(2)&lt;&#x2F;code&gt; from a chunk fd directly to a socket. Hot ranges stay cached across readers. Userspace with &lt;code&gt;O_DIRECT&lt;&#x2F;code&gt; actively avoids all of this.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;mmap&lt;&#x2F;code&gt; on chunks.&lt;&#x2F;strong&gt; Decoders can map a chunk and decode in place; no read buffer, no copy.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Tail-able &lt;code&gt;current&lt;&#x2F;code&gt; file.&lt;&#x2F;strong&gt; The chunk being written is a growing file. &lt;code&gt;ffmpeg -i &#x2F;mnt&#x2F;eye&#x2F;cameras&#x2F;front&#x2F;current&lt;&#x2F;code&gt; follows live. &lt;code&gt;poll(2)&lt;&#x2F;code&gt; wakes on new data. &lt;code&gt;inotify&lt;&#x2F;code&gt; fires on new chunks.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Standard tooling.&lt;&#x2F;strong&gt; &lt;code&gt;ls&lt;&#x2F;code&gt;, &lt;code&gt;find&lt;&#x2F;code&gt;, &lt;code&gt;du&lt;&#x2F;code&gt;, &lt;code&gt;rsync&lt;&#x2F;code&gt;, &lt;code&gt;tar&lt;&#x2F;code&gt;, &lt;code&gt;restic&lt;&#x2F;code&gt;, &lt;code&gt;scp&lt;&#x2F;code&gt;, &lt;code&gt;ffmpeg&lt;&#x2F;code&gt;, &lt;code&gt;mpv&lt;&#x2F;code&gt;, NFS (&lt;code&gt;exportfs&lt;&#x2F;code&gt;), Samba. No export subcommand; no replication daemon.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Kernel I&#x2F;O integration.&lt;&#x2F;strong&gt; Writeback, merging, I&#x2F;O scheduler interaction, &lt;code&gt;blktrace&lt;&#x2F;code&gt;, &lt;code&gt;bpftrace&lt;&#x2F;code&gt;, &lt;code&gt;iostat&lt;&#x2F;code&gt;, &lt;code&gt;perf&lt;&#x2F;code&gt; all work.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;ZNS mapping.&lt;&#x2F;strong&gt; Zoned Namespace SSDs require sequential-per-zone writes and whole-zone resets. A circular log of append-only chunks is the canonical ZNS workload. In-kernel, &lt;code&gt;blk-zoned&lt;&#x2F;code&gt; and &lt;code&gt;zonefs&lt;&#x2F;code&gt; are right there.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Access control.&lt;&#x2F;strong&gt; Unix perms and mount namespaces. No custom auth layer.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;What the kernel does &lt;em&gt;not&lt;&#x2F;em&gt; do: RTSP. That stays userspace (see §9).&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;4-reference-implementation-zonefs&quot;&gt;4. Reference implementation: zonefs&lt;a class=&quot;post-anchor&quot; href=&quot;#4-reference-implementation-zonefs&quot; aria-label=&quot;Anchor link for: 4-reference-implementation-zonefs&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;eyeFS is structurally zonefs-shaped — one file per logical unit (chunk), append-only, VFS-exposed, no hierarchical allocation — but adds H.265-specific semantics (sidecar SEI chunk markers, per-camera namespacing) and circular overwrite (zonefs&#x27;s zones don&#x27;t auto-evict). The zonefs source at &lt;code&gt;fs&#x2F;zonefs&#x2F;&lt;&#x2F;code&gt; is ~2,000 lines and is the right model to read before writing eyeFS. Expect eyeFS to be 2–3× that, most of the delta being the camera registry, the chunk assembler, and the rescan&#x2F;recovery path.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;5-on-disk-layout&quot;&gt;5. On-disk layout&lt;a class=&quot;post-anchor&quot; href=&quot;#5-on-disk-layout&quot; aria-label=&quot;Anchor link for: 5-on-disk-layout&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;The block device is laid out as:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  offset 0             offset 4 KiB        offset 36 KiB              offset D&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ┌──────────────────┬──────────────────┬────────────────────────────────────┐&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  │ Superblock       │ Camera registry  │ Ring region                        │&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  │ 4 KiB, immutable │ 32 KiB (128×256B)│ chunks packed back-to-back, wraps  │&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  │ after format     │ updated on       │ to ring_start on overflow          │&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  │                  │ add&#x2F;remove       │                                    │&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  └──────────────────┴──────────────────┴────────────────────────────────────┘&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;5-1-superblock&quot;&gt;5.1 Superblock&lt;a class=&quot;post-anchor&quot; href=&quot;#5-1-superblock&quot; aria-label=&quot;Anchor link for: 5-1-superblock&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;4 KiB at offset 0, written once at &lt;code&gt;mkfs.eyeFS&lt;&#x2F;code&gt; time. Fields are a superset of the userspace design:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Field&lt;&#x2F;th&gt;&lt;th&gt;Size&lt;&#x2F;th&gt;&lt;th&gt;Notes&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;magic&lt;&#x2F;td&gt;&lt;td&gt;8 B&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;&quot;EYEFS\0\0\0&quot;&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;version&lt;&#x2F;td&gt;&lt;td&gt;4 B&lt;&#x2F;td&gt;&lt;td&gt;on-disk format revision&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;device_uuid&lt;&#x2F;td&gt;&lt;td&gt;16 B&lt;&#x2F;td&gt;&lt;td&gt;identifies this volume&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;logical_sector_sz&lt;&#x2F;td&gt;&lt;td&gt;4 B&lt;&#x2F;td&gt;&lt;td&gt;512 or 4096&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;physical_sector_sz&lt;&#x2F;td&gt;&lt;td&gt;4 B&lt;&#x2F;td&gt;&lt;td&gt;alignment hint&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;registry_start&lt;&#x2F;td&gt;&lt;td&gt;8 B&lt;&#x2F;td&gt;&lt;td&gt;typically 4096&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;registry_size&lt;&#x2F;td&gt;&lt;td&gt;8 B&lt;&#x2F;td&gt;&lt;td&gt;typically 32768&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;ring_start&lt;&#x2F;td&gt;&lt;td&gt;8 B&lt;&#x2F;td&gt;&lt;td&gt;typically 36864&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;ring_end&lt;&#x2F;td&gt;&lt;td&gt;8 B&lt;&#x2F;td&gt;&lt;td&gt;device capacity&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;nominal_chunk_sz&lt;&#x2F;td&gt;&lt;td&gt;8 B&lt;&#x2F;td&gt;&lt;td&gt;default target, overridable per-mount&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;min_chunk_sz&lt;&#x2F;td&gt;&lt;td&gt;8 B&lt;&#x2F;td&gt;&lt;td&gt;below this, wrap discards tail&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;max_cameras&lt;&#x2F;td&gt;&lt;td&gt;4 B&lt;&#x2F;td&gt;&lt;td&gt;registry slot count&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;created_at&lt;&#x2F;td&gt;&lt;td&gt;8 B&lt;&#x2F;td&gt;&lt;td&gt;unix ns&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;last_write_head&lt;&#x2F;td&gt;&lt;td&gt;8 B&lt;&#x2F;td&gt;&lt;td&gt;updated at each checkpoint&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;last_global_seq&lt;&#x2F;td&gt;&lt;td&gt;8 B&lt;&#x2F;td&gt;&lt;td&gt;checkpoint of sequence allocator&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;crc32c&lt;&#x2F;td&gt;&lt;td&gt;4 B&lt;&#x2F;td&gt;&lt;td&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;reserved&lt;&#x2F;td&gt;&lt;td&gt;pad to 4 KiB&lt;&#x2F;td&gt;&lt;td&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;The superblock is not in the writable ring. &lt;code&gt;last_write_head&lt;&#x2F;code&gt; and &lt;code&gt;last_global_seq&lt;&#x2F;code&gt; are updated at checkpoints; see §12 for how this bounds recovery scan time.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;5-2-camera-registry&quot;&gt;5.2 Camera registry&lt;a class=&quot;post-anchor&quot; href=&quot;#5-2-camera-registry&quot; aria-label=&quot;Anchor link for: 5-2-camera-registry&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;A fixed-size table of 128 slots × 256 bytes = 32 KiB directly after the superblock. Each slot:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Field&lt;&#x2F;th&gt;&lt;th&gt;Size&lt;&#x2F;th&gt;&lt;th&gt;Notes&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;slot_flags&lt;&#x2F;td&gt;&lt;td&gt;4 B&lt;&#x2F;td&gt;&lt;td&gt;`FREE&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;camera_id&lt;&#x2F;td&gt;&lt;td&gt;2 B&lt;&#x2F;td&gt;&lt;td&gt;stable, assigned once, never reused in-lifetime&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;camera_uuid&lt;&#x2F;td&gt;&lt;td&gt;16 B&lt;&#x2F;td&gt;&lt;td&gt;for cross-device identification&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;display_name&lt;&#x2F;td&gt;&lt;td&gt;64 B&lt;&#x2F;td&gt;&lt;td&gt;UTF-8, null-padded&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;added_at&lt;&#x2F;td&gt;&lt;td&gt;8 B&lt;&#x2F;td&gt;&lt;td&gt;wall-clock ns&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;removed_at&lt;&#x2F;td&gt;&lt;td&gt;8 B&lt;&#x2F;td&gt;&lt;td&gt;0 = still active&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;codec&lt;&#x2F;td&gt;&lt;td&gt;4 B&lt;&#x2F;td&gt;&lt;td&gt;`H265_MAIN&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;default_pts_rate&lt;&#x2F;td&gt;&lt;td&gt;4 B&lt;&#x2F;td&gt;&lt;td&gt;Hz (e.g. 90000)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;vps_len&lt;&#x2F;td&gt;&lt;td&gt;2 B&lt;&#x2F;td&gt;&lt;td&gt;stored length of VPS for reference&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;sps_len&lt;&#x2F;td&gt;&lt;td&gt;2 B&lt;&#x2F;td&gt;&lt;td&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;pps_len&lt;&#x2F;td&gt;&lt;td&gt;2 B&lt;&#x2F;td&gt;&lt;td&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;vps&#x2F;sps&#x2F;pps&lt;&#x2F;td&gt;&lt;td&gt;112 B&lt;&#x2F;td&gt;&lt;td&gt;inlined; actual chunks carry their own&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;crc32c&lt;&#x2F;td&gt;&lt;td&gt;4 B&lt;&#x2F;td&gt;&lt;td&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;reserved&lt;&#x2F;td&gt;&lt;td&gt;pad to 256 B&lt;&#x2F;td&gt;&lt;td&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;The registry is updated in place by slot: add allocates a free slot, remove flips &lt;code&gt;ACTIVE → REMOVED&lt;&#x2F;code&gt; and sets &lt;code&gt;removed_at&lt;&#x2F;code&gt;. A removed camera&#x27;s chunks remain readable until wrapped over; its slot is reclaimable for new cameras only after all its chunks have aged out of the ring.&lt;&#x2F;p&gt;
&lt;p&gt;Registry writes use the same ordering rule as chunk writes: write + fdatasync, then in-memory update, then WAL record on the NVMe-side index.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;5-3-ring-region&quot;&gt;5.3 Ring region&lt;a class=&quot;post-anchor&quot; href=&quot;#5-3-ring-region&quot; aria-label=&quot;Anchor link for: 5-3-ring-region&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;Each chunk is a valid Annex B bitstream beginning with a sidecar SEI NAL unit (H.265 &lt;code&gt;user_data_unregistered&lt;&#x2F;code&gt;, NAL unit type 39), followed by VPS, SPS, PPS, IDR, and the rest of the GOP. Decoders ignore the SEI; eyeFS uses it as a structured, scannable anchor.&lt;&#x2F;p&gt;
&lt;p&gt;Sidecar SEI payload for eyeFS (extended from the userspace design):&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  sidecar payload:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    magic_uuid      : 16 B  (identifies the eyeFS chunk format)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    global_seq      : u64   (monotonic across all cameras on this device)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    camera_id       : u16   (registry slot ID)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    chunk_length    : u64   (total bytes including this SEI)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    pts_start       : i64   (stream&amp;#39;s timescale, per camera)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    pts_end         : i64&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    wall_clock_ns   : i64   (global log time)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    prev_chunk_off  : u64   (backpointer in global order)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    flags           : u32&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    crc32c          : u32&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Per-camera sequence numbers are &lt;em&gt;not&lt;&#x2F;em&gt; stored on disk; they are derived from &lt;code&gt;(camera_id, global_seq)&lt;&#x2F;code&gt; at rebuild time by counting per-camera chunks in global-seq order. This keeps the on-device record fixed-size and avoids coordinating two counters during the flush critical section.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;6-multi-feed-architecture&quot;&gt;6. Multi-feed architecture&lt;a class=&quot;post-anchor&quot; href=&quot;#6-multi-feed-architecture&quot; aria-label=&quot;Anchor link for: 6-multi-feed-architecture&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;The core decision: &lt;strong&gt;one ring, one write head, chunks tagged by camera_id.&lt;&#x2F;strong&gt; Zipper-merge across feeds. Alternatives considered and rejected:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Partition table, one ring per camera.&lt;&#x2F;strong&gt; Not dynamic; adding a camera requires resizing existing partitions.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Multiple independent rings on one device, sized by bitrate share.&lt;&#x2F;strong&gt; Dynamic-capable, but rebalancing is complex and per-camera eviction makes cross-camera time queries awkward.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Per-camera virtual rings over shared physical space.&lt;&#x2F;strong&gt; Same semantics as the chosen design at significantly higher bookkeeping cost.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The chosen design is strictly simpler and gives uniform retention across cameras, which is what operators actually want (legal holds, compliance windows, &quot;the last 30 days of everything&quot;).&lt;&#x2F;p&gt;
&lt;h3 id=&quot;6-1-zipper-merge-mechanics&quot;&gt;6.1 Zipper-merge mechanics&lt;a class=&quot;post-anchor&quot; href=&quot;#6-1-zipper-merge-mechanics&quot; aria-label=&quot;Anchor link for: 6-1-zipper-merge-mechanics&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;Per camera, eyeFS maintains an in-kernel NAL accumulator: a buffer of access units since the last flush, plus a running size and the offset of the last IDR within the buffer. When the userspace ingestor feeds an access unit (§9) the kernel appends it.&lt;&#x2F;p&gt;
&lt;p&gt;A flush is triggered when, for some camera:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;The accumulator size ≥ &lt;code&gt;nominal_chunk_sz&lt;&#x2F;code&gt;, &lt;strong&gt;and&lt;&#x2F;strong&gt;&lt;&#x2F;li&gt;
&lt;li&gt;The next incoming access unit is an IDR.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;At that point the accumulator is cut immediately before the incoming IDR (the IDR becomes the first frame of the next chunk). The cut chunk is handed to the flush path.&lt;&#x2F;p&gt;
&lt;p&gt;The flush path takes the global &lt;strong&gt;write-head spinlock&lt;&#x2F;strong&gt;, stamps &lt;code&gt;global_seq&lt;&#x2F;code&gt; and &lt;code&gt;wall_clock_ns&lt;&#x2F;code&gt;, prepends the sidecar SEI, writes the chunk at the current write head, advances the head, releases the lock. Chunk writes are seconds apart per camera; at 32 cameras and 128 MiB chunks, the write rate is ~one chunk per few hundred milliseconds. Spinlock contention is not a factor.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;6-2-dynamic-add&quot;&gt;6.2 Dynamic add&lt;a class=&quot;post-anchor&quot; href=&quot;#6-2-dynamic-add&quot; aria-label=&quot;Anchor link for: 6-2-dynamic-add&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;code&gt;echo &quot;rtsp:&#x2F;&#x2F;user:pass@cam4.lan&#x2F;main front_entrance&quot; &amp;gt; &#x2F;sys&#x2F;fs&#x2F;eyeFS&#x2F;&amp;lt;uuid&amp;gt;&#x2F;cameras&#x2F;add&lt;&#x2F;code&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The kernel:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Allocates a free registry slot, assigns &lt;code&gt;camera_id&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;Writes the registry slot to disk, fdatasyncs.&lt;&#x2F;li&gt;
&lt;li&gt;Creates the VFS subtree &lt;code&gt;&#x2F;mnt&#x2F;eye&#x2F;cameras&#x2F;&amp;lt;camera_id&amp;gt;&#x2F;&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;Opens a per-camera write-side character device (or the &lt;code&gt;current&lt;&#x2F;code&gt; file becomes writable; see §9).&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;The userspace ingestor daemon (a separate systemd service, watching for registry changes via inotify on &lt;code&gt;cameras&#x2F;&lt;&#x2F;code&gt;) picks up the new camera, opens the RTSP URL, and starts feeding access units. The first access unit that is an IDR starts the first chunk; earlier units arrive before the first IDR are discarded.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;6-3-dynamic-remove&quot;&gt;6.3 Dynamic remove&lt;a class=&quot;post-anchor&quot; href=&quot;#6-3-dynamic-remove&quot; aria-label=&quot;Anchor link for: 6-3-dynamic-remove&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;code&gt;echo &amp;lt;camera_id&amp;gt; &amp;gt; &#x2F;sys&#x2F;fs&#x2F;eyeFS&#x2F;&amp;lt;uuid&amp;gt;&#x2F;cameras&#x2F;remove&lt;&#x2F;code&gt;&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Mark slot &lt;code&gt;REMOVED&lt;&#x2F;code&gt;, set &lt;code&gt;removed_at&lt;&#x2F;code&gt;, persist.&lt;&#x2F;li&gt;
&lt;li&gt;Close the per-camera write side; further access units are rejected.&lt;&#x2F;li&gt;
&lt;li&gt;Existing chunks remain visible under &lt;code&gt;&#x2F;mnt&#x2F;eye&#x2F;cameras&#x2F;&amp;lt;camera_id&amp;gt;&#x2F;&lt;&#x2F;code&gt; until overwritten.&lt;&#x2F;li&gt;
&lt;li&gt;When the last surviving chunk for this &lt;code&gt;camera_id&lt;&#x2F;code&gt; is evicted, the slot is reclaimable.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h3 id=&quot;6-4-offline-reconnecting-cameras&quot;&gt;6.4 Offline &#x2F; reconnecting cameras&lt;a class=&quot;post-anchor&quot; href=&quot;#6-4-offline-reconnecting-cameras&quot; aria-label=&quot;Anchor link for: 6-4-offline-reconnecting-cameras&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;A camera whose RTSP connection drops simply stops feeding the kernel. No placeholder writes, no padding, no sentinel chunks. The ring sees fewer chunks from that camera during the outage; other cameras continue normally. When the camera reconnects, the userspace ingestor resumes feeding, and the next IDR starts a new chunk.&lt;&#x2F;p&gt;
&lt;p&gt;The retention window for the affected camera shrinks proportionally during the outage — if camera A was offline for 10% of a retention cycle, the last surviving chunk for A will be from 10% further back in time than the other cameras&#x27; last surviving chunks. This is correct behavior, not a bug: the ring preserves as much recent footage as possible.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;6-5-log-time-vs-frame-pts&quot;&gt;6.5 Log-time vs frame PTS&lt;a class=&quot;post-anchor&quot; href=&quot;#6-5-log-time-vs-frame-pts&quot; aria-label=&quot;Anchor link for: 6-5-log-time-vs-frame-pts&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;Two timelines coexist and must not be conflated:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Log time&lt;&#x2F;strong&gt; (&lt;code&gt;wall_clock_ns&lt;&#x2F;code&gt; in the sidecar SEI): a global, monotonic, NTP-ish timestamp assigned by the kernel at flush time. This is the axis for retention, eviction, and cross-camera time-range queries. All cameras on a device share this axis.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Frame PTS&lt;&#x2F;strong&gt; (&lt;code&gt;pts_start&lt;&#x2F;code&gt;, &lt;code&gt;pts_end&lt;&#x2F;code&gt; in the sidecar SEI): per-camera, in that camera&#x27;s own timescale (typically 90 kHz). Preserved verbatim from the stream. This is the axis decoders see.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Time-range playback queries express T1..T2 in log time. The kernel resolves them against the global index and returns per-camera byte ranges. The decoder reconstructs frame timing from the in-stream PTS.&lt;&#x2F;p&gt;
&lt;p&gt;This separation is what the user called &quot;synchronous timestamps.&quot; It is not that the cameras are frame-synchronized (they aren&#x27;t, and can&#x27;t be in general). It is that their &lt;em&gt;log time&lt;&#x2F;em&gt; is the same single axis — which is all that eviction and queries need.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;6-6-bandwidth-sharing-no-fairness&quot;&gt;6.6 Bandwidth sharing: no fairness&lt;a class=&quot;post-anchor&quot; href=&quot;#6-6-bandwidth-sharing-no-fairness&quot; aria-label=&quot;Anchor link for: 6-6-bandwidth-sharing-no-fairness&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;eyeFS does not enforce per-camera quotas. A camera with double the bitrate consumes double the ring space and shortens everyone&#x27;s retention together. This is deliberate: the ring is a shared resource with a single retention window, and any per-camera quota scheme would either waste ring space (reserving quota that isn&#x27;t used) or require per-camera rings (rejected in §6 intro).&lt;&#x2F;p&gt;
&lt;p&gt;If quotas are ever needed, the correct mechanism is upstream: rate-limit the chatty camera at the ingestor, or give it its own device.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;7-vfs-namespace&quot;&gt;7. VFS namespace&lt;a class=&quot;post-anchor&quot; href=&quot;#7-vfs-namespace&quot; aria-label=&quot;Anchor link for: 7-vfs-namespace&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&#x2F;mnt&#x2F;eye&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;├── .super                                (ro; superblock view, text)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;├── all&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   └── by-seq&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│       └── 00000000000001847.h265        (global cross-camera, by global_seq)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;└── cameras&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ├── 001&#x2F;                              (camera_id)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    │   ├── info                          (ro; registry slot as text)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    │   ├── chunks&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    │   │   ├── 00000000000001847.h265    (named by global_seq)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    │   │   └── 00000000000001851.h265&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    │   ├── by-time&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    │   │   └── 2026-04-23T14:30:00.000Z  (lookup-resolved symlink)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    │   └── current                       (tail-able; grows as kernel writes)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ├── 002&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    │   └── …&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    └── …&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Properties:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Chunk files are read-only, &lt;code&gt;mmap&lt;&#x2F;code&gt;-able, and &lt;code&gt;sendfile&lt;&#x2F;code&gt;&#x2F;&lt;code&gt;splice&lt;&#x2F;code&gt;-eligible.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;by-time&#x2F;&lt;&#x2F;code&gt; is a synthetic directory: &lt;code&gt;readdir&lt;&#x2F;code&gt; enumerates known chunk wall-clock prefixes, &lt;code&gt;lookup(ts)&lt;&#x2F;code&gt; returns the nearest chunk. Uses the kernel&#x27;s dcache for negative caching.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;current&lt;&#x2F;code&gt; is the only writable file and only the kernel-owned write side can append to it. Reads return the bytes written so far. &lt;code&gt;poll(POLLIN)&lt;&#x2F;code&gt; wakes on new data. &lt;code&gt;fstat.st_size&lt;&#x2F;code&gt; grows monotonically until the next flush, then resets (the new &lt;code&gt;current&lt;&#x2F;code&gt; is the next in-progress chunk).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;inotify(IN_CREATE)&lt;&#x2F;code&gt; on &lt;code&gt;chunks&#x2F;&lt;&#x2F;code&gt; fires when a chunk is flushed.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;all&#x2F;by-seq&#x2F;&lt;&#x2F;code&gt; is a flat merged view, useful for archival scripts that don&#x27;t care which camera.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Symlink targets across the synthetic trees are resolved through the same in-memory index; there is no persistent symlink data on disk.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;8-sysfs-interface&quot;&gt;8. Sysfs interface&lt;a class=&quot;post-anchor&quot; href=&quot;#8-sysfs-interface&quot; aria-label=&quot;Anchor link for: 8-sysfs-interface&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;8-1-per-device-tree&quot;&gt;8.1 Per-device tree&lt;a class=&quot;post-anchor&quot; href=&quot;#8-1-per-device-tree&quot; aria-label=&quot;Anchor link for: 8-1-per-device-tree&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&#x2F;sys&#x2F;fs&#x2F;eyeFS&#x2F;&amp;lt;device_uuid&amp;gt;&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;├── device                           → ..&#x2F;..&#x2F;block&#x2F;sdX       (symlink)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;├── info&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   ├── format_version&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   ├── capacity_bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   ├── ring_start&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   ├── created_at&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   └── logical_sector_size&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;├── state&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   ├── write_head&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   ├── wrap_count&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   ├── oldest_global_seq&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   ├── newest_global_seq&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   ├── oldest_wall_clock&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   ├── newest_wall_clock&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   ├── chunks_live&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   └── bytes_live&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;├── config&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   ├── nominal_chunk_size           (rw)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   ├── min_chunk_size               (rw)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   ├── fsync_mode                   (rw; per_chunk | batched:N)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   ├── align_to_iu                  (rw)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   └── paused                       (rw)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;├── stats&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   ├── chunks_written&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   ├── chunks_evicted&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   ├── bytes_written&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   ├── wrap_tail_wasted&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   ├── fsync_latency_ns_p50&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   ├── fsync_latency_ns_p99&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   ├── scanner_runs&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   └── torn_chunks_recovered&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;├── actions&#x2F;                         (write-only triggers)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   ├── rebuild_index&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   ├── checkpoint_now&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   ├── force_cut                    (cut all cameras&amp;#39; current chunks immediately)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   └── cameras&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│       ├── add                      (write: &amp;quot;&amp;lt;rtsp_url&amp;gt; &amp;lt;display_name&amp;gt;&amp;quot;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│       └── remove                   (write: &amp;lt;camera_id&amp;gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;└── cameras&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ├── 001&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    │   ├── info&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    │   │   ├── uuid&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    │   │   ├── display_name&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    │   │   ├── added_at&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    │   │   ├── codec&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    │   │   └── rtsp_url             (stored for the ingestor; not used by FS)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    │   ├── state&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    │   │   ├── chunks_live&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    │   │   ├── bytes_live&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    │   │   ├── oldest_wall_clock&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    │   │   ├── newest_wall_clock&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    │   │   ├── last_keyframe_ns&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    │   │   └── online               (0 = ingestor hasn&amp;#39;t written recently)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    │   └── stats&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    │       ├── chunks_written&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    │       ├── bytes_written&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    │       ├── access_units_received&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    │       └── gaps_ns              (total time with no data)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    └── 002&#x2F;…&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;8-2-debugfs-dev-only-not-abi&quot;&gt;8.2 Debugfs (dev-only, not ABI)&lt;a class=&quot;post-anchor&quot; href=&quot;#8-2-debugfs-dev-only-not-abi&quot; aria-label=&quot;Anchor link for: 8-2-debugfs-dev-only-not-abi&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&#x2F;sys&#x2F;kernel&#x2F;debug&#x2F;eyeFS&#x2F;&amp;lt;uuid&amp;gt;&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;├── chunks           (one line per live chunk: global_seq cam_id off len pts wc)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;├── wal_tail         (pending index WAL records)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;├── scan_trace       (last rebuild scan, per-chunk timings)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;└── slowlog          (flushes &amp;gt; threshold)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Debugfs is gated by &lt;code&gt;CONFIG_EYEFS_DEBUG&lt;&#x2F;code&gt; and is not part of the stable interface.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;9-userspace-ingestor-split&quot;&gt;9. Userspace ingestor split&lt;a class=&quot;post-anchor&quot; href=&quot;#9-userspace-ingestor-split&quot; aria-label=&quot;Anchor link for: 9-userspace-ingestor-split&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;RTSP is a TLS-capable, auth-bearing, vendor-quirky text protocol with reconnect logic that varies per camera firmware. It does not belong in the kernel. eyeFS splits the pipeline cleanly:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Userspace&lt;&#x2F;strong&gt; (&lt;code&gt;electricEye-ingestor&lt;&#x2F;code&gt;, one systemd service per running daemon): maintains RTSP sessions, depacketizes RTP, assembles access units, hands them to the kernel.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Kernel&lt;&#x2F;strong&gt; (eyeFS): per-camera accumulator, chunk framing, circular write, VFS&#x2F;sysfs surfaces, crash recovery.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The interface between them is the per-camera write side. Two viable shapes:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Shape A: write to &lt;code&gt;cameras&#x2F;&amp;lt;id&amp;gt;&#x2F;current&lt;&#x2F;code&gt;.&lt;&#x2F;strong&gt; The ingestor opens the file O_WRONLY and &lt;code&gt;write(2)&lt;&#x2F;code&gt;s framed access units. Framing is length-prefixed Annex B access units plus a small header carrying PTS and NAL flags. Simple, but conflates data and metadata on one fd.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Shape B: per-camera character device&lt;&#x2F;strong&gt; (&lt;code&gt;&#x2F;dev&#x2F;eyeFS-&amp;lt;uuid&amp;gt;-&amp;lt;camera_id&amp;gt;&lt;&#x2F;code&gt;) with a structured protocol: an ioctl registers codec params, then &lt;code&gt;write(2)&lt;&#x2F;code&gt; calls carry &lt;code&gt;(pts, flags, annex_b_bytes)&lt;&#x2F;code&gt; records. Cleaner separation; mirrors how other media subsystems handle this (v4l2).&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Recommended: &lt;strong&gt;Shape B.&lt;&#x2F;strong&gt; The write-side is a private interface between the ingestor and the kernel; it does not need to be POSIX-file-shaped. Shape A would force the &lt;code&gt;current&lt;&#x2F;code&gt; file to simultaneously serve &quot;readable live stream&quot; and &quot;writable ingestion target,&quot; which couples unrelated concerns. With Shape B, &lt;code&gt;current&lt;&#x2F;code&gt; is read-only to everyone including the ingestor.&lt;&#x2F;p&gt;
&lt;p&gt;The ingestor&#x27;s total job: open RTSP, depacketize, call &lt;code&gt;write&lt;&#x2F;code&gt; per access unit. No storage state, no retention logic, no index. It is a small Go program, drawn largely from the existing &lt;code&gt;electricEye&lt;&#x2F;code&gt; codebase (&lt;code&gt;src&#x2F;ingest.go&lt;&#x2F;code&gt;), with the astiav&#x2F;MKV muxer path removed.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;10-write-path&quot;&gt;10. Write path&lt;a class=&quot;post-anchor&quot; href=&quot;#10-write-path&quot; aria-label=&quot;Anchor link for: 10-write-path&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Per-camera accumulation state (in kernel):&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;struct eyefs_camera_writer {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    u16             camera_id;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    struct list_head nal_buf;       &#x2F;&#x2F; accumulated access units&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    u64             buf_bytes;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    u64             last_idr_off;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    i64             pts_first, pts_last;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    spinlock_t      lock;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;};&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Per-device shared state:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;struct eyefs_device {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    struct block_device *bdev;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    spinlock_t      head_lock;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    u64             write_head;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    u64             global_seq;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    struct eyefs_camera_writer *cams[MAX_CAMERAS];&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    …&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;};&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;10-1-per-access-unit&quot;&gt;10.1 Per access unit&lt;a class=&quot;post-anchor&quot; href=&quot;#10-1-per-access-unit&quot; aria-label=&quot;Anchor link for: 10-1-per-access-unit&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;ol&gt;
&lt;li&gt;Ingestor &lt;code&gt;write(2)&lt;&#x2F;code&gt;s &lt;code&gt;(pts, flags, bytes)&lt;&#x2F;code&gt; on the per-camera chardev.&lt;&#x2F;li&gt;
&lt;li&gt;eyeFS takes &lt;code&gt;cam-&amp;gt;lock&lt;&#x2F;code&gt;, appends NAL bytes, updates &lt;code&gt;buf_bytes&lt;&#x2F;code&gt;, updates &lt;code&gt;pts_last&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;If the access unit is an IDR and &lt;code&gt;buf_bytes ≥ nominal_chunk_sz&lt;&#x2F;code&gt;: trigger flush (§10.2) using the buffer up to but not including this IDR, then append this IDR as the start of the next buffer.&lt;&#x2F;li&gt;
&lt;li&gt;Release &lt;code&gt;cam-&amp;gt;lock&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Keyframe detection is a two-byte check on the NAL header (nal_unit_type ∈ {19, 20} for IDR). Same as the userspace design.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;10-2-flush&quot;&gt;10.2 Flush&lt;a class=&quot;post-anchor&quot; href=&quot;#10-2-flush&quot; aria-label=&quot;Anchor link for: 10-2-flush&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;ol&gt;
&lt;li&gt;Build sidecar SEI (&lt;code&gt;global_seq&lt;&#x2F;code&gt; is stamped inside the critical section below).&lt;&#x2F;li&gt;
&lt;li&gt;Take &lt;code&gt;device-&amp;gt;head_lock&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;Assign &lt;code&gt;global_seq = device-&amp;gt;global_seq++&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;Compute write offset; handle wrap if chunk wouldn&#x27;t fit before &lt;code&gt;ring_end&lt;&#x2F;code&gt; (§10.3).&lt;&#x2F;li&gt;
&lt;li&gt;Compute the eviction set (all live chunks whose byte ranges intersect the new chunk&#x27;s range, wrap-aware).&lt;&#x2F;li&gt;
&lt;li&gt;Issue &lt;code&gt;submit_bio&lt;&#x2F;code&gt; for the chunk data at the write head.&lt;&#x2F;li&gt;
&lt;li&gt;Release &lt;code&gt;device-&amp;gt;head_lock&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;On I&#x2F;O completion (callback): &lt;code&gt;fdatasync&lt;&#x2F;code&gt;-equivalent barrier, then append index WAL records (EVICT ×N, ADD ×1) on the NVMe-side index file, fsync WAL, update in-memory index, create the new VFS dentry under &lt;code&gt;chunks&#x2F;&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;The &lt;code&gt;head_lock&lt;&#x2F;code&gt; is held only long enough to stamp seq, compute offset+eviction set, and submit the bio. It is not held across I&#x2F;O completion.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;10-3-wrap&quot;&gt;10.3 Wrap&lt;a class=&quot;post-anchor&quot; href=&quot;#10-3-wrap&quot; aria-label=&quot;Anchor link for: 10-3-wrap&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;If &lt;code&gt;write_head + chunk_len &amp;gt; ring_end&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;If &lt;code&gt;ring_end - write_head ≥ min_chunk_sz&lt;&#x2F;code&gt;: cut the flush candidate at its last-fitting IDR, write the shorter terminal chunk at &lt;code&gt;write_head&lt;&#x2F;code&gt;, spill the remainder into the next chunk which starts at &lt;code&gt;ring_start&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;If &lt;code&gt;ring_end - write_head &amp;lt; min_chunk_sz&lt;&#x2F;code&gt;: abandon those bytes, wrap &lt;code&gt;write_head&lt;&#x2F;code&gt; to &lt;code&gt;ring_start&lt;&#x2F;code&gt;, write the chunk there.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The only wasted space on the device is at most one &lt;code&gt;min_chunk_sz&lt;&#x2F;code&gt; per wrap cycle (default 16 MiB ≈ 1 ppm on a 10 TB device).&lt;&#x2F;p&gt;
&lt;h3 id=&quot;10-4-ordering&quot;&gt;10.4 Ordering&lt;a class=&quot;post-anchor&quot; href=&quot;#10-4-ordering&quot; aria-label=&quot;Anchor link for: 10-4-ordering&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  bio complete → device barrier → WAL append → WAL fsync → in-memory index update&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Inverting any pair creates a window where the index advertises data that isn&#x27;t durable, or the index stays stale for a durable chunk. The former is silent corruption; the latter is recoverable by rescan (§12).&lt;&#x2F;p&gt;
&lt;h3 id=&quot;10-5-fsync-mode&quot;&gt;10.5 fsync mode&lt;a class=&quot;post-anchor&quot; href=&quot;#10-5-fsync-mode&quot; aria-label=&quot;Anchor link for: 10-5-fsync-mode&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;per_chunk&lt;&#x2F;code&gt; (default): one barrier per flush. Cheap on NVMe, acceptable on HDD (~tens of ms amortized over minutes of footage).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;batched:N&lt;&#x2F;code&gt;: barrier every N chunks. Widens the durability window but is safe: the rescan path finds and recovers any unindexed chunks on restart.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;11-read-path&quot;&gt;11. Read path&lt;a class=&quot;post-anchor&quot; href=&quot;#11-read-path&quot; aria-label=&quot;Anchor link for: 11-read-path&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;11-1-static-chunks&quot;&gt;11.1 Static chunks&lt;a class=&quot;post-anchor&quot; href=&quot;#11-1-static-chunks&quot; aria-label=&quot;Anchor link for: 11-1-static-chunks&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;code&gt;open&lt;&#x2F;code&gt; a chunk file → kernel looks up the chunk in the in-memory index → returns an inode whose data ops do bounded &lt;code&gt;pread&lt;&#x2F;code&gt; against the block device within &lt;code&gt;[offset, offset+length)&lt;&#x2F;code&gt;. Read-ahead, page cache, &lt;code&gt;mmap&lt;&#x2F;code&gt;, &lt;code&gt;sendfile&lt;&#x2F;code&gt;, &lt;code&gt;splice&lt;&#x2F;code&gt; all work via standard VFS plumbing.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;11-2-current-live&quot;&gt;11.2 &lt;code&gt;current&lt;&#x2F;code&gt; (live)&lt;a class=&quot;post-anchor&quot; href=&quot;#11-2-current-live&quot; aria-label=&quot;Anchor link for: 11-2-current-live&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;code&gt;open&lt;&#x2F;code&gt; the per-camera &lt;code&gt;current&lt;&#x2F;code&gt; → kernel returns an inode backed by the active accumulator (not yet on disk). &lt;code&gt;read&lt;&#x2F;code&gt; returns bytes up to the current accumulator length. &lt;code&gt;poll(POLLIN)&lt;&#x2F;code&gt; wakes on new access-unit append. When a flush happens, the in-progress chunk becomes a regular file in &lt;code&gt;chunks&#x2F;&lt;&#x2F;code&gt; and a new &lt;code&gt;current&lt;&#x2F;code&gt; begins; consumers receive &lt;code&gt;POLLHUP&lt;&#x2F;code&gt; (or a rename-style event) and reopen.&lt;&#x2F;p&gt;
&lt;p&gt;This is the killer feature: &lt;code&gt;ffmpeg -i &#x2F;mnt&#x2F;eye&#x2F;cameras&#x2F;001&#x2F;current -c copy out.mkv&lt;&#x2F;code&gt; is a live tap, no daemon in the middle.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;11-3-time-range-queries&quot;&gt;11.3 Time-range queries&lt;a class=&quot;post-anchor&quot; href=&quot;#11-3-time-range-queries&quot; aria-label=&quot;Anchor link for: 11-3-time-range-queries&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;code&gt;&#x2F;mnt&#x2F;eye&#x2F;cameras&#x2F;&amp;lt;id&amp;gt;&#x2F;by-time&#x2F;2026-04-23T14:30:00Z&lt;&#x2F;code&gt; → kernel looks up the nearest chunk with &lt;code&gt;wall_clock_start ≤ T&lt;&#x2F;code&gt; → returns a symlink to that chunk&#x27;s file. A time-range query is a &lt;code&gt;readdir&lt;&#x2F;code&gt; of &lt;code&gt;by-time&#x2F;&lt;&#x2F;code&gt; bounded by T1..T2 and sequential &lt;code&gt;cat&lt;&#x2F;code&gt; of the resolved chunks. Because every chunk begins with VPS&#x2F;SPS&#x2F;PPS&#x2F;IDR, concatenation produces a valid Annex B stream.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;11-4-cross-camera&quot;&gt;11.4 Cross-camera&lt;a class=&quot;post-anchor&quot; href=&quot;#11-4-cross-camera&quot; aria-label=&quot;Anchor link for: 11-4-cross-camera&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;code&gt;&#x2F;mnt&#x2F;eye&#x2F;all&#x2F;by-seq&#x2F;&lt;&#x2F;code&gt; gives a global view indexed by &lt;code&gt;global_seq&lt;&#x2F;code&gt;. Useful for archival (&quot;copy everything from global_seq 100000 to 200000 to this tarball&quot;) and for tools that don&#x27;t need per-camera separation.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;12-crash-consistency&quot;&gt;12. Crash consistency&lt;a class=&quot;post-anchor&quot; href=&quot;#12-crash-consistency&quot; aria-label=&quot;Anchor link for: 12-crash-consistency&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Three failure modes:&lt;&#x2F;p&gt;
&lt;h3 id=&quot;12-1-daemon-dies-mid-access-unit-ingestor-side&quot;&gt;12.1 Daemon dies mid-access-unit (ingestor side)&lt;a class=&quot;post-anchor&quot; href=&quot;#12-1-daemon-dies-mid-access-unit-ingestor-side&quot; aria-label=&quot;Anchor link for: 12-1-daemon-dies-mid-access-unit-ingestor-side&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;Accumulator holds a partial AU. Kernel discards it on next IDR. No on-device state changes. Safe.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;12-2-kernel-panic-with-unflushed-accumulators&quot;&gt;12.2 Kernel panic with unflushed accumulators&lt;a class=&quot;post-anchor&quot; href=&quot;#12-2-kernel-panic-with-unflushed-accumulators&quot; aria-label=&quot;Anchor link for: 12-2-kernel-panic-with-unflushed-accumulators&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;Accumulators are in RAM and lost. Last durable state is the last flushed chunk. Next boot: mount, replay index WAL, start accumulating again. Up to one &lt;code&gt;nominal_chunk_sz&lt;&#x2F;code&gt; of footage per camera lost — by construction, since that&#x27;s what the accumulator is.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;12-3-power-loss-between-bio-completion-and-wal-fsync&quot;&gt;12.3 Power loss between bio completion and WAL fsync&lt;a class=&quot;post-anchor&quot; href=&quot;#12-3-power-loss-between-bio-completion-and-wal-fsync&quot; aria-label=&quot;Anchor link for: 12-3-power-loss-between-bio-completion-and-wal-fsync&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;Device holds a chunk with a valid sidecar SEI that the index doesn&#x27;t know about. Next mount:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Read superblock&#x27;s &lt;code&gt;last_write_head&lt;&#x2F;code&gt; and &lt;code&gt;last_global_seq&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;Scan the ring forward from &lt;code&gt;last_write_head&lt;&#x2F;code&gt;. At each position, read one logical block, look for the eyeFS sidecar SEI UUID. If found, validate CRC, read &lt;code&gt;chunk_length&lt;&#x2F;code&gt;, advance by that amount, repeat. Each valid chunk found is ADDed to the WAL.&lt;&#x2F;li&gt;
&lt;li&gt;Stop when either an invalid SEI is hit (torn tail of a partial write — the prior chunk was the last durable one) or when &lt;code&gt;global_seq&lt;&#x2F;code&gt; goes backward (we&#x27;ve walked past the tail into previous-wrap residue).&lt;&#x2F;li&gt;
&lt;li&gt;Update superblock&#x27;s &lt;code&gt;last_write_head&lt;&#x2F;code&gt; and &lt;code&gt;last_global_seq&lt;&#x2F;code&gt;, resume.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Bounded by (current head − last checkpoint) which is controlled by the checkpoint cadence (default every 10,000 chunks or 5 min, whichever first). Worst case scan on unbounded skew is sequential read at device bandwidth — §12.4.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;12-4-full-rescan&quot;&gt;12.4 Full rescan&lt;a class=&quot;post-anchor&quot; href=&quot;#12-4-full-rescan&quot; aria-label=&quot;Anchor link for: 12-4-full-rescan&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;If the index is lost entirely (NVMe failure, operator wipe):&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  mount -t eyefs -o rebuild_index &#x2F;dev&#x2F;sdX &#x2F;mnt&#x2F;eye&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The kernel scans the entire ring, finds every valid sidecar SEI, and reconstructs the index from scratch. Per-camera sequence numbers are derived by counting per-camera chunks in &lt;code&gt;global_seq&lt;&#x2F;code&gt; order.&lt;&#x2F;p&gt;
&lt;p&gt;Cost: sequential read at device bandwidth. 500 MB&#x2F;s on a modern device → ~5.5 hours for 10 TB. Pure sequential I&#x2F;O, CPU-negligible.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;12-5-partial-wrap-ambiguity&quot;&gt;12.5 Partial-wrap ambiguity&lt;a class=&quot;post-anchor&quot; href=&quot;#12-5-partial-wrap-ambiguity&quot; aria-label=&quot;Anchor link for: 12-5-partial-wrap-ambiguity&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;After the ring has wrapped, the device contains current-cycle chunks followed by trailing residue from the previous cycle (the tail of an overwritten old chunk that wasn&#x27;t fully clobbered by the most recent write). The scanner distinguishes them by &lt;code&gt;global_seq&lt;&#x2F;code&gt; monotonicity: old residue has strictly smaller &lt;code&gt;global_seq&lt;&#x2F;code&gt; than adjacent current-cycle chunks. Anything preceding a sequence-number discontinuity is discarded.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;12-6-registry-consistency&quot;&gt;12.6 Registry consistency&lt;a class=&quot;post-anchor&quot; href=&quot;#12-6-registry-consistency&quot; aria-label=&quot;Anchor link for: 12-6-registry-consistency&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;The camera registry is updated out-of-band from chunk writes; a power loss mid-registry-update could leave a half-written slot. Each slot carries its own CRC; torn slots are treated as FREE on mount and logged. An active camera whose registry slot is invalidated this way will be re-added by the ingestor on next start (same RTSP URL, new &lt;code&gt;camera_id&lt;&#x2F;code&gt;), at the cost of its prior chunks becoming orphans (still present on-device, reachable via &lt;code&gt;all&#x2F;by-seq&#x2F;&lt;&#x2F;code&gt; but not under a named camera). This is tolerable; the operator recovers with a manual registry repair tool if it matters.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;13-eviction-semantics&quot;&gt;13. Eviction semantics&lt;a class=&quot;post-anchor&quot; href=&quot;#13-eviction-semantics&quot; aria-label=&quot;Anchor link for: 13-eviction-semantics&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;New chunk at &lt;code&gt;[write_head, write_head+len)&lt;&#x2F;code&gt; evicts every live chunk whose byte range intersects that interval (wrap-aware). Variable chunk sizes mean 1..N evictions per write.&lt;&#x2F;p&gt;
&lt;p&gt;Consequence in the multi-feed world: eviction is &lt;strong&gt;global FIFO by write-time&lt;&#x2F;strong&gt;, not per-camera FIFO. The oldest live chunk is the oldest by &lt;code&gt;wall_clock_ns&lt;&#x2F;code&gt;, not the oldest of any particular camera. All cameras share the retention window.&lt;&#x2F;p&gt;
&lt;p&gt;Transient dead region: if a new chunk is smaller than the oldest it overwrites, the residual bytes of the old chunk sit unreachable until the &lt;em&gt;next&lt;&#x2F;em&gt; chunk comes through. Bounded by one chunk size at any moment.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;14-mount-options&quot;&gt;14. Mount options&lt;a class=&quot;post-anchor&quot; href=&quot;#14-mount-options&quot; aria-label=&quot;Anchor link for: 14-mount-options&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;mount -t eyefs \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -o chunk_size=128M,min_chunk_size=16M,fsync_mode=per_chunk,\&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;       align_to_iu=0,max_cameras=128,index_path=&#x2F;var&#x2F;lib&#x2F;eyefs \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &#x2F;dev&#x2F;sdX &#x2F;mnt&#x2F;eye&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Option&lt;&#x2F;th&gt;&lt;th&gt;Default&lt;&#x2F;th&gt;&lt;th&gt;Notes&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;chunk_size&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;128M&lt;&#x2F;td&gt;&lt;td&gt;nominal flush target&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;min_chunk_size&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;16M&lt;&#x2F;td&gt;&lt;td&gt;wrap discard threshold&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;fsync_mode&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;per_chunk&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;per_chunk&lt;&#x2F;code&gt; or &lt;code&gt;batched:N&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;align_to_iu&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;0&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;align chunk starts to indirection unit (ZNS&#x2F;NVMe)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;max_cameras&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;128&lt;&#x2F;td&gt;&lt;td&gt;registry slot count (must match format-time)&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;index_path&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;&#x2F;var&#x2F;lib&#x2F;eyefs&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;NVMe-side index directory&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;rebuild_index&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;0&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;force full rescan at mount&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;Options marked format-time (e.g. &lt;code&gt;max_cameras&lt;&#x2F;code&gt;) are set by &lt;code&gt;mkfs.eyeFS&lt;&#x2F;code&gt; and cannot be changed at mount.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;15-tradeoffs&quot;&gt;15. Tradeoffs&lt;a class=&quot;post-anchor&quot; href=&quot;#15-tradeoffs&quot; aria-label=&quot;Anchor link for: 15-tradeoffs&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Zipper-merge vs per-camera rings.&lt;&#x2F;strong&gt; Chosen: zipper. Simpler, uniform retention, dynamic add&#x2F;remove is trivial. Cost: no per-camera bandwidth isolation — a chatty camera shortens everyone&#x27;s retention.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Chunk size vs camera count.&lt;&#x2F;strong&gt; At N cameras and nominal chunk size C, the total chunk rate is N × (bitrate&#x2F;C). At 32 cameras × 10 Mbps &#x2F; 128 MiB ≈ one chunk every 3.3 seconds. Index size scales linearly; still negligible (5 MiB per 10 TB at default C, 2.5 MiB at 256 MiB C).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Synchronized log-time simplification.&lt;&#x2F;strong&gt; Rules out per-camera retention policies (&quot;keep camera A for 90 days, camera B for 7 days&quot;). If ever needed, the right answer is two devices — one per retention class — not one device with two retention policies.&lt;&#x2F;li&gt;
&lt;li&gt;**Kernel vs userspace.**The kernel path is justified once you decide to own the storage path; the only reason to stay in userspace is to avoid kernel development, and this user has explicitly discounted that reason.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Writing RTSP in userspace.&lt;&#x2F;strong&gt; Not a tradeoff — it&#x27;s the only sane place. Kernel-space TLS and auth and vendor quirks are a non-starter.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;16-open-questions&quot;&gt;16. Open questions&lt;a class=&quot;post-anchor&quot; href=&quot;#16-open-questions&quot; aria-label=&quot;Anchor link for: 16-open-questions&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ZNS as a first-class backend.&lt;&#x2F;strong&gt; Natural fit; a zoned-mode eyeFS uses one zone per chunk (or one zone per N chunks), and wrap becomes zone-reset. Worth a follow-up paper.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Per-camera priority.&lt;&#x2F;strong&gt; Not implemented; if needed, the mechanism is upstream at the ingestor (rate-limit) or downstream (separate devices).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Encryption at rest.&lt;&#x2F;strong&gt; dm-crypt underneath eyeFS is the no-brain answer. Per-chunk encryption with a scannable outer envelope is possible but deferred.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Hot-swap of block device.&lt;&#x2F;strong&gt; The superblock carries a UUID; the mount binds to a specific UUID. Unplug&#x2F;replug with the same UUID should resume; mismatched UUID refuses. Behavior under degraded states (I&#x2F;O errors mid-flush) needs a full write-up.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Multi-device striping &#x2F; mirroring.&lt;&#x2F;strong&gt; Out of scope for eyeFS itself; push to md&#x2F;dm below.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Registry slot reuse after full wrap.&lt;&#x2F;strong&gt; Mechanism exists (slot is reclaimable once all its chunks are overwritten); edge cases around camera_id recycling deserve care to avoid confusing archival tools that cached old associations.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Upstreaming.&lt;&#x2F;strong&gt; eyeFS is narrow enough that upstream inclusion is unlikely to be welcomed without a larger ZNS story. Out-of-tree is fine; DKMS via the NixOS monorepo handles distribution.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;hr &#x2F;&gt;
&lt;h2 id=&quot;17-summary&quot;&gt;17. Summary&lt;a class=&quot;post-anchor&quot; href=&quot;#17-summary&quot; aria-label=&quot;Anchor link for: 17-summary&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;eyeFS is a kernel filesystem that treats a block device as a circular log of H.265 chunks and exposes it through standard VFS and sysfs surfaces. Multiple cameras share one ring via zipper-merge on a single write head, tagged by &lt;code&gt;camera_id&lt;&#x2F;code&gt; in a decoder-safe sidecar SEI, dynamically added and removed at runtime, with uniform retention across feeds. The userspace surface is what users expect from a filesystem — chunks are files, &lt;code&gt;current&lt;&#x2F;code&gt; tails live, &lt;code&gt;sendfile&lt;&#x2F;code&gt; streams to sockets, &lt;code&gt;inotify&lt;&#x2F;code&gt; fires on new chunks, NFS exports the whole thing. The kernel surface is what an operator expects from a tunable block-device subsystem — sysfs for state and config, debugfs for diagnostics, mount options for format-compatible defaults. RTSP stays in userspace, as it should.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Alpine Linux on arm install Part 3</title>
          <pubDate>Thu, 26 Mar 2026 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://blog.s99.sh/blog/alpine-p3/</link>
          <guid>https://blog.s99.sh/blog/alpine-p3/</guid>
          <description xml:base="https://blog.s99.sh/blog/alpine-p3/">&lt;h2 id=&quot;step-3-write-u-boot-and-install-alpine-rootfs&quot;&gt;Step 3: Write U-Boot and Install Alpine Rootfs&lt;a class=&quot;post-anchor&quot; href=&quot;#step-3-write-u-boot-and-install-alpine-rootfs&quot; aria-label=&quot;Anchor link for: step-3-write-u-boot-and-install-alpine-rootfs&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;h2 id=&quot;overview&quot;&gt;Overview&lt;a class=&quot;post-anchor&quot; href=&quot;#overview&quot; aria-label=&quot;Anchor link for: overview&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Flash u-boot to the SD card, then mount the partitions and extract the Alpine Linux aarch64 rootfs.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;write-u-boot-to-the-sd-card&quot;&gt;Write u-boot to the SD card&lt;a class=&quot;post-anchor&quot; href=&quot;#write-u-boot-to-the-sd-card&quot; aria-label=&quot;Anchor link for: write-u-boot-to-the-sd-card&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Sunxi (Allwinner) boards expect u-boot at the 8KB offset:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sudo dd if=.&#x2F;result&#x2F;u-boot-sunxi-with-spl.bin of=&#x2F;dev&#x2F;sdd bs=1024 seek=8&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;mount-the-partitions&quot;&gt;Mount the partitions&lt;a class=&quot;post-anchor&quot; href=&quot;#mount-the-partitions&quot; aria-label=&quot;Anchor link for: mount-the-partitions&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sudo mkdir -p &#x2F;mnt&#x2F;sdcard&#x2F;{boot,root}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sudo mount &#x2F;dev&#x2F;sdd1 &#x2F;mnt&#x2F;sdcard&#x2F;boot&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sudo mount &#x2F;dev&#x2F;sdd2 &#x2F;mnt&#x2F;sdcard&#x2F;root&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;download-and-extract-alpine-rootfs&quot;&gt;Download and extract Alpine rootfs&lt;a class=&quot;post-anchor&quot; href=&quot;#download-and-extract-alpine-rootfs&quot; aria-label=&quot;Anchor link for: download-and-extract-alpine-rootfs&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;The Orange Pi Zero 2W is AArch64 (Allwinner H618), so use the aarch64 u-boot tarball:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;wget https:&#x2F;&#x2F;dl-cdn.alpinelinux.org&#x2F;alpine&#x2F;v3.21&#x2F;releases&#x2F;aarch64&#x2F;alpine-uboot-3.21.3-aarch64.tar.gz&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Extract to the root partition:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sudo tar -xzf alpine-uboot-3.21.3-aarch64.tar.gz -C &#x2F;mnt&#x2F;sdcard&#x2F;root&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;populate-the-boot-partition&quot;&gt;Populate the boot partition&lt;a class=&quot;post-anchor&quot; href=&quot;#populate-the-boot-partition&quot; aria-label=&quot;Anchor link for: populate-the-boot-partition&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Copy boot files (kernel, dtbs, extlinux config) to the FAT32 boot partition:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sudo cp -r &#x2F;mnt&#x2F;sdcard&#x2F;root&#x2F;boot&#x2F;* &#x2F;mnt&#x2F;sdcard&#x2F;boot&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Verify that the DTB &lt;code&gt;sun50i-h618-orangepi-zero2w.dtb&lt;&#x2F;code&gt; is present in the boot partition and referenced correctly in &lt;code&gt;extlinux&#x2F;extlinux.conf&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;unmount&quot;&gt;Unmount&lt;a class=&quot;post-anchor&quot; href=&quot;#unmount&quot; aria-label=&quot;Anchor link for: unmount&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sudo umount &#x2F;mnt&#x2F;sdcard&#x2F;boot &#x2F;mnt&#x2F;sdcard&#x2F;root&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;</description>
      </item>
      <item>
          <title>Alpine Linux on arm install Part 3</title>
          <pubDate>Thu, 26 Mar 2026 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://blog.s99.sh/blog/alpine_linux_orange_pi/s03-filesystems/</link>
          <guid>https://blog.s99.sh/blog/alpine_linux_orange_pi/s03-filesystems/</guid>
          <description xml:base="https://blog.s99.sh/blog/alpine_linux_orange_pi/s03-filesystems/">&lt;h2 id=&quot;step-3-write-u-boot-and-install-alpine-rootfs&quot;&gt;Step 3: Write U-Boot and Install Alpine Rootfs&lt;&#x2F;h2&gt;
&lt;h2 id=&quot;overview&quot;&gt;Overview&lt;&#x2F;h2&gt;
&lt;p&gt;Flash u-boot to the SD card, then mount the partitions and extract the Alpine Linux aarch64 rootfs.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;write-u-boot-to-the-sd-card&quot;&gt;Write u-boot to the SD card&lt;&#x2F;h2&gt;
&lt;p&gt;Sunxi (Allwinner) boards expect u-boot at the 8KB offset:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sudo dd if=.&#x2F;result&#x2F;u-boot-sunxi-with-spl.bin of=&#x2F;dev&#x2F;sdd bs=1024 seek=8&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;mount-the-partitions&quot;&gt;Mount the partitions&lt;&#x2F;h2&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sudo mkdir -p &#x2F;mnt&#x2F;sdcard&#x2F;{boot,root}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sudo mount &#x2F;dev&#x2F;sdd1 &#x2F;mnt&#x2F;sdcard&#x2F;boot&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sudo mount &#x2F;dev&#x2F;sdd2 &#x2F;mnt&#x2F;sdcard&#x2F;root&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;download-and-extract-alpine-rootfs&quot;&gt;Download and extract Alpine rootfs&lt;&#x2F;h2&gt;
&lt;p&gt;The Orange Pi Zero 2W is AArch64 (Allwinner H618), so use the aarch64 u-boot tarball:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;wget https:&#x2F;&#x2F;dl-cdn.alpinelinux.org&#x2F;alpine&#x2F;v3.21&#x2F;releases&#x2F;aarch64&#x2F;alpine-uboot-3.21.3-aarch64.tar.gz&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Extract to the root partition:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sudo tar -xzf alpine-uboot-3.21.3-aarch64.tar.gz -C &#x2F;mnt&#x2F;sdcard&#x2F;root&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;populate-the-boot-partition&quot;&gt;Populate the boot partition&lt;&#x2F;h2&gt;
&lt;p&gt;Copy boot files (kernel, dtbs, extlinux config) to the FAT32 boot partition:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sudo cp -r &#x2F;mnt&#x2F;sdcard&#x2F;root&#x2F;boot&#x2F;* &#x2F;mnt&#x2F;sdcard&#x2F;boot&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Verify that the DTB &lt;code&gt;sun50i-h618-orangepi-zero2w.dtb&lt;&#x2F;code&gt; is present in the boot partition and referenced correctly in &lt;code&gt;extlinux&#x2F;extlinux.conf&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;unmount&quot;&gt;Unmount&lt;&#x2F;h2&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sudo umount &#x2F;mnt&#x2F;sdcard&#x2F;boot &#x2F;mnt&#x2F;sdcard&#x2F;root&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;</description>
      </item>
      <item>
          <title>Alpine Linux on arm install Part 2</title>
          <pubDate>Wed, 18 Mar 2026 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://blog.s99.sh/blog/alpine-p2/</link>
          <guid>https://blog.s99.sh/blog/alpine-p2/</guid>
          <description xml:base="https://blog.s99.sh/blog/alpine-p2/">&lt;h2 id=&quot;step-2-get-u-boot&quot;&gt;Step 2: Get U-Boot&lt;a class=&quot;post-anchor&quot; href=&quot;#step-2-get-u-boot&quot; aria-label=&quot;Anchor link for: step-2-get-u-boot&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;h2 id=&quot;overview&quot;&gt;Overview&lt;a class=&quot;post-anchor&quot; href=&quot;#overview&quot; aria-label=&quot;Anchor link for: overview&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Build u-boot for the Orange Pi Zero using Nix&#x27;s cross-compilation infrastructure.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;build-u-boot-with-nix&quot;&gt;Build u-boot with Nix&lt;a class=&quot;post-anchor&quot; href=&quot;#build-u-boot-with-nix&quot; aria-label=&quot;Anchor link for: build-u-boot-with-nix&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;nix-build &amp;#39;&amp;lt;nixpkgs&amp;gt;&amp;#39; -A pkgsCross.aarch64-multiplatform.ubootOrangePiZero2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The resulting &lt;code&gt;u-boot-sunxi-with-spl.bin&lt;&#x2F;code&gt; will be in &lt;code&gt;.&#x2F;result&#x2F;&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;notes-from-the-nixos-u-boot-wiki&quot;&gt;Notes from the NixOS U-Boot Wiki&lt;a class=&quot;post-anchor&quot; href=&quot;#notes-from-the-nixos-u-boot-wiki&quot; aria-label=&quot;Anchor link for: notes-from-the-nixos-u-boot-wiki&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Reference: https:&#x2F;&#x2F;wiki.nixos.org&#x2F;wiki&#x2F;U-Boot&lt;&#x2F;p&gt;
&lt;h3 id=&quot;cross-compilation-package-sets&quot;&gt;Cross-compilation package sets&lt;a class=&quot;post-anchor&quot; href=&quot;#cross-compilation-package-sets&quot; aria-label=&quot;Anchor link for: cross-compilation-package-sets&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;Nix provides cross-compilation package sets for ARM targets:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Target Architecture&lt;&#x2F;th&gt;&lt;th&gt;Package Set&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;AArch64&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;aarch64-multiplatform&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;ARMv7l&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;armv7l-hf-multiplatform&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;ARMv6l&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;raspberryPi&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;h3 id=&quot;general-command-format&quot;&gt;General command format&lt;a class=&quot;post-anchor&quot; href=&quot;#general-command-format&quot; aria-label=&quot;Anchor link for: general-command-format&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;nix-build -A [pkgsCross.$cross_name.]$packageName&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;When building from an x86_64 host for an ARM board, use the &lt;code&gt;pkgsCross&lt;&#x2F;code&gt; prefix. On a native AArch64 system, the prefix can be omitted:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;nix-build -A ubootOrangePiZero2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;finding-available-u-boot-packages&quot;&gt;Finding available u-boot packages&lt;a class=&quot;post-anchor&quot; href=&quot;#finding-available-u-boot-packages&quot; aria-label=&quot;Anchor link for: finding-available-u-boot-packages&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;To find board-specific u-boot packages in nixpkgs, search for &lt;code&gt;uboot&lt;&#x2F;code&gt; in the package set:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;nix-env -qaP -A nixpkgs.pkgsCross.aarch64-multiplatform | grep -i uboot&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;For board defconfigs and SD card write offsets, consult the official U-Boot documentation or board-specific pages, as the large variety of ecosystems makes a single guide impractical.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Alpine Linux on arm install Part 2</title>
          <pubDate>Wed, 18 Mar 2026 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://blog.s99.sh/blog/alpine_linux_orange_pi/s02-getuboot/</link>
          <guid>https://blog.s99.sh/blog/alpine_linux_orange_pi/s02-getuboot/</guid>
          <description xml:base="https://blog.s99.sh/blog/alpine_linux_orange_pi/s02-getuboot/">&lt;h2 id=&quot;step-2-get-u-boot&quot;&gt;Step 2: Get U-Boot&lt;&#x2F;h2&gt;
&lt;h2 id=&quot;overview&quot;&gt;Overview&lt;&#x2F;h2&gt;
&lt;p&gt;Build u-boot for the Orange Pi Zero using Nix&#x27;s cross-compilation infrastructure.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;build-u-boot-with-nix&quot;&gt;Build u-boot with Nix&lt;&#x2F;h2&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;nix-build &amp;#39;&amp;lt;nixpkgs&amp;gt;&amp;#39; -A pkgsCross.aarch64-multiplatform.ubootOrangePiZero2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The resulting &lt;code&gt;u-boot-sunxi-with-spl.bin&lt;&#x2F;code&gt; will be in &lt;code&gt;.&#x2F;result&#x2F;&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;notes-from-the-nixos-u-boot-wiki&quot;&gt;Notes from the NixOS U-Boot Wiki&lt;&#x2F;h2&gt;
&lt;p&gt;Reference: https:&#x2F;&#x2F;wiki.nixos.org&#x2F;wiki&#x2F;U-Boot&lt;&#x2F;p&gt;
&lt;h3 id=&quot;cross-compilation-package-sets&quot;&gt;Cross-compilation package sets&lt;&#x2F;h3&gt;
&lt;p&gt;Nix provides cross-compilation package sets for ARM targets:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Target Architecture&lt;&#x2F;th&gt;&lt;th&gt;Package Set&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;AArch64&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;aarch64-multiplatform&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;ARMv7l&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;armv7l-hf-multiplatform&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;ARMv6l&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;raspberryPi&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;h3 id=&quot;general-command-format&quot;&gt;General command format&lt;&#x2F;h3&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;nix-build -A [pkgsCross.$cross_name.]$packageName&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;When building from an x86_64 host for an ARM board, use the &lt;code&gt;pkgsCross&lt;&#x2F;code&gt; prefix. On a native AArch64 system, the prefix can be omitted:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;nix-build -A ubootOrangePiZero2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;finding-available-u-boot-packages&quot;&gt;Finding available u-boot packages&lt;&#x2F;h3&gt;
&lt;p&gt;To find board-specific u-boot packages in nixpkgs, search for &lt;code&gt;uboot&lt;&#x2F;code&gt; in the package set:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;nix-env -qaP -A nixpkgs.pkgsCross.aarch64-multiplatform | grep -i uboot&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;For board defconfigs and SD card write offsets, consult the official U-Boot documentation or board-specific pages, as the large variety of ecosystems makes a single guide impractical.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Alpine Linux on arm install Part 1</title>
          <pubDate>Sat, 07 Mar 2026 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://blog.s99.sh/blog/alpine-p1/</link>
          <guid>https://blog.s99.sh/blog/alpine-p1/</guid>
          <description xml:base="https://blog.s99.sh/blog/alpine-p1/">&lt;h1 id=&quot;step-1-partition-the-sd-card&quot;&gt;Step 1: Partition the SD Card&lt;a class=&quot;post-anchor&quot; href=&quot;#step-1-partition-the-sd-card&quot; aria-label=&quot;Anchor link for: step-1-partition-the-sd-card&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h1&gt;
&lt;h2 id=&quot;overview&quot;&gt;Overview&lt;a class=&quot;post-anchor&quot; href=&quot;#overview&quot; aria-label=&quot;Anchor link for: overview&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Partition an SD card for installing Alpine Linux on an Orange Pi Zero 2W (Allwinner H618, AArch64). The card needs two partitions: a FAT32 boot partition for u-boot and the kernel, and an ext4 root partition for the filesystem.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;identify-the-sd-card&quot;&gt;Identify the SD Card&lt;a class=&quot;post-anchor&quot; href=&quot;#identify-the-sd-card&quot; aria-label=&quot;Anchor link for: identify-the-sd-card&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Find the SD card device with:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;lsblk -d -o NAME,SIZE,MODEL,TRAN&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;In this case, the device was &lt;code&gt;&#x2F;dev&#x2F;sdd&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;partition-with-sfdisk&quot;&gt;Partition with sfdisk&lt;a class=&quot;post-anchor&quot; href=&quot;#partition-with-sfdisk&quot; aria-label=&quot;Anchor link for: partition-with-sfdisk&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Use &lt;code&gt;sfdisk&lt;&#x2F;code&gt; for scriptable partitioning:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;echo &amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;label: dos&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;unit: sectors&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&#x2F;dev&#x2F;sdd1 : size=+512M, type=c, bootable&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&#x2F;dev&#x2F;sdd2 : type=83&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;#39; | sudo sfdisk &#x2F;dev&#x2F;sdd&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This creates:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Partition 1&lt;&#x2F;strong&gt; — 512MB, FAT32 (LBA), bootable — boot partition (u-boot + kernel)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Partition 2&lt;&#x2F;strong&gt; — remaining space, Linux — root filesystem&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;&#x2F;strong&gt; 256MB is too small for the boot partition — the Alpine kernel, modloop, and System.map won&#x27;t fit. Use 512MB.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;format-the-partitions&quot;&gt;Format the Partitions&lt;a class=&quot;post-anchor&quot; href=&quot;#format-the-partitions&quot; aria-label=&quot;Anchor link for: format-the-partitions&quot;&gt;&lt;span aria-hidden=&quot;true&quot;&gt;#&lt;&#x2F;span&gt;&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sudo mkfs.vfat &#x2F;dev&#x2F;sdd1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sudo mkfs.ext4 &#x2F;dev&#x2F;sdd2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;</description>
      </item>
      <item>
          <title>Alpine Linux on arm install Part 1</title>
          <pubDate>Sat, 07 Mar 2026 00:00:00 +0000</pubDate>
          <author>Unknown</author>
          <link>https://blog.s99.sh/blog/alpine_linux_orange_pi/s01-partition/</link>
          <guid>https://blog.s99.sh/blog/alpine_linux_orange_pi/s01-partition/</guid>
          <description xml:base="https://blog.s99.sh/blog/alpine_linux_orange_pi/s01-partition/">&lt;h1 id=&quot;step-1-partition-the-sd-card&quot;&gt;Step 1: Partition the SD Card&lt;&#x2F;h1&gt;
&lt;h2 id=&quot;overview&quot;&gt;Overview&lt;&#x2F;h2&gt;
&lt;p&gt;Partition an SD card for installing Alpine Linux on an Orange Pi Zero 2W (Allwinner H618, AArch64). The card needs two partitions: a FAT32 boot partition for u-boot and the kernel, and an ext4 root partition for the filesystem.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;identify-the-sd-card&quot;&gt;Identify the SD Card&lt;&#x2F;h2&gt;
&lt;p&gt;Find the SD card device with:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;lsblk -d -o NAME,SIZE,MODEL,TRAN&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;In this case, the device was &lt;code&gt;&#x2F;dev&#x2F;sdd&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;partition-with-sfdisk&quot;&gt;Partition with sfdisk&lt;&#x2F;h2&gt;
&lt;p&gt;Use &lt;code&gt;sfdisk&lt;&#x2F;code&gt; for scriptable partitioning:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;echo &amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;label: dos&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;unit: sectors&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&#x2F;dev&#x2F;sdd1 : size=+512M, type=c, bootable&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&#x2F;dev&#x2F;sdd2 : type=83&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;#39; | sudo sfdisk &#x2F;dev&#x2F;sdd&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This creates:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Partition 1&lt;&#x2F;strong&gt; — 512MB, FAT32 (LBA), bootable — boot partition (u-boot + kernel)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Partition 2&lt;&#x2F;strong&gt; — remaining space, Linux — root filesystem&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;&#x2F;strong&gt; 256MB is too small for the boot partition — the Alpine kernel, modloop, and System.map won&#x27;t fit. Use 512MB.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;format-the-partitions&quot;&gt;Format the Partitions&lt;&#x2F;h2&gt;
&lt;pre class=&quot;giallo z-code&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sudo mkfs.vfat &#x2F;dev&#x2F;sdd1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sudo mkfs.ext4 &#x2F;dev&#x2F;sdd2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;</description>
      </item>
    </channel>
</rss>
