clusev/docker/caddy/Caddyfile

76 lines
2.7 KiB
Caddyfile

# Clusev reverse proxy — the panel domain is changeable FROM THE DASHBOARD.
#
# TLS is issued ON DEMAND: on the first HTTPS handshake for a host, Caddy asks the app
# (/_caddy/ask) whether to issue a certificate, and the app approves ONLY the domain
# currently configured in the dashboard (DB) / .env. So the operator changes the domain
# in the UI, points DNS at this host, restarts the stack, and Caddy gets the cert for the
# new domain automatically — no Caddyfile edit, no SITE_ADDRESS.
#
# HTTP is ALWAYS served (bare-IP access + the operator's recovery path); the app issues
# the HTTPS redirect for the configured domain (PanelScheme), and never for the raw IP.
# Reverb (Pusher protocol) rides the same address over /app/* (WS) and /apps/* (event
# API), proxied to reverb:8080 — no extra exposed port, no mixed content.
{
# ACME contact for Let's Encrypt (set by install.sh; defaults to admin@localhost).
email {$ACME_EMAIL}
# Only mint a certificate for the host the app approves — prevents cert-issuance
# abuse from arbitrary domains pointed at this IP.
on_demand_tls {
ask http://app:80/_caddy/ask
}
# Keep serving plain HTTP (still issue certs on demand): the APP decides the
# HTTP->HTTPS redirect — only for the configured domain, never for the raw IP —
# so bare-IP HTTP always stays reachable as the operator's recovery path.
auto_https disable_redirects
}
# Snippet: the shared proxy + hardening, reused by both the HTTP and HTTPS sites.
(clusev_proxy) {
@reverb path /app/* /apps/*
reverse_proxy @reverb reverb:8080
reverse_proxy app:80
encode zstd gzip
header {
# conservative hardening; HSTS is asserted by the app only over HTTPS
X-Content-Type-Options nosniff
X-Frame-Options DENY
Referrer-Policy strict-origin-when-cross-origin
-Server
}
}
# HTTPS for any host the app approves (the configured panel domain); cert obtained
# on demand on the first handshake.
https:// {
tls {
on_demand
}
import clusev_proxy
}
# Plain HTTP: always served — bare-IP access and the recovery path. Also carries the
# ACME HTTP-01 challenge. The app redirects regular requests to HTTPS for the configured
# domain (PanelScheme); the raw IP stays on HTTP.
http:// {
# Reverb (/app WS, /apps events) bypasses the app, so enforce transport here: on a
# HOSTNAME force the WS/event paths onto HTTPS, so broadcasts (incl. the public metrics
# channel) are never carried in cleartext and a stale/wrong host is refused by on-demand
# TLS. On a bare IP there is no TLS, so plaintext there is expected and left alone.
@reverb_cleartext {
path /app/* /apps/*
not header_regexp Host ^(\d{1,3}\.){3}\d{1,3}(:\d+)?$
}
handle @reverb_cleartext {
redir https://{host}{uri}
}
handle {
import clusev_proxy
}
}