Add database backup and restore
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
#!/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())
|
||||
Reference in New Issue
Block a user