108 lines
2.7 KiB
Bash
Executable file
108 lines
2.7 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
|
|
set -Eeuo pipefail
|
|
|
|
ACTIVATE=false
|
|
if [[ "${1:-}" == "--activate" ]]; then
|
|
ACTIVATE=true
|
|
shift
|
|
fi
|
|
if [[ "$#" -ne 0 ]]; then
|
|
echo "Usage: $0 [--activate]" >&2
|
|
exit 2
|
|
fi
|
|
|
|
REMOTE="${BLISS_BACKUP_REMOTE:-root@134.122.14.193}"
|
|
APP_DIR="${BLISS_BACKUP_APP_DIR:-/home/nodejs/bliss2}"
|
|
LOCAL_DIR="${BLISS_BACKUP_LOCAL_DIR:-$(pwd)/backups}"
|
|
STAMP="$(date +%Y%m%d-%H%M%S)"
|
|
ARCHIVE="$LOCAL_DIR/bliss-backup-$STAMP.tar.gz"
|
|
PARTIAL="$ARCHIVE.partial"
|
|
|
|
mkdir -p "$LOCAL_DIR"
|
|
rm -f "$PARTIAL"
|
|
|
|
cleanup() {
|
|
rm -f "$PARTIAL"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
echo "Backing up $REMOTE:$APP_DIR"
|
|
echo "You may be prompted for the droplet passcode."
|
|
|
|
ssh -o ConnectTimeout=15 "$REMOTE" bash -s -- "$APP_DIR" >"$PARTIAL" <<'REMOTE_SCRIPT'
|
|
set -Eeuo pipefail
|
|
|
|
APP_DIR="$1"
|
|
test -d "$APP_DIR/public"
|
|
test -d "$APP_DIR/dbs"
|
|
test -f "$APP_DIR/package.json"
|
|
|
|
TMP_DIR="$(mktemp -d /root/bliss-backup.XXXXXX)"
|
|
cleanup_remote() {
|
|
rm -rf "$TMP_DIR"
|
|
}
|
|
trap cleanup_remote EXIT
|
|
|
|
mkdir -p "$TMP_DIR/dbs"
|
|
cd "$APP_DIR"
|
|
|
|
echo "Creating consistent SQLite snapshots..." >&2
|
|
BACKUP_DIR="$TMP_DIR/dbs" node <<'NODE'
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
const Database = require("better-sqlite3");
|
|
|
|
(async () => {
|
|
const names = fs.readdirSync("dbs").filter(name => name.endsWith(".sqlite"));
|
|
if (names.length === 0) throw new Error("No SQLite databases found in dbs/");
|
|
|
|
for (const name of names) {
|
|
const source = new Database(path.resolve("dbs", name), { readonly: true });
|
|
const destination = path.join(process.env.BACKUP_DIR, name);
|
|
await source.backup(destination);
|
|
source.close();
|
|
|
|
const snapshot = new Database(destination, { readonly: true });
|
|
const result = snapshot.pragma("quick_check", { simple: true });
|
|
snapshot.close();
|
|
if (result !== "ok") throw new Error(`${name}: SQLite quick_check returned ${result}`);
|
|
console.error(` ${name}: OK`);
|
|
}
|
|
})().catch(error => {
|
|
console.error(error);
|
|
process.exit(1);
|
|
});
|
|
NODE
|
|
|
|
echo "Copying static files..." >&2
|
|
cp -a public "$TMP_DIR/public"
|
|
|
|
printf 'source=%s\ncreated_utc=%s\n' \
|
|
"$APP_DIR" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" >"$TMP_DIR/MANIFEST.txt"
|
|
|
|
echo "Streaming compressed archive..." >&2
|
|
tar -C "$TMP_DIR" -czf - MANIFEST.txt public dbs
|
|
REMOTE_SCRIPT
|
|
|
|
echo "Verifying downloaded archive..."
|
|
gzip -t "$PARTIAL"
|
|
tar -tzf "$PARTIAL" >/dev/null
|
|
|
|
DB_COUNT="$(tar -tzf "$PARTIAL" | awk '/^dbs\/[^/]+\.sqlite$/ {count++} END {print count+0}')"
|
|
if [[ "$DB_COUNT" -eq 0 ]]; then
|
|
echo "Backup verification failed: archive contains no SQLite databases" >&2
|
|
exit 1
|
|
fi
|
|
|
|
mv "$PARTIAL" "$ARCHIVE"
|
|
trap - EXIT
|
|
|
|
echo "Backup complete: $ARCHIVE"
|
|
echo "SQLite databases: $DB_COUNT"
|
|
du -h "$ARCHIVE"
|
|
|
|
if [[ "$ACTIVATE" == true ]]; then
|
|
echo
|
|
"$(dirname "$0")/switch-bliss-data.sh" "$ARCHIVE"
|
|
fi
|