<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>Parker Jones Dev Blog - homelab</title>
    <subtitle>Dev Blog of Parker Jones</subtitle>
    <link rel="self" type="application/atom+xml" href="https://parkerjones.dev/tags/homelab/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://parkerjones.dev"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-06-26T00:00:00+00:00</updated>
    <id>https://parkerjones.dev/tags/homelab/atom.xml</id>
    <entry xml:lang="en">
        <title>Nix Fleet, Part 4: Installing a Helios64 NAS from Bare Metal with nixos-anywhere and disko</title>
        <published>2026-06-26T00:00:00+00:00</published>
        <updated>2026-06-26T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://parkerjones.dev/posts/nix-fleet-helios64-disko/"/>
        <id>https://parkerjones.dev/posts/nix-fleet-helios64-disko/</id>
        
        <content type="html" xml:base="https://parkerjones.dev/posts/nix-fleet-helios64-disko/">&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Nix Fleet, part 4.&lt;&#x2F;strong&gt; The series so far: &lt;a href=&quot;&#x2F;posts&#x2F;nix-fleet-one-flake&#x2F;&quot;&gt;the one-flake architecture&lt;&#x2F;a&gt;, &lt;a href=&quot;&#x2F;posts&#x2F;nix-fleet-remote-builders&#x2F;&quot;&gt;remote builders&lt;&#x2F;a&gt;, and &lt;a href=&quot;&#x2F;posts&#x2F;nix-fleet-agenix-secrets&#x2F;&quot;&gt;fleet-wide secrets&lt;&#x2F;a&gt;. This part is where it all gets pointed at bare metal — a 5-bay ARM NAS, installed declaratively and reproducibly.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;The &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;wiki.kobol.io&#x2F;helios64&#x2F;intro&#x2F;&quot;&gt;Kobol Helios64&lt;&#x2F;a&gt; is a 5-bay NAS built on a Rockchip RK3399 (&lt;code&gt;aarch64&lt;&#x2F;code&gt;, 4–8 GB RAM). It&#x27;s a lovely, slightly cantankerous little ARM board, and it&#x27;s the storage backbone of my homelab. The goal for this host was the same as every other machine in the fleet: &lt;strong&gt;I should be able to reinstall it from the flake and get an identical result, disks and all.&lt;&#x2F;strong&gt; No clicking through an installer, no hand-partitioning, no &quot;what did I configure last time.&quot;&lt;&#x2F;p&gt;
&lt;p&gt;That&#x27;s what &lt;code&gt;disko&lt;&#x2F;code&gt; and &lt;code&gt;nixos-anywhere&lt;&#x2F;code&gt; make possible.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-two-tools&quot;&gt;The two tools&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;nix-community&#x2F;disko&quot;&gt;disko&lt;&#x2F;a&gt;&lt;&#x2F;strong&gt; turns disk layout into declarative Nix. Instead of running &lt;code&gt;fdisk&lt;&#x2F;code&gt; and &lt;code&gt;mkfs&lt;&#x2F;code&gt; by hand, you &lt;em&gt;describe&lt;&#x2F;em&gt; the partitions, filesystems, and pools you want, and disko makes the disks match. Partitioning becomes config you can review, version, and re-apply.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;numtide&#x2F;nixos-anywhere&quot;&gt;nixos-anywhere&lt;&#x2F;a&gt;&lt;&#x2F;strong&gt; installs a NixOS flake onto a remote machine over SSH — it can kexec into an installer, run disko to lay out the disks, and install the full system, all from my workstation.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Together they collapse &quot;install a NAS&quot; into one command:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code data-lang=&quot;bash&quot;&gt;nix run github:numtide&#x2F;nixos-anywhere -- --flake .#helios64 &amp;lt;ssh-host&amp;gt;
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That partitions the drives per the disko spec, installs the &lt;code&gt;helios64&lt;&#x2F;code&gt; host config from my flake, and reboots into a running NixOS. The disk layout is no longer a one-time manual act I&#x27;d have to remember — it&#x27;s part of the host definition.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-arm-specific-bits&quot;&gt;The ARM-specific bits&lt;&#x2F;h2&gt;
&lt;p&gt;An ARM SBC NAS has a few wrinkles a generic x86 box doesn&#x27;t, and the host config names them explicitly:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code data-lang=&quot;nix&quot;&gt;# hosts&#x2F;nixos&#x2F;helios64.nix
{
  networking.hostName = &amp;quot;helios64&amp;quot;;

  # The RK3399 needs its device tree to boot correctly
  hardware.deviceTree.enable = true;
  hardware.deviceTree.name = &amp;quot;rockchip&#x2F;rk3399-kobol-helios64.dtb&amp;quot;;

  services.openssh.enable = true;
  system.stateVersion = &amp;quot;24.11&amp;quot;;
}
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;strong&gt;device tree&lt;&#x2F;strong&gt; is the load-bearing line — without &lt;code&gt;rk3399-kobol-helios64.dtb&lt;&#x2F;code&gt;, the board doesn&#x27;t come up right. The boot chain itself is U-Boot&#x2F;Tow-Boot on SD&#x2F;eMMC with the NixOS root on SATA. And because this is &lt;code&gt;aarch64-linux&lt;&#x2F;code&gt;, the system image builds on my x86 box via the emulation I set up in &lt;a href=&quot;&#x2F;posts&#x2F;nix-fleet-remote-builders&#x2F;&quot;&gt;Part 2&lt;&#x2F;a&gt; — I never need a native ARM build host just for the NAS.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;A note on honesty: the committed host config is intentionally minimal right now (it even evaluates as a container in CI to bypass boot&#x2F;filesystem asserts). Storage and services are phased work. So treat this post as the &lt;em&gt;design and install method&lt;&#x2F;em&gt; I&#x27;m executing against, not a claim that every service below is already live. The series documents the build as it happens.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;storage-zfs-and-why&quot;&gt;Storage: ZFS, and why&lt;&#x2F;h2&gt;
&lt;p&gt;The storage decision got its own &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;adr.github.io&#x2F;&quot;&gt;ADR&lt;&#x2F;a&gt; (I keep architecture decision records in the repo — &lt;code&gt;docs&#x2F;adr&#x2F;0001-choose-zfs-for-helios64.md&lt;&#x2F;code&gt;, accepted 2025-08-27). The short version:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Decision: ZFS.&lt;&#x2F;strong&gt; End-to-end checksums, snapshots, &lt;code&gt;send&lt;&#x2F;code&gt;&#x2F;&lt;code&gt;receive&lt;&#x2F;code&gt;, and self-healing are worth a lot for data I actually care about, and NixOS&#x27;s ZFS integration is mature. The cost is memory pressure on a low-RAM ARM board and tighter kernel&#x2F;module version coupling. The alternative considered — &lt;code&gt;mdraid&lt;&#x2F;code&gt; + &lt;code&gt;ext4&lt;&#x2F;code&gt;&#x2F;&lt;code&gt;btrfs&lt;&#x2F;code&gt; — is simpler and lighter but has weaker integrity guarantees. For a NAS, integrity wins.&lt;&#x2F;p&gt;
&lt;p&gt;The pool config reflects the board&#x27;s constraints:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code data-lang=&quot;nix&quot;&gt;boot.supportedFilesystems = [ &amp;quot;zfs&amp;quot; ];
services.zfs = {
  autoScrub.enable = true;
  trim.enable = true;
};
# On a 4 GB board, cap the ARC so ZFS doesn&amp;#39;t starve everything else:
# boot.kernelParams = [ &amp;quot;zfs.zfs_arc_max=1073741824&amp;quot; ]; # 1 GiB
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Sensible defaults for the pool (&lt;code&gt;tank&lt;&#x2F;code&gt;): &lt;code&gt;ashift=12&lt;&#x2F;code&gt;, &lt;code&gt;compression=zstd&lt;&#x2F;code&gt;, &lt;code&gt;autotrim=on&lt;&#x2F;code&gt;, &lt;code&gt;atime=off&lt;&#x2F;code&gt; for bulk data, and a topology chosen by disk count — mirror for 2 disks, RAIDZ1 for 3–5, RAIDZ2 when I want to survive two failures. Datasets split by purpose: &lt;code&gt;tank&#x2F;data&#x2F;shares&lt;&#x2F;code&gt; for SMB, &lt;code&gt;tank&#x2F;data&#x2F;media&lt;&#x2F;code&gt; for streaming, &lt;code&gt;tank&#x2F;backups&lt;&#x2F;code&gt; for restic targets (with credentials supplied by &lt;a href=&quot;&#x2F;posts&#x2F;nix-fleet-agenix-secrets&#x2F;&quot;&gt;agenix&lt;&#x2F;a&gt; — that&#x27;s the payoff of Part 3).&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-gotchas-this-board-taught-me&quot;&gt;The gotchas this board taught me&lt;&#x2F;h2&gt;
&lt;p&gt;Real hardware has opinions, and the runbook records them so I don&#x27;t relearn them at 2am:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The 2.5GbE port can be flaky&lt;&#x2F;strong&gt; on some Helios64 units. The mitigation is unglamorous: prefer the 1GbE port, lock the link speed at the switch, and capture &lt;code&gt;dmesg&lt;&#x2F;code&gt; for 24h before trusting it. There&#x27;s an ADR for that decision too (&lt;code&gt;0002-networking-1g-vs-2_5g.md&lt;&#x2F;code&gt;).&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;ZFS ARC will eat a 4 GB board alive&lt;&#x2F;strong&gt; if you let it. Cap &lt;code&gt;zfs_arc_max&lt;&#x2F;code&gt; to 1–2 GiB and re-evaluate after watching real workload for a week.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;RAIDZ1 + power events is a risk.&lt;&#x2F;strong&gt; A NAS without tested backups isn&#x27;t a backup. The drive-replacement playbook (&lt;code&gt;zpool offline&lt;&#x2F;code&gt; → swap → &lt;code&gt;zpool replace&lt;&#x2F;code&gt; → watch the resilver) lives in the design doc, and restore tests are scheduled quarterly.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;why-the-ceremony-is-the-point&quot;&gt;Why the ceremony is the point&lt;&#x2F;h2&gt;
&lt;p&gt;It would be faster, &lt;em&gt;once&lt;&#x2F;em&gt;, to flash an SD card and click through a setup. But &quot;once&quot; is the trap. The value of the disko + nixos-anywhere + flake approach is the &lt;strong&gt;second&lt;&#x2F;strong&gt; time — when a disk dies, or I want to rebuild on fresh drives, or I&#x27;m reproducing the NAS to test an upgrade. Then the disk layout, the ZFS topology, the device tree, the services, and the secrets are all &lt;em&gt;declared&lt;&#x2F;em&gt;, and the rebuild is a command, not an archaeology project.&lt;&#x2F;p&gt;
&lt;p&gt;That&#x27;s the whole thesis of this series, taken to its hardest case. &lt;a href=&quot;&#x2F;posts&#x2F;nix-fleet-one-flake&#x2F;&quot;&gt;Part 1&lt;&#x2F;a&gt; claimed one flake could configure every machine — laptop, server, and NAS — reproducibly. The NAS is the machine where reproducibility is least convenient and most valuable, and it&#x27;s the one that proves the model. Same flake, same discipline, all the way down to the spinning rust.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;The host config and ADRs referenced here live in &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;parallaxisjones&#x2F;dotfiles&quot;&gt;&lt;code&gt;parallaxisjones&#x2F;dotfiles&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;— Parker Jones, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;parkerjones.dev&quot;&gt;parkerjones.dev&lt;&#x2F;a&gt;&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
</content>
        
    </entry>
</feed>
