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)" }