Harden for production: fsync writes, deployment + backup tooling
All checks were successful
CI / check (push) Successful in 3m55s

The event log is the account/channel authority, so make it durable:
persist() now fsyncs every committed event before reporting success, and
a serialisation failure is an error instead of a silently-dropped blank
line. Add a hardened systemd unit (scripts/echo.service), an atomic
gzip backup script with retention (scripts/backup.sh), and a Deployment
wiki page (install, durability, backup/restore, upgrade, monitoring).
This commit is contained in:
Jean Chevronnet 2026-07-15 11:45:50 +00:00
parent 7d1edefb70
commit e551a01cbf
No known key found for this signature in database
4 changed files with 82 additions and 1 deletions

30
scripts/backup.sh Executable file
View file

@ -0,0 +1,30 @@
#!/usr/bin/env bash
# Back up Echo's event log. The log is append-only, and compaction replaces it
# atomically (temp file + rename), so a plain copy is a consistent snapshot; a
# copy taken mid-append has at most a truncated final line, which Echo skips on
# load. Run from cron or a systemd timer, e.g. hourly.
#
# ECHO_DATA_DIR=/opt/echo ECHO_BACKUP_DIR=/opt/echo/backups ./backup.sh
#
# Restore: stop the service, gunzip a backup over the live log, start again:
# systemctl stop echo
# gunzip -c /opt/echo/backups/echo.db.<stamp>.jsonl.gz > /opt/echo/echo.db.jsonl
# systemctl start echo
set -euo pipefail
DATA_DIR="${ECHO_DATA_DIR:-/opt/echo}"
BACKUP_DIR="${ECHO_BACKUP_DIR:-$DATA_DIR/backups}"
KEEP="${ECHO_BACKUP_KEEP:-48}"
LOG="$DATA_DIR/echo.db.jsonl"
[ -f "$LOG" ] || { echo "no event log at $LOG" >&2; exit 1; }
mkdir -p "$BACKUP_DIR"
stamp=$(date -u +%Y%m%dT%H%M%SZ)
dest="$BACKUP_DIR/echo.db.$stamp.jsonl.gz"
gzip -c "$LOG" > "$dest.tmp"
mv "$dest.tmp" "$dest"
echo "backed up $LOG -> $dest ($(du -h "$dest" | cut -f1))"
# Keep the most recent $KEEP backups.
ls -1t "$BACKUP_DIR"/echo.db.*.jsonl.gz 2>/dev/null | tail -n +"$((KEEP + 1))" | xargs -r rm -f

43
scripts/echo.service Normal file
View file

@ -0,0 +1,43 @@
# Systemd unit for running Echo in production. Copy to /etc/systemd/system/,
# edit the paths and User, then: systemctl daemon-reload && systemctl enable --now echo
#
# Assumes a dedicated system user and a data directory holding config.toml and
# the event log (echo.db.jsonl, written relative to WorkingDirectory):
# useradd --system --home-dir /opt/echo --shell /usr/sbin/nologin echo
# install -o echo -g echo target/release/echo /opt/echo/echo
# install -o echo -g echo config.toml /opt/echo/config.toml
[Unit]
Description=Echo IRC services
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=echo
Group=echo
WorkingDirectory=/opt/echo
ExecStart=/opt/echo/echo /opt/echo/config.toml
Restart=on-failure
RestartSec=5s
# Give in-flight work a moment; the store fsyncs every event so a hard stop
# never loses a committed change.
TimeoutStopSec=15s
# Hardening. Echo needs only the network and its own data directory.
NoNewPrivileges=true
ProtectSystem=full
ProtectHome=true
PrivateTmp=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
RestrictSUIDSGID=true
RestrictRealtime=true
LockPersonality=true
ReadWritePaths=/opt/echo
# If you do NOT run a local sendmail (i.e. [email] uses a network relay like
# msmtp), you can tighten this to ProtectSystem=strict for a read-only root.
[Install]
WantedBy=multi-user.target