From a455c60ed0e01a1030c8b33c194d4bf3a2364c1b Mon Sep 17 00:00:00 2001 From: jwinterm Date: Sun, 19 Jul 2026 15:52:07 -0400 Subject: [PATCH] docs: operator console guide + fix example recipes + document all env vars - 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. --- .env.example | 86 +++++++++++- README.md | 4 + docs/operations/CONSOLE.md | 219 ++++++++++++++++++++++++++++++ docs/operations/OPERATOR_SETUP.md | 13 ++ docs/operations/RELAY.md | 18 +++ examples/community/.env.example | 13 ++ examples/friends/.env.example | 13 ++ examples/paid-access/.env.example | 13 ++ examples/paid-relay/.env.example | 13 ++ examples/personal/.env.example | 13 ++ examples/public-free/.env.example | 13 ++ 11 files changed, 417 insertions(+), 1 deletion(-) create mode 100644 docs/operations/CONSOLE.md diff --git a/.env.example b/.env.example index a83dcb4..9a4d80f 100644 --- a/.env.example +++ b/.env.example @@ -13,6 +13,19 @@ SERVER_PORT=8080 # so every real deployment must replace it. PUBLIC_API_URL=https://backend.example.org/api/v1 ENVIRONMENT=development +# Deployment topology: single (default) permits in-process challenge/nonce state; +# fleet means a load-balanced set of instances and requires shared/stateless +# challenge state. Must be single|fleet or boot fails. +DEPLOYMENT_MODE=single +# Comma-separated CIDRs or bare IPs whose X-Forwarded-For is trusted for the +# client IP (rate-limiting + audit). Empty (default) = always use the real TCP +# peer. Set this to your reverse proxy's address(es) when it terminates TLS. +TRUSTED_PROXIES= +# Comma-separated browser origins allowed by CORS (e.g. the web wallet). Empty +# (default) allows any origin — safe here because auth is a Bearer token, not a +# cookie, so no ambient credentials ride a cross-origin request. Credentials are +# never allowed. +CORS_ALLOWED_ORIGINS= # ── Database ────────────────────────────────────────────────────────────── DATABASE_URL=postgres://user:password@127.0.0.1:5432/smirk @@ -36,6 +49,10 @@ FEATURE_POW=false ALTCHA_HMAC_KEY= POW_REQUIRED=false ALTCHA_COST=100000 +# Testing aid: comma-separated lowercase pubkey hashes that ALWAYS require PoW, +# even when POW_REQUIRED=false (opt-in, for exercising the gate). Requires +# FEATURE_POW=true or boot fails closed. Leave empty in normal deployments. +TEST_POW_REQUIRED_FOR_PUBKEYS= # ── Registration gates (composable; each advertised via /capabilities) ──── # Require an operator-minted invite code to create a NEW wallet. Mint codes with @@ -74,10 +91,32 @@ PAYMENT_EXPIRES_MINUTES=60 # enabled the two modes are identical. REGISTRATION_GATE_MODE=all +# ── Chain enablement (each chain is independently feature-flagged) ───────── +# All default to TRUE. FAIL-CLOSED RULE: in production, an ENABLED chain with no +# source configured makes the server REFUSE TO BOOT — a chain that cannot be +# served must not advertise itself as available via /capabilities. So set the +# ones you do not run to false, or configure the source for each you keep on: +# BTC / LTC — an electrum/Fulcrum URL (BTC_ELECTRUM_URL / LTC_ELECTRUM_URL) or +# a fallback list. +# XMR / WOW — the *_LWS_ADMIN_KEY (plus its LWS + daemon URLs). +# GRIN — GRIN_OWNER_API_SECRET (plus the GRIN_* wallet/node wiring). +# In development these are warnings rather than a hard boot error. +FEATURE_BTC=true +FEATURE_LTC=true +FEATURE_XMR=true +FEATURE_WOW=true +FEATURE_GRIN=true + # ── Bitcoin (Electrum/Fulcrum only; the backend runs no Core RPC node) ───── BTC_NETWORK=mainnet BTC_ELECTRUM_URL= BTC_ELECTRUM_FALLBACKS= +# Require CA-verified TLS (webpki + hostname) for ssl:// electrum servers. Applies +# to BOTH BTC and LTC. Default false: public electrum servers are self-signed by +# convention and their data is treated as hostile regardless, so accepting any +# cert keeps the public fallbacks usable. Set true if you run your own CA-cert'd +# Fulcrum and want to harden. +ELECTRUM_STRICT_TLS=false # ── Litecoin (Electrum/Fulcrum only; the backend runs no Core RPC node) ──── LTC_NETWORK=mainnet @@ -142,8 +181,26 @@ WALLET_RESTORE_POW_MAX_BITS=24 FEATURE_PRICES=true PRICES_PROVIDER=coingecko PRICES_FETCH_INTERVAL_SECS=300 +# Which assets the price feed quotes. UNSET = all supported (btc,ltc,xmr,wow,grin); +# a subset like "btc,xmr" narrows it; PRESENT-BUT-EMPTY (PRICES_ASSETS= with no +# value) means none. Leave commented to serve all. +#PRICES_ASSETS= +# Currency the feed quotes in. One of: usd, eur, gbp, jpy, cny, aud, cad, chf, btc. +# A typo fails closed at boot (rather than serving an empty feed). +PRICES_CURRENCY=usd +# Nostr-native identity (NIP-98 login/link, NIP-05 directory). ON by default; when +# on, PUBLIC_API_URL is required (and must be https in production). +FEATURE_NOSTR_IDENTITY=true +# Grin slatepack relay: a non-custodial encrypted store-and-forward mailbox for +# interactive Grin transfers. ON by default; can be disabled independently of Grin +# chain access. +FEATURE_GRIN_RELAY=true # Tips are parked for the conditional-transfer redesign; off until that lands. FEATURE_TIPS=false +# Base URL for public-tip share links (a tip's share URL is {base}/{tip_id}). +# REQUIRED when FEATURE_TIPS=true (boot fails closed without it); never hardcode a +# host, so federated/self-hosted instances build their own share links. +TIP_SHARE_BASE_URL= # ── Nostr relay (optional messaging plane; see docs/operations/RELAY.md) ──── # Off by default. When on, the operator runs a relay (the packaged @@ -205,11 +262,21 @@ FEED_OWNER_NPUB= FEED_ALLOWLIST_NPUBS= FEED_EXTRA_RELAYS= -# ── Admin surface (see docs; default posture is loopback/Tor) ────────────── +# ── Admin surface (see docs/operations/CONSOLE.md; default posture is loopback/Tor) ─ # Admin auth is "Sign in with Smirk": only admin PUBLIC keys are stored here, # never a seed or private key. The allowlist is mutated CLI/loopback-only in v1. +# When enabled, the embedded Operator Console SPA is served at /admin on this plane. ADMIN_ENABLED=false ADMIN_BIND=127.0.0.1:8081 +# Allow ADMIN_BIND to be a NON-loopback address. Default false: boot REFUSES a +# public admin bind (confidentiality is by socket). Only set true if you front the +# plane with Tor client-auth or a trusted proxy and firewall it. +ADMIN_ALLOW_PUBLIC_BIND=false +# Tor onion host you reach the admin plane at (e.g. .onion). REQUIRED for the +# Tor-onion posture: it is added to the admin Host allowlist, so without it a +# request whose Host header is the onion is rejected 403 (anti DNS-rebinding). +# Never logged or surfaced publicly. Leave empty for the loopback/SSH-tunnel path. +TOR_ADMIN_ONION= # Public absolute URL of THIS node's admin surface. The per-node id that binds # signed admin actions (so a challenge signed for one node can't be replayed to # another across a fleet) is DERIVED from this value — give each node in a fleet a @@ -221,6 +288,17 @@ ADMIN_JWT_SECRET= ADMIN_KEY_INTEGRITY_SECRET= ADMIN_MAX_KEYS=8 ADMIN_PENDING_KEY_TTL_DAYS=7 +# RESERVED / inert: ADMIN_PUBKEYS is parsed but NOT consumed anywhere in this +# version — it does not gate admin access. Authorization is the MAC-protected key +# allowlist in the database (managed via `smirk-admin` and the console Keys tab), +# never this variable. Leave it unset; setting it has no effect. +ADMIN_PUBKEYS= +# How the Operator Console applies a RESTART-REQUIRED config change (see CONSOLE.md): +# manual (default) — persist + flag; you apply it with a graceful restart. +# auto — the instance self-restarts after saving. REQUIRES a +# supervisor that brings it back (systemd Restart=always); +# otherwise it goes down and stays down. +RESTART_APPLY_MODE=manual # ── Public landing page (OFF by default; full + per-field tunable when on) ── PUBLIC_LANDING_ENABLED=false @@ -234,9 +312,15 @@ PUBLIC_STATS_ENABLED=false PUBLIC_STATS_CACHE_HOURS=24 # ── Data retention / erasure ────────────────────────────────────────────── +# When ERASURE_ENABLED=true, self-service erasure needs ADMIN_KEY_INTEGRITY_SECRET +# (its audit trail is the integrity-MAC'd hash chain) and PUBLIC_API_URL (its +# proofs bind that URL); boot fails closed otherwise. ERASURE_ENABLED=false ERASURE_PURGE_LOGIN_EVENTS=true ERASURE_EXPORT_PER_DAY=3 +# Grace window (hours) between a confirmed erasure and its execution. Max 87600 +# (10 years). +ERASURE_GRACE_PERIOD_HOURS=72 # Log retention (auto-deletion); 0 = keep indefinitely. RETENTION_LOGIN_EVENTS_DAYS=90 RETENTION_AUDIT_DAYS=365 diff --git a/README.md b/README.md index f6a9b62..7b49fc3 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,10 @@ Operator/admin functions live on a **separate loopback listener** (front it with Tor or an SSH tunnel) — confidentiality by socket, never merged into the public router or OpenAPI. +- **Operator Console**: an embedded single-page app served at `/admin` on the + loopback admin plane. Sign in with your admin key, then review status, manage + admin keys and invite codes, and edit the runtime config overlay (env plus a + validated DB layer). Guide: [docs/operations/CONSOLE.md](docs/operations/CONSOLE.md). - **Sign-in-with-Smirk admin auth** — a NIP-98 *signed action* over a single-use nonce (AUTHN) + a MAC-protected key allowlist (AUTHZ), composed so a route can't run without both. diff --git a/docs/operations/CONSOLE.md b/docs/operations/CONSOLE.md new file mode 100644 index 0000000..595f150 --- /dev/null +++ b/docs/operations/CONSOLE.md @@ -0,0 +1,219 @@ +# Operator Console: admin surface guide + +The Operator Console is an embedded single-page app served at `/admin` on the +backend's loopback admin plane. It is the browser front end for the same admin +JSON API the `smirk-admin` CLI complements: sign in with your admin key, review +status, manage admin keys and invite codes, and edit the runtime config overlay. + +The console ships inside the binary (built from `admin-ui/` and embedded at +compile time), so self-hosting needs no separate front-end deploy. It is off +unless the admin plane is enabled. + +## 1. Enable and reach the admin plane + +The admin plane is a separate listener from the public API; confidentiality is by +socket, not by middleware ordering. It never joins the public router or the +OpenAPI surface. + +Enable it in your `.env`: + +```sh +ADMIN_ENABLED=true +ADMIN_BIND=127.0.0.1:8081 # loopback by default +ADMIN_PUBLIC_URL=https://admin.example.org +ADMIN_JWT_SECRET= +ADMIN_KEY_INTEGRITY_SECRET= # back this up; losing it locks out admin +``` + +`ADMIN_BIND` defaults to loopback. Boot refuses a non-loopback bind unless you +also set `ADMIN_ALLOW_PUBLIC_BIND=true`, so the plane cannot be exposed on a +public interface by accident. Never bind it to a public address; reach it one of +two ways instead: + +- **SSH tunnel.** `ssh -L 8081:127.0.0.1:8081 you@your-server`, then open + `http://127.0.0.1:8081/admin` in your local browser. Because the Host is + `127.0.0.1`, the admin plane's Host allowlist accepts it with no extra config. +- **Tor onion service.** Point a hidden service at `127.0.0.1:8081` and set + `TOR_ADMIN_ONION=.onion`. The onion host is added to the admin Host + allowlist; without `TOR_ADMIN_ONION`, a request whose `Host` header is the + onion is rejected with `403` (the allowlist defends the loopback socket against + DNS rebinding, and only loopback names plus the one configured onion pass). + Then browse `http://.onion/admin` in Tor Browser. + +The console shell (`GET /admin`, `/admin/`, `/admin/assets/*`) is served +unauthenticated: it loads without a session and runs the login client-side. Every +JSON route it calls (`/admin/keys`, `/admin/config`, `/admin/invites`, ...) is +guarded, so serving the shell openly exposes nothing. + +### `ADMIN_PUBLIC_URL` is the per-node identity + +`ADMIN_PUBLIC_URL` is the value your wallet's signed action binds against (the +NIP-98 `u` tag), and a per-node id is derived from it so a challenge signed for +one node cannot be replayed to another node in a fleet. Give each node in a fleet +a distinct `ADMIN_PUBLIC_URL`. The server dictates and verifies this value from +config, never from the request `Host` header, so the value you reach the plane at +(loopback, tunnel, onion) does not have to equal `ADMIN_PUBLIC_URL`. + +## 2. Sign in with your admin key + +Login is a NIP-98 signed action ("Sign in with Smirk"): you prove control of your +admin private key by signing a single-use, server-issued challenge with your +Smirk wallet. Only admin public keys are ever stored; the server never holds an +admin seed or private key. + +1. Bootstrap a first admin key if you have not already (see + [OPERATOR_SETUP.md](OPERATOR_SETUP.md) section 5): `smirk-admin setup --pubkey + ` seeds your pubkey active, or `smirk-admin create-admin-wallet` + generates a pending key that activates on its first login. +2. Open the console and start login. It calls `POST /admin/auth/challenge`, which + returns a nonce, the `u` URL to bind, and the instance id. +3. Sign the kind-27235 (NIP-98) challenge event with the Smirk wallet holding your + admin key. The console posts it to `POST /admin/auth/verify`. +4. On success you receive an admin access token plus a refresh token, and the + console loads the authenticated tabs. A pending key activates on this first + login. + +Every admin-auth rejection (bad signature, a valid signature for a non-admin key, +a missing session) collapses to the same opaque `401`, so the surface is not an +enumeration oracle. Login and privileged actions are written to a hash-chained, +tamper-evident audit trail. + +## 3. Console tabs + +- **Status**: the effective feature/capability view, including any downgrades + (a flag that is on in config but serving as disabled because its secret or URL + is unset, e.g. `xmr` on with `XMR_LWS_ADMIN_KEY` empty). +- **Keys**: add, list, revoke, and rotate admin keys. A new key is added pending + and activates on its holder's first login. Revoking the last live key over the + network is refused (use the CLI); rotate is allowed on a solo key because it is + add-plus-revoke, the in-band recovery for a compromised sole key. +- **Invites**: mint single-use invite codes (`POST /admin/invites`) and list + them (`GET /admin/invites`). Raw codes are shown once; only their hash is + stored. +- **Config**: the runtime config overlay editor (below). + +## 4. Config: the env-vs-DB precedence model + +Effective config is layered: + +``` +defaults -> environment (.env) -> DB overlay (operator settings) -> validate +``` + +The DB overlay is a sparse, per-section patch stored in the `operator_settings` +table, one MAC'd row per section. A field present in the overlay overrides env for +that field; an absent field keeps its env (or default) value. `GET /admin/config` +returns, for every editable field, its effective value, the DB overlay, the +per-section version, its runtime class, and whether a restart is pending. So the +console can show each field's current value and whether its source is env/default +or the DB overlay. + +Editing goes through `PUT /admin/config` with a sparse patch. The patch is merged +onto the persisted overlay and re-run through the exact same `Config::validate` +that boot uses, so a bad edit is rejected with the identical error boot would +raise and nothing invalid is ever persisted or applied. Secrets are excluded from +the editable surface by construction: no overlay field maps to a secret (JWT +secrets, peppers, the ALTCHA HMAC key, the payment processor API key, the relay +admission bind), so the console can neither read nor set one. Secrets stay in env. + +The nine editable sections are: `landing`, `retention`, `restore`, `console`, +`registration`, `pow`, `features`, `relay`, `premium`. + +### Runtime-safe vs restart-required fields + +Each field is classified as runtime-safe (the change hot-swaps onto the live +config and takes effect on the next request) or restart-required (the change is +persisted and applied on the next graceful restart, because a boot-built client, +socket, or worker captured the old value). The classification is the single +source of truth in `runtime_class()` and is returned per field by +`GET /admin/config`. The exact classification: + +| Section | Class | Notes | +| --- | --- | --- | +| `landing.*` | runtime-safe | read per request on the landing/render path | +| `retention.*` | runtime-safe | **except** `retention.erasure_enabled` | +| `retention.erasure_enabled` | restart-required | gates a boot-spawned erasure sweep worker | +| `restore.*` | runtime-safe | restore policy is read per request | +| `console.*` | runtime-safe | `restart_apply_mode` is read at save time | +| `registration.*` | runtime-safe | **except** `registration.require_payment` | +| `registration.require_payment` | restart-required | gates the boot-built payment client (the provider is `None` at boot unless payment or premium is on) | +| `pow.*` | runtime-safe | the PoW gate is read per request | +| `features.*` | restart-required | every field: feature flags and per-chain enablement gate boot-built clients/workers (price poller, chain adapters) | +| `relay.*` | restart-required | the relay/admission service is boot-bound and the URL is advertised at boot | +| `premium.*` | restart-required | premium is enforced by the boot-bound relay admission service | + +Any `(section, field)` not listed defaults to restart-required, the safe default: +never hot-swap something whose consumers have not been reasoned about. + +A `PUT` that touches only runtime-safe fields hot-swaps just that patch onto the +running config and reports `applied: "runtime"`. A `PUT` that touches any +restart-required field persists the change and reports `applied: +"restart-required"` with `restart_pending: true`. `restart_pending` stays true +until a restart applies it, and it reflects any restart-required change saved by +an earlier `PUT`, even when the current edit was itself live. + +### Optimistic concurrency (`expected_versions`) + +Each section carries a version that increments on every write. To guard against +two operators editing at once, send `expected_versions` in the `PUT` body with the +version you last read per touched section. If the stored version has moved on, the +write is rejected with `409 Conflict` and nothing is persisted; reload and retry. +Omitting `expected_versions` skips the check (last write wins). + +### `restart_apply_mode`: manual vs auto + +`console.restart_apply_mode` (env `RESTART_APPLY_MODE`, default `manual`) decides +how a restart-required change is applied: + +- `manual` (default): the change is persisted and flagged `restart_pending`. You + apply it yourself with a graceful restart of the service. +- `auto`: after saving a restart-required change, the instance self-restarts + shortly after responding so the change takes effect. The console warns before + saving in this mode. + +Auto mode REQUIRES the service to be brought back on exit. The self-restart is a +graceful shutdown (it drains in-flight requests and exits); nothing relaunches the +process on its own. Run under systemd with `Restart=always` (or an equivalent +supervisor) so the instance comes back with the new config applied. Without that, +`auto` takes the instance down and leaves it down. Do not use `auto` for a +process you start by hand. + +### Clearing a DB override to fall back to env + +The overlay is stored per section, and a value you set through the console is a DB +override that wins over env for that field. Note the behavior in this version: + +- There is no per-field null clear. A `null` in a `PUT` patch means "leave this + field unchanged" and is skipped, so sending `null` does not remove an existing + override. +- To make a field's effect match env again, set it back through the console to the + same value env provides. The effective value then equals env, though the field's + source still reports `db`. +- `landing.title` is the one special case: setting it to an empty string clears it + back to the env value (unset). +- To fully drop a section's DB overlay so its source returns to env/default (and + future env edits for that section flow through again), delete that section's row + from the `operator_settings` table and restart, for example: + + ```sql + DELETE FROM operator_settings WHERE section = 'landing'; + ``` + + On load, any section whose row is absent, whose MAC fails, or which fails to + decode is ignored and falls back to env, fail-closed on tamper and + fail-open-to-env on a missing section. There is no console/API/CLI command to + clear a section in this version; the direct table delete is the supported path. + +## 5. Reserved / inert config + +- `ADMIN_PUBKEYS` is parsed from the environment but is NOT consumed anywhere in + this version: it does not gate admin access. Admin authorization is the + MAC-protected key allowlist in the database (managed via `smirk-admin` and the + Keys tab), never this variable. Treat `ADMIN_PUBKEYS` as reserved and leave it + unset; setting it has no effect and does not grant access. + +## Related + +- [OPERATOR_SETUP.md](OPERATOR_SETUP.md): full deployment and admin bootstrap. +- [RELAY.md](RELAY.md): the optional Nostr relay messaging plane. +- The root [`.env.example`](../../.env.example) documents every environment key. diff --git a/docs/operations/OPERATOR_SETUP.md b/docs/operations/OPERATOR_SETUP.md index 902fbbe..c24800e 100644 --- a/docs/operations/OPERATOR_SETUP.md +++ b/docs/operations/OPERATOR_SETUP.md @@ -100,6 +100,19 @@ 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`. +- **SSH tunnel:** `ssh -L 8081:127.0.0.1:8081 you@server`, then reach the plane at + `http://127.0.0.1:8081`. The loopback Host passes the admin allowlist with no + extra config. +- **Tor onion:** point a hidden service at `127.0.0.1:8081` and set + `TOR_ADMIN_ONION=.onion`. That onion host is added to the admin Host + allowlist; without it, a request whose `Host` header is the onion is rejected + (`403`), because the allowlist only accepts loopback names plus the one + configured onion (anti DNS-rebinding). + +The Operator Console (an embedded admin SPA served at `/admin` on this same +plane) gives you a browser front end for keys, invites, status, and the runtime +config overlay. See [CONSOLE.md](CONSOLE.md). + ## 6. Registration policy (optional) Gates are composable and advertised via `/capabilities`; returning wallets and diff --git a/docs/operations/RELAY.md b/docs/operations/RELAY.md index 8be3e5e..29ac414 100644 --- a/docs/operations/RELAY.md +++ b/docs/operations/RELAY.md @@ -37,11 +37,29 @@ recipient), which a static pubkey whitelist can't do. | `inbox-outbox` (default) | registered npubs publish their own events (outbox); **anyone** may deliver a NIP-17 gift-wrap (kind 1059) **addressed to a registered user** (inbox) | | `author-allowlist` | only registered Smirk npubs may publish anything (no external inbound) | | `open` | accept everything (the relay's resource caps still apply) | +| `premium-post` | registered users post wallet-functional events (DMs, tips, swap coordination) free; **general** notes require an active premium subscription (see the premium tier below). This is the flagship monetized relay policy | `RELAY_INBOUND_POW_BITS` (0 = off) requires a NIP-13 proof-of-work on cross-ecosystem (non-registered-author) inbound events — spam friction without an author allowlist. Only the `inbox-outbox` external-inbound branch is gated. +`RELAY_WRITE_ALLOWLIST_NPUBS` (comma-separated `npub1…` or 64-char hex; empty by +default) lists write-exempt npubs that may publish **any** kind regardless of the +write policy or premium status. The intended use is an announcements or +feed-owner account that seeds a `premium-post` feed without itself holding a +subscription. It is an exemption list, not the `author-allowlist` policy. + +## Premium tier (`premium-post`) + +The `premium-post` policy pairs with the premium subscription block +(`PREMIUM_ENABLED=true`, `PREMIUM_CURRENCY`, `PREMIUM_PLANS`) and the `PAYMENT_*` +processor. Wallet use stays free; premium unlocks general Nostr posting to your +relay. `Config::validate` fails closed if `PREMIUM_ENABLED` is set without +`RELAY_ENABLED=true`, `RELAY_WRITE_POLICY=premium-post`, the processor +credentials, and at least one priced `PREMIUM_PLANS` entry. Full design: +[../design/premium-relay.md](../design/premium-relay.md); ready-to-copy config: +the [`paid-relay`](../../examples/paid-relay/) recipe. + ## Enable it 1. Set the relay env (see `.env.example` → *Nostr relay*): diff --git a/examples/community/.env.example b/examples/community/.env.example index 7e2e5e2..e7d310a 100644 --- a/examples/community/.env.example +++ b/examples/community/.env.example @@ -43,6 +43,19 @@ RELAY_WRITE_POLICY=author-allowlist RELAY_INBOUND_POW_BITS=0 RELAY_ADMISSION_BIND=127.0.0.1:8090 +# ── Chains: enable ONLY what this recipe wires up ──────────────────────────── +# FAIL-CLOSED RULE: in production, an ENABLED chain with no source configured makes +# the server REFUSE TO BOOT. Every FEATURE_* chain defaults to true, so this recipe +# turns off the chains it does not wire up. It wires up Monero + Wownero (the +# XMR_/WOW_ blocks below); fill their *_LWS_ADMIN_KEY and it boots. To add BTC/LTC +# (electrum URL) or GRIN (GRIN_OWNER_API_SECRET + wiring), configure the source +# first, THEN flip its flag to true. +FEATURE_XMR=true +FEATURE_WOW=true +FEATURE_BTC=false +FEATURE_LTC=false +FEATURE_GRIN=false + # Chain endpoints — see ../../.env.example. XMR_LWS_URL= XMR_LWS_ADMIN_URL= diff --git a/examples/friends/.env.example b/examples/friends/.env.example index c54bca9..6eb8077 100644 --- a/examples/friends/.env.example +++ b/examples/friends/.env.example @@ -45,6 +45,19 @@ RELAY_WRITE_POLICY=inbox-outbox RELAY_INBOUND_POW_BITS=0 RELAY_ADMISSION_BIND=127.0.0.1:8090 +# ── Chains: enable ONLY what this recipe wires up ──────────────────────────── +# FAIL-CLOSED RULE: in production, an ENABLED chain with no source configured makes +# the server REFUSE TO BOOT. Every FEATURE_* chain defaults to true, so this recipe +# turns off the chains it does not wire up. It wires up Monero + Wownero (the +# XMR_/WOW_ blocks below); fill their *_LWS_ADMIN_KEY and it boots. To add BTC/LTC +# (electrum URL) or GRIN (GRIN_OWNER_API_SECRET + wiring), configure the source +# first, THEN flip its flag to true. +FEATURE_XMR=true +FEATURE_WOW=true +FEATURE_BTC=false +FEATURE_LTC=false +FEATURE_GRIN=false + # Chain endpoints — point at your own nodes / LWS. See ../../.env.example. XMR_LWS_URL= XMR_LWS_ADMIN_URL= diff --git a/examples/paid-access/.env.example b/examples/paid-access/.env.example index 3206d08..ca49ae5 100644 --- a/examples/paid-access/.env.example +++ b/examples/paid-access/.env.example @@ -50,6 +50,19 @@ FEATURE_PRICES=true FEATURE_TIPS=false RELAY_ENABLED=false +# ── Chains: enable ONLY what this recipe wires up ──────────────────────────── +# FAIL-CLOSED RULE: in production, an ENABLED chain with no source configured makes +# the server REFUSE TO BOOT. Every FEATURE_* chain defaults to true, so this recipe +# turns off the chains it does not wire up. It wires up Monero + Wownero (the +# XMR_/WOW_ blocks below); fill their *_LWS_ADMIN_KEY and it boots. To add BTC/LTC +# (electrum URL) or GRIN (GRIN_OWNER_API_SECRET + wiring), configure the source +# first, THEN flip its flag to true. +FEATURE_XMR=true +FEATURE_WOW=true +FEATURE_BTC=false +FEATURE_LTC=false +FEATURE_GRIN=false + # Chain endpoints — see ../../.env.example. XMR_LWS_URL= XMR_LWS_ADMIN_URL= diff --git a/examples/paid-relay/.env.example b/examples/paid-relay/.env.example index 11443bd..087ac21 100644 --- a/examples/paid-relay/.env.example +++ b/examples/paid-relay/.env.example @@ -71,6 +71,19 @@ FEATURE_PRICES=true FEATURE_TIPS=false ADMIN_ENABLED=false +# ── Chains: enable ONLY what this recipe wires up ──────────────────────────── +# FAIL-CLOSED RULE: in production, an ENABLED chain with no source configured makes +# the server REFUSE TO BOOT. Every FEATURE_* chain defaults to true, so this recipe +# turns off the chains it does not wire up. It wires up Monero + Wownero (the +# XMR_/WOW_ blocks below); fill their *_LWS_ADMIN_KEY and it boots. To add BTC/LTC +# (electrum URL) or GRIN (GRIN_OWNER_API_SECRET + wiring), configure the source +# first, THEN flip its flag to true. +FEATURE_XMR=true +FEATURE_WOW=true +FEATURE_BTC=false +FEATURE_LTC=false +FEATURE_GRIN=false + # Chain endpoints — see ../../.env.example (XMR/WOW LWS + daemon, GRIN_*, electrum). XMR_LWS_URL= XMR_LWS_ADMIN_URL= diff --git a/examples/personal/.env.example b/examples/personal/.env.example index b2752a3..89826c6 100644 --- a/examples/personal/.env.example +++ b/examples/personal/.env.example @@ -45,6 +45,19 @@ RELAY_ENABLED=false ADMIN_ENABLED=false ADMIN_KEY_INTEGRITY_SECRET= +# ── Chains: enable ONLY what this recipe wires up ──────────────────────────── +# FAIL-CLOSED RULE: in production, an ENABLED chain with no source configured makes +# the server REFUSE TO BOOT. Every FEATURE_* chain defaults to true, so this recipe +# turns off the chains it does not wire up. It wires up Monero + Wownero (the +# XMR_/WOW_ blocks below); fill their *_LWS_ADMIN_KEY and it boots. Running your own +# node, you will likely want more chains: to add BTC/LTC set the electrum URL, to +# add GRIN set GRIN_OWNER_API_SECRET (+ wiring), THEN flip that chain's flag to true. +FEATURE_XMR=true +FEATURE_WOW=true +FEATURE_BTC=false +FEATURE_LTC=false +FEATURE_GRIN=false + # Chain endpoints — point at YOUR nodes / light-wallet servers. See the full list # and formats in ../../.env.example (XMR/WOW LWS + daemon, GRIN_*, BTC/LTC electrum). XMR_LWS_URL= diff --git a/examples/public-free/.env.example b/examples/public-free/.env.example index 9ae4c19..6534ba8 100644 --- a/examples/public-free/.env.example +++ b/examples/public-free/.env.example @@ -49,6 +49,19 @@ RELAY_ENABLED=false # Admin HTTP plane off at launch; enable + bootstrap a key when you need it. ADMIN_ENABLED=false +# ── Chains: enable ONLY what this recipe wires up ──────────────────────────── +# FAIL-CLOSED RULE: in production, an ENABLED chain with no source configured makes +# the server REFUSE TO BOOT. Every FEATURE_* chain defaults to true, so this recipe +# turns off the chains it does not wire up. It wires up Monero + Wownero (the +# XMR_/WOW_ blocks below); fill their *_LWS_ADMIN_KEY and it boots. To add BTC/LTC +# (electrum URL) or GRIN (GRIN_OWNER_API_SECRET + wiring), configure the source +# first, THEN flip its flag to true. +FEATURE_XMR=true +FEATURE_WOW=true +FEATURE_BTC=false +FEATURE_LTC=false +FEATURE_GRIN=false + # Chain endpoints — reuse your existing daemons / LWS. See ../../.env.example. XMR_LWS_URL= XMR_LWS_ADMIN_URL=