Files
aipackage/autoinstall/offline/recover-python-profile-on-boot.py

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())