Backroom Relay

backroom / relay / docs / operations

Operations

One static Go binary, configured entirely by environment variables, deployed by git push behind a test gate with automatic rollback. This page is generated from the same repository as the code and is checked against it by the test suite.

Configuration

All configuration is environment variables, twelve-factor style, because the relay runs as a managed daemon. There are no config files. Every knob, with its default:

VariableDefaultMeaning
RELAY_LISTEN_ADDR127.0.0.1:8787Bind address. In production this is localhost and nginx fronts it.
RELAY_MAX_MESSAGE_BYTES1048576Max size of a single frame (1 MiB). A public endpoint without this is a trivial memory-exhaustion target.
RELAY_MAX_ROOMS4096Global cap on concurrent rooms, so a flood of distinct room ids cannot grow the registry without bound.
RELAY_SEND_BUFFER32Frames that may queue for a peer before the pair is torn down for backpressure. Small on purpose: the relay is a pipe, not a mailbox.
RELAY_CONN_RATE_PER_IP1Sustained new-connection rate per IP, per second.
RELAY_CONN_BURST20Connection burst allowance per IP.
RELAY_PING_INTERVAL30sHow often an idle peer is pinged. Proves the peer is alive.
RELAY_KEEPALIVE_INTERVAL45sHow often a peer is sent a keepalive data frame. Keeps the network path open, which pings do not: Cloudflare and cellular NAT ignore control frames and drop an idle tunnel at about 100 seconds. 0 disables it.
RELAY_WRITE_TIMEOUT10sPer-frame write deadline. A peer slower than this is treated as dead.
RELAY_PING_TIMEOUT10sDeadline for a ping round trip.
RELAY_ROOM_ID_MIN_LEN16Minimum room id length: the entropy floor.
RELAY_ROOM_ID_MAX_LEN256Maximum room id length.
RELAY_TRUST_PROXY_HEADER(empty)If set (for example X-Real-IP), the header a trusted proxy uses for the real client IP. Required for meaningful per-IP limits behind nginx. Leave empty on a raw public bind, or clients could spoof it.

Running locally

go build -o backroom-relay .
RELAY_LISTEN_ADDR=127.0.0.1:8787 ./backroom-relay

For local testing over wss:// there is a dev-only flag that mints a throwaway self-signed certificate in memory, never written to disk and never used in production:

./backroom-relay --tls

The tests start the real server and exchange bytes over real WebSocket connections, rather than asserting against mocks:

go test -race ./...

The deploy pipeline

Deploying is git push. The server builds from its own checkout; no binary is committed or uploaded by hand, and the deploy logic lives in the repository where it is reviewable, not in a web text box.

git push origin main
      │
      ▼  Forge pulls, runs deploy/forge-deploy.sh
go test ./...        red tests abort here, live binary untouched
      │
      ▼
go build → backroom-relay.new → atomic rename over backroom-relay
      │
      ▼
restart daemon → poll /healthz → compare X-Relay-Rev to the pushed commit
      │                                   │
      ▼ healthy                           ▼ not healthy
 drop .prev, done                    restore .prev, restart, fail loudly

X-Relay-Rev on /healthz names the commit the running binary was built from, so "did my deploy actually take?" is one request:

curl -sI https://backroom.jck.xyz/healthz | grep -i x-relay-rev
Documentation gate

The test suite includes a documentation check: every RELAY_* variable read by the code must appear on this page and in the README, or the tests fail. Since deploys run the tests before building, a configuration change cannot reach production with stale documentation.

The nginx essentials

The binary speaks plain WS on localhost; nginx terminates TLS at the edge. Three things bite everyone who fronts a WebSocket with nginx, and all three are handled in the repository's config:

Self-hosting checklist

The relay is a generic pipe, so running your own is deliberately boring:

  1. Build the static binary: CGO_ENABLED=0 go build -ldflags="-s -w" -o backroom-relay .
  2. Run it as a daemon bound to localhost, environment variables as above. A systemd unit ships in deploy/.
  3. Front it with nginx terminating TLS, with the WebSocket upgrade headers, the long timeouts, and (if behind a CDN) the real-IP block.
  4. Set RELAY_TRUST_PROXY_HEADER=X-Real-IP so per-IP limiting sees real clients.
  5. Point an uptime check at /healthz.

There is nothing else: no database to provision, no secrets to distribute, no state to back up. Restarting the relay drops connections and the endpoints reconnect.