Closes FUNCTIONAL_AUDIT.md §4 #1: WebRTC 1:1 calls had working
signaling but no NAT traversal, so calls between two peers behind
symmetric NAT (corporate firewalls, mobile carrier CGNAT, Incus
container default networking) failed silently after the SDP exchange.
Backend:
- GET /api/v1/config/webrtc (public) returns {iceServers: [...]}
built from WEBRTC_STUN_URLS / WEBRTC_TURN_URLS / *_USERNAME /
*_CREDENTIAL env vars. Half-config (URLs without creds, or vice
versa) deliberately omits the TURN block — a half-configured TURN
surfaces auth errors at call time instead of falling back cleanly
to STUN-only.
- 4 handler tests cover the matrix.
Frontend:
- services/api/webrtcConfig.ts caches the config for the page
lifetime and falls back to the historical hardcoded Google STUN
if the fetch fails.
- useWebRTC fetches at mount, hands iceServers synchronously to
every RTCPeerConnection, exposes a {hasTurn, loaded} hint.
- CallButton tooltip warns up-front when TURN isn't configured
instead of letting calls time out silently.
Ops:
- infra/coturn/turnserver.conf — annotated template with the SSRF-
safe denied-peer-ip ranges, prometheus exporter, TLS for TURNS,
static lt-cred-mech (REST-secret rotation deferred to v1.1).
- infra/coturn/README.md — Incus deploy walkthrough, smoke test
via turnutils_uclient, capacity rules of thumb.
- docs/ENV_VARIABLES.md gains a 13bis. WebRTC ICE servers section.
Coturn deployment itself is a separate ops action — this commit lands
the plumbing so the deploy can light up the path with zero code
changes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The maintenance toggle lived in a package-level `bool` inside
`middleware/maintenance.go`. Flipping it via `PUT /admin/maintenance`
only updated the pod handling that request — the other N-1 pods stayed
open for traffic. In practice this meant deploys-in-progress or
incident playbooks silently failed to put the fleet into maintenance.
New storage:
* Migration `976_platform_settings.sql` adds a typed key/value table
(`value_bool` / `value_text` to avoid string parsing in the hot
path) and seeds `maintenance_mode=false`. Idempotent on re-run.
* `middleware/maintenance.go` rewritten around a `maintenanceState`
with a 10s TTL cache. `InitMaintenanceMode(db, logger)` primes the
cache at boot; `MaintenanceModeEnabled()` refreshes lazily when the
next request lands after the TTL. Startup `MAINTENANCE_MODE` env is
still honoured for fresh pods.
* `router.go` calls `InitMaintenanceMode` before applying the
`MaintenanceGin()` middleware so the first request sees DB truth.
* `PUT /api/v1/admin/maintenance` in `routes_core.go` now does an
`INSERT ... ON CONFLICT DO UPDATE` on the table *before* the
in-memory setter, so the flip survives restarts and propagates to
every pod within ~10s (one TTL window).
Tests: `TestMaintenanceGin_DBBacked` flips the DB row, waits past a
shrunk-for-test TTL, and asserts the cache picked up the change. All
four pre-existing tests preserved (`Disabled`, `Enabled_Returns503`,
`HealthExempt`, `AdminExempt`).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
INT-02: TrackService.copyFileAsync now calls StreamService.StartProcessing
after successful file copy. Wires the stream server integration into
all track route registrations.
Conditionally register pprof routes only when APP_ENV is not production.
Prevents leaking sensitive runtime information via profiling endpoints.
Phase 1 audit - P1.5
- Add early validation in Setup() returning error if Redis nil in production
- Remove panic/Fatal from routes_core.go and router.go applyCSRFProtection
- Handle Setup() error in cmd/api/main.go and cmd/modern-server/main.go
- Mark audit item 1.4 as done
- Add DATABASE_READ_URL config and InitReadReplica in database package
- Add ForRead() helper for read-only handler routing
- Update TrackService and TrackSearchService to use read replica for reads
- Document setup in DEPLOYMENT_GUIDE.md and .env.template