64 lines
2.5 KiB
Python
64 lines
2.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Create a consistent SQLite backup from the running Docker service."""
|
|
|
|
import argparse
|
|
import subprocess
|
|
import sys
|
|
from datetime import datetime, timedelta, timezone
|
|
from pathlib import Path
|
|
from uuid import uuid4
|
|
|
|
CONTAINER_BACKUP = """import sqlite3
|
|
source = sqlite3.connect('/data/eternity.db')
|
|
destination = sqlite3.connect('/tmp/eternity-backup.db')
|
|
with destination:
|
|
source.backup(destination)
|
|
destination.close()
|
|
source.close()
|
|
"""
|
|
|
|
|
|
def run(command, **kwargs):
|
|
return subprocess.run(command, check=True, text=True, **kwargs)
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Back up the live Eternity Project SQLite database.")
|
|
parser.add_argument("--output-dir", default="backups", help="Directory for timestamped backup files")
|
|
parser.add_argument("--keep-days", type=int, default=30, help="Delete backups older than this many days; use 0 to keep all")
|
|
args = parser.parse_args()
|
|
|
|
output_dir = Path(args.output_dir).resolve()
|
|
output_dir.mkdir(parents=True, exist_ok=True)
|
|
timestamp = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
|
|
destination = output_dir / f"eternity-{timestamp}.db"
|
|
temporary_path = f"/tmp/eternity-backup-{uuid4().hex}.db"
|
|
|
|
try:
|
|
container_id = run(["docker", "compose", "ps", "-q", "eternityproject"], capture_output=True).stdout.strip()
|
|
if not container_id:
|
|
raise RuntimeError("The eternityproject Docker service must be running.")
|
|
run(["docker", "exec", container_id, "python", "-c", CONTAINER_BACKUP.replace("/tmp/eternity-backup.db", temporary_path)])
|
|
run(["docker", "cp", f"{container_id}:{temporary_path}", str(destination)])
|
|
except (OSError, subprocess.CalledProcessError, RuntimeError) as error:
|
|
print(f"DATABASE_BACKUP_FAILED: {error}", file=sys.stderr)
|
|
return 1
|
|
finally:
|
|
if "container_id" in locals() and container_id:
|
|
subprocess.run(["docker", "exec", container_id, "rm", "-f", temporary_path], check=False)
|
|
|
|
print(f"DATABASE_BACKUP_CREATED={destination}")
|
|
if args.keep_days > 0:
|
|
cutoff = datetime.now(timezone.utc) - timedelta(days=args.keep_days)
|
|
removed = 0
|
|
for backup in output_dir.glob("eternity-*.db"):
|
|
if backup != destination and datetime.fromtimestamp(backup.stat().st_mtime, timezone.utc) < cutoff:
|
|
backup.unlink()
|
|
removed += 1
|
|
print(f"DATABASE_BACKUP_RETENTION: removed={removed} keep_days={args.keep_days}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|