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:
parent
f5f00108aa
commit
625cebed25
8 changed files with 832 additions and 342 deletions
|
|
@ -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/"
|
||||
|
|
|
|||
44
deploy/nixos/flake.lock
generated
44
deploy/nixos/flake.lock
generated
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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;
|
||||
};
|
||||
};
|
||||
}
|
||||
89
deploy/nixos/hardware/douro.nix
Normal file
89
deploy/nixos/hardware/douro.nix
Normal 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"
|
||||
'';
|
||||
}
|
||||
|
|
@ -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
348
flake.lock
generated
Normal 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
267
flake.nix
|
|
@ -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
173
nix/mkAtmApp.nix
Normal 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" ];
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue