feat(deploy): run the tejo from USB (disk-image-tejo-usb) #120
1 changed files with 107 additions and 73 deletions
refactor(deploy): one USB-image helper for both bootloader shapes
disk-image-sintra-usb was a 60-line inline copy of everything
mkUsbDiskImage already does, plus the GRUB/hybrid bits the Aaeon
firmware needs — so the two implementations had already drifted: the
sintra image never picked up the `nofail` /boot that keeps a slow
ESP-USB enumeration out of emergency mode, nor the uas/autosuspend
hardening batm3.nix and douro.nix carry.
- mkUsbDiskImage takes named args with `partitionTableType` ("efi" for
systemd-boot, "hybrid" for GRUB) and `grubBiosDevice`. The ESP relabel
is layout-independent: the hybrid table creates the ESP first and
bios_grub second, so it stays partition 1 either way.
- New `usbGrubHybridModule` + `usbBusHardening` modules. The hardening is
scoped to the -usb configs rather than hardware/upboard.nix, which
sintra's eMMC install also reads — no cmdline change on a production
machine.
- `nixosConfigurations.sintra-usb` is now a named config, so a running
stick can be updated in place (nix copy + switch-to-configuration)
like batm3-usb and douro-usb.
- grub.devices is "nodev" in the config and mkForce'd to the build VM's
disk only for the image: an in-place switch on a live stick has no
/dev/vda, and GRUB's embedded core.img reads grub.cfg off the
partition, so the MBR stage needs no per-generation rewrite. Plain
definition rather than mkForce, since two mkForce lists merge into
[ "/dev/vda" "nodev" ] instead of replacing.
douro-usb and batm3-usb evaluate to byte-identical kernelParams,
blacklistedKernelModules, fileSystems, bootloader and autoUpgrade
config as before.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit
81a001c3d3
180
flake.nix
180
flake.nix
|
|
@ -391,19 +391,92 @@
|
|||
system.autoUpgrade.enable = lib.mkForce false;
|
||||
};
|
||||
|
||||
# Bus hardening that makes a USB stick a reliable boot medium: keep the
|
||||
# flash drive off the flaky UAS driver (many bridges advertise UAS then
|
||||
# drop off the bus under sustained write load — "device offline error,
|
||||
# dev sdb"), and stop USB autosuspend cutting power mid-I/O. douro.nix
|
||||
# and batm3.nix carry this in their hardware files because those machines
|
||||
# boot from USB exclusively; hardware/upboard.nix is SHARED with sintra's
|
||||
# eMMC install, so for the UP Board models it is scoped to the -usb
|
||||
# config here rather than changing a production machine's cmdline.
|
||||
usbBusHardening = {
|
||||
boot.blacklistedKernelModules = [ "uas" ];
|
||||
boot.kernelParams = [ "usbcore.autosuspend=-1" ];
|
||||
};
|
||||
|
||||
# Aaeon UP Board firmware (tejo, sintra) USB-boots in Legacy/BIOS mode —
|
||||
# it boots the live ISO via its isolinux (BIOS) El Torito image, not the
|
||||
# UEFI ESP. systemd-boot is UEFI-only, so a dd'd systemd-boot image is not
|
||||
# recognised as bootable at all. Switch the UP Board USB configs to GRUB
|
||||
# with BOTH BIOS (MBR + bios_grub partition, via mkUsbDiskImage's
|
||||
# partitionTableType = "hybrid") and UEFI (removable
|
||||
# /EFI/BOOT/BOOTX64.EFI) — mirroring the live ISO's dual boot — so the
|
||||
# stick boots on Legacy and UEFI alike. Scoped to the USB configs; the
|
||||
# eMMC installs keep systemd-boot.
|
||||
#
|
||||
# devices = [ "nodev" ] here, NOT the image's disk. This config is also
|
||||
# what in-place updates (`nix copy` + switch-to-configuration) run against
|
||||
# on a LIVE stick, where the build VM's /dev/vda does not exist and a BIOS
|
||||
# grub-install against it would fail the switch. "nodev" regenerates
|
||||
# grub.cfg and skips the MBR write, which is the correct behaviour for an
|
||||
# update: GRUB's embedded core.img reads grub.cfg off the partition, so
|
||||
# the MBR stage never needs rewriting per generation. mkUsbDiskImage's
|
||||
# grubBiosDevice overrides this for the image build, where the BIOS stage
|
||||
# genuinely has to be written.
|
||||
usbGrubHybridModule = { lib, ... }: {
|
||||
boot.loader.systemd-boot.enable = lib.mkForce false;
|
||||
boot.loader.efi.canTouchEfiVariables = lib.mkForce false;
|
||||
boot.loader.grub = {
|
||||
enable = lib.mkForce true;
|
||||
efiSupport = true;
|
||||
efiInstallAsRemovable = true;
|
||||
# Plain definition, NOT mkForce: grubBiosDevice overrides it with
|
||||
# mkForce, and two mkForce list definitions would merge (both
|
||||
# priority 50) into [ "/dev/vda" "nodev" ] instead of replacing.
|
||||
# Nothing else in the module stack defines grub.devices.
|
||||
devices = [ "nodev" ];
|
||||
};
|
||||
};
|
||||
|
||||
# dd-able USB image of a <model>-usb config. make-disk-image gives the
|
||||
# ext4 root the nixos-usb label directly (-L) but hardcodes the ESP FAT
|
||||
# label to "ESP", so the volume is relabelled to ESP-USB afterwards —
|
||||
# volume label only; bootloader files are untouched and UEFI loads
|
||||
# /EFI/BOOT/BOOTX64.EFI regardless. Keeps systemd-boot: both the batm3
|
||||
# and douro firmware UEFI-USB-boot fine via that removable fallback.
|
||||
mkUsbDiskImage = machineModel: usbConfig:
|
||||
# /EFI/BOOT/BOOTX64.EFI regardless.
|
||||
#
|
||||
# partitionTableType: "efi" (GPT + ESP, systemd-boot) for batm3 and douro,
|
||||
# whose firmware UEFI-USB-boots fine via that removable fallback;
|
||||
# "hybrid" (GPT + bios_grub + ESP) for the UP Board models, paired with
|
||||
# usbGrubHybridModule. In BOTH layouts the ESP is partition 1 — the hybrid
|
||||
# table creates the ESP first and the bios_grub partition second — so the
|
||||
# parted/mlabel relabel below is layout-independent.
|
||||
#
|
||||
# grubBiosDevice: the build VM's disk, for hybrid images only. GRUB must
|
||||
# write its BIOS stage to that disk's MBR at image-build time, while the
|
||||
# config itself says "nodev" so in-place updates on a live stick work;
|
||||
# see usbGrubHybridModule.
|
||||
mkUsbDiskImage =
|
||||
{ machineModel
|
||||
, usbConfig
|
||||
, partitionTableType ? "efi"
|
||||
, grubBiosDevice ? null
|
||||
}:
|
||||
let
|
||||
imageConfig =
|
||||
if grubBiosDevice == null then
|
||||
usbConfig
|
||||
else
|
||||
usbConfig.extendModules {
|
||||
modules = [
|
||||
({ lib, ... }: {
|
||||
boot.loader.grub.devices = lib.mkForce [ grubBiosDevice ];
|
||||
})
|
||||
];
|
||||
};
|
||||
baseImage = import (nixpkgs + "/nixos/lib/make-disk-image.nix") {
|
||||
inherit pkgs lib;
|
||||
config = usbConfig.config;
|
||||
inherit pkgs lib partitionTableType;
|
||||
config = imageConfig.config;
|
||||
format = "raw";
|
||||
partitionTableType = "efi";
|
||||
diskSize = "auto";
|
||||
label = "nixos-usb"; # ext4 root label (make-disk-image -L)
|
||||
};
|
||||
|
|
@ -469,6 +542,14 @@
|
|||
douro-usb = self.nixosConfigurations.douro-installed.extendModules {
|
||||
modules = [ usbBootModule ];
|
||||
};
|
||||
# UP Board models get the same run-from-USB shape plus two things the
|
||||
# Bay Trail / OptiPlex boxes don't need: GRUB on a hybrid table, because
|
||||
# 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.
|
||||
sintra-usb = self.nixosConfigurations.sintra-installed.extendModules {
|
||||
modules = [ usbBootModule usbBusHardening usbGrubHybridModule ];
|
||||
};
|
||||
};
|
||||
|
||||
# ── Standalone NixOS module ───────────────────────────────────
|
||||
|
|
@ -536,71 +617,6 @@
|
|||
diskSize = "auto";
|
||||
};
|
||||
|
||||
# USB-bootable Sintra image with DISTINCT partition labels
|
||||
# (nixos-usb / ESP-USB) so the stick can be booted on a Sintra whose
|
||||
# eMMC already holds a nixos/ESP-labelled install without a by-label
|
||||
# collision — stage-1 would otherwise race between the two roots and
|
||||
# likely mount the eMMC. Auto-upgrade is disabled: this is a portable
|
||||
# test / hand-off image, not a managed fleet member, and disabling it
|
||||
# also removes the scheduled bootloader writes that could otherwise
|
||||
# land on the eMMC's ESP.
|
||||
disk-image-sintra-usb =
|
||||
let
|
||||
cfg = self.nixosConfigurations.sintra-installed.extendModules {
|
||||
modules = [
|
||||
({ lib, ... }: {
|
||||
fileSystems."/".device = lib.mkForce "/dev/disk/by-label/nixos-usb";
|
||||
fileSystems."/boot".device = lib.mkForce "/dev/disk/by-label/ESP-USB";
|
||||
system.autoUpgrade.enable = lib.mkForce false;
|
||||
|
||||
# The Sintra's Aaeon firmware USB-boots in Legacy/BIOS mode — it
|
||||
# boots the live ISO via its isolinux (BIOS) El Torito image, not
|
||||
# the UEFI ESP. systemd-boot is UEFI-only, so a dd'd systemd-boot
|
||||
# image isn't recognised as bootable. Switch THIS USB image to
|
||||
# GRUB with BOTH BIOS (MBR + bios_grub partition, via the "hybrid"
|
||||
# table below) and UEFI (removable /EFI/BOOT/BOOTX64.EFI) — mirroring
|
||||
# the live ISO's dual boot — so it boots on Legacy and UEFI alike.
|
||||
# Scoped to the USB image; the eMMC install keeps systemd-boot.
|
||||
boot.loader.systemd-boot.enable = lib.mkForce false;
|
||||
boot.loader.efi.canTouchEfiVariables = lib.mkForce false;
|
||||
boot.loader.grub = {
|
||||
enable = lib.mkForce true;
|
||||
efiSupport = true;
|
||||
efiInstallAsRemovable = true;
|
||||
# make-disk-image's build VM exposes the image as /dev/vda;
|
||||
# GRUB installs its BIOS stage to that disk's MBR.
|
||||
devices = lib.mkForce [ "/dev/vda" ];
|
||||
};
|
||||
})
|
||||
];
|
||||
};
|
||||
baseImage = import (nixpkgs + "/nixos/lib/make-disk-image.nix") {
|
||||
inherit pkgs lib;
|
||||
config = cfg.config;
|
||||
format = "raw";
|
||||
# hybrid = GPT + bios_grub partition + ESP → BIOS + UEFI bootable.
|
||||
partitionTableType = "hybrid";
|
||||
diskSize = "auto";
|
||||
label = "nixos-usb"; # ext4 root label (make-disk-image -L)
|
||||
};
|
||||
in
|
||||
pkgs.runCommand "nixos-disk-image-sintra-usb"
|
||||
{ nativeBuildInputs = [ pkgs.parted pkgs.mtools ]; }
|
||||
''
|
||||
mkdir -p $out
|
||||
cp --sparse=always ${baseImage}/nixos.img $out/nixos.img
|
||||
chmod +w $out/nixos.img
|
||||
# make-disk-image hardcodes the ESP FAT label to "ESP"; relabel the
|
||||
# volume to ESP-USB so /boot (by-label/ESP-USB) doesn't collide with
|
||||
# the eMMC's ESP. Volume label only — bootloader files are untouched,
|
||||
# and UEFI loads /EFI/BOOT/BOOTX64.EFI regardless of the label.
|
||||
espStart=$(parted -sm "$out/nixos.img" unit B print | awk -F: '$1==1 {gsub("B","",$2); print $2}')
|
||||
echo "ESP partition starts at byte $espStart — relabelling to ESP-USB"
|
||||
export MTOOLS_SKIP_CHECK=1
|
||||
mlabel -i "$out/nixos.img@@$espStart" ::ESP-USB
|
||||
printf 'verify ESP label: '; mlabel -i "$out/nixos.img@@$espStart" -s :: || true
|
||||
'';
|
||||
|
||||
# USB-bootable images (see usbBootModule / mkUsbDiskImage in the let
|
||||
# block). Flash with dd or balenaEtcher, boot the stick, done — no
|
||||
# installer step. The plain disk-image-<model> reuses the generic
|
||||
|
|
@ -609,8 +625,26 @@
|
|||
# stage-1's by-label/nixos resolve to the internal drive instead of the
|
||||
# stick — the stage-2 init path baked into the USB's boot entry isn't on
|
||||
# that root, so stage 1 aborts. These variants can't hit that.
|
||||
disk-image-batm3-usb = mkUsbDiskImage "batm3" self.nixosConfigurations.batm3-usb;
|
||||
disk-image-douro-usb = mkUsbDiskImage "douro" self.nixosConfigurations.douro-usb;
|
||||
disk-image-batm3-usb = mkUsbDiskImage {
|
||||
machineModel = "batm3";
|
||||
usbConfig = self.nixosConfigurations.batm3-usb;
|
||||
};
|
||||
disk-image-douro-usb = mkUsbDiskImage {
|
||||
machineModel = "douro";
|
||||
usbConfig = self.nixosConfigurations.douro-usb;
|
||||
};
|
||||
|
||||
# UP Board models: hybrid table + GRUB, so one stick boots on the Aaeon
|
||||
# firmware's Legacy/BIOS USB path as well as UEFI. Distinct labels also
|
||||
# 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-sintra-usb = mkUsbDiskImage {
|
||||
machineModel = "sintra";
|
||||
usbConfig = self.nixosConfigurations.sintra-usb;
|
||||
partitionTableType = "hybrid";
|
||||
grubBiosDevice = "/dev/vda";
|
||||
};
|
||||
|
||||
# Backwards compat
|
||||
iso = self.nixosConfigurations.douro.config.system.build.isoImage;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue