#!/bin/sh set -eu umask 077 if [ "${3:-}" != "--confirm-restore" ]; then echo 'Refusing restore: pass --confirm-restore explicitly.' >&2 exit 2 fi SOURCE=${1:-} TARGET=${2:-${PROSPECT_API_DB:-${DATA_DIR:-/data}/prospects.db}} BACKUP_DIR=${BACKUP_DIR:-$(dirname -- "$SOURCE")} case "$SOURCE$TARGET" in *secret*|*credentials*|*.env*) echo 'refusing secret-like path' >&2; exit 2;; esac [ -f "$SOURCE" ] || { echo 'restore source does not exist' >&2; exit 2; } case "$TARGET" in /*) ;; *) echo 'restore target must be an absolute path' >&2; exit 2;; esac mkdir -p -- "$BACKUP_DIR" if [ -f "$TARGET" ]; then pre_restore=$("$(dirname -- "$0")/backup_sqlite.sh" "$TARGET" "$BACKUP_DIR" 30) pre_restore_db=$(printf '%s\n' "$pre_restore" | tail -n 1) pre_restore_name="$BACKUP_DIR/pre-restore-$(date -u +%Y%m%dT%H%M%SZ)-$$.db" mv -- "$pre_restore_db" "$pre_restore_name" if [ -f "$pre_restore_db.sha256" ]; then mv -- "$pre_restore_db.sha256" "$pre_restore_name.sha256"; fi fi python3 - "$SOURCE" "$TARGET" <<'PY' import hashlib, os, sqlite3, sys, tempfile from pathlib import Path source, target = Path(sys.argv[1]).resolve(), Path(sys.argv[2]).resolve() if not source.is_file(): raise SystemExit('restore source must be a regular file') sidecar = Path(str(source)+'.sha256') if sidecar.exists(): expected = sidecar.read_text(encoding='ascii').split()[0] actual = hashlib.sha256(source.read_bytes()).hexdigest() if expected != actual: raise SystemExit('restore checksum mismatch') target.parent.mkdir(parents=True, exist_ok=True) fd, tmp = tempfile.mkstemp(prefix='.restore-', suffix='.tmp', dir=target.parent); os.close(fd) try: with sqlite3.connect(f'file:{source}?mode=ro', uri=True) as src, sqlite3.connect(tmp) as dst: src.backup(dst); result = dst.execute('PRAGMA integrity_check').fetchone()[0] if result != 'ok': raise SystemExit('restored database integrity check failed') dst.commit() os.chmod(tmp, 0o600); os.replace(tmp, target) print(target) finally: Path(tmp).unlink(missing_ok=True) PY