59 lines
1.9 KiB
Python
59 lines
1.9 KiB
Python
#!/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())
|