44 lines
2.0 KiB
Bash
Executable File
44 lines
2.0 KiB
Bash
Executable File
#!/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
|