Merge docs/nixos-install.md — NixOS Anywhere eval, DTB clarification, install approach
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:
-
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. -
Shared /boot is incompatible with NixOS's boot generation model. NixOS auto-generates boot entries. Every
nixos-rebuild switchornixos-rebuild bootwrites to /boot and would overwrite our extlinux.conf, removing the Arch and Armbian entries. -
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.
Recommended Install Approach
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 copythe closure to a path on the SD card, thennix-store --importon the OPi3B (if Nix is installed) - Option B: Use
nixos-enteror 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:
- On the build machine, build a new closure with the updated kernel
- Extract the new kernel + initrd + DTB
- Copy them to the OPi3B's shared /boot via SD card or SSH+scp
- Update the extlinux.conf
LINUX/INITRDpaths if filenames changed - 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