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

@ -2,6 +2,7 @@
# Build a bootable NixOS Live USB ISO for testing the ATM Electron app
# on physical hardware.
#
# The build is fully pure — no local pnpm or .env manipulation needed.
# After booting, provision credentials with: bash provision-atm.sh <atm-ip>
#
# Usage: bash build-iso.sh <model>
@ -19,100 +20,26 @@ if [ -z "$MODEL" ]; then
exit 1
fi
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
MACHINE_DIR="$REPO_ROOT/apps/machine"
ENV_FILE="$MACHINE_DIR/.env"
ENV_BACKUP=""
echo "=== Building Lamassu ATM Live USB ISO (model: $MODEL) ==="
# Step 1: Set machine-specific .env for Vite build (VITE_ vars are compile-time)
echo ""
echo "--- Step 1: Configuring .env for $MODEL ---"
if [ -f "$ENV_FILE" ]; then
ENV_BACKUP="$ENV_FILE.build-backup"
cp "$ENV_FILE" "$ENV_BACKUP"
echo "Backed up existing .env"
fi
# Write model-specific env (production endpoints baked into the build)
cat > "$ENV_FILE" << 'ENVEOF'
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
ENVEOF
# Append model-specific config
case "$MODEL" in
douro)
cat >> "$ENV_FILE" << 'EOF'
VITE_LAMASSU_MACHINE_MODEL=douro
VITE_LAMASSU_FIAT_CODE=GTQ
EOF
;;
tejo)
cat >> "$ENV_FILE" << 'EOF'
VITE_LAMASSU_MACHINE_MODEL=tejo
VITE_LAMASSU_FIAT_CODE=GTQ
EOF
;;
sintra)
cat >> "$ENV_FILE" << 'EOF'
VITE_LAMASSU_MACHINE_MODEL=sintra
VITE_LAMASSU_FIAT_CODE=EUR
EOF
;;
douro|tejo|sintra) ;;
*)
echo "ERROR: Unknown model '$MODEL'. Use 'douro', 'tejo', or 'sintra'."
# Restore backup
if [ -n "$ENV_BACKUP" ]; then
mv "$ENV_BACKUP" "$ENV_FILE"
fi
exit 1
;;
esac
echo "Set VITE_LAMASSU_MACHINE_MODEL=$MODEL"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
# Step 2: Build the Electron app (with model-specific .env baked in)
echo "=== Building Lamassu ATM Live USB ISO (model: $MODEL) ==="
echo ""
echo "--- Step 2: Building Electron app ---"
echo "This is a pure Nix build — no local pnpm required."
echo ""
cd "$REPO_ROOT"
pnpm run build --filter=@lamassu/machine
nix build ".#iso-${MODEL}" --show-trace
# Restore original .env
if [ -n "$ENV_BACKUP" ]; then
mv "$ENV_BACKUP" "$ENV_FILE"
echo "Restored original .env"
fi
# Step 3: Verify build outputs exist
echo ""
echo "--- Step 3: Verifying build outputs ---"
if [ ! -d "$MACHINE_DIR/dist" ]; then
echo "ERROR: $MACHINE_DIR/dist not found. Build failed?"
exit 1
fi
if [ ! -d "$MACHINE_DIR/dist-electron" ]; then
echo "ERROR: $MACHINE_DIR/dist-electron not found. Build failed?"
exit 1
fi
echo "OK: dist/ and dist-electron/ present"
# Step 4: Build the NixOS ISO
echo ""
echo "--- Step 4: Building NixOS ISO for $MODEL (this takes a while) ---"
cd "$SCRIPT_DIR"
export MACHINE_DIR="$MACHINE_DIR"
nix build ".#iso-${MODEL}" --impure --show-trace
# Step 5: Print results
# Print results
ISO_PATH=$(ls result/iso/*.iso 2>/dev/null | head -1)
if [ -z "$ISO_PATH" ]; then
echo "ERROR: ISO not found in result/iso/"

View file

@ -1,44 +0,0 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1735563628,
"narHash": "sha256-OnSAY7XDSx7CtDoqNh8jwVwh4xNL/2HaJxGjryLWzX8=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "b134951a4c9f3c995fd7be05f3243f8ecd65d798",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-24.05",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-unstable": {
"locked": {
"lastModified": 1771177547,
"narHash": "sha256-trTtk3WTOHz7hSw89xIIvahkgoFJYQ0G43IlqprFoMA=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "ac055f38c798b0d87695240c7b761b82fc7e5bc2",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs",
"nixpkgs-unstable": "nixpkgs-unstable"
}
}
},
"root": "root",
"version": 7
}

View file

@ -1,71 +0,0 @@
{
description = "Lamassu Next ATM - NixOS Deployment";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
# For Electron/Node.js packaging
nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
};
outputs = { self, nixpkgs, nixpkgs-unstable }:
let
system = "x86_64-linux";
pkgs = import nixpkgs {
inherit system;
config.allowUnfree = true;
};
pkgs-unstable = import nixpkgs-unstable {
inherit system;
config.allowUnfree = true;
};
# Helper to create a live USB config for a specific machine model
mkLiveConfig = machineModel: nixpkgs.lib.nixosSystem {
inherit system;
specialArgs = { inherit pkgs-unstable nixpkgs machineModel; };
modules = [ ./live.nix ];
};
in
{
# NixOS configuration for UP Board ATM (installed to disk)
nixosConfigurations.lamassu-atm = nixpkgs.lib.nixosSystem {
inherit system;
specialArgs = { inherit pkgs-unstable; };
modules = [
./hardware/upboard.nix
./configuration.nix
./lamassu-atm.nix
];
};
# Live USB configurations per machine model
nixosConfigurations.lamassu-live-douro = mkLiveConfig "douro";
nixosConfigurations.lamassu-live-tejo = mkLiveConfig "tejo";
nixosConfigurations.lamassu-live-sintra = mkLiveConfig "sintra";
# Keep generic for backwards compat
nixosConfigurations.lamassu-live = mkLiveConfig "douro";
# Standalone module for importing into existing NixOS configs
nixosModules.default = import ./lamassu-atm.nix;
nixosModules.lamassu-atm = import ./lamassu-atm.nix;
packages.${system} = {
# ISO images per machine model
iso-douro = self.nixosConfigurations.lamassu-live-douro.config.system.build.isoImage;
iso-tejo = self.nixosConfigurations.lamassu-live-tejo.config.system.build.isoImage;
iso-sintra = self.nixosConfigurations.lamassu-live-sintra.config.system.build.isoImage;
# Default (backwards compat)
iso = self.nixosConfigurations.lamassu-live.config.system.build.isoImage;
# SD card image (if needed)
sdcard = self.nixosConfigurations.lamassu-atm.config.system.build.sdImage;
};
};
}

View file

@ -0,0 +1,89 @@
# Douro (Bay Trail) Hardware Configuration
# Bay Trail Atom SoC with eDP panel, mSATA internal storage.
#
# Kernel 5.15 LTS required: i915 eDP display regression in 6.x kernels
# causes blank screen on Bay Trail with embedded DisplayPort panels.
{ config, lib, pkgs, ... }:
{
boot = {
loader = {
systemd-boot.enable = true;
efi.canTouchEfiVariables = true;
timeout = 3;
};
# Bay Trail needs 5.15 LTS (i915 eDP regression in 6.x)
kernelPackages = pkgs.linuxPackages_5_15;
initrd.availableKernelModules = [
"xhci_pci"
"ahci"
"usb_storage"
"sd_mod"
"sdhci_pci"
"i915"
];
kernelModules = [
"kvm-intel"
"i2c-dev"
"spi-dev"
];
kernelParams = [
"i915.enable_psr=0"
"vt.handoff=7" # Bay Trail: preserve BIOS display init
"quiet"
"splash"
];
};
# Disk layout: GPT with ESP (sda1) + ext4 root (sda2)
fileSystems."/" = {
device = "/dev/disk/by-label/nixos";
fsType = "ext4";
};
# make-disk-image.nix labels the ESP as "ESP" (not "boot")
fileSystems."/boot" = {
device = "/dev/disk/by-label/ESP";
fsType = "vfat";
};
# 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;
};
powerManagement = {
enable = true;
cpuFreqGovernor = "performance";
};
# Disable suspend/hibernate for kiosk
systemd.targets = {
sleep.enable = false;
suspend.enable = false;
hibernate.enable = false;
hybrid-sleep.enable = false;
};
# Serial port access for bill validator/dispenser
services.udev.extraRules = lib.mkAfter ''
KERNEL=="ttyS[0-9]*", MODE="0666"
KERNEL=="ttyUSB[0-9]*", MODE="0666"
KERNEL=="ttyACM[0-9]*", MODE="0666"
'';
}

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

348
flake.lock generated Normal file
View file

@ -0,0 +1,348 @@
{
"nodes": {
"cachix": {
"inputs": {
"devenv": [
"devenv"
],
"flake-compat": [
"devenv",
"flake-compat"
],
"git-hooks": [
"devenv",
"git-hooks"
],
"nixpkgs": [
"devenv",
"nixpkgs"
]
},
"locked": {
"lastModified": 1760971495,
"narHash": "sha256-IwnNtbNVrlZIHh7h4Wz6VP0Furxg9Hh0ycighvL5cZc=",
"owner": "cachix",
"repo": "cachix",
"rev": "c5bfd933d1033672f51a863c47303fc0e093c2d2",
"type": "github"
},
"original": {
"owner": "cachix",
"ref": "latest",
"repo": "cachix",
"type": "github"
}
},
"devenv": {
"inputs": {
"cachix": "cachix",
"flake-compat": "flake-compat",
"flake-parts": "flake-parts",
"git-hooks": "git-hooks",
"nix": "nix",
"nixd": "nixd",
"nixpkgs": [
"nixpkgs-unstable"
]
},
"locked": {
"lastModified": 1772125979,
"narHash": "sha256-Le5RmbvN+wWnmoRmrtFMpWaNft3GnsFEymxq7asEPP8=",
"owner": "cachix",
"repo": "devenv",
"rev": "6ff63ee1aaec6710af8083561f95c33e4de63e14",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "devenv",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1761588595,
"narHash": "sha256-XKUZz9zewJNUj46b4AJdiRZJAvSZ0Dqj2BNfXvFlJC4=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "f387cd2afec9419c8ee37694406ca490c3f34ee5",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-parts": {
"inputs": {
"nixpkgs-lib": [
"devenv",
"nixpkgs"
]
},
"locked": {
"lastModified": 1760948891,
"narHash": "sha256-TmWcdiUUaWk8J4lpjzu4gCGxWY6/Ok7mOK4fIFfBuU4=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "864599284fc7c0ba6357ed89ed5e2cd5040f0c04",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"flake-root": {
"locked": {
"lastModified": 1723604017,
"narHash": "sha256-rBtQ8gg+Dn4Sx/s+pvjdq3CB2wQNzx9XGFq/JVGCB6k=",
"owner": "srid",
"repo": "flake-root",
"rev": "b759a56851e10cb13f6b8e5698af7b59c44be26e",
"type": "github"
},
"original": {
"owner": "srid",
"repo": "flake-root",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"git-hooks": {
"inputs": {
"flake-compat": [
"devenv",
"flake-compat"
],
"gitignore": "gitignore",
"nixpkgs": [
"devenv",
"nixpkgs"
]
},
"locked": {
"lastModified": 1760663237,
"narHash": "sha256-BflA6U4AM1bzuRMR8QqzPXqh8sWVCNDzOdsxXEguJIc=",
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "ca5b894d3e3e151ffc1db040b6ce4dcc75d31c37",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "git-hooks.nix",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"devenv",
"git-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1709087332,
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"nix": {
"inputs": {
"flake-compat": [
"devenv",
"flake-compat"
],
"flake-parts": [
"devenv",
"flake-parts"
],
"git-hooks-nix": [
"devenv",
"git-hooks"
],
"nixpkgs": [
"devenv",
"nixpkgs"
],
"nixpkgs-23-11": [
"devenv"
],
"nixpkgs-regression": [
"devenv"
]
},
"locked": {
"lastModified": 1771532737,
"narHash": "sha256-H26FQmOyvIGnedfAioparJQD8Oe+/byD6OpUpnI/hkE=",
"owner": "cachix",
"repo": "nix",
"rev": "7eb6c427c7a86fdc3ebf9e6cbf2a84e80e8974fd",
"type": "github"
},
"original": {
"owner": "cachix",
"ref": "devenv-2.32",
"repo": "nix",
"type": "github"
}
},
"nixd": {
"inputs": {
"flake-parts": [
"devenv",
"flake-parts"
],
"flake-root": "flake-root",
"nixpkgs": [
"devenv",
"nixpkgs"
],
"treefmt-nix": "treefmt-nix"
},
"locked": {
"lastModified": 1763964548,
"narHash": "sha256-JTRoaEWvPsVIMFJWeS4G2isPo15wqXY/otsiHPN0zww=",
"owner": "nix-community",
"repo": "nixd",
"rev": "d4bf15e56540422e2acc7bc26b20b0a0934e3f5e",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixd",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1735563628,
"narHash": "sha256-OnSAY7XDSx7CtDoqNh8jwVwh4xNL/2HaJxGjryLWzX8=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "b134951a4c9f3c995fd7be05f3243f8ecd65d798",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-24.05",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-unstable": {
"locked": {
"lastModified": 1771923393,
"narHash": "sha256-Fy0+UXELv9hOE8WjYhJt8fMDLYTU2Dqn3cX4BwoGBos=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "ea7f1f06811ce7fcc81d6c6fd4213150c23edcf2",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"devenv": "devenv",
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
"nixpkgs-unstable": "nixpkgs-unstable",
"rust-overlay": "rust-overlay"
}
},
"rust-overlay": {
"inputs": {
"nixpkgs": [
"nixpkgs-unstable"
]
},
"locked": {
"lastModified": 1772075164,
"narHash": "sha256-93XcvAt+6p7aAq1ERlxD2T17zLGoYGo64KJYasGcpgc=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "07601339b15fa6810541c0e7dc2f3664d92a7ad0",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"treefmt-nix": {
"inputs": {
"nixpkgs": [
"devenv",
"nixd",
"nixpkgs"
]
},
"locked": {
"lastModified": 1734704479,
"narHash": "sha256-MMi74+WckoyEWBRcg/oaGRvXC9BVVxDZNRMpL+72wBI=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "65712f5af67234dad91a5a4baee986a8b62dbf8f",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "treefmt-nix",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

267
flake.nix
View file

@ -2,85 +2,226 @@
description = "Lamassu Next - Nostr-Native Lightning ATM";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
# Stable NixOS for the ATM OS base
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
# Unstable for Electron, Node.js, pnpm (latest versions)
nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
flake-utils.url = "github:numtide/flake-utils";
rust-overlay = {
url = "github:oxalica/rust-overlay";
inputs.nixpkgs.follows = "nixpkgs";
inputs.nixpkgs.follows = "nixpkgs-unstable";
};
devenv = {
url = "github:cachix/devenv";
inputs.nixpkgs.follows = "nixpkgs";
inputs.nixpkgs.follows = "nixpkgs-unstable";
};
};
outputs = { self, nixpkgs, flake-utils, rust-overlay, devenv }:
flake-utils.lib.eachDefaultSystem (system:
let
overlays = [ (import rust-overlay) ];
pkgs = import nixpkgs { inherit system overlays; };
outputs = { self, nixpkgs, nixpkgs-unstable, flake-utils, rust-overlay, devenv }:
let
system = "x86_64-linux";
rustToolchain = pkgs.rust-bin.stable.latest.default.override {
extensions = [ "rust-src" "rust-analyzer" ];
targets = [ "wasm32-unknown-unknown" ];
pkgs = import nixpkgs {
inherit system;
config.allowUnfree = true;
};
pkgs-unstable = import nixpkgs-unstable {
inherit system;
config.allowUnfree = true;
overlays = [ (import rust-overlay) ];
};
# Pure ATM app builder (no --impure needed)
mkAtmApp = import ./nix/mkAtmApp.nix {
inherit pkgs pkgs-unstable;
src = self;
};
# Fiat code per machine model
fiatCodeForModel = {
douro = "GTQ";
tejo = "GTQ";
sintra = "EUR";
};
lib = nixpkgs.lib;
# Helper to create a live USB NixOS config for a specific machine model
mkLiveConfig = machineModel:
let
atm-app = mkAtmApp {
model = machineModel;
fiatCode = fiatCodeForModel.${machineModel} or "USD";
};
in
nixpkgs.lib.nixosSystem {
inherit system;
specialArgs = {
inherit pkgs-unstable nixpkgs machineModel atm-app;
};
modules = [ ./deploy/nixos/live.nix ];
};
# Helper to create a disk-installed NixOS config for a specific machine model.
# Unlike live configs (squashfs + tmpfs), installed configs use ext4 root
# and support `nixos-rebuild switch` for in-place updates.
mkInstalledConfig = machineModel: hardwareModule:
let
atm-app = mkAtmApp {
model = machineModel;
fiatCode = fiatCodeForModel.${machineModel} or "USD";
};
fiatCode = fiatCodeForModel.${machineModel} or "USD";
in
nixpkgs.lib.nixosSystem {
inherit system;
specialArgs = {
inherit pkgs-unstable atm-app;
};
modules = [
hardwareModule
./deploy/nixos/configuration.nix
./deploy/nixos/lamassu-atm.nix
({ config, lib, pkgs, ... }: {
services.lamassu-atm = {
enable = true;
appDir = "${atm-app}";
};
# Electron sandbox needs unprivileged user namespaces
boot.kernel.sysctl."kernel.unprivileged_userns_clone" = 1;
# Env template — runtime secrets provisioned via provision-atm.sh
system.activationScripts.lamassu-env = ''
mkdir -p /var/lib/lamassu-atm
if [ ! -f /var/lib/lamassu-atm/.env ]; then
cp ${pkgs.writeText "lamassu-atm-env-default" ''
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=${fiatCode}
ELECTRON_FORCE_PROD=1
DISPLAY=:0
''} /var/lib/lamassu-atm/.env
chmod 600 /var/lib/lamassu-atm/.env
chown lamassu:lamassu /var/lib/lamassu-atm/.env
fi
'';
# Override systemd service for Electron runtime
systemd.services.lamassu-atm = {
serviceConfig = {
EnvironmentFile = lib.mkForce "/var/lib/lamassu-atm/.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-gpu --enable-logging ${atm-app}";
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" ];
};
};
# Reset eDP display output after X starts
systemd.services.display-reset = {
description = "Reset eDP display output";
after = [ "display-manager.service" ];
requires = [ "display-manager.service" ];
wantedBy = [ "graphical.target" ];
before = [ "lamassu-atm.service" ];
serviceConfig = {
Type = "oneshot";
User = "lamassu";
Environment = "DISPLAY=:0";
ExecStart = "${pkgs.bash}/bin/bash -c '${pkgs.xorg.xrandr}/bin/xrandr --output eDP-1 --off; sleep 1; ${pkgs.xorg.xrandr}/bin/xrandr --output eDP-1 --auto'";
};
};
# SSH with password for initial provisioning
services.openssh.settings.PasswordAuthentication = lib.mkForce true;
})
];
};
in
{
# ── NixOS Configurations (top-level, not per-system) ──────────
nixosConfigurations = {
# Ergonomic names: `nixos-rebuild switch --flake .#douro`
douro = mkLiveConfig "douro";
tejo = mkLiveConfig "tejo";
sintra = mkLiveConfig "sintra";
# Backwards compat
lamassu-live-douro = mkLiveConfig "douro";
lamassu-live-tejo = mkLiveConfig "tejo";
lamassu-live-sintra = mkLiveConfig "sintra";
lamassu-live = mkLiveConfig "douro";
# Installed-to-disk configs (proper GPT + systemd-boot, supports nixos-rebuild)
douro-installed = mkInstalledConfig "douro" ./deploy/nixos/hardware/douro.nix;
# UP Board (tejo) installed config
lamassu-atm = mkInstalledConfig "tejo" ./deploy/nixos/hardware/upboard.nix;
};
# ── Standalone NixOS module ───────────────────────────────────
nixosModules.default = import ./deploy/nixos/lamassu-atm.nix;
nixosModules.lamassu-atm = import ./deploy/nixos/lamassu-atm.nix;
# ── Packages (x86_64-linux only for ATM hardware) ─────────────
packages.${system} = {
# Pure ATM app derivations
atm-app-douro = mkAtmApp { model = "douro"; fiatCode = "GTQ"; };
atm-app-tejo = mkAtmApp { model = "tejo"; fiatCode = "GTQ"; };
atm-app-sintra = mkAtmApp { model = "sintra"; fiatCode = "EUR"; };
# ISO images
iso-douro = self.nixosConfigurations.douro.config.system.build.isoImage;
iso-tejo = self.nixosConfigurations.tejo.config.system.build.isoImage;
iso-sintra = self.nixosConfigurations.sintra.config.system.build.isoImage;
# Raw disk images (dd-able to mSATA/eMMC, proper GPT + ESP)
disk-image-douro = import (nixpkgs + "/nixos/lib/make-disk-image.nix") {
inherit pkgs lib;
config = self.nixosConfigurations.douro-installed.config;
format = "raw";
partitionTableType = "efi";
diskSize = "auto";
};
# Backwards compat
iso = self.nixosConfigurations.douro.config.system.build.isoImage;
};
}
//
# ── Dev shells (per-system via flake-utils) ───────────────────
flake-utils.lib.eachDefaultSystem (sys:
let
dev-pkgs = import nixpkgs-unstable {
system = sys;
overlays = [ (import rust-overlay) ];
};
in
{
# Development shell via devenv
devShells.default = devenv.lib.mkShell {
inherit pkgs;
pkgs = dev-pkgs;
modules = [ ./devenv.nix ];
};
# Packages
packages = {
# HAL Rust crate
hal = pkgs.rustPlatform.buildRustPackage {
pname = "lamassu-hal";
version = "0.1.0";
src = ./packages/hal;
cargoLock.lockFile = ./packages/hal/Cargo.lock;
nativeBuildInputs = with pkgs; [
pkg-config
];
buildInputs = with pkgs; [
openssl
libudev-zero
];
};
# Full application (TODO)
default = self.packages.${system}.hal;
};
# NixOS module for deployment
nixosModules.default = { config, lib, pkgs, ... }: {
options.services.lamassu-atm = {
enable = lib.mkEnableOption "Lamassu ATM service";
relayUrl = lib.mkOption {
type = lib.types.str;
description = "Nostr relay URL";
};
lightningPubUrl = lib.mkOption {
type = lib.types.str;
description = "Lightning.Pub connection URL";
};
identityPath = lib.mkOption {
type = lib.types.path;
default = "/etc/lamassu/machine.nsec";
description = "Path to machine identity (nsec)";
};
};
config = lib.mkIf config.services.lamassu-atm.enable {
# TODO: systemd service, firewall rules, etc.
};
};
}
);
}

173
nix/mkAtmApp.nix Normal file
View file

@ -0,0 +1,173 @@
# Pure Nix derivation for the Lamassu ATM Electron app.
#
# Uses fetchPnpmDeps + pnpmConfigHook to build entirely inside the Nix sandbox,
# eliminating the need for --impure or a local pnpm install.
#
# Usage (from flake.nix):
# mkAtmApp = import ./nix/mkAtmApp.nix { inherit pkgs pkgs-unstable; src = self; };
# atm-app-douro = mkAtmApp { model = "douro"; fiatCode = "GTQ"; };
{ pkgs, pkgs-unstable, src }:
{ model, fiatCode }:
let
nodejs = pkgs-unstable.nodejs_22;
pnpm = pkgs-unstable.pnpm_9;
electron = pkgs-unstable.electron;
# Electron's Node.js headers — needed to compile native addons (better-sqlite3)
# that use V8 C++ API (not N-API). Must match the Electron version exactly.
electronHeaders = pkgs.fetchurl {
url = "https://electronjs.org/headers/v${electron.version}/node-v${electron.version}-headers.tar.gz";
hash = "sha256-xtGHm/2Sb0ILU4yUVAqXTRLwzf8yiXxD0zIIrBk8NDw=";
};
in
pkgs.stdenv.mkDerivation (finalAttrs: {
pname = "lamassu-atm-app";
version = "0.1.0";
inherit src;
# Filter to only the workspace packages needed for @lamassu/machine
pnpmWorkspaces = [
"@lamassu/machine..." # "..." suffix = include transitive workspace deps
];
pnpmDeps = pkgs-unstable.fetchPnpmDeps {
inherit (finalAttrs) pname version src pnpmWorkspaces;
inherit pnpm;
fetcherVersion = 3;
hash = "sha256-i8nFieH6VSibEDDDFtMqko1+JQ1MWxBCTNUJ6Jo8mE4=";
};
nativeBuildInputs = [
nodejs
pnpm
pkgs-unstable.pnpmConfigHook
pkgs.python3 # node-gyp (better-sqlite3)
pkgs.pkg-config
pkgs.autoPatchelfHook # patch .node ELF binaries
];
buildInputs = [
pkgs.sqlite.dev # better-sqlite3
pkgs.libudev-zero # serialport
pkgs.stdenv.cc.cc.lib # libstdc++
];
env = {
ELECTRON_SKIP_BINARY_DOWNLOAD = "1";
# Vite compile-time variables (baked into the frontend bundle)
VITE_LAMASSU_MACHINE_MODEL = model;
VITE_LAMASSU_FIAT_CODE = fiatCode;
};
buildPhase = ''
runHook preBuild
# Extract Electron's Node.js headers for native addon compilation.
# better-sqlite3 uses the V8 C++ API (not N-API), so it MUST be compiled
# against Electron's headers — not Node.js headers — or you get
# "undefined symbol: _ZN2v811HandleScopeC1EPNS_7IsolateE" at runtime.
electron_nodedir=$(mktemp -d)
tar -xzf ${electronHeaders} -C "$electron_nodedir" --strip-components=1
echo "=== Rebuilding better-sqlite3 against Electron ${electron.version} headers ==="
pushd node_modules/.pnpm/better-sqlite3@*/node_modules/better-sqlite3
HOME=$TMPDIR ${nodejs}/bin/npx --yes node-gyp rebuild \
--nodedir="$electron_nodedir" \
--arch=x64
popd
# Build the Electron app (turbo builds all workspace deps + app)
pnpm --filter="@lamassu/machine..." build
runHook postBuild
'';
# Cherry-pick only the runtime node_modules needed by the Electron app.
# Vite bundles most JS deps — these are native addons and dynamic imports
# that can't be bundled.
installPhase = ''
runHook preInstall
mkdir -p $out/node_modules/{@lamassu,@serialport}
# Helper: find a package dir inside the pnpm virtual store.
# pnpm store dirs look like: node_modules/.pnpm/<name>@<ver>[_<peer-suffix>]/node_modules/<name>
# The glob must handle version + optional peer suffixes.
copy_pnpm_pkg() {
local pkg="$1" # e.g. "debug" or "@serialport/stream"
local dest="$2" # e.g. "$out/node_modules/debug"
# Convert @scope/name to @scope+name for the .pnpm dir prefix
local store_prefix="''${pkg/\//-}" # debug -> debug, @serialport/stream -> @serialport-stream
store_prefix="''${store_prefix//@/@}"
# Try globbing; sort to pick the first match deterministically
local found=$(find node_modules/.pnpm -maxdepth 1 -name "''${store_prefix}@*" -type d | sort | head -1)
if [ -z "$found" ]; then
# Fallback: scoped packages use + instead of -
store_prefix="''${pkg/\//+}"
found=$(find node_modules/.pnpm -maxdepth 1 -name "''${store_prefix}@*" -type d | sort | head -1)
fi
if [ -z "$found" ]; then
echo "ERROR: could not find pnpm store dir for $pkg" >&2
exit 1
fi
cp -rL "$found/node_modules/$pkg" "$dest"
}
# Electron app build outputs
cp -r apps/machine/dist $out/dist
cp -r apps/machine/dist-electron $out/dist-electron
cp apps/machine/package.json $out/package.json
# --- Native modules (not bundleable by Vite) ---
# better-sqlite3 (compiled native addon)
copy_pnpm_pkg better-sqlite3 $out/node_modules/better-sqlite3
copy_pnpm_pkg bindings $out/node_modules/bindings
copy_pnpm_pkg 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 packages/hal/dist/* $out/node_modules/@lamassu/hal/dist/
cp packages/hal/package.json $out/node_modules/@lamassu/hal/package.json
# serialport (native RS232 driver chain)
copy_pnpm_pkg serialport $out/node_modules/serialport
copy_pnpm_pkg @serialport/stream $out/node_modules/@serialport/stream
# bindings-cpp: only dist/ + prebuilds/ + package.json (build/ has broken symlinks)
mkdir -p $out/node_modules/@serialport/bindings-cpp
bcpp_store=$(find node_modules/.pnpm -maxdepth 1 -name "@serialport+bindings-cpp@*" -type d | sort | head -1)
cp -rL "$bcpp_store/node_modules/@serialport/bindings-cpp/dist" $out/node_modules/@serialport/bindings-cpp/dist
cp -rL "$bcpp_store/node_modules/@serialport/bindings-cpp/prebuilds" $out/node_modules/@serialport/bindings-cpp/prebuilds
cp "$bcpp_store/node_modules/@serialport/bindings-cpp/package.json" $out/node_modules/@serialport/bindings-cpp/package.json
copy_pnpm_pkg @serialport/bindings-interface $out/node_modules/@serialport/bindings-interface
copy_pnpm_pkg @serialport/binding-mock $out/node_modules/@serialport/binding-mock
# Transitive deps of serialport
copy_pnpm_pkg debug $out/node_modules/debug
copy_pnpm_pkg ms $out/node_modules/ms
copy_pnpm_pkg node-addon-api $out/node_modules/node-addon-api
copy_pnpm_pkg node-gyp-build $out/node_modules/node-gyp-build
copy_pnpm_pkg lodash-es $out/node_modules/lodash-es
# serialport parser sub-packages (barrel import requires all 10)
for parser in \
parser-byte-length parser-cctalk parser-delimiter \
parser-inter-byte-timeout parser-packet-length parser-readline \
parser-ready parser-regex parser-slip-encoder parser-spacepacket; do
copy_pnpm_pkg "@serialport/$parser" "$out/node_modules/@serialport/$parser"
done
runHook postInstall
'';
# autoPatchelfHook will scan $out for .node ELF binaries and patch their
# RPATH to find libstdc++, libudev, libsqlite3, etc.
# The musl prebuild ships alongside glibc — we only use glibc on NixOS.
autoPatchelfIgnoreMissingDeps = [ "libc.musl-x86_64.so.1" ];
})