<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>News 'n' Updates | Guerilla Ontology</title>
  <link href="http://john-craig.github.io/feed.xml" rel="self"/>
  <link href="http://john-craig.github.io/"/>
  <updated>2026-09-23T11:35:47+00:00</updated>
  <id>http://john-craig.github.io/</id>
  <author>
    <name>John Craig</name>
  </author>
  <generator>Jekyll v3.10.0</generator>

  
  <entry>
    <title>A Button-Press Receipt Scanner for Paperless</title>
    <link href="http://john-craig.github.io/epson-es-50-paperless-receipts.html"/>
    <updated>2026-09-19T00:00:00+00:00</updated>
    <id>http://john-craig.github.io/epson-es-50-paperless-receipts</id>
    <content type="html">&lt;h1 id=&quot;a-button-press-receipt-scanner-for-paperless&quot;&gt;A Button-Press Receipt Scanner for Paperless&lt;/h1&gt;

&lt;p&gt;Not so long ago, I got my hands on an &lt;a href=&quot;https://epson.com/For-Home/Scanners/Document-Scanners/WorkForce-ES-50-Portable-Document-Scanner/p/B11B252201&quot;&gt;Epson ES-50&lt;/a&gt; document scanner. My main interest was for a quick and easy way to scan receipts and upload them to my self-hosted &lt;a href=&quot;https://github.com/paperless-ngx/paperless-ngx&quot;&gt;paperless-ng&lt;/a&gt; instance. My eventual goal is to be able to correlate the receipts to financial transactions so that I can get itemized-detail of my purchases, but that’s a tale for another time.&lt;/p&gt;

&lt;p&gt;At first, this project seemed easy. Indeed, it could have been. I quickly found that the ES-50 scanner didn’t require any special drivers or libraries to scan documents on Linux. It’s a USB-powered device, so it was pretty much plug-‘n’-play. I just entered a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scanimage&lt;/code&gt; command and the scanner would pull the receipt through its feeder and a new image would appear in my filesystem.&lt;/p&gt;

&lt;p&gt;Getting that image into paperless-ng wasn’t too hard either; unsurprisingly, uploading documents is one of paperless’ primary functions, so it had first-class API support. All I needed was an API-token and a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;curl&lt;/code&gt; command. Wrapping the whole thing in one script and setting it as a shell alias was the work of one afternoon.&lt;/p&gt;

&lt;p&gt;It could have been that simple. But then, there was the button.&lt;/p&gt;

&lt;h2 id=&quot;the-button&quot;&gt;The Button&lt;/h2&gt;

&lt;p&gt;The Epson ES-50 scanner has a little button on the scanner itself. Presumably, on Windows, pressing this button allows you to trigger a scan of a document from the scanner itself. On Linux, no such functionality was supported out-of-the-box. It’s one thing to support a peripheral as a scanner, but supporting a peripheral as a scanner &lt;em&gt;and&lt;/em&gt; recieving input events from some vendor-specific device button? Now that’s a tall order.&lt;/p&gt;

&lt;p&gt;“It works fine,” I told myself. One command from the commandline was all I needed to scan a receipt. It was easy. It wasn’t even that inconvenient.&lt;/p&gt;

&lt;p&gt;But it could have been better.&lt;/p&gt;

&lt;p&gt;I spent a few afternoons playing around with the scanner myself. I set up a program that could capture raw input events directly from the USB device to see if there was any way I could detect the button press and trigger my own events in response. As I recall, the issue I ran into was that although it was possible to detect the button press, it required an exclusive lock on the input device from the program performing the detection. This unfortunately prevented the scanner from actually being used as a scanner, since &lt;a href=&quot;https://www.sane-project.org/&quot;&gt;SANE&lt;/a&gt; couldn’t read from it.&lt;/p&gt;

&lt;p&gt;At that point, I was at least satisfied that matter was impossible. I had probably wasted far more time out of my life experimenting with the damn scanner than being able to trigger scans from the button could have possibly saved me, but that was pretty much par for the course with homelab stuff anyways.&lt;/p&gt;

&lt;h2 id=&quot;es2button&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;es2button&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;I endured the agony of knowing a task I perform for only about twenty minutes once a month could theoretically, if not practically, be made slightly more efficient for a while. Then, I moved apartments. In my new apartment, I chose to situate the Beelink Mini which I use for my homelab’s media kiosk on a shelf in a closet. This would be the perfect place to put my receipt scanner so that I could just walk in the door, scan a new receipt, and toss in the trash. The only issue was that my media kiosk is meant to be a kiosk– no keyboard for me to issue terminal commands into for triggering the scan.&lt;/p&gt;

&lt;p&gt;So, back to the button.&lt;/p&gt;

&lt;p&gt;I reviewed the literature (with the help Claude, admittedly), and found that there was a project that I had either overlooked in the past, or had been created since my initial attempt, called &lt;a href=&quot;https://github.com/janrueth/es2button&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;es2button&lt;/code&gt;&lt;/a&gt;. And this did exactly what I was looking for: it listened for a button event from a connected Epson scanner, and was able to trigger other events in response. And, seemingly, it worked without blocking the device from also being used as a scanner.&lt;/p&gt;

&lt;h2 id=&quot;the-technical-details&quot;&gt;The Technical Details&lt;/h2&gt;

&lt;p&gt;The technical solution ended up being a little more involved than a shell alias, but not dramatically so. On the media kiosk, I run two systemd services: an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;es2button&lt;/code&gt; listener service and a receipt worker service.&lt;/p&gt;

&lt;p&gt;The listener service runs the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;es2button&lt;/code&gt; package and waits for input from the scanner. When it sees Button 1, it writes a small protected &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pending&lt;/code&gt; file and starts the worker service. It doesn’t try to scan the receipt itself. It just records the event and gets out of the way.&lt;/p&gt;

&lt;p&gt;The worker service runs as a dedicated &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;epson-receipt&lt;/code&gt; user. It takes a lock so that two button presses can’t start two scans at once, stops the listener, waits a few seconds for the USB connection to settle, and then runs the existing receipt scanning and Paperless upload script. When the worker finishes, it starts the listener service again. If the upload fails, the scanned document is left in a restricted recovery directory instead of being discarded.&lt;/p&gt;

&lt;h2 id=&quot;setting-it-up-with-nixos&quot;&gt;Setting it up with NixOS&lt;/h2&gt;

&lt;p&gt;I packaged this setup as a NixOS module in my &lt;a href=&quot;https://github.com/john-craig/nixpkgs-apocrypha&quot;&gt;nixpkgs-apocrypha repository&lt;/a&gt;. The module is exported as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nixosModules.receiptScanner&lt;/code&gt;, and turns the moving parts above into two services, udev rules, the scanner group, the SANE backend, and a small manual trigger command.&lt;/p&gt;

&lt;p&gt;Add the repository as a flake input:&lt;/p&gt;

&lt;div class=&quot;language-nix highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;inputs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;nixpkgs-apocrypha&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;url&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;github:john-craig/nixpkgs-apocrypha&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then import the module in the host configuration. The following is the shape of my configuration; the scanner identifier, USB IDs, Paperless URL, and Epson Scan 2 package are host-specific.&lt;/p&gt;

&lt;div class=&quot;language-nix highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;inputs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;pkgs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}:&lt;/span&gt;

&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;imports&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;inputs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;nixpkgs-apocrypha&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;nixosModules&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;receiptScanner&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

  &lt;span class=&quot;nv&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;receiptScanner&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;enable&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;c&quot;&gt;# Use the stable identifier from `scanimage -L`, not a libusb bus/device ID.&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;scannerDevice&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;epsonscan2:Epson ES-50:SERIAL:esci2:usb:ES017E:364&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;usbVendorId&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;04b8&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;usbProductId&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;016c&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;nv&quot;&gt;paperlessUrl&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;https://paperless.example&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;paperlessTokenFile&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;/run/secrets/paperless-token&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;c&quot;&gt;# es2button is packaged by nixpkgs-apocrypha.&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;es2buttonPackage&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;inputs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;nixpkgs-apocrypha&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;packages&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;pkgs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;system&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;es2button&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;saneBackendPackage&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;pkgs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;epsonscan2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;epsonScan2Runtime&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;pkgs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;epsonscan2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;c&quot;&gt;# Optional: add an existing human user to the scanner group.&lt;/span&gt;
    &lt;span class=&quot;nv&quot;&gt;manualUser&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;john&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;es2buttonPackage&lt;/code&gt; line uses the package exported by the same flake, so there is no second input or separate installation step. The package builds the upstream &lt;a href=&quot;https://github.com/janrueth/es2button&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;es2button&lt;/code&gt;&lt;/a&gt; listener for Linux. The Paperless token is deliberately an external file rather than a value in the Nix configuration. On a host where Epson Scan 2 is installed outside Nix, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;saneBackendPackage&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;epsonScan2Runtime&lt;/code&gt; can be supplied another way, but the module requires &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;allowExternallyProvisionedRuntime = true&lt;/code&gt; when no runtime path is available.&lt;/p&gt;

&lt;p&gt;Before applying the configuration, I use these commands to find the values that belong in it:&lt;/p&gt;

&lt;div class=&quot;language-console highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;scanimage &lt;span class=&quot;nt&quot;&gt;-L&lt;/span&gt;
&lt;span class=&quot;go&quot;&gt;device `epsonscan2:Epson ES-50:SERIAL:esci2:usb:ES017E:364&apos; is a Epson ES-50 scanner

&lt;/span&gt;&lt;span class=&quot;gp&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;lsusb
&lt;span class=&quot;go&quot;&gt;Bus 001 Device 004: ID 04b8:016c Seiko Epson Corp.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The exact SANE output and USB product ID can differ by device or backend, so these are examples rather than values to copy blindly. In particular, the module rejects a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;libusb:&lt;/code&gt; scanner identifier because bus and device numbers can change when the scanner is reconnected.&lt;/p&gt;

&lt;p&gt;After rebuilding, the button listener is normally started by the udev rule when the scanner is plugged in. For a keyboard-free test, the module also installs a manual trigger:&lt;/p&gt;

&lt;div class=&quot;language-console highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;go&quot;&gt;sudo systemctl start receipt-scanner-manual-trigger.service
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The worker reads the token through systemd’s credential mechanism, uploads to Paperless, and leaves failed uploads under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/var/lib/receipt-scanner/recovery&lt;/code&gt;. The two service logs are the first place to look when something goes wrong:&lt;/p&gt;

&lt;div class=&quot;language-console highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;go&quot;&gt;journalctl -u receipt-scanner-listener.service -u receipt-scanner-worker.service
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</content>
  </entry>
  
  <entry>
    <title>Rhizomatic Note Taking with Obsidian</title>
    <link href="http://john-craig.github.io/rhizomatic-notes-obsidian-plugin.html"/>
    <updated>2026-08-06T00:00:00+00:00</updated>
    <id>http://john-craig.github.io/rhizomatic-notes-obsidian-plugin</id>
    <content type="html">&lt;h1 id=&quot;rhizomatic-note-taking&quot;&gt;Rhizomatic Note Taking&lt;/h1&gt;

&lt;p&gt;I got start with Obsidian a few years ago. It satisfied my basic requirements: a pleasant-looking Markdown editor with offline capabilities. I was also intrigued by the zettelkasten system as a method for organizing ideas and information, and Obsidian’s intralink system seemed like an effective way of accomplishing that.&lt;/p&gt;

&lt;p&gt;But, as I continued to use Obsidian to expand my knowledge base, I quickly encountered a problem: taxonomy paralysis.&lt;/p&gt;

&lt;p&gt;I found that before I could commit my ideas and thoughts to a note, I first had to decide very carefully where the note ought to fit within the taxonomy of my existing knowledge base.&lt;/p&gt;

&lt;p&gt;If I encountered an issue with an SSH connection while I was working on a project to automate deployments, did that information belong in a dedicated note about secure shell, or in the note about the project? If I was setting up network-bound disk encryption, should that be categorized as “system administration” or “cyber security”? Should I track the physical hardware of my devices separately from the operating system and software configurations installed on them? Did I need to differentiate computers that I physically used, like my workstation and laptop, from computers that mostly acted as servers, like my raspberry pis?&lt;/p&gt;

&lt;p&gt;At a certain point, I recalled a concept I had heard from a &lt;a href=&quot;https://www.youtube.com/watch?v=RQ2rJWwXilw&quot;&gt;video essay&lt;/a&gt; about the philosophical concept of “rhizomes”. I certainly don’t claim to be an expert in the subject, but a brief definition from &lt;a href=&quot;https://en.wikipedia.org/wiki/Rhizome_(philosophy)&quot;&gt;Wikipedia&lt;/a&gt; will serve well:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;[A rhizome is] an assemblage that allows connections between any of its constituent elements, regardless of any predefined ordering, structure, or entry point.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This sounded like exactly what I needed. A system that allowed elements to added into it without any concern for a predefined structure. This would remove the cognitive load of worrying about how the information was to be categorized, and if it needed to be added to an existing place or not. Ideally, I would record the content immediately, and then the structure linking content together would emerge organically later.&lt;/p&gt;

&lt;h2 id=&quot;themagraphs&quot;&gt;Themagraphs&lt;/h2&gt;

&lt;p&gt;Obsidian already has a very effective mechanism for linking together different notes: intralinks. These are inline references to other notes or tags that can be inserted quite seamlessly while writing. This mechanism is already very well-suited for creating a rhizomatic knowledge base, because you can simply write some of the keywords in a note as intralinks and this will automatically link them to a corresponding note for that keyword (or just create a “loose” intralink if no note exists).&lt;/p&gt;

&lt;p&gt;My problem with intralinks as Obsidian implemented them was that the scope was too big. Ideally, if I were totally dispensing with categorization, I’d be able to just brain-dump everything into one long daily note and be done with it, tagging relevant intralinks as I went along. But, I can visit a lot of different topics over the course of the day, and not all of them are meaningfully associated with one another. Just because I happen to talk about the gas mileage of my truck on the same day that I’m debugging a Rust script doesn’t mean there’s a meaningful semantic connection.&lt;/p&gt;

&lt;p&gt;This problem lead me to the concept of a “themagraph”: a paragraph or cluster of paragraphs within a single note pertaining to a particular topic or theme. The themagraph was, for me, the ideal scope for the base element of my rhizomatic note taking network. I could start a new themagraph when I knew I wanted to transition to another topic, or quickly go back to a previous themagraph when I knew I had more to add.&lt;/p&gt;

&lt;p&gt;However, to properly implement this vision, I needed to extend Obsidian’s base functionality. This led me down the path of creating my own dedicated plugin that I could use to experiment and implement my ideas– we’ll get back to that plugin a bit later.&lt;/p&gt;

&lt;h2 id=&quot;rhizomatic-queries&quot;&gt;Rhizomatic Queries&lt;/h2&gt;

&lt;p&gt;Getting past taxonomy paralysis was great, but I did want to eventually go back and assess the emergent patterns and links between my themagraphs. In order to do this, I needed a way to view all of the themagraphs associated with a certain intralink or group of intralinks.&lt;/p&gt;

&lt;p&gt;This lead me naturally to a query grammar based around Obsidian intralinks. There was no need to make it overly complex, so I reused typical boolean operators. To match themagraphs containing both &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[[foo]]&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[[bar]]&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[[foo]] &amp;amp;&amp;amp; [[bar]]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;to match themagraphs that included either:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[[foo]] || [[bar]]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I did want to be able to make broader queries, however, so I added the meta-operator:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;*[[foo]]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This operator would match any themagraphs that included links which were included in any themagraphs that included &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[[foo]]&lt;/code&gt;. Obviously, this kind of an operator can become huge, but it’s useful for a big very of a very broad topic, like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[[programming]]&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;named-queries&quot;&gt;Named Queries&lt;/h2&gt;

&lt;p&gt;While queries were incredibly useful for searching and exploring my rhizomatic network, I found myself also wanting a way to save the queries in a way that could be referenced more easily later. To solve this problem, I came up with the idea of “named queries”: Obsidian intralinks which served as a direct alias for a certain rhizomatic query.&lt;/p&gt;

&lt;p&gt;To avoid introducing an entirely new mechanism for storing named queries, I simply defined named queries as any themagraph which contains a text body like so:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[[my-query-name]] = &amp;lt;my-rhizomatic-query&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Here, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;my-rhizomatic-query&amp;gt;&lt;/code&gt; can be any valid rhizomatic query. When a themagraph is created containing this psuedocode, and nothing else, it defines a new named query.&lt;/p&gt;

&lt;p&gt;Named queries are automatically expanded inside of another rhizomatic query. For example, if &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[[my-query-name]]&lt;/code&gt; is assigned to the query &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[[foo]] &amp;amp;&amp;amp; [[bar]]&lt;/code&gt;, then the following expansion occurs:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[[my-query-name]] || [[baz]]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;becomes,&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;([[my-query-name]] || ([[foo]] &amp;amp;&amp;amp; [[bar]])) || [[baz]]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This way a rhizomatic query can use either the named query as a convenient shorthand, or any of the actual links of the query, and themagraphs will still be correctly matched.&lt;/p&gt;

&lt;h2 id=&quot;rhizomatic-templates&quot;&gt;Rhizomatic Templates&lt;/h2&gt;

&lt;p&gt;Finally, to take full advantage of rhizomatic note taking, we need a semi-permanent way to actually view the themagraphs of these queries. This takes me to rhizomatic templates. These are simple Markdown-based template files, inspired by the &lt;a href=&quot;https://community.obsidian.md/plugins/templater-obsidian&quot;&gt;Templater plugin&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If a file is created in the vault that has both a rhizomatic query and a rhizomatic template set in the Obsidian properties of its frontmatter, it will be dynamically populated based on the template layout and the themagraphs returned by the query.&lt;/p&gt;

&lt;p&gt;The interesting part is that a template can use subqueries to divide those results into different sections. The special &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$query&lt;/code&gt; value represents the query from the file’s frontmatter, and a template expression can add another condition to it. For example, a project file might look like this:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nn&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;rhizoid-query&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;[[my-project]]&quot;&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;rhizomatic-template&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;project&lt;/span&gt;
&lt;span class=&quot;nn&quot;&gt;---&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The corresponding &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;templates/project.md&lt;/code&gt; file could separate a definition from the project’s other notes:&lt;/p&gt;

&lt;div class=&quot;language-markdown highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gh&quot;&gt;# &amp;lt;% title() %&amp;gt;&lt;/span&gt;

&lt;span class=&quot;gu&quot;&gt;## Definition&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;query&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;[[&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;definition&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;]]&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;

&lt;span class=&quot;gu&quot;&gt;## Additional details&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;query&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;![[&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;definition&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;]]&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;

&lt;span class=&quot;gu&quot;&gt;## Tasks&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;tasks&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;($&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The first subquery, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$query &amp;amp;&amp;amp; [[definition]]&lt;/code&gt;, places only the themagraphs linked to both the project and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[[definition]]&lt;/code&gt; under the Definition heading. The negated subquery, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$query &amp;amp;&amp;amp; ![[definition]]&lt;/code&gt;, places the remaining project themagraphs under Additional details. The task expression uses the complete project query and renders the matching tasks at the end. As the project grows, these sections update automatically from the intralinks, without requiring me to maintain a list of notes by hand.&lt;/p&gt;

&lt;p&gt;The same idea works for a broader rollup. For example, I could use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[[programming]] || [[writing]]&lt;/code&gt; as the query for a daily journal and give it a template like this:&lt;/p&gt;

&lt;div class=&quot;language-markdown highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gh&quot;&gt;# &amp;lt;% title() %&amp;gt;&lt;/span&gt;

&lt;span class=&quot;gu&quot;&gt;## Related themagraphs&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;query&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;

&lt;span class=&quot;gu&quot;&gt;## Open tasks&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;tasks&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;($&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The query combines two topics, so the journal becomes a single view of the programming and writing themagraphs that I have accumulated. I could also add more selective sections by composing further subqueries, such as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$query &amp;amp;&amp;amp; [[project]]&lt;/code&gt; for project-related material or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$query &amp;amp;&amp;amp; ![[definition]]&lt;/code&gt; for notes that are not definitions. In this way, the query determines which themagraphs belong in the file, while the template determines how I want to arrange them.&lt;/p&gt;

&lt;h2 id=&quot;installation&quot;&gt;Installation&lt;/h2&gt;

&lt;p&gt;If the rhizomatic knowledge system described in this article is of interest to you, the &lt;a href=&quot;https://github.com/john-craig/rhizomatic-themagraphs-obsidian&quot;&gt;Rhizomatic Notes plugin&lt;/a&gt; is public, so it is possible to install the plugin directly from its source. This is a little more involved than installing a plugin from Obsidian’s community-plugin browser, but it only takes a few steps:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Install &lt;a href=&quot;https://nodejs.org/&quot;&gt;Node.js&lt;/a&gt; and Git if they are not already installed.&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Clone the repository and install its dependencies:&lt;/p&gt;

    &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git clone https://github.com/john-craig/rhizomatic-themagraphs-obsidian.git
&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;rhizomatic-themagraphs-obsidian
npm &lt;span class=&quot;nb&quot;&gt;install
&lt;/span&gt;npm run build
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;Create a directory for the plugin inside the vault at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.obsidian/plugins/rhizomatic-notes/&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Copy &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main.js&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;manifest.json&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;styles.css&lt;/code&gt; from the repository into that directory.&lt;/li&gt;
  &lt;li&gt;Restart or reload Obsidian, open &lt;strong&gt;Settings → Community plugins&lt;/strong&gt;, and enable &lt;strong&gt;Rhizomatic Notes&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The plugin uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;notes&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;projects&lt;/code&gt; as its default template directories. Templates can be stored in a vault-level &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;templates/&lt;/code&gt; directory and selected with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rhizomatic-template&lt;/code&gt; frontmatter property. If those directories do not fit the way a vault is organized, they can be changed in the plugin settings.&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Restricted Remote Access for AI Agents with lshell</title>
    <link href="http://john-craig.github.io/lshell-agentic-remote-access.html"/>
    <updated>2026-03-10T00:00:00+00:00</updated>
    <id>http://john-craig.github.io/lshell-agentic-remote-access</id>
    <content type="html">&lt;h1 id=&quot;restricted-remote-access-for-ai-agents-with-lshell&quot;&gt;Restricted Remote Access for AI Agents with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lshell&lt;/code&gt;&lt;/h1&gt;
&lt;p&gt;AI-powered coding agents like Claude, Codex, and Bob are skyrocketing in popularity in recent months. Part of what is driving their mass adoption is the ability to extend their functionality through MCP servers, allowing for the creation of truly agentic workflows through different tools and interfaces.&lt;/p&gt;

&lt;p&gt;What has been overlooked during this frenzy are guardrails and security controls. Although not as exciting, these are needed to ensure that these agents cannot utilize these tools to damage their environment, either as an unintended consequence of their own attempts at solving problems or due do malicious hijacking.&lt;/p&gt;

&lt;p&gt;As with all things, MCP tools must maintain a balance between security and usability. A tool in which this balance is particularly nuanced is remote connections over secure shell. On the one hand, an agent with the ability to connect to remote systems is incredibly powerful; however, that power comes with a substantial risk. Allowing remote access increases the footprint of devices that an agent can potentially modify and thus, damage.&lt;/p&gt;

&lt;h3 id=&quot;lshell-the-limited-shell&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lshell&lt;/code&gt;: The Limited Shell&lt;/h3&gt;
&lt;p&gt;One useful tool for restricting an AI agent’s capabilities on a remote system is a Python package called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lshell&lt;/code&gt;. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lshell&lt;/code&gt; is a user shell, like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bash&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zsh&lt;/code&gt;, but what makes it unique is that it can be configured to allow only a specific list of commands to be executed.&lt;/p&gt;

&lt;p&gt;This means that by creating a dedicated user for our AI agent and changing their shell to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lshell&lt;/code&gt;, we can control exactly which commands the AI agent is capable of executing on the remote server. If we wanted our agent to be able to retrieve system diagnostics, for example, we might permit a set of commands like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;netstat&lt;/code&gt;, for seeing which ports are open, or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;top&lt;/code&gt;, to see which processes are using the most resources.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[global]
logpath = /tmp/lshell.log
loglevel = 0

[default]
allowed = [ &apos;netstat&apos;, &apos;top&apos; ]
forbidden = [&apos;;&apos;, &apos;&amp;amp;&apos;, &apos;|&apos;, &apos;`&apos;, &apos;&amp;gt;&apos;, &apos;&amp;lt;&apos;, &apos;$(&apos;, &quot;${&quot;]
sudo_commands   = [  ]
warning_counter = 3
overssh = [ &apos;netstat&apos;, &apos;top&apos; ]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We can also combine these per-command restrictions at the shell level with the existing per-command privilege escalation restrictions provided by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo&lt;/code&gt;. For example, if we wanted our agent to retrieve the status of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;systemd&lt;/code&gt; units, as well as logs from the system journal, we might create a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/sudoers&lt;/code&gt; entry like so:&lt;/p&gt;
&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;agent     &lt;span class=&quot;nv&quot;&gt;ALL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=(&lt;/span&gt;ALL:ALL&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;    NOPASSWD: /usr/bin/systemctl status, NOPASSWD: /usr/bin/systemctl show, NOPASSWD: /usr/bin/systemctl list-unit-files, NOPASSWD: /usr/bin/journalctl
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Note that these will also require adding the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;systemctl&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;journalctl&lt;/code&gt; commands to the above &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lshell.conf&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;These kinds of configurations allow our AI agents to safely and securely retrieve information from a remote system without the risk of making unintended modifications while doing so.&lt;/p&gt;

&lt;p&gt;I hope you found this article useful, and it allows you to make your agentic environment a little more secure. :)&lt;/p&gt;
</content>
  </entry>
  
  <entry>
    <title>Archlinux Installation Guide</title>
    <link href="http://john-craig.github.io/archlinux-installation-guide.html"/>
    <updated>2026-02-01T00:00:00+00:00</updated>
    <id>http://john-craig.github.io/archlinux-installation-guide</id>
    <content type="html">&lt;p&gt;&lt;em&gt;Introduction&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;Today, I am going to be going walking through the installation process of Archlinux. This is my Archlinux installation tutorial. There are many Archlinux installation tutorials like it, but this one is mine.
This tutorial will be aimed at producing an Archlinux installation with a high degree of security, including features such as full disk encryption, a hardened kernel, and a detached boot partition. We will be using UEFI with GRUB for our boot setup, LVM on LUKS for encryption and logical volume management, and BTRFS for our filesystems. In a follow up video, I will also be demonstrating how to configure secure boot on installed operating system. It is left as an exercise for the viewer to decide whether these security features are necessary for their own use-case and threat model.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;Prerequisites&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;To follow this tutorial, you will need, naturally, a computer on which to install Archlinux, as well as at least two USB drives, or some other type of external drive. One of these drives will be used for your detached boot partition, while the other will be used for the installer. There are numerous instructions available for how to create an installer, and it will depend somewhat on your starting platform, so I won’t be covering it here.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;Drive Preparation&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;Once we’ve booted up the installer, our first step is to prepare our drives for installation. You can use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lsblk&lt;/code&gt; to view disk and partition information on the system. Since I am on a virtual machine, your disk names will probably look slightly different than mine. The important thing is to identify which disk is your device’s main drive, which is the installer drive, and which is the drive for the detached boot partition. It should be obvious based on the size of the respective drives which is the system’s main hard drive. As for the installer, you can tell based on whether it has any mount points listed under it.
In my case, my main drive is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/dev/vda&lt;/code&gt;, while my detached boot drive is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/dev/vdb&lt;/code&gt;. The next thing we are going to do is run the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shred&lt;/code&gt; command to overwrite each disk with random data:&lt;/p&gt;
  &lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;shred&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--random-source&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/dev/urandom &lt;span class=&quot;nt&quot;&gt;-n1&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--zero&lt;/span&gt; /dev/vda
&lt;span class=&quot;nb&quot;&gt;shred&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--random-source&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/dev/urandom &lt;span class=&quot;nt&quot;&gt;-n1&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--zero&lt;/span&gt; /dev/vdb
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;
  &lt;p&gt;This step is optional, and can possibly take a few hours if your drive is upwards of a terabyte in size, so I would recommend starting this command and letting it run overnight. There are two reasons for doing this. The first is to prevent any leftover data on the drives from being easily recovered. The second is to help obfuscate the drive’s contents once we complete our installation. You see, even though full disk encryption can protect data written to a disk, it does nothing to hide where that data is being written. This means that if we were to, for example, install Archlinux on a completely fresh disk, there would be a sort of “silhouette” created in the disk sectors, allowing an attacker to at least identify the presence of an encrypted filesystem stored on the disk. It may even be possible to identify what kind of filesystem and even what operating system distribution is installed, based upon file offsets. By filling the disk with random data befpre, it becomes almost impossible for an attacker to distinguish encrypted filesystem contents from noise.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;Partitioning the Disks&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;Next we’ll be partitioning the disks, using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fdisk&lt;/code&gt; utility. First up is the detached boot disk. We’ll press ‘g’ create a new GPT partition table, and then press &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;n&lt;/code&gt; to create a new partition. This will be our EFI System Partition, also called an ESP. We’ll keep the default number and starting sector, and respond with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;+256M&lt;/code&gt; for the end sector, to give ourselves just a bit of wiggle room for the future. Now that we’ve defined the partition, we want to press &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;t&lt;/code&gt; to set the partition type to EFI System, which is partition type 1. Once that’s done we’ll press &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;n&lt;/code&gt; again to make a second partition, again using the default partition number, and keeping the defaults for the start and end sectors so that the partition takes up all the remaining space on the drive. 
Now, in this tutorial I will be referring to the first partition as the EFI system partition or the ESP, and to the second partition as the boot partition. Some tutorials will not have a separate boot partition, and therefore refer to the EFI system partition and the boot partition interchangeably. But, in this tutorial, they are separate and distinct.
Now that we have these two partitions created, we’ll finally press &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;w&lt;/code&gt; to save our changes to the disk.
The partition for the main disk is not strictly necessary, since we’re using LUKS, but I will go through it anyways for consistency. This time we run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fdisk&lt;/code&gt; against the main disk. Again we use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;g&lt;/code&gt; to generate a GPT partition table, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;n&lt;/code&gt; to create a partition, and then accept all the defaults to have it fill up the entirety of the disk. There is no specific partition type for LUKS, so leaving it as a Linux filesystem partition is fine. Lastly we use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;w&lt;/code&gt; to save our changes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;fdisk /dev/vdb &lt;span class=&quot;c&quot;&gt;# Set up partitions of the detached boot USB&lt;/span&gt;
	g &lt;span class=&quot;c&quot;&gt;# New disk label&lt;/span&gt;
	n &lt;span class=&quot;c&quot;&gt;# New partition&lt;/span&gt;
	1 &lt;span class=&quot;c&quot;&gt;# Set partition number&lt;/span&gt;
	&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;default&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# start sector&lt;/span&gt;
	+256M &lt;span class=&quot;c&quot;&gt;# last sector&lt;/span&gt;
	
	t &lt;span class=&quot;c&quot;&gt;# Change partition type&lt;/span&gt;
	1 &lt;span class=&quot;c&quot;&gt;# Set to EFI system&lt;/span&gt;
	
	n &lt;span class=&quot;c&quot;&gt;# New partition&lt;/span&gt;
	2 &lt;span class=&quot;c&quot;&gt;# Set partition number&lt;/span&gt;
	&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;default&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# Start sector&lt;/span&gt;
	&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;default&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# Last sector&lt;/span&gt;
	
	w

fdisk /dev/vda &lt;span class=&quot;c&quot;&gt;# Set up partitions of main disk&lt;/span&gt;
	g &lt;span class=&quot;c&quot;&gt;# New disk label&lt;/span&gt;
	n &lt;span class=&quot;c&quot;&gt;# New partition&lt;/span&gt;
	1 &lt;span class=&quot;c&quot;&gt;# Set partition number&lt;/span&gt;
	&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;defualt&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# Start sector&lt;/span&gt;
	&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;default&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# Last sector&lt;/span&gt;
	w &lt;span class=&quot;c&quot;&gt;# Write&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Boot Drive Setup&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;Now we’ll set up the filesystems on the detached boot drive. The filesystem for the EFI system partition must be FAT32. We can create it with the command, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mkfs -t fat -F 32&lt;/code&gt;, followed by the path to the partition, in my case &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/dev/vdb1&lt;/code&gt;. The filesystem type of the boot is not as critical, but I typically use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ext4&lt;/code&gt; since it is broadly supported. For this the command is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mkfs -t ext4 /dev/vdb2&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mkfs &lt;span class=&quot;nt&quot;&gt;-t&lt;/span&gt; fat &lt;span class=&quot;nt&quot;&gt;-F&lt;/span&gt; 32 /dev/vdb1
mkfs &lt;span class=&quot;nt&quot;&gt;-t&lt;/span&gt; ext4 /dev/vdb2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Disk Encryption Setup&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;Now that we’ve created the filesystems for our detached boot drive, we can begin setting up disk encryption on our main drive. The first thing we want to do is mount the boot partition. Again, this is the boot partition, not the EFI system partition. In my case, it is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/dev/vdb2&lt;/code&gt;. So I will mount it with the command &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mount /dev/vdb2 /mnt&lt;/code&gt;. Once this partition has been mounted, I will use the following command to create a 16-megabyte file inside the boot partition.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mount /dev/vdb2 /mnt0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;This file will be used the LUKS header for our main disk. Without going into too much detail about the design of LUKS– which stands for Linux Unified Key Setup– the header file is a critical part of full disk encryption. If the header file is lost, it is impossible to decrypt the drive; all the data is as good as lost. Additionally, the header file is also the only part of a LUKS-encrypted disk that is distinguishable from random data.
Because of this, we are going to keep the header file stored on our detached boot drive from the moment it is created. This way even in the event of unexpected power failure during the installation process, it won’t be lost. I do, however, strongly recommend creating a backup of the entire boot drive when the process is complete, just in case that drive is ever lost or fails.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;dd &lt;/span&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/dev/zero &lt;span class=&quot;nv&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/mnt/header.img &lt;span class=&quot;nv&quot;&gt;bs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;16M &lt;span class=&quot;nv&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;Now, in order to actually set up LUKS, we will use this command: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cryptsetup luksformat --header /mnt/header.img --cipher serpent-xts-plain64 --key-size 512 --hash whirlpool /dev/vda1&lt;/code&gt;. Let’s break this down slightly. Here we are specifying two non-default algorithms, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;serpent-xts-plain64&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;whirlpool&lt;/code&gt;, for our disk encryption and hashing respectively. Both of these algorithms are cryptographically more resilient than their standardized counterparts, Rijndael and SHA-256, but they are notably slower in terms of performance. Again, I would urge the viewer to consider for themselves whether this is the right choice.
Once we run this command we will be prompted for a password. For the purposes of this tutorial, I am going to use the password, ‘password’. I’ll leave it up to you to come up with your own strong password and store it safely. Personally, my method is to get a dictionary and roll some dice to select a page and then a word on page, and repeat that until I have at least five or six words, and I write them down on at least two different index cards.&lt;/p&gt;
  &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cryptsetup luksFormat --header /mnt/header.img --cipher serpent-xts-plain64 --key-size 512 --hash whirlpool /dev/vda1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;
  &lt;p&gt;Once the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;luksFormat&lt;/code&gt; command is complete, we now have to unlock the encrypted disk. To do this we will use the command &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cryptsetup open --header /mnt/header.img /dev/vda1 crypt0&lt;/code&gt;. Note that we have to specify the header here; if you don’t use the header from the previous step, this command will not work. Here, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;crypt0&lt;/code&gt; is the name of the unlocked device. That choice is just a convention, in principle you could use anything.&lt;/p&gt;
  &lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cryptsetup open &lt;span class=&quot;nt&quot;&gt;--header&lt;/span&gt; /mnt/header.img /dev/vda1 crypt0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;If run the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lsblk&lt;/code&gt; command again, we will see a very different picture from the first time.&lt;/p&gt;
  &lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;lsblk
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;LVM Setup&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;Now we can start setting up LVM. LVM, or Logical Volume Manager, is a device mapper framework that enables the creation of logical volumes. Logical volumes are useful because easily be resized, and they can span over multiple physical devices. Generally, they’re a good choice for future-proofing your installation, and they can save you a lot time and headaches down the line.
To set up LVM, we’ll start by running the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pvcreate&lt;/code&gt; command against our unlocked device, which available at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/dev/mapper/crypt0&lt;/code&gt;. This marks the device as a physical volume. Next we’ll use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vgcreate&lt;/code&gt; to create a volume group on that physical volume named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;volgroup0&lt;/code&gt;. Again, this named is just a convention and in practice you can call it whatever you’d like.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;pvcreate /dev/mapper/crypt0
vgcreate volgroup0 /dev/mapper/crypt0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;Once we’ve created our volume group, we’ll create two logical volumes inside of it with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lvcreate&lt;/code&gt; command. The first one will be our swap volume, which I’ll name &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lv_swap&lt;/code&gt;. The size of this is going to depend on the amount of free space on your hard drive and the amount of RAM on your system. I tend to create my swap volumes of about equal size to my RAM, but this tutorial I will be keeping it for 4GB.
Next we’ll create our root volume. This is where almost our entire installation will live. For this command I’ll use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-l 100%FREE&lt;/code&gt; option to take up all the remaining space in the volume group. Note that this is different from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-L&lt;/code&gt; flag used in the previous command.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;lvcreate -L 4G volgroup0 -n lv_swap
lvcreate -l 100%FREE volgroup0 -n lv_root
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Main Disk Filesystem Setup&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;Now we’ll be setting up the filesystems on our logical volumes. For the swap volume, this is as simple as running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mkswap&lt;/code&gt; on the volume, which is available at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/dev/volgroup0/lv_swap&lt;/code&gt;.
For our root volume, we will be using a BTRFS filesystem, meaning the setup is slightly more involved. BTRFS, also pronounced “better FS” and “butter FS”, is a copy-on-write filesystem. One if its main features is the ability to define different subvolumes and take snapshots of them separately. Unlike LVM logical volumes, these subvolumes don’t need to have a defined size, allowing them to grow organically.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mkswap /dev/volgroup0/lv_swap
mkfs &lt;span class=&quot;nt&quot;&gt;-t&lt;/span&gt; btrfs /dev/volgroup0/lv_root
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;Once we use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mkfs&lt;/code&gt; command to create the BTRFS filesystem on our root volume, we can start setting up our subvolumes. The first step is to actually mount the filesystem. Since we’re done with the boot partition for now, we can unmount that with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;umount /mnt&lt;/code&gt;, and then use that as the mount point for the root volume.&lt;/p&gt;
  &lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;umount /mnt
mount /dev/volgroup0/lv_root /mnt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;After the root volume is mounted, we’ll create our subvolumes with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;btrfs subvolume create&lt;/code&gt; command. Chosing which directories should be subvolumes is going to be somewhat dependent upon your use-case, but I usually have one subvolume for my root directory, and another subvolume for my home directory. Because I plan to install the Nix daemon on this system in a later video in this series, I will also create a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/nix&lt;/code&gt; subvolume.
```sh&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;btrfs subvolume create /mnt/root
btrfs subvolume create /mnt/nix
btrfs subvolume create /mnt/home&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;gt;Once all the subvolumes are created, we now have to unmount the filesystem. Next we can mount the root subvolume by passing the `subvol=root` option to the mount command. We&apos;ll also add the `compress=zstd` option to use real-time compression.

```sh
umount /mnt
mount -o compress=zstd,subvol=root /dev/volgroup0/lv_root /mnt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;This marks the first point where we are able to interact with what will become our new Archlinux installation. It is completely empty at a moment, however, so we’ll need to create some directories with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mkdir&lt;/code&gt; command. An easy way to create multiple directories at once is with this syntax.&lt;/p&gt;
  &lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;mkdir&lt;/span&gt; /mnt/&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;boot,home,nix&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;Now we can mount the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/home&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/nix&lt;/code&gt; subvolumes onto their mount points, using the same &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;subvol&lt;/code&gt; option as before. We can also mount our boot partition, and swap on our swap volume with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;swapon&lt;/code&gt; command.&lt;/p&gt;
  &lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mount &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;compress&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;zstd,subvol&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;home /dev/volgroup0/lv_root /mnt/home
mount &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;compress&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;zstd,subvol&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;nix /dev/volgroup0/lv_root /mnt/nix
mount /dev/vdb2 /mnt/boot
swapon /dev/volgroup0/lv_swap
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;
  &lt;p&gt;For the EFI system partition, we’ll create another mount point under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/mnt/boot&lt;/code&gt;, and mount it there.&lt;/p&gt;
  &lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;mkdir&lt;/span&gt; /mnt/boot/esp
mount /dev/vdb1 /mnt/boot/esp
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;
  &lt;p&gt;Now let’s run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lsblk&lt;/code&gt; once more and see the full disk layout. This is the skeleton of our Archlinux installation. Now, it’s time to start putting some meat on those bones.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;System Installation&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;To actually perform the system installation, we’ll use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pacstrap&lt;/code&gt; command. This command sets up the bulk of our installation’s directory structure, configuration files, and system packages. We invoke it by specifying the directory where our system is mounted, in our case &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/mnt&lt;/code&gt;, and then a list of the packages we want installed; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pacstrap&lt;/code&gt; does the rest.
For this tutorial, we’ll be using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;linux-hardened&lt;/code&gt;, which is a package containing the Linux kernel compiled with a number of security-focused patches applied. To go along with it, we’ll also be installing the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;linux-hardened-headers&lt;/code&gt; package, and the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;linux-firmware&lt;/code&gt; which contains firmware for a number of common hardware devices. I will also add the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;base-devel&lt;/code&gt; package, which contains the C compiler collection and a number of other libraries used for software development, and the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;btrfs-progs&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lvm2&lt;/code&gt; packages, which contain utilities for interacting with BTRFS and LVM respectively, and the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nano&lt;/code&gt; packages, which we will need later in this tutorial. Finally, we’ll also add the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;grub&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;efibootmgr&lt;/code&gt; package, as these will be needed for our boot process.&lt;/p&gt;
  &lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;pacstrap &lt;span class=&quot;nt&quot;&gt;-K&lt;/span&gt; /mnt base linux-hardened linux-hardened-headers linux-firmware base-devel btrfs-progs lvm2 git nano grub efibootmgr
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;
  &lt;p&gt;Once this command completes, our next step is to use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;genfstab&lt;/code&gt; command to generate the filesystem table for our installation. This is required to recreate the mounting layout that we have set up manually during this installation during the next boot.&lt;/p&gt;
  &lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;genfstab &lt;span class=&quot;nt&quot;&gt;-U&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; /mnt/etc/fstab
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;Chrooting into the System&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;Now that the bootstrapping process is completed and we’ve generated the FS table, we can actually enter our newly-installed system using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;arch-chroot&lt;/code&gt; command. This command, which is based on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chroot&lt;/code&gt;, or “change root”, changes the apparent root directory of a process. Here we are changing our root from the installer’s root directory to our installation’s root directory, giving us an environment similar to if we have booted from the installation itself.&lt;/p&gt;
  &lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;arch-chroot /mnt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;
  &lt;p&gt;Once we’ve changed root, we can begin making changes to the installed system directly. The first change we’ll want to make is setting up the time and locale. We can set the local time zone of the installation by symlinking the appropriate local time file to the path &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/localtime&lt;/code&gt;. In my case, I’ll be symlinking the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;America/New_York&lt;/code&gt; file, since that’s closest to my local time zone.&lt;/p&gt;
  &lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;ln&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-sf&lt;/span&gt; /usr/share/zoneinfo/America/New_York /etc/localtime
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;
  &lt;p&gt;Afterwards we want to synchronize the system’s clock to the hardware clock. We can do this with the command &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hwclock --systohc&lt;/code&gt;.&lt;/p&gt;
  &lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;hwclock &lt;span class=&quot;nt&quot;&gt;--systohc&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;
  &lt;p&gt;Next we want to generate the locale files with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;locale-gen&lt;/code&gt; command. We can also set our language by populating the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/locale.conf&lt;/code&gt;. In my case I want the system language to be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;en_US.UTF-8&lt;/code&gt;, so I will simply echo that into the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;local.conf&lt;/code&gt; file.&lt;/p&gt;
  &lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;locale-gen
&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;LANG=en_US.UTF-8&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; /etc/locale.conf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;
  &lt;p&gt;Finally, we want to configure networking. First we’ll set the system’s hostname by echoing the desired name into the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/hostname&lt;/code&gt; file. I’ll use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;virtual-machine&lt;/code&gt; for mine. Next we’ll enable two SystemD services, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;systemd-networkd&lt;/code&gt; for network management and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;systemd-resolved&lt;/code&gt; for DNS resolution management. The last step is symlinking one of two files to the into the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/systemd/network&lt;/code&gt; directory depending on type of network connection. For ethernet, we’ll symlink from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/usr/lib/systemd/network/89-ethernet.network.example&lt;/code&gt; to the path &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/systemd/network/89-ethernet.network&lt;/code&gt;. For wireless, we’ll symlink &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/usr/lib/systemd/network/80-wifi-station.network.example&lt;/code&gt; to the path &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/systemd/network/80-wifi-station.network&lt;/code&gt;.
```sh
echo ‘virtual-machine’ &amp;gt; /etc/hostname
systemctl enable systemd-networkd
systemctl enable systemd-resolved&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1 id=&quot;for-ethernet&quot;&gt;For ethernet&lt;/h1&gt;
&lt;p&gt;ln -s /usr/lib/systemd/network/89-ethernet.network.example /etc/systemd/network/89-ethernet.network&lt;/p&gt;
&lt;h1 id=&quot;for-wifi&quot;&gt;For wifi&lt;/h1&gt;
&lt;p&gt;ln -s /usr/lib/systemd/network/80-wifi-station.network.example /etc/systemd/network/80-wifi-station.network&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
*Bootloader Installation*
&amp;gt; Because of our detached boot drive, and particularly because of our detached LUKS header file, the boot process of our system will be somewhat unorthodox, and even the standard boot process is not trivial, so let&apos;s take a brief moment for review.
&amp;gt; When the machine is turned on, the very first program to run in the firmware. In our case, this is UEFI, or the Unified Extensible Firmware Interface. The firmware then starts the system&apos;s bootloader. In UEFI, the bootloader is an EFI application found in the EFI system partition. Our configuration will use GRUB, the Grand Unified Bootloader, as the bootloader. Next, the bootloader loads the kernel into memory, followed by the initial RAM-based filesystem, or `initramfs`. The `initramfs` provides any files or kernel modules that required to proceed to the next stage of the boot process. It is at this point, usually, that the main disk of the system is unlocked, if it is encrypted. Once the main disk is decrypted and the root filesystem is mounted, the system&apos;s initialization proceeds from there.
&amp;gt; For our system to work need to make sure that our `initramfs` has access to the header file so it can unlock the main disk of our operating system. On Archlinux, the `initramfs` is typically generated with a utility called `mkinitcpio`. This utility can be given user-defined hooks to embed additional packages and functions into the `initramfs`, which is how we&apos;ll be adding the ability for it to work with a detached LUKS header.
&amp;gt; To obtain the hook used in this process, we would typically install it from the Archlinux User Repository, or AUR, using an AUR helper like `yay` or `paru`. We don&apos;t have those available right now, though, so we&apos;ll just clone and build the package ourselves manually.
&amp;gt; When installing packages or software outside of Archlinux&apos;s package management, I usually use the `/opt` directory. In practice, however, the location you chose for this process doesn&apos;t matter. After changing to our desired directory, we&apos;ll use `git` to clone the repository containing the hook&apos;s source. Unfortunately, there&apos;s no way around just typing out this URL manually, so be mindful of spelling mistakes.
```sh
cd /opt
git clone https://github.com/john-craig/mkinitcpio-encrypt-detached-header
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;blockquote&gt;
  &lt;p&gt;If we try to run the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;makepkg&lt;/code&gt; command to build the package right away, we’ll encounter an error. This is because running the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;makepkg&lt;/code&gt; command as the root user runs the risk of a malicious package gaining arbitrary code execution. To build the package safely, we need to run the command as a non-root user. We have a few non-root users available on the system already, but the simplest one is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nobody&lt;/code&gt;, which represents a user with the least possible privileges. We’ll change ownership of this directory to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nobody&lt;/code&gt; with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chown&lt;/code&gt; command, then we can run the build command with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;runuser -unobody&lt;/code&gt;, followed by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;makepkg&lt;/code&gt;.&lt;/p&gt;
  &lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;chown&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-R&lt;/span&gt; nobody &lt;span class=&quot;nb&quot;&gt;.&lt;/span&gt;
runuser &lt;span class=&quot;nt&quot;&gt;-unobody&lt;/span&gt; makepkg &lt;span class=&quot;nt&quot;&gt;-si&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;
  &lt;p&gt;When the build completes, we’ll have our package archive built and ready to install. We can install a package directly from a package archive with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pacman -U&lt;/code&gt;.&lt;/p&gt;
  &lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;pacman &lt;span class=&quot;nt&quot;&gt;-U&lt;/span&gt; mkinitcpio-encrypt-detached-header&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;.pkg.tar.zst
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;
  &lt;p&gt;Not that we have our detached header hook package installed, we can edit the configuration file of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mkinitcpio&lt;/code&gt; to utilize it with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nano&lt;/code&gt;. Here we’ll edit two lines. First, we’ll add the path to the header file to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FILES&lt;/code&gt; array, so that it gets embedded into the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;initramfs&lt;/code&gt;. Then we’ll modify the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HOOKS&lt;/code&gt; file. We can get rid of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;systemd&lt;/code&gt; and replace it with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;udev&lt;/code&gt;. Then add our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;encrypt-dh&lt;/code&gt; hook after the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;block&lt;/code&gt; device hook. After that, we’ll also need to add the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lvm2&lt;/code&gt; hook. We can save our changes with Ctrl+O, and then exit with Ctrl+X.&lt;/p&gt;
  &lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;nano /etc/mkinitcpio.conf
	&lt;span class=&quot;nv&quot;&gt;FILES&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=(&lt;/span&gt;/boot/header.img&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
	...
	&lt;span class=&quot;nv&quot;&gt;HOOKS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=(&lt;/span&gt;base udev autodetect microcode modconf kms keyboard keymap consolefont block encrypt-dh lvm2 filesystems fsck&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;
  &lt;p&gt;That takes care of the configuration for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mkinitcpio&lt;/code&gt;, but we also have to manipulate the kernel parameters passed to the kernel when it is first loaded into memory by the bootloader. These kernel parameters are then passed to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;encrypt-dh&lt;/code&gt; hook so that it can function correctly. The kernel parameters are controlled by GRUB, so to change them we’ll have to modify the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/default/grub&lt;/code&gt; file.
However, one of the things we need to specify with these kernel parameters is the path to our encrypted block device on the main disk. Up until now, we’ve been interacting with this block device using the path &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/dev/vda1&lt;/code&gt;. However, this path can potentially change between reboots when there are multiple disks on the system. To ensure our path is consistent, we can use the symlinks under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/dev/disk/by-partuuid&lt;/code&gt;, because these are dependent upon the partition UUID of the block device, which does not change. To get the partition UUID, we can use the command &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;blkid&lt;/code&gt; on the block device path. You will notice, however, that this value is very long, and would be difficult type. To make things easier on ourselves, we can just append the partition UUID to the end of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/default/grub&lt;/code&gt; file, like so. Notice the double right-carats. That is important. Two carats appends, a single carat overwrites.&lt;/p&gt;
  &lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;blkid /dev/vda1
blkid &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; value /dev/vda1 &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; /etc/default/grub
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;
  &lt;p&gt;Now that we’ve appended the partition UUID of our primary disk to the end of the file, we can edit it. Here, we want to change two lines. The first is the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GRUB_CMDLINE_LINUX_DEFAULT&lt;/code&gt; line, which tells GRUB which kernel parameters to pass. 
We have to add a few things here: first, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cryptdevice&lt;/code&gt; parameter. This is where our block device path for the primary disk goes. Once we’re ready to type the UUID, we can navigate to the bottom of the file, then cut the UUID we appended earlier with Ctrl+K, then go back up to the kernel parameter line and paste it with Ctrl+U. Afterwards we’ll write the named that we wanted the unlocked device to have.
Next we’ll define the parameter for the detached header. This is the path of the file inside of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;initramfs&lt;/code&gt;, so we have to specify that by putting &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rootfs&lt;/code&gt; first, then the path. Next we’ll place the path of the root volume, which for us is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/dev/volgroup0/lv_root&lt;/code&gt;, and the swap volume, which is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/dev/volgroup/lv_swap&lt;/code&gt;. Lastly we want to uncomment this line for enabling the GRUB cryptodisk feature.&lt;/p&gt;
  &lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;nano /etc/default/grub
	&lt;span class=&quot;nv&quot;&gt;GRUB_CMDLINE_LINUX_DEFAULT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;loglevel3 cryptdevice=/dev/disk/by-partuuid/&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PARTUUID&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;:crypt0 cryptheader=rootfs:/boot/header.img root=/dev/volgroup0/lv_root resume=/dev/volgroup0/lv_swap quiet&quot;&lt;/span&gt;
	...
	&lt;span class=&quot;nv&quot;&gt;GRUB_ENABLE_CRYPTODISK&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;y
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;
  &lt;p&gt;Now that all the configuration files for GRUB and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mkinitcpio&lt;/code&gt; have been prepared, we can actually set them up. First we run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mkinitcpio -P&lt;/code&gt; to generate the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;initramfs&lt;/code&gt;. When this is done, we install grub with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;grub-install --target=x86_64-efi --efi-directory=/boot/esp --removable&lt;/code&gt;. Lastly we generate the GRUB config with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;grub-mkconfig -o /boot/grub/grub.cfg&lt;/code&gt;.&lt;/p&gt;
  &lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mkinitcpio &lt;span class=&quot;nt&quot;&gt;-P&lt;/span&gt;
grub-install &lt;span class=&quot;nt&quot;&gt;--target&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;x86_64-efi &lt;span class=&quot;nt&quot;&gt;--efi-directory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/boot/esp &lt;span class=&quot;nt&quot;&gt;--removable&lt;/span&gt;
grub-mkconfig &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; /boot/grub/grub.cfg
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;Finishing Touches&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;At this point, are system is fully functional. The last thing we need to do before we reboot is run the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;passwd&lt;/code&gt; command to set the password of our root user, otherwise we won’t be able to log in once the system comes back up. Additionally, this would be a good time to use BTRFS to take a snapshot of the initial state of the filesystem. For this we’ll use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;btrfs subvolume snapshot / /.snapshot-initial&lt;/code&gt;.
```sh
passwd&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;btrfs subvolume snapshot / /.snapshot-initial&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&amp;gt; Finally, we use the `exit` command to exit our chroot, and then `poweroff` to poweroff the system. This gives us time to unplug our installer USB and boot into our new Archlinux installation.
```sh
exit
poweroff
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Post-Installation Steps&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;Once we’ve been able to successfully boot into our newly-installed system for the first time, there are a few final post-installation steps. Namely, we want to create a user for ourselves so that we are not constantly logged in to root. We can do this with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;useradd&lt;/code&gt; command, passing the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-m&lt;/code&gt; flag to create a home directory for the new user. To keep things simple, my user’s username will be &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;operator&lt;/code&gt;.
Once the user is created we want to set its password so we can login. We can do this with the command &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;passwd&lt;/code&gt;, followed by the username. I’ll be using the password &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;operator&lt;/code&gt;. Next, we’ll want to give our user &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo&lt;/code&gt; privileges. We can do this with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;visudo&lt;/code&gt; command, which is used to securely edit the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/etc/sudoers&lt;/code&gt; file. I’ll also set &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;EDITOR=nano&lt;/code&gt; when running the command to tell it to use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nano&lt;/code&gt; editor.
In this file, we need want to add the following line for our user. This will allow the user to run any command as root after providing their password.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;useradd &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; operator
passwd operator      &lt;span class=&quot;c&quot;&gt;# Set password to &apos;operator&apos;&lt;/span&gt;

&lt;span class=&quot;nv&quot;&gt;EDITOR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;nano visudo
	&lt;span class=&quot;c&quot;&gt;## User privilege specification&lt;/span&gt;
	operator &lt;span class=&quot;nv&quot;&gt;ALL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=(&lt;/span&gt;ALL:ALL&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; ALL
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;Now, in theory, this is the last thing we have to do. However, practically speaking, this tutorial isn’t complete until we have some kind of Arch User Repository helper installer. I typically use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;paru&lt;/code&gt;.
To install &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;paru&lt;/code&gt;, we can first switch to our newly-created user with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;su&lt;/code&gt; command. Next, we want to create a directory to clone the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;paru&lt;/code&gt; repository into. Like the the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mkinitpcio&lt;/code&gt; hook from earlier, this can technically be anywhere, but I like to use the path &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.local/opt&lt;/code&gt; in the user’s home directory. First we have to create this directory with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mkdir -p&lt;/code&gt;, then we can change directory to it and clone the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;paru&lt;/code&gt; repository with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git&lt;/code&gt;.
Now all that’s left to do is enter the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;paru&lt;/code&gt; directory and run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;makepkg -si&lt;/code&gt;. This command will build &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;paru&lt;/code&gt;, and then finally prompt us for our password to complete the installation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;mkdir&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; ~/.local/opt
&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; ~/.local/opt
git clone https://aur.archlinux.org/paru.git
&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;paru
makepkg &lt;span class=&quot;nt&quot;&gt;-si&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;And there you have it. Archlinux, fully installed and ready to use.&lt;/p&gt;
&lt;/blockquote&gt;

</content>
  </entry>
  
  <entry>
    <title>Setting up a Binary Cache from an Alternate /nix/store</title>
    <link href="http://john-craig.github.io/nix-binary-cache-alt-store.html"/>
    <updated>2025-11-18T00:00:00+00:00</updated>
    <id>http://john-craig.github.io/nix-binary-cache-alt-store</id>
    <content type="html">&lt;h1 id=&quot;setting-up-a-binary-cache-from-a-secondary-nix-store&quot;&gt;Setting up a Binary Cache from a Secondary Nix Store&lt;/h1&gt;
&lt;p&gt;In this article we will learn how to set up Nix binary cache from a Nix store not located at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/nix/store&lt;/code&gt;. This will include serving packages from this binary cache over HTTP(S) and pushing to the binary cache over SSH.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Set This Up?&lt;/strong&gt;
The main reasons you may want to run a binary cache from a secondary Nix store are the following:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;keep the packages available in the cache isolated from the packages used by the system running the binary cache&lt;/li&gt;
  &lt;li&gt;store the cache on an external storage medium, or some other mount point separate from the one where the regular &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/nix&lt;/code&gt; directory is mounted&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;serving-the-binary-cache-over-http&quot;&gt;Serving the Binary Cache over HTTP&lt;/h2&gt;
&lt;p&gt;There exist a wide variety of projects aimed at serving binary caches over HTTP. For the purposes of this tutorial, we will be using &lt;a href=&quot;https://github.com/aristanetworks/nix-serve-ng&quot;&gt;nix-serve-ng&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Our approach will be to use mount namespaces to make it appear to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nix-serve-ng&lt;/code&gt; process as though the non-standard &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/nix/store&lt;/code&gt; we want to use is the same as the system’s root. For example, if we wanted to serve a Nix store from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/srv/cache/nix/store&lt;/code&gt;, we would use the following command:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;exec unshare --mount --propagation private bash -euxc &quot;
    mount --rbind /srv/cache/nix /nix
    mount --make-rslave /nix
    exec nix-serve --listen 0.0.0.0:5001
&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This command uses &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;unshare&lt;/code&gt; to create a mount namespace and then executes a series of commands within that namespace. These commands bind-mount the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/srv/cache/nix&lt;/code&gt; directory to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/nix&lt;/code&gt;. Then, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nix-serve&lt;/code&gt; process is started. When this process interacts with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/nix/store&lt;/code&gt; directory to serve packages, it will really be interacting with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/srv/cache/nix/store&lt;/code&gt; directory outside of the mount namespace.&lt;/p&gt;

&lt;p&gt;A full configuration with a SystemD service would look like so:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;systemd.services.nix-cache = {
    enable = true;
    description = &quot;HTTP Nix binary cache&quot;;
    wantedBy = [ &quot;multi-user.target&quot; ];
    after = [ &quot;network.target&quot; ];

    path = [ 
        pkgs.nix-serve-ng
        pkgs.nix
        pkgs.util-linux
        pkgs.coreutils
        pkgs.bash 
    ];

    preStart = &apos;&apos;
        ${pkgs.nix}/bin/nix copy --to /srv/cache ${pkgs.nix-serve-ng}
        ${pkgs.nix}/bin/nix copy --to /srv/cache ${pkgs.util-linux}
        ${pkgs.nix}/bin/nix copy --to /srv/cache ${pkgs.bash}
        ${pkgs.nix}/bin/nix copy --to /srv/cache ${pkgs.coreutils}
    &apos;&apos;;
    script = &apos;&apos;
        exec unshare --mount --propagation private bash -euxc &quot;
            mount --rbind /srv/cache/nix /nix
            mount --make-rslave /nix
            exec nix-serve --listen 0.0.0.0:5001
        &quot;
    &apos;&apos;;
    environment = {
        NIX_SECRET_KEY_FILE = &quot;${config.sops.secrets.&quot;nix/nix-serve/cache-key&quot;.path}&quot;;
    };
};
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This example also sets the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NIX_SECRET_KEY_FILE&lt;/code&gt; environment variable to a package signing key populated by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sops-nix&lt;/code&gt;. If you don’t care about signing the packages in your cache, you can safely ignore this variable.&lt;/p&gt;

&lt;h2 id=&quot;pushing-packages-to-the-binary-cache&quot;&gt;Pushing Packages to the Binary Cache&lt;/h2&gt;
&lt;p&gt;In order to push the packages to our Nix store from another host, we can utilize the existing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nix copy&lt;/code&gt; command like so:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;nix copy --substitute-on-destination \
    --to ssh://cacher@$cacheServer?remote-store=local?root=/srv/cache \
    /nix/store/000j6dh3wl59wijhl99np8bph69m0wpz-python3.13-pyqrcode-1.2.1.drv
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To push all the packages used by the current system, we can use:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;nix copy --substitute-on-destination \
    --to ssh://cacher@$cacheServer?remote-store=local?root=/srv/cache \
    /run/current-system
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Ensure that the remote user used for pushing to cache is a trusted user for the Nix daemon of the cache server and has privileges to read and write the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/srv/cache/nix&lt;/code&gt; directory.&lt;/p&gt;

&lt;h2 id=&quot;cleaning-up-the-binary-cache&quot;&gt;Cleaning Up the Binary Cache&lt;/h2&gt;
&lt;p&gt;Lastly, we will want to occasionally prune packages from this binary cache. Although we can use the existing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nix-collect-garbage&lt;/code&gt; with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--store&lt;/code&gt; option, we need to be careful because none of the packages we have pushed to the store have been added to the garbage collector’s roots yet, meaning it will treat everything as garbage and remove all the packages.&lt;/p&gt;

&lt;p&gt;To add the packages to the roots, all we need to do is created a symlink from the store path of the package we want to preserve to the directory &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/srv/cache/nix/var/nix/gcroots&lt;/code&gt;. For example:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ln -s /srv/cache/store/000j6dh3wl59wijhl99np8bph69m0wpz-python3.13-pyqrcode-1.2.1.drv /srv/cache/nix/var/nix/gcroots/python3.13-pyqrcode-1.2.1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then running the command &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nix-collect-garbage -d --store /srv/cache&lt;/code&gt; will not remove the symlinked package or any of its dependencies.&lt;/p&gt;
</content>
  </entry>
  
</feed>
