Files
Robert Goodall dd388061d1
ci / build (pull_request) Successful in 1m1s
ci / image (pull_request) Skipped
Auto-join #nosterm on startup; add mission/roadmap; chat-window banner
2026-07-25 08:17:20 -04:00

7.3 KiB

Nosterm

Nosterm — a terminal-style Nostr chat application

A terminal-style Nostr chat client.

The Nosterm web client: a terminal-style Nostr chat SPA built with SvelteKit and served as static files by Caddy. Nostr is a client-side protocol, so there is no application backend here — the browser talks directly to relays over WebSocket. Run a relay from the companion nosterm-relay repository if you want your own home relay.

This is the public, contribution-focused repository for the Nosterm client. Open a pull request against main — see CONTRIBUTING.md. Each merge to main builds the container image and publishes it to ECR Public for anonymous pulls (see Deploy).

Mission

Nosterm aims to be the premier, full-featured Nostr Relay Chat (NRC) client — the terminal-native way to talk over Nostr. We believe private communication is a right, not a feature, so Nosterm is built to advocate for and promote anonymity, end-to-end encryption, and censorship resistance by default rather than as opt-in extras.

Three principles guide every decision:

  • Small footprint. A static SPA with no application backend, served from a single lightweight container. It should stay auditable, fast to load, and cheap to self-host — no build-time secrets, no telemetry, no lock-in.
  • 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 client should make the private path the easy path.

Roadmap

The roadmap turns the mission into concrete direction. Items are aspirational and unordered — contributions that move any of them forward are welcome.

  • Full NRC feature set — channels, threads, DMs, presence, and moderation tools that make Nosterm a complete relay-chat experience, not just a demo.
  • 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 — modern encrypted DMs and private groups (e.g. NIP-17 / gift-wrapped events), with sensible key handling and no plaintext fallbacks that surprise the user.
  • Anonymity by design — minimize metadata leakage, support ephemeral and disposable identities, and avoid any client behavior that fingerprints or phones home.
  • Censorship resistance — graceful multi-relay failover, easy relay discovery and rotation, and resilience when individual relays are blocked or disappear.
  • Mature the Nostermd companion relay — grow the nostermd-relay home relay into a first-class NIP-29 (relay-based groups) implementation, deepening its federation and group-chat features so a self-hosted relay is a complete NRC backend for the client.
  • Stay small — hold the line on bundle size, dependency count, and container footprint as features grow.

Layout

nosterm-client/
  src/                  # SvelteKit source (components, stores, nostr, commands…)
  static/               # static assets
  Dockerfile            # build the SPA → serve with Caddy
  Caddyfile             # static file server + security headers + optional /relay proxy
  docker-compose.yml    # one-command deploy on port 8080
  .env.example          # copy to .env and configure
  package.json          # dev/build/test scripts

Develop

npm install
npm run dev            # vite dev server
npm run check          # svelte-check (type-check)
npm run lint           # prettier --check + eslint
npm test               # vitest
npm run test:e2e       # playwright
npm run build          # static build → ./build

Deploy

cp .env.example .env          # then set NOSTERM_DEFAULT_RELAYS at minimum
docker compose up -d --build

The client is then served at http://localhost:8080.

To run the prebuilt public image with your own relays — no rebuild, no AWS account (anonymous pull from ECR Public):

docker run -p 8080:80 \
  -e NOSTERM_DEFAULT_RELAYS=wss://relay.example.com \
  public.ecr.aws/k3k1z1x5/nosterm:latest

NOSTERM_DEFAULT_RELAYS accepts a comma-separated list. On startup the container's entrypoint writes it to /config.json, which the SPA fetches on load — so one shared image serves every deployment with different relays.

Configuration

Relays are configured at runtime (NOSTERM_DEFAULT_RELAYS), so a single prebuilt image works for any deployment. The remaining PUBLIC_* tunables are build-time args baked into the browser bundle (client-visible, never secrets) — change one → rebuild the image.

Runtime (set on the running container, no rebuild):

Variable Default Purpose
NOSTERM_DEFAULT_RELAYS empty Comma-separated relay WebSocket URLs to preload
RELAY_UPSTREAM relay:3334 Upstream for Caddy's optional /relay reverse-proxy (below)

Build-time (baked into the bundle; rebuild to change):

Variable Default Purpose
PUBLIC_DEFAULT_RELAYS empty Fallback relays when no runtime config is set
PUBLIC_DEFAULT_THEME nord Theme applied before the user picks one
PUBLIC_DEFAULT_CHANNEL nosterm Channel auto-joined on startup (blank = disabled)
PUBLIC_MAX_TERMINAL_ENTRIES 1000 Terminal entries kept in memory / rendered
PUBLIC_MAX_MESSAGE_LENGTH 2000 Max outbound message length (characters)
PUBLIC_MAX_CACHED_MESSAGES_PER_ROOM 500 Messages retained per room in IndexedDB

Talking to a relay

The client connects directly to whatever relays are configured in NOSTERM_DEFAULT_RELAYS (and any the user adds at runtime). Two common setups:

  • External relay — set NOSTERM_DEFAULT_RELAYS=wss://relay.example.com. You can delete the handle /relay* block from the Caddyfile.
  • Same-origin home relay — run nosterm-relay, set NOSTERM_DEFAULT_RELAYS=ws://localhost:8080/relay (or wss://… behind TLS), and set RELAY_UPSTREAM to the relay's address so Caddy proxies /relay to it. Put both services on a shared Docker network for name resolution.

Production TLS

For a real domain, edit the Caddyfile: replace :80 with your hostname (e.g. nosterm.example.com { … }) and Caddy will obtain and renew a certificate automatically. Expose ports 80 and 443 in docker-compose.yml.