refactor(deploy): rename system user lamassu → bitspire

System-level half of the lamassu → bitspire rebrand the rest of dev
already did at the path / service / package layers. Touches user/group
declarations, every systemd `User=` block, the udev rules filename, all
chown calls in flake.nix + live.nix, the displayManager autoLogin user,
the trusted-users nix entry, provision-atm.sh's ATM_USER, plus README +
CLAUDE.md doc references.

In-place migration for the Sintra dev unit (which auto-pulls dev at
04:00) lives in `system.activationScripts.bitspire-user-migration` and:
- copies `/home/lamassu/.ssh/authorized_keys` → `/home/bitspire/` once,
  so SSH access survives the rename
- recursively chowns `/var/lib/bitspire` to the new bitspire UID on
  every boot — cheap no-op once done, but covers the case where the
  data dir was written by the now-removed lamassu UID
- leaves `/home/lamassu/` in place as evidence; operator can `rm -rf`
  after confirming bitspire login works

Recovery path if the migration breaks SSH access: root key is still in
configuration.nix:142-144 (padreug@gizmo), so ssh root@<host> works.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Padreug 2026-05-25 08:01:32 +02:00
commit 264cc47e0c
9 changed files with 61 additions and 32 deletions

View file

@ -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 <sintra-lan-ip> 22
```
The script SSHes to `lamassu@<sintra-lan-ip>: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@<sintra-lan-ip>: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@<sintra-lan-ip> --use-remote-sudo
--target-host bitspire@<sintra-lan-ip> --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.

View file

@ -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

View file

@ -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 = ''

View file

@ -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";

View file

@ -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'";
};

View file

@ -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,