feat: seed-driven pairing over the LNbits nostr-transport (#70) #73

Merged
padreug merged 17 commits from feat/seed-driven-pairing into dev 2026-07-02 21:54:11 +00:00
Showing only changes of commit 20dbc8ca80 - Show all commits

fix(deploy): provision-atm.sh writes relay/pubkey only on explicit override (#70)

The script unconditionally wrote VITE_RELAY_URL + VITE_LNBITS_SERVER_PUBKEY (and
hard-exited if it couldn't scrape the pubkey), env-pinning every provisioned
machine and defeating the seed — the same bug as the activation default. Make it
seed-first: with a SPIRE_SEED, relay + pubkey come from the seed and are written
only when the operator explicitly passes RELAY_URL / LNBITS_SERVER_PUBKEY as a
deliberate pin. The no-seed dev-nsec path still scrapes/defaults them. Also drops
the unused VITE_LNBITS_HTTP_URL line.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Padreug 2026-07-02 15:38:14 +02:00 • committed by padreug

View file

@ -4,19 +4,22 @@
# kind-21000 NIP-44 v2 events on a relay — there is no out-of-band token, # kind-21000 NIP-44 v2 events on a relay — there is no out-of-band token,
# the ATM's nostr private key IS the credential. # pragma: allowlist secret # the ATM's nostr private key IS the credential. # pragma: allowlist secret
# #
# Required environment variables (or edit defaults below): # The primary input is SPIRE_SEED — the pairing seed carries the relay, the
# LNBITS_SERVER_PUBKEY Hex pubkey published by the LNbits server at startup. # LNbits server pubkey AND the signing identity, so a seed-provisioned machine
# From the LNbits compose: # needs nothing else (aiolabs/bitspire#70).
# docker logs lnbits | grep 'nostr_transport pubkey' #
# LNBITS_HTTP_URL Origin LNbits is reachable at over HTTP, used only # Environment variables:
# to compose the LNURL-withdraw callback URL that # SPIRE_SEED RECOMMENDED. The spire pairing seed
# customer wallets dereference. Default: http://10.0.2.2:5000 # (`spire-seed:v1:<base64url>`) minted by spirekeeper.
# RELAY_URL Nostr relay LNbits + the bunker subscribe on. # Carries relay + LNbits server pubkey + the production
# Default: ws://$HOST_IP:5001/nostrrelay/test (LNbits # identity under the NIP-46 bunker (aiolabs/bitspire#52 / #70).
# bundled nostrrelay). Override for a separate relay. # RELAY_URL OPTIONAL override — pins VITE_RELAY_URL and WINS over the
# SPIRE_SEED The spire pairing seed (`spire-seed:v1:<base64url>`) # seed's relay (env-first precedence). Leave unset to let the
# minted by spirekeeper. THIS is the production # seed drive it. Required only on the no-seed dev path
# identity under the NIP-46 bunker (aiolabs/bitspire#52). # (default there: ws://$HOST_IP:5001/nostrrelay/test).
# LNBITS_SERVER_PUBKEY OPTIONAL override (hex). Leave unset with a seed. On the
# no-seed dev path it's scraped from
# `docker logs lnbits | grep 'nostr_transport pubkey'`.
# ATM_PRIVATE_KEY DEV-ONLY 32-byte hex nsec fallback, used only when # ATM_PRIVATE_KEY DEV-ONLY 32-byte hex nsec fallback, used only when
# SPIRE_SEED is unset (no bunker). Generated if unset # SPIRE_SEED is unset (no bunker). Generated if unset
# AND no SPIRE_SEED is provided. # AND no SPIRE_SEED is provided.
@ -61,40 +64,51 @@ else
echo "--- LAN ATM: using $HOST_IP as dev machine address ---" echo "--- LAN ATM: using $HOST_IP as dev machine address ---"
fi fi
# Step 2: Resolve the LNbits server pubkey. Prefer the env override; else # Steps 2-4: transport config (relay + LNbits server pubkey) + signing identity.
# fall back to scraping the local docker compose stack. #
if [ -z "${LNBITS_SERVER_PUBKEY:-}" ]; then # Under aiolabs/bitspire#70 the relay + server pubkey come from the pairing SEED,
echo "" # so a seed-provisioned machine needs NEITHER in .env. We only pin them when the
echo "--- Step 1: Extracting LNbits nostr-transport pubkey from docker logs ---" # operator EXPLICITLY passes RELAY_URL / LNBITS_SERVER_PUBKEY (a deliberate
LNBITS_SERVER_PUBKEY=$(docker logs lnbits 2>&1 \ # override that WINS over the seed via env-first precedence), or when there is no
| grep -oP 'nostr_transport pubkey:?\s*\K[a-f0-9]{64}' \ # seed (the dev-nsec fallback has nothing else to supply them, so we scrape/default).
| tail -1 || true) TRANSPORT_LINES=""
if [ -z "$LNBITS_SERVER_PUBKEY" ]; then
echo "ERROR: Could not extract LNbits pubkey. Set LNBITS_SERVER_PUBKEY explicitly"
echo "or start the LNbits stack first (docker compose -f docker/docker-compose.dev.yml up lnbits)."
exit 1
fi
fi
echo "LNbits server pubkey: ${LNBITS_SERVER_PUBKEY:0:16}..."
# Step 3: Pin LNbits HTTP origin.
LNBITS_HTTP_URL="${LNBITS_HTTP_URL:-http://$HOST_IP:5000}"
# Step 4: Relay URL. Defaults to the LNbits bundled nostrrelay.
RELAY_URL="${RELAY_URL:-ws://$HOST_IP:5001/nostrrelay/test}"
# Step 5: Signing identity. Prefer the spire pairing seed (bunker). Only fall
# back to a generated dev nsec when no seed is supplied.
if [ -n "${SPIRE_SEED:-}" ]; then if [ -n "${SPIRE_SEED:-}" ]; then
echo ""
echo "--- Using spire pairing seed (bunker-backed identity) ---"
case "$SPIRE_SEED" in case "$SPIRE_SEED" in
spire-seed:v1:*) : ;; spire-seed:v1:*) : ;;
*) echo "ERROR: SPIRE_SEED must start with 'spire-seed:v1:'"; exit 1 ;; *) echo "ERROR: SPIRE_SEED must start with 'spire-seed:v1:'"; exit 1 ;;
esac esac
echo ""
echo "--- Spire pairing seed: relay + LNbits pubkey come from the seed ---"
if [ -n "${RELAY_URL:-}" ]; then
echo " (pinning VITE_RELAY_URL=$RELAY_URL — overrides the seed's relay)"
TRANSPORT_LINES="VITE_RELAY_URL=$RELAY_URL"
fi
if [ -n "${LNBITS_SERVER_PUBKEY:-}" ]; then
TRANSPORT_LINES="${TRANSPORT_LINES:+$TRANSPORT_LINES
}VITE_LNBITS_SERVER_PUBKEY=$LNBITS_SERVER_PUBKEY"
fi
IDENTITY_LINES="# Spire pairing seed — bunker-backed identity (aiolabs/bitspire#52) IDENTITY_LINES="# Spire pairing seed — bunker-backed identity (aiolabs/bitspire#52)
VITE_SPIRE_SEED=$SPIRE_SEED" VITE_SPIRE_SEED=$SPIRE_SEED"
else else
# No seed → DEV-ONLY nsec fallback. Nothing else supplies the relay + pubkey,
# so scrape/default them.
if [ -z "${LNBITS_SERVER_PUBKEY:-}" ]; then
echo ""
echo "--- No seed: extracting LNbits nostr-transport pubkey from docker logs ---"
LNBITS_SERVER_PUBKEY=$(docker logs lnbits 2>&1 \
| grep -oP 'nostr_transport pubkey:?\s*\K[a-f0-9]{64}' \
| tail -1 || true)
if [ -z "$LNBITS_SERVER_PUBKEY" ]; then
echo "ERROR: no SPIRE_SEED, and could not extract the LNbits pubkey."
echo "Provide a SPIRE_SEED (recommended — the seed carries relay + pubkey),"
echo "or set LNBITS_SERVER_PUBKEY explicitly."
exit 1
fi
fi
RELAY_URL="${RELAY_URL:-ws://$HOST_IP:5001/nostrrelay/test}"
TRANSPORT_LINES="VITE_RELAY_URL=$RELAY_URL
VITE_LNBITS_SERVER_PUBKEY=$LNBITS_SERVER_PUBKEY"
if [ -z "${ATM_PRIVATE_KEY:-}" ]; then if [ -z "${ATM_PRIVATE_KEY:-}" ]; then
ATM_PRIVATE_KEY=$(openssl rand -hex 32) ATM_PRIVATE_KEY=$(openssl rand -hex 32)
echo "" echo ""
@ -110,10 +124,10 @@ echo "--- Step 2: Writing .env to ATM ---"
ENV_CONTENT="# bitSpire Configuration ENV_CONTENT="# bitSpire Configuration
# Auto-generated by provision-atm.sh on $(date -Iseconds) # Auto-generated by provision-atm.sh on $(date -Iseconds)
# LNbits nostr-transport connection # LNbits nostr-transport. Relay + server pubkey come from the pairing seed
VITE_RELAY_URL=$RELAY_URL # (aiolabs/bitspire#70); present below only as an explicit override or the
VITE_LNBITS_SERVER_PUBKEY=$LNBITS_SERVER_PUBKEY # no-seed dev fallback.
VITE_LNBITS_HTTP_URL=$LNBITS_HTTP_URL $TRANSPORT_LINES
$IDENTITY_LINES $IDENTITY_LINES
@ -132,6 +146,6 @@ echo ""
echo "=== ATM provisioned successfully ===" echo "=== ATM provisioned successfully ==="
echo "" echo ""
echo "Credentials written to /var/lib/bitspire/.env" echo "Credentials written to /var/lib/bitspire/.env"
echo "ATM service restarted. It should connect to LNbits via relay $RELAY_URL." echo "ATM service restarted. Relay: ${RELAY_URL:-from the pairing seed}."
echo "" echo ""
echo "To check status: ssh -p $ATM_SSH_PORT $ATM_USER@$ATM_HOST 'sudo journalctl -u bitspire -f'" echo "To check status: ssh -p $ATM_SSH_PORT $ATM_USER@$ATM_HOST 'sudo journalctl -u bitspire -f'"