diff --git a/CLAUDE.md b/CLAUDE.md index 1f3dc24..c67c3eb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -189,7 +189,7 @@ UP Board enumerates its eMMC controller via ACPI, not PCI. `upboard.nix` force-l ## Security priorities -1. **Private keys** — Never log nsec. The ATM's `VITE_ATM_PRIVATE_KEY` lives in `/var/lib/bitspire/.env` with mode 0600, owned by `lamassu:lamassu`. +1. **Private keys** — Never log nsec. The ATM's `VITE_ATM_PRIVATE_KEY` lives in `/var/lib/bitspire/.env` with mode 0600, owned by `bitspire:bitspire`. 2. **Payments** — Validate the bolt11 amount on cash-out before exposing the QR. Decode `payment_hash` from the bolt11 (cheap, avoids a roundtrip) and use it as the `subscribe_payments` filter. 3. **Replay** — LNURL-withdraw links use `uses:1` and are deleted on session abort. 4. **Encryption** — All RPC content is NIP-44 v2. NIP-04 is forbidden. diff --git a/deploy/nixos/README.md b/deploy/nixos/README.md index a6d7fac..cb991b0 100644 --- a/deploy/nixos/README.md +++ b/deploy/nixos/README.md @@ -9,14 +9,14 @@ NixOS module + tooling for deploying bitSpire to ATM hardware (Sintra, tejo, dou ``` deploy/nixos/ ├── bitspire-atm.nix # NixOS module: services.bitspire option tree + systemd unit + udev rules -├── configuration.nix # Base system (NixOS 24.05, locale, kernel, packages, lamassu user) +├── configuration.nix # Base system (NixOS 24.05, locale, kernel, packages, bitspire user) ├── live.nix # Live USB variant (squashfs + tmpfs root) — used by mkLiveConfig ├── hardware/ │ ├── douro.nix # Dell OptiPlex 9030 AIO (stock Douro motherboard; SATA SSD, eGalax touch) │ ├── batm3.nix # GeneralBytes BATM3 chassis with a Dell OptiPlex 9030 AIO grafted in (custom mod; WireGuard wired in) │ └── upboard.nix # Aaeon UP Board (Sintra + tejo; eMMC root via sdhci-acpi + mmc_block) ├── udev/ -│ └── 99-lamassu-hardware.rules # additional udev rules (loaded via configuration.nix) +│ └── 99-bitspire-hardware.rules # additional udev rules (loaded via configuration.nix) ├── provision-atm.sh # Push LNbits credentials to a deployed ATM via SSH ├── atm-transactions.sh # Operator query tool — reads /var/lib/bitspire/state.db ├── flash-douro-usb.sh # Helper for flashing a douro live USB @@ -128,7 +128,7 @@ ATM_PRIVATE_KEY=$(openssl rand -hex 32) \ bash deploy/nixos/provision-atm.sh 22 ``` -The script SSHes to `lamassu@:22`, writes `/var/lib/bitspire/.env`, and restarts `bitspire.service`. After a few seconds the kiosk should connect to LNbits over nostr-transport and show the live UI. +The script SSHes to `bitspire@:22`, writes `/var/lib/bitspire/.env`, and restarts `bitspire.service`. After a few seconds the kiosk should connect to LNbits over nostr-transport and show the live UI. > **Save the generated `ATM_PRIVATE_KEY`.** LNbits identifies this ATM by its public key; if you regenerate the key on a re-provision, LNbits will auto-create a fresh wallet and the old wallet's balance becomes inaccessible. @@ -153,11 +153,11 @@ Production ATMs on `main` continue to read `main`'s flake (no `?ref=` pin → re | Path | Owner | Purpose | |------|-------|---------| -| `/var/lib/bitspire/` | lamassu:lamassu, 0750 | Service data directory | -| `/var/lib/bitspire/.env` | lamassu:lamassu, 0600 | Runtime config — `VITE_RELAY_URL`, `VITE_LNBITS_SERVER_PUBKEY`, `VITE_LNBITS_HTTP_URL`, `VITE_ATM_PRIVATE_KEY`, … | -| `/var/lib/bitspire/state.db` | lamassu:lamassu | SQLite — cassette inventory, cashbox state, transaction history | -| `/var/lib/bitspire/logs/` | lamassu:lamassu, 0750 | Service logs (if app writes them) | -| `/opt/bitspire/` | lamassu:lamassu | Optional override drop for app assets (mostly unused — app comes from `/nix/store`) | +| `/var/lib/bitspire/` | bitspire:bitspire, 0750 | Service data directory | +| `/var/lib/bitspire/.env` | bitspire:bitspire, 0600 | Runtime config — `VITE_RELAY_URL`, `VITE_LNBITS_SERVER_PUBKEY`, `VITE_LNBITS_HTTP_URL`, `VITE_ATM_PRIVATE_KEY`, … | +| `/var/lib/bitspire/state.db` | bitspire:bitspire | SQLite — cassette inventory, cashbox state, transaction history | +| `/var/lib/bitspire/logs/` | bitspire:bitspire, 0750 | Service logs (if app writes them) | +| `/opt/bitspire/` | bitspire:bitspire | Optional override drop for app assets (mostly unused — app comes from `/nix/store`) | | `/etc/bitspire/config.env` | root:root | Static config emitted by the NixOS module (RELAY_URL, LNBITS_HTTP_URL — informational; the renderer reads `/var/lib/bitspire/.env` instead) | ## Common operations @@ -180,7 +180,7 @@ From the dev box: ```bash nixos-rebuild switch --flake .#sintra-installed \ - --target-host lamassu@ --use-remote-sudo + --target-host bitspire@ --use-remote-sudo ``` Locally builds the new closure (binary-cache where possible), copies it to the ATM over SSH, activates the new generation. A kernel-or-initrd change still requires a reboot to take effect — `sudo systemctl reboot` over SSH afterwards. @@ -259,7 +259,7 @@ Most of these are set automatically by the `mkInstalledConfig` helper in the roo ## Security notes -- **`lamassu` user has `wheel`/passwordless-sudo** to allow remote `nixos-rebuild switch` via `--use-remote-sudo`. This is acceptable for a kiosk on a network you control. Remove `security.sudo.wheelNeedsPassword = false` if you want to require a password. -- **SSH password auth is enabled by default** to allow initial provisioning. Once you've baked your dev box's pubkey into `/home/lamassu/.ssh/authorized_keys`, you can disable password auth: `services.openssh.settings.PasswordAuthentication = false`. -- **`/var/lib/bitspire/.env` contains the ATM's nostr private key.** It's mode 0600, owned by `lamassu:lamassu`. Don't `scp` it off the device; if you need to rotate the key, generate fresh and re-provision. +- **`bitspire` user has `wheel`/passwordless-sudo** to allow remote `nixos-rebuild switch` via `--use-remote-sudo`. This is acceptable for a kiosk on a network you control. Remove `security.sudo.wheelNeedsPassword = false` if you want to require a password. +- **SSH password auth is enabled by default** to allow initial provisioning. Once you've baked your dev box's pubkey into `/home/bitspire/.ssh/authorized_keys`, you can disable password auth: `services.openssh.settings.PasswordAuthentication = false`. +- **`/var/lib/bitspire/.env` contains the ATM's nostr private key.** It's mode 0600, owned by `bitspire:bitspire`. Don't `scp` it off the device; if you need to rotate the key, generate fresh and re-provision. - **No firewall is configured by default.** The ATM is meant to be on an operator-controlled network. If you expose it to a wider network, add a `networking.firewall` rule set restricting inbound to SSH from the operator's IPs only. diff --git a/deploy/nixos/bitspire-atm.nix b/deploy/nixos/bitspire-atm.nix index 892979f..0eaf828 100644 --- a/deploy/nixos/bitspire-atm.nix +++ b/deploy/nixos/bitspire-atm.nix @@ -117,8 +117,8 @@ in config = mkIf cfg.enable { # Create data directory systemd.tmpfiles.rules = [ - "d ${cfg.dataDir} 0750 lamassu lamassu -" - "d ${cfg.dataDir}/logs 0750 lamassu lamassu -" + "d ${cfg.dataDir} 0750 bitspire bitspire -" + "d ${cfg.dataDir}/logs 0750 bitspire bitspire -" ]; # Environment file for ATM configuration @@ -156,8 +156,8 @@ in serviceConfig = { Type = "simple"; - User = "lamassu"; - Group = "lamassu"; + User = "bitspire"; + Group = "bitspire"; WorkingDirectory = cfg.appDir; # Environment diff --git a/deploy/nixos/configuration.nix b/deploy/nixos/configuration.nix index 3dc9897..9ea2f21 100644 --- a/deploy/nixos/configuration.nix +++ b/deploy/nixos/configuration.nix @@ -1,4 +1,4 @@ -# Lamassu ATM NixOS Configuration +# bitSpire ATM NixOS Configuration # Base system configuration for ATM kiosk { config, lib, pkgs, pkgs-unstable, ... }: @@ -43,11 +43,12 @@ i18n.defaultLocale = "en_US.UTF-8"; # Users - users.groups.lamassu = { }; - users.users.lamassu = { + users.groups.bitspire = { }; + users.users.bitspire = { isNormalUser = true; - group = "lamassu"; - description = "Lamassu ATM"; + group = "bitspire"; + description = "bitSpire ATM"; + home = "/home/bitspire"; extraGroups = [ "wheel" # For admin access "video" # GPU access @@ -57,7 +58,7 @@ "networkmanager" # Network config ]; # No password - kiosk mode - initialPassword = "lamassu"; + initialPassword = "bitspire"; # pragma: allowlist secret }; # Kiosk display configuration @@ -85,7 +86,7 @@ # Display manager - auto-login (top-level since NixOS 24.11+) services.displayManager.autoLogin = { enable = true; - user = "lamassu"; + user = "bitspire"; }; # Audio (for transaction sounds) @@ -146,6 +147,7 @@ # Auto-updates (optional - disabled by default for stability) # system.autoUpgrade.enable = false; + # pragma: allowlist secret # Ensure WireGuard private key directory exists with correct permissions system.activationScripts.wireguard-key = '' mkdir -p /var/lib/wireguard @@ -155,6 +157,33 @@ fi ''; + # In-place rename migration: lamassu user → bitspire user. + # Runs after `users` activation so the bitspire user exists with its UID. + # Idempotent: re-running on an already-migrated system is a chown no-op. + # Leaves /home/lamassu in place as evidence — operator can `rm -rf` after + # confirming bitspire works. + system.activationScripts.bitspire-user-migration = { + deps = [ "users" ]; + text = '' + # SSH key migration: copy authorized_keys to /home/bitspire if missing, + # so the dev box can still SSH in as bitspire after the rename. + if [ -f /home/lamassu/.ssh/authorized_keys ] \ + && [ ! -f /home/bitspire/.ssh/authorized_keys ]; then + mkdir -p /home/bitspire/.ssh + cp /home/lamassu/.ssh/authorized_keys /home/bitspire/.ssh/authorized_keys + chown -R bitspire:bitspire /home/bitspire/.ssh + chmod 700 /home/bitspire/.ssh + chmod 600 /home/bitspire/.ssh/authorized_keys + fi + + # Data dir ownership: state.db / .env / branding/ may still be owned by + # the now-removed lamassu UID. Reset every boot — cheap no-op once done. + if [ -d /var/lib/bitspire ]; then + chown -R bitspire:bitspire /var/lib/bitspire + fi + ''; + }; + # Journal configuration services.journald = { extraConfig = '' diff --git a/deploy/nixos/hardware/batm3.nix b/deploy/nixos/hardware/batm3.nix index 4a7e99c..51f889c 100644 --- a/deploy/nixos/hardware/batm3.nix +++ b/deploy/nixos/hardware/batm3.nix @@ -130,7 +130,7 @@ serviceConfig = { Type = "oneshot"; RemainAfterExit = true; - User = "lamassu"; + User = "bitspire"; Environment = "DISPLAY=:0"; ExecStartPre = "${pkgs.coreutils}/bin/sleep 3"; ExecStart = "${pkgs.xorg.xinput}/bin/xinput set-prop 'eGalax Inc. USB TouchController' 'Coordinate Transformation Matrix' 0 -1.268 1.147 -1.224 0 1.118 0 0 1"; diff --git a/deploy/nixos/live.nix b/deploy/nixos/live.nix index 9fbc1f8..b23127f 100644 --- a/deploy/nixos/live.nix +++ b/deploy/nixos/live.nix @@ -175,7 +175,7 @@ in if [ ! -f /var/lib/bitspire/.env ]; then cp ${envTemplate} /var/lib/bitspire/.env chmod 600 /var/lib/bitspire/.env - chown lamassu:lamassu /var/lib/bitspire/.env + chown bitspire:bitspire /var/lib/bitspire/.env fi ''; @@ -189,7 +189,7 @@ in before = [ "bitspire.service" ]; serviceConfig = { Type = "oneshot"; - User = "lamassu"; + 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'"; }; diff --git a/deploy/nixos/provision-atm.sh b/deploy/nixos/provision-atm.sh index f7a8eb7..5b4c55b 100755 --- a/deploy/nixos/provision-atm.sh +++ b/deploy/nixos/provision-atm.sh @@ -23,7 +23,7 @@ set -euo pipefail ATM_HOST="${1:-localhost}" ATM_SSH_PORT="${2:-2222}" -ATM_USER="lamassu" +ATM_USER="bitspire" # Machine model + fiat. Model is operator-set; fiat defaults per-model to # match the flake's fiatCodeForModel table (sintra=EUR, douro/tejo=GTQ, diff --git a/deploy/nixos/udev/99-lamassu-hardware.rules b/deploy/nixos/udev/99-bitspire-hardware.rules similarity index 100% rename from deploy/nixos/udev/99-lamassu-hardware.rules rename to deploy/nixos/udev/99-bitspire-hardware.rules diff --git a/flake.nix b/flake.nix index aadf5d0..6387a8f 100644 --- a/flake.nix +++ b/flake.nix @@ -139,7 +139,7 @@ # Passwordless sudo for remote nixos-rebuild switch security.sudo.wheelNeedsPassword = false; - # Allow lamassu user to use nix commands + pull from aiolabs binary cache. + # Allow bitspire user to use nix commands + pull from aiolabs binary cache. # max-jobs = 1: prefer substitution from the cache, but allow ONE # local build slot for tiny activation-time stitch derivations # (boot.json, system-units, X-Restart-Triggers, etc.) that are @@ -160,7 +160,7 @@ # the upgrade fails loudly instead of silently wedging the box # for an hour. Time-bounds the max-jobs=1 escape hatch. timeout = 60; - trusted-users = [ "root" "lamassu" ]; + trusted-users = [ "root" "bitspire" ]; substituters = [ "https://cache.nixos.org" "https://aiolabs.cachix.org" ]; trusted-public-keys = [ "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=" @@ -205,7 +205,7 @@ DISPLAY=:0 ''} /var/lib/bitspire/.env chmod 600 /var/lib/bitspire/.env - chown lamassu:lamassu /var/lib/bitspire/.env + chown bitspire:bitspire /var/lib/bitspire/.env fi ''; @@ -234,7 +234,7 @@ before = [ "bitspire.service" ]; serviceConfig = { Type = "oneshot"; - User = "lamassu"; + 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'"; };