# deploy/Caddyfile — the edge config for rutster-edge (T2) + rutster-allinone (T1). # Same file, same build (spec §3.2 + ADR-0011). Read by Caddy v2.8 (the # custom xcaddy build from deploy/Dockerfile). # # Env vars (Caddyfile interpolation syntax {$VAR}): # RUTSTER_DOMAIN — the public hostname (e.g. pbx.example.com). Required. # RUTSTER_ACME_EMAIL — the Let's Encrypt account email. Required for ACME. # RUTSTER_LOCAL_CERTS — set to "true" in CI to use Caddy's internal CA # (no ACME round-trips, no rate-limit budget burns). # Overrides the ACME block below via the `local_certs` # global option. # # Timeouts (TLS brief §3(a); spec §2.1, §2.2, §3.1 invariant 3): # * read_body 30s — Twilio webhooks fit comfortably in 30s (15s cap # by invariant 6). # * read_header 30s — same. # * write 0 — disable; 20ms media frames must flush immediately. # * idle 24h — the universal 60s-class default kills quiet WS; # spec §2.1 invariant 3 sets max call duration 24h. # * stream_close_delay 24h — above max call duration; the load-bearing # mitigation for caddy #6420/#7222 (TLS brief §5 # risk 1; spec §9 reload-during-call smoke). # # X-Forwarded-* honesty (spec §3.1 invariant 5; plan-A Task 5 # reconstruct_public_url): Caddy sets these to the real client values via # the `header_up` directives below. Engine-side RUTSTER_TRUSTED_PROXIES is # the trust gate (fail-closed default). { # CI override: set RUTSTER_LOCAL_CERTS=true to use Caddy's internal CA # (no ACME in CI; spec §9). Production leaves this unset. {$RUTSTER_LOCAL_CERTS:local_certs_off} local_certs # Send logs to stdout (s6-overlay / docker logs collects from there). log { output stdout format console } } # Default ACME email — overridden by RUTSTER_LOCAL_CERTS=true in CI. { email {$RUTSTER_ACME_EMAIL:you@example.com} } # Main site block — the public hostname. {$RUTSTER_DOMAIN:localhost} { encode zstd gzip # The reverse_proxy upstream. Caddy env-substituted form # {$RUTSTER_UPSTREAM:127.0.0.1:8080} — preserves the T1 loopback # default (single container) AND lets T2 compose override via the # engine service's `RUTSTER_UPSTREAM: engine:8080` env entry. (Pre-T6 # the upstream was a hardcoded 127.0.0.1:8080 literal — fine in T1, # wrong in T2 where Caddy reaches the engine on the compose network # via the `engine` DNS name, not loopback.) Same Caddyfile, two netns # realities. # # `header_up Host {host}` + X-Forwarded-* — sets the honest hop values # (the engine's reconstruct_public_url reads these; spec §3.1 invariant 5). # `header_up X-Forwarded-For {remote_host}` — included for completeness; # the engine does NOT use X-Forwarded-For for signature validation, only # X-Forwarded-Proto/Host. Forwarding the For header is standard proxy # hygiene — engine-side RUTSTER_TRUSTED_PROXIES is the gate. reverse_proxy {$RUTSTER_UPSTREAM:127.0.0.1:8080} { header_up Host {host} header_up X-Real-IP {remote_host} header_up X-Forwarded-Proto {scheme} header_up X-Forwarded-Host {host} header_up X-Forwarded-For {remote_host} # Tuned timeouts — the load-bearing ones for calls lasting hours. transport http { read_buffer 64KiB write_buffer 64KiB dial_timeout 5s response_header_timeout 30s } # stream_close_delay above max call duration (24h) — the caddy # #6420/#7222 mitigation (TLS brief §5 risk 1; spec §9 smoke). # An operator Caddyfile reload mid-call would normally kill # upgraded WS tunnels; the delay keeps them alive until the # streams close naturally (the call ends) — verified by the # reload-during-live-call CI smoke (Task 9). stream_close_delay 24h } # Timeouts for the public-facing side. servers { read_body 30s read_header 30s write 0 # never write-timeout; 20ms frames self-flush idle 24h # above max call duration — the universal 60s-class default kills quiet WS } }