#!/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