Backup & Restore
WAL-aware online backup, validated restore, deliberate-by-design reset.
Since v0.8 every persistent server-side artefact lives in
<data-dir>/state.db. Portunus ships matching backup, restore, and
reset CLI subcommands.
Backup
portunus-server backup --out ~/portunus-backup-$(date +%F).dbUses the SQLite Online Backup API (rusqlite::backup::Backup). The
backup is WAL-aware and consistent without quiescing writers — safe
to run while traffic is flowing.
Refuses to overwrite an existing destination — pick a unique path
(date-stamped is the convention). If --out is an existing directory,
the backup is written as portunus-state-<RFC3339>.db inside it.
Verify:
sqlite3 ~/portunus-backup-2026-05-08.db \
'SELECT version FROM schema_migrations ORDER BY version DESC LIMIT 1;'Cron schedule
# /etc/cron.daily/portunus-backup
#!/bin/sh
set -eu
DEST=/var/backups/portunus/$(date +%F).db
mkdir -p "$(dirname "$DEST")"
sudo -u portunus portunus-server \
--data-dir /var/lib/portunus \
backup --out "$DEST"
find /var/backups/portunus -name '*.db' -mtime +30 -deleteRestore
# 1. Stop the running server.
sudo systemctl stop portunus-server
# 2. Wipe local state.
portunus-server reset --confirm
# 3. Restore.
portunus-server restore --in /tmp/portunus-backup-2026-05-08.db
# 4. Start.
sudo systemctl start portunus-serverrestore:
- Validates the SQLite header magic before copying.
- Refuses to clobber a non-empty data dir without
--force. With--forceit first removesstate.dband the-wal/-shm/.locksidecars, then copies the artefact in. - Runs schema migrations automatically if the binary is newer than the backup's schema.
- On any migration or open failure, rolls back the half-written
state.db(and its sidecars) so the next start does not see a corrupt file. - Refuses if the backup is newer than the binary, exiting
78withevent=startup.schema_version_too_new. Either keep the older backup or restore using the matching binary version.
Reset
# Dry run (no flag) prints what would be removed.
portunus-server reset
# Real wipe.
portunus-server reset --confirmRemoves state.db plus the sidecars state.db-wal, state.db-shm,
and state.db.lock.
The reset CLI refuses to operate on a path whose first 16 bytes
don't start with the SQLite header — protects against typo'd
--data-dir. It does not wipe the generated TLS material
(server.crt / server.key) or an optional server.toml in the same
data-dir. On an empty data-dir reset is a no-op and exits 0.
Cross-host migration
identity.json (v0.5–v0.7) is mode-0600 atomic-write JSON, safe to
rsync across hosts. The state.db file is not — use the
backup / restore flow for clean migration.
Common pitfalls
- NFS / tmpfs / ramfs for
--data-diris rejected at startup withevent=startup.unsupported_filesystem. Use a local writable filesystem. - Two
portunus-server serveagainst the same data-dir exit withevent=startup.store_in_use(exit 75). Clustering is out of scope for the current release. reset --confirmdoes not wipe TLS material orserver.toml— only SQLite state. Clear those files separately if you need to.