Architecture
Syrinx is a small stack with a sharp division of responsibility: the server coordinates; devices hold content and keys.
Components
| Piece | Role |
|---|---|
| HTTP API (Go) | REST with protobuf bodies: identity, reeds metadata, invites, deletion certs, recovery, server info, federation |
| Realtime service (Go) | WebSockets, binary protobuf events, fanout and catch-up via Go channels |
| SPA (SvelteKit) | Primary client / PWA: local keys, IndexedDB, signing, feeds, restore |
Ops CLI (bin/ops) | Operator tooling: export/import server identity bundles, rotate the server key and its passphrase |
| Cleanup jobs | Cron binaries that expire idle ripples and stale challenges |
The API and realtime layers run in one process and talk over in-process Go channels—not a mesh of microservices. Docker Compose can run Postgres, the API, and the SPA dev server together.
The server is a tracker, not a library
Published reeds (posts) are authenticated and distributed in a P2P-ish fashion:
- The author signs the reed and registers metadata with the server (who wrote what, enough to allocate and notify).
- The server does not keep the body as authoritative content storage. Think tracker: anonymized references and routing, not a media archive.
- Holders—peers that already have the reed—relay the content when someone requests it.
- Recipients verify signatures before trusting or persisting the body.
Most users sit behind NAT, so true peer-to-peer sockets are rare. The server relays in transit, and the holder encrypts each body to the requester first, so the server only ever carries ciphertext. That is why this is “P2P-ish”: distribution semantics without trusting the server with your corpus.
Client responsibilities
The SPA is not a thin view over a database. It:
- Holds the user’s OpenPGP key material locally
- Signs requests and content
- Stores reeds and peer data it chose to keep (see Content)
- Verifies user and server signatures before applying sensitive updates
- Drives restore and recovery flows when the server must be rebuilt
- Precaches the app shell (HTML/JS/CSS) in a service worker so the UI loads offline; reed bodies stay in IndexedDB; API and WebSocket still need the network for live sync (see Philosophy — Offline-first)
Realtime path
WebSocket sessions authenticate with PGP: a detached signature over the server, user, a timestamp and a single-use nonce, so a captured handshake can't be replayed. Clients subscribe to user-specific and broadcast channels. Events cover new reeds, relay requests/responses, removals, account gone notices, sync catch-up, and related bookkeeping. Missed deliveries are reconciled on reconnect via sync—not by trusting the server’s word alone when certificates are involved.
Federation
Servers peer explicitly: one admin sends an encrypted invite, the other server calls back, and an admin approves the connection before any trust is extended. Once connected, members follow, reply to and fetch reeds across the border. Each server folds what it sends: one delivery per piece of content per peer, which the peer then distributes to its own members. See Relay model — Crossing a server border.
A server's identity is its signing key, not its domain. After a move to a new domain, peering has to be re-established.
Where to go next
- Trust model — who is trusted to attest what
- Cryptography — how payloads are signed
- Content distribution — feeds, relay, local storage rules
- Operators — running the stack