Files
automations/deployments/ergo/Caddyfile
T
57_WolveandClaude Opus 5 134736c082 fix(ergo): findings from reviewing the first live deploy
Seven confirmed findings from an observed-vs-expected review of a real deploy
transcript against the code (four other proposed findings were refuted and no
change was made for them).

- Caddyfile reformatted so `caddy fmt` is clean, removing the warning Caddy
  printed on every validate and every start. Two causes, not one: `caddy fmt`
  indents with TABS, and it deletes a blank line whose following line begins
  with `{` -- which is why the warning pointed at line 17, the blank before the
  global options block. The check is a whole-file byte comparison, so the line
  number was only the first difference and the entire file had to be
  reformatted. Verified whitespace-only outside the heredoc, whose body is left
  byte-identical: Caddy strips padding derived from the closing marker's
  indentation, so re-indenting it would change what the page serves.
- deploy.sh no longer upgrades Caddy behind the operator's back. A bare
  `docker compose pull` refreshed the floating `caddy:2-alpine` on every re-run
  and `up -d` then recreated it, doing exactly what CADDY_AUTOUPDATE=0 promises
  not to, with none of update.sh's health check or rollback. Pulls are now
  per service: the pinned Ergo tag always, Caddy only when absent or opted in.
- deploy.sh seeds the ACME_EMAIL and NETWORK_NAME prompts from .env, so pressing
  Enter through a re-run no longer renames the network to the hostname in the
  summary while .env keeps the real one.
- The Caddy restart guard compares the container's identity across `up -d`. A
  container compose created or recreated has already read the new Caddyfile;
  only one left running still holds the old config, and `svc_state` cannot tell
  those apart.
- Container logs are now rotated (json-file, 10m x 3) on every service. Alpine's
  docker package ships no daemon.json, so the default is unbounded, and the
  60-second health probe alone writes a log line per run.
- ergoctl notes that LUSERS counts its own probe connection, which is why a
  server with nobody on it reports one invisible user.

Verified: seven local suites pass, the embedded archive round-trips, line
endings are LF, and the reformatted Caddyfile is a verified fixed point of the
formatter (semantically identical token-for-token to the previous one).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-02 15:38:13 -05:00

76 lines
2.4 KiB
Caddyfile

# Caddyfile for the Ergo stack. Caddy runs in the HOST network namespace.
#
# Roles:
# 1. Obtain and renew the Let's Encrypt certificate for {$ERGO_DOMAIN}. Ergo's
# :6697 listener reuses that cert -- `update.sh certsync` copies it into
# ./ircd and rehashes Ergo (runs every 15 minutes, silent when unchanged).
# 2. Terminate HTTPS for IRC-over-WebSocket: wss://{$ERGO_DOMAIN}/webirc ->
# Ergo's plaintext websocket listener on 127.0.0.1:8097. Caddy adds
# X-Forwarded-For / X-Forwarded-Proto and Ergo trusts them from loopback
# (proxy-allowed-from: localhost), so web users keep their real IP and are
# marked secure (+Z).
# 3. A plain-text landing page with connection details at /.
#
# Add your own site config (e.g. a Gamja web client, see README) as
# conf.d/*.caddy -- deploy.sh installs this file but never touches conf.d/.
# Apply changes with: docker compose restart caddy
{
email {$ACME_EMAIL}
# Host networking would put the admin API on the HOST's 127.0.0.1:2019,
# reachable by every local process and container. The config is static, so
# turn it off; changes are applied by restarting the container.
admin off
# No HTTP/3: it would bind udp/443 on the host (not in the firewall's port
# list) and browsers do not run WebSockets over h3 anyway.
servers {
protocols h1 h2
}
}
# Loopback-only health endpoint for the compose healthcheck (no admin API to ask).
http://127.0.0.1 {
respond "ok" 200
}
{$ERGO_DOMAIN} {
encode zstd gzip
# IRC over WebSocket. Ergo ignores the request path; Caddy proxies the
# Upgrade transparently and supplies X-Forwarded-For/-Proto itself.
handle_path /webirc* {
reverse_proxy 127.0.0.1:8097
}
# Operator additions (web client, redirects, ...). See conf.d/00-readme.caddy.
import conf.d/*.caddy
handle {
header Content-Type "text/plain; charset=utf-8"
respond <<TXT
{$NETWORK_NAME} -- IRC server
Connect with any IRC client:
server: {$ERGO_DOMAIN}
port: 6697 (TLS)
web: wss://{$ERGO_DOMAIN}/webirc (IRC-over-WebSocket endpoint)
Register a nickname: /msg NickServ REGISTER <password>
Powered by Ergo (https://ergo.chat).
TXT 200
}
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Content-Type-Options "nosniff"
Referrer-Policy "strict-origin-when-cross-origin"
-Server
}
log {
output stdout
format console
}
}