cyberdeck/docs/nixos-install.md
BarnacleBoy 35db0c5e1b NixOS install doc — Anywhere eval, DTB notes, install approach, boot management (#6)
Merge docs/nixos-install.md — NixOS Anywhere eval, DTB clarification, install approach
2026-06-12 22:21:38 +00:00

11 KiB

NixOS Install — Orange Pi 3B (Cyberdeck Multi-Boot)

Goal: NixOS on eMMC partition p4 (root_nixos), booting via shared extlinux.conf alongside Orange Pi OS Arch and Armbian.

Context

The cyberdeck's eMMC has a four-partition layout with a shared /boot on p1:

mmcblk0 (233GB eMMC user data area)
├── mmcblk0p1  FAT32  1GB    /boot        (shared — kernels, DTBs, extlinux.conf)
├── mmcblk0p2  ext4   85GB   root_opios   (Orange Pi OS Arch — live)
├── mmcblk0p3  ext4   85GB   root_armbian (Armbian — live or planned)
└── mmcblk0p4  ext4   ~84GB  root_nixos   (NixOS — target)

Boot is Rockchip-standard user-data-area: PARTITION_CONFIG=0x78, idbloader at sector 64, u-boot.itb at sector 16384, SPI flash erased. u-boot reads extlinux.conf from the FAT32 /boot partition for the boot menu.

Full walkthrough of the boot architecture and partition setup: docs/emmc-multiboot.md. Existing second-OS install pattern: docs/armbian-install.md.

NixOS Anywhere — Evaluated, Not a Fit

NixOS Anywhere is an SSH-based tool that installs NixOS onto a remote machine. It uses kexec to boot a temporary NixOS installer in RAM, then uses disko to partition and format the disk. It's a great tool for Hetzner servers and fresh-install scenarios, but it doesn't work for our multiboot setup for three reasons:

  1. Disko is destructive by default. Default mode is destroy,format,mount — it wipes the entire disk and repartitions from scratch. There is no supported flow to say "install into partition 4 and leave the rest alone." GitHub issue #274 documents this gap; the disko dual-boot discourse thread shows someone whose existing partitions got eaten.

  2. Shared /boot is incompatible with NixOS's boot generation model. NixOS auto-generates boot entries. Every nixos-rebuild switch or nixos-rebuild boot writes to /boot and would overwrite our extlinux.conf, removing the Arch and Armbian entries.

  3. kexec on ARM adds complexity without solving anything. Aarch64 kexec images exist (from nix-community/nixos-images) but even once you kexec in, you're back to the disko problem.

Verdict: NixOS Anywhere is built for clean-slate installations on blank disks. Our shared-extlinux.conf multiboot setup needs a manual approach.

Device Tree Blob (DTB) — Same One Works

The DTB describes the Orange Pi 3B's hardware — the RK3566 SoC, its memory map, its peripherals. It's not OS-specific. The same rk3566-orangepi-3b.dtb that Orange Pi OS Arch uses will boot NixOS.

The only caveat: DTBs are compiled from the kernel source tree, and kernel bindings evolve. A DTB from kernel 6.6 might not have everything a kernel 6.12 expects. But in practice, for Rockchip boards, they're backward-compatible within a major version range.

Recommendation: Point the NixOS extlinux.conf FDT directive at the DTB file already on the shared /boot partition. If it doesn't work (kernel panic about missing clocks or an undetectable PHY), extract a matching DTB from the NixOS closure instead.

NixOS-Specific Challenges

NixOS differs from Arch and Debian in how it handles kernels and initrds. These three wrinkles make installation more involved than rsync and an extlinux.conf entry.

1. Kernel + DTB Are Inside the Nix Store

Unlike Arch (where /boot/vmlinuz-linux is a standalone file) or Debian (where /boot/vmlinuz-* is installed by the kernel package), NixOS bundles the kernel and DTB inside the Nix store at paths like:

/nix/store/<hash>-linux-<version>/Image
/nix/store/<hash>-linux-<version>/dtbs/rockchip/rk3566-orangepi-3b.dtb

These are not on a traditional /boot partition. They're symlinked into the NixOS /boot by systemd-boot or the extlinux generator, but the actual files live in the Nix store. You can't just copy them — you need to build the closure on a machine with Nix installed, then extract the boot artifacts.

2. Initrd Is Generated From Configuration

NixOS generates the initrd at build time from the system configuration. It doesn't exist as a standalone file until nixos-rebuild produces it. Like the kernel, it lives in the Nix store:

/nix/store/<hash>-initrd-linux-<version>/initrd

You need to extract it from a pre-built closure and place it on the shared /boot manually.

3. NixOS Wants to Manage /boot

By default, NixOS installs a bootloader (systemd-boot on aarch64 UEFI, or extlinux on non-UEFI). Once running, nixos-rebuild switch attempts to write to /boot — which would overwrite our shared extlinux.conf and break the other OSes.

Fix: Configure NixOS to manage only the kernel and initrd on /boot, not the bootloader menu. Set:

boot.loader.grub.enable = false;
boot.loader.generic-extlinux-compatible.enable = true;
# OR, for manual-only:
boot.loader.generic-extlinux-compatible.enable = false;

With generic-extlinux-compatible enabled, NixOS writes a boot.txt or extlinux.conf to /boot, but will overwrite our shared version. With it disabled, NixOS doesn't touch /boot at all — you manage the extlinux.conf entry yourself on every kernel update.

The pattern is: pre-build on a machine with Nix, extract boot artifacts, copy to eMMC.

Prerequisites

  • A machine with Nix installed (Lucy, Mermaid Man, or BarnacleBoy if Nix is available)
  • An SD card to transfer files to the Orange Pi 3B
  • The Orange Pi 3B booted into Orange Pi OS Arch (or any working OS on eMMC)

Step 1: Build a NixOS closure (on Lucy or Mermaid Man)

Create a Nix flake for the Orange Pi 3B configuration. At minimum:

{
  description = "NixOS for Orange Pi 3B";
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
  };
  outputs = { self, nixpkgs }: {
    nixosConfigurations.orangepi3b = nixpkgs.lib.nixosSystem {
      system = "aarch64-linux";
      modules = [
        ({ pkgs, ... }: {
          # Minimal config for first boot
          boot.loader.grub.enable = false;
          boot.loader.generic-extlinux-compatible.enable = false;
          boot.kernelParams = [ "console=tty1" "console=ttyS2,1500000n8" ];
          system.stateVersion = "24.11";
          # Root on p4 — set the UUID or /dev path
          fileSystems."/" = {
            device = "/dev/mmcblk0p4";
            fsType = "ext4";
          };
          # Do NOT mount /boot — it's managed externally
          # fileSystems."/boot" = ...; /* leave this out */
          users.users.root.initialPassword = "changeme";
          services.openssh.enable = true;
        })
      ];
    };
  };
}

Build it:

nix build .#nixosConfigurations.orangepi3b.config.system.build.toplevel

Step 2: Extract boot artifacts

From the built closure, extract the kernel, initrd, and DTB:

# Find the closure path
ls -la result/

# Kernel
cp result/boot/Image /tmp/nixos-kernel

# Initrd
cp result/boot/initrd /tmp/nixos-initrd

# DTB — find it under the kernel package
find result/ -name "rk3566-orangepi-3b.dtb" -exec cp {} /tmp/ \;

# The entire Nix store closure (for copying to p4)
nix copy --to ./result /tmp/nixos-closure

Getting the full rootfs onto p4 is the trickiest part. Options:

  • Option A: nix copy the closure to a path on the SD card, then nix-store --import on the OPi3B (if Nix is installed)
  • Option B: Use nixos-enter or manual chroot manipulation
  • Option C: Build a tarball of the rootfs and extract it on the target

Step 3: Copy to the Orange Pi 3B

Put the kernel, initrd, DTB, and rootfs archive on an SD card. Boot the OPi3B into Orange Pi OS Arch.

# Mount the SD card
sudo mount /dev/mmcblk1p1 /mnt/sd

# Mount the target partitions
sudo mount /dev/mmcblk0p4 /mnt/nixos_root
sudo mount /dev/mmcblk0p1 /mnt/nixos_root/boot

# Copy kernel, initrd, DTB to shared /boot
sudo cp /mnt/sd/nixos-kernel /mnt/nixos_root/boot/
sudo cp /mnt/sd/nixos-initrd /mnt/nixos_root/boot/
sudo cp /mnt/sd/rk3566-orangepi-3b.dtb /mnt/nixos_root/boot/

# Extract rootfs to p4
sudo tar -xzf /mnt/sd/nixos-rootfs.tar.gz -C /mnt/nixos_root/

Step 4: Add extlinux.conf entry

Edit /mnt/nixos_root/boot/extlinux/extlinux.conf (the shared one) and add:

LABEL NixOS
    LINUX /nixos-kernel
    INITRD /nixos-initrd
    FDT /rk3566-orangepi-3b.dtb
    APPEND root=UUID=<uuid-of-p4> ro console=tty1 console=ttyS2,1500000n8

Get the UUID:

sudo blkid /dev/mmcblk0p4

Step 5: Boot and finish setup

sudo umount /mnt/nixos_root/boot /mnt/nixos_root
sudo sync

Reboot, pick "NixOS" from the u-boot menu. Once booted:

# Login as root with the password set in config
# Set up SSH keys, add a user, etc.
# Do NOT run nixos-rebuild switch until /boot management is understood

Boot Management After Install

Once NixOS is running, kernel updates become a manual process because NixOS isn't managing /boot:

  1. On the build machine, build a new closure with the updated kernel
  2. Extract the new kernel + initrd + DTB
  3. Copy them to the OPi3B's shared /boot via SD card or SSH+scp
  4. Update the extlinux.conf LINUX / INITRD paths if filenames changed
  5. Reboot

Alternatively, if the kernel and initrd paths stay constant (same filenames), you can overwrite in place and the existing extlinux.conf entry keeps working.

NixOS on ARM — Board Support

The Orange Pi 3B doesn't have a dedicated NixOS wiki page yet. The NixOS on ARM wiki confirms AArch64 has full upstream support. The generic aarch64 images from Hydra should work on the OPi3B with the correct DTB. The Orange Pi 5 (RK3588) wiki page at https://wiki.nixos.org/wiki/NixOS_on_ARM/Orange_Pi_5_Plus serves as a reference — the approach is the same, just different SoC.

Verification Checklist

  • NixOS boots from u-boot menu
  • Network works (the kernel needs the right DTB for the Ethernet PHY)
  • SSH access (configured in the NixOS flake)
  • extlinux.conf entries for all three OSes still present after NixOS boots
  • Kernel update works (build, extract, copy, reboot cycle)

References

  • NixOS Anywhere — evaluated, not suitable (see above)
  • Disko — declarative partitioning, destructive by default
  • NixOS on ARM wiki — AArch64 support status
  • Orange Pi 5 Plus wiki — reference for Rockchip NixOS approach
  • nix-community/nixos-images — aarch64 kexec installer images
  • eMMC multi-boot setup: docs/emmc-multiboot.md
  • Armbian install pattern: docs/armbian-install.md
  • Bring-up reference: docs/initial-bringup.md