Files
QSafe/server/backup.py
T

37 lines
1.3 KiB
Python

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