The fresh-boot `/var/lib/bitspire/.env` template at flake.nix's
`bitspire-env` activation script seeded `VITE_RELAY_URL=` empty, which
forced every operator to run `provision-atm.sh` (or hand-edit .env)
before the renderer could resolve a relay. Meanwhile the NixOS option
`services.bitspire.relayUrl` was wired only to the dead-code
`/etc/bitspire/config.env` (mkForce-shadowed by `/var/lib/bitspire/.env`).
Thread the NixOS option through: seed `VITE_RELAY_URL=${cfg.relayUrl}`
on first boot. The .env override path remains intact — provision-atm.sh
or a hand edit still take precedence at runtime (the file is the
EnvironmentFile, not the activation-time template). Existing ATMs
already have a populated `.env` and aren't affected (the activation
script's `if [ ! -f ... ]` guard skips the rewrite).
Renderer resolution order:
/var/lib/bitspire/.env → NixOS module default → renderer fallback
(`ws://localhost:7777` in lightning.ts:63 / main.ts:284)
Also expand the `services.bitspire.relayUrl` option description so
future readers see the wire-through + the override path documented
where the option lives.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
281 lines
7.5 KiB
Nix
281 lines
7.5 KiB
Nix
# bitSpire ATM Service Module
|
|
# Manages the ATM Electron application and related services
|
|
|
|
{
|
|
config,
|
|
lib,
|
|
pkgs,
|
|
pkgs-unstable,
|
|
...
|
|
}:
|
|
|
|
with lib;
|
|
|
|
let
|
|
cfg = config.services.bitspire;
|
|
in
|
|
{
|
|
options.services.bitspire = {
|
|
enable = mkEnableOption "bitSpire ATM service";
|
|
|
|
relayUrl = mkOption {
|
|
type = types.str;
|
|
default = "wss://relay.aiolabs.dev";
|
|
description = ''
|
|
Nostr relay URL the ATM and LNbits both subscribe to.
|
|
|
|
On a fresh-boot disk image this value is seeded into
|
|
`/var/lib/bitspire/.env` as `VITE_RELAY_URL=…` (see flake.nix
|
|
`bitspire-env` activation script). The operator can override
|
|
the seeded value at runtime by editing `.env` directly or by
|
|
re-running `deploy/nixos/provision-atm.sh` with a different
|
|
`RELAY_URL`. The renderer's resolution order is:
|
|
`/var/lib/bitspire/.env` → this NixOS default → renderer
|
|
hardcoded fallback (`ws://localhost:7777`).
|
|
'';
|
|
};
|
|
|
|
lnbitsServerPubkey = mkOption {
|
|
type = types.str;
|
|
default = "";
|
|
description = ''
|
|
LNbits nostr-transport server pubkey (hex, 64 chars). Published
|
|
by the LNbits server on startup. Required for the ATM to talk
|
|
to its wallet. Provisioned by provision-atm.sh; can be left
|
|
empty on disk-image builds.
|
|
'';
|
|
};
|
|
|
|
appDir = mkOption {
|
|
type = types.path;
|
|
default = "/opt/bitspire";
|
|
description = "Directory containing the ATM application";
|
|
};
|
|
|
|
dataDir = mkOption {
|
|
type = types.path;
|
|
default = "/var/lib/bitspire";
|
|
description = "Directory for ATM data and configuration";
|
|
};
|
|
|
|
logLevel = mkOption {
|
|
type = types.enum [
|
|
"error"
|
|
"warn"
|
|
"info"
|
|
"debug"
|
|
];
|
|
default = "info";
|
|
description = "Logging level for the ATM application";
|
|
};
|
|
|
|
# Hardware configuration
|
|
billValidator = {
|
|
enable = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
description = "Enable bill validator support";
|
|
};
|
|
|
|
device = mkOption {
|
|
type = types.str;
|
|
default = "/dev/ttyUSB0";
|
|
description = "Serial device for bill validator";
|
|
};
|
|
|
|
type = mkOption {
|
|
type = types.enum [
|
|
"id003"
|
|
"mei"
|
|
"ccnet"
|
|
];
|
|
default = "id003";
|
|
description = "Bill validator protocol type";
|
|
};
|
|
};
|
|
|
|
billDispenser = {
|
|
enable = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = "Enable bill dispenser support (two-way machines)";
|
|
};
|
|
|
|
device = mkOption {
|
|
type = types.str;
|
|
default = "/dev/ttyUSB1";
|
|
description = "Serial device for bill dispenser";
|
|
};
|
|
|
|
type = mkOption {
|
|
type = types.enum [
|
|
"puloon"
|
|
"genmega"
|
|
];
|
|
default = "puloon";
|
|
description = "Bill dispenser type";
|
|
};
|
|
};
|
|
|
|
camera = {
|
|
enable = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
description = "Enable camera for QR code scanning";
|
|
};
|
|
|
|
device = mkOption {
|
|
type = types.str;
|
|
default = "/dev/video0";
|
|
description = "Camera device";
|
|
};
|
|
};
|
|
};
|
|
|
|
config = mkIf cfg.enable {
|
|
# Create data directory
|
|
systemd.tmpfiles.rules = [
|
|
"d ${cfg.dataDir} 0750 bitspire bitspire -"
|
|
"d ${cfg.dataDir}/logs 0750 bitspire bitspire -"
|
|
# Operator branding override target (issue #47); empty by default
|
|
"d ${cfg.dataDir}/branding 0755 bitspire bitspire -"
|
|
];
|
|
|
|
# Environment file for ATM configuration
|
|
environment.etc."bitspire/config.env".text = ''
|
|
# bitSpire ATM Configuration
|
|
RELAY_URL=${cfg.relayUrl}
|
|
LNBITS_SERVER_PUBKEY=${cfg.lnbitsServerPubkey}
|
|
LOG_LEVEL=${cfg.logLevel}
|
|
DATA_DIR=${cfg.dataDir}
|
|
|
|
# Hardware
|
|
BILL_VALIDATOR_ENABLED=${boolToString cfg.billValidator.enable}
|
|
BILL_VALIDATOR_DEVICE=${cfg.billValidator.device}
|
|
BILL_VALIDATOR_TYPE=${cfg.billValidator.type}
|
|
|
|
BILL_DISPENSER_ENABLED=${boolToString cfg.billDispenser.enable}
|
|
BILL_DISPENSER_DEVICE=${cfg.billDispenser.device}
|
|
BILL_DISPENSER_TYPE=${cfg.billDispenser.type}
|
|
|
|
CAMERA_ENABLED=${boolToString cfg.camera.enable}
|
|
CAMERA_DEVICE=${cfg.camera.device}
|
|
|
|
# Display
|
|
DISPLAY=:0
|
|
ELECTRON_DISABLE_GPU=false
|
|
'';
|
|
|
|
# Main ATM service
|
|
systemd.services.bitspire = {
|
|
description = "bitSpire ATM Application";
|
|
wantedBy = [ "graphical.target" ];
|
|
after = [
|
|
"graphical.target"
|
|
"network-online.target"
|
|
];
|
|
wants = [ "network-online.target" ];
|
|
|
|
serviceConfig = {
|
|
Type = "simple";
|
|
User = "bitspire";
|
|
Group = "bitspire";
|
|
WorkingDirectory = cfg.appDir;
|
|
|
|
# Environment
|
|
EnvironmentFile = "/etc/bitspire/config.env";
|
|
|
|
# Start the Electron app
|
|
ExecStart = "${pkgs-unstable.electron}/bin/electron ${cfg.appDir}";
|
|
|
|
# Restart policy
|
|
Restart = "always";
|
|
RestartSec = 5;
|
|
|
|
# Resource limits
|
|
MemoryMax = "1G";
|
|
CPUQuota = "80%";
|
|
|
|
# Security hardening
|
|
NoNewPrivileges = true;
|
|
ProtectSystem = "strict";
|
|
ProtectHome = true;
|
|
ReadWritePaths = [
|
|
cfg.dataDir
|
|
"/tmp"
|
|
];
|
|
PrivateTmp = true;
|
|
|
|
# Allow device access for hardware
|
|
DeviceAllow = [
|
|
"/dev/ttyUSB* rw"
|
|
"/dev/ttyACM* rw"
|
|
"/dev/ttyS* rw"
|
|
"/dev/video* rw"
|
|
];
|
|
};
|
|
|
|
# Pre-start script to verify hardware
|
|
preStart = ''
|
|
echo "bitSpire starting..."
|
|
echo "Relay: ${cfg.relayUrl}"
|
|
|
|
# Check bill validator if enabled
|
|
if [ "${boolToString cfg.billValidator.enable}" = "true" ]; then
|
|
if [ ! -c "${cfg.billValidator.device}" ]; then
|
|
echo "Warning: Bill validator device ${cfg.billValidator.device} not found"
|
|
fi
|
|
fi
|
|
|
|
# Check camera if enabled
|
|
if [ "${boolToString cfg.camera.enable}" = "true" ]; then
|
|
if [ ! -c "${cfg.camera.device}" ]; then
|
|
echo "Warning: Camera device ${cfg.camera.device} not found"
|
|
fi
|
|
fi
|
|
'';
|
|
};
|
|
|
|
# Openbox autostart for kiosk mode
|
|
environment.etc."xdg/openbox/autostart".text = ''
|
|
# Disable screen saver and power management
|
|
xset s off
|
|
xset -dpms
|
|
xset s noblank
|
|
|
|
# Hide cursor after inactivity
|
|
unclutter -idle 3 &
|
|
|
|
# Start ATM (handled by systemd, but ensure display is ready)
|
|
sleep 2
|
|
'';
|
|
|
|
# udev rules for ATM hardware
|
|
services.udev.extraRules = ''
|
|
# ID-003 Bill Validator (JCM)
|
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="0451", ATTRS{idProduct}=="3410", MODE="0666", SYMLINK+="bill-validator"
|
|
|
|
# MEI Bill Validator
|
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="0b00", MODE="0666", SYMLINK+="bill-validator"
|
|
|
|
# CCNET Bill Validator (CashCode)
|
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="0x1b5a", MODE="0666", SYMLINK+="bill-validator"
|
|
|
|
# Puloon Bill Dispenser
|
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", MODE="0666", SYMLINK+="bill-dispenser"
|
|
|
|
# Generic USB-Serial adapters
|
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="067b", MODE="0666"
|
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", MODE="0666"
|
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="10c4", MODE="0666"
|
|
|
|
# Camera access
|
|
SUBSYSTEM=="video4linux", MODE="0666"
|
|
'';
|
|
|
|
# Additional packages for hardware support
|
|
environment.systemPackages = with pkgs; [
|
|
unclutter # Hide cursor
|
|
];
|
|
};
|
|
}
|