diff --git a/deploy/Caddyfile b/deploy/Caddyfile new file mode 100644 index 0000000..6bd349f --- /dev/null +++ b/deploy/Caddyfile @@ -0,0 +1,98 @@ +# 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 to the FOB. 127.0.0.1:8080 is correct in T1 + # (single container, loopback) AND T2 (compose: the engine service's + # `rutster-engine` DNS name resolves on the compose network; Caddy + # reaches it via that name — replace 127.0.0.1:8080 with + # http://engine:8080 in advance; here we use the loopback literal so + # the same Caddyfile works in both T1 and T2 spelled identically — + # for T2 deployments, the operator overrides the upstream via a env- + # substituted site-address block; the shipped default targets loopback + # for the bundled all-in-one single-container case which is the harder + # constraint). + # + # `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 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 + } +}