diff --git a/flake.nix b/flake.nix index 3fe1bba..29f44b1 100644 --- a/flake.nix +++ b/flake.nix @@ -292,6 +292,37 @@ # at ttyJ5, dispenser at ttyJ7 layout) — reuse the same hw module. sintra-installed = mkInstalledConfig "sintra" ./deploy/nixos/hardware/upboard.nix; batm3-installed = mkInstalledConfig "batm3" ./deploy/nixos/hardware/batm3.nix; + + # USB-bootable variant of batm3-installed. This is the config the + # flashed USB stick actually runs — distinct fs labels so stage-1 can't + # latch the internal drive, nofail /boot, no growPartition, autoUpgrade + # off. Exposed as a named config (not just inline in the disk-image + # target) so its system closure can be built here and deployed in-place + # with `nix copy` + `switch-to-configuration` — updating the app on a + # running stick WITHOUT reflashing (preserves pairing + /var/lib state). + # disk-image-batm3-usb builds its filesystem image from this same config. + batm3-usb = self.nixosConfigurations.batm3-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"; + # /boot must NOT be a hard boot dependency on the USB image. The + # firmware already loaded the bootloader before Linux; without + # nofail, a slow/late ESP-USB enumeration (BOT is slower than UAS) + # blows past systemd's 90s device-timeout into emergency mode with + # root locked — a dead end. nofail + short timeout lets the + # already-mounted root carry the boot; /boot mounts if/when it shows. + fileSystems."/boot".options = [ "nofail" "x-systemd.device-timeout=10s" ]; + # NO growPartition/autoResize: sfdisk rewriting the partition table + # on first boot is the single most bus-stressing write, and flaky + # USB bridges drop off the bus mid-rewrite (sfdisk wedges in D-state + # and ESP-USB vanishes with the device). Persistent state is a few + # MB and the image ships ~2GB free. The internal-SATA disk-image- + # batm3 keeps growPartition (a real AHCI SSD won't drop the bus). + system.autoUpgrade.enable = lib.mkForce false; + }) + ]; + }; }; # ── Standalone NixOS module ─────────────────────────────────── @@ -443,41 +474,12 @@ # internal drive's ESP). disk-image-batm3-usb = let - cfg = self.nixosConfigurations.batm3-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"; - # /boot must NOT be a hard boot dependency on the USB test - # image. The firmware already loaded the bootloader from the - # ESP before Linux started; /boot is only remounted so the OS - # can *update* the bootloader — which this image never does - # (autoUpgrade off, no nixos-rebuild on the stick). Without - # nofail, a slow/late ESP-USB enumeration (BOT is slower than - # UAS) blows past systemd's 90s device-timeout and drops to - # emergency mode — with root locked, an unrecoverable dead end. - # nofail + a short timeout lets the (already-mounted) root carry - # the boot to completion; /boot mounts if/when the ESP shows up. - fileSystems."/boot".options = [ "nofail" "x-systemd.device-timeout=10s" ]; - # DELIBERATELY NO growPartition/autoResize on the USB image. - # growPartition runs sfdisk to rewrite the stick's partition - # table on first boot — the single most bus-stressing write of - # the boot. Flaky USB bridges drop off the bus mid-rewrite - # (sfdisk hangs forever as an uninterruptible D-state task) and, - # worse, partition 1 (ESP-USB) vanishes with the device, so - # /boot times out too. The kiosk's persistent state (state.db, - # .env, wifi.conf, logs) is a few MB and the built image already - # carries ~2GB free inside root — growing to fill the stick buys - # nothing and costs reliability. The internal-SATA target - # (disk-image-batm3) keeps growPartition: a real AHCI SSD won't - # drop the bus and there filling the disk is worth it. - system.autoUpgrade.enable = lib.mkForce false; - }) - ]; - }; + # Filesystem image of the batm3-usb config (defined in + # nixosConfigurations). Same config that in-place deploys target, so + # a reflash and a `switch-to-configuration` converge on one system. baseImage = import (nixpkgs + "/nixos/lib/make-disk-image.nix") { inherit pkgs lib; - config = cfg.config; + config = self.nixosConfigurations.batm3-usb.config; format = "raw"; partitionTableType = "efi"; diskSize = "auto";