Polyglot NixOS: The Same Disk Image for All Architectures
Recently a colleague mentioned building NixOS images that run unchanged on multiple architectures. Given the past adventures on this blog with systemd-repart and cross-compiling NixOS, I decide to give this a go.
tl;dr You can find a quick’n’dirty implementation here. Check the repo for details on how to build and run it.
So do we want to do: We want to build one disk image that boots on x86_64, ARM AArch64, and RISC-V 64-bit. We limit ourselves here to UEFI platforms, which makes this pretty straight forward.
From a high-level we need to:
- Have a NixOS configuration.
- Build the system closure for each target.
- Throw everything into one
/nix/storepartition. - Populate the ESP to boot the right closure depending on the architecture.
All of this is surprisingly straight-forward. The ESP has
architecture-dependent default filenames for what the firmware should
boot, given no other configuration. This means we can build an
UKI per
architecture and drop it at the right place in the ESP
(/EFI/BOOT/BOOTX64.EFI for 64-bit x86) and we are done!
By linking the system’s UKI in these locations on the ESP, we skip over having an actual bootloader and thus can’t have multiple generations, but it makes for a much leaner example!
The example repo puts the closure for each architecture in a single Nix store partition. I thought this would bring some space savings, because files that are not binary code should be largely the same. This doesn’t really pan out in this small example and we only save a couple of percent. Maybe it makes a bigger difference for larger closures.
If you want to dig into the details, the example repo has the instructions how to build and boot the image. I’m also eager to see someone building a more comprehensive version of this that includes a fully functioning bootloader and multiple generations!