bitspire/deploy/nixos/live.nix
Padreug 3128975224 chore(nix): bump nixpkgs 25.05 → 25.11
isoImage.isoName was renamed to image.fileName in 25.11 (unified
image module). Split the rename out — the rest of isoImage.* stays
put (makeEfiBootable, makeBiosBootable, squashfsCompression).

All 8 nixosConfigurations evaluate clean on 25.11 with zero
deprecation warnings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-01 19:12:11 +02:00

263 lines
9.8 KiB
Nix

# Lamassu ATM Live USB Configuration
# Bootable ISO for testing on physical hardware without installing to disk.
#
# Parameterized by machineModel (passed via specialArgs from flake.nix):
# "douro" - Bay Trail Atom, kernel 5.15 (i915 regression in newer kernels), eDP panel
# "tejo" - UP4000/UPBoard, default kernel 6.6, standard Intel GPU
#
# Builds with: nix build .#iso-douro or nix build .#iso-tejo
#
# 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", atm-app, ... }:
let
# Fiat code per machine model (for envTemplate display only)
fiatCodeForModel = {
douro = "GTQ";
tejo = "GTQ";
sintra = "EUR";
batm3 = "USD";
}.${machineModel} or "USD";
# .env template — runtime secrets are provisioned later via provision-atm.sh.
# Only non-secret defaults and display vars go here.
envTemplate = pkgs.writeText "bitspire-env" ''
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
DISPLAY=:0
'';
in
{
imports = [
# NixOS ISO image builder (nixpkgs path passed via specialArgs from flake.nix)
"${nixpkgs}/nixos/modules/installer/cd-dvd/iso-image.nix"
"${nixpkgs}/nixos/modules/profiles/all-hardware.nix"
# Reuse kiosk config (X11, openbox, users, networking)
./configuration.nix
# Reuse ATM systemd service module
./bitspire-atm.nix
];
# ISO image settings
image.fileName = "bitspire-${machineModel}-live.iso";
isoImage = {
makeEfiBootable = true;
makeBiosBootable = true;
squashfsCompression = "zstd -Xcompression-level 6";
};
# No fileSystems override needed — iso-image.nix handles squashfs + tmpfs root.
# We don't import hardware/upboard.nix, so there are no conflicting disk mounts.
# Kernel: Douro Bay Trail needs 5.15 LTS (i915 eDP regression in 6.x kernels)
boot.kernelPackages = lib.mkIf (machineModel == "douro") pkgs.linuxPackages_5_15;
# Boot: kernel modules and parameters (model-specific)
boot = {
initrd.availableKernelModules = [
"xhci_pci"
"ahci"
"usb_storage"
"sd_mod"
"sdhci_pci"
"i915"
"squashfs"
"iso9660"
"loop"
];
kernelModules = [
"kvm-intel"
"i2c-dev"
"spi-dev"
] ++ lib.optionals (machineModel == "tejo") [
"usbserial" # USB-to-serial adapters (ttyUSB0/1 for validator/printer)
"ftdi_sio" # FTDI USB serial (common in ATM peripherals)
"cp210x" # CP210x USB serial (alternative adapter)
] ++ lib.optionals (machineModel == "batm3") [
"cdc_acm" # USB CDC ACM for MEI BNR Advance validator
"usbserial" # USB-to-serial for F56 dispenser adapter
"ftdi_sio" # FTDI USB serial (common RS232 adapter)
"cp210x" # CP210x USB serial (alternative adapter)
];
kernelParams = [
"i915.enable_psr=0"
"quiet"
"splash"
] ++ lib.optionals (machineModel == "douro") [
# Bay Trail: preserve BIOS display init (matches working kernel 5.4 config)
"vt.handoff=7"
] ++ lib.optionals (machineModel == "tejo") [
# UP Board debug UART for serial console
"console=ttyS4,115200n8"
"console=tty0"
];
};
# Intel GPU support
hardware = {
graphics = {
enable = true;
extraPackages = with pkgs; [
intel-media-driver
libva-vdpau-driver
libvdpau-va-gl
];
};
enableRedistributableFirmware = true;
cpu.intel.updateMicrocode = true;
};
# Performance governor for responsive kiosk
powerManagement = {
enable = true;
cpuFreqGovernor = "performance";
};
# Disable suspend/hibernate
systemd.targets = {
sleep.enable = false;
suspend.enable = false;
hibernate.enable = false;
hybrid-sleep.enable = false;
};
# Enable the ATM service with live USB paths
services.bitspire = {
enable = true;
appDir = "${atm-app}";
};
# Allow unprivileged user namespaces (Electron sandbox needs this)
boot.kernel.sysctl."kernel.unprivileged_userns_clone" = 1;
# Override the systemd service for live USB environment
systemd.services.bitspire = {
serviceConfig = {
EnvironmentFile = lib.mkForce "/var/lib/bitspire/.env";
# Native modules need libstdc++ on NixOS
Environment = "LD_LIBRARY_PATH=${pkgs.stdenv.cc.cc.lib}/lib";
# Electron needs --no-sandbox in the live/testing environment
# --enable-logging makes renderer console.log visible in journalctl
ExecStart = lib.mkForce "${pkgs-unstable.electron}/bin/electron --no-sandbox --disable-gpu-sandbox --disable-gpu --disable-software-rasterizer --enable-logging ${atm-app}";
# Prevent Electron from consuming all RAM on memory-constrained ATMs
MemoryMax = lib.mkForce "1G";
# Disable all security hardening that conflicts with Electron
NoNewPrivileges = lib.mkForce false;
ProtectSystem = lib.mkForce false;
ProtectHome = lib.mkForce false;
PrivateTmp = lib.mkForce false;
# Allow access to all character devices (serial ports for HAL hardware)
DevicePolicy = lib.mkForce "auto";
DeviceAllow = lib.mkForce [ "char-* rw" ];
};
};
# Install the .env on first boot
system.activationScripts.bitspire-env = ''
mkdir -p /var/lib/bitspire
if [ ! -f /var/lib/bitspire/.env ]; then
cp ${envTemplate} /var/lib/bitspire/.env
chmod 600 /var/lib/bitspire/.env
chown bitspire:bitspire /var/lib/bitspire/.env
fi
'';
# Reset display output after X starts (required for kexec boots where
# the GPU wasn't reinitialized by BIOS firmware)
systemd.services.display-reset = {
description = "Reset eDP display output";
after = [ "display-manager.service" ];
requires = [ "display-manager.service" ];
wantedBy = [ "graphical.target" ];
before = [ "bitspire.service" ];
serviceConfig = {
Type = "oneshot";
User = "bitspire";
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'";
};
};
# Swap file — Douro/Tejo have only 2GB RAM; without swap the system
# hard-freezes under memory pressure instead of gracefully OOM-killing.
swapDevices = [{
device = "/var/swapfile";
size = 1024; # MB
}];
# Clean /tmp on boot to prevent stale Nix build artifacts from filling disk
boot.tmp.cleanOnBoot = true;
# SSH password auth disabled — machines are provisioned with SSH keys.
# Base configuration.nix sets PasswordAuthentication = false.
# Serial port udev rules — generic permissions for all models
services.udev.extraRules = lib.mkAfter (''
KERNEL=="ttyS[0-9]*", MODE="0666"
KERNEL=="ttyUSB[0-9]*", MODE="0666"
KERNEL=="ttyACM[0-9]*", MODE="0666"
'' + lib.optionalString (machineModel == "batm3") ''
# ── BATM3 MEI BNR Advance USB CDC ACM ──────────────────────────────
# Stable symlink for the bill validator (vendor 0x0bed = MEI)
SUBSYSTEM=="tty", ATTRS{idVendor}=="0bed", SYMLINK+="ttyValidator"
'' + lib.optionalString (machineModel == "tejo") ''
# ── Tejo serial port symlinks ──────────────────────────────────────
# Both UP Board and UP4000 rules are included so one ISO works on either.
# Rules match different kernel paths so they don't conflict.
# Printer (ttyJ4): UP Board via USB hub path, UP4000 via ttyUSB0
KERNELS=="1-7.2:1.0", SYMLINK+="ttyJ4"
KERNEL=="ttyUSB0", SYMLINK+="ttyJ4"
# Validator (ttyJ5): UP Board via USB hub path, UP4000 via ttyUSB1
KERNELS=="1-7.3:1.0", SYMLINK+="ttyJ5"
KERNEL=="ttyUSB1", SYMLINK+="ttyJ5"
# Dispenser (ttyJ7): UP Board uses ttyS1, UP4000 uses ttyS5
KERNEL=="ttyS1", SYMLINK+="ttyJ7"
KERNEL=="ttyS5", SYMLINK+="ttyJ7"
# Legacy ttyAMA0 alias
SUBSYSTEM=="tty", KERNEL=="ttyS1", SYMLINK+="ttyAMA0", GROUP="dialout"
# ── Camera devices ─────────────────────────────────────────────────
# QR scanner camera (A4tech USB, vendor 0ac8:0345)
SUBSYSTEM=="video4linux", ATTR{index}=="0", KERNELS=="1-5", ATTRS{idVendor}=="0ac8", ATTRS{idProduct}=="0345", SYMLINK+="video-scan"
SUBSYSTEM=="video4linux", ATTR{index}=="0", KERNELS=="1-2", ATTRS{idVendor}=="0ac8", ATTRS{idProduct}=="0345", SYMLINK+="video-scan"
# Front-facing camera
SUBSYSTEM=="video4linux", ATTR{index}=="0", KERNELS=="1-6", ATTRS{idVendor}=="0ac8", ATTRS{idProduct}=="0345", SYMLINK+="video-front"
SUBSYSTEM=="video4linux", ATTR{index}=="0", KERNELS=="1-3", ATTRS{idVendor}=="0ac8", ATTRS{idProduct}=="0345", SYMLINK+="video-front"
# ── LED SPI / peripherals ──────────────────────────────────────────
KERNEL=="spidev1.0", SYMLINK+="ledspi"
KERNEL=="spidev2.0", SYMLINK+="ledspi"
# SPI and I2C group permissions
SUBSYSTEM=="spidev", GROUP="spi", MODE="0660"
SUBSYSTEM=="i2c-dev", GROUP="i2c", MODE="0660"
# UP Board FPGA LED permissions
SUBSYSTEM=="leds", KERNEL=="upboard:*", ACTION=="add|change", RUN+="${pkgs.findutils}/bin/find /sys$devpath -type f -exec ${pkgs.coreutils}/bin/chmod g+u {} + -exec ${pkgs.coreutils}/bin/chown :leds {} +"
# Disable USB autosuspend (prevents serial adapters from sleeping)
ACTION=="add", SUBSYSTEM=="usb", TEST=="power/control", ATTR{power/control}="on"
'');
}