Files
docker-ep-web-server/scripts/backup-database.py
T

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())