#!/usr/bin/env python3 """Recover interrupted Python profile mutation safely after process/reboot restart.""" from __future__ import annotations import argparse import importlib.util import json from pathlib import Path HERE = Path(__file__).resolve().parent def load_module(name: str, filename: str): spec = importlib.util.spec_from_file_location(name, HERE / filename) module = importlib.util.module_from_spec(spec) assert spec.loader spec.loader.exec_module(module) return module installer = load_module("boot_profile_installer", "install-python-profile.py") journal_api = load_module("boot_profile_journal", "atomic-profile-journal.py") def recover(journal: Path, checkpoint: Path, target: Path) -> dict: current = journal_api.read_verified(journal) if current is None: return {"action": "none", "reason": "journal_absent"} state = current["state"] if state in {"installing", "rollback_required"}: result = installer.recover(journal, checkpoint, target) return {"action": "rollback_recovered", **result} if state in {"installed", "rolled_back"}: return {"action": "none", "reason": f"terminal_{state}"} if state in {"profile_selected", "checkpoint_ready"}: return {"action": "none", "reason": f"non_mutating_{state}"} raise ValueError(f"UNKNOWN_JOURNAL_STATE:{state}") def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("--journal", type=Path, required=True) parser.add_argument("--checkpoint", type=Path, required=True) parser.add_argument("--target", type=Path, required=True) args = parser.parse_args() try: result = recover(args.journal, args.checkpoint, args.target) except Exception as error: print(json.dumps({"recovered": False, "code": str(error)}, sort_keys=True)) return 1 print(json.dumps(result, sort_keys=True)) return 0 if __name__ == "__main__": raise SystemExit(main())