Ergo keeps history in RAM by default, so it is lost on every restart -- including the ones this stack's updater performs. HISTORY now selects a backend at first deploy: sqlite (default, a file beside ircd.db, no extra container), postgres (a pinned container via docker-compose.postgres.yml, loopback-only, POSTGRES_PASSWORD_FILE so no secret lands in .env), or off. Both SQL backends need Ergo 2.18.0+, which deploy.sh enforces. HISTORY_EXPIRE (default 30d) sets retention, because upstream's 1w expire-time DELETES from persistent storage -- persistence with the shipped default would buy only a week. Ergo opens the history backend only at startup and, unlike MySQL, has no "after launching the server" guard for sqlite/postgresql: a rehash that enables one reports success and then silently discards every message. So `ergoctl history` restarts, and `ergoctl edit` detects a backend change and refuses to apply it by rehash. The config validator runs with no network by design, so it neutralises postgresql in its copy and reachability is checked separately. Audit fixes (six lenses over first-deploy, runtime, lifecycle, security, docs): - SECURITY (blocker): ircd/ is writable by the container uid while the 15-minute cert sync and every ergoctl config edit run as root. cp/install/> follow a symlink, so code execution in Ergo could redirect a root write onto any host file. All such writes now stage under $STACK_DIR (0700 root) and land via mv (rename(2) replaces a symlink instead of following it); root reads use cp -P or refuse. Regression-tested with real symlinks. - deploy.sh no longer swaps the image when ERGO_TAG changes on a deployed stack (that bypassed pre-flight, snapshot, user warning and rollback); it points at `TARGET_VERSION=... ergoctl update update`. - restore was lossy and could lock you out: it now snapshots the current config as well as the database, restores message history, re-points the postgres password and re-hashes the local admin password into the restored config, and validates before starting. - `install -d -m` re-modes existing directories: `ergoctl backup /var/backups` no longer chmods it 0700 root, and the updater no longer re-modes /var/log (0775 root:syslog on Debian, which rsyslog needs). - The admin oper password is printed only on a first run at a TTY, so it stays out of cloud-init serial-console logs. - A failed update is remembered, so the daily job stops repeating a disruptive warn/stop/swap/roll-back cycle every night; postgres readiness gates an update that would otherwise stop a healthy server it cannot restart. - certsync no longer sends "TLS recovered" for outcomes that synced nothing. - ergoctl history writes .env only after the restart is healthy, and returns 0. - CR/LF is stripped before IRC framing, so a multi-line argument cannot inject a second command; the ntfy token moves out of curl's argv. - ufw/firewalld are additive, so 6667 is now explicitly revoked when PLAINTEXT=0. - build.sh refuses to build a deploy.sh whose archive is missing a file the script reads -- the failure mode that would have shipped a stack aborting on every host. Docs corrected against the code throughout, including retention, the pre-connect account-registration default, encrypted-restore (AGE_IDENTITY), what a re-run really does to .env, and what the update log does and does not contain. Verified locally: all six suites pass (config render for each backend against the real 2.19.1 template, yaml/oper/version/env helpers, the IRC client against a fake server, and the audit fixes including the symlink escalation). Still not exercised on a Docker host: the containers, ACME issuance, cert sync and PostgreSQL itself. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
91 lines
4.5 KiB
Bash
91 lines
4.5 KiB
Bash
# Copy to .env and fill in. docker compose picks .env up automatically.
|
|
# deploy.sh seeds this file on first run and never rewrites it afterwards.
|
|
#
|
|
# NOTE: values are NOT quoted and must not contain a dollar sign (Compose
|
|
# interpolates .env). Secrets never live here: the admin oper password is in
|
|
# secrets/admin.pass (0600) and only its bcrypt hash is in ircd/ircd.yaml.
|
|
|
|
# --- Identity -----------------------------------------------------------------
|
|
# Public hostname (lower-case). Becomes Ergo's server.name, the Let's Encrypt
|
|
# cert subject, the Caddy site and the allowed websocket origin. Immutable after
|
|
# the first deploy (Ergo refuses to rehash a changed server name).
|
|
ERGO_DOMAIN=irc.example.com
|
|
|
|
# Email for Let's Encrypt registration / expiry notices.
|
|
ACME_EMAIL=admin@example.com
|
|
|
|
# IRC network name (ISUPPORT NETWORK=, welcome line). Letters, digits, . _ -
|
|
# only -- no spaces. deploy.sh defaults it to ERGO_DOMAIN.
|
|
NETWORK_NAME=irc.example.com
|
|
|
|
# --- Image --------------------------------------------------------------------
|
|
# ghcr.io tags carry the leading 'v' (v2.19.1). deploy.sh pins the newest
|
|
# release on first deploy; update.sh bumps this when it updates. Never pin
|
|
# 'stable'/'latest' here -- those are separate builds and defeat rollback.
|
|
ERGO_IMAGE=ghcr.io/ergochat/ergo
|
|
ERGO_TAG=v2.19.1
|
|
|
|
# uid:gid Ergo runs as inside the container = the host's `ergo` system user
|
|
# (created by deploy.sh; owns ./ircd). Filled in by deploy.sh.
|
|
ERGO_UID=
|
|
ERGO_GID=
|
|
|
|
# --- Listeners ----------------------------------------------------------------
|
|
# 0 (default): plaintext 6667 on 127.0.0.1 only (healthcheck + ergoctl).
|
|
# 1: ALSO serve plaintext IRC on :6667 publicly, with STS advertised so capable
|
|
# clients pin TLS. Credentials sent over 6667 cross the internet in clear.
|
|
# Chosen at the FIRST deploy: it selects a listener in ircd/ircd.yaml, which
|
|
# deploy.sh never rewrites. To change it later, edit that listener with
|
|
# 'ergoctl edit', set the value here, 'ergoctl restart', then re-run deploy.sh
|
|
# 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 <backend>'
|
|
# (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
|
|
# /etc/ergo-update.conf, overrides what is set here for that run.)
|
|
# latest - update to the newest release whenever one exists (default)
|
|
# security - update ONLY when a published GitHub security advisory covers the
|
|
# running version, or a release in range has a "### Security"
|
|
# section in its notes
|
|
# off - never change the running version (check/notify only)
|
|
UPDATE_POLICY=latest
|
|
# Releases whose notes announce "Compatibility breaks" are held for review and
|
|
# only notified, unless FORCE_UPDATE=1.
|
|
FORCE_UPDATE=0
|
|
# Seconds of warning (NOTICE to all users) before an update restarts Ergo.
|
|
UPDATE_GRACE=60
|
|
# 1 = the daily run also pulls a newer Caddy image and recreates it (drops every
|
|
# web-client websocket at that moment). 0 = Caddy updates via `ergoctl update caddy`.
|
|
CADDY_AUTOUPDATE=0
|
|
|
|
# --- Image tags ---------------------------------------------------------------
|
|
CADDY_TAG=2-alpine
|