Files
MarketingTool/scripts/backup_sqlite.sh
T

41 lines
2.1 KiB
Bash
Raw Normal View History

#!/bin/sh
set -eu
umask 077
DB_PATH=${1:-${PROSPECT_API_DB:-${DATA_DIR:-/data}/prospects.db}}
BACKUP_DIR=${2:-${BACKUP_DIR:-/var/backups/prospect-platform}}
RETENTION=${3:-${BACKUP_RETENTION:-30}}
case "$RETENTION" in ''|*[!0-9]*) echo 'retention must be a non-negative integer' >&2; exit 2;; esac
case "$BACKUP_DIR" in /*) ;; *) echo 'backup directory must be an absolute path' >&2; exit 2;; esac
case "$DB_PATH" in *.env|*secret*|*credentials*) echo 'refusing secret-like database path' >&2; exit 2;; esac
mkdir -p -- "$BACKUP_DIR"
python3 - "$DB_PATH" "$BACKUP_DIR" "$RETENTION" <<'PY'
import hashlib, os, sqlite3, sys, tempfile, time
from pathlib import Path
src, out_dir, retention = Path(sys.argv[1]).expanduser(), Path(sys.argv[2]).expanduser(), int(sys.argv[3])
if not src.is_file() or not src.is_absolute(): raise SystemExit('database must be an existing absolute regular file')
out_dir = out_dir.resolve()
if src.resolve() == out_dir: raise SystemExit('backup directory must differ from database')
out_dir.mkdir(parents=True, exist_ok=True)
name = f"{os.environ.get('BACKUP_PREFIX', 'prospects')}-{time.strftime('%Y%m%dT%H%M%SZ', time.gmtime())}-{os.getpid()}"
final = out_dir / (name + '.db')
fd, temp_name = tempfile.mkstemp(prefix='.backup-', suffix='.tmp', dir=out_dir)
os.close(fd)
try:
with sqlite3.connect(f'file:{src}?mode=ro', uri=True) as source, sqlite3.connect(temp_name) as target:
source.backup(target)
target.execute('PRAGMA integrity_check')
target.commit()
with open(temp_name, 'rb') as handle:
os.fsync(handle.fileno())
os.chmod(temp_name, 0o600); os.replace(temp_name, final)
digest = hashlib.sha256(final.read_bytes()).hexdigest()
sidecar = Path(str(final) + '.sha256')
sidecar.write_text(f'{digest} {final.name}\n', encoding='ascii'); os.chmod(sidecar, 0o600)
candidates = sorted([*out_dir.glob('prospects-*.db'), *out_dir.glob('pre-restore-*.db')], key=lambda p: p.stat().st_mtime, reverse=True)
for old in candidates[retention:]:
old.unlink(missing_ok=True); Path(str(old)+'.sha256').unlink(missing_ok=True)
print(final)
finally:
Path(temp_name).unlink(missing_ok=True)
PY