chahinebrini 0cac3c9d1a feat(calls): Phase 1a — TURN ice-servers endpoint + coturn ops + DM call-button header
Backend:
- GET /api/calls/ice-servers: ephemeral HMAC TURN credentials (10-min TTL),
  iceTransportPolicy:"relay" (no IP leak), 503 until coturn configured
- nitro runtimeConfig: turnHost/turnSecret/turnRealm (Infisical staging set)

Ops:
- ops/calls/ runbook + turnserver.conf (self-hosted coturn, force-relay,
  use-auth-secret, hardening). coturn provisioned + verified on rebreak-server.

Frontend (DM header redesign):
- removed standalone "i" button; header center (avatar+name+chevron) opens info sheet
- call icon top-right, only when canCall (mutual-follow + callsEnabled);
  shows "coming soon" until the WebRTC client lands

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 03:06:33 +02:00

2.6 KiB

Voice-Calls — coturn TURN-Server Runbook

Self-hosted TURN (coturn) auf Hetzner für die DM-Voice-Calls. Force-Relay → kein direkter IP-Austausch zwischen Usern (Anonymität, kein neuer Sub-Auftragsverarbeiter).

Architektur

App A ──┐                              ┌── App B
        │  WebRTC (DTLS-SRTP, E2E)     │
        ├──────────────► coturn ◄──────┤   ← relay-only, sieht nur verschlüsselten Audio-Stream
        │                              │
   Signaling (SDP/ICE) über Supabase Realtime (ephemerer Channel pro Call)
   ICE-Credentials      über GET /api/calls/ice-servers (HMAC, 10-min-TTL)
  • coturn validiert ephemere Credentials per use-auth-secret (kein DB-Lookup).
  • Das Backend mintet sie aus TURN_SECRET = coturn static-auth-secret.

Provisioning (einmalig)

⚠️ Destruktive/Infra-Schritte — nur mit User-GO ausführen. Secrets NIE committen, nur in Infisical (staging).

  1. DNS: A-Record turn.rebreak.org → Server-IP (Hetzner).
  2. Install: apt update && apt install -y coturn
  3. TLS-Cert: certbot certonly --standalone -d turn.rebreak.org (Port 80 muss kurz frei sein). Auto-Renew via certbot-Timer.
  4. Config: ops/calls/turnserver.conf/etc/turnserver.conf. static-auth-secret durch das echte Secret ersetzen (siehe Schritt 6).
  5. Firewall (Hetzner Cloud Firewall + ufw): öffnen
    • 3478/udp, 3478/tcp (STUN/TURN)
    • 5349/tcp (TURN over TLS)
    • 49160-49200/udp (Relay-Range, = min/max-port in der Config)
  6. Secret generieren + in Infisical (env=staging) setzen:
    • TURN_SECRET = openssl rand -hex 32 (gleicher Wert in turnserver.conf)
    • TURN_HOST = turn.rebreak.org
    • TURN_REALM = rebreak.org
  7. Enable: in /etc/default/coturnTURNSERVER_ENABLED=1, dann systemctl enable --now coturn.
  8. Verify:
    • ss -lunp | grep 3478 (lauscht UDP)
    • turnutils_uclient -v -t -u <user> -w <pw> turn.rebreak.org (oder Trickle-ICE-Test im Browser gegen turns:turn.rebreak.org:5349).

Backend

  • GET /api/calls/ice-servers liefert { iceServers, iceTransportPolicy:"relay", ttl }.
  • Wirft 503 calls_not_configured, solange TURN_HOST/TURN_SECRET fehlen → kein IP-leakender Fallback.
  • runtimeConfig-Keys in backend/nitro.config.ts: turnHost, turnSecret, turnRealm.

Betrieb / Kosten

  • Audio ≈ 50 kbps/Call → Bandbreite vernachlässigbar.
  • coturn kann auf der bestehenden Hetzner-Box mitlaufen (eigene Ports).
  • Log-Minimierung: coturn no-cli, kein verbose-Logging in Prod.