#!/usr/bin/env python3 """Publish a secret-free, atomic backup/restore status projection for the Hub UI.""" from __future__ import annotations import argparse import hashlib import json import os from datetime import UTC, datetime from pathlib import Path def publish(output: Path, artifacts: list[Path], *, restore_verified: bool) -> dict[str, object]: if not artifacts or len(artifacts) > 1000: raise ValueError("one to 1000 backup artifacts are required") checksums: list[dict[str, object]] = [] for artifact in artifacts: if artifact.is_symlink(): raise ValueError("backup artifact must be a regular file") resolved = artifact.resolve(strict=True) if not resolved.is_file(): raise ValueError("backup artifact must be a regular file") digest = hashlib.sha256() size = 0 with resolved.open("rb") as handle: while chunk := handle.read(1024 * 1024): size += len(chunk) digest.update(chunk) if size <= 0: raise ValueError("backup artifact must not be empty") checksums.append({"name": resolved.name[:255], "bytes": size, "sha256": digest.hexdigest()}) document: dict[str, object] = { "schemaVersion": "1", "completedAt": datetime.now(UTC).isoformat(), "restoreVerified": restore_verified, "artifactCount": len(checksums), "artifacts": checksums, } output.parent.mkdir(mode=0o700, parents=True, exist_ok=True) temporary = output.with_name(f".{output.name}.tmp") temporary.write_text(json.dumps(document, indent=2, sort_keys=True) + "\n", encoding="utf-8") os.chmod(temporary, 0o600) temporary.replace(output) return document def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--output", type=Path, required=True) parser.add_argument("--artifact", type=Path, action="append", required=True) parser.add_argument("--restore-verified", action="store_true") args = parser.parse_args() document = publish(args.output, args.artifact, restore_verified=args.restore_verified) print( json.dumps( { "artifactCount": document["artifactCount"], "restoreVerified": document["restoreVerified"], "statusFile": str(args.output), }, sort_keys=True, ) ) return 0 if __name__ == "__main__": raise SystemExit(main())