seed default channel(s) on boot
fresh relay seeds configured open channels (RELAY_DEFAULT_CHANNELS, default general) so a client always has a room. relay-signed 9007 through the normal pipeline; idempotent; never reopens a closed group. also adds mission/roadmap and separation rationale to the readme.
This commit is contained in:
@@ -9,6 +9,84 @@ Nosterm client can feature-gate its UI and fall back gracefully on plain relays.
|
||||
This is the "home relay" in Nosterm's hybrid model: the client connects to this relay for
|
||||
its own communities while still connecting to public relays for general Nostr content.
|
||||
|
||||
## Mission
|
||||
|
||||
nostermd aims to be the premier self-hosted **home relay** for the Nosterm
|
||||
[Nostr Relay Chat (NRC)](https://git.nerdworks.io/nerdworks/nosterm-client) client — a
|
||||
complete, standards-based **NIP-29** group-chat backend anyone can run. We believe private
|
||||
communication is a right, not a feature, so the relay is built to advance **anonymity,
|
||||
end-to-end encryption, and censorship resistance** by default rather than as opt-in extras.
|
||||
|
||||
While we build features specifically to support the Nosterm client — the Nosterm capability
|
||||
handshake being the first — we are committed to **standards over lock-in**. We are more than
|
||||
happy to fold our developments into existing NIPs (or propose new ones) wherever the
|
||||
protocol can absorb them, so that what we build stays interoperable with the wider Nostr
|
||||
ecosystem rather than fragmenting into a Nosterm-only dialect.
|
||||
|
||||
Three principles guide every decision:
|
||||
|
||||
- **Small footprint.** A single pure-Go static binary with no cgo, running on a
|
||||
`scratch`/distroless image. It should stay auditable, fast to start, and cheap to
|
||||
self-host — no telemetry, no lock-in, no heavyweight dependencies.
|
||||
- **Easy to deploy, anywhere.** One prebuilt image, configured at runtime, that drops
|
||||
cleanly onto different **overlay network protocols** (Tor, I2P, Yggdrasil, Nym, and
|
||||
WireGuard-style meshes) so operators can run it wherever they trust the transport.
|
||||
- **Privacy as the default.** Anonymity, encryption, and anti-censorship are the baseline
|
||||
behavior. The relay should make the private path the easy path for the client that talks
|
||||
to it.
|
||||
|
||||
## Roadmap
|
||||
|
||||
The roadmap turns the mission into concrete direction. Items are aspirational and
|
||||
unordered — contributions that move any of them forward are welcome.
|
||||
|
||||
- **Full NIP-29 implementation** — grow beyond the MVP subset into complete relay-based
|
||||
groups: private/closed groups, roles, invites, the 39001/39002 admin/member lists, and
|
||||
richer moderation so a self-hosted relay is a complete NRC backend.
|
||||
- **Overlay-network first** — first-class, documented deployment recipes for Tor hidden
|
||||
services, I2P, and other overlay transports, keeping the same single runtime-configured
|
||||
image.
|
||||
- **Encryption everywhere** — support modern encrypted group and direct messaging (e.g.
|
||||
NIP-17 / gift-wrapped events) end to end, with no plaintext fallbacks that surprise
|
||||
operators or their users.
|
||||
- **Anonymity by design** — minimize metadata retained and leaked, support ephemeral and
|
||||
disposable identities, and avoid any relay behavior that fingerprints or phones home.
|
||||
- **Censorship resistance** — deepen **federation** so a channel survives any single relay
|
||||
disappearing and can be distributed across operators, with graceful reconnection and
|
||||
replay.
|
||||
- **Client-driven, standards-first** — build the features the Nosterm client needs, but
|
||||
upstream them: contribute our extensions back into existing NIPs (or propose new ones)
|
||||
rather than growing a Nosterm-only dialect, keeping the relay useful to any Nostr client.
|
||||
- **Stay small** — hold the line on binary size, dependency count, and container footprint
|
||||
as features grow.
|
||||
|
||||
## Why a separate repository
|
||||
|
||||
The Nosterm client and the nostermd relay ship as two independent repositories — and two
|
||||
independent container images — on purpose. Nostr is a client-side protocol: the browser
|
||||
talks directly to relays over WebSocket, so there is no shared backend binding the two
|
||||
together. Keeping them separate keeps that boundary honest and the architecture **modular**:
|
||||
|
||||
- **Mix and match.** The client is just a Nostr client — it works against any relay, not
|
||||
only this one. The relay is just a Nostr relay — it serves any NIP-29 client, not only
|
||||
Nosterm. Neither depends on the other's internals, so you can run one without the other.
|
||||
- **Deploy each where it belongs.** A static SPA served by Caddy and a stateful Go relay
|
||||
with a BoltDB store have different runtime, scaling, and persistence needs. Separate
|
||||
images let operators place, scale, and secure each independently — a shared home relay
|
||||
behind one client, one relay per client, many clients against a public relay, or the
|
||||
same-origin `/relay` proxy setup.
|
||||
- **Standards over coupling.** Because the split forces everything across the boundary to
|
||||
travel as plain Nostr events, it keeps us honest about being **standards-first** (see
|
||||
Mission) — the relay can never quietly grow a private, client-only channel that a
|
||||
non-Nosterm client couldn't use.
|
||||
- **Independent lifecycles.** Each repo has its own issues, releases, and contribution flow.
|
||||
A relay change ships without rebuilding the client and vice versa, so the two can evolve
|
||||
(and be audited) at their own pace.
|
||||
|
||||
If you want the batteries-included experience, the client's Caddy service can proxy `/relay`
|
||||
to this relay on a shared Docker network — modular pieces, one deployment. But that's a
|
||||
composition choice at deploy time, not a coupling baked into the code.
|
||||
|
||||
## What it does
|
||||
|
||||
- Serves NIP-01/11/42 via khatru, with pure-Go [BoltDB](https://github.com/etcd-io/bbolt)
|
||||
@@ -25,6 +103,9 @@ its own communities while still connecting to public relays for general Nostr co
|
||||
| 39000 | group metadata | (re)published + signed by the relay |
|
||||
- Advertises `software: "nostermd"` and `features: ["channels"]` in NIP-11 (adds
|
||||
`"federation"` when any federation peer is configured).
|
||||
- **Seeds a default channel on boot** so a fresh relay is never empty — a client always has
|
||||
at least one open room (`#general` by default) to join. Configure with
|
||||
`RELAY_DEFAULT_CHANNELS`.
|
||||
- **Optional federation** — mirror/ingest chat with peer relays (see below).
|
||||
|
||||
> **MVP scope.** Groups are auto-created as **open** on first join so the client flow works
|
||||
@@ -108,6 +189,7 @@ can proxy `/relay` to this relay on a shared Docker network).
|
||||
| `RELAY_CONTACT` | `relay@nosterm.com` | NIP-11 contact (NIP-05-style operator address) |
|
||||
| `RELAY_MOTD` | _(nerdworks.io banner)_ | Message-of-the-day shown in the client's relay server window (defaults to the built-in ascii banner) |
|
||||
| `RELAY_ENV` | _(dev)_ | Set to `production` to require a stable key (fail-fast if unset) |
|
||||
| `RELAY_DEFAULT_CHANNELS` | `general` | Comma-separated channel ids seeded as open groups on boot (leading `#` ok) |
|
||||
| `RELAY_SECRET_KEY` | _(dev: persisted)_ | 64-char hex identity that signs group metadata/rosters |
|
||||
| `RELAY_RETENTION_DAYS` | `0` | Prune chat messages older than N days (0 = unlimited) |
|
||||
| `RELAY_RETENTION_MAX_MESSAGES` | `0` | Keep at most N chat messages, newest first (0 = unlimited) |
|
||||
@@ -201,8 +283,10 @@ Tests live beside the package they cover:
|
||||
- `cmd/nostermd` — integration over a real in-process khatru server: **relay-key
|
||||
handling** (explicit key used verbatim; dev key persisted and stable across restarts), a
|
||||
**cross-user** WebSocket round-trip (one client joins and publishes, another receives via
|
||||
subscription), and two end-to-end **federation** two-relay tests (a message ingested across
|
||||
relays, and a mirrored post delivered exactly once despite the echo path).
|
||||
subscription), **default-channel seeding** (a fresh relay seeds its configured open
|
||||
channels, is idempotent, and never reopens a pre-existing closed channel), and two
|
||||
end-to-end **federation** two-relay tests (a message ingested across relays, and a mirrored
|
||||
post delivered exactly once despite the echo path).
|
||||
|
||||
> Run without `-race`: go-nostr's `unsafe`-based JSON serializer trips the race detector's
|
||||
> `checkptr` during any event signing (an upstream library issue, not a relay data race). The
|
||||
|
||||
Reference in New Issue
Block a user