nsecbunkerd/docs/runbook-migrations.md
Padreug 14e20d50d4
Some checks failed
Docker image / build-and-push-image (push) Has been cancelled
docs: add migration & DB-maintenance runbook (never full-wipe nsecbunker.db)
Captures the deploy hazard found during #27 rollout (cfaun): the
nsecbunkerd<->LNbits pairing is split across both systems, so a full
nsecbunker.db wipe orphans LNbits's signer_config and forces an
identity-changing re-provision. Documents the targeted
'DELETE FROM SigningCondition' procedure, the keys-live-in-json fact,
and the migrate-on-boot no-op (#31).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 22:58:12 +02:00

3 KiB

nsecbunkerd migration & DB-maintenance runbook

Operational notes for applying schema migrations and ACL/pairing maintenance on deployed, LNbits-connected nsecbunkerd instances.

⚠️ Never full-wipe nsecbunker.db on an LNbits-connected instance

The nsecbunkerd ↔ LNbits pairing is split across both systems:

  • Bunker (nsecbunker.db): per-account KeyUser binding (keyed by LNbits's stable client pubkey) + redeemed Token + a shared Policy.
  • LNbits (accounts.signer_config, RemoteBunkerSigner): {token, client_nsec, policy_id}.

RemoteBunkerSigner.sign_event() signs directly with the stored client_nsec — it does NOT re-connect/re-redeem, and there is no auto-repair on restart or sign-failure. provision() runs only at new-account creation and mints a NEW npub (which changes the user's nostr identity).

Consequence of a full nsecbunker.db wipe: the KeyUser bindings are deleted → every LNbits account's stored config dangles → all signing fails, and the only standard "repair" (provision()) changes identities. Do not do it.

Correct way to strip the #24 materialized photocopies

Post-#27, token grants are evaluated live via the ACL step-4 Token → Policy → PolicyRule join, so the old materialized SigningCondition rows are redundant — and, written with expiresAt = NULL, they would keep granting past a token's expiry (silently re-opening #24 for already-paired clients). Strip them with a targeted delete that preserves the pairing:

-- Verify first.
SELECT COUNT(*) FROM Token WHERE redeemedAt IS NOT NULL;  -- bindings to preserve
SELECT COUNT(*) FROM SigningCondition;                    -- photocopies to strip

-- Keeps KeyUser + Token + Policy intact. Live-token clients keep working
-- untouched; only the stale photocopies are removed.
DELETE FROM SigningCondition;

Run against each instance's nsecbunker.db. If an instance was already full-wiped, recover by restoring the pre-wipe nsecbunker.db backup, then run the targeted delete.

Manual-override grants (add_signing_condition, web-approval) also live in SigningCondition. On an LNbits-only bunker there typically are none, so a blanket DELETE FROM SigningCondition is safe. If an instance uses manual overrides, delete only the policy-derived rows you intend to strip.

Keys are never in the DB

Key material lives in nsecbunker.json (keys), never in nsecbunker.db. A DB wipe loses ACL/pairing state, never keys. LNbits holds the bunker admin nsec (LNBITS_NSEC_BUNKER_ADMIN_NSEC) and is the sole admin client.

Schema migrations

Migrations are applied by the deploy's prisma migrate deploy, not by the daemon on boot — the in-start.js npm run prisma:migrate step is a no-op (tracked in #31). After adding a migration, make sure the deploy applies it.

Prisma on NixOS needs the engine env pinned to prisma-engines_6 (the bare prisma-engines attr is now 7.x with no libquery_engine.node; devShell fix tracked in #30). The deploy's package.nix already pins _6.