mirror of
https://github.com/Such-Software/smirk-backend-core.git
synced 2026-10-17 07:21:32 +02:00
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.
This commit is contained in:
@@ -133,6 +133,32 @@ PRICES_FETCH_INTERVAL_SECS=300
|
||||
# Tips are parked for the conditional-transfer redesign; off until that lands.
|
||||
FEATURE_TIPS=false
|
||||
|
||||
# ── Nostr relay (optional messaging plane; see docs/operations/RELAY.md) ────
|
||||
# Off by default. When on, the operator runs a relay (the packaged
|
||||
# nostr-rs-relay under deploy/relay, or an external one) that this backend
|
||||
# advertises (/capabilities + NIP-05) and write-restricts via a gRPC admission
|
||||
# service. It is the user's DM INBOX, never the only relay — clients also use the
|
||||
# public interop relays. Self-hosting concern.
|
||||
RELAY_ENABLED=false
|
||||
# bundled (run deploy/relay's nostr-rs-relay) | external (point at your own).
|
||||
RELAY_MODE=bundled
|
||||
# Public ws(s):// URL clients connect to (wss required in production).
|
||||
RELAY_URL=
|
||||
# Write policy the admission service enforces:
|
||||
# inbox-outbox — registered npubs publish their own events; anyone may
|
||||
# deliver a gift-wrapped DM (kind 1059) to a registered user.
|
||||
# author-allowlist — only registered npubs may publish anything.
|
||||
# open — accept everything (relay resource caps still apply).
|
||||
RELAY_WRITE_POLICY=inbox-outbox
|
||||
# NIP-13 proof-of-work bits required on cross-ecosystem (non-registered-author)
|
||||
# inbound events; 0 disables the PoW gate.
|
||||
RELAY_INBOUND_POW_BITS=0
|
||||
# Loopback address the gRPC event-admission service binds (the relay calls it).
|
||||
# Keep it loopback; the bundled compose reaches it via the host network.
|
||||
RELAY_ADMISSION_BIND=127.0.0.1:8090
|
||||
RELAY_MAX_EVENT_BYTES=65536
|
||||
RELAY_RETENTION_DAYS=30
|
||||
|
||||
# ── Admin surface (see docs; 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.
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
# nostr-rs-relay config for a locked-down Smirk messaging relay.
|
||||
#
|
||||
# REFERENCE TEMPLATE — the exact key set is version-specific; check it against the
|
||||
# nostr-rs-relay release you pin. The load-bearing piece is [grpc]
|
||||
# event_admission_server: it points at the Smirk backend's gRPC admission service,
|
||||
# which enforces RELAY_WRITE_POLICY + the NIP-13 PoW gate per event. Do NOT also
|
||||
# set a static [authorization] pubkey_whitelist — that would double-gate and
|
||||
# break the inbox (gift-wraps from non-registered senders).
|
||||
|
||||
[info]
|
||||
relay_url = "wss://relay.example.org/"
|
||||
name = "Smirk relay"
|
||||
description = "Locked-down Nostr relay for a self-hosted Smirk backend (DM inbox)."
|
||||
# pubkey = "<operator x-only hex pubkey>" # optional contact identity
|
||||
|
||||
[database]
|
||||
# SQLite is simplest for a single-node self-host; switch to postgres to share
|
||||
# infra. Keep it on a persisted volume (see docker-compose.yml).
|
||||
engine = "sqlite"
|
||||
data_directory = "/usr/src/app/db"
|
||||
|
||||
[grpc]
|
||||
# Smirk backend event-admission (nauthz). With host networking the backend's
|
||||
# loopback RELAY_ADMISSION_BIND is reachable here. This is what enforces the
|
||||
# write policy — keep it set.
|
||||
event_admission_server = "http://127.0.0.1:8090"
|
||||
|
||||
[network]
|
||||
address = "0.0.0.0"
|
||||
port = 8080
|
||||
|
||||
# Conservative, self-host-friendly resource ceilings (the anti-abuse floor even
|
||||
# for the `open` policy). Tune to your box.
|
||||
[limits]
|
||||
messages_per_sec = 10
|
||||
subscriptions_per_min = 60
|
||||
max_event_bytes = 65536
|
||||
max_ws_message_bytes = 131072
|
||||
max_ws_frame_bytes = 131072
|
||||
broadcast_buffer = 16384
|
||||
event_persist_buffer = 4096
|
||||
limit_scrapers = true
|
||||
|
||||
[options]
|
||||
# Reject events dated too far in the future (clock-skew abuse).
|
||||
reject_future_seconds = 1800
|
||||
|
||||
[authorization]
|
||||
# Write restriction is enforced by the gRPC admission service above, NOT a static
|
||||
# whitelist. Leave pubkey_whitelist unset.
|
||||
@@ -0,0 +1,35 @@
|
||||
# Bundled, locked-down Nostr relay for a self-hosted Smirk backend.
|
||||
#
|
||||
# This runs nostr-rs-relay as the messaging-plane relay. WRITE ADMISSION is
|
||||
# delegated to the Smirk backend's gRPC service (RELAY_ADMISSION_BIND, default
|
||||
# 127.0.0.1:8090) via nostr-rs-relay's [grpc] event_admission_server hook — so
|
||||
# the RELAY_WRITE_POLICY (inbox-outbox / author-allowlist / open) + NIP-13 PoW
|
||||
# are enforced by the backend, not baked into this file.
|
||||
#
|
||||
# Networking: the relay uses the HOST network so it can reach the backend's
|
||||
# loopback admission socket (127.0.0.1:8090) while keeping that socket private.
|
||||
# host networking is Linux-only; on other hosts, bind RELAY_ADMISSION_BIND to a
|
||||
# docker-reachable address behind your firewall and drop `network_mode: host`,
|
||||
# adding `extra_hosts: ["host.docker.internal:host-gateway"]` + a port mapping.
|
||||
#
|
||||
# This is a REFERENCE template — verify the config keys against the nostr-rs-relay
|
||||
# version you pin (https://github.com/scsibug/nostr-rs-relay).
|
||||
services:
|
||||
relay:
|
||||
image: ghcr.io/scsibug/nostr-rs-relay:latest # pin a digest in production
|
||||
restart: unless-stopped
|
||||
network_mode: host # reach the backend admission socket on 127.0.0.1
|
||||
user: "1000:1000"
|
||||
volumes:
|
||||
- ./config.toml:/usr/src/app/config.toml:ro
|
||||
- relay-data:/usr/src/app/db
|
||||
environment:
|
||||
RUST_LOG: info
|
||||
# Modest, self-host-friendly resource ceilings.
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 512M
|
||||
|
||||
volumes:
|
||||
relay-data:
|
||||
@@ -110,6 +110,13 @@ self-hosting bypass them. All default off or open.
|
||||
- **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](RELAY.md).
|
||||
|
||||
## Testing an instance
|
||||
|
||||
The `@smirk/smoke-tests` harness drives the API client end-to-end. Point it at an
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
# Nostr relay (messaging plane) — operator guide
|
||||
|
||||
The relay is **optional**. It gives your users a first-party, **locked-down**
|
||||
Nostr relay that acts as their **encrypted-DM inbox** — the transport foundation
|
||||
for messaging (and, later, P2P coordination). It is **off by default**; a Smirk
|
||||
backend is fully functional without it.
|
||||
|
||||
## What it is (and isn't)
|
||||
|
||||
- **It is the user's inbox relay**, advertised via `/capabilities` and NIP-05
|
||||
relay hints, and **write-restricted** by policy (see below).
|
||||
- **It is NOT the only relay.** For cross-wallet delivery (e.g. to/from Goblin or
|
||||
any Nostr user), clients also connect to the public interop relays
|
||||
(`relay.damus.io`, `nos.lol`). A private relay alone would black-hole DMs to
|
||||
outsiders — so the NIP-05 hints list your relay **first**, then the public set.
|
||||
- The backend **does not run the relay process** — it *connects to + advertises*
|
||||
it and enforces the write policy. You run the relay (bundled or external).
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
wallet ──ws──▶ your relay (nostr-rs-relay) ──gRPC EventAdmit──▶ smirk-backend
|
||||
(RELAY_WRITE_POLICY
|
||||
+ NIP-13 PoW,
|
||||
registered-npub DB)
|
||||
```
|
||||
|
||||
`nostr-rs-relay`'s `[grpc] event_admission_server` calls the backend once per
|
||||
inbound event; the backend runs the pure policy engine (`src/infra/relay/policy.rs`)
|
||||
against the user DB and returns PERMIT/DENY. This is dynamic (per-event, per-
|
||||
recipient), which a static pubkey whitelist can't do.
|
||||
|
||||
## Write policies (`RELAY_WRITE_POLICY`)
|
||||
|
||||
| policy | who may publish |
|
||||
| --- | --- |
|
||||
| `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) |
|
||||
|
||||
`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.
|
||||
|
||||
## Enable it
|
||||
|
||||
1. Set the relay env (see `.env.example` → *Nostr relay*):
|
||||
```sh
|
||||
RELAY_ENABLED=true
|
||||
RELAY_MODE=bundled # or external
|
||||
RELAY_URL=wss://relay.yourdomain.tld # what clients connect to (wss in prod)
|
||||
RELAY_WRITE_POLICY=inbox-outbox
|
||||
RELAY_INBOUND_POW_BITS=0
|
||||
RELAY_ADMISSION_BIND=127.0.0.1:8090 # loopback gRPC admission
|
||||
```
|
||||
The backend fail-closes at startup on a bad relay config, and for any non-`open`
|
||||
policy it spawns the gRPC admission service on `RELAY_ADMISSION_BIND`.
|
||||
|
||||
2. **Bundled** (`RELAY_MODE=bundled`): run the reference `nostr-rs-relay` in
|
||||
[`deploy/relay/`](../../deploy/relay/):
|
||||
```sh
|
||||
# set relay_url + event_admission_server in deploy/relay/config.toml first
|
||||
docker compose -f deploy/relay/docker-compose.yml up -d
|
||||
```
|
||||
It uses host networking so the relay reaches the loopback admission socket
|
||||
while that socket stays private (Linux; see the compose comments for other
|
||||
hosts). Front `RELAY_URL` with your TLS terminator.
|
||||
|
||||
3. **External** (`RELAY_MODE=external`): point `RELAY_URL` at any relay you run,
|
||||
and configure *its* `[grpc] event_admission_server` at `RELAY_ADMISSION_BIND`.
|
||||
|
||||
## Verify
|
||||
|
||||
- `GET /capabilities` → `features.nostr_relay: true` and a `messaging` object
|
||||
(`relay_url`, `write_policy`, `inbound_pow_bits`, `supported_nips`).
|
||||
- `GET /.well-known/nostr.json?name=<user>` lists your relay first in `relays`.
|
||||
- Publish a registered-npub event → accepted; a random-author, non-gift-wrap
|
||||
event → rejected (`inbox-outbox`); an under-PoW cross-ecosystem event →
|
||||
rejected (when `RELAY_INBOUND_POW_BITS > 0`).
|
||||
|
||||
## Modularity
|
||||
|
||||
`nostr-rs-relay` is the first adapter behind the `RelayProvider` seam
|
||||
(`src/infra/relay/`). The write policy lives in a pure engine; the gRPC transport
|
||||
is the adapter's concern. A different relay (strfry, or another messaging
|
||||
protocol) can enforce the same policy over a different mechanism without touching
|
||||
the policy or the advertising.
|
||||
Reference in New Issue
Block a user