bitspire/deploy/nixos/provision-access.sh
Padreug f11aced450 chore(access): prune unwired readers, amend ADR-003 for what shipped
ADR-003, .env.example, the access module's headers and the provisioning
schema still described the planned npub-QR → UID → serial-reader path.
What shipped (#86) is Bolt Card tap-to-enter over the main-process
pcscd reader with external_id as the identity, soft entry and
verify-at-payment. Nothing ever called availableAccessReaders(): the
camera npub-QR reader, the mock reader and the AccessReader seam were
dead, so they go; services/access now holds authorize, the card parser
and the credential types. The unused 'uid' scan variant goes with them;
'npub' (+PIN) and the 'challenge' seam stay.

The ADR gets an amendment section recording the differences, including
that open enrollment is not a security boundary and that the audit is
still a stub (both tracked as issues).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-20 15:16:59 +02:00

69 lines
2.7 KiB
Bash
Executable file

#!/usr/bin/env bash
# Provision the access-control gate (ADR-003) to a deployed bitSpire ATM.
# Pushes an access.json to /var/lib/bitspire/ and restarts the service, so the
# gate can be toggled on a machine without an image rebuild (mirrors
# provision-branding.sh). Env defaults are overridden by whatever this file sets.
#
# Usage:
# bash provision-access.sh <access.json> # SSH to localhost:2222 (QEMU)
# bash provision-access.sh <access.json> 192.168.1.50 # a real ATM on the LAN
# bash provision-access.sh <access.json> 192.168.1.50 22 # custom SSH port
#
# access.json schema (all keys optional; omitted keys fall back to env/defaults):
# {
# "enabled": true, // master switch for the gate
# "openEnrollment": true, // admit any Bolt Card (gate is not a security boundary)
# "devUnlock": false, // on-screen dev/operator unlock (bypasses the gate; default off)
# "salt": "per-machine", // hashing salt (provision a real one for prod)
# "allowList": [ // authorized identities (hashed); empty in open mode
# { "idHash": "<hashId(external_id,salt)>", "role": "user", "pinHash": "<hashPin(pin,salt)>" }
# ]
# }
#
# To DISABLE the gate again: push a file with {"enabled": false} (or delete
# /var/lib/bitspire/access.json on the machine) and restart.
set -euo pipefail
ACCESS_FILE="${1:-}"
ATM_HOST="${2:-localhost}"
ATM_SSH_PORT="${3:-2222}"
ATM_USER="bitspire"
REMOTE_FILE="/var/lib/bitspire/access.json"
if [ -z "$ACCESS_FILE" ]; then
echo "Usage: $0 <access.json> [host] [port]" >&2
echo " $0 ./access.json (QEMU on localhost:2222)" >&2
echo " $0 ./access.json 192.168.1.50 (real ATM)" >&2
exit 1
fi
if [ ! -f "$ACCESS_FILE" ]; then
echo "ERROR: access file not found: $ACCESS_FILE" >&2
exit 1
fi
# Fail fast on malformed JSON before touching the machine.
if command -v jq >/dev/null 2>&1; then
jq empty "$ACCESS_FILE" || { echo "ERROR: $ACCESS_FILE is not valid JSON" >&2; exit 1; }
fi
echo "=== Provisioning access gate to $ATM_HOST:$ATM_SSH_PORT ==="
echo "Local file : $ACCESS_FILE"
echo "Remote file: $REMOTE_FILE"
cat "$ACCESS_FILE"
echo ""
# Copy over SSH. --rsync-path=sudo because /var/lib/bitspire is owned by the
# bitspire service user, not the SSH user.
rsync -avz \
--rsync-path="sudo rsync" \
-e "ssh -o StrictHostKeyChecking=no -p $ATM_SSH_PORT" \
"$ACCESS_FILE" \
"$ATM_USER@$ATM_HOST:$REMOTE_FILE"
# Restart so loadAccessControl() re-reads the file.
ssh -o StrictHostKeyChecking=no -p "$ATM_SSH_PORT" "$ATM_USER@$ATM_HOST" \
"sudo systemctl restart bitspire"
echo ""
echo "=== Access gate provisioned. Service restarted. ==="