Falling back to public Electrum is a PRIVACY decision, not just an availability
one: the queries carry user addresses. It is easy to miss because the failover
is silent and correct, so the only symptom is that balances keep working. Our own
LTC Fulcrum was dead for 11 days with every lookup going to third parties, and
nothing surfaced it.
- startup: log how many third-party fallbacks are configured and how to fail
closed instead, so the posture is visible in the operator's own logs
- .env.example: state the tradeoff at the setting, with both choices spelled out
rather than a default presented as obviously correct
- OPERATOR_SETUP: same note where an operator configures chains
Also expands the shipped fallback lists (BTC 3 -> 8, LTC 4 -> 7), which is both
more resilience and a wider spread of which server sees any given query.
- New docs/operations/CONSOLE.md: reaching /admin, NIP-98 login, the
defaults->env->DB precedence model, per-section runtime-safe vs
restart-required table, expected_versions concurrency, restart_apply_mode
(with the systemd Restart=always requirement for auto), clearing a DB
override. Linked from README + OPERATOR_SETUP.
- All six example recipes gained an explicit chain-enablement block
(FEATURE_XMR/WOW on, BTC/LTC/GRIN off) so they no longer hard-fail
Config::validate in production (default-on chains with empty sources).
- Documented all 16 previously-undocumented env vars in .env.example
(RESTART_APPLY_MODE, TOR_ADMIN_ONION, DEPLOYMENT_MODE, TIP_SHARE_BASE_URL,
...); RELAY.md gained the premium-post policy + RELAY_WRITE_ALLOWLIST_NPUBS;
ADMIN_PUBKEYS noted as reserved/inert.
Completes the operator side of the relay integration:
- deploy/relay/: reference docker-compose + config.toml for a locked-down
nostr-rs-relay wired to the backend's gRPC admission (host networking so the
loopback admission socket stays private). Clearly marked a template to pin/
verify against the operator's nostr-rs-relay version.
- docs/operations/RELAY.md: what it is (inbox relay, not the only relay), the
admission architecture, the three write policies + NIP-13 PoW, bundled vs
external, verification, and the seam's modularity. Linked from OPERATOR_SETUP.
- .env.example: the RELAY_* block (all off/placeholder).
Backend relay integration (config → seam → policy → gRPC admission →
capabilities → NIP-05 → packaging/docs) is now complete. Client DM stack next.
create-admin-wallet registered a pending admin key but never latched setup, so on
a fresh instance it left the bootstrap half-open and a later `setup` collided with
it (setup's guard only checked for an ACTIVE admin; the cli key was pending).
- New atomic Database::create_admin_key_bootstrapping: under the admin-keys
advisory lock, insert the pending generated key AND latch `locked` (unless
already validly locked), append audit, commit — one transaction. Fails CLOSED on
a tampered latch (defers to reset-setup) instead of silently healing it.
- bootstrap_admin (setup) now refuses on ANY live admin key (active OR pending),
not just active, so setup composes with create-admin-wallet. The adopt-existing-
deployment path (users but no admin keys) is unaffected: no live key -> proceeds.
- reset_setup now takes the same advisory lock (all latch writers serialize) and
documents the live-key recovery path.
- CLI prints bootstrapped-vs-added and a terminating lost-secret recovery hint.
- Docs (README + OPERATOR_SETUP section 5): both mutually-exclusive bootstrap paths.
Adversarially reviewed (atomicity / lockout / regression — all ship). L1 test
extended: fresh-latch, no-relatch-when-locked, pending semantics, composition
guard, and tamper-refuse (fails closed + rolls back the key insert).
- .env.example: add the required at-rest identity secrets (SEED_FINGERPRINT_PEPPER,
REFRESH_TOKEN_PEPPER, IP_SALT). They were missing, so `cp .env.example .env` on a
fresh setup boot-failed closed on the first required-secret check.
- README: correct the first-admin bootstrap to `smirk-admin setup --pubkey`.
create-admin-wallet registers a pending key but does not latch, so on a fresh
instance it leaves the bootstrap uninitialized.
- docs/operations/OPERATOR_SETUP.md: full deployment guide — database, secrets,
per-chain backends, run + verify, admin bootstrap, registration policy, and
smoke-testing an instance (SMOKE_API_STYLE=namespaced).
Surfaced by a clean-room operator setup against a fresh instance.