From c3c531f1f3e0279edc6d435b99106d4c15138107 Mon Sep 17 00:00:00 2001 From: Padreug Date: Wed, 3 Jun 2026 16:28:08 +0200 Subject: [PATCH 1/3] dev compose: wire key master + nsec bunker integration, CORS allowlist, transport roster gate LNbits now fail-closes at boot without LNBITS_KEY_MASTER (aiolabs/lnbits#9 PR #17), and account provisioning routes through the nsec bunker (aiolabs/lnbits#18 phase 2.1+2.2) when LNBITS_NSEC_BUNKER_* are set, so both wiring blocks are required for the dev stack to boot and serve a webapp signup. Also covers: - LNBITS_CORS_ALLOWED_ORIGINS for the standalone webapp dev ports (aiolabs/lnbits PR #31; standalone hubs 5173 + 5180-5187 across the three browser-distinct origins localhost / 192.168.0.32 / 127.0.0.1). - NOSTR_TRANSPORT_ROSTER_REQUIRED to reject kind-21000 RPCs from unregistered senders rather than the silent-drop-via-auto-create failure mode caught 2026-05-30 (aiolabs/lnbits#42, satmachineadmin#20). - Bunker reliability: restart=unless-stopped so the watchdog's clean exit(0) on prolonged relay-disconnect gets auto-recovered, plus NSEC_BUNKER_DISABLE_WATCHDOG=1 to keep the bunker idling patiently through lnbits rebuilds longer than the 60s grace window, and NSEC_BUNKER_AUTOUNLOCK_PASSPHRASE so RemoteBunkerSigner clients don't need to drive a per-restart unlock_key RPC (aiolabs/nsecbunkerd#16). Co-Authored-By: Claude Opus 4.7 (1M context) --- docker-compose.dev.yml | 74 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 9716e65..e50eaa7 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -45,6 +45,47 @@ services: NOSTR_TRANSPORT_RELAYS: '["ws://localhost:5001/nostrrelay/test"]' NOSTR_TRANSPORT_PRIVATE_KEY: ${NOSTR_TRANSPORT_PRIVATE_KEY} NOSTR_TRANSPORT_PUBLIC_KEY: ${NOSTR_TRANSPORT_PUBLIC_KEY} + # Path-B roster gate (aiolabs/lnbits#42 / satmachineadmin#20). When + # true, inbound kind-21000 RPCs from unregistered sender pubkeys are + # rejected + ERROR-logged instead of auto-creating an account-from- + # npub (which was the silent-drop failure mode on 2026-05-30). + # satmachineadmin's resolver routes registered ATMs to the operator's + # wallet directly; under this flag, that's the ONLY routing path. + NOSTR_TRANSPORT_ROSTER_REQUIRED: "true" + # Envelope-encryption master key for user nostr nsecs (aiolabs/lnbits#9 + # PR #17). Required — lnbits fail-closes at startup if unset. Sourced + # from .env; rotate via `openssl rand -hex 32` (one-time ops procedure). + LNBITS_KEY_MASTER: ${LNBITS_KEY_MASTER} + + # nsec bunker integration (aiolabs/lnbits#18 phase 2.1 + 2.2). + # When set, new accounts get provisioned via the sidecar bunker + # (signer_type=RemoteBunkerSigner) instead of the LocalSigner + # encrypted-blob path. All four must be present together or + # NsecBunkerAdminClient.from_settings raises. + # Bunker container needs NSECBUNKER_ADMIN_NPUBS configured with the + # npub corresponding to LNBITS_NSEC_BUNKER_ADMIN_NSEC below. + LNBITS_NSEC_BUNKER_URL: ${LNBITS_NSEC_BUNKER_URL:-} + LNBITS_NSEC_BUNKER_PUBKEY: ${LNBITS_NSEC_BUNKER_PUBKEY:-} + LNBITS_NSEC_BUNKER_ADMIN_NSEC: ${LNBITS_NSEC_BUNKER_ADMIN_NSEC:-} + LNBITS_NSEC_BUNKER_KEYSTORE_PASSPHRASE: ${LNBITS_NSEC_BUNKER_KEYSTORE_PASSPHRASE:-} + # CORS allowlist for credentialed cross-origin webapp calls + # (aiolabs/lnbits PR #31). Mirrors the aio-demo wiring from + # server-deploy `b920b79`; covers three hostname flavors browsers + # treat as distinct origins: `localhost`, the LAN IP `192.168.0.32`, + # and the loopback IP `127.0.0.1` (browsers do NOT alias loopback + # to localhost for SOP purposes — `localhost` and `127.0.0.1` are + # different origins as far as CORS is concerned; gotcha caught + # 2026-05-30T11:05Z when a chromium session opened on the loopback + # IP got rejected against an allowlist that only had localhost + + # LAN-IP). Covers every standalone webapp dev port from + # `~/dev/webapp/dev/vite.*.config.ts` plus the umbrella 5173: + # 5173 main, 5180 libra, 5181 activities, 5182 wallet, + # 5183 chat, 5184 forum, 5185 market, 5186 tasks, + # 5187 restaurant. + # Empty allowlist would fall back to `Access-Control-Allow-Origin: *` + # (no credentials); explicit allowlist also covers the + # /auth/sign-event credentialed flow when bucket-B PRs land. + LNBITS_CORS_ALLOWED_ORIGINS: '["http://localhost:5173","http://192.168.0.32:5173","http://127.0.0.1:5173","http://localhost:5180","http://192.168.0.32:5180","http://127.0.0.1:5180","http://localhost:5181","http://192.168.0.32:5181","http://127.0.0.1:5181","http://localhost:5182","http://192.168.0.32:5182","http://127.0.0.1:5182","http://localhost:5183","http://192.168.0.32:5183","http://127.0.0.1:5183","http://localhost:5184","http://192.168.0.32:5184","http://127.0.0.1:5184","http://localhost:5185","http://192.168.0.32:5185","http://127.0.0.1:5185","http://localhost:5186","http://192.168.0.32:5186","http://127.0.0.1:5186","http://localhost:5187","http://192.168.0.32:5187","http://127.0.0.1:5187"]' # Lowered from the 40_000 default just to make sharding easy to # exercise in local tests without seeding hundreds of payments. # Production runs should leave this unset (defaults to 40_000). @@ -124,7 +165,17 @@ services: # nsecbunkerd dev branch and ~/dev/coordination/log.md 2026-05-27. build: ${NSECBUNKER_SRC:-/home/padreug/dev/nsecbunkerd/dev} hostname: nsecbunker - restart: on-failure + # Use `unless-stopped` (not `on-failure`) so the watchdog's deliberate + # exit(0) on prolonged relay-disconnect gets auto-recovered. The + # bunker's relay-watchdog is designed to exit-and-let-supervisor- + # restart; `on-failure` is half-implementing that contract (catches + # crashes but not the clean exit-as-restart-signal). See the watchdog + # comment in src/daemon/admin/index.ts and the routine-rebuild + # gotcha: any restart of the lnbits container that takes >60s knocks + # the bunker's WebSocket out beyond the grace window, which under + # `on-failure` left the bunker silently dead after every + # `regtest-lnbits-rebuild` until manually started. + restart: unless-stopped pids_limit: 100 mem_limit: 256mb memswap_limit: 256mb @@ -135,6 +186,27 @@ services: # Set to '1' in .env or shell to enable REQUEST_IN / RESPONSE_SENT / # PUBLISHED / PUBLISH_FAILED instrumentation. Default off. NSEC_BUNKER_DEBUG_TRANSPORT: ${NSEC_BUNKER_DEBUG_TRANSPORT:-0} + # Disable the relay-connection watchdog in the regtest stack. + # The watchdog calls process.exit(0) if the relay pool reports + # no connected relays for >60s; in dev that fires routinely + # during lnbits rebuilds (regtest-lnbits-rebuild takes more + # than 60s, the bunker's WebSocket drops, watchdog exits the + # process). With this set to '1' the bunker idles patiently + # instead — NDK's relay-reconnect loop attaches once lnbits is + # back up, and the in-memory unlocked-keys state from autounlock + # persists across the disconnect. Production deployments should + # leave this unset so external liveness checking gets the + # exit-as-restart-signal it expects. + NSEC_BUNKER_DISABLE_WATCHDOG: "1" + # Boot-time autounlock per aiolabs/nsecbunkerd#16 — every encrypted + # key in the Key table is unlocked at startup using this passphrase + # so RemoteBunkerSigner clients (lnbits, future webapp NIP-46) don't + # need to drive an admin unlock_key RPC per restart. Same passphrase + # the operator-side LNBITS_NSEC_BUNKER_KEYSTORE_PASSPHRASE uses to + # provision keys. See docs/AUTOUNLOCK.md in the bunker repo for the + # security trade. Leave unset to keep autounlock off (manual unlock + # per key per restart, the pre-#16 default). + NSEC_BUNKER_AUTOUNLOCK_PASSPHRASE: ${LNBITS_NSEC_BUNKER_KEYSTORE_PASSPHRASE:-} volumes: - ./data/nsecbunker:/app/config From 0b3970d0c07d101980eeddc6ed189394a340d651 Mon Sep 17 00:00:00 2001 From: Padreug Date: Tue, 9 Jun 2026 23:11:34 +0200 Subject: [PATCH 2/3] dev compose: drop Secure flag on auth cookies for HTTP LAN-IP dev MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sets LNBITS_AUTH_HTTPS_ONLY=false on the lnbits service so the regtest stack works when the webapp dev server hits lnbits over a LAN IP (e.g. http://192.168.0.32:5181 → http://192.168.0.32:5001) instead of localhost. Browsers silently discard `Secure` cookies over plain HTTP from non-loopback origins, which kills the /auth/sign-event double- submit CSRF flow (the GET succeeds, the cookie never lands, every POST 403s). aiolabs/lnbits#52 made the lnbits-side gate use settings.auth_https_only for the XSRF-TOKEN cookie (matching the existing auth_cookie_token behavior). With that fix on dev + this env var here, the regtest stack finally lets the bookmark/RSVP flow round-trip from a LAN-IP browser. Production deploys leave the setting unset (default true) — Secure stays on, behavior unchanged. --- docker-compose.dev.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index e50eaa7..b5daf90 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -86,6 +86,14 @@ services: # (no credentials); explicit allowlist also covers the # /auth/sign-event credentialed flow when bucket-B PRs land. LNBITS_CORS_ALLOWED_ORIGINS: '["http://localhost:5173","http://192.168.0.32:5173","http://127.0.0.1:5173","http://localhost:5180","http://192.168.0.32:5180","http://127.0.0.1:5180","http://localhost:5181","http://192.168.0.32:5181","http://127.0.0.1:5181","http://localhost:5182","http://192.168.0.32:5182","http://127.0.0.1:5182","http://localhost:5183","http://192.168.0.32:5183","http://127.0.0.1:5183","http://localhost:5184","http://192.168.0.32:5184","http://127.0.0.1:5184","http://localhost:5185","http://192.168.0.32:5185","http://127.0.0.1:5185","http://localhost:5186","http://192.168.0.32:5186","http://127.0.0.1:5186","http://localhost:5187","http://192.168.0.32:5187","http://127.0.0.1:5187"]' + # Drop the `Secure` flag on auth + CSRF cookies so the regtest + # stack works over plain HTTP from a LAN IP (e.g. testing the + # webapp at http://192.168.0.32:5181 against this lnbits at + # http://192.168.0.32:5001). Browsers silently discard `Secure` + # cookies served over `http://` from non-loopback origins, which + # breaks the /auth/sign-event double-submit CSRF flow. Production + # leaves this unset (default `true`) — see aiolabs/lnbits#52. + LNBITS_AUTH_HTTPS_ONLY: "false" # Lowered from the 40_000 default just to make sharding easy to # exercise in local tests without seeding hundreds of payments. # Production runs should leave this unset (defaults to 40_000). From 0ca8f03a9d766b52f883d4bc1a2bcbd87266395d Mon Sep 17 00:00:00 2001 From: Padreug Date: Mon, 15 Jun 2026 22:53:16 +0200 Subject: [PATCH 3/3] fix(bitcoind): accept lnbits/lnbits rpcauth alongside cookie The lnbits regtest test harness (tests/regtest/helpers.py) authenticates to bitcoind with -rpcuser=lnbits -rpcpassword=lnbits, but bitcoind was cookie-only, so mine_blocks / pay_onchain / create_onchain_address (and any bitcoind-using regtest test) failed with "Incorrect rpcuser or rpcpassword". Add -rpcauth for user lnbits (password lnbits). rpcauth does not set rpcpassword, so the auto-generated .cookie is still produced and the LN nodes' --bitcoind.rpccookie auth keeps working. Verified: both cookie and lnbits/lnbits creds authenticate; wrong creds rejected. Surfaced while validating aiolabs/lnbits#53 (native on-chain receive/send) against real lnd-1. Co-Authored-By: Claude Opus 4.8 (1M context) --- docker-compose.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 232b814..6f44302 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -107,6 +107,11 @@ services: image: boltz/bitcoin-core:25.0 command: - -regtest + # creds for the lnbits regtest test harness (tests/regtest/helpers.py uses + # -rpcuser=lnbits -rpcpassword=lnbits). rpcauth keeps the auto-generated + # cookie working too, so the LN nodes' --bitcoind.rpccookie auth is intact. + # $$ escapes compose interpolation. (user=lnbits, password=lnbits) + - -rpcauth=lnbits:a1b2c3d4e5f60718293a4b5c6d7e8f90$$64c29b7500c0e20b7917aa7f6dc6ce3eec896dd8f0bd3834f98c5ee489ef233f - -fallbackfee=0.00000253 - -zmqpubrawtx=tcp://0.0.0.0:29000 - -zmqpubrawblock=tcp://0.0.0.0:29001