"""V2R2C1 adapter using the existing V2R1C7 deployment primitives. The service identity and loopback port remain stable for a safe cutover. The new release is installed under its own immutable directory and keeps the previous V2R1C7 release available for an explicit, owner-approved rollback. """ from __future__ import annotations import json import os import shutil import sys from argparse import Namespace from pathlib import Path from typing import Any, Mapping import v2r1c7_production_adapter as base VERSION = "V2R2C1" SERVICE = "aios-v2r1c7-production.service" PORT = 18927 RELEASE_ROOT = Path("/opt/aios/releases") CURRENT_LINK = Path("/opt/aios/v2r2c1") ENV_FILE = Path("/etc/aios/aios-v2r2c1.env") STATE_FILE = Path("/var/lib/aios/v2r2c1/deployment-state.json") UNIT_FILE = Path("/etc/systemd/system/aios-v2r1c7-production.service") ROLLBACK_ROOT = Path("/var/lib/aios/v2r2c1/rollback") REQUIRED_MEMBERS = { "payload/VERSION", "payload/active-runtime/app/deployment_state.py", "payload/active-runtime/app/v2r1c6_privacy_tools.py", "payload/active-runtime/app/v2r2c1_health_service.py", "payload/deploy/aios-v2/systemd/aios-v2r1c7-production.service", "payload/deploy/aios-v2/v2r2c1-runtime-config.json", "payload/deploy/aios-v2/v2r2c1-rollback.json", "payload/scripts/probe_v2r2c1_health.py", "payload/scripts/rollback_v2r2c1_release.sh", "payload/BUILD-METADATA.json", "payload/ARTIFACT-MANIFEST.json", "payload/ROLLBACK-METADATA.json", } def _configure_base() -> None: base.VERSION = VERSION base.SERVICE = SERVICE base.PORT = PORT base.REQUIRED_MEMBERS = REQUIRED_MEMBERS def _args() -> Namespace: return Namespace( release_root=RELEASE_ROOT, current_link=CURRENT_LINK, env_file=ENV_FILE, state_file=STATE_FILE, unit_file=UNIT_FILE, health_timeout=45, ) def load_request() -> dict[str, Any]: _configure_base() value = base.load_request() if value.get("version") != VERSION or value.get("production_service") != SERVICE or int(value.get("production_port", -1)) != PORT: raise ValueError("V2R2C1 production binding mismatch") return value def _backup(path: Path, name: str) -> None: if path.is_file() and not path.is_symlink(): ROLLBACK_ROOT.mkdir(parents=True, exist_ok=True) destination = ROLLBACK_ROOT / name if not destination.exists(): shutil.copy2(path, destination) def execute_request(value: Mapping[str, Any]) -> dict[str, Any]: _configure_base() if value.get("schema") != "aios.production-executor-request.v1": raise ValueError("request_schema_invalid") if value.get("operation") == "apply": _backup(UNIT_FILE, "aios-v2r1c7-production.service.previous") _backup(Path("/etc/aios/aios-v2r1c7.env"), "aios-v2r1c7.env.previous") _backup(Path("/var/lib/aios/v2r1c7/deployment-state.json"), "v2r1c7-deployment-state.previous") return base.install_release(_args(), value) if value.get("operation") == "probe": return base.probe(_args(), value) if value.get("operation") == "rollback": return rollback(value) raise ValueError("unsupported_operation") def rollback(value: Mapping[str, Any]) -> dict[str, Any]: """Explicit rollback only; this adapter never invokes it automatically.""" import subprocess subprocess.run(["systemctl", "stop", SERVICE], check=False, capture_output=True, text=True, timeout=45) previous_unit = ROLLBACK_ROOT / "aios-v2r1c7-production.service.previous" previous_env = ROLLBACK_ROOT / "aios-v2r1c7.env.previous" previous_state = ROLLBACK_ROOT / "v2r1c7-deployment-state.previous" if not previous_unit.is_file() or not previous_env.is_file(): raise RuntimeError("ROLLBACK_RECEIPT_MISSING") UNIT_FILE.parent.mkdir(parents=True, exist_ok=True) ENV_FILE.parent.mkdir(parents=True, exist_ok=True) shutil.copy2(previous_unit, UNIT_FILE) shutil.copy2(previous_env, Path("/etc/aios/aios-v2r1c7.env")) if previous_state.is_file(): Path("/var/lib/aios/v2r1c7").mkdir(parents=True, exist_ok=True) shutil.copy2(previous_state, Path("/var/lib/aios/v2r1c7/deployment-state.json")) subprocess.run(["systemctl", "daemon-reload"], check=True, capture_output=True, text=True, timeout=45) subprocess.run(["systemctl", "restart", SERVICE], check=True, capture_output=True, text=True, timeout=45) result = base.health("http://127.0.0.1:18927/health", { "version": "V2R1C7", "target_oid": str(json.loads(Path("/var/lib/aios/v2r1c7/deployment-state.json").read_text(encoding="utf-8")).get("commit_oid")), "artifact_sha256": str(json.loads(Path("/var/lib/aios/v2r1c7/deployment-state.json").read_text(encoding="utf-8")).get("artifact_sha256")), }) return {"status": "ROLLED_BACK", "rollback_health": result, "automatic_rollback": False} def main() -> int: try: value = load_request() result = execute_request(value) print(json.dumps(result, sort_keys=True)) return 0 except Exception as exc: print(json.dumps({"status": "FAIL_CLOSED", "error": type(exc).__name__, "reason": str(exc)}, sort_keys=True)) return 2 if __name__ == "__main__": raise SystemExit(main())