When TETHER_RENDEZVOUS_TOKEN is set, register/peers require `Authorization: Bearer <token>` (healthz stays open); empty token = open, so the first cross-CGNAT test needs no token but production can gate. IrohTransport sends the token from the same env var, so agent/CLI clients pick it up automatically (iOS needs a token field — small Swift follow-up). Verified: 401 without/with wrong token, 200 with the right one, healthz open; and the full engine flow (text + 200KB photo) still syncs end-to-end through a token-gated rendezvous. Default ship build untouched. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2.7 KiB
tether-rendezvous
The one piece of server tether still needs once it's on iroh: a bootstrap rendezvous. To join the gossip topic a device needs to reach ≥1 peer already in it; this hands it one. After that, everything is peer-to-peer.
It is deliberately tiny and content-blind:
- maps
room → {EndpointId: last_seen}in memory, TTL 90s roomis alreadysha256(account)[..4], so the account is never exposed- it only ever sees iroh public keys + timestamps — never clipboard content (that's E2E between peers)
This is "supernode #1, the boring half." The other half is a self-hosted
iroh-relay (for holepunch-assist / relay fallback). Neither replaces the old
Go server's role in content — there is none anymore.
API
POST /rendezvous/register {"room":"<hex>","id":"<endpoint-id>"}
→ {"peers":["<other-id>", ...]} # current members minus the caller
GET /rendezvous/peers?room=<hex> → {"peers":[...]}
GET /healthz → "ok"
Clients register on a short interval (well under the 90s TTL) so the member list reflects who's actually online.
Run
cargo run --release # binds 0.0.0.0:8765
PORT=9000 cargo run --release
Deploy (homelab)
It needs to be publicly reachable for cross-network (cellular/CGNAT) devices to bootstrap — same requirement as the iroh-relay. On the homelab:
-
Run it on a CT/VM (systemd unit below).
-
Front it with Caddy for TLS, e.g.
rendezvous.pecord.io:rendezvous.pecord.io { reverse_proxy 127.0.0.1:8765 }(Plain HTTP reverse-proxy is fine — unlike the iroh-relay, there's no UDP/QUIC path here, just small JSON over HTTPS.)
-
Point the tether clients' server field at
https://rendezvous.pecord.io.
systemd unit
[Unit]
Description=tether rendezvous
After=network-online.target
[Service]
ExecStart=/opt/tether/tether-rendezvous
Environment=PORT=8765
Restart=always
RestartSec=2
[Install]
WantedBy=multi-user.target
Auth
Set TETHER_RENDEZVOUS_TOKEN to require Authorization: Bearer <token> on
register/peers (healthz stays open). Clients send it via the same env var
(TETHER_RENDEZVOUS_TOKEN), so the agent/CLI pick it up automatically; the iOS
app needs a field for it (small Swift follow-up). Empty token = open — fine
for a LAN/test run, but set one before exposing it publicly. Without a token,
anyone who knows a room (a hash) can register/enumerate — metadata only, content
stays E2E, but still worth gating.
Remaining hardening (follow-up): per-IP rate-limiting (the MAX_PER_ROOM cap is
only a flood blunt); behind Caddy use X-Forwarded-For for the real client IP.