feat(deploy): add aarch64 Raspberry Pi 5 target (sd-image)

Proper aarch64 NixOS target for the DIY Pi 5 build, wired additively so
the x86 fleet path is untouched (both the new Pi sd-image and the
existing sintra-installed still evaluate cleanly):

- nixos-hardware input (raspberry-pi-5 module) for Pi kernel/firmware/GPU.
- aarch64 pkgs + pkgs-unstable + mkAtmApp instances (parallel to x86).
- mkPiConfig: aarch64 nixosSystem reusing the shared configuration.nix +
  bitspire-atm service, replicating the installed-config runtime (bitspire
  service, first-boot env seed, electron service override, swap). Drops
  the x86 fleet machinery for a first bring-up: no determinate/autoUpgrade
  (not yet fleet-managed) and no atm-tui (needs an aarch64 package).
- raspberry-pi-5.nix hardware module: extlinux boot, vc4/v3d KMS for X,
  primary UART free for a GPIO-wired validator, no-suspend, and stable
  /dev/ttyValidator* udev symlinks for USB-serial validator adapters
  (Apex 7600 RS-232 via adapter, NV10 USB+).
- nixosConfigurations.rpi5-installed + packages.aarch64-linux.sd-image-rpi5.

BUILD NOTE: the app closure (aarch64 electron/native addons) needs an
aarch64 builder — a native Pi/arm box or `boot.binfmt` qemu emulation on
an x86 host. Config evaluates on x86; it just can't build there.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018ivBosaWmv8vwFE7ejrdHW
This commit is contained in:
Padreug 2026-08-16 21:30:53 +02:00
commit a77bae50ee
3 changed files with 217 additions and 2 deletions

122
flake.nix
View file

@ -36,9 +36,12 @@
url = "git+ssh://forgejo@git.atitlan.io/aiolabs/atm-tui.git";
inputs.nixpkgs.follows = "nixpkgs-unstable";
};
# Raspberry Pi 5 (aarch64) hardware support for the DIY Pi build.
nixos-hardware.url = "github:NixOS/nixos-hardware";
};
outputs = { self, nixpkgs, nixpkgs-unstable, flake-utils, rust-overlay, devenv, determinate, atm-tui }:
outputs = { self, nixpkgs, nixpkgs-unstable, flake-utils, rust-overlay, devenv, determinate, atm-tui, nixos-hardware }:
let
system = "x86_64-linux";
@ -59,6 +62,24 @@
src = self;
};
# aarch64 (Raspberry Pi 5) toolchain — a parallel set of pkgs + app
# builder for the DIY Pi build. Kept fully separate from the x86 fleet
# path so nothing above changes.
pkgsAarch64 = import nixpkgs {
system = "aarch64-linux";
config.allowUnfree = true;
};
pkgsUnstableAarch64 = import nixpkgs-unstable {
system = "aarch64-linux";
config.allowUnfree = true;
overlays = [ (import rust-overlay) ];
};
mkAtmAppAarch64 = import ./nix/mkAtmApp.nix {
pkgs = pkgsAarch64;
pkgs-unstable = pkgsUnstableAarch64;
src = self;
};
# Fiat code per machine model
fiatCodeForModel = {
douro = "GTQ";
@ -261,6 +282,92 @@
})
];
};
# 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
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 = [
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
({ config, lib, pkgs, pkgs-unstable, ... }: {
services.bitspire = {
enable = true;
appDir = "${atm-app}";
};
environment.systemPackages = [
(pkgs.writeShellScriptBin "fund-atm" ''
exec ${pkgs-unstable.nodejs}/bin/node ${atm-app}/dist-electron/fund-atm.bundle.cjs "$@"
'')
];
environment.variables.ATM_DB_PATH = "/var/lib/bitspire/state.db";
boot.kernel.sysctl."kernel.unprivileged_userns_clone" = 1;
security.sudo.wheelNeedsPassword = false;
# Same first-boot env seed as the x86 installed configs.
system.activationScripts.bitspire-env = ''
mkdir -p /var/lib/bitspire
if [ ! -f /var/lib/bitspire/.env ]; then
cp ${pkgs.writeText "bitspire-env-default" (''
VITE_LAMASSU_MACHINE_MODEL=${machineModel}
VITE_LAMASSU_FIAT_CODE=${fiatCode}
VITE_SPIRE_SEED=
ELECTRON_FORCE_PROD=1
DISPLAY=:0
'' + pkgs.lib.optionalString (config.services.bitspire.relayUrl != "") ''
VITE_RELAY_URL=${config.services.bitspire.relayUrl}
'' + pkgs.lib.optionalString (config.services.bitspire.lnbitsServerPubkey != "") ''
VITE_LNBITS_SERVER_PUBKEY=${config.services.bitspire.lnbitsServerPubkey}
'')} /var/lib/bitspire/.env
chmod 600 /var/lib/bitspire/.env
chown bitspire:bitspire /var/lib/bitspire/.env
fi
'';
# Electron runtime override (same flags as the x86 fleet, aarch64
# electron). No eDP display-reset here — that's UP-Board-specific;
# the Pi drives HDMI/DSI directly.
systemd.services.bitspire.serviceConfig = {
EnvironmentFile = lib.mkForce "/var/lib/bitspire/.env";
Environment = "LD_LIBRARY_PATH=${pkgs.stdenv.cc.cc.lib}/lib";
ExecStart = lib.mkForce "${pkgs-unstable.electron}/bin/electron --no-sandbox --disable-gpu-sandbox --disable-software-rasterizer --enable-logging ${atm-app}";
MemoryMax = lib.mkForce "2G";
NoNewPrivileges = lib.mkForce false;
ProtectSystem = lib.mkForce false;
ProtectHome = lib.mkForce false;
PrivateTmp = lib.mkForce false;
DevicePolicy = lib.mkForce "auto";
DeviceAllow = lib.mkForce [ "char-* rw" ];
};
swapDevices = [{ device = "/var/swapfile"; size = 2048; }];
boot.tmp.cleanOnBoot = true;
services.openssh.settings.PasswordAuthentication = lib.mkForce true;
})
];
};
in
{
# ── NixOS Configurations (top-level, not per-system) ──────────
@ -293,6 +400,10 @@
sintra-installed = mkInstalledConfig "sintra" ./deploy/nixos/hardware/upboard.nix;
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";
# 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
@ -506,6 +617,15 @@
# Backwards compat
iso = self.nixosConfigurations.douro.config.system.build.isoImage;
};
# ── Packages (aarch64-linux — Raspberry Pi 5 build) ───────────
# Flashable SD image for the Pi 5. Build on an aarch64 builder (native Pi
# / 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;
atm-app-rpi5 = mkAtmAppAarch64 { model = "rpi5"; fiatCode = "USD"; };
};
}
//
# ── Dev shells (per-system via flake-utils) ───────────────────