Files
smirk-backend-core/docs/operations/OPERATOR_SETUP.md
T
jwinterm 17bb7bcc20 docs(relay): packaging (deploy/relay) + operator guide + .env.example
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.
2026-07-02 07:06:36 -04:00

5.7 KiB

Operator setup

Deploy a smirk-backend-core instance. It holds no seed and no spend key; it forwards view credentials to your chain backends per request and relays signed bytes. Run your own for full privacy.

Prerequisites

  • Rust (stable) and PostgreSQL 14+.
  • A chain backend for each chain you enable (all optional, feature-flagged):
    • Bitcoin / Litecoin — an Electrum/Fulcrum server (your own, or public).
    • Monero / Wownero — a light-wallet-server (LWS) and a daemon.
    • Grin — grin-wallet (owner/foreign API) and a grin node.
  • A reverse proxy terminating TLS in front of the public port (production).

1. Database

createdb smirk_backend_core     # or point DATABASE_URL at an existing database

Migrations run automatically on first start.

2. Secrets and configuration

cp .env.example .env

Generate each required secret with openssl rand -hex 32:

  • JWT_SECRET, SEED_FINGERPRINT_PEPPER, REFRESH_TOKEN_PEPPER, IP_SALT — always required.
  • ADMIN_JWT_SECRET, ADMIN_KEY_INTEGRITY_SECRET — required when ADMIN_ENABLED=true. Back up ADMIN_KEY_INTEGRITY_SECRET; losing it locks out admin.

Then set:

  • DATABASE_URL — the database from step 1.
  • PUBLIC_API_URL — the public absolute base URL, e.g. https://backend.example.org/api/v1. It is the canonical value NIP-98 tokens bind (never the request Host), and is required for Nostr identity and erasure.
  • ENVIRONMENT=production — enforces HTTPS on external URLs and rejects placeholder secrets.

The server fails closed: it refuses to start on a missing or weak secret, or an inconsistent feature configuration. .env.example documents every key.

3. Chain backends

Enable a chain with its FEATURE_* flag and configure its source. A chain whose flag is on but whose source is unconfigured reports enabled: false via /capabilities instead of failing at call time.

Chain Keys
BTC / LTC {BTC,LTC}_ELECTRUM_URL (primary) and {BTC,LTC}_ELECTRUM_FALLBACKS (comma-separated). ssl:// / tls:// verify TLS with hostname checking; tcp:// is plaintext (LAN only). Primary is tried first, then fallbacks in random order.
XMR / WOW {XMR,WOW}_LWS_URL, {XMR,WOW}_LWS_ADMIN_URL, {XMR,WOW}_LWS_ADMIN_KEY, {XMR,WOW}_DAEMON_URL.
Grin GRIN_OWNER_API_URL + GRIN_OWNER_API_SECRET, GRIN_WALLET_PASSWORD, GRIN_FOREIGN_API_URL, and the node's GRIN_NODE_*.

4. Run

cargo run --release       # or run the built ./target/release/smirk-backend-core

Verify:

curl -s localhost:8080/health
curl -s localhost:8080/api/v1/capabilities    # enabled chains, features, restore + registration policy

5. First admin (optional)

Admin functions — key management, erasure, tamper-evident audit — run on a separate loopback listener; the public API needs no admin. To enable it, set ADMIN_ENABLED=true and the two admin secrets, then bootstrap with one of two mutually-exclusive first-run commands:

# A) You already have a Sign-in-with-Smirk (Nostr) key — seed its pubkey (active now):
smirk-admin setup --pubkey <x-only-hex>    # seed the first admin + latch the bootstrap

# B) You want the CLI to generate a key — writes the secret 0600, registers it
#    PENDING, and latches in one step (no `setup` needed):
smirk-admin create-admin-wallet --out admin-key.hex

smirk-admin doctor                         # database, live keys, audit chain

<x-only-hex> is the admin's Sign-in-with-Smirk (Nostr) public key; the admin authenticates with the matching key over NIP-98. Both commands latch the bootstrap, so run only one — after either, the other is refused by design (a live admin key already exists). With (B) the generated key is pending and activates on the holder's first NIP-98 login; import admin-key.hex into your signer and log in. If you lose that secret before the first login, re-run create-admin-wallet (adds a fresh key) and revoke-key the stale one.

The bootstrap latch is MAC-protected and one-way — a live deployment is adopted as already-bootstrapped, and tampering fails closed (the CLI and the boot path both refuse a tampered latch; run reset-setup --i-understand to re-open setup).

Expose the admin plane over a Tor onion or an SSH tunnel; never bind it to a public interface. ADMIN_BIND defaults to loopback, and a non-loopback bind requires an explicit ADMIN_ALLOW_PUBLIC_BIND=true.

6. Registration policy (optional)

Gates are composable and advertised via /capabilities; returning wallets and self-hosting bypass them. All default off or open.

  • Invite codes — REGISTRATION_REQUIRE_INVITE=true; mint with smirk-admin mint-invite --count <n>.
  • Proof-of-work — FEATURE_POW=true, POW_REQUIRED=true, ALTCHA_HMAC_KEY.
  • Restore policy — WALLET_RESTORE_POLICY=create-only|bounded|unlimited (default create-only), with optional proof-of-work pricing on restore depth.
  • Pay-to-register — REGISTRATION_REQUIRE_PAYMENT=true against a BTCPay-compatible processor (PAYMENT_*); a new wallet settles an invoice, paid to your own wallet, before it can register.

7. Nostr relay (optional)

Run a first-party, locked-down Nostr relay as your users' encrypted-DM inbox (RELAY_ENABLED=true + RELAY_*). Off by default; write-restricted via a gRPC admission service, advertised through /capabilities + NIP-05. Full guide: RELAY.md.

Testing an instance

The @smirk/smoke-tests harness drives the API client end-to-end. Point it at an instance with SMOKE_BACKEND_URL, and set SMOKE_API_STYLE=namespaced — this backend serves namespaced /wallet/utxo/* routes, not the legacy flat paths:

SMOKE_BACKEND_URL=https://backend.example.org/api/v1 SMOKE_API_STYLE=namespaced \
  npm run check-balance -w @smirk/smoke-tests