Commit Graph
2 Commits
Author SHA1 Message Date
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
57_WolveandClaude Opus 5 85d945ccde feat(ergo): persistent message history, plus deployment-readiness audit fixes
Ergo keeps history in RAM by default, so it is lost on every restart --
including the ones this stack's updater performs. HISTORY now selects a backend
at first deploy: sqlite (default, a file beside ircd.db, no extra container),
postgres (a pinned container via docker-compose.postgres.yml, loopback-only,
POSTGRES_PASSWORD_FILE so no secret lands in .env), or off. Both SQL backends
need Ergo 2.18.0+, which deploy.sh enforces. HISTORY_EXPIRE (default 30d) sets
retention, because upstream's 1w expire-time DELETES from persistent storage --
persistence with the shipped default would buy only a week.

Ergo opens the history backend only at startup and, unlike MySQL, has no
"after launching the server" guard for sqlite/postgresql: a rehash that enables
one reports success and then silently discards every message. So `ergoctl
history` restarts, and `ergoctl edit` detects a backend change and refuses to
apply it by rehash. The config validator runs with no network by design, so it
neutralises postgresql in its copy and reachability is checked separately.

Audit fixes (six lenses over first-deploy, runtime, lifecycle, security, docs):

- SECURITY (blocker): ircd/ is writable by the container uid while the 15-minute
  cert sync and every ergoctl config edit run as root. cp/install/> follow a
  symlink, so code execution in Ergo could redirect a root write onto any host
  file. All such writes now stage under $STACK_DIR (0700 root) and land via mv
  (rename(2) replaces a symlink instead of following it); root reads use cp -P
  or refuse. Regression-tested with real symlinks.
- deploy.sh no longer swaps the image when ERGO_TAG changes on a deployed stack
  (that bypassed pre-flight, snapshot, user warning and rollback); it points at
  `TARGET_VERSION=... ergoctl update update`.
- restore was lossy and could lock you out: it now snapshots the current config
  as well as the database, restores message history, re-points the postgres
  password and re-hashes the local admin password into the restored config, and
  validates before starting.
- `install -d -m` re-modes existing directories: `ergoctl backup /var/backups`
  no longer chmods it 0700 root, and the updater no longer re-modes /var/log
  (0775 root:syslog on Debian, which rsyslog needs).
- The admin oper password is printed only on a first run at a TTY, so it stays
  out of cloud-init serial-console logs.
- A failed update is remembered, so the daily job stops repeating a disruptive
  warn/stop/swap/roll-back cycle every night; postgres readiness gates an update
  that would otherwise stop a healthy server it cannot restart.
- certsync no longer sends "TLS recovered" for outcomes that synced nothing.
- ergoctl history writes .env only after the restart is healthy, and returns 0.
- CR/LF is stripped before IRC framing, so a multi-line argument cannot inject a
  second command; the ntfy token moves out of curl's argv.
- ufw/firewalld are additive, so 6667 is now explicitly revoked when PLAINTEXT=0.
- build.sh refuses to build a deploy.sh whose archive is missing a file the
  script reads -- the failure mode that would have shipped a stack aborting on
  every host.

Docs corrected against the code throughout, including retention, the pre-connect
account-registration default, encrypted-restore (AGE_IDENTITY), what a re-run
really does to .env, and what the update log does and does not contain.

Verified locally: all six suites pass (config render for each backend against the
real 2.19.1 template, yaml/oper/version/env helpers, the IRC client against a
fake server, and the audit fixes including the symlink escalation). Still not
exercised on a Docker host: the containers, ACME issuance, cert sync and
PostgreSQL itself.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-02 14:32:37 -05:00