# Tx OS — Caddy reverse proxy + TLS termination. # # Defaults (HTTPS_MODE=local or byo): listen on :443 with the mounted # certificate pair from /certs, redirect HTTP→HTTPS automatically. # When HTTPS_MODE=skip, we serve plaintext on :80 only (developer-only; # the host script never enables this in production paths). # # The site MUST stay single-origin so the API session cookie # (sameSite=lax) keeps working across SPA + /api requests. { # Disable Caddy's automatic Let's Encrypt issuance — we always # bring our own cert (mkcert locally, real cert in BYO mode). auto_https disable_certs admin off } (api_proxy) { # WebSocket / Socket.IO upgrade for /api/socket.io @websocket { header Connection *Upgrade* header Upgrade websocket } reverse_proxy /api/socket.io/* api:8080 reverse_proxy /api/* api:8080 } (spa_proxy) { # Static SPA bundle is served by the `web` container (nginx-alpine) # on port 80 inside the docker network. Caddy is the public edge. reverse_proxy web:80 } # ---------- HTTPS site (default) ---------- {$LOCAL_DOMAIN:tx.local}, {$LOCAL_IP:127.0.0.1} { tls /certs/local-cert.pem /certs/local-key.pem encode zstd gzip import api_proxy import spa_proxy } # Catch-all HTTPS host (covers raw IP / Tailscale name without # touching the named site). :443 { tls /certs/local-cert.pem /certs/local-key.pem encode zstd gzip import api_proxy import spa_proxy } # Plain HTTP — issue a permanent redirect to HTTPS in normal modes, # but become the actual site when HTTPS_MODE=skip. :80 { @skip expression {$HTTPS_MODE:local} == "skip" handle @skip { encode zstd gzip import api_proxy import spa_proxy } handle { redir https://{host}{uri} permanent } }