From 0b8794bc5cddeebe950ed026e5b6ea3c9ed61f72 Mon Sep 17 00:00:00 2001 From: Raphael Avocegamou Date: Thu, 3 Sep 2026 13:34:27 +0200 Subject: [PATCH] docs: rebuild the relay guide --- README.md | 315 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 199 insertions(+), 116 deletions(-) diff --git a/README.md b/README.md index 82302e2..ede9a03 100644 --- a/README.md +++ b/README.md @@ -1,64 +1,105 @@ # PocketStation Relay -PocketStation Relay carries source-aware audio over WebRTC. It forwards -independent application, microphone, caller, and generated-audio buses to -native or browser receivers without taking ownership of capture, recording, -models, or durable Session state. +Stream independently named application, microphone, caller, or generated-audio +buses to native and browser receivers over WebRTC. ```text -authenticated source attachment - ↓ - one RelaySession - ↓ - named AudioBus + generation - ↓ - BusSubscription fan-out with per-subscriber queue limits - ↓ - RTP continuity, pacing, repair, and observations +authenticated publisher + │ + ├─ application AudioBus ── selected receivers + ├─ microphone AudioBus ─── selected receivers + └─ assistant AudioBus ──── selected receivers ``` -An `AudioBus` keeps a stable semantic identity while a transient publisher -attachment, SSRC, and source generation may change. A subscriber selects one -bus or the declared `mix` output. +Relay forwards live media. It does not capture desktop audio, run models, write +recordings, or store durable application state. PocketStation Core captures and +routes audio. The Control Plane creates RelaySessions and receiver invitations +when a deployment uses control-plane mode. -## Choose who issues credentials +An `AudioBus` is a name such as `application`, `microphone`, or `assistant`. +The name stays stable when a publisher reconnects; `source_generation` +identifies the new attachment. A receiver is authorized for one bus or for a +declared `mix` output. -A self-hosted deployment normally lets the control plane issue credentials: +## Start Relay locally -```text -control plane creates the Session and capabilities -Relay validates those capabilities and owns live attachments -Relay sends authenticated, revisioned full-state snapshots back +You need Go 1.26 or newer and UDP access on the host. + +Generate development credentials and start standalone mode: + +```bash +export POCKETSTATION_JWT_SECRET="$(openssl rand -hex 32)" +export RELAY_INVITATION_SECRET="$(openssl rand -hex 32)" +export RELAY_AUTHORITY_MODE="standalone" +go run ./cmd/relay-server ``` -Set: +Relay listens on `http://127.0.0.1:4800` by default. In another terminal: -```text -RELAY_AUTHORITY_MODE=control-plane -RELAY_API_SERVER_URL=https://control.example.com -POCKETSTATION_JWT_SECRET= -POCKETSTATION_INTERNAL_SECRET= +```bash +curl --fail http://127.0.0.1:4800/healthz +``` + +The response is `ok`. This confirms that the HTTP server is accepting work; it +does not confirm ICE connectivity or media delivery. + +Publish three seconds of synthetic Opus with the repository test source: + +```bash +go run ./cmd/relay-test-source -- \ + --relay http://127.0.0.1:4800 \ + --duration 3s +``` + +The command creates a temporary RelaySession and prints the session, bus, and +credentials. The fixture checks transport behavior. It is not desktop capture +or physical-device evidence. + +Stop Relay with `Ctrl-C`. The default process shutdown deadline is 30 seconds; +HTTP work receives a five-second drain interval inside that deadline. + +Continue with the [local setup guide](docs/getting-started/run-relay.md) when a +receiver or Control Plane will join the test. + +## Choose how RelaySessions are created + +Relay supports two explicit operating modes. + +### Control-plane mode + +Use this mode when an application service owns RelaySession creation, +required-bus readiness, receiver invitations, and scoped credentials. + +```bash +export RELAY_AUTHORITY_MODE="control-plane" +export RELAY_API_SERVER_URL="https://control.example.com" +export POCKETSTATION_JWT_SECRET="" +export POCKETSTATION_INTERNAL_SECRET="" +go run ./cmd/relay-server ``` -Deploy the control plane and Relay under endpoints you own. PocketStation's -Fly endpoints are a small, rate-limited demonstration environment used by the -installed Python example. They are not a hosted service, an SLA, or a -default for Relay itself, and may return `429 Too Many Requests` when the demo -capacity is in use. +The Control Plane signs source and receiver capabilities. Relay verifies their +issuer, audience, token type, role, expiry, RelaySession ID, and AudioBus scope. +Relay sends complete attachment-state snapshots back to the Control Plane after +changes and at the configured reconciliation interval. -In this mode Relay rejects its local Session and invitation mutation routes. -Subscriber capabilities are signed by the control plane and validated by Relay -with the same strict issuer, audience, token-type, role, and bus-scope profile. +Relay rejects local RelaySession and invitation creation endpoints in this +mode. -`RELAY_AUTHORITY_MODE=standalone` is an explicit self-hosted mode. Relay then -creates its own Sessions and single-use invitations. It uses the independent -`RELAY_INVITATION_SECRET` for subscriber capabilities. Credentials issued in -one mode are not accepted in the other. +### Standalone mode + +Use standalone mode for a self-contained deployment or protocol development. +Relay creates temporary RelaySessions and single-use invitations itself. It +uses `RELAY_INVITATION_SECRET` to sign receiver capabilities. + +Credentials from one mode are not accepted in the other. Choose one mode for a +deployment; do not configure fallback verification with the other issuer or +secret. ## Publish named buses -A source capability lists every bus the publisher may attach. One signaling -connection can declare multiple independent tracks: +A source capability lists the buses a publisher may attach. One signaling +connection can publish several WebRTC tracks: ```json { @@ -72,29 +113,37 @@ connection can declare multiple independent tracks: } ``` -Every declared bus must be inside the token scope. Stream IDs and bus IDs must -be unique. Relay rejects ambiguous, oversized, malformed, or out-of-scope -declarations before media attachment. +Every bus must be included in the capability. Stream IDs and bus IDs must be +unique. Relay rejects missing credentials, ambiguous declarations, oversized +messages, malformed SDP, and buses outside the token scope before attaching +media. -For one track, send an explicit `bus_id` instead. +Rust applications can use the +[`pocketstation-relay` Connector](https://github.com/pocketstation-io/connectors/tree/main/relay) +instead of implementing signaling, Opus, RTP, and WebRTC publication. -## Receive a bus +## Receive one selected bus -A subscriber capability contains exactly one `bus_id`. Use it with WebSocket -signaling or WHEP: +A receiver capability identifies exactly one RelaySession and AudioBus. Use it +with WebSocket signaling or WHEP: ```http POST /v1/sessions/{session_id}/whep?bus=application -Authorization: Bearer +Authorization: Bearer Content-Type: application/sdp ``` -The URL bus cannot exceed the token scope. `mix` is a declared virtual output; -it is not an unrestricted wildcard. +The bus in the URL must match the capability. `mix` is an explicitly declared +output, not permission to subscribe to every source. -## Control-state reconciliation +For all HTTP endpoints and authentication requirements, read +[HTTP and WebRTC](docs/reference/http-and-webrtc.md). For every signaling +message, field limit, and error code, read +[WebSocket signaling](docs/reference/signaling.md). -Relay sends one complete state document for every accepted attachment change: +## Keep the Control Plane synchronized + +Relay sends one complete state document after an accepted attachment change: ```json { @@ -112,79 +161,112 @@ Relay sends one complete state document for every accepted attachment change: } ``` -The callback is authenticated, has a maximum payload size, and finishes within -an HTTP deadline. A full snapshot replaces all Relay-owned state, so duplicate -delivery is safe and callback loss is repaired -by periodic reconciliation. Reconciliation resends the current revision; it -does not manufacture a new transition. +A later complete snapshot replaces earlier Relay-owned state. Duplicate or +reordered revisions are safe to acknowledge without replaying mutations. A new +`relay_epoch` identifies a Relay process restart. -State-change notification uses an atomic, nonblocking handoff. It does not add -a lock, allocation, network call, or log operation to RTP forwarding. +State-change notification enters a fixed-capacity mailbox without a lock, +allocation, network request, or log call in RTP forwarding. If a notification +is missed, periodic reconciliation sends the latest complete state again. -## Run locally +## Configure capacity before accepting traffic -Control-plane mode: +The server defaults are development values, not sizing recommendations: -```bash -POCKETSTATION_JWT_SECRET=development-source-secret-32-bytes \ -POCKETSTATION_INTERNAL_SECRET=development-internal-secret-32-bytes \ -RELAY_AUTHORITY_MODE=control-plane \ -RELAY_API_SERVER_URL=http://127.0.0.1:4801 \ -go run ./cmd/relay-server -``` +| Resource | Environment variable | Default | +|---|---|---:| +| active RelaySessions | `RELAY_MAX_ROOMS` | 100 | +| subscribers per RelaySession | `RELAY_MAX_SUBSCRIBERS_PER_SESSION` | 50 | +| AudioBuses per RelaySession | `RELAY_MAX_BUSES_PER_SESSION` | 16 | +| concurrent signaling and WHIP/WHEP handshakes | `RELAY_MAX_CONCURRENT_HANDSHAKES` | 128 | +| standalone RelaySession creation per client IP per minute | `MAX_ROOMS_PER_IP_PER_MINUTE` | 10 | +| inactive RelaySession lifetime | `ROOM_EXPIRY_MINUTES` | 30 minutes | +| publisher reconnect window | `SOURCE_RECONNECT_WINDOW_SEC` | 60 seconds | -Standalone mode: +Each receiver pacer holds 32 RTP packets and discards packets older than 120 +milliseconds. Relay counts queue-full and age-related drops. When an admission +limit is reached, it rejects new work with an HTTP or signaling error instead +of growing resource use without a limit. -```bash -POCKETSTATION_JWT_SECRET=development-source-secret-32-bytes \ -RELAY_INVITATION_SECRET=development-receiver-secret-32-bytes \ -RELAY_AUTHORITY_MODE=standalone \ -go run ./cmd/relay-server -``` +Set these values from the expected audience, host memory, CPU, bandwidth, and +TURN allocation budget. Read +[capacity and recovery](docs/operations/capacity-and-recovery.md) for metrics, +reconnect behavior, shutdown, and the measurements required for a network +claim. -The deterministic `relay-test-source` fixture can publish one named test bus: +## Secure a public deployment -```bash -go run ./cmd/relay-test-source -- \ - --relay http://127.0.0.1:4800 \ - --session \ - --bus application \ - --token \ - --duration 3s +- Store JWT, internal synchronization, TURN, and webhook secrets in the + deployment secret manager. +- Serve public HTTP and signaling endpoints over TLS. +- Set `ALLOWED_ORIGINS` to the exact HTTPS origins hosting browser receivers. +- Restrict `/metrics` and RelaySession diagnostic endpoints with a private + network, proxy, or ingress policy; Relay does not add administrator auth to + them. +- Treat single-use invitation codes as credentials until redeemed or expired. +- Plan coordinated secret rotation around the remaining capability lifetime. + +WebRTC uses DTLS-SRTP. Optional SFrame messages provide separate end-to-end +media encryption only when both clients implement the same key exchange. + +Read [security](docs/operations/security.md) before exposing Relay publicly. + +## Configure ICE and TURN + +Set `ICE_UDP_PORT` and expose that UDP port for a public Relay. Use +`RELAY_PUBLIC_IPS` when the server must advertise a public address. Optional +ICE-TCP uses `ICE_TCP_PORT`. + +Set `TURN_PUBLIC_IP` and a 32-byte-or-longer `TURN_SHARED_SECRET` to enable the +embedded TURN server. Expose the selected TURN listeners and the complete UDP +allocation port range. Authentication can succeed while media still fails if +firewalls block allocation ports. + +The complete environment-variable reference is in +[Relay configuration](docs/reference/configuration.md). + +## Observe a RelaySession + +`GET /metrics` exposes Prometheus metrics for active RelaySessions, +subscribers, forwarded and dropped RTP packets, handshakes, ICE restarts, +callbacks, webhooks, and key exchange. + +Inspect one affected RelaySession with: + +```text +GET /v1/sessions/{id}/health +GET /v1/sessions/{id}/latency +GET /v1/sessions/{id}/media-debug +GET /v1/sessions/{id}/packet-log?bus={bus_id}&limit=100 ``` -It emits valid synthetic Opus for transport verification. It is not physical -capture evidence. +The packet-log endpoint returns at most 1,000 records. Restrict these endpoints +before public deployment. -For a complete local setup, public-network requirements, configuration, and -operating guidance, start with the [Relay documentation](docs/README.md). +These observations describe Relay and WebRTC behavior. They do not prove the +exact sample a loudspeaker played or what a person heard. -## Set capacity limits +## Published limits -Relay bounds: +Repository and same-host tests establish component and local integration +behavior. They do not establish every NAT topology, cross-network latency, +multi-region behavior, or production load. -- RelaySessions and AudioBuses per Session; -- subscriptions per Session; -- concurrent signaling and WHIP/WHEP handshakes; -- pending invitations in standalone mode; -- control-state notifications; -- callback duration and response size; -- packet queues, repair caches, and packet age. +PocketStation's Fly deployment is a small, rate-limited environment used by +the installed Python example. It is not Relay's default configuration, a +hosted product, or an SLA. Operate Relay and the Control Plane under endpoints +and limits you control for an application deployment. -When capacity is unavailable, Relay rejects new work before allocating media -resources. It returns an explicit capacity response instead of allowing retry -or callback queues to grow without a limit. Operators should set limits for -their own budget and expected audience; the repository's `fly.toml` -intentionally describes only a small demonstration deployment. +## Continue from the task you have -The checked-in Fly configuration keeps one 512 MB Relay machine running and -lets the 256 MB Control Plane stop when idle. At current `iad` shared-CPU -pricing, keeping both machines running for an entire month would exceed USD 5 -before network costs. The low-traffic demonstration can remain below that -target only when the Control Plane is stopped for enough idle time. Network -transfer, public IPs, the browser receiver, taxes, and future provider pricing -are separate. Fly does not currently provide a built-in billing alert, so this -configuration is not a hard billing ceiling. +| Task | Guide | +|---|---| +| Start a local Relay | [Run Relay locally](docs/getting-started/run-relay.md) | +| Configure every environment variable | [Configuration](docs/reference/configuration.md) | +| Secure credentials and browser access | [Security](docs/operations/security.md) | +| Set capacity and recover from failures | [Capacity and recovery](docs/operations/capacity-and-recovery.md) | +| Implement a client | [WebSocket signaling](docs/reference/signaling.md) | +| Inspect HTTP, WHIP, WHEP, and diagnostics | [HTTP and WebRTC](docs/reference/http-and-webrtc.md) | ## Verify a change @@ -194,9 +276,10 @@ go test -race -short ./... go test -race ./internal/server ./test/integration ``` -CI must pass before Fly deploys. Deployment checks out the exact successful CI -revision and records it in the OCI image. A successful local or same-host test -does not establish WAN/TURN or multi-region performance. +CI must pass before deployment. The deploy workflow checks out the successful +revision and records it in the OCI image. Local or same-host tests do not +establish WAN/TURN or multi-region performance. + +## License -See [the signaling protocol](docs/reference/signaling.md) for message -formats and failure behavior. +PocketStation Relay is available under the MIT license.