2026-07-26 06:56:38 +00:00
|
|
|
# /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)
|
|
|
|
|
#
|
registry: move the registry host to dev.jinemi.com, MCP to mcp.jinemi.com
Completes the Forgejo domain migration. ROOT_URL moved to dev.jinemi.com
earlier; the registry half was deliberately deferred. Every image name,
REGISTRY_HOST default, runner label and --add-host pin now names
dev.jinemi.com, so the registry host and the bearer-token realm agree again.
Both names address the same Forgejo, so no image needs re-pushing and a
rollback to a tag pushed under the old prefix still resolves.
git.thermograph.org therefore stays served off the same Caddy site block --
one block, so the /v2/* mesh-only matcher keeps covering both names -- for
pre-migration tags and for runners holding it as their registered instance
URL.
Mesh clients now pin both names in /etc/hosts: the new one as registry host
and token realm, the old one for pre-migration tags. runner-vps2/config.yaml
carries both --add-host entries for the same reason.
Also renames Centralis' endpoint to mcp.jinemi.com in the two places this
repo names it; Centralis itself is provisioned outside this repo.
Host-side steps this cannot do (documented in deploy/forgejo/README.md,
"Host-side steps"): the Forgejo Actions variable REGISTRY_HOST, docker login
against the new host, and the /etc/hosts pins.
2026-08-01 23:06:22 +00:00
|
|
|
# and Centralis at mcp.jinemi.com, which is provisioned separately.
|
2026-07-26 06:56:38 +00:00
|
|
|
#
|
|
|
|
|
# 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
|
|
|
|
|
# }
|