server · Go · self-hosted

A meeting point you can own.

Relay does one thing: it holds the desktop's outbound connection, matches it to the client's, and moves bytes between them. Pair a browser and those bytes are ciphertext it has no key for; serve the OpenAI-compatible endpoint and it reads them to speak HTTP. Either way, it is one Go binary you can run yourself.

Single binary or container · AGPL-3.0 · ours is live at relay.41tunnels.com

# one container, both paths
docker run -d -p 8080:8080 \
-e RELAY_ALLOWED_ORIGINS=https://your.app \
ghcr.io/41tunnels/relay:latest
# then, in Amallo → Settings → Relay:
# wss://relay.example.com

what Relay does, and doesn't

Splices two connections

Desktop dials in and waits; a client arrives with a matching pair ID; Relay joins the two sockets and gets out of the way.

Blind on the paired path

Pair a browser and the session key never touches Relay: it moves AES-256-GCM frames and knows nothing about their contents.

Keeps no history

No prompts, no completions, no transcripts. Connection metadata only, and only while the connection is open.

Rotatable pairing secret

The pairing code is the credential, and it lasts until you replace it. Regenerate it in Amallo and every client holding the old one is locked out. Handshakes get five seconds to complete, and new connections are rate-limited per IP.

Sighted on the OpenAI-compatible endpoint

Serving plain OpenAI HTTP means Relay can read that traffic: a third-party client has no shared secret to encrypt with. It is on by default, because it is what most people install Amallo for, so it is named as a tradeoff rather than buried. Turn it off with RELAY_HTTP_ENABLED, or host the box yourself.

A normative spec, with vectors

The wire format is written down in spec/PROTOCOL.md and both clients implement it independently against shared test vectors. Where code and spec disagree, the spec wins.

run it yourself

Container

Every release publishes a multi-arch image to the GitHub Container Registry, tagged with the version and :latest, for linux/amd64 and linux/arm64.

ghcr.io/41tunnels/relay:latest

Image and tags →

From source

One Go module, no CGO, no external dependencies at runtime. Defaults are usable out of the box; override with RELAY_* environment variables for anything internet-facing.

go run ./cmd/relay

Source and deploy notes →

In front of it

Relay expects a reverse proxy that terminates TLS and passes WebSocket upgrades through untouched. The repo ships a Caddyfile.example that also notes the two things nginx and Traefik need to get right.

GET /stats is a deliberately aggregate-only status page: pairs, agents, clients, uptime, and nothing else. Ours is public →

the settings worth knowing

RELAY_ADDR:8080

Public listener.

RELAY_ALLOWED_ORIGINS*

WebSocket origin allowlist. Open by default: the pairing secret is what gates a connection, not the browser origin.

RELAY_HTTP_ENABLEDtrue

Serve the OpenAI-compatible HTTP endpoint. The one setting that changes what Relay can see.

RELAY_MAX_PAIRS10000

Capacity cap on new pair creation.

RELAY_METRICS_ADDR:9091

Prometheus listener, bind to loopback only.

RELAY_TRUST_PROXYtrue

Trust X-Forwarded-For from the reverse proxy in front.

Config is validated at startup; a malformed value fails the boot rather than silently falling back. Full list →

Use ours, or run it on a box you own.

source, deploy notes and flags on GitHub

Run your own Relay