feat(deploy): add NixOS live USB ISO for ATM hardware testing

Add NixOS configuration to build a bootable live USB ISO that runs the
ATM Electron app in kiosk mode on physical hardware (UpBoard). The ISO
boots from squashfs, auto-starts X11/openbox, and launches Electron in
production mode.

Key changes:
- deploy/nixos/live.nix: Live USB module (squashfs+tmpfs, no disk install)
- deploy/nixos/flake.nix: Nix flake with ISO build output
- deploy/nixos/provision-atm.sh: Auto-provision LP credentials via API
- deploy/nixos/build-iso.sh: End-to-end build workflow script
- apps/machine: Fix Electron production mode (ELECTRON_FORCE_PROD),
  Vue Router hash mode for file:// protocol, relative asset paths

Build: cd deploy/nixos && bash build-iso.sh
Test:  qemu-system-x86_64 -enable-kvm -m 2G -cdrom result/iso/*.iso

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Patrick Mulligan 2026-02-18 20:11:23 -05:00
commit 19d43c2939
13 changed files with 1203 additions and 3 deletions

181
deploy/nixos/live.nix Normal file
View file

@ -0,0 +1,181 @@
# Lamassu ATM Live USB Configuration
# Bootable ISO for testing on physical hardware (UpBoard) without installing to disk.
# Builds with: nix build .#iso
#
# Does NOT import hardware/upboard.nix (its fileSystems conflict with live boot).
# Instead, duplicates only the hardware-relevant kernel modules and GPU config.
{ config, lib, pkgs, pkgs-unstable, nixpkgs, ... }:
let
# Pre-built Electron app copied into the Nix store.
# Build first: pnpm run build --filter=@lamassu/machine
# Requires --impure: reads MACHINE_DIR env var to find build artifacts (dist/ is gitignored)
machineDir = builtins.getEnv "MACHINE_DIR";
atm-app = assert machineDir != ""
|| throw "MACHINE_DIR env var must be set (use build-iso.sh or: export MACHINE_DIR=/path/to/apps/machine)";
pkgs.runCommand "lamassu-atm-app" { } ''
mkdir -p $out
cp -r ${builtins.path { path = machineDir + "/dist"; name = "dist"; }} $out/dist
cp -r ${builtins.path { path = machineDir + "/dist-electron"; name = "dist-electron"; }} $out/dist-electron
cp ${builtins.path { path = machineDir + "/package.json"; name = "package.json"; }} $out/package.json
'';
# .env template with regtest defaults pointing to the dev machine
envTemplate = pkgs.writeText "lamassu-atm-env" ''
# Lamassu ATM Live USB Configuration
# Edit this file and restart: sudo systemctl restart lamassu-atm
#
# Dev machine LAN address (adjust to your network)
VITE_RELAY_URL=ws://192.168.1.190:7777
VITE_LIGHTNING_PUB_API_URL=http://192.168.1.190:1776
VITE_EXTENSION_API_URL=http://192.168.1.190:1777
# Lightning.Pub credentials (fill in after boot)
VITE_LIGHTNING_PUB_PUBKEY=
VITE_ATM_PRIVATE_KEY=
VITE_ADMIN_TOKEN=
VITE_APP_ID=
VITE_APP_TOKEN=
VITE_LINKING_TOKEN=
# Machine configuration
VITE_LAMASSU_MACHINE_MODEL=sintra
VITE_LAMASSU_FIAT_CODE=USD
# Hardware (uncomment and set for real hardware)
# VITE_LAMASSU_VALIDATOR_DEVICE=/dev/ttyUSB0
# VITE_LAMASSU_DISPENSER_DEVICE=/dev/ttyUSB1
# VITE_LAMASSU_CASSETTES=
# Force production mode when running via `electron /path`
ELECTRON_FORCE_PROD=1
# Display
DISPLAY=:0
'';
in
{
imports = [
# NixOS ISO image builder (nixpkgs path passed via specialArgs from flake.nix)
"${nixpkgs}/nixos/modules/installer/cd-dvd/iso-image.nix"
"${nixpkgs}/nixos/modules/profiles/all-hardware.nix"
# Reuse kiosk config (X11, openbox, users, networking)
./configuration.nix
# Reuse ATM systemd service module
./lamassu-atm.nix
];
# ISO image settings
isoImage = {
isoName = "lamassu-atm-live.iso";
makeEfiBootable = true;
makeBiosBootable = true;
squashfsCompression = "zstd -Xcompression-level 6";
};
# No fileSystems override needed — iso-image.nix handles squashfs + tmpfs root.
# We don't import hardware/upboard.nix, so there are no conflicting disk mounts.
# Boot: UpBoard-relevant kernel modules (from hardware/upboard.nix) without disk mounts
boot = {
initrd.availableKernelModules = [
"xhci_pci"
"ahci"
"usb_storage"
"sd_mod"
"sdhci_pci"
"i915"
"squashfs"
"iso9660"
"loop"
];
kernelModules = [
"kvm-intel"
"i2c-dev"
"spi-dev"
];
kernelParams = [
"i915.enable_psr=0"
"quiet"
"splash"
];
};
# Intel GPU support (from hardware/upboard.nix)
# NB: nixos-24.05 uses hardware.opengl, not hardware.graphics
hardware = {
opengl = {
enable = true;
extraPackages = with pkgs; [
intel-media-driver
vaapiIntel
vaapiVdpau
libvdpau-va-gl
];
};
enableRedistributableFirmware = true;
cpu.intel.updateMicrocode = true;
};
# Performance governor for responsive kiosk
powerManagement = {
enable = true;
cpuFreqGovernor = "performance";
};
# Disable suspend/hibernate
systemd.targets = {
sleep.enable = false;
suspend.enable = false;
hibernate.enable = false;
hybrid-sleep.enable = false;
};
# Enable the ATM service with live USB paths
services.lamassu-atm = {
enable = true;
appDir = "${atm-app}";
};
# Allow unprivileged user namespaces (Electron sandbox needs this)
boot.kernel.sysctl."kernel.unprivileged_userns_clone" = 1;
# Override the systemd service for live USB environment
systemd.services.lamassu-atm = {
serviceConfig = {
EnvironmentFile = lib.mkForce "/var/lib/lamassu-atm/.env";
# Electron needs --no-sandbox in the live/testing environment
ExecStart = lib.mkForce "${pkgs-unstable.electron}/bin/electron --no-sandbox --disable-gpu-sandbox ${atm-app}";
# Disable all security hardening that conflicts with Electron
NoNewPrivileges = lib.mkForce false;
ProtectSystem = lib.mkForce false;
ProtectHome = lib.mkForce false;
PrivateTmp = lib.mkForce false;
};
};
# Install the .env template on first boot
system.activationScripts.lamassu-env = ''
mkdir -p /var/lib/lamassu-atm
if [ ! -f /var/lib/lamassu-atm/.env ]; then
cp ${envTemplate} /var/lib/lamassu-atm/.env
chmod 600 /var/lib/lamassu-atm/.env
chown lamassu:lamassu /var/lib/lamassu-atm/.env
fi
'';
# Allow SSH with password for initial setup on the live system
services.openssh.settings.PasswordAuthentication = lib.mkForce true;
# Serial port udev rules (from hardware/upboard.nix)
services.udev.extraRules = lib.mkAfter ''
KERNEL=="ttyS[0-9]*", MODE="0666"
KERNEL=="ttyUSB[0-9]*", MODE="0666"
KERNEL=="ttyACM[0-9]*", MODE="0666"
'';
}