Files
Luke Childs 87f03be2b7 Refactor USB installer into a minimal NixOS SquashFS live ISO
## What

Replaces the Docker-rootfs-in-an-ISO USB installer with a NixOS-built
live ISO. The old installer dumped a Debian Docker image's rootfs onto
an iso9660 filesystem, hand-deleted kernel module directories to save
space, and booted it read-write (so it could get corrupted). The new
installer is a declarative NixOS config that boots an immutable xz
squashfs with a tmpfs overlay — the boot medium is never written to and
can't be corrupted, even root gets `EROFS` writing to `/iso` or the
store.

## UEFI USB boot on strict firmware

NixOS's `iso-image` builds the hybrid ISO with `-isohybrid-gpt-basdat`,
which puts the EFI partition *inside* the ISO9660 area, overlapping the
main partition. Lenient firmware (and QEMU's OVMF) boot this fine, but
GPT-preferring UEFI firmware — e.g. the Umbrel Pro — ignores a USB disk
that has no real, standalone EFI System Partition and falls through to
internal storage. So the final ISO is re-mastered into the standard
distro layout: syslinux El Torito + isohybrid MBR for BIOS, and the EFI
image as a proper appended GPT EFI System Partition for UEFI (CD and
USB). Confirmed booting on a physical Umbrel Pro.

## Two-phase build

1. `./build.sh base` builds the installer ISO *without* an umbrelOS
image. This is the slow part and doesn't depend on the OS image, so CI
builds it concurrently with the OS image builds.
2. `./build.sh inject` adds `umbrelos-amd64.img.xz` and re-masters the
ISO with the EFI System Partition layout above, producing the final ISO.
It's fast, so the image is dropped in as the last release step.

Both phases run Nix inside a pinned `nixos/nix` Docker image, so the
build is identical on macOS and CI with no toolchain setup. nixpkgs is
pinned to nixos-26.05 via `flake.lock`.

## Behaviour parity

- Same `custom-tty` installer with the same options: Umbrel Home
auto-flash to NVMe, Umbrel Pro auto-flash to eMMC, interactive device
selection otherwise (image read from `/iso/umbrelos-amd64.img.xz`; loop
devices excluded from the list).
- Same boot menu entries — "umbrelOS installer" (defaults to
`nomodeset`, see #2013) and "umbrelOS installer (alt graphics)" (a NixOS
specialisation) — now rendered as a text-mode GRUB menu for
headless/quirky-graphics reliability. 5s timeout.
- Hybrid ISO bootable as CD (VMs) and USB (hardware) on both BIOS and
UEFI. Same `UMBRELINSTALLER` volume label and release asset name.
- Quiet boot (`consoleLogLevel=3`) so harmless firmware ACPI / systemd
console spam doesn't flash by before the installer UI appears.
- Root login on tty2+ for debugging.

## Size

~257MB before the OS image is injected (smaller than the old Debian
rootfs despite its manual stripping). Trimmed by declaratively pruning
kernel module categories an installer never needs
(gpu/net/media/sound/…), dropping Nix/grub-efi/systemd-importd from the
runtime closure, and xz-compressing the squashfs.

## Testing

- Local QEMU matrix (BIOS+CD, BIOS+USB, UEFI+CD, UEFI+USB): all boot to
the installer; interactive flash byte-identical to source; Umbrel Home
auto-flash + unattended poweroff; read-only guarantees hold from a root
shell.
- New CI VM test (`usb-installer-auto-flash-umbrel-home`): builds the
installer, boots it under UEFI, auto-flashes a blank Umbrel Home NVMe,
and verifies the flashed OS boots into onboarding. Adds reusable `vm.sh`
capabilities (`--cdrom`, `--boot-disk none`, `--boot-nvme-slot`).
- Booted from USB on a physical Umbrel Pro.
- Full release pipeline validated end-to-end via a tag dry-run: produces
`umbrelos-amd64-usb-installer.iso` in the release assets.
2026-09-02 21:44:44 +07:00

20 lines
862 B
JSON

{
"scripts": {
"build": "./build.sh",
"build:amd64": "SKIP_ARM64=true SKIP_PI=true npm run build",
"build:amd64:rugix": "SKIP_AMD64_MENDER=true SKIP_MENDER_ARTIFACTS=true npm run build:amd64",
"build:amd64:usb-installer:prepare": "xz --keep --force --threads=0 build/umbrelos-amd64.img",
"build:amd64:usb-installer:base": "./usb-installer/build.sh base",
"build:amd64:usb-installer:inject": "./usb-installer/build.sh inject",
"build:amd64:usb-installer": "./usb-installer/build.sh",
"build:arm64": "SKIP_AMD64=true SKIP_PI=true npm run build",
"build:pi": "SKIP_AMD64=true SKIP_ARM64=true npm run build",
"build:pi5": "SKIP_AMD64=true SKIP_ARM64=true SKIP_PI4=true npm run build",
"clean": "rm -rf rugix/build rugix/.rugix build",
"vm": "./vm.sh"
},
"devDependencies": {
"opentimestamps": "^0.4.9"
}
}