feat(deploy): USB-bootable tejo image (disk-image-tejo-usb)

The tejo still runs its factory Debian (ubilinux4, kernel 4.9) on
internal storage and has never had bitspire on it. Rather than flash
that drive, give it the run-from-USB shape douro and batm3 already use:
the stick is the system and the internal install is never touched.

- nixosConfigurations.tejo-usb — tejo-installed + usbBootModule +
  usbBusHardening + usbGrubHybridModule. Evaluates identically to
  sintra-usb, which shares hardware/upboard.nix.
- packages.disk-image-tejo-usb — hybrid table, GRUB, BIOS + UEFI.

NOT the efi/systemd-boot shape douro uses. The tejo is the same Aaeon
UP Board as sintra, whose firmware was found to USB-boot in Legacy/BIOS
mode; systemd-boot is UEFI-only, so a dd'd systemd-boot stick would not
be recognised as bootable at all. The hybrid image boots either path, so
it is also the safe choice if the firmware turns out to differ.

README documents both bootloader shapes and which models take which.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Padreug 2026-10-06 19:18:47 +02:00
commit c3e01c9cd3
2 changed files with 40 additions and 6 deletions

View file

@ -19,7 +19,7 @@ deploy/nixos/
│ └── 99-bitspire-hardware.rules # additional udev rules (loaded via configuration.nix)
├── provision-atm.sh # Push LNbits credentials to a deployed ATM via SSH
├── atm-transactions.sh # Operator query tool — reads /var/lib/bitspire/state.db
├── flash-douro-usb.sh # Helper for flashing a douro live USB
├── flash-douro-usb.sh # Helper for flashing a douro LIVE ISO (not the -usb disk image)
└── build-iso.sh # Convenience wrapper for `nix build .#iso-<model>`
```
@ -33,19 +33,37 @@ Each ATM model has two flake outputs:
| `nixosConfigurations.<model>-installed` | installed | full GPT + systemd-boot install, ext4 root, supports `nixos-rebuild switch` |
| `packages.x86_64-linux.iso-<model>` | ISO | ISO image of the live variant |
| `packages.x86_64-linux.disk-image-<model>` | raw image | dd-able full disk image of the installed variant |
| `nixosConfigurations.<model>-usb` | installed, on a stick | `<model>-installed` hardened to run from a USB stick: `nixos-usb`/`ESP-USB` labels, `nofail` `/boot`, no partition growing, auto-upgrade off (`batm3`, `douro` only) |
| `nixosConfigurations.<model>-usb` | installed, on a stick | `<model>-installed` hardened to run from a USB stick: `nixos-usb`/`ESP-USB` labels, `nofail` `/boot`, no partition growing, auto-upgrade off, `uas` blacklisted |
| `packages.x86_64-linux.disk-image-<model>-usb` | raw image | dd-able image of the `-usb` variant — flash, plug in, boot; no installer step |
Models: `douro`, `tejo`, `sintra`, `batm3`.
Models: `douro`, `tejo`, `sintra`, `batm3`. All four have `-usb` outputs.
**Run-from-USB deployments** (`batm3` today, `douro` since the LNbits cutover)
skip the Alpine + dd-to-internal-disk procedure below entirely: the stick *is*
the system. Flash `disk-image-<model>-usb` with balenaEtcher (it verifies the
**Run-from-USB deployments** skip the Alpine + dd-to-internal-disk procedure
below entirely: the stick *is* the system. That is how `batm3` runs today, how
`douro` runs since the LNbits cutover, and how `tejo` is being brought up — its
internal storage still holds the factory Debian (`ubilinux4`) and is never
touched. Flash `disk-image-<model>-usb` with balenaEtcher (it verifies the
write — a truncated or bad copy fails stage-1 fsck on first boot) onto a stick
of 16 GB or more, plug it in, power on. Updates go in-place against the
`<model>-usb` config (`nix copy` the toplevel + `switch-to-configuration`),
which keeps pairing and `/var/lib/bitspire`.
### Two bootloader shapes, picked by firmware
The `-usb` images are not interchangeable between models, because the fleet's
firmware is not:
| Models | Partition table | Bootloader | Why |
|---|---|---|---|
| `batm3`, `douro` | `efi` (GPT + ESP) | systemd-boot | Their firmware UEFI-USB-boots fine via the ESP's removable `/EFI/BOOT/BOOTX64.EFI` fallback |
| `tejo`, `sintra` | `hybrid` (GPT + `bios_grub` + ESP) | GRUB, BIOS **and** UEFI | Aaeon UP Board firmware USB-boots in Legacy/BIOS mode — it boots the live ISO via isolinux, not the ESP. systemd-boot is UEFI-only, so a dd'd systemd-boot stick isn't recognised as bootable at all. The hybrid image boots either way |
Both shapes come out of `mkUsbDiskImage` in `flake.nix`; the GRUB override is
`usbGrubHybridModule`. A UP Board `-usb` config installs GRUB with
`devices = [ "nodev" ]` so an in-place `switch-to-configuration` on a live
stick only regenerates `grub.cfg` — the image build is the one place the BIOS
stage gets written to an MBR.
```bash
# Build a Sintra disk image
nix build .#disk-image-sintra

View file

@ -547,6 +547,16 @@
# the Aaeon firmware USB-boots in Legacy/BIOS mode (usbGrubHybridModule),
# and the uas/autosuspend hardening from here instead of
# hardware/upboard.nix, which sintra's eMMC install also reads.
#
# tejo: the unit still runs its factory Debian (ubilinux4) on internal
# storage, so the stick has to BE the system, same as douro. Its
# internal install is not NixOS and carries no nixos/ESP labels, which
# makes the label disambiguation moot there today — but the hardened
# /boot and the bus settings are what make a stick a reliable boot
# medium, so it takes the identical module set as sintra.
tejo-usb = self.nixosConfigurations.tejo-installed.extendModules {
modules = [ usbBootModule usbBusHardening usbGrubHybridModule ];
};
sintra-usb = self.nixosConfigurations.sintra-installed.extendModules {
modules = [ usbBootModule usbBusHardening usbGrubHybridModule ];
};
@ -639,6 +649,12 @@
# matter more here than on douro — a sintra's eMMC already holds a
# nixos/ESP-labelled install, and stage-1 would otherwise race the two
# roots and likely mount the eMMC.
disk-image-tejo-usb = mkUsbDiskImage {
machineModel = "tejo";
usbConfig = self.nixosConfigurations.tejo-usb;
partitionTableType = "hybrid";
grubBiosDevice = "/dev/vda";
};
disk-image-sintra-usb = mkUsbDiskImage {
machineModel = "sintra";
usbConfig = self.nixosConfigurations.sintra-usb;