Robert Goodall

Projects and writing from the layer everything else runs on.

Listening to

Saskatchewan in 1881

Colter Wall

« Portfolio

nostermd active

The home relay that complements Nosterm — a minimal NIP-29 managed-group Nostr relay in pure Go, with a Nosterm capability handshake and optional chat federation.

role
author & maintainer
language
Go (khatru)
storage
BoltDB (pure Go, no cgo)
protocol
NIP-01/11/42, NIP-29 groups
image
static binary on distroless
status
active

Overview

nostermd is the home relay that complements the Nosterm client — a minimal NIP-29 managed-group Nostr relay built on khatru . It adds a Nosterm capability handshake: a features array in its NIP-11 document so a Nosterm client can feature-gate its UI and fall back gracefully on any plain relay.

Nostr is a client-side protocol — the browser talks directly to relays over WebSocket, so there is no shared backend. Nosterm runs a hybrid model: the client connects to public relays for general Nostr content while pointing at your own nostermd instance for private communities.

Why a separate repository

The client and the relay ship as two independent repositories and two independent images on purpose. Nothing binds them at runtime, so keeping them apart keeps the boundary honest:

  • Mix and match — the client works against any relay; the relay serves any NIP-29 client. Neither depends on the other’s internals.
  • Deploy each where it belongs — a static SPA and a stateful Go relay with a BoltDB store have different runtime, scaling, and persistence needs.
  • Standards over coupling — forcing everything across the boundary to travel as plain Nostr events keeps the relay standards-first, never a Nosterm-only dialect.

What it does

  • Serves NIP-01/11/42 via khatru, with pure-Go BoltDB storage — no cgo, so a fully static binary that runs on scratch/distroless.
  • Enforces a focused subset of NIP-29 managed groups: create/join/leave, admin put/remove user, member chat, and relay-signed group metadata.
  • Advertises software: "nostermd" and a features array in NIP-11 so clients can detect its capabilities.
  • Seeds a default channel on boot so a fresh relay is never empty — a client always has at least one open room to join.
  • Optional federation — mirror or ingest chat with peer relays so a channel survives any single relay going down.

Architecture

Standard Go layout — a thin command over focused, independently-testable packages:

cmd/nostermd/     main: env config, key loading, khatru wiring, NIP-11 serving
internal/
  group/          NIP-29 managed-group state machine (membership, admin, metadata)
  federation/     optional chat mirroring/ingest with peer relays
  retention/      background pruning of old chat messages
  relayinfo/      Nosterm capability handshake (NIP-11 features array)

Federation

Federation lets a channel live on more than one relay. It is off by default and chat-only by design: only chat messages cross the relay boundary, while membership, admins, bans, and metadata stay local to each relay — every operator moderates their own instance. Loop prevention is structural: a re-received event is a duplicate in the store, so the egress hook never fires twice and A↔B↔A cycles die on the second sight. No “seen” set to tune or get wrong.

Principles

  • Small footprint — a single pure-Go static binary, auditable, fast to start, cheap to self-host. No telemetry, no lock-in.
  • Deploy anywhere — one runtime-configured image that drops onto overlay networks (Tor, I2P, Yggdrasil, Nym, and WireGuard-style meshes).
  • Privacy by default — anonymity, encryption, and anti-censorship as the baseline behavior, not opt-in extras.