feat(deploy): split rpi5 target into installed + image variants

`rpi5-installed` previously bundled the sd-image-aarch64 module, so it
was only good for building a flashable image — a `nixos-rebuild switch`
against it (local or the git+ssh remote form) would drag in the image
builder and its image-specific fs wiring. Split it, mirroring the x86
fleet's mkLiveConfig/mkInstalledConfig separation:

- rpi5-installed → in-place rebuild target. Declares the flashed media's
  own root fs (NIXOS_SD / FIRMWARE labels), nothing image-specific. This
  is what
    sudo nixos-rebuild switch --flake \
      "git+ssh://forgejo@git.atitlan.io/aiolabs/bitspire.git?ref=<branch>#rpi5-installed"
  targets, the aarch64 equivalent of the sintra/douro deploy ritual.
- rpi5-image → same shared runtime + the sd-image builder. Its
  system.build.sdImage is the flashable artifact; packages.aarch64-linux
  .sd-image-rpi5 now points here.

Shared runtime extracted into piBaseModules/mkPiRuntime; folded the
aiolabs cachix substituter + trusted key into the Pi's nix.settings so a
remote rebuild substitutes the heavy aarch64 closure instead of building
it on the Pi (no max-jobs/timeout watchdog — the Pi 5 can build locally
if it must).

Verified: rpi5-installed evaluates to a valid system toplevel (root fs
present), rpi5-image/sd-image-rpi5 to the .img.zst builder, and x86
sintra-installed is byte-identically unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SGUJJjBDuYwRaWSkFdK3bm
This commit is contained in:
Padreug 2026-08-17 08:41:52 +02:00
commit d8680f67ae

107
flake.nix
View file

@ -283,30 +283,36 @@
];
};
# Raspberry Pi 5 (aarch64) installed config — the DIY Pi build. Mirrors
# mkInstalledConfig's runtime (bitspire service, env activation, electron
# service override, swap) on aarch64 + Pi hardware, but deliberately drops
# the x86 fleet machinery for a first bring-up: no determinate/autoUpgrade
# (not yet a managed fleet member) and no atm-tui (add once it publishes an
# aarch64 package). Builds an SD image; needs an aarch64 builder (native
# Pi / arm box / binfmt emulation) — the app closure won't build on x86.
mkPiConfig = machineModel:
let
# Raspberry Pi 5 (aarch64) DIY build. Two products from one shared runtime:
# - mkPiInstalled: the in-place rebuild target. `nixos-rebuild switch
# --flake .#rpi5-installed` (or the git+ssh remote form) targets this.
# Declares the flashed media's own root fs (NIXOS_SD / FIRMWARE) and
# NOTHING image-specific, so a switch on a running Pi never trips over
# the sd-image builder.
# - mkPiImage: the same runtime + the aarch64 sd-image module, whose
# system.build.sdImage is the flashable artifact. The module supplies
# its OWN NIXOS_SD/FIRMWARE fileSystems + u-boot firmware, so we must
# not re-declare the root fs here (double definition = eval conflict).
#
# The runtime mirrors mkInstalledConfig (bitspire service, env activation,
# electron override, cache substituters) on aarch64 + Pi hardware, but
# deliberately drops the x86 fleet machinery for first bring-up: no
# determinate, no atm-tui (add once it ships an aarch64 package). Any Pi
# build needs an aarch64 builder (native Pi / arm box / binfmt emulation) —
# the app closure won't build on x86.
mkPiRuntime = machineModel: {
atm-app = mkAtmAppAarch64 {
model = machineModel;
fiatCode = fiatCodeForModel.${machineModel} or "USD";
};
fiatCode = fiatCodeForModel.${machineModel} or "USD";
in
nixpkgs.lib.nixosSystem {
system = "aarch64-linux";
specialArgs = {
pkgs-unstable = pkgsUnstableAarch64;
inherit atm-app;
};
modules = [
# Shared module list (everything EXCEPT the root fs and the sd-image
# builder). atm-app/fiatCode are threaded in so both products share one
# evaluated app closure.
piBaseModules = { machineModel, atm-app, fiatCode }: [
nixos-hardware.nixosModules.raspberry-pi-5
(nixpkgs + "/nixos/modules/installer/sd-card/sd-image-aarch64.nix")
./deploy/nixos/configuration.nix
./deploy/nixos/bitspire-atm.nix
./deploy/nixos/hardware/raspberry-pi-5.nix
@ -326,6 +332,21 @@
boot.kernel.sysctl."kernel.unprivileged_userns_clone" = 1;
security.sudo.wheelNeedsPassword = false;
# Pull from the aiolabs binary cache so a `nixos-rebuild switch`
# (local or the git+ssh remote form) substitutes the heavy aarch64
# closure instead of compiling on the Pi. Mirrors the x86 fleet's
# nix.settings, minus max-jobs/timeout — the Pi 5 can actually build
# locally if it must, so we don't want the 60s watchdog killing a
# legitimate first build.
nix.settings = {
trusted-users = [ "root" "bitspire" ];
substituters = [ "https://cache.nixos.org" "https://aiolabs.cachix.org" ];
trusted-public-keys = [
"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="
"aiolabs.cachix.org-1:PrAjsGU9PE77tFKP2+iO+mgR88c4xv3utM9JmpTblUQ="
];
};
# Same first-boot env seed as the x86 installed configs.
system.activationScripts.bitspire-env = ''
mkdir -p /var/lib/bitspire
@ -367,6 +388,47 @@
services.openssh.settings.PasswordAuthentication = lib.mkForce true;
})
];
# In-place rebuild target — declares the flashed media's own filesystems
# (the labels mkPiImage's sd-image module writes), no image builder.
mkPiInstalled = machineModel:
let rt = mkPiRuntime machineModel; in
nixpkgs.lib.nixosSystem {
system = "aarch64-linux";
specialArgs = {
pkgs-unstable = pkgsUnstableAarch64;
inherit (rt) atm-app;
};
modules = (piBaseModules { inherit machineModel; inherit (rt) atm-app fiatCode; }) ++ [
{
fileSystems."/" = {
device = "/dev/disk/by-label/NIXOS_SD";
fsType = "ext4";
};
fileSystems."/boot/firmware" = {
device = "/dev/disk/by-label/FIRMWARE";
fsType = "vfat";
options = [ "nofail" "noauto" ];
};
}
];
};
# Flashable SD/USB image — same runtime + the aarch64 sd-image builder,
# which brings its own NIXOS_SD/FIRMWARE fileSystems and the u-boot
# firmware. Its system.build.sdImage is exposed as
# packages.aarch64-linux.sd-image-rpi5.
mkPiImage = machineModel:
let rt = mkPiRuntime machineModel; in
nixpkgs.lib.nixosSystem {
system = "aarch64-linux";
specialArgs = {
pkgs-unstable = pkgsUnstableAarch64;
inherit (rt) atm-app;
};
modules = (piBaseModules { inherit machineModel; inherit (rt) atm-app fiatCode; }) ++ [
(nixpkgs + "/nixos/modules/installer/sd-card/sd-image-aarch64.nix")
];
};
in
{
@ -401,8 +463,13 @@
batm3-installed = mkInstalledConfig "batm3" ./deploy/nixos/hardware/batm3.nix;
# Raspberry Pi 5 (aarch64) DIY build — Apex 7600 / NV10 over USB-serial.
# Build the SD image via packages.aarch64-linux.sd-image-rpi5.
rpi5-installed = mkPiConfig "rpi5";
# rpi5-installed → in-place rebuild target:
# sudo nixos-rebuild switch --flake \
# "git+ssh://forgejo@git.atitlan.io/aiolabs/bitspire.git?ref=<branch>#rpi5-installed"
# rpi5-image → source of the flashable image
# (packages.aarch64-linux.sd-image-rpi5).
rpi5-installed = mkPiInstalled "rpi5";
rpi5-image = mkPiImage "rpi5";
# USB-bootable variant of batm3-installed. This is the config the
# flashed USB stick actually runs — distinct fs labels so stage-1 can't
@ -623,7 +690,7 @@
# / arm box / `boot.binfmt` emulation on this x86 host):
# nix build .#packages.aarch64-linux.sd-image-rpi5
packages.aarch64-linux = {
sd-image-rpi5 = self.nixosConfigurations.rpi5-installed.config.system.build.sdImage;
sd-image-rpi5 = self.nixosConfigurations.rpi5-image.config.system.build.sdImage;
atm-app-rpi5 = mkAtmAppAarch64 { model = "rpi5"; fiatCode = "USD"; };
};
}