37 lines
1.8 KiB
Bash
37 lines
1.8 KiB
Bash
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# Creates a consistent SQLite backup through the running web container, then archives Hub uploads.
|
|
# Optional: BACKUP_RETENTION_DAYS=30 BACKUP_OFFSITE_DIR=/mnt/backup/mebbling ./scripts/backup.sh
|
|
root_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
cd "$root_dir"
|
|
stamp="$(date +%Y%m%d-%H%M%S)"
|
|
backup_dir="data/backups/$stamp"
|
|
mkdir -p "$backup_dir"
|
|
|
|
docker compose exec -T -e BACKUP_PATH="/app/data/backups/$stamp/hub.db" web node -e '
|
|
const Database = require("better-sqlite3");
|
|
const db = new Database(process.env.DATABASE_PATH);
|
|
db.backup(process.env.BACKUP_PATH).then(() => db.close()).catch((error) => { console.error(error); process.exit(1); });
|
|
'
|
|
|
|
tar -czf "$backup_dir/uploads.tar.gz" -C public uploads
|
|
|
|
docker compose exec -T -e BACKUP_PATH="/app/data/backups/$stamp/hub.db" web node -e '
|
|
const Database = require("better-sqlite3"); const db = new Database(process.env.BACKUP_PATH, { readonly: true });
|
|
const row = db.prepare("PRAGMA integrity_check").get(); db.close(); if (row.integrity_check !== "ok") { console.error("SQLite integrity check failed"); process.exit(1); }
|
|
'
|
|
tar -tzf "$backup_dir/uploads.tar.gz" >/dev/null
|
|
(cd "$backup_dir" && sha256sum hub.db uploads.tar.gz > SHA256SUMS)
|
|
|
|
if [[ -n "${BACKUP_OFFSITE_DIR:-}" ]]; then
|
|
destination="$BACKUP_OFFSITE_DIR/$stamp"; mkdir -p "$destination"
|
|
cp -a "$backup_dir/." "$destination/"
|
|
printf 'Copied verified backup to: %s\n' "$destination"
|
|
fi
|
|
if [[ -n "${BACKUP_RETENTION_DAYS:-}" ]]; then
|
|
[[ "$BACKUP_RETENTION_DAYS" =~ ^[0-9]+$ ]] || { echo "BACKUP_RETENTION_DAYS must be a non-negative integer" >&2; exit 2; }
|
|
find data/backups -mindepth 1 -maxdepth 1 -type d -mtime "+$BACKUP_RETENTION_DAYS" -exec rm -rf {} +
|
|
fi
|
|
printf 'Created and verified backup: %s\n' "$backup_dir"
|