refactor(nix): consolidate deploy flake into root flake with pure ISO builds

Move deploy/nixos/flake.nix into the root flake.nix, adding mkAtmApp
for pure Nix builds of the Electron app (no local pnpm needed). Simplify
build-iso.sh to a thin wrapper around `nix build .#iso-<model>`. Add
douro hardware configuration. Streamline live.nix to consume the
Nix-built app package.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Patrick Mulligan 2026-02-27 10:58:43 -05:00
commit 625cebed25
8 changed files with 832 additions and 342 deletions

View file

@ -10,100 +10,27 @@
# 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, machineModel ? "douro", ... }:
{ config, lib, pkgs, pkgs-unstable, nixpkgs, machineModel ? "douro", atm-app, ... }:
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";
# Resolve pnpm virtual store paths for native modules
pnpmStore = machineDir + "/../../node_modules/.pnpm";
betterSqliteStore = pnpmStore + "/better-sqlite3@11.10.0/node_modules";
bindingsStore = pnpmStore + "/bindings@1.5.0/node_modules";
fileUriStore = pnpmStore + "/file-uri-to-path@1.0.0/node_modules";
# HAL package (workspace dep, resolved via symlink in pnpm)
halDir = machineDir + "/../../packages/hal";
# serialport and its transitive native deps (required by @lamassu/hal at runtime)
serialportStore = pnpmStore + "/serialport@12.0.0/node_modules";
serialportStreamStore = pnpmStore + "/@serialport+stream@12.0.0/node_modules/@serialport";
serialportBindingsCppStore = pnpmStore + "/@serialport+bindings-cpp@12.0.1/node_modules/@serialport";
serialportBindingsIfaceStore = pnpmStore + "/@serialport+bindings-interface@1.2.2/node_modules/@serialport";
serialportBindingMockStore = pnpmStore + "/@serialport+binding-mock@10.2.2/node_modules/@serialport";
# serialport parser sub-packages (barrel import in serialport@12 eagerly requires all of these)
parserStore = suffix: pnpmStore + "/@serialport+${suffix}@12.0.0/node_modules/@serialport";
debugStore = pnpmStore + "/debug@4.3.4/node_modules";
msStore = pnpmStore + "/ms@2.1.2/node_modules";
nodeAddonApiStore = pnpmStore + "/node-addon-api@7.0.0/node_modules";
nodeGypBuildStore = pnpmStore + "/node-gyp-build@4.6.0/node_modules";
lodashEsStore = pnpmStore + "/lodash-es@4.17.23/node_modules";
# Fiat code per machine model
# Fiat code per machine model (for envTemplate display only)
fiatCodeForModel = {
douro = "GTQ";
tejo = "GTQ";
sintra = "EUR";
}.${machineModel} or "USD";
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/node_modules/@lamassu $out/node_modules/@serialport
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
# Native modules required at runtime (not bundled by Vite)
# Resolved from pnpm virtual store to get real files (not symlinks)
cp -rL ${builtins.path { path = betterSqliteStore + "/better-sqlite3"; name = "better-sqlite3"; }} $out/node_modules/better-sqlite3
cp -rL ${builtins.path { path = bindingsStore + "/bindings"; name = "bindings"; }} $out/node_modules/bindings
cp -rL ${builtins.path { path = fileUriStore + "/file-uri-to-path"; name = "file-uri-to-path"; }} $out/node_modules/file-uri-to-path
# @lamassu/hal (workspace package, dynamically imported for hardware access)
mkdir -p $out/node_modules/@lamassu/hal/dist
cp -rL ${builtins.path { path = halDir + "/dist"; name = "hal-dist"; }}/* $out/node_modules/@lamassu/hal/dist/
cp ${builtins.path { path = halDir + "/package.json"; name = "hal-package-json"; }} $out/node_modules/@lamassu/hal/package.json
# serialport and transitive deps (native module chain for RS232 hardware)
cp -rL ${builtins.path { path = serialportStore + "/serialport"; name = "serialport"; }} $out/node_modules/serialport
cp -rL ${builtins.path { path = serialportStreamStore + "/stream"; name = "serialport-stream"; }} $out/node_modules/@serialport/stream
# bindings-cpp: copy only dist/ + prebuilds/ + package.json (build/ has broken symlinks)
mkdir -p $out/node_modules/@serialport/bindings-cpp
cp -rL ${builtins.path { path = serialportBindingsCppStore + "/bindings-cpp/dist"; name = "serialport-bindings-cpp-dist"; }} $out/node_modules/@serialport/bindings-cpp/dist
cp -rL ${builtins.path { path = serialportBindingsCppStore + "/bindings-cpp/prebuilds"; name = "serialport-bindings-cpp-prebuilds"; }} $out/node_modules/@serialport/bindings-cpp/prebuilds
cp ${builtins.path { path = serialportBindingsCppStore + "/bindings-cpp/package.json"; name = "serialport-bindings-cpp-pkg"; }} $out/node_modules/@serialport/bindings-cpp/package.json
cp -rL ${builtins.path { path = serialportBindingsIfaceStore + "/bindings-interface"; name = "serialport-bindings-interface"; }} $out/node_modules/@serialport/bindings-interface
cp -rL ${builtins.path { path = serialportBindingMockStore + "/binding-mock"; name = "serialport-binding-mock"; }} $out/node_modules/@serialport/binding-mock
cp -rL ${builtins.path { path = debugStore + "/debug"; name = "debug"; }} $out/node_modules/debug
cp -rL ${builtins.path { path = msStore + "/ms"; name = "ms"; }} $out/node_modules/ms
cp -rL ${builtins.path { path = nodeAddonApiStore + "/node-addon-api"; name = "node-addon-api"; }} $out/node_modules/node-addon-api
cp -rL ${builtins.path { path = nodeGypBuildStore + "/node-gyp-build"; name = "node-gyp-build"; }} $out/node_modules/node-gyp-build
cp -rL ${builtins.path { path = lodashEsStore + "/lodash-es"; name = "lodash-es"; }} $out/node_modules/lodash-es
# serialport parser sub-packages (barrel import requires all 10)
cp -rL ${builtins.path { path = parserStore "parser-byte-length" + "/parser-byte-length"; name = "parser-byte-length"; }} $out/node_modules/@serialport/parser-byte-length
cp -rL ${builtins.path { path = parserStore "parser-cctalk" + "/parser-cctalk"; name = "parser-cctalk"; }} $out/node_modules/@serialport/parser-cctalk
cp -rL ${builtins.path { path = parserStore "parser-delimiter" + "/parser-delimiter"; name = "parser-delimiter"; }} $out/node_modules/@serialport/parser-delimiter
cp -rL ${builtins.path { path = parserStore "parser-inter-byte-timeout" + "/parser-inter-byte-timeout"; name = "parser-inter-byte-timeout"; }} $out/node_modules/@serialport/parser-inter-byte-timeout
cp -rL ${builtins.path { path = parserStore "parser-packet-length" + "/parser-packet-length"; name = "parser-packet-length"; }} $out/node_modules/@serialport/parser-packet-length
cp -rL ${builtins.path { path = parserStore "parser-readline" + "/parser-readline"; name = "parser-readline"; }} $out/node_modules/@serialport/parser-readline
cp -rL ${builtins.path { path = parserStore "parser-ready" + "/parser-ready"; name = "parser-ready"; }} $out/node_modules/@serialport/parser-ready
cp -rL ${builtins.path { path = parserStore "parser-regex" + "/parser-regex"; name = "parser-regex"; }} $out/node_modules/@serialport/parser-regex
cp -rL ${builtins.path { path = parserStore "parser-slip-encoder" + "/parser-slip-encoder"; name = "parser-slip-encoder"; }} $out/node_modules/@serialport/parser-slip-encoder
cp -rL ${builtins.path { path = parserStore "parser-spacepacket" + "/parser-spacepacket"; name = "parser-spacepacket"; }} $out/node_modules/@serialport/parser-spacepacket
'';
# .env template with regtest defaults pointing to the dev machine
# .env template — runtime secrets are provisioned later via provision-atm.sh.
# Only non-secret defaults and display vars go here.
envTemplate = pkgs.writeText "lamassu-atm-env" ''
VITE_RELAY_URL=wss://relay.atm.aiolabs.dev
VITE_LIGHTNING_PUB_PUBKEY=64b0d9b1af689e99e4b8a23ee7b77715b394740a6830bdccf4718a060a53649a
VITE_LIGHTNING_PUB_API_URL=https://lp.atm.aiolabs.dev
VITE_ADMIN_TOKEN=lamassu-dev-admin-token
VITE_ATM_PRIVATE_KEY=f391a2c3fc734f443b0f685688a0441b5fb9805853c0023f570c5a3c6412b136
VITE_EXTENSION_API_URL=https://lp-ext.atm.aiolabs.dev
VITE_APP_ID=6016dadc6c677f131bc82cc0cb780d2b1090b83b8b7d0c972e469010270a4208
VITE_LNDCONNECT_URL=lndconnect://lnd.atm.aiolabs.dev:443?cert=&macaroon=AgEDbG5kAvgBAwoQjLYgcUni_8EKmwpX_vwOWxIBMBoWCgdhZGRyZXNzEgRyZWFkEgV3cml0ZRoTCgRpbmZvEgRyZWFkEgV3cml0ZRoXCghpbnZvaWNlcxIEcmVhZBIFd3JpdGUaIQoIbWFjYXJvb24SCGdlbmVyYXRlEgRyZWFkEgV3cml0ZRoWCgdtZXNzYWdlEgRyZWFkEgV3cml0ZRoXCghvZmZjaGFpbhIEcmVhZBIFd3JpdGUaFgoHb25jaGFpbhIEcmVhZBIFd3JpdGUaFAoFcGVlcnMSBHJlYWQSBXdyaXRlGhgKBnNpZ25lchIIZ2VuZXJhdGUSBHJlYWQAAAYgClsDux9_gPaKUK7PI54y-sTwt5WGmSzrzfKaKamwvK0
VITE_RELAY_URL=
VITE_LIGHTNING_PUB_PUBKEY=
VITE_LIGHTNING_PUB_API_URL=
VITE_ADMIN_TOKEN=
VITE_ATM_PRIVATE_KEY=
VITE_EXTENSION_API_URL=
VITE_APP_ID=
VITE_LNDCONNECT_URL=
VITE_LAMASSU_MACHINE_MODEL=${machineModel}
VITE_LAMASSU_FIAT_CODE=${fiatCodeForModel}
ELECTRON_FORCE_PROD=1