#!/usr/bin/env python3 """Consistent encrypted SQLite backup, verified and atomically published on a separate filesystem.""" import datetime import os import pathlib import sqlite3 import sys from contextlib import closing os.umask(0o077) source, target = map(pathlib.Path, sys.argv[1:3]) target.mkdir(parents=True, exist_ok=True) stamp = datetime.datetime.now(datetime.timezone.utc).strftime("%Y%m%dT%H%M%S%fZ") temp = target / (stamp + ".tmp") final = target / (stamp + ".sqlite3") try: with closing(sqlite3.connect(f"file:{source}?mode=ro", uri=True)) as src, closing(sqlite3.connect(temp)) as dst: src.backup(dst) dst.execute("PRAGMA journal_mode=DELETE") if dst.execute("PRAGMA integrity_check").fetchone()[0] != "ok": raise RuntimeError("Backup failed integrity check") with temp.open("rb") as f: os.fsync(f.fileno()) temp.replace(final) descriptor = os.open(target, os.O_RDONLY | os.O_DIRECTORY) try: os.fsync(descriptor) finally: os.close(descriptor) total = 0 for index, old in enumerate(sorted(target.glob("*.sqlite3"), reverse=True)): total += old.stat().st_size if index > 0 and (index >= 30 or total > 512 * 1024 * 1024): old.unlink() finally: temp.unlink(missing_ok=True)