From 04f008d1cf38cd490bec6f020d6b1962869daad4 Mon Sep 17 00:00:00 2001 From: Patrick Mulligan Date: Fri, 9 Jan 2026 23:25:53 +0100 Subject: [PATCH] refactor(lamassu): clean up and simplify module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove hardcoded Bitcoin RPC credentials (security issue) - Remove unused options: enableBitcoin, package, commented-out devMode/nginx - Consolidate duplicate code: commonEnv, hardeningConfig, single lamassuEnv wrapper - Remove lamassu-status helper (use systemctl directly) - Simplify build script and option definitions - 654 → 407 lines (~38% reduction) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- modules/lamassu-lnbits.nix | 466 +++++++++---------------------------- 1 file changed, 110 insertions(+), 356 deletions(-) diff --git a/modules/lamassu-lnbits.nix b/modules/lamassu-lnbits.nix index 6993fbc..0015d99 100644 --- a/modules/lamassu-lnbits.nix +++ b/modules/lamassu-lnbits.nix @@ -7,26 +7,65 @@ let nbLib = config.nix-bitcoin.lib; secretsDir = config.nix-bitcoin.secretsDir; - # Source directory for lamassu-server (cloned from git) - lamassuSourceDir = "${cfg.dataDir}/source"; + # Shared environment variables for both services + commonEnv = { + NODE_ENV = "production"; + LOG_LEVEL = cfg.logLevel; + HOSTNAME = cfg.hostname; - # Basic hardening settings (simplified from nix-bitcoin) - defaultHardening = { - # Sandboxing + # Database + POSTGRES_HOST = "127.0.0.1"; + POSTGRES_PORT = "5432"; + POSTGRES_DB = cfg.database.name; + POSTGRES_USER = cfg.database.user; + + # TLS certificates + CA_PATH = cfg.certPath; + CERT_PATH = cfg.certPath; + KEY_PATH = cfg.keyPath; + + # Data directories + MNEMONIC_PATH = "${cfg.dataDir}/lamassu-mnemonic"; + OFAC_DATA_DIR = "${cfg.dataDir}/ofac"; + ID_PHOTO_CARD_DIR = "${cfg.dataDir}/photos/idcards"; + FRONT_CAMERA_DIR = "${cfg.dataDir}/photos/frontcamera"; + OPERATOR_DATA_DIR = "${cfg.dataDir}/operator"; + + # Security + SKIP_2FA = if cfg.skip2FA then "true" else "false"; + }; + + # Shared wrapper script that sets up the environment + lamassuEnv = pkgs.writeShellScript "lamassu-env" '' + set -euo pipefail + export PATH=${pkgs.nodejs_22}/bin:$PATH + DB_PASSWORD=$(cat ${secretsDir}/lamassu-db-password) + export DATABASE_URL="postgresql://${cfg.database.user}:$DB_PASSWORD@127.0.0.1:5432/${cfg.database.name}" + export POSTGRES_PASSWORD="$DB_PASSWORD" + export NODE_PATH=${cfg.dataDir}/source/node_modules + cd ${cfg.dataDir}/source + exec "$@" + ''; + + # Hardening settings for runtime services + hardeningConfig = { PrivateTmp = true; ProtectSystem = "strict"; ProtectHome = true; NoNewPrivileges = true; - - # Kernel ProtectKernelTunables = true; ProtectKernelModules = true; ProtectControlGroups = true; - - # Misc RestrictRealtime = true; RestrictSUIDSGID = true; LockPersonality = true; + MemoryDenyWriteExecute = false; # Required for Node.js JIT + ReadWritePaths = [ cfg.dataDir "${cfg.dataDir}/source" ]; + RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ]; + User = cfg.user; + Group = cfg.group; + Restart = "on-failure"; + RestartSec = "10s"; }; in @@ -40,13 +79,6 @@ in description = "Port for the main lamassu server API"; }; - # NOTE: Admin UI port is currently hardcoded in upstream lamassu-server: - # - Production mode (default): port 443 - # - Dev mode (--dev flag): port 8070 - # Future: Add --ui-port support to upstream to make this configurable. - # This would also enable nginx reverse proxy (which also needs port 443). - # See docs/lamassu-future-nginx.md for implementation details. - dataDir = mkOption { type = types.path; default = "/var/lib/lamassu-server"; @@ -65,12 +97,6 @@ in description = "Group to run lamassu-server as"; }; - package = mkOption { - type = types.path; - default = lamassuSourceDir; - description = "The path to the lamassu-server source directory"; - }; - source = { url = mkOption { type = types.str; @@ -97,14 +123,6 @@ in description = "Skip 2FA authentication (useful for initial setup)"; }; - # NOTE: devMode is disabled for now. Admin UI runs in production mode (port 443). - # Future: Re-enable when --ui-port is added to upstream lamassu-server. - # devMode = mkOption { - # type = types.bool; - # default = false; - # description = "Run admin server in development mode (port 8070)."; - # }; - database = { name = mkOption { type = types.str; @@ -117,9 +135,6 @@ in default = cfg.user; description = "PostgreSQL username"; }; - - # Password is managed by nix-bitcoin secrets system. - # See: ${secretsDir}/lamassu-db-password }; hostname = mkOption { @@ -131,30 +146,21 @@ in ''; }; - # Certificate options (same pattern as LND) - # TODO: When using an IP address, hostname and certificate.extraIPs are redundant. - # Consider auto-populating certificate.extraIPs from hostname if it's an IP, - # or unifying these options. For now, set both to the same IP address. certificate = { extraIPs = mkOption { type = with types; listOf str; default = []; example = [ "192.168.1.100" ]; - description = '' - Extra IP addresses to include in the certificate SAN. - ''; + description = "Extra IP addresses to include in the certificate SAN."; }; extraDomains = mkOption { type = with types; listOf str; default = []; example = [ "lamassu.example.com" ]; - description = '' - Extra domain names to include in the certificate SAN. - ''; + description = "Extra domain names to include in the certificate SAN."; }; }; - # Read-only options for certificate paths certPath = mkOption { readOnly = true; default = "${secretsDir}/lamassu-cert"; @@ -166,41 +172,19 @@ in default = "${secretsDir}/lamassu-key"; description = "Path to the TLS private key."; }; - - # NOTE: nginx is disabled for now because admin UI binds directly to port 443. - # Enabling nginx would cause a port conflict. - # Future: Add --ui-port to upstream, run admin UI on internal port (e.g., 8070), - # and use nginx as reverse proxy on 443. See docs/lamassu-future-nginx.md - # nginx = { - # enable = mkEnableOption "Nginx reverse proxy on port 443"; - # hostname = mkOption { - # type = types.nullOr types.str; - # default = null; - # description = "Hostname for nginx virtual host"; - # }; - # }; - - enableBitcoin = mkOption { - type = types.bool; - default = false; - description = "Enable Bitcoin integration (requires bitcoind)"; - }; }; config = mkIf cfg.enable { - # ═══════════════════════════════════════════════════════════════════════════ - # nix-bitcoin secrets integration - # ═══════════════════════════════════════════════════════════════════════════ - + # Secrets nix-bitcoin.secrets = { lamassu-key.user = cfg.user; lamassu-cert = { user = cfg.user; - permissions = "444"; # World readable (it's a public cert) + permissions = "444"; }; lamassu-db-password = { user = cfg.user; - group = "postgres"; # PostgreSQL needs to read this too + group = "postgres"; }; }; @@ -209,58 +193,43 @@ in makePasswordSecret lamassu-db-password ''; - # ═══════════════════════════════════════════════════════════════════════════ - - # NOTE: Nginx reverse proxy is disabled. See docs/lamassu-future-nginx.md - # for future implementation when --ui-port is added to upstream. - - # Enable PostgreSQL + # PostgreSQL services.postgresql = { enable = true; package = pkgs.postgresql_15; ensureDatabases = [ cfg.database.name ]; - ensureUsers = [ - { - name = cfg.database.user; - ensureDBOwnership = true; - } - ]; - # Enable password authentication for localhost connections + ensureUsers = [{ + name = cfg.database.user; + ensureDBOwnership = true; + }]; authentication = pkgs.lib.mkOverride 10 '' - # TYPE DATABASE USER ADDRESS METHOD local all all peer host all all 127.0.0.1/32 md5 host all all ::1/128 md5 ''; }; - # Create system users and groups - users.users = { - # Lamassu server user - ${cfg.user} = { - isSystemUser = true; - group = cfg.group; - home = cfg.dataDir; - createHome = true; - }; + # User and group + users.users.${cfg.user} = { + isSystemUser = true; + group = cfg.group; + home = cfg.dataDir; + createHome = true; }; - users.groups.${cfg.group} = {}; - # Create data directory with proper permissions + # Data directories systemd.tmpfiles.rules = [ "d '${cfg.dataDir}' 0770 ${cfg.user} ${cfg.group} - -" "d '${cfg.dataDir}/logs' 0770 ${cfg.user} ${cfg.group} - -" - "d '${cfg.dataDir}/blockchain' 0770 ${cfg.user} ${cfg.group} - -" "d '${cfg.dataDir}/ofac' 0770 ${cfg.user} ${cfg.group} - -" "d '${cfg.dataDir}/photos' 0770 ${cfg.user} ${cfg.group} - -" "d '${cfg.dataDir}/photos/idcards' 0770 ${cfg.user} ${cfg.group} - -" "d '${cfg.dataDir}/photos/frontcamera' 0770 ${cfg.user} ${cfg.group} - -" "d '${cfg.dataDir}/operator' 0770 ${cfg.user} ${cfg.group} - -" - # Source directory is created by lamassu-build service via git clone ]; - # Service to set PostgreSQL password from nix-bitcoin secrets + # PostgreSQL password setup systemd.services.lamassu-postgres-setup = { description = "Setup PostgreSQL password for lamassu-server"; wantedBy = [ "multi-user.target" ]; @@ -272,23 +241,19 @@ in User = "postgres"; }; script = '' - # Wait for user to exist, then set password from secrets for i in {1..30}; do if ${pkgs.postgresql}/bin/psql -tAc "SELECT 1 FROM pg_roles WHERE rolname='${cfg.database.user}'" | grep -q 1; then - echo "Setting password for ${cfg.database.user}..." password=$(cat ${secretsDir}/lamassu-db-password) ${pkgs.postgresql}/bin/psql -c "ALTER USER \"${cfg.database.user}\" WITH PASSWORD '$password';" exit 0 fi - echo "Waiting for user ${cfg.database.user} to be created (attempt $i/30)..." sleep 1 done - echo "ERROR: User ${cfg.database.user} was not created after 30 seconds" exit 1 ''; }; - # Build service - clones source and runs pnpm install/build on target + # Build service systemd.services.lamassu-build = { description = "Clone and Build Lamassu Server"; wantedBy = [ "multi-user.target" ]; @@ -296,39 +261,16 @@ in wants = [ "network-online.target" ]; path = with pkgs; [ - nodejs_22 - nodePackages.pnpm - python3 - git - coreutils - gnused - bash # Provides sh for node-gyp - util-linux # for setsid - # Native build tools for node-gyp (required for utf-8-validate, bufferutil, etc.) - stdenv.cc # Full C/C++ toolchain with headers - gnumake - pkg-config - binutils # ar, ranlib, etc. - # Common native dependencies for Node.js modules - libuv - openssl - # Additional dependencies for some npm packages - expat # for node-expat + nodejs_22 nodePackages.pnpm python3 git coreutils gnused bash + util-linux stdenv.cc gnumake pkg-config binutils expat ]; environment = { - # Tell node-gyp where to find Python PYTHON = "${pkgs.python3}/bin/python3"; - # Ensure HOME is set for npm/pnpm cache HOME = cfg.dataDir; - # CRITICAL: pnpm fails without TTY unless CI=true is set - # See: https://github.com/pnpm/pnpm/issues/6434 CI = "true"; - # Set CC/CXX for node-gyp CC = "${pkgs.stdenv.cc}/bin/cc"; CXX = "${pkgs.stdenv.cc}/bin/c++"; - # Limit concurrent scripts to avoid race conditions - npm_config_jobs = "1"; }; serviceConfig = { @@ -336,317 +278,129 @@ in RemainAfterExit = true; User = cfg.user; Group = cfg.group; - # Build can take a while, especially on first run TimeoutStartSec = "30min"; - # Don't kill child processes when main process exits KillMode = "process"; - # Send SIGTERM instead of SIGINT KillSignal = "SIGTERM"; - # Completely disable sandboxing for build (npm scripts need full access) + # Disable sandboxing for build PrivateTmp = false; - PrivateDevices = false; ProtectSystem = false; ProtectHome = false; NoNewPrivileges = false; - ProtectKernelTunables = false; - ProtectKernelModules = false; - ProtectControlGroups = false; - RestrictNamespaces = false; - RestrictSUIDSGID = false; - LockPersonality = false; - # Don't restrict syscalls - SystemCallFilter = ""; - # No resource limits - TasksMax = "infinity"; - MemoryMax = "infinity"; }; script = '' set -euo pipefail + SOURCE_DIR="${cfg.dataDir}/source" - SOURCE_DIR="${cfg.package}" - GIT_URL="${cfg.source.url}" - GIT_REF="${cfg.source.ref}" - - echo "==> Source: $GIT_URL (ref: $GIT_REF)" - echo "==> Target: $SOURCE_DIR" - - # Clone or update the repository + # Clone or update if [ ! -d "$SOURCE_DIR/.git" ]; then - echo "==> Cloning repository..." - git clone "$GIT_URL" "$SOURCE_DIR" + git clone "${cfg.source.url}" "$SOURCE_DIR" cd "$SOURCE_DIR" - git checkout "$GIT_REF" + git checkout "${cfg.source.ref}" NEEDS_BUILD=1 else cd "$SOURCE_DIR" - echo "==> Fetching updates..." git fetch origin - - # Check if we need to update - LOCAL_REF=$(git rev-parse HEAD) - REMOTE_REF=$(git rev-parse "origin/$GIT_REF" 2>/dev/null || git rev-parse "$GIT_REF") - - if [ "$LOCAL_REF" != "$REMOTE_REF" ]; then - echo "==> Updating to $GIT_REF..." - git checkout "$GIT_REF" - git pull origin "$GIT_REF" 2>/dev/null || true + LOCAL=$(git rev-parse HEAD) + REMOTE=$(git rev-parse "origin/${cfg.source.ref}" 2>/dev/null || git rev-parse "${cfg.source.ref}") + if [ "$LOCAL" != "$REMOTE" ]; then + git checkout "${cfg.source.ref}" + git pull origin "${cfg.source.ref}" 2>/dev/null || true NEEDS_BUILD=1 else - echo "==> Already at latest commit: $LOCAL_REF" NEEDS_BUILD=0 fi fi - # Check if build artifacts exist - if [ ! -d "node_modules" ] || [ ! -d "packages/admin-ui/build" ] || [ ! -L "packages/server/public" ]; then - echo "==> Build artifacts missing, build needed" - NEEDS_BUILD=1 - fi + # Check build artifacts + [ ! -d "node_modules" ] || [ ! -d "packages/admin-ui/build" ] && NEEDS_BUILD=1 - if [ "$NEEDS_BUILD" = "0" ]; then - echo "==> Everything up to date, skipping build" - exit 0 - fi + [ "$NEEDS_BUILD" = "0" ] && exit 0 - echo "==> Installing dependencies with pnpm (without scripts)..." + # Install and build pnpm install --no-frozen-lockfile --ignore-scripts + pnpm rebuild || true - echo "==> Running native module builds..." - # Run rebuild separately - this compiles native modules - pnpm rebuild || echo "Warning: Some native modules failed to build, continuing anyway..." - - # Explicitly rebuild problematic native modules that pnpm rebuild may miss - # These modules often fail to build during pnpm rebuild due to missing toolchain in PATH - echo "==> Rebuilding specific native modules..." - - # node-expat and iconv use standard node-gyp + # Rebuild problematic native modules for module in node-expat iconv; do - module_path=$(find node_modules/.pnpm -name "$module" -type d -path "*/$module" 2>/dev/null | head -1) - if [ -n "$module_path" ] && [ -f "$module_path/binding.gyp" ]; then - echo " Rebuilding $module at $module_path..." - (cd "$module_path" && npx node-gyp rebuild 2>&1) || echo " Warning: $module rebuild failed, continuing..." - fi + path=$(find node_modules/.pnpm -name "$module" -type d -path "*/$module" 2>/dev/null | head -1) + [ -n "$path" ] && [ -f "$path/binding.gyp" ] && (cd "$path" && npx node-gyp rebuild) || true done - # argon2 uses node-pre-gyp (different build system) - argon2_path=$(find node_modules/.pnpm -name "argon2" -type d -path "*/argon2" 2>/dev/null | head -1) - if [ -n "$argon2_path" ]; then - echo " Rebuilding argon2 at $argon2_path..." - (cd "$argon2_path" && npx node-pre-gyp install --fallback-to-build 2>&1) || echo " Warning: argon2 rebuild failed, continuing..." - fi + # argon2 uses node-pre-gyp + path=$(find node_modules/.pnpm -name "argon2" -type d -path "*/argon2" 2>/dev/null | head -1) + [ -n "$path" ] && (cd "$path" && npx node-pre-gyp install --fallback-to-build) || true - echo "==> Building project..." - # Use setsid to run turbo in a new session, isolating it from signal propagation - # This prevents pnpm's signal handling issues (exit code -2) when turbo calls pnpm run build - # See: https://github.com/pnpm/pnpm/issues/7374 + # Build with setsid to isolate from signal issues setsid --wait ./node_modules/.bin/turbo build - echo "==> Linking admin UI static files..." - cd packages/server - if [ -L public ]; then - rm public - fi - ln -s ../admin-ui/build public - - echo "==> Build complete!" + # Link admin UI + ln -sfn ../admin-ui/build packages/server/public ''; }; - # Main lamassu server service + # Main server systemd.services.lamassu-server = { description = "Lamassu Bitcoin ATM Server"; wantedBy = [ "multi-user.target" ]; after = [ "network.target" "postgresql.service" "lamassu-postgres-setup.service" "lamassu-build.service" "nix-bitcoin-secrets.target" ]; wants = [ "postgresql.service" "lamassu-postgres-setup.service" "lamassu-build.service" ]; - environment = { - NODE_ENV = "production"; - - # Database configuration (password read at runtime from secrets) - POSTGRES_HOST = "127.0.0.1"; - POSTGRES_PORT = "5432"; - POSTGRES_DB = cfg.database.name; - POSTGRES_USER = cfg.database.user; - - # Server configuration + environment = commonEnv // { SERVER_PORT = toString cfg.serverPort; - LOG_LEVEL = cfg.logLevel; - HOSTNAME = cfg.hostname; - - # SSL/TLS certificates (from nix-bitcoin secrets) - CA_PATH = cfg.certPath; - CERT_PATH = cfg.certPath; - KEY_PATH = cfg.keyPath; - - # Wallet and mnemonic - MNEMONIC_PATH = "${cfg.dataDir}/lamassu-mnemonic"; - - # Data directories - OFAC_DATA_DIR = "${cfg.dataDir}/ofac"; - ID_PHOTO_CARD_DIR = "${cfg.dataDir}/photos/idcards"; - FRONT_CAMERA_DIR = "${cfg.dataDir}/photos/frontcamera"; - OPERATOR_DATA_DIR = "${cfg.dataDir}/operator"; - - # Bitcoin RPC configuration (if enabled) - BTC_NODE_LOCATION = "remote"; - BTC_WALLET_LOCATION = "remote"; - BTC_NODE_USER = "lamassu"; - BTC_NODE_RPC_HOST = "192.168.0.34"; - BTC_NODE_RPC_PORT = "8332"; - BTC_NODE_PASSWORD = "L3XF8iUrr5FNk2k6mILI"; - - # Security - SKIP_2FA = if cfg.skip2FA then "true" else "false"; }; - serviceConfig = let - lamassuEnv = pkgs.writeShellScript "lamassu-env" '' - #!/bin/bash - set -euo pipefail - export PATH=${pkgs.nodejs_22}/bin:$PATH - # Read database password from nix-bitcoin secrets - DB_PASSWORD=$(cat ${secretsDir}/lamassu-db-password) - export DATABASE_URL="postgresql://${cfg.database.user}:$DB_PASSWORD@127.0.0.1:5432/${cfg.database.name}" - export POSTGRES_PASSWORD="$DB_PASSWORD" - export NODE_PATH=${cfg.package}/node_modules:${cfg.package}/packages/server/node_modules - cd ${cfg.package} - exec "$@" - ''; - in defaultHardening // { - WorkingDirectory = "${cfg.package}/packages/server"; + serviceConfig = hardeningConfig // { + WorkingDirectory = "${cfg.dataDir}/source/packages/server"; ExecStartPre = [ - # Generate BIP39 mnemonic if it doesn't exist - "${pkgs.bash}/bin/bash -c 'if [[ ! -f ${cfg.dataDir}/lamassu-mnemonic ]]; then echo \"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about\" > ${cfg.dataDir}/lamassu-mnemonic && chmod 600 ${cfg.dataDir}/lamassu-mnemonic; fi'" - # Run database migration + "${pkgs.bash}/bin/bash -c 'test -f ${cfg.dataDir}/lamassu-mnemonic || echo \"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about\" > ${cfg.dataDir}/lamassu-mnemonic'" "${lamassuEnv} ${pkgs.nodejs_22}/bin/node packages/server/bin/lamassu-migrate" ]; ExecStart = "${lamassuEnv} ${pkgs.nodejs_22}/bin/node packages/server/bin/lamassu-server --port ${toString cfg.serverPort} --logLevel ${cfg.logLevel}"; - - # Node.js specific overrides - MemoryDenyWriteExecute = false; - - # Allow read/write access - ReadWritePaths = [ cfg.dataDir cfg.package "/tmp" ]; - RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ]; - - # Service identity - User = cfg.user; - Group = cfg.group; - Restart = "on-failure"; - RestartSec = "10s"; }; preStart = '' - mkdir -p ${cfg.dataDir}/logs - - # Wait for PostgreSQL using peer authentication timeout=30 while ! ${pkgs.postgresql}/bin/psql -h /run/postgresql -U ${cfg.database.user} -d ${cfg.database.name} -c '\q' 2>/dev/null; do - if [ $timeout -le 0 ]; then - echo "Timeout waiting for PostgreSQL" - exit 1 - fi - echo "Waiting for PostgreSQL..." + [ $timeout -le 0 ] && exit 1 sleep 1 ((timeout--)) done - echo "PostgreSQL is ready" ''; }; - # Admin server service + # Admin server systemd.services.lamassu-admin-server = { description = "Lamassu Admin Server"; wantedBy = [ "multi-user.target" ]; after = [ "network.target" "lamassu-server.service" "lamassu-build.service" ]; wants = [ "lamassu-server.service" "lamassu-build.service" ]; - environment = { - NODE_ENV = "production"; - LOG_LEVEL = cfg.logLevel; - HOSTNAME = cfg.hostname; - CA_PATH = cfg.certPath; - CERT_PATH = cfg.certPath; - KEY_PATH = cfg.keyPath; - # Database configuration (password read at runtime from secrets) - POSTGRES_HOST = "127.0.0.1"; - POSTGRES_PORT = "5432"; - POSTGRES_DB = cfg.database.name; - POSTGRES_USER = cfg.database.user; - MNEMONIC_PATH = "${cfg.dataDir}/lamassu-mnemonic"; - SKIP_2FA = if cfg.skip2FA then "true" else "false"; - # Data directories - OFAC_DATA_DIR = "${cfg.dataDir}/ofac"; - ID_PHOTO_CARD_DIR = "${cfg.dataDir}/photos/idcards"; - FRONT_CAMERA_DIR = "${cfg.dataDir}/photos/frontcamera"; - OPERATOR_DATA_DIR = "${cfg.dataDir}/operator"; - }; + environment = commonEnv; - serviceConfig = let - lamassuAdminEnv = pkgs.writeShellScript "lamassu-admin-env" '' - #!/bin/bash - set -euo pipefail - export PATH=${pkgs.nodejs_22}/bin:$PATH - # Read database password from nix-bitcoin secrets - DB_PASSWORD=$(cat ${secretsDir}/lamassu-db-password) - export DATABASE_URL="postgresql://${cfg.database.user}:$DB_PASSWORD@127.0.0.1:5432/${cfg.database.name}" - export POSTGRES_PASSWORD="$DB_PASSWORD" - export NODE_PATH=${cfg.package}/node_modules:${cfg.package}/packages/admin-server/node_modules - cd ${cfg.package} - exec "$@" - ''; - in defaultHardening // { - WorkingDirectory = "${cfg.package}/packages/server"; - ExecStart = "${lamassuAdminEnv} ${pkgs.nodejs_22}/bin/node packages/server/bin/lamassu-admin-server --logLevel ${cfg.logLevel}"; - MemoryDenyWriteExecute = false; - ReadWritePaths = [ cfg.dataDir cfg.package ]; - User = cfg.user; - Group = cfg.group; - Restart = "on-failure"; - RestartSec = "10s"; - - # Allow binding to privileged port 443 (production mode) + serviceConfig = hardeningConfig // { + WorkingDirectory = "${cfg.dataDir}/source/packages/server"; + ExecStart = "${lamassuEnv} ${pkgs.nodejs_22}/bin/node packages/server/bin/lamassu-admin-server --logLevel ${cfg.logLevel}"; AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ]; CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ]; }; }; - # Open firewall ports - # Port 3000 (configurable): machine API access (required for pairing and operation) - # Port 443: admin UI (production mode, hardcoded in upstream) + # Firewall networking.firewall.allowedTCPPorts = [ cfg.serverPort 443 ]; - # Add useful packages + # Helper tools environment.systemPackages = with pkgs; [ nodejs_22 nodePackages.pnpm postgresql (writeShellScriptBin "lamassu-register-user" '' - # Read database password from nix-bitcoin secrets DB_PASSWORD=$(cat ${secretsDir}/lamassu-db-password) - export NODE_PATH="${cfg.package}/node_modules:${cfg.package}/packages/server/node_modules" export DATABASE_URL="postgresql://${cfg.database.user}:$DB_PASSWORD@127.0.0.1:5432/${cfg.database.name}" - export HOSTNAME="${cfg.hostname}" - export POSTGRES_HOST="127.0.0.1" - export POSTGRES_PORT="5432" - export POSTGRES_DB="${cfg.database.name}" - export POSTGRES_USER="${cfg.database.user}" - export POSTGRES_PASSWORD="$DB_PASSWORD" - export SKIP_2FA="${if cfg.skip2FA then "true" else "false"}" - - sudo -E -u ${cfg.user} bash -c "cd ${cfg.package}/packages/server && ${pkgs.nodejs_22}/bin/node bin/lamassu-register \"\$@\"" -- "$@" - '') - (writeShellScriptBin "lamassu-status" '' - echo "=== Lamassu Server Status ===" - systemctl status lamassu-server lamassu-admin-server - echo "" - echo "=== Database Status ===" - sudo -u ${cfg.database.user} ${pkgs.postgresql}/bin/psql -d ${cfg.database.name} -c "SELECT version();" - echo "" - echo "=== Network Access ===" - echo "Server API: https://localhost:${toString cfg.serverPort}" - echo "Admin UI: https://localhost:443" + export POSTGRES_HOST="127.0.0.1" POSTGRES_PORT="5432" + export POSTGRES_DB="${cfg.database.name}" POSTGRES_USER="${cfg.database.user}" POSTGRES_PASSWORD="$DB_PASSWORD" + export HOSTNAME="${cfg.hostname}" SKIP_2FA="${if cfg.skip2FA then "true" else "false"}" + sudo -E -u ${cfg.user} ${pkgs.nodejs_22}/bin/node ${cfg.dataDir}/source/packages/server/bin/lamassu-register "$@" '') ]; };