Unpaired machine shows "ATM Unavailable" instead of the pairing wizard (config validated before pairing) #70
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
A fresh / unprovisioned machine boots to the "ATM Unavailable" maintenance screen instead of the QR-pairing wizard. The whole point of pairing is that a fresh machine needs nothing pre-provisioned — but the boot flow currently requires
VITE_LNBITS_SERVER_PUBKEY(and a non-localhostVITE_RELAY_URL) before it ever checks pairing state, so an unpaired machine fails config validation and never reaches the wizard.Found while testing the freshly-built Sintra images (live ISO +
disk-image-sintra-usb): both have an empty.env(relay + server pubkey blank by design) and boot straight to "ATM Unavailable".Root cause
apps/machine/src/services/lightning.ts→initializeLightningServices()runs in this order:loadLightningConfig()lnbitsServerPubkeyif (!CONFIG.lnbitsServerPubkey) throw 'VITE_LNBITS_SERVER_PUBKEY is required'— fires even in non-strict moderesolveSigner(...)← only here doesNoPairingErrorget thrown for an unpaired machineapps/machine/src/services/init-error.ts→classifyInitError()maps onlyNoPairingError/BunkerRejectedError→unpaired(→ wizard). The config error at step 3 is a genericError, so it surfaces its raw message → the static "ATM Unavailable" screen (App.vue), never the wizard.So: config validation happens before the pairing check. An unpaired machine with a blank relay/pubkey can't reach the wizard.
Design intent (per discussion)
An unpaired machine should not need
VITE_RELAY_URLorVITE_LNBITS_SERVER_PUBKEYprovisioned — pairing is supposed to provide what's needed. Today the seed only carries the signing/transport bits, not the LNbits transport config:spire-seedcarries: one-shot NIP-46 connect token, spire signing pubkey, bunker URL (which itself contains a relay for NIP-46). Seepackages/nostr-client/src/bunker-signer.ts(spirePubkey,bunkerUrl).VITE_RELAY_URL) or the LNbits server pubkey (VITE_LNBITS_SERVER_PUBKEY) — those are still provisioned separately into.env(viaprovision-atm.sh/ the activation.envtemplate), and the journal shows them as distinct from the ATM/spire pubkey.Proposed direction (two parts)
NoPairingError(→unpaired→ wizard) regardless of relay/pubkey. The relay/pubkey validation then only applies to a paired machine that's actually trying to talk to LNbits..envprovisioning. Either extend thespire-seed(spirekeeper mint +parseSpireSeed) to include the LNbits transport relay + LNbits server pubkey, or derive them from the bunker/pairing.Open questions (for tomorrow)
bunker://…?relay=URL the same relay used for the ATM↔LNbits kind-21000 transport, or separate? If same, the relay is already in the seed and only the LNbits server pubkey needs adding.Related
Code refs
apps/machine/src/services/lightning.ts—initializeLightningServices()(config validation beforeresolveSigner)apps/machine/src/services/init-error.ts—classifyInitError()apps/machine/src/services/signer-resolver.ts—resolveSigner/NoPairingErrorpackages/nostr-client/src/bunker-signer.ts— seed fields (spirePubkey,bunkerUrl)Part 1 (reorder) — done in
334cb86(dev)initializeLightningServices()now resolves the signer before the strict +VITE_LNBITS_SERVER_PUBKEYvalidation. An unpaired machine (no seed, no binding, blank.env) throwsNoPairingError→unpaired→ wizard regardless of relay/pubkey provisioning; a paired machine still hits the config validation it legitimately needs. Typecheck clean, 34/34 tests pass.Open question #1 answered: the seed already carries the transport relay
Reading
packages/nostr-client/src/seed.ts, thespire-seedalready includes arelays[]array, documented as "relays where the spire publishes its own events (kind 21000 / 30078)". kind-21000 is the LNbits nostr-transport — so the transport relay is already in the seed; it's just not consumed asVITE_RELAY_URLtoday. Thebunker_url'srelay=is a separate (possibly different) relay for NIP-46 and is not the thing we need here.So part 2 narrows: the only field genuinely missing from the seed is the LNbits server pubkey (
VITE_LNBITS_SERVER_PUBKEY).Part 2 (follow-up) — narrowed scope
Two sides:
relayUrlfromseed.relaysandlnbitsServerPubkeyfrom a new optional seed field when paired, falling back to env (env override stays useful for dev). Requires threading the parsed seed'srelays+lnbitsPubkeyout ofresolveSignerand persisting them into thebunker_bindingrecord (state.db + Electron IPC types) so resume-from-binding also has them.lnbits_pubkeyto the seed JSON at/pairmint time. Hard dependency — until spirekeeper mints it, the field is always absent, so part 2's consumer side can't be exercised end-to-end.Open question #2 (where the server pubkey comes from): spirekeeper is the operator dashboard and knows/operates the LNbits instance, so minting it into the seed at
/pairtime is the natural source — no fresh-machine discovery step needed.This is why part 1 shipped on its own: it's self-contained and unblocks the wizard; part 2 spans repos and needs the spirekeeper mint first.
Parts D + E implemented
D — bitspire consumer wiring (on
dev, local commits53a0c2d,549491a,e578680; push held, see rollout):seed.ts): pubkey carried once as npub → derive hex + reconstructbunker_url; optionalbunker_relay→relays[0]; newlnbits_npub→lnbitsServerPubkey.resolveSignernow returns{ signer, transport }. Transport (relays +lnbitsServerPubkey) comes from the seed on pair / seeded resume, from the binding on seedless resume. Threaded out ofresolveSignerrather than re-parsed inloadLightningConfig, because the seed arrives over the one-shotget-atm-secretsIPC — a second consumer would break that contract.bunker_bindingpersistsrelays+lnbits_server_pubkey(state.db v11→v12, nullable → pre-#70 bindings resume and fall back to env).initializeLightningServicesresolves effective transport env-wins → pairing → dev localhost, validating the resolved values.E — spirekeeper mint: PR aiolabs/spirekeeper#37 (awaiting merge via Forgejo UI).
build_seed_urlemits the slim shape;pair_spirereadssettings.nostr_transport_public_key→hex_to_npub→lnbits_npub, raisingPairingErrorif the transport isn't running. 216 tests pass (incl. a separate commit fixing two pre-existing endpoint-test failures on main).Rollout gate
bitspire
devpush is held until spirekeeper#37 merges and the deployed lnbits has the updated extension — otherwise bitspire's new parser would reject an old-shape seed still minted by the deployed spirekeeper. Order: merge #37 → bumplnbits-extensionscatalog (spirekeeper) → redeploy demo lnbits → push bitspiredev+ cache → re-pair the Sintra with a fresh seed → verify blank-.env→ wizard → paired → backend end-to-end.(The Sintra itself keeps working through the gap regardless: old-shape seed + binding → resume from binding → env-provided relay/pubkey.)