Some checks failed
Sync infra to hosts / sync-beta (push) Has been skipped
Sync infra to hosts / sync-prod (push) Has been skipped
Sync infra to hosts / sync-dev (push) Failing after 6s
secrets-guard / encrypted (push) Successful in 6s
shell-lint / shellcheck (push) Successful in 13s
Validate observability stack / validate (push) Successful in 17s
PR build (required check) / changes (pull_request) Successful in 6s
secrets-guard / encrypted (pull_request) Successful in 5s
PR build (required check) / build-backend (pull_request) Has been skipped
shell-lint / shellcheck (pull_request) Successful in 6s
PR build (required check) / build-frontend (pull_request) Has been skipped
PR build (required check) / validate-observability (pull_request) Successful in 18s
PR build (required check) / gate (pull_request) Successful in 2s
145 lines
5 KiB
Text
145 lines
5 KiB
Text
# /etc/caddy/Caddyfile on **vps2** (169.58.46.181) — the public-facing box.
|
|
#
|
|
# vps2 serves BOTH environments an external user can reach:
|
|
# thermograph.org -> prod (loopback LB on 127.0.0.1:8137 / :8080)
|
|
# beta.thermograph.org -> beta (loopback LB on 127.0.0.1:8237 / :8180)
|
|
#
|
|
# and Centralis at mcp.thermograph.org, which is provisioned separately.
|
|
#
|
|
# Each domain's A/AAAA record must already point here — Caddy provisions a
|
|
# Let's Encrypt cert on first request and auto-renews. Nothing else to do for
|
|
# TLS (just make sure ports 80 and 443 are open).
|
|
#
|
|
# This file was split out of a single deploy/Caddyfile that described both
|
|
# boxes' sites at once. That was workable while each box served an unrelated
|
|
# set; it stopped being workable when the two ports 8137/8080 started meaning
|
|
# "prod on vps2" here and nothing at all on the other box. See Caddyfile.vps1
|
|
# for git/dashboard/portfolio.
|
|
#
|
|
# Thermograph owns thermograph.org's root, so both services run with
|
|
# THERMOGRAPH_BASE=/ — pages, assets and API all sit at "/" with no sub-path
|
|
# prefix. Backend and frontend are separate containers, each on its own loopback
|
|
# port; Caddy path-splits directly to whichever owns a given path, so the
|
|
# browser still sees one apparent origin. The enumerated backend list below
|
|
# mirrors backend/web/app.py's own routing exactly (a single catch-all proxy to
|
|
# frontend for everything else) -- unlike frontend's paths, backend's don't grow
|
|
# every time a new static asset filename is added. A gap in this list still just
|
|
# degrades to "one extra hop" through backend's own proxy fallback, never a 404.
|
|
|
|
thermograph.org {
|
|
encode zstd gzip
|
|
|
|
@backend_paths path /api/* /digest /discord/interactions
|
|
|
|
# Active health check on the same cheap /healthz route each container's own
|
|
# HEALTHCHECK uses (Dockerfile) — so a deploy that's still restarting/booting
|
|
# never gets proxied into (a reload alone has no gate, hop-1 runbook hazard #10).
|
|
# 15s (was 5s): plenty responsive for a process that only restarts on a deploy,
|
|
# and a quarter of the polling load.
|
|
handle @backend_paths {
|
|
reverse_proxy 127.0.0.1:8137 {
|
|
health_uri /healthz
|
|
health_interval 15s
|
|
health_timeout 3s
|
|
health_status 2xx
|
|
}
|
|
}
|
|
|
|
handle {
|
|
reverse_proxy 127.0.0.1:8080 {
|
|
health_uri /healthz
|
|
health_interval 15s
|
|
health_timeout 3s
|
|
health_status 2xx
|
|
}
|
|
}
|
|
|
|
# Access-log hygiene: the default JSON encoder serializes full request headers,
|
|
# the TLS block, and response headers on every line (measured ~1,133B/line) --
|
|
# strip those with the `filter` format encoder. Also strip the query string from
|
|
# the logged URI: Caddy's default logger records request.uri *including* the
|
|
# query string, so every `?q=<search text>` a visitor typed sat in Loki next to
|
|
# their client IP for the full 30-day retention -- a real privacy leak, not just
|
|
# noise. Bot/crawler skipping stays out of here: `log_skip` needs Caddy >= 2.7
|
|
# and an upgrade is out of scope, so that's handled downstream in Alloy's
|
|
# loki.process "caddy" stage instead (see observability/alloy/config.alloy).
|
|
#
|
|
# The FILENAME is load-bearing now: Alloy attributes each access log to an
|
|
# environment by filename (thermograph.log -> host="prod", beta.log ->
|
|
# host="beta"). One Caddy fronts two environments here, so a single log file
|
|
# would file every beta request under prod. Renaming either file means
|
|
# updating loki.process "caddy" in observability/alloy/config.alloy.
|
|
log {
|
|
output file /var/log/caddy/thermograph.log {
|
|
roll_size 20MiB
|
|
roll_keep 5
|
|
}
|
|
format filter {
|
|
wrap json
|
|
fields {
|
|
request>headers delete
|
|
request>tls delete
|
|
resp_headers delete
|
|
request>uri regexp \?.* ""
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# Beta — same shape as prod, pointed at beta's loopback LB. It moved here from
|
|
# its own box; the DNS A record for beta.thermograph.org must point at vps2.
|
|
#
|
|
# Deliberately NOT indexed: beta serves the same content as prod at a different
|
|
# hostname, which is a duplicate-content problem for search engines and an
|
|
# invitation for users to land on a rehearsal environment from a search result.
|
|
# The app itself never pings IndexNow from beta (see env-topology.sh's
|
|
# TG_POST_DEPLOY), and this header closes the other half.
|
|
beta.thermograph.org {
|
|
encode zstd gzip
|
|
|
|
header {
|
|
X-Robots-Tag "noindex, nofollow"
|
|
}
|
|
|
|
@backend_paths path /api/* /digest /discord/interactions
|
|
|
|
handle @backend_paths {
|
|
reverse_proxy 127.0.0.1:8237 {
|
|
health_uri /healthz
|
|
health_interval 15s
|
|
health_timeout 3s
|
|
health_status 2xx
|
|
}
|
|
}
|
|
|
|
handle {
|
|
reverse_proxy 127.0.0.1:8180 {
|
|
health_uri /healthz
|
|
health_interval 15s
|
|
health_timeout 3s
|
|
health_status 2xx
|
|
}
|
|
}
|
|
|
|
log {
|
|
output file /var/log/caddy/beta.log {
|
|
roll_size 20MiB
|
|
roll_keep 5
|
|
}
|
|
format filter {
|
|
wrap json
|
|
fields {
|
|
request>headers delete
|
|
request>tls delete
|
|
resp_headers delete
|
|
request>uri regexp \?.* ""
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# Optional: redirect www -> apex. Add the www CNAME/A record first, then
|
|
# uncomment.
|
|
# www.thermograph.org {
|
|
# redir https://thermograph.org{uri} permanent
|
|
# }
|