From 85d945ccde17965305cea6bd099865569dd1b175 Mon Sep 17 00:00:00 2001 From: William Gill Date: Wed, 2 Sep 2026 14:32:37 -0500 Subject: [PATCH] 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 --- README.md | 2 +- automations.sh | 1 + deployments/ergo/.env.example | 27 + deployments/ergo/README.md | 105 +- deployments/ergo/build.sh | 13 + deployments/ergo/cloud-init.yml | 1 + deployments/ergo/deploy.sh | 1255 +++++++++++------- deployments/ergo/docker-compose.postgres.yml | 64 + deployments/ergo/ergoctl | 228 +++- deployments/ergo/ergolib.sh | 147 +- deployments/ergo/update.sh | 66 +- 11 files changed, 1374 insertions(+), 535 deletions(-) create mode 100644 deployments/ergo/docker-compose.postgres.yml diff --git a/README.md b/README.md index a0ca0dd..092acc5 100644 --- a/README.md +++ b/README.md @@ -120,7 +120,7 @@ deployments// # one folder per stack | [`copyparty`](deployments/copyparty/) | Portable file server — web UI/WebDAV behind Caddy, plus direct **SFTP** + **FTPS**. Ships a security-notices-aware updater. | — | | [`simplex`](deployments/simplex/) | SimpleX SMP + XFTP relay with Tor hidden services + encrypted backups. | globals/age-pubkey.txt | | [`openbao`](deployments/openbao/) | Hardened tape-encryption key store for **Kanrisha** (the LTO tape-archive system — separate repo/host). **Exception: native TLS on the LAN, no Caddy/LE.** raft storage, mlock, self-signed or Smallstep-ACME cert, manual/HSM unseal. | globals/age-pubkey.txt (backups) | -| [`ergo`](deployments/ergo/) | IRC server ([Ergo](https://ergo.chat)) with Caddy for Let's Encrypt + the websocket endpoint. **Both containers use host networking** (real client IPs for bans/cloaking; the host firewall applies). Ships `ergoctl` (admin CLI) and a health-checked updater that also syncs Caddy's cert into Ergo. | — | +| [`ergo`](deployments/ergo/) | IRC server ([Ergo](https://ergo.chat)) with Caddy for Let's Encrypt + the websocket endpoint. **Both containers use host networking** (real client IPs for bans/cloaking; the host firewall applies). Persistent message history via SQLite (default) or an optional PostgreSQL container. Ships `ergoctl` (admin CLI) and a health-checked updater that also syncs Caddy's cert into Ergo. | — | ## Conventions diff --git a/automations.sh b/automations.sh index b86bd4c..4f0f290 100644 --- a/automations.sh +++ b/automations.sh @@ -188,6 +188,7 @@ ask_deployment_vars() { ask ERGO_DOMAIN "IRC server hostname (e.g. irc.example.com)" ask ACME_EMAIL "Let's Encrypt email" ask NETWORK_NAME "IRC network name, no spaces (blank = the hostname)" optional + ask HISTORY "Persistent message history: sqlite | postgres | off (blank = sqlite)" optional ask PLAINTEXT "Also serve PUBLIC plaintext IRC on 6667? (1 = yes, blank = no)" optional ask UPDATE_POLICY "Auto-update policy: latest | security | off (blank = latest)" optional ;; esac diff --git a/deployments/ergo/.env.example b/deployments/ergo/.env.example index 2550aef..0e810de 100644 --- a/deployments/ergo/.env.example +++ b/deployments/ergo/.env.example @@ -40,6 +40,33 @@ ERGO_GID= # so the host firewall matches. PLAINTEXT=0 +# --- Message history ---------------------------------------------------------- +# Ergo keeps history in RAM by default, so it is lost on every restart -- +# including the ones the updater performs. Persist it with: +# sqlite (default) a file beside ircd.db; no extra container +# postgres a pinned PostgreSQL container (docker-compose.postgres.yml) +# off RAM only, upstream behaviour +# Needs Ergo >= 2.18.0. How long messages are kept is governed by +# history.restrictions.expire-time in ircd.yaml (1 week by default), NOT here. +# Chosen at the first deploy; switch later with 'ergoctl history ' +# (which does not migrate existing messages). +HISTORY=sqlite + +# Set by deploy.sh so every `docker compose` command in this stack sees the same +# services. It gains ':docker-compose.postgres.yml' when HISTORY=postgres. +COMPOSE_FILE=docker-compose.yml + +# --- PostgreSQL (only when HISTORY=postgres) ---------------------------------- +# The MAJOR version is pinned on purpose: PostgreSQL refuses to start on a data +# directory written by a different major, so update.sh never touches this image. +# Changing it is a deliberate dump-and-restore -- see the README. +POSTGRES_TAG=17-alpine +POSTGRES_USER=ergo +POSTGRES_DB=ergo_history +# Published on 127.0.0.1 only. The password lives in secrets/postgres.pass +# (0600), never here -- compose interpolates .env and a dollar sign would break it. +POSTGRES_PORT=5432 + # --- Updates (update.sh) ------------------------------------------------------ # This file is where the scheduled jobs read these four knobs from; edit them # here. (An explicit environment variable, or a value pinned in diff --git a/deployments/ergo/README.md b/deployments/ergo/README.md index 15963cc..c0c776f 100644 --- a/deployments/ergo/README.md +++ b/deployments/ergo/README.md @@ -47,6 +47,7 @@ the host's loopback), and there is no `ports:` section — host mode. | `NETWORK_NAME` | IRC network name (letters, digits, `. _ -`; no spaces). Defaults to `ERGO_DOMAIN`. | | `ERGO_TAG` | Pinned image tag **with the `v`** (`v2.19.1`). Set by deploy.sh to the newest release; bumped by update.sh. Never `stable`/`latest` — if a first deploy could not reach GitHub it falls back to `stable`, and the next update run pins it. | | `PLAINTEXT` | `0` (default) or `1` — public plaintext 6667. **Fixed at the first deploy** (it selects a listener in `ircd.yaml`, which deploy.sh never rewrites); see below to change it. | +| `HISTORY` | `sqlite` (default) / `postgres` / `off` — see [Message history](#message-history). | | `UPDATE_POLICY` | `latest` (default) / `security` / `off` — see [Updates](#updates). | See [`.env.example`](.env.example) for the rest (`UPDATE_GRACE`, @@ -126,10 +127,11 @@ listener as the `admin` oper. | `ergoctl passwd [show\|rotate]` | the `admin` oper password | | `ergoctl announce ` | NOTICE to everyone | | `ergoctl kill [reason]` · `ban add\|del\|list\|info` · `defcon [1-5]` | moderation (`UBAN` under the hood). `ban add` takes an **IP, CIDR, `nick!user@host` mask, or account name** — a bare name is an *account* to suspend, not a connected nick, so run `ban info ` first to get their IP | -| `ergoctl cmd ` | anything else as the admin oper, e.g. `ergoctl cmd NS SAREGISTER alice hunter2`, `ergoctl cmd CS PURGE #spam`; replies are printed with control characters stripped | +| `ergoctl cmd ` | anything else as the admin oper, e.g. `ergoctl cmd NS SAREGISTER alice hunter2`, `ergoctl cmd CS PURGE ADD #spam` (`CS PURGE DEL` to undo); replies are printed with control characters stripped | | `ergoctl cert [show\|sync]` | cert on disk vs the one served on 6697; force a sync | | `ergoctl backup [--live] [dir]` / `restore ` | see [Backups](#backups) | | `ergoctl update …` | passthrough to `update.sh` | +| `ergoctl history [backend]` | show or switch the persistent-history backend | | `ergoctl debug on\|off` · `caddy-restart` · `shell` | log level; apply Caddyfile/conf.d changes; a shell in the container | Editing by hand works too: change `ircd/ircd.yaml`, then `ergoctl rehash` — a @@ -160,10 +162,65 @@ Silent when nothing changed. The daily run also warns when the served cert is still self-signed (DNS/80/443 not right yet) or expires within 14 days (certsync not running). `ergoctl cert` shows both sides. +## Message history + +Ergo keeps channel and DM history for `CHATHISTORY`, `/HISTORY`, autoreplay-on-join +and always-on (bouncer) clients. **Upstream keeps it in RAM**, so it is lost on +every restart — including the ones this stack's updater performs. So persistence +is on by default here: + +| `HISTORY` | What it does | +|---|---| +| `sqlite` *(default)* | A file at `ircd/ergo_history.db`, next to the account database. No extra container, backed up with everything else. Right for a single-node server. | +| `postgres` | A pinned PostgreSQL container (`docker-compose.postgres.yml`), reachable only on `127.0.0.1`. Choose it if you want a real database to query, or already run Postgres. | +| `off` | Upstream behaviour: RAM only. | + +Both SQL backends need **Ergo 2.18.0+** (deploy.sh refuses older pins). How long +messages are kept is `history.restrictions.expire-time` in `ircd.yaml` — **one +week by default** — not a size limit: enabling persistence removes the in-memory +`channel-length`/`client-length` caps. Raise or lower it with `ergoctl edit`, and +note the privacy point upstream flags: persisted messages are personal data, so +check what your jurisdiction expects of you before extending retention. + +```bash +ergoctl history # backend, database size / connectivity, retention +ergoctl history postgres # switch (restarts Ergo; does NOT migrate messages) +``` + +Switching backends leaves the old store on disk and starts the new one empty — +there is no migration path, so pick one at deploy time if you can. + +### PostgreSQL + +`HISTORY=postgres` adds `docker-compose.postgres.yml` to `COMPOSE_FILE` in `.env`, +which `docker compose` reads by itself — so every command in this stack sees the +same services with no extra flags. The container publishes **only to +`127.0.0.1:5432`**, Ergo reaches it there (the host-networked containers cannot +use compose service DNS), and `depends_on: service_healthy` keeps Ergo from +starting before the database accepts connections. The password is generated into +`secrets/postgres.pass` (0600) and passed via `POSTGRES_PASSWORD_FILE`, so it +never lands in `.env`. + +**The major version is pinned and the updater never touches it.** PostgreSQL +refuses to start on a data directory written by a different major version, so +moving from `17-alpine` to `18-alpine` is a deliberate dump-and-restore: + +```bash +ergoctl backup # contains a pg_dump of the history database +ergoctl history off && docker compose down -v postgres # drops the old data volume +# set POSTGRES_TAG=18-alpine in .env, then: +ergoctl history postgres && ergoctl restore backups/ergo-backup-.tar.gz +``` + +Note that Postgres 18 also changed the image's default data directory layout; the +compose file pins `PGDATA` explicitly so a future image default cannot move it. + ## Updates ```bash -ergoctl update check # running vs latest, published security advisories, cert state; changes nothing +ergoctl update check # running vs latest, published security advisories, cert state + # reports only, with one exception: a floating ERGO_TAG + # ('stable') is pinned to the running version in .env ergoctl update update # update now (TARGET_VERSION=2.19.1 to pin); honours the safety rails ergoctl update install # (re)schedule / uninstall to stop ``` @@ -203,16 +260,27 @@ web-client websockets. Update results reuse the ntfy config at ## Backups `ergoctl backup` writes `backups/ergo-backup-.tar.gz` (0600) with -`ircd.yaml`, `ircd.db`, `ergo.motd` and a `meta` file (version, date). By default +`ircd.yaml`, `ircd.db`, `ergo.motd`, the message history (the SQLite file, or a +`pg_dump` — a file copy of a live PostgreSQL data directory would not be a valid +backup) and a `meta` file (version, history backend, date). By default it stops Ergo for a few seconds for a consistent copy (`--live` skips that). Put an age public key in `$STACK_DIR/age-recipients.txt` (e.g. from `globals/age-pubkey.txt`) **and install `age`** (`apk add age`, `apt install age`, `dnf install age` — deploy.sh does not) and backups are encrypted with it; with the recipients file present but `age` missing, `ergoctl backup` refuses rather than writing the account and oper hashes out in the clear. TLS files are not -included — certsync regenerates them. `ergoctl restore ` stops Ergo, -warns if the backup came from a newer version (older Ergo cannot read a newer -schema), restores, and waits for health. Ergo's own `datastore.autoupgrade` +included — certsync regenerates them. `ergoctl restore ` stops Ergo, keeps the current config and database as +`backups/{ircd.yaml,ircd.db}.pre-restore.`, warns if the backup came from a +newer version (older Ergo cannot read a newer schema), restores, validates the +result before starting, and waits for health. Because the archive carries the +*original* host's credentials, restore re-points `datastore.postgresql.password` +at this host's `secrets/postgres.pass` and re-hashes this host's +`secrets/admin.pass` into the config — otherwise a rebuild would come up healthy +but leave you unable to `/OPER`. An encrypted backup needs the private key: + +```bash +AGE_IDENTITY=/root/age.key ergoctl restore backups/ergo-backup-.tar.gz.age +``` Ergo's own `datastore.autoupgrade` leaves `ircd/ircd.db.v..bak` files behind on schema upgrades; prune them once you are happy with a release. @@ -240,6 +308,12 @@ file_server }` block into `caddy/etc/conf.d/` (see `00-readme.caddy`), then filesystem, all capabilities dropped, `no-new-privileges`. `ircd/` (database, config with the oper hash, TLS key) is `0700 ergo:ergo`; `secrets/`, `backups/`, `caddy/data` are `0700 root`. +- **Root never follows a symlink into `ircd/`.** That directory is writable by + the container uid while the cert sync and every `ergoctl` config edit run as + root, so a plain `cp`/`install`/`>` there would let code execution inside Ergo + redirect a root write onto any file on the host. Every such write stages under + `$STACK_DIR` (0700 root) and lands with `mv` — `rename(2)` replaces a symlink + instead of following it — and root reads use `cp -P` or refuse outright. - **Caddy runs as root** (it needs `NET_BIND_SERVICE` for 80/443) with every other capability dropped, admin API off, HTTP/3 off. - **Loopback plaintext is the control plane by design**: OPER over loopback is @@ -255,6 +329,7 @@ file_server }` block into `caddy/etc/conf.d/` (see `00-readme.caddy`), then | File | Purpose | |---|---| | `docker-compose.yml` | caddy + ergo, both `network_mode: host`, hardened; IRC-level healthcheck. | +| `docker-compose.postgres.yml` | Optional PostgreSQL overlay for `HISTORY=postgres` (loopback-only, pinned major). | | `Caddyfile` | ACME, `/webirc` websocket proxy, text landing page, `admin off`, no h3; imports `conf.d/*.caddy`. | | `conf.d-readme.caddy` | Installed once as `caddy/etc/conf.d/00-readme.caddy` (operator drop-ins; Gamja example). | | `ergo.motd` | MOTD template (`__NETWORK_NAME__`, `__DOMAIN__`). | @@ -268,8 +343,10 @@ file_server }` block into `caddy/etc/conf.d/` (see `00-readme.caddy`), then On the host: `/srv/ergo/{ircd,caddy/{etc,data,config},secrets,backups,templates}` plus `.state/` (notification de-duplication) and `.env.bak.` copies from each update (last 5 kept); `/etc/ergo-update.conf` (where the jobs find the stack); -`/usr/local/bin/ergoctl`; `/var/log/ergo-update.log` (scheduled runs log there, -since busybox crond has nowhere else to put their output). +`/usr/local/bin/ergoctl`; `/var/log/ergo-update.log` (the **daily update run** +logs there, since busybox crond has nowhere else to put its output — cert-sync +failures instead surface through ntfy and as `certsync: LAST FAILURE` in +`ergoctl status`). ## Notes @@ -277,5 +354,13 @@ since busybox crond has nowhere else to put their output). another Caddy/80/443 stack (deploy.sh checks the ports). - DNS for `ERGO_DOMAIN` must resolve to the host and 80/443 be reachable before deploy for the cert to issue; Ergo stays up on a self-signed cert meanwhile. -- Ergo's built-in registration is open by default (`accounts.registration.enabled`); - edit `ircd.yaml` if you want an invite-only network. +- Ergo's built-in registration is open by default, and **`allow-before-connect` + is also on**, so accounts can be created by a client that has not finished + connecting, throttled only globally (30 attempts per 10 minutes ≈ 4,300/day). + Each account is a durable row in `ircd.db`. For a private network set + `accounts.registration.enabled: false`, or keep registration but set + `allow-before-connect: false`, with `ergoctl edit`. +- The admin oper password is printed by `deploy.sh` only on the first run and + only to a terminal. Unattended runs (cloud-init) print the path instead, so the + credential does not end up in the provider's serial-console log; read it with + `ergoctl passwd show`. diff --git a/deployments/ergo/build.sh b/deployments/ergo/build.sh index 0066140..be1348d 100644 --- a/deployments/ergo/build.sh +++ b/deployments/ergo/build.sh @@ -17,6 +17,7 @@ MARKER="__ARCHIVE_BELOW__" # generated on the host from the pulled image's own default config. FILES=( docker-compose.yml + docker-compose.postgres.yml Caddyfile conf.d-readme.caddy ergo.motd @@ -31,6 +32,18 @@ for f in "${FILES[@]}"; do [[ -f "$DIR/$f" ]] || { echo "Missing $DIR/$f" >&2; exit 1; } done +# Guard: every file deploy.sh reads out of the extracted archive must be in +# FILES, and must be in deploy.sh's own EMBEDDED manifest. Forgetting either is +# a deploy-time abort on every host, so catch it here instead. +missing=0 +while IFS= read -r ref; do + case " ${FILES[*]} " in *" $ref "*) ;; *) echo "deploy.sh reads \$SCRIPT_DIR/$ref but build.sh does not embed it" >&2; missing=1 ;; esac +done < <(grep -oE '\$SCRIPT_DIR/[A-Za-z0-9._-]+' "$SCRIPT" | sed 's|^\$SCRIPT_DIR/||' | sort -u) +for f in "${FILES[@]}"; do + grep -q "EMBEDDED=(.*$f" "$SCRIPT" || { echo "$f is embedded but missing from deploy.sh's EMBEDDED manifest" >&2; missing=1; } +done +(( missing == 0 )) || { echo "Refusing to build a deploy.sh that would abort on the target host." >&2; exit 1; } + # Syntax-check the bash payload before embedding it. for f in ergolib.sh update.sh ergoctl deploy.sh; do bash -n "$DIR/$f" || { echo "bash -n failed: $f" >&2; exit 1; } diff --git a/deployments/ergo/cloud-init.yml b/deployments/ergo/cloud-init.yml index b7cef80..6e3551b 100644 --- a/deployments/ergo/cloud-init.yml +++ b/deployments/ergo/cloud-init.yml @@ -36,6 +36,7 @@ runcmd: ERGO_DOMAIN=irc.example.com \ ACME_EMAIL=admin@example.com \ NETWORK_NAME=ExampleNet \ + HISTORY=sqlite \ PLAINTEXT=0 \ UPDATE_POLICY=latest \ SKIP_PROMPTS=1 \ diff --git a/deployments/ergo/deploy.sh b/deployments/ergo/deploy.sh index 240de71..51e1429 100644 --- a/deployments/ergo/deploy.sh +++ b/deployments/ergo/deploy.sh @@ -10,9 +10,10 @@ # update.sh, ergoctl (+ /usr/local/bin/ergoctl wrapper), ergolib.sh. # 3. Seeds .env on first run, pinning ERGO_TAG to the newest release. # 4. Generates ircd/ircd.yaml ONCE from the pulled image's own default config -# (server name, network name, listeners, websocket origin, admin oper with a -# random password saved to secrets/admin.pass) and validates it with a -# throwaway `ergo run --smoke` before anything starts. Never rewritten. +# (server name, network name, listeners, websocket origin, message-history +# backend, admin oper with a random password saved to secrets/admin.pass) +# and validates it with a throwaway `ergo run --smoke` before anything +# starts. Never rewritten. # 5. Opens 80/443/6697 (+6667 with PLAINTEXT=1) on the host firewall. Both # containers use host networking, so this firewall really applies. # 6. Pulls images, validates the Caddyfile, brings the stack up, waits for @@ -20,19 +21,26 @@ # 7. Schedules update.sh: certsync every 15 min, update check daily. # # Idempotent: re-run to apply compose/Caddyfile changes or pull images. It never -# touches ircd/ircd.yaml, secrets/, caddy/etc/conf.d/ or .env after the first run. +# touches ircd/ircd.yaml, secrets/ or caddy/etc/conf.d/; in .env it only refreshes +# ERGO_UID/GID, re-pins a floating ERGO_TAG, and writes back settings you passed +# explicitly on this run (everything else is adopted FROM .env, not overwritten). # -# Self-contained: docker-compose.yml, Caddyfile, conf.d-readme.caddy, ergo.motd, -# .env.example, ergolib.sh, update.sh and ergoctl are embedded as a base64 -# tar.gz at the bottom of this file. Rebuild with build.sh after editing them. +# Self-contained: docker-compose.yml, docker-compose.postgres.yml, Caddyfile, +# conf.d-readme.caddy, ergo.motd, .env.example, ergolib.sh, update.sh and ergoctl +# are embedded as a base64 tar.gz at the bottom of this file. Rebuild with +# build.sh after editing them. # # Usage: # bash deploy.sh # interactive prompts # ERGO_DOMAIN=irc.example.com ACME_EMAIL=me@x.com NETWORK_NAME=MyNet \ # SKIP_PROMPTS=1 bash deploy.sh # non-interactive +# HISTORY=postgres bash deploy.sh # FIRST deploy only: persistent history in +# # PostgreSQL (default sqlite; off = RAM only) +# HISTORY_EXPIRE=90d bash deploy.sh # how long messages are kept (default 30d) # PLAINTEXT=1 bash deploy.sh # FIRST deploy only: also serve plaintext # # :6667 publicly (+STS). Fixed thereafter. -# ERGO_TAG=v2.19.1 bash deploy.sh # pin a specific release +# ERGO_TAG=v2.19.1 bash deploy.sh # pin a release on the FIRST deploy; after +# # that use: ergoctl update update # UPDATE_POLICY=security bash deploy.sh # also latest | off # ERGO_AUTOUPDATE=0 bash deploy.sh # install update.sh but don't schedule the # # daily update (certsync is still scheduled) @@ -57,10 +65,20 @@ set -euo pipefail # (`|| true` on the trailing `&&`s below: a false test makes the whole list # return 1, which aborts the script under `set -e` if it ever ends a block.) EXPLICIT="" -for _k in ERGO_TAG PLAINTEXT UPDATE_POLICY FORCE_UPDATE UPDATE_GRACE CADDY_AUTOUPDATE CADDY_TAG; do +for _k in ERGO_TAG PLAINTEXT HISTORY UPDATE_POLICY FORCE_UPDATE UPDATE_GRACE CADDY_AUTOUPDATE CADDY_TAG; do [[ -n "${!_k:-}" ]] && EXPLICIT="${EXPLICIT} ${_k}" || true done : "${PLAINTEXT:=0}" +: "${HISTORY:=sqlite}" # sqlite | postgres | off (see the README) +# How long messages are kept. Upstream's 1w applies to PERSISTENT storage too -- +# it deletes, it is not just a query cutoff -- so persistence with the shipped +# default buys only a week. Retention is a policy (and privacy) decision: raise +# or lower it here, or later with 'ergoctl edit'. +: "${HISTORY_EXPIRE:=30d}" +: "${POSTGRES_TAG:=17-alpine}" # major version is PINNED; update.sh never moves it +: "${POSTGRES_USER:=ergo}" +: "${POSTGRES_DB:=ergo_history}" +: "${POSTGRES_PORT:=5432}" : "${UPDATE_POLICY:=latest}" : "${FORCE_UPDATE:=0}" : "${UPDATE_GRACE:=60}" @@ -95,7 +113,7 @@ else die "No embedded archive found. Run build.sh to embed deployment files." fi -EMBEDDED=(docker-compose.yml Caddyfile conf.d-readme.caddy ergo.motd .env.example ergolib.sh update.sh ergoctl) +EMBEDDED=(docker-compose.yml docker-compose.postgres.yml Caddyfile conf.d-readme.caddy ergo.motd .env.example ergolib.sh update.sh ergoctl) for f in "${EMBEDDED[@]}"; do [[ -f "$SCRIPT_DIR/$f" ]] || die "Embedded archive missing $f" done @@ -162,6 +180,19 @@ open_ports() { # firewall genuinely governs them (unlike Docker-published ports). local ports=(80/tcp 443/tcp 6697/tcp) [[ "$PLAINTEXT" == "1" ]] && ports+=(6667/tcp) + # ufw and firewalld are additive: unlike the repo's declarative ports.d + # engine they never drop a port that left the list, so turning PLAINTEXT + # back off would leave 6667 open. Revoke it explicitly. + if [[ "$PLAINTEXT" != "1" ]]; then + if command -v ufw >/dev/null 2>&1 && ufw status 2>/dev/null | grep -q '^Status: active'; then + ufw status 2>/dev/null | grep -q '^6667/tcp' && { log "Closing 6667/tcp in ufw (PLAINTEXT=0)..."; ufw delete allow 6667/tcp >/dev/null 2>&1 || true; } + elif command -v firewall-cmd >/dev/null 2>&1 && firewall-cmd --state >/dev/null 2>&1; then + if firewall-cmd --query-port=6667/tcp --permanent >/dev/null 2>&1; then + log "Closing 6667/tcp in firewalld (PLAINTEXT=0)..." + firewall-cmd -q --remove-port=6667/tcp --permanent || true; firewall-cmd -q --reload || true + fi + fi + fi if [[ -d /etc/firewall/ports.d && -x /usr/local/sbin/firewall-apply ]]; then log "Registering ${ports[*]} with the host firewall..." printf '%s\n' "${ports[@]}" > /etc/firewall/ports.d/ergo.rule @@ -217,6 +248,7 @@ prompt NETWORK_NAME "IRC network name (no spaces)" "$ERGO_DOMAIN" || die "NETWORK_NAME '$NETWORK_NAME' must be 1-64 chars of letters, digits, . _ - (no spaces: it is sent as an IRC parameter)." [[ "$PLAINTEXT" == "0" || "$PLAINTEXT" == "1" ]] || die "PLAINTEXT must be 0 or 1." case "$UPDATE_POLICY" in latest|security|off) ;; *) die "UPDATE_POLICY must be latest, security or off." ;; esac +case "$HISTORY" in sqlite|postgres|off) ;; *) die "HISTORY must be sqlite, postgres or off." ;; esac # Re-run guard: server.name is immutable once Ergo has run with it, and the # cert/websocket origin/Caddy site all derive from ERGO_DOMAIN. @@ -236,11 +268,14 @@ fi # because the listener it selects lives in ircd.yaml, which is never rewritten. # --------------------------------------------------------------------------- if [[ -f "$ENV_FILE" ]]; then - for _k in PLAINTEXT UPDATE_POLICY FORCE_UPDATE UPDATE_GRACE CADDY_AUTOUPDATE CADDY_TAG; do + for _k in PLAINTEXT HISTORY UPDATE_POLICY FORCE_UPDATE UPDATE_GRACE CADDY_AUTOUPDATE CADDY_TAG POSTGRES_TAG POSTGRES_USER POSTGRES_DB POSTGRES_PORT; do _v="$(env_get "$_k")" [[ -n "$_v" ]] || continue if [[ " $EXPLICIT " == *" $_k "* ]]; then [[ "${!_k}" == "$_v" ]] && continue + if [[ "$_k" == "HISTORY" ]]; then + die "This stack was deployed with HISTORY=${_v} and you passed HISTORY=${!_k}. The backend is recorded in ircd/ircd.yaml, which deploy.sh never rewrites, so re-running cannot switch it (and switching does not migrate existing messages). To change it: 'ergoctl history ${!_k}'." + fi if [[ "$_k" == "PLAINTEXT" ]]; then die "This stack was deployed with PLAINTEXT=${_v} and you passed PLAINTEXT=${!_k}. The listener is fixed in ircd/ircd.yaml, which deploy.sh never rewrites, so re-running cannot change it. To switch: edit the plaintext listener with 'ergoctl edit' ($( [[ "$_v" == 1 ]] && echo '":6667": -> "127.0.0.1:6667":' || echo '"127.0.0.1:6667": -> ":6667":' ) and server.sts.enabled), set PLAINTEXT=${!_k} in $ENV_FILE, run 'ergoctl restart', then re-run this script to fix the firewall." fi @@ -281,7 +316,8 @@ log "Setting up $STACK_DIR..." install -d -m 0700 "$STACK_DIR" "$SECRETS_DIR" "$BACKUP_DIR" "$TEMPLATES_DIR" "$STATE_DIR" install -d -m 0700 "$CADDY_DIR" "$CADDY_DIR/data" "$CADDY_DIR/config" install -d -m 0755 "$CADDY_DIR/etc" "$CADDY_DIR/etc/conf.d" -install -m 0640 "$SCRIPT_DIR/docker-compose.yml" "$STACK_DIR/docker-compose.yml" +install -m 0640 "$SCRIPT_DIR/docker-compose.yml" "$STACK_DIR/docker-compose.yml" +install -m 0640 "$SCRIPT_DIR/docker-compose.postgres.yml" "$STACK_DIR/docker-compose.postgres.yml" # Caddy has no admin API here and bind-mounted file contents are not part of the # compose config hash, so `up -d` alone would leave a changed Caddyfile unloaded. CADDY_CHANGED=0 @@ -317,6 +353,7 @@ if [[ ! -f "$ENV_FILE" ]]; then env_set ERGO_IMAGE "$ERGO_IMAGE" env_set ERGO_TAG "$ERGO_TAG" env_set PLAINTEXT "$PLAINTEXT" + env_set HISTORY "$HISTORY" env_set UPDATE_POLICY "$UPDATE_POLICY" env_set FORCE_UPDATE "$FORCE_UPDATE" env_set UPDATE_GRACE "$UPDATE_GRACE" @@ -335,7 +372,11 @@ else _envtag="$(env_get ERGO_TAG)" if [[ "$ERGO_TAG" == v[0-9]* && "$ERGO_TAG" != "$_envtag" ]]; then case "$_envtag" in - v[0-9]*) [[ " $EXPLICIT " == *" ERGO_TAG "* ]] && { env_set ERGO_TAG "$ERGO_TAG"; log "Pinned ERGO_TAG=${ERGO_TAG} (was ${_envtag})."; } || true ;; + # Changing the version of a DEPLOYED stack here would swap the image + # with none of the updater's rails: no pre-flight against the live + # config, no database snapshot, no warning to connected users and no + # rollback if the new build never becomes healthy. Send them there. + v[0-9]*) die "This stack is pinned to ${_envtag} and you passed ERGO_TAG=${ERGO_TAG}. Re-running deploy.sh would swap the image with no pre-flight, no database snapshot and no rollback. Use: TARGET_VERSION=$(normver "$ERGO_TAG") ergoctl update update" ;; *) env_set ERGO_TAG "$ERGO_TAG"; log "Pinned ERGO_TAG=${ERGO_TAG} (was '${_envtag}', which floats)." ;; esac fi @@ -345,6 +386,39 @@ env_set ERGO_GID "$ERGO_GID" ERGO_TAG="$(ergo_tag)" IMAGE="$(ergo_image)" +# --------------------------------------------------------------------------- +# Persistent history backend +# +# Ergo's history is ON by default but lives in RAM, so it is lost on every +# restart -- including the ones this stack's updater performs. sqlite persists +# it in a file beside ircd.db with no extra moving parts; postgres adds a +# pinned container for people who want a real database. +# --------------------------------------------------------------------------- +if [[ "$HISTORY" != "off" ]]; then + # Both SQL history backends were added in Ergo 2.18.0. + _v="$(normver "$ERGO_TAG")" + if [[ -n "$_v" ]] && ! ver_ge "$_v" 2.18.0; then + die "HISTORY=$HISTORY needs Ergo 2.18.0 or newer (the SQL history backends were added there), but ERGO_TAG=$ERGO_TAG. Use HISTORY=off or a newer release." + fi +fi +if [[ "$HISTORY" == "postgres" ]]; then + env_set POSTGRES_TAG "$POSTGRES_TAG" + env_set POSTGRES_USER "$POSTGRES_USER" + env_set POSTGRES_DB "$POSTGRES_DB" + env_set POSTGRES_PORT "$POSTGRES_PORT" + # docker compose reads COMPOSE_FILE from .env, so every compose command in + # this stack picks up the overlay without needing extra flags. + env_set COMPOSE_FILE "docker-compose.yml:docker-compose.postgres.yml" + if [[ ! -f "$SECRETS_DIR/postgres.pass" ]]; then + log "Generating the PostgreSQL password (secrets/postgres.pass)..." + ( umask 077; ergo_random_password 32 > "$SECRETS_DIR/postgres.pass" ) + fi + chmod 0600 "$SECRETS_DIR/postgres.pass" + PG_PASS="$(cat "$SECRETS_DIR/postgres.pass")" +else + env_set COMPOSE_FILE "docker-compose.yml" +fi + missing=() for var in ERGO_DOMAIN ACME_EMAIL NETWORK_NAME ERGO_TAG ERGO_UID ERGO_GID; do grep -E "^${var}=.+$" "$ENV_FILE" >/dev/null || missing+=("$var") @@ -460,6 +534,10 @@ assert_ircd_yaml() { # hard post-conditions -- a silent sed miss would be a pub _nochk '^ path: languages$' "unpatched languages path" if [[ "$PLAINTEXT" == "1" ]]; then _chk '^ ":6667":' "public plaintext listener"; _nochk '^ "127.0.0.1:6667":' "loopback-only listener with PLAINTEXT=1" else _chk '^ "127.0.0.1:6667":' "loopback plaintext listener"; _nochk '^ ":6667":' "PUBLIC plaintext listener with PLAINTEXT=0"; fi + # The history backend must be exactly the one asked for -- yaml_set_nested + # targets a specific block, so a wrong result means the template moved. + local got; got="$(history_backend "$f")" + if [[ "$got" != "$HISTORY" ]]; then warn "ircd.yaml check failed: history backend is '$got', expected '$HISTORY'"; fail=1; fi (( fail == 0 )) } @@ -478,6 +556,33 @@ if [[ ! -f "$CONF" ]]; then ( umask 077; printf '%s\n' "$ADMIN_PW" > "$ADMIN_PASS_FILE" ) render_ircd_yaml "$TEMPLATE" "$CONF" + + # Persistent history. Ergo keeps the in-memory limits (channel-length etc.) + # for the RAM backend; with persistence on, retention is governed by + # history.restrictions.expire-time (1w by default). + if [[ "$HISTORY" != "off" && -n "$HISTORY_EXPIRE" ]]; then + yaml_set_nested "$CONF" history restrictions expire-time "$HISTORY_EXPIRE" \ + || die "Could not set history.restrictions.expire-time." + log "Message retention: ${HISTORY_EXPIRE} (history.restrictions.expire-time)." + fi + case "$HISTORY" in + sqlite) + log "Enabling persistent history (SQLite: ircd/ergo_history.db)..." + yaml_set_nested "$CONF" datastore sqlite enabled true || die "Could not enable datastore.sqlite in the generated config." + yaml_set_nested "$CONF" history persistent enabled true || die "Could not enable history.persistent in the generated config." ;; + postgres) + log "Enabling persistent history (PostgreSQL on 127.0.0.1:${POSTGRES_PORT})..." + yaml_set_nested "$CONF" datastore postgresql enabled true || die "Could not enable datastore.postgresql." + yaml_set_nested "$CONF" datastore postgresql host "\"127.0.0.1\"" || die "Could not set the postgresql host." + yaml_set_nested "$CONF" datastore postgresql port "$POSTGRES_PORT" || die "Could not set the postgresql port." + yaml_set_nested "$CONF" datastore postgresql user "\"${POSTGRES_USER}\"" || die "Could not set the postgresql user." + yaml_set_nested "$CONF" datastore postgresql password "\"${PG_PASS}\"" || die "Could not set the postgresql password." + yaml_set_nested "$CONF" datastore postgresql history-database "\"${POSTGRES_DB}\"" || die "Could not set the postgresql database." + yaml_set_nested "$CONF" history persistent enabled true || die "Could not enable history.persistent." ;; + off) + log "Persistent history disabled (HISTORY=off): messages stay in RAM and are lost on restart." ;; + esac + assert_ircd_yaml "$CONF" || { rm -f "$CONF"; die "Generated ircd.yaml failed its checks (the upstream template layout changed?). Nothing started. Template kept at $TEMPLATE."; } # MOTD: the bind mount hides the image's ergo.motd, and a missing MOTD is silently @@ -499,6 +604,15 @@ else chown -R "$ERGO_UID:$ERGO_GID" "$IRCD_DIR" fi ADMIN_PW="$(head -n1 "$ADMIN_PASS_FILE" 2>/dev/null || echo '')" +# Only put the password on screen for a human at a terminal, and only on the run +# that created it. Unattended paths (cloud-init) send stdout to the provider's +# serial console and /var/log/cloud-init-output.log, where a server-admin oper +# credential would sit indefinitely, readable by anyone with console access. +if [[ "$FIRST_RUN" == "1" && -t 1 ]]; then + ADMIN_PW_LINE="/OPER admin ${ADMIN_PW} (also in ${ADMIN_PASS_FILE})" +else + ADMIN_PW_LINE="see ${ADMIN_PASS_FILE}, or run: ergoctl passwd show" +fi # --------------------------------------------------------------------------- # Confirm @@ -529,6 +643,13 @@ if (( CADDY_CHANGED )) && [[ "$(svc_state caddy)" == running* ]]; then fi log "Waiting for services to become healthy..." +if [[ "$HISTORY" == "postgres" ]]; then + # Ergo refuses to start when its history backend is unreachable, and + # depends_on holds it back until this passes. + if wait_healthy postgres 120; then log "PostgreSQL is ready." + else docker compose logs --tail 20 postgres >&2 || true + die "PostgreSQL did not become healthy; Ergo cannot start without it. See the log above."; fi +fi wait_healthy caddy 60 || warn "Caddy is not healthy yet (docker compose logs caddy)." if ! wait_healthy ergo 120; then docker compose logs --tail 30 ergo >&2 || true @@ -575,12 +696,20 @@ Server: ${ERGO_DOMAIN} port 6697 (TLS)$( [[ "$PLAINTEXT" == "1" ]] && print WebSocket: wss://${ERGO_DOMAIN}/webirc (landing page: https://${ERGO_DOMAIN}) Stack dir: ${STACK_DIR} -Oper login: /OPER admin ${ADMIN_PW} - (stored in ${ADMIN_PASS_FILE}; rotate with: ergoctl passwd rotate) +Oper login: ${ADMIN_PW_LINE} + (rotate with: ergoctl passwd rotate) Config: ${IRCD_DIR}/ircd.yaml MOTD: ${IRCD_DIR}/ergo.motd ergoctl edit / ergoctl motd (validates, then rehashes) +History: $( case "$HISTORY" in + sqlite) printf 'persistent, SQLite (%s/ergo_history.db)' "$IRCD_DIR" ;; + postgres) printf 'persistent, PostgreSQL (container ergo-postgres, db %s)' "$POSTGRES_DB" ;; + off) printf 'in RAM only -- lost on every restart, including updates' ;; + esac ) + $( [[ "$HISTORY" == "off" ]] || printf 'Messages are kept for %s, then deleted (history.restrictions.expire-time).' "$HISTORY_EXPIRE" ) + Switch backend: ergoctl history sqlite|postgres|off + Administer (from anywhere on this host): ergoctl status versions, health, cert, user count ergoctl users who is on @@ -592,8 +721,13 @@ Administer (from anywhere on this host): ergoctl backup stop-consistent backup of config + database TLS: Caddy holds the Let's Encrypt cert; 'update.sh certsync' copies it to - Ergo and rehashes (every 15 min, silent when unchanged). Check with - 'ergoctl cert'. If it says self-signed, DNS/80/443 are not right yet. + Ergo and rehashes (every 15 min, silent when unchanged). + Check with 'ergoctl cert'. While it says SELF-SIGNED, IRC clients will + refuse to connect to 6697 with a certificate error -- that is a pending + issuance, not a broken install. Fix it in this order: + dig +short ${ERGO_DOMAIN} # must return this host's public IP + docker compose logs caddy # shows why ACME failed (80/443 blocked?) + ergoctl cert sync # apply now instead of waiting 15 min Updates: ergoctl update check current vs latest (+ security advisories) ergoctl update update update now (health-checked, DB snapshot, rollback) @@ -602,7 +736,9 @@ Updates: ergoctl update check current vs latest (+ security advisories) || printf "Daily run NOT scheduled (ERGO_AUTOUPDATE=0); enable it with 'ergoctl update install'." ) Restarting Ergo disconnects every user; the daily run warns them first. -Re-running this script is idempotent (it never touches ircd.yaml, secrets or .env). +Re-running this script is idempotent: it never touches ircd.yaml, secrets/ or +caddy/etc/conf.d/, and it adopts the settings above from .env rather than +resetting them to defaults. ================================================================ EOF @@ -611,474 +747,615 @@ EOF # build.sh. exit 0 __ARCHIVE_BELOW__ -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 -9fnr89DP/wY35ASUAGgBAA== +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 +z5+fPz////r8fxIvxe4A4AEA diff --git a/deployments/ergo/docker-compose.postgres.yml b/deployments/ergo/docker-compose.postgres.yml new file mode 100644 index 0000000..f8e3ba8 --- /dev/null +++ b/deployments/ergo/docker-compose.postgres.yml @@ -0,0 +1,64 @@ +# PostgreSQL overlay -- used ONLY when HISTORY=postgres. +# +# deploy.sh activates it by writing +# COMPOSE_FILE=docker-compose.yml:docker-compose.postgres.yml +# into .env, which docker compose reads by itself. So every `docker compose` +# command in this stack (up, ps, stop, logs, exec) sees the same set of services +# with no extra flags -- unlike compose profiles, where a service can be silently +# absent from one command and present in another. +# +# Networking: ergo and caddy run in the HOST namespace, so they cannot use +# compose's service DNS. PostgreSQL therefore stays on a normal bridge network +# and publishes only to the host's loopback; Ergo reaches it at 127.0.0.1:5432 +# (datastore.postgresql.host in ircd.yaml). Nothing is exposed off-box. +# +# The major version is PINNED. PostgreSQL will not start on a data directory +# written by a different major version, so `update.sh` never touches this image. +# Upgrading it is a deliberate dump-and-restore -- see the README ("PostgreSQL"). + +services: + postgres: + image: postgres:${POSTGRES_TAG:-17-alpine} + container_name: ergo-postgres + restart: unless-stopped + # Loopback only. A Docker-published port bypasses the host INPUT firewall, + # so the bind address is the real restriction here. + ports: + - "127.0.0.1:${POSTGRES_PORT:-5432}:5432" + environment: + POSTGRES_USER: "${POSTGRES_USER:-ergo}" + POSTGRES_DB: "${POSTGRES_DB:-ergo_history}" + # The password is read from a file so it never has to live in .env + # (compose interpolates .env, and a '$' in a password would break it). + POSTGRES_PASSWORD_FILE: /run/secrets/postgres-password + # 17 and below keep the classic layout; set it explicitly so a future + # image default cannot move the data directory under us. + PGDATA: /var/lib/postgresql/data/pgdata + volumes: + - postgres-data:/var/lib/postgresql/data + - ./secrets/postgres.pass:/run/secrets/postgres-password:ro + cap_drop: [ALL] + cap_add: + - CHOWN # initdb/entrypoint fix ownership of PGDATA + - DAC_READ_SEARCH + - FOWNER + - SETGID # the entrypoint drops from root to the postgres user + - SETUID + security_opt: [no-new-privileges:true] + healthcheck: + test: ["CMD-SHELL", "pg_isready -U \"${POSTGRES_USER:-ergo}\" -d \"${POSTGRES_DB:-ergo_history}\" -q"] + interval: 15s + timeout: 5s + retries: 5 + start_period: 30s + + # Ergo must not come up before the database is accepting connections: with + # persistent history enabled it fails to start if the backend is unreachable. + # depends_on is orchestration only, so it works across the host/bridge split. + ergo: + depends_on: + postgres: + condition: service_healthy + +volumes: + postgres-data: diff --git a/deployments/ergo/ergoctl b/deployments/ergo/ergoctl index 1b564fe..5334b81 100644 --- a/deployments/ergo/ergoctl +++ b/deployments/ergo/ergoctl @@ -44,6 +44,10 @@ Configuration (validated, then REHASH; reverted on failure) restart | stop | start the ergo container (restart drops every user) caddy-restart apply Caddyfile / conf.d changes debug on|off switch the log level (info <-> debug) + history [sqlite|postgres|off] + show, or switch, the persistent-history backend. + Switching does NOT migrate existing messages and + needs a restart (Ergo fixes the backend at startup). Operators oper list @@ -64,10 +68,14 @@ Moderation (as the admin oper) (replies are printed; control characters stripped) Data - backup [--live] [dir] tar.gz of ircd.yaml, ircd.db, ergo.motd (default: stop Ergo - briefly for a consistent copy; --live = crash-consistent). + backup [--live] [dir] tar.gz of ircd.yaml, ircd.db, ergo.motd and the message + history (SQLite file, or a pg_dump). Stops Ergo briefly + for a consistent copy; --live = crash-consistent. Encrypted with age when $STACK_DIR/age-recipients.txt exists. - restore restore a backup (stops Ergo, restarts, waits for health) + restore restore a backup: stops Ergo, keeps the current config and + database as backups/*.pre-restore., reconciles this + host's credentials, validates, then restarts. + Encrypted backup: AGE_IDENTITY=/path/to/age.key ergoctl restore .age Updates (update.sh) update [check|update|run|certsync|caddy|install|uninstall] @@ -102,6 +110,30 @@ apply_change() { for f in "$@"; do rm -f "$f.last-good"; done return 0 fi + # Ergo opens the history backend ONLY at startup. Unlike MySQL, sqlite and + # postgresql have no "after launching the server" guard, so a rehash that + # turns one on reports success and then silently discards every message. + # Catch that here rather than letting the operator believe it worked. + for f in "$@"; do + [[ "$f" == "${CONF:-}" && -f "$f.last-good" ]] || continue + if [[ "$(history_backend "$f")" != "$(history_backend "$f.last-good")" ]]; then + warn "This change switches the message-history backend, which Ergo only reads at startup." + warn "A rehash would report success and silently do nothing, so a restart is required." + if [[ "${ERGOCTL_ASSUME_YES:-0}" != 1 ]]; then + read -r -p "Restart Ergo now (disconnects every user)? [y/N] " ans + [[ "${ans,,}" == y* ]] || { cat "$f.last-good" > "$f"; die "Reverted; nothing changed. Use 'ergoctl history ' to switch it properly."; } + fi + if ! validate_or_fail "$what"; then cat "$f.last-good" > "$f"; die "$what rejected; previous version restored."; fi + irc_raw --oper --quiet 1 -- 'NOTICE $$* :Server restarting to change message-history storage.' >/dev/null 2>&1 || true + dc up -d --no-deps ergo >/dev/null 2>&1 || dc restart ergo >/dev/null 2>&1 || true + if wait_healthy ergo 120 && irc_probe; then + rm -f "$f.last-good"; log "$what applied (Ergo restarted)."; return 0 + fi + dc logs --tail 30 ergo >&2 || true + cat "$f.last-good" > "$f" + die "Ergo is not healthy after the restart; config reverted. Run 'ergoctl restart'." + fi + done # Take the status from the call itself: `$?` after an `if` whose condition # failed and that has no else branch is 0, which would make the SIGHUP # fallback below unreachable. @@ -156,6 +188,12 @@ cmd_status() { latest="$(normver "$(ergo_latest_tag || true)")" printf 'Ergo: %s | running %s | pinned %s | latest %s\n' "${st:-not created}" "${cur:-?}" "$(ergo_tag)" "${latest:-?}" if [[ "$(env_get PLAINTEXT)" == "1" ]]; then echo " PUBLIC PLAINTEXT LISTENER on :6667 (PLAINTEXT=1)"; fi + local hb; hb="$(history_backend)" + case "$hb" in + postgres) pg_check || true; printf 'History: persistent, PostgreSQL -- %s\n' "$PG_CHECK_MSG" ;; + sqlite) printf 'History: persistent, SQLite (%s)\n' "$IRCD_DIR/$(yaml_get_nested "$CONF" datastore sqlite database-path)" ;; + *) printf 'History: RAM only -- lost on every restart\n' ;; + esac cmd_cert show if ergo_running; then echo; cmd_users || true @@ -278,7 +316,11 @@ cmd_backup() { require_root; require_stack local live=0 dest="$BACKUP_DIR" ts stage out was_running=0 users="" while [[ $# -gt 0 ]]; do case "$1" in --live) live=1 ;; *) dest="$1" ;; esac; shift; done - install -d -m 0700 "$dest" + # `install -d -m` also re-modes an EXISTING directory, so only create-and-mode + # our own default; never change the mode of a directory the operator named + # (`ergoctl backup /var/backups` must not make it 0700 root). + if [[ "$dest" == "$BACKUP_DIR" ]]; then install -d -m 0700 "$dest" + else [[ -d "$dest" ]] || install -d -m 0700 "$dest"; fi # A recipients file means "encrypt these" -- refuse rather than silently # writing account hashes and the oper hash out in the clear. if [[ -f "$STACK_DIR/age-recipients.txt" ]] && ! command -v age >/dev/null 2>&1; then @@ -297,12 +339,37 @@ cmd_backup() { warn "Live backup: ircd.db is copied while Ergo writes to it (crash-consistent; may miss the last second)." fi fi - cp -p "$CONF" "$stage/ircd.yaml" - [[ -f "$IRCD_DIR/ircd.db" ]] && cp -p "$IRCD_DIR/ircd.db" "$stage/ircd.db" - [[ -f "$MOTD" ]] && cp -p "$MOTD" "$stage/ergo.motd" + # -P everywhere: ircd/ is writable by the container uid, so a symlink there + # must be copied as a link, never followed by this root process. + refuse_symlink "$CONF" "ircd/ircd.yaml" || die "Refusing to back up through a symlink." + cp -Pp "$CONF" "$stage/ircd.yaml" + [[ -f "$IRCD_DIR/ircd.db" ]] && cp -Pp "$IRCD_DIR/ircd.db" "$stage/ircd.db" + [[ -f "$MOTD" ]] && cp -Pp "$MOTD" "$stage/ergo.motd" + # Message history, whichever backend holds it. + local backend hf + backend="$(history_backend)" + case "$backend" in + sqlite) + hf="$IRCD_DIR/$(yaml_get_nested "$CONF" datastore sqlite database-path)" + # SQLite writes -wal/-shm siblings; with Ergo stopped they are already + # checkpointed, and on a --live backup we take them along. + for f in "$hf" "$hf-wal" "$hf-shm"; do + [[ -f "$f" ]] && cp -Pp "$f" "$stage/$(basename "$f")" + done ;; + postgres) + if pg_check; then + log "Dumping the PostgreSQL history database..." + # A file copy of a live PGDATA is not a valid backup; pg_dump is. + dc exec -T postgres pg_dump -U "$(env_get POSTGRES_USER)" -d "$(env_get POSTGRES_DB)" \ + > "$stage/history.sql" 2>/dev/null || { rm -f "$stage/history.sql"; warn "pg_dump failed; the backup will NOT contain message history."; } + else + warn "PostgreSQL is not reachable ($PG_CHECK_MSG); the backup will NOT contain message history." + fi ;; + esac cat > "$stage/meta" </dev/null || hostname) CONSISTENT=$(( live == 0 )) @@ -340,13 +407,149 @@ cmd_restore() { log "Stopping Ergo and restoring from $file..." dc stop -t 15 ergo >/dev/null 2>&1 || true install -d -m 0700 "$BACKUP_DIR" - [[ -f "$IRCD_DIR/ircd.db" ]] && cp -p "$IRCD_DIR/ircd.db" "$BACKUP_DIR/ircd.db.pre-restore.$(date -u +%Y%m%d%H%M%S)" - install -m 0600 -o "$(ergo_uid)" -g "$(ergo_gid)" "$stage/ircd.yaml" "$CONF" - install -m 0600 -o "$(ergo_uid)" -g "$(ergo_gid)" "$stage/ircd.db" "$IRCD_DIR/ircd.db" - [[ -f "$stage/ergo.motd" ]] && install -m 0644 -o "$(ergo_uid)" -g "$(ergo_gid)" "$stage/ergo.motd" "$MOTD" + # Keep the CURRENT config and database, not just the database: if the restore + # turns out to be unloadable there has to be a way back. + local ts uid gid + ts="$(date -u +%Y%m%d%H%M%S)"; uid="$(ergo_uid)"; gid="$(ergo_gid)" + [[ -f "$CONF" ]] && cp -Pp "$CONF" "$BACKUP_DIR/ircd.yaml.pre-restore.$ts" + [[ -f "$IRCD_DIR/ircd.db" ]] && cp -Pp "$IRCD_DIR/ircd.db" "$BACKUP_DIR/ircd.db.pre-restore.$ts" + safe_replace "$stage/ircd.yaml" "$CONF" 0600 "$uid" "$gid" + safe_replace "$stage/ircd.db" "$IRCD_DIR/ircd.db" 0600 "$uid" "$gid" + [[ -f "$stage/ergo.motd" ]] && safe_replace "$stage/ergo.motd" "$MOTD" 0644 "$uid" "$gid" + + # The backup carries the ORIGINAL host's credentials. Reconcile them with + # this host's, or the restored server is unreachable and unadministrable. + if [[ "$(history_backend)" == "postgres" && -f "$SECRETS_DIR/postgres.pass" ]]; then + log "Re-pointing datastore.postgresql at this host's database password..." + yaml_set_nested "$CONF" datastore postgresql password "\"$(cat "$SECRETS_DIR/postgres.pass")\"" \ + || warn "Could not update the postgresql password; Ergo may fail to reach its history database." + fi + if [[ -f "$ADMIN_PASS_FILE" ]]; then + local h + if h="$(ergo_genpasswd "$(ergo_image)" "$(head -n1 "$ADMIN_PASS_FILE")")"; then + yaml_oper_set_password "$CONF" admin "$h" \ + && log "Re-hashed this host's admin password into the restored config (ergoctl keeps working)." \ + || warn "Could not update the admin oper hash; 'ergoctl passwd rotate' will fix it." + else + warn "Could not hash the local admin password; the restored config keeps the backup's admin hash. If OPER fails, run 'ergoctl passwd rotate'." + fi + fi + # Message history + local f + for f in "$stage"/ergo_history.db*; do + [[ -f "$f" ]] || continue + install -m 0600 -o "$(ergo_uid)" -g "$(ergo_gid)" "$f" "$IRCD_DIR/$(basename "$f")" + done + if [[ -f "$stage/history.sql" ]]; then + log "Starting PostgreSQL and restoring the history dump..." + dc up -d --no-deps postgres >/dev/null 2>&1 || true + if wait_healthy postgres 120; then + dc exec -T postgres psql -U "$(env_get POSTGRES_USER)" -d "$(env_get POSTGRES_DB)" < "$stage/history.sql" >/dev/null 2>&1 \ + || warn "Restoring the history dump failed; Ergo will start with an empty history." + else + warn "PostgreSQL did not become healthy; message history was NOT restored." + fi + fi rm -rf "$stage" + # Validate before starting, so a bad backup does not cost an outage. + if ! validate_or_fail "the restored config"; then + [[ -f "$BACKUP_DIR/ircd.yaml.pre-restore.$ts" ]] && cp -p "$BACKUP_DIR/ircd.yaml.pre-restore.$ts" "$CONF" + [[ -f "$BACKUP_DIR/ircd.db.pre-restore.$ts" ]] && cp -p "$BACKUP_DIR/ircd.db.pre-restore.$ts" "$IRCD_DIR/ircd.db" + dc up -d --no-deps ergo >/dev/null 2>&1 || true + die "The backup's config does not load; rolled back to what was here before (copies kept as $BACKUP_DIR/*.pre-restore.$ts)." + fi dc up -d --no-deps ergo >/dev/null 2>&1 || true - if wait_healthy ergo 120 && irc_probe; then log "Restored; Ergo is healthy."; else dc logs --tail 20 ergo >&2 || true; die "Ergo is not healthy after the restore."; fi + if wait_healthy ergo 120 && irc_probe; then + log "Restored; Ergo is healthy." + log "Previous state kept at $BACKUP_DIR/{ircd.yaml,ircd.db}.pre-restore.$ts" + else + dc logs --tail 20 ergo >&2 || true + die "Ergo is not healthy after the restore. Roll back with: cp $BACKUP_DIR/ircd.yaml.pre-restore.$ts $CONF && cp $BACKUP_DIR/ircd.db.pre-restore.$ts $IRCD_DIR/ircd.db && ergoctl start" + fi +} + +cmd_history() { + require_stack + local want="${1:-}" cur pw + cur="$(history_backend)" + if [[ -z "$want" ]]; then + echo "History backend: $cur" + case "$cur" in + sqlite) local f="$IRCD_DIR/$(yaml_get_nested "$CONF" datastore sqlite database-path)" + [[ -f "$f" ]] && echo " $f ($(wc -c < "$f") bytes)" || echo " (database not created yet)" ;; + postgres) pg_check && echo " $PG_CHECK_MSG" || echo " $PG_CHECK_MSG" ;; + off) echo " messages are kept in RAM only and lost on every restart" ;; + esac + echo " retention: history.restrictions.expire-time = $(yaml_get_nested "$CONF" history restrictions expire-time)" + return 0 + fi + require_root + case "$want" in sqlite|postgres|off) ;; *) die "Usage: ergoctl history [sqlite|postgres|off]" ;; esac + [[ "$want" != "$cur" ]] || { log "History is already using '$cur'."; return 0; } + # .env and ircd.yaml must never disagree: deploy.sh reconciles them from .env + # on its next run, so a half-applied switch would let it tear down the wrong + # thing. Remember both and put them back on every failure path. + local prev_hist prev_compose + prev_hist="$(env_get HISTORY)"; prev_compose="$(env_get COMPOSE_FILE)" + _hist_revert() { + [[ -f "$CONF.last-good" ]] && cat "$CONF.last-good" > "$CONF" + [[ -n "$prev_hist" ]] && env_set HISTORY "$prev_hist" + [[ -n "$prev_compose" ]] && env_set COMPOSE_FILE "$prev_compose" + return 0 + } + if [[ "$want" == "postgres" ]]; then + [[ -f "$STACK_DIR/docker-compose.postgres.yml" ]] || die "docker-compose.postgres.yml is missing; re-run deploy.sh to install it." + if [[ ! -f "$SECRETS_DIR/postgres.pass" ]]; then + log "Generating the PostgreSQL password..." + ( umask 077; ergo_random_password 32 > "$SECRETS_DIR/postgres.pass" ) + fi + chmod 0600 "$SECRETS_DIR/postgres.pass"; pw="$(cat "$SECRETS_DIR/postgres.pass")" + [[ -n "$(env_get POSTGRES_TAG)" ]] || env_set POSTGRES_TAG 17-alpine + [[ -n "$(env_get POSTGRES_USER)" ]] || env_set POSTGRES_USER ergo + [[ -n "$(env_get POSTGRES_DB)" ]] || env_set POSTGRES_DB ergo_history + [[ -n "$(env_get POSTGRES_PORT)" ]] || env_set POSTGRES_PORT 5432 + env_set COMPOSE_FILE "docker-compose.yml:docker-compose.postgres.yml" + fi + warn "Switching the history backend does NOT migrate existing messages; the old store is left in place." + snapshot_file "$CONF" + # Turn every backend off first, then enable the requested one. + yaml_set_nested "$CONF" datastore sqlite enabled false || true + yaml_set_nested "$CONF" datastore postgresql enabled false || true + case "$want" in + sqlite) yaml_set_nested "$CONF" datastore sqlite enabled true || { cat "$CONF.last-good" > "$CONF"; die "Could not enable datastore.sqlite."; } + yaml_set_nested "$CONF" history persistent enabled true || true ;; + postgres) yaml_set_nested "$CONF" datastore postgresql enabled true || { cat "$CONF.last-good" > "$CONF"; die "Could not enable datastore.postgresql."; } + yaml_set_nested "$CONF" datastore postgresql host "\"127.0.0.1\"" || true + yaml_set_nested "$CONF" datastore postgresql port "$(env_get POSTGRES_PORT)" || true + yaml_set_nested "$CONF" datastore postgresql user "\"$(env_get POSTGRES_USER)\"" || true + yaml_set_nested "$CONF" datastore postgresql password "\"${pw}\"" || true + yaml_set_nested "$CONF" datastore postgresql history-database "\"$(env_get POSTGRES_DB)\"" || true + yaml_set_nested "$CONF" history persistent enabled true || true + log "Starting PostgreSQL..." + dc up -d --no-deps postgres >/dev/null 2>&1 || true + wait_healthy postgres 120 || { _hist_revert; die "PostgreSQL did not become healthy; config reverted."; } ;; + off) yaml_set_nested "$CONF" history persistent enabled false || true ;; + esac + if ! validate_or_fail "history backend $want"; then _hist_revert; die "Config rejected; reverted."; fi + log "Restarting Ergo (the history backend is fixed at startup; every user is disconnected)..." + irc_raw --oper --quiet 1 -- 'NOTICE $$* :Server restarting to change message-history storage.' >/dev/null 2>&1 || true + dc up -d --no-deps ergo >/dev/null 2>&1 || dc restart ergo >/dev/null 2>&1 || true + if wait_healthy ergo 120 && irc_probe; then + # Only now is the switch real, so only now does .env change. + env_set HISTORY "$want" + [[ "$want" == "postgres" ]] || env_set COMPOSE_FILE "docker-compose.yml" + rm -f "$CONF.last-good" + log "History backend is now '$want'." + if [[ "$want" == "off" ]]; then + warn "Existing persisted messages are still on disk; remove them yourself if that is the point." + fi + else + dc logs --tail 30 ergo >&2 || true + _hist_revert + dc up -d --no-deps ergo >/dev/null 2>&1 || true + die "Ergo is not healthy after the switch; config and .env reverted. Check 'ergoctl logs'." + fi + return 0 # never let a trailing false test become this command's exit status } cmd_debug() { @@ -379,6 +582,7 @@ case "${1:-help}" in dc run --rm --no-deps -T caddy caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile >/dev/null || die "Caddyfile invalid (see caddy/etc/Caddyfile, conf.d/)." dc restart caddy; wait_healthy caddy 60 && log "Caddy is healthy." || die "Caddy did not become healthy." ;; debug) shift; cmd_debug "$@" ;; + history) shift; cmd_history "$@" ;; oper) shift; cmd_oper "$@" ;; passwd) shift; cmd_passwd "$@" ;; cmd) shift; cmd_cmd "$@" ;; diff --git a/deployments/ergo/ergolib.sh b/deployments/ergo/ergolib.sh index d35584d..bc8c6da 100644 --- a/deployments/ergo/ergolib.sh +++ b/deployments/ergo/ergolib.sh @@ -79,6 +79,36 @@ fetch() { fi } +# --------------------------------------------------------------------------- +# Writing into ircd/ safely +# +# ircd/ is owned by the unprivileged container uid, but several things that +# write there run as ROOT (the 15-minute cert sync, every ergoctl config edit). +# A plain `cp`, `install` or `>` follows a symlink at the destination, so code +# execution inside Ergo could point one of those writes at any root-owned file +# and win the host. rename(2) is the one operation that REPLACES a symlink +# instead of following it, so every root write into ircd/ goes: create a temp +# under $STACK_DIR (root-owned 0700, same filesystem) -> set mode/owner -> mv. +# --------------------------------------------------------------------------- +# A temp file that an unprivileged uid cannot reach or pre-empt. +stage_tmp() { mktemp "$STACK_DIR/.stage.XXXXXX"; } + +# safe_replace [mode] [uid] [gid] +safe_replace() { + local tmp="$1" target="$2" mode="${3:-0600}" uid="${4:-}" gid="${5:-}" + chmod "$mode" "$tmp" 2>/dev/null || true + [[ -n "$uid" ]] && chown "${uid}:${gid:-$uid}" "$tmp" 2>/dev/null + mv -f "$tmp" "$target" +} + +# Refuse to read a path under ircd/ that the container uid has turned into a +# symlink (a root reader would otherwise copy out whatever it points at). +refuse_symlink() { # [what] + [[ -L "$1" ]] || return 0 + warn "${2:-$1} is a symlink; refusing to touch it as root (see README: Security model)." + return 1 +} + # --------------------------------------------------------------------------- # .env access # --------------------------------------------------------------------------- @@ -95,6 +125,26 @@ env_set() { # : update KEY in .env, or append if absent ergo_domain() { env_get ERGO_DOMAIN | tr 'A-Z' 'a-z' || true; } +# The compose invocation must include the postgres overlay whenever history runs +# on PostgreSQL. deploy.sh writes COMPOSE_FILE into .env, which docker compose +# reads by itself -- these helpers are for code that needs to know. +pg_enabled() { [[ -f "$STACK_DIR/docker-compose.postgres.yml" ]] && grep -q '^COMPOSE_FILE=.*postgres' "$ENV_FILE" 2>/dev/null; } + +# Is PostgreSQL up and accepting our credentials? rc 0 ok, 1 not. +PG_CHECK_MSG="" +pg_check() { + PG_CHECK_MSG="" + if ! pg_enabled; then PG_CHECK_MSG="postgres not in use"; return 0; fi + if [[ "$(svc_state postgres)" != running* ]]; then PG_CHECK_MSG="the postgres container is not running"; return 1; fi + local db user + db="$(env_get POSTGRES_DB)"; user="$(env_get POSTGRES_USER)" + if dc exec -T postgres pg_isready -U "${user:-ergo}" -d "${db:-ergo_history}" >/dev/null 2>&1; then + PG_CHECK_MSG="postgres ready (${db:-ergo_history})"; return 0 + fi + PG_CHECK_MSG="postgres is running but not accepting connections yet" + return 1 +} + # Fill the named variables from .env when they are not already set in the # environment. Used by update.sh/ergoctl so the knobs .env documents really are # the ones the scheduled jobs obey (environment > /etc/ergo-update.conf > .env). @@ -199,7 +249,13 @@ wait_healthy() { # [timeout-seconds] # NICK+USER+OPER is 3 of the 5-line burst, so keep pre-oper payloads short. # --------------------------------------------------------------------------- IRC_LAST="" -_irc_send() { printf '%s\r\n' "$1" >&3 2>/dev/null; } +# Strip CR/LF/NUL before framing: an argument containing a newline (e.g. +# `ergoctl announce "$(cat file)"`) would otherwise terminate this line and make +# the rest a second command, executed with the admin oper's full privileges. +_irc_send() { + local l="${1//$'\r'/}"; l="${l//$'\n'/ }"; l="${l//$'\0'/}" + printf '%s\r\n' "$l" >&3 2>/dev/null +} _irc_cmd() { # echo the command/numeric token of a server line local l="$1" if [[ "$l" == :* ]]; then l="${l#* }"; fi @@ -349,6 +405,15 @@ ergo_validate_config() { # [image] VALIDATE_LOG="$(mktemp)" if ! cp -a "$IRCD_DIR"/. "$tmp"/ 2>>"$VALIDATE_LOG"; then rm -rf "$tmp"; return 1; fi rm -f "$tmp/ircd.lock" + # The validator has no network (so it cannot fight the live server for ports), + # which means it cannot reach a PostgreSQL history backend either. Neutralise + # that one setting in the COPY so everything else -- listeners, opers, MOTD, + # certs, and any pending DB schema upgrade -- is still really validated. + # PostgreSQL reachability is checked separately (see pg_check in update.sh). + if [[ "$(yaml_get_nested "$tmp/ircd.yaml" datastore postgresql enabled)" == "true" ]]; then + yaml_set_nested "$tmp/ircd.yaml" datastore postgresql enabled false || true + yaml_set_nested "$tmp/ircd.yaml" history persistent enabled false || true + fi chown -R "$(ergo_uid):$(ergo_gid)" "$tmp" ergo_helper -v "$tmp:/ircd" -w /ircd --entrypoint /bin/sh -- "$image" \ -c '/ircd-bin/ergo mkcerts >/dev/null 2>&1; exec /ircd-bin/ergo run --smoke' >>"$VALIDATE_LOG" 2>&1 @@ -387,7 +452,7 @@ yaml_oper_set_password() { # top=="opers:" && cur==ENVIRON["NAME"] && /^ password:/ { print " password: \"" ENVIRON["HASH"] "\""; done=1; next } { print } END { exit done ? 0 : 1 }' "$1" > "$tmp" || { rm -f "$tmp"; return 1; } - cat "$tmp" > "$1"; rm -f "$tmp" + safe_replace "$tmp" "$1" 0600 "$(ergo_uid)" "$(ergo_gid)" } yaml_oper_add() { # [whois-line] @@ -403,7 +468,7 @@ yaml_oper_add() { # [whois-line] /^[a-z]/ { if (inopers && !done) { printf "%s\n", ENVIRON["BLOCK"]; done=1 } inopers=($1=="opers:") } { print } END { if (inopers && !done) printf "%s", ENVIRON["BLOCK"] }' "$1" > "$tmp" || { rm -f "$tmp"; return 1; } - cat "$tmp" > "$1"; rm -f "$tmp" + safe_replace "$tmp" "$1" 0600 "$(ergo_uid)" "$(ergo_gid)" yaml_oper_has "$1" "$2" } @@ -415,10 +480,49 @@ yaml_oper_rm() { # top=="opers:" && skip && (/^ / || /^[ \t]*$/) { next } top=="opers:" && skip { skip=0 } { print }' "$1" > "$tmp" || { rm -f "$tmp"; return 1; } - cat "$tmp" > "$1"; rm -f "$tmp" + safe_replace "$tmp" "$1" 0600 "$(ergo_uid)" "$(ergo_gid)" ! yaml_oper_has "$1" "$2" } +# Set a key nested as :/<4-space sub>:/<8-space key>. Only the key inside +# THAT block is rewritten -- datastore's mysql/postgresql/sqlite sub-blocks all +# have an `enabled` key, so a plain sed would hit the wrong one. A key that is +# not present is an error (rc 1), never a silent no-op. +yaml_set_nested() { # + local tmp; tmp="$(mktemp)" + TOP="$2" SUB="$3" KEY="$4" VAL="$5" awk ' + /^[a-z][a-z-]*:/ { t=$0; sub(/:.*/,"",t); cur_top=t; cur_sub="" } + /^ [a-z][a-z0-9-]*:/ { s=$0; sub(/^ /,"",s); sub(/:.*/,"",s); cur_sub=s } + cur_top==ENVIRON["TOP"] && cur_sub==ENVIRON["SUB"] && $0 ~ "^ " ENVIRON["KEY"] ":" { + print " " ENVIRON["KEY"] ": " ENVIRON["VAL"]; done=1; next + } + { print } + END { exit done ? 0 : 1 }' "$1" > "$tmp" || { rm -f "$tmp"; return 1; } + safe_replace "$tmp" "$1" 0600 "$(ergo_uid)" "$(ergo_gid)" +} + +# Read a nested key back (for status/asserts); empty when absent. +yaml_get_nested() { # + TOP="$2" SUB="$3" KEY="$4" awk ' + /^[a-z][a-z-]*:/ { t=$0; sub(/:.*/,"",t); cur_top=t; cur_sub="" } + /^ [a-z][a-z0-9-]*:/ { s=$0; sub(/^ /,"",s); sub(/:.*/,"",s); cur_sub=s } + cur_top==ENVIRON["TOP"] && cur_sub==ENVIRON["SUB"] && $0 ~ "^ " ENVIRON["KEY"] ":" { + sub(/^ [a-z0-9-]+:[ \t]*/,""); gsub(/"/,""); print; exit + }' "$1" +} + +# Which persistent-history backend the deployed ircd.yaml actually uses: +# prints sqlite | postgres | off. Reads the config, not .env, so it reports what +# Ergo is really doing after a hand edit. +history_backend() { # [ircd.yaml] + local f="${1:-$IRCD_DIR/ircd.yaml}" + [[ -f "$f" ]] || { printf 'off'; return 0; } + if [[ "$(yaml_get_nested "$f" history persistent enabled)" != "true" ]]; then printf 'off'; return 0; fi + if [[ "$(yaml_get_nested "$f" datastore postgresql enabled)" == "true" ]]; then printf 'postgres'; return 0; fi + if [[ "$(yaml_get_nested "$f" datastore sqlite enabled)" == "true" ]]; then printf 'sqlite'; return 0; fi + printf 'off' +} + # The first logging block's level (info <-> debug), for `ergoctl debug on|off`. yaml_set_log_level() { # local tmp; tmp="$(mktemp)" @@ -427,7 +531,7 @@ yaml_set_log_level() { # top=="logging:" && !done && /^ level:/ { print " level: " ENVIRON["LEVEL"]; done=1; next } { print } END { exit done ? 0 : 1 }' "$1" > "$tmp" || { rm -f "$tmp"; return 1; } - cat "$tmp" > "$1"; rm -f "$tmp" + safe_replace "$tmp" "$1" 0600 "$(ergo_uid)" "$(ergo_gid)" } # --------------------------------------------------------------------------- @@ -486,16 +590,28 @@ ergo_certsync() { CERTSYNC_MSG="cert/key mismatch in Caddy storage (renewal in progress?); retrying later"; return 0 fi uid="$(ergo_uid)"; gid="$(ergo_gid)" - tmpc="$IRCD_DIR/.fullchain.pem.new"; tmpk="$IRCD_DIR/.privkey.pem.new" + # Stage and keep the rollback copies OUTSIDE ircd/: that directory is + # writable by the container uid, and `install`/`cp` would follow a symlink + # planted there and write as root wherever it points. Only `mv` (rename(2), + # which replaces a symlink rather than following it) touches ircd/. + local prevdir="$STACK_DIR/.certsync" + install -d -m 0700 "$prevdir" || { CERTSYNC_MSG="could not create $prevdir"; return 1; } + tmpc="$prevdir/fullchain.pem.new"; tmpk="$prevdir/privkey.pem.new" if ! install -m 0600 -o "$uid" -g "$gid" "$src_crt" "$tmpc" || ! install -m 0600 -o "$uid" -g "$gid" "$src_key" "$tmpk"; then - rm -f "$tmpc" "$tmpk"; CERTSYNC_MSG="could not stage the new pair in $IRCD_DIR"; return 1 + rm -f "$tmpc" "$tmpk"; CERTSYNC_MSG="could not stage the new pair in $prevdir"; return 1 fi # source must not have moved under us mid-copy if ! cmp -s "$src_crt" "$tmpc" || ! cmp -s "$src_key" "$tmpk"; then rm -f "$tmpc" "$tmpk"; CERTSYNC_MSG="source changed during copy; retrying later"; return 0 fi - [[ -f "$dst_crt" ]] && cp -p "$dst_crt" "$dst_crt.prev" - [[ -f "$dst_key" ]] && cp -p "$dst_key" "$dst_key.prev" + # Roll-back copies, also outside ircd/. Read the live pair only if it is a + # real file -- a symlink there is the container trying to exfiltrate. + rm -f "$prevdir/fullchain.pem.prev" "$prevdir/privkey.pem.prev" + if ! refuse_symlink "$dst_crt" "ircd/fullchain.pem" || ! refuse_symlink "$dst_key" "ircd/privkey.pem"; then + rm -f "$tmpc" "$tmpk"; CERTSYNC_MSG="ircd/ TLS path is a symlink; refusing to sync"; return 1 + fi + [[ -f "$dst_crt" ]] && cp -p "$dst_crt" "$prevdir/fullchain.pem.prev" + [[ -f "$dst_key" ]] && cp -p "$dst_key" "$prevdir/privkey.pem.prev" mv -f "$tmpc" "$dst_crt"; mv -f "$tmpk" "$dst_key" if ergo_running; then dc kill -s HUP ergo >/dev/null 2>&1 || true @@ -508,15 +624,15 @@ ergo_certsync() { if [[ "$got" != "$want" ]]; then # Ergo kept the previous tls config (a failed rehash leaves the old one # active). Put the old pair back so the next start is not fatal. - if [[ -f "$dst_crt.prev" && -f "$dst_key.prev" ]]; then - mv -f "$dst_crt.prev" "$dst_crt"; mv -f "$dst_key.prev" "$dst_key" + if [[ -f "$prevdir/fullchain.pem.prev" && -f "$prevdir/privkey.pem.prev" ]]; then + mv -f "$prevdir/fullchain.pem.prev" "$dst_crt"; mv -f "$prevdir/privkey.pem.prev" "$dst_key" dc kill -s HUP ergo >/dev/null 2>&1 || true fi CERTSYNC_MSG="Ergo did not present the new cert after SIGHUP (see: docker compose logs ergo); previous pair restored" return 1 fi fi - rm -f "$dst_crt.prev" "$dst_key.prev" + rm -f "$prevdir/fullchain.pem.prev" "$prevdir/privkey.pem.prev" CERTSYNC_MSG="installed cert for $(cert_subject_cn "$dst_crt") (expires $(cert_enddate "$dst_crt")) from ${src_crt#"$CADDY_DIR"/data/caddy/certificates/}" return 0 } @@ -541,12 +657,17 @@ send_notice() { # <priority> <body> local title="$1" prio="$2" body="$3" host host="$(hostname -f 2>/dev/null || hostname 2>/dev/null || echo unknown)" set -- -fsS -m 5 -H "X-Title: ${title}" -H "X-Priority: ${prio}" - [[ -n "${NTFY_TOKEN:-}" ]] && set -- "$@" -H "Authorization: Bearer ${NTFY_TOKEN}" [[ -n "${NTFY_EMAIL:-}" ]] && set -- "$@" -H "X-Email: ${NTFY_EMAIL}" local t="ergo"; [[ -n "${NTFY_REGION:-}" ]] && t="${t},${NTFY_REGION}" set -- "$@" -H "X-Tags: ${t}" if [[ "${DRY_RUN:-0}" == "1" ]]; then echo "DRY: curl ntfy ($prio): $body" + elif [[ -n "${NTFY_TOKEN:-}" ]]; then + # The token goes in on stdin, never in argv: /proc/<pid>/cmdline is + # world-readable, and every local process is inside this stack's trust + # boundary already (see README: Security model). + printf 'header = "Authorization: Bearer %s"\n' "$NTFY_TOKEN" \ + | curl "$@" --config - -d "${body} [${host}]" "$NTFY_URL" >/dev/null 2>&1 || true else curl "$@" -d "${body} [${host}]" "$NTFY_URL" >/dev/null 2>&1 || true fi diff --git a/deployments/ergo/update.sh b/deployments/ergo/update.sh index 192c319..a26791d 100644 --- a/deployments/ergo/update.sh +++ b/deployments/ergo/update.sh @@ -224,16 +224,29 @@ apply_update() { # <from x.y.z> <to x.y.z> <reason> ts="$(date -u +%Y%m%d%H%M%S)" install -d -m 0700 "$BACKUP_DIR" snap="$BACKUP_DIR/ircd.db.${from:-unknown}.${ts}" - if [[ -f "$IRCD_DIR/ircd.db" ]]; then - cp -p "$IRCD_DIR/ircd.db" "$snap" && chmod 0600 "$snap" + # -P: never follow a symlink in the uid-owned ircd/ (see ergolib's note). + if [[ -f "$IRCD_DIR/ircd.db" ]] && refuse_symlink "$IRCD_DIR/ircd.db" "ircd/ircd.db"; then + cp -Pp "$IRCD_DIR/ircd.db" "$snap" && chmod 0600 "$snap" else snap="" fi + # The message history is a separate store; snapshot it too so a rollback is + # not silently lossy. + case "$(history_backend)" in + sqlite) [[ -f "$IRCD_DIR/ergo_history.db" ]] && cp -Pp "$IRCD_DIR/ergo_history.db" "$BACKUP_DIR/ergo_history.db.${from:-unknown}.${ts}" 2>/dev/null || true ;; + postgres) dc up -d --no-deps postgres >/dev/null 2>&1 || true + if pg_check; then + dc exec -T postgres pg_dump -U "$(env_get POSTGRES_USER)" -d "$(env_get POSTGRES_DB)" \ + > "$BACKUP_DIR/history.sql.${from:-unknown}.${ts}" 2>/dev/null \ + || { rm -f "$BACKUP_DIR/history.sql.${from:-unknown}.${ts}"; warn "pg_dump failed; the rollback would not restore message history."; } + fi ;; + esac env_set ERGO_TAG "v${to}" dc up -d --no-deps ergo >/dev/null 2>&1 || true if wait_healthy ergo 120 && irc_probe; then log "Ergo ${to} is healthy." + state_rm failed record_template "$new_image" "$from" "$to" prune_snapshots local b="updated ${from:-?} -> ${to} (${reason})" @@ -259,8 +272,13 @@ apply_update() { # <from x.y.z> <to x.y.z> <reason> wait_healthy ergo 90 || true fi fi + prune_snapshots + # Remember the target that failed so the daily run does not repeat this + # whole disruptive cycle (warn users, stop, swap, roll back) every night. + # A manual `ergoctl update update` still forces a retry. + state_set failed "$to" if [[ "$(svc_state ergo)" == "running healthy" ]]; then - send_notice "Ergo update FAILED" "high" "${to} unhealthy (${cause:-see docker compose logs ergo}); rolled back to ${prev_tag}. Snapshot: ${snap:-none}" + send_notice "Ergo update FAILED" "high" "${to} unhealthy (${cause:-see docker compose logs ergo}); rolled back to ${prev_tag}. Snapshot: ${snap:-none}. Will not retry automatically; run 'ergoctl update update' after investigating." else send_notice "Ergo DOWN after failed update" "urgent" "${to} failed (${cause:-?}) and ${prev_tag} did not come back healthy. Snapshot: ${snap:-none}. Run 'ergoctl status' / 'docker compose logs ergo'." fi @@ -287,10 +305,13 @@ record_template() { # <image> <from> <to> log "$TEMPLATE_NOTE" } -prune_snapshots() { # keep the last 5 DB snapshots and .env backups - local f n=0 - for f in $(ls -1t "$BACKUP_DIR"/ircd.db.* 2>/dev/null); do - n=$((n + 1)); (( n > 5 )) && rm -f "$f" +prune_snapshots() { # keep the last 5 of each snapshot kind, and 5 .env backups + local f n=0 pat + for pat in 'ircd.db.*' 'ergo_history.db.*' 'history.sql.*'; do + n=0 + for f in $(ls -1t "$BACKUP_DIR"/$pat 2>/dev/null); do + n=$((n + 1)); (( n > 5 )) && rm -f "$f" + done done n=0 for f in $(ls -1t "$STACK_DIR"/.env.bak.* 2>/dev/null); do @@ -321,7 +342,12 @@ do_certsync() { prev="$(state_get certsync)" case "$rc" in 0) [[ "$CERTSYNC_MSG" == "up to date" ]] || log "certsync: $CERTSYNC_MSG" - if [[ -n "$prev" ]]; then state_rm certsync; send_notice "Ergo TLS recovered" "default" "cert sync ok: ${CERTSYNC_MSG}"; fi ;; + # rc 0 also covers the "renewal in progress, retrying later" cases, + # which synced nothing -- clearing a stored failure on those would + # send a false all-clear while the real problem persists. + if [[ -n "$prev" && ( "$CERTSYNC_MSG" == "up to date" || "$CERTSYNC_MSG" == installed\ cert* ) ]]; then + state_rm certsync; send_notice "Ergo TLS recovered" "default" "cert sync ok: ${CERTSYNC_MSG}" + fi ;; 2) [[ -n "${VERBOSE:-}" ]] && log "certsync: $CERTSYNC_MSG" return 0 ;; *) warn "certsync: $CERTSYNC_MSG" @@ -380,6 +406,17 @@ preflight() { command -v jq >/dev/null 2>&1 || die "jq is required (release metadata); install it." } +# With HISTORY=postgres, Ergo will not start if its history backend is down -- +# so an update must not stop a healthy server until the database is confirmed up. +# The config validator cannot check this: it runs with no network on purpose. +pg_gate() { # <what we are about to do> + pg_enabled || return 0 + pg_check && return 0 + warn "PostgreSQL is not ready ($PG_CHECK_MSG); skipping $1 -- Ergo would not restart." + send_notice "Ergo update skipped" "high" "PostgreSQL not ready (${PG_CHECK_MSG}); ${1} was not attempted." + return 1 +} + do_check() { preflight local cur latest ghsa held="" prio="min" note @@ -412,7 +449,11 @@ do_check() { do_run() { preflight if [[ "$DRY_RUN" != "1" ]]; then - install -d -m 0755 "$(dirname "$LOG")" 2>/dev/null || true + # Create the log directory only when it is missing. `install -d -m` also + # re-modes an EXISTING directory, and this resolves to /var/log, which on + # Debian/Ubuntu ships 0775 root:syslog so rsyslog (running as `syslog`) + # can create files there -- re-moding it to 0755 silently breaks logging. + [[ -d "$(dirname "$LOG")" ]] || install -d -m 0755 "$(dirname "$LOG")" 2>/dev/null || true echo "=== ergo-update $(date -u +%FT%TZ) ===" >> "$LOG" # Scheduled runs have nowhere to send stdout (busybox crond would try to # mail it and these hosts have no MTA), so keep the whole run in the log. @@ -426,7 +467,11 @@ do_run() { resolve_target "$cur" log "current=${cur:-?} | ${REASON}" if [[ -n "$TARGET" && -n "$cur" ]] && ver_gt "$TARGET" "$cur"; then - if [[ "$HOLD" == 1 && "$FORCE_UPDATE" != "1" ]]; then + if [[ "$(state_get failed)" == "$TARGET" && "$FORCE_UPDATE" != "1" ]]; then + log "Skipping ${TARGET}: it already failed here and was rolled back. Investigate, then run 'ergoctl update update' (or set FORCE_UPDATE=1)." + elif ! pg_gate "the update to ${TARGET}"; then + : # postgres is down; pg_gate already warned and notified + elif [[ "$HOLD" == 1 && "$FORCE_UPDATE" != "1" ]]; then warn "HELD: ${TARGET} announces compatibility breaks. Review the release notes, then 'ergoctl update update' (or FORCE_UPDATE=1)." if [[ "$(state_get held)" != "$TARGET" ]]; then state_set held "$TARGET" @@ -459,6 +504,7 @@ do_update() { warn "Release notes between ${cur} and ${TARGET} announce compatibility breaks: ${NOTES}" warn "Proceeding because you asked explicitly (this is 'update update'). Set FORCE_UPDATE=1 to also auto-apply such releases." fi + pg_gate "the update to ${TARGET}" || die "Start PostgreSQL first (docker compose up -d postgres), then retry." apply_update "$cur" "$TARGET" "${REASON} (manual)" }