159 lines
7.3 KiB
Markdown
159 lines
7.3 KiB
Markdown
# Nosterm
|
|
|
|
<p align="center">
|
|
<img src="assets/nosterm-banner.svg" alt="Nosterm — a terminal-style Nostr chat application" width="640" />
|
|
</p>
|
|
|
|
**A terminal-style Nostr chat client.**
|
|
|
|
The Nosterm web client: a terminal-style Nostr chat SPA built with
|
|
[SvelteKit](https://kit.svelte.dev/) and served as static files by
|
|
[Caddy](https://caddyserver.com/). 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](https://git.nerdworks.io/nerdworks/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](CONTRIBUTING.md). Each
|
|
merge to `main` builds the container image and publishes it to ECR Public for
|
|
anonymous pulls (see [Deploy](#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](https://git.nerdworks.io/nerdworks/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
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
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):
|
|
|
|
```bash
|
|
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](https://git.nerdworks.io/nerdworks/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`.
|