precedentai / scripts /storage_maintenance.py
lothnic's picture
Enable private Supabase accounts, saved history and durable budgets
c731c95 verified
Raw History Blame Contribute Delete
3.48 kB
"""Run private application backup/restore and retention outside serving requests."""
from __future__ import annotations
import argparse
import hashlib
import json
import os
from datetime import UTC, datetime
from pathlib import Path
from shared.database import StorageUnavailable
from shared.storage_ops import (
BackupError,
backup,
cleanup,
key_file,
prune_backups,
read_backup,
restore,
)
ROOT = Path(__file__).resolve().parents[1]
DEFAULT_KEY = ROOT / "deploy/.generated/precedent-backup.key"
DEFAULT_BACKUPS = ROOT / "deploy/.generated/storage-backups"
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--key-file", type=Path, default=DEFAULT_KEY)
subcommands = parser.add_subparsers(dest="operation", required=True)
subcommands.add_parser(
"init-key", help="Create a private encryption key; never print or overwrite it."
)
export = subcommands.add_parser(
"backup", help="Encrypt a consistent application-data snapshot."
)
export.add_argument("--output", type=Path)
for name in ("inspect", "restore"):
command = subcommands.add_parser(name)
command.add_argument("--backup", type=Path, required=True)
maintenance = subcommands.add_parser("cleanup")
maintenance.add_argument("--batch", type=int, default=500)
pruning = subcommands.add_parser("prune-backups")
pruning.add_argument("--directory", type=Path, default=DEFAULT_BACKUPS)
args = parser.parse_args()
try:
if args.operation == "init-key":
key_file(args.key_file, create=True)
print(json.dumps({"status": "key_created", "path": str(args.key_file)}))
return
if args.operation == "inspect":
document = read_backup(args.backup, key_file(args.key_file))
report = {
"version": document["version"],
"created_at": document["created_at"],
"rows": {table: len(rows) for table, rows in document["data"].items()},
}
elif args.operation == "prune-backups":
report = prune_backups(args.directory, key_file(args.key_file))
else:
dsn = os.environ.get("LEGAL_AI_QUOTA_DATABASE_URL")
if not dsn:
raise BackupError(
"Set LEGAL_AI_QUOTA_DATABASE_URL privately in the operator environment."
)
if args.operation == "cleanup":
report = cleanup(dsn, batch=args.batch)
elif args.operation == "backup":
destination = args.output or DEFAULT_BACKUPS / datetime.now(UTC).strftime(
"precedent-%Y%m%dT%H%M%S%fZ.pab"
)
report = backup(dsn, destination, key_file(args.key_file))
report.update(
path=str(destination),
sha256=hashlib.sha256(destination.read_bytes()).hexdigest(),
)
else:
report = restore(dsn, args.backup, key_file(args.key_file))
print(json.dumps({"operation": args.operation, **report}))
except (BackupError, StorageUnavailable) as exc:
raise SystemExit(str(exc)) from None
except (OSError, ImportError):
raise SystemExit(
"Storage maintenance failed. Check private file access and the storage/ops extras."
) from None
if __name__ == "__main__":
main()