Fosniedocsv0.1

Backups & DR

Back up Postgres, Qdrant, and storage – and restore safely.

A Fosnie backup captures everything stateful: the database (application data and the audit log), the Qdrant vectors, and the storage directories. Redis is ephemeral and isn't backed up.

StoreContentsMethod
Postgresapp data + audit hash-chainpg_dump -Fc
Qdrantevery collection's vectorssnapshot API
Storagedocuments, workspace, artefacts, exports, branding, skills, promptstar -z

A backup is a timestamped bundle (postgres.dump, per-collection Qdrant snapshots, storage.tar.gz, a manifest, and checksums). Set an age recipient to encrypt the whole bundle.

Schedule backups

Configure /etc/pai/backup.env:

PAI_DB_URL=postgres://pai:PASS@127.0.0.1:5432/pai
PAI_QDRANT_URL=http://127.0.0.1:6333
PAI_STORAGE_DIRS="/var/lib/pai/documents /var/lib/pai/workspace /var/lib/pai/artefacts /var/lib/pai/exports /var/lib/pai/branding /var/lib/pai/skills /var/lib/pai/prompts"
PAI_BACKUP_DIR=/var/backups/pai
PAI_BACKUP_RETAIN=14
# PAI_BACKUP_AGE_RECIPIENT=age1...   # optional encryption

Install the scripts and enable the timer:

install -m0755 deploy/scripts/backup.sh deploy/scripts/restore.sh /opt/pai/deploy/scripts/
cp deploy/systemd/pai-backup.{service,timer} /etc/systemd/system/
systemctl enable --now pai-backup.timer
systemctl start pai-backup.service        # take one now; check with journalctl -u pai-backup

The timer runs daily by default – tighten it to hourly if your tolerance for data loss is smaller. Your recovery-point objective equals the backup interval; sub-hour or zero-loss recovery needs Postgres point-in-time recovery (WAL archiving), which is a separate tier.

Restore

Restore is ordered – Postgres → Qdrant → storage – and the script verifies checksums first and refuses to overwrite a populated database without --force:

# decrypt first if the bundle is encrypted:
#   age -d -i key.txt bundle.tar.age | tar -x
PAI_DB_URL=postgres://pai:PASS@HOST:5432/pai \
PAI_QDRANT_URL=http://HOST:6333 \
  deploy/scripts/restore.sh /var/backups/pai/pai-backup-<timestamp>

Verify the audit chain before cutover. Start the backend on the restored database and export the audit log (Admin → Audit, or GET /api/admin/audit/export). A bad result means the chain didn't survive – restore an earlier known-good bundle rather than going live. Then smoke-test chat, a document, and retrieval. Test-restore into a scratch environment quarterly.

Was this page helpful?

On this page