The image gets a second, empty store slot. systemd-sysupdate (wrapped as
router-update) installs a new version into the slot not in use: both
store partitions, then the UKI with boot counting, verified against a
signed SHA256SUMS. system.build.update holds a version's files.
Counted boots must pass router-healthy (the router's units plus
router.image.healthChecks, each checked on its own) to reach
boot-complete.target; otherwise the box reboots, and once the tries are
used up systemd-boot falls back to the previous version.
checks.image now installs v2 into the empty slot, boots and blesses it,
refuses a tampered update, and rolls a broken v3 back to v2.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nixosModules.image builds a GPT disk image with systemd-repart: ESP with a
UKI carrying the store's verity root hash, an erofs /nix/store on a
dm-verity partition, a tmpfs root, an immutable /etc overlay and an ext4
state partition on /var. No Nix on the device; it is replaced as a whole.
The store is made with 4 KiB erofs blocks: systemd-repart passes the 512
byte sector size to mkfs.erofs, and libblkid cannot verify the checksum of
such a superblock, so udev never reported the file system and the initrd
waited forever for /dev/mapper/usr.
examples/image (nix build .#image) is a hardware test build with a debug
console password; checks.image boots the image under UEFI.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>