Repository navigation
Backup and Restore
Scheduled, encrypted backups of the database and uploads/, plus the disaster-recovery path to
restore one onto a fresh box.
Backups are encrypted with a passphrase that lives only in a root-only file on the server,
/etc/itflow/backup-passphrase — never on argv, never in the systemd unit itself:
sudo mkdir -p /etc/itflow
sudo bash -c 'openssl rand -base64 48 > /etc/itflow/backup-passphrase'
sudo chmod 600 /etc/itflow/backup-passphrase
sudo chown root:root /etc/itflow/backup-passphraseImmediately copy that passphrase somewhere other than this server — a password manager, a printed copy in a safe, a secrets vault, anything off-box. An encrypted backup whose only decryption passphrase lives on the same disk as the backup provides no real protection if that disk is what's lost, stolen, or ransomwared.
Enable the scheduled timer:
sudo systemctl enable --now itflow-backup.timer
sudo systemctl list-timers itflow-backup.timer # confirm the next scheduled runRun deploy/backup.sh once by hand first to confirm it succeeds and see where it writes output, before
trusting the timer to run it unattended:
sudo deploy/backup.sh --app-dir=<path> --passphrase-file=/etc/itflow/backup-passphraseRuns as root (the final encrypted file is chmod 600, owned root:root — deliberately not
readable by www-data, so a webshell can't read this instance's whole backup history).
Each run produces one file: <dest>/backup-<dbname>-<timestamp>.tar.gz.enc — an
openssl enc -aes-256-cbc -pbkdf2 encrypted tar archive containing:
- the
mysqldumpoutput - a small
backup-manifest.json(installation ID +config_settings_enc_key, if the source instance ever set one — see below for why this travels separately from the SQL dump) uploads/
The application also has its own independent, manual, on-demand backup feature reachable from inside
the app (Settings → Backup → "Download Backup" / "Save to Server") that produces a similar
database-dump-plus-uploads-zip on demand — useful before a risky change, but not encrypted and
not scheduled, so it doesn't replace deploy/backup.sh for actual disaster-recovery purposes. The
two coexist in the same backups/ directory without either deleting the other's files.
deploy/restore.sh decrypts a backup-*.tar.gz.enc archive and overwrites an already-installed
instance's database and uploads/ with what's inside — the "new server, only have my offsite encrypted
backup" path. Run Bare Metal Install's deploy/install.sh first (or use its --restore-from flag
directly, which calls this script for you) to get a fresh, empty instance, then point this at the
backup:
sudo deploy/restore.sh --app-dir=<path> --backup=<path-to-backup.tar.gz.enc> \
--passphrase-file=/etc/itflow/backup-passphrase --confirm-restore-
Pre-restore safety backup of the target's current state before touching anything (mandatory,
or an explicit
--no-pre-restore-backup-confirmedopt-out — skip only if the target has nothing worth keeping). - Decrypt + extract the backup archive.
-
Recover the manifest (
installation_id/config_settings_enc_key) if the archive has one. Older backups predate this and print a warning instead. -
DROP DATABASE+CREATE DATABASE, then import the SQL dump — deliberately not a plain import into whatever's already there.mysqldump's own per-tableDROP TABLE IF EXISTSonly covers tables that exist in the dump; a table that exists in the target but not in an older backup (schema drift between when the backup was taken and the target's currentdb.sql) would otherwise survive untouched. The app's own DB user already holds the grants this needs. -
Replace
uploads/with the backup's (rsync -a --delete), then restore ownership/permissions. -
Apply the recovered
config_settings_enc_keyto the target'sconfig.php, if the manifest had one and it differs from what's already there.
config_settings_enc_key lives only in config.php — never in the database, and therefore never
in the SQL dump. A naive restore (dump + uploads only) onto a fresh instance with its own freshly-blank
config.php would silently corrupt every setting that key protected (SMTP, IMAP, RMM/webhook secrets —
mostly not applicable today, since scripts/setup_cli.php doesn't currently generate one by default,
but the manifest travels defensively in case an instance set one by hand). The manifest is encrypted
inside the same archive as everything else, so it's no less protected than the data it travels with.
Test a restore before you need it for real. A backup you've never restored is a backup you don't
actually have. Point --app-dir at a disposable second instance
(deploy/install.sh --domain=<scratch>.example.com) rather than a production one you care about,
restore into that, verify, then delete it. restore.sh has no special "dry run" mode of its own — the
isolation comes entirely from which --app-dir you point it at.
Credential vault data in the database is encrypted with each user's own zero-knowledge key, derived from their password — not a separate recoverable secret bundled into the SQL dump. As long as the admin account's password is unchanged after a restore, vault data decrypts normally.