Simplify Athena stack and recovery

This commit is contained in:
Mikei386 committed 2026-08-25 22:27:26 +02:00
1 parent c4851305d1
commit 069da8b4f0
70 files changed
+887 -5806

No files matched your search

+23 -50
View File
@@ -29,7 +29,7 @@ from pathlib import Path
from typing import Any
VERSION = "3.0.0"
VERSION = "3.1.0"
STACK = Path(os.environ.get("ATHENA_OPERATOR_STACK", "/opt/mike-ai/stack")).resolve()
REPOSITORY = Path(os.environ.get("ATHENA_OPERATOR_REPOSITORY", str(STACK))).resolve()
STATE = Path(os.environ.get("ATHENA_OPERATOR_STATE", "/data/mike-ai-operator/state")).resolve()
@@ -58,7 +58,7 @@ PROTECTED_CONTAINERS = {
}
ALLOWED_OPERATIONS = {
"file_update", "patch_update", "mcp_release", "run_checks", "compose_deploy", "container_action",
"openwebui_sync", "git_publish", "model_download", "benchmark", "recovery",
"openwebui_sync", "git_publish", "model_download", "benchmark", "backup",
}
HUNK_HEADER = re.compile(r"^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@")
@@ -70,7 +70,7 @@ ALLOWED_CHECKS = {
# runtime environment. Validating without it reports required model/token
# variables as missing even though the deployed stack is valid.
"compose-main": ["docker", "compose", "--env-file", "/etc/mike-ai/stack.env", "-f", "compose.yaml", "config", "-q"],
"compose-mcp": ["docker", "compose", "--env-file", "/etc/mike-ai/stack.env", "-f", "platform/mcp/compose.yaml", "config", "-q"],
"compose-mcp": ["docker", "compose", "--env-file", "/etc/mike-ai/stack.env", "-f", "compose.yaml", "config", "-q"],
}
# Athena is physically remote. The general terminal is intentionally broad,
@@ -253,7 +253,7 @@ def ensure_repository() -> None:
if not (REPOSITORY / ".git").is_dir():
raise RuntimeError(
f"canonical Git checkout is missing at {REPOSITORY}; "
"restore /opt/mike-ai/stack with the documented recovery script"
"restore /opt/mike-ai/stack from Git and run the documented restore command"
)
remote = os.environ.get("ATHENA_OPERATOR_GIT_REMOTE", "").strip()
if remote:
@@ -264,6 +264,10 @@ def ensure_repository() -> None:
def inspect(subject: str, arguments: dict[str, Any]) -> dict[str, Any]:
ensure_repository()
if subject == "guide":
guide = REPOSITORY / "ATHENA.md"
text = guide.read_text(encoding="utf-8", errors="replace")
return {"guide": text, "source": "ATHENA.md", "sha256": sha(guide.read_bytes())}
if subject == "overview":
return {
"version": VERSION,
@@ -448,9 +452,7 @@ def normalise_operation(operation: str, payload: dict[str, Any]) -> tuple[dict[s
checks = payload.get("checks") or ["operator-tests", "compose-mcp"]
if not isinstance(checks, list) or not checks or any(name not in ALLOWED_CHECKS for name in checks):
raise ValueError("unknown release check suite")
compose_file = str(payload.get("compose_file", "platform/mcp/compose.yaml"))
if compose_file != "platform/mcp/compose.yaml":
raise ValueError("MCP releases must use platform/mcp/compose.yaml")
compose_file = "compose.yaml"
services = payload.get("services") or []
if not isinstance(services, list) or not 1 <= len(services) <= 12 or any(not SAFE_NAME.fullmatch(str(x)) for x in services):
raise ValueError("invalid MCP service list")
@@ -461,23 +463,18 @@ def normalise_operation(operation: str, payload: dict[str, Any]) -> tuple[dict[s
selected = [str(safe_relative(str(path))) for path in paths]
if len(set(selected)) != len(selected) or not set(item["path"] for item in files).issubset(set(selected)):
raise ValueError("release paths must be unique and include every patched file")
label = str(payload.get("recovery_label", time.strftime("%Y%m%d-%H%M")))
if not SAFE_NAME.fullmatch(label):
raise ValueError("invalid recovery label")
normal = {
"files": files, "checks": checks, "compose_file": compose_file,
"services": [str(x) for x in services], "build": bool(payload.get("build", True)),
"openwebui_sync": bool(payload.get("openwebui_sync", True)),
"hermes_sync": bool(payload.get("hermes_sync", False)),
"message": message, "paths": selected,
"create_recovery": bool(payload.get("create_recovery", True)), "recovery_label": label,
}
preview = (
f"ONE MCP RELEASE\nServices: {', '.join(normal['services'])}\n"
f"Checks: {', '.join(checks)}\nOpenWebUI sync: {normal['openwebui_sync']}\n"
f"Hermes sync: {normal['hermes_sync']}\n"
f"Selective commit: {message}\nPaths: {', '.join(selected)}\n"
f"Recovery: {normal['create_recovery']} ({label})\n\n"
f"Selective commit: {message}\nPaths: {', '.join(selected)}\n\n"
+ "\n".join(part for part in (import_preview, patch_preview) if part)
)
return normal, preview
@@ -488,7 +485,7 @@ def normalise_operation(operation: str, payload: dict[str, Any]) -> tuple[dict[s
return {"checks": checks}, "Will run: " + ", ".join(checks)
if operation == "compose_deploy":
compose_file = str(payload.get("compose_file", ""))
if compose_file not in {"compose.yaml", "platform/mcp/compose.yaml"}:
if compose_file != "compose.yaml":
raise ValueError("unsupported compose file")
services = payload.get("services") or []
if not isinstance(services, list) or not 1 <= len(services) <= 12 or any(not SAFE_NAME.fullmatch(str(x)) for x in services):
@@ -544,11 +541,8 @@ def normalise_operation(operation: str, payload: dict[str, Any]) -> tuple[dict[s
if not isinstance(args, list) or len(args) > 20 or any(not isinstance(x, str) or len(x) > 200 or re.search(r"[\x00\n\r]", x) for x in args):
raise ValueError("invalid benchmark arguments")
return {"script": str(script), "arguments": args}, f"Run versioned benchmark {script} with {args!r}"
if operation == "recovery":
label = str(payload.get("label", time.strftime("%Y%m%d")))
if not SAFE_NAME.fullmatch(label):
raise ValueError("invalid recovery label")
return {"label": label}, f"Create encrypted recovery bundle and self-contained data kit: {label}"
if operation == "backup":
return {}, "Create one Docker-data backup now in /data/docker-backups."
raise AssertionError(operation)
@@ -631,30 +625,6 @@ def publish_paths(message: str, selected: list[str]) -> dict[str, Any]:
return {"commit": head, "commit_output": commit, "push_output": pushed}
def perform_recovery(label: str) -> dict[str, Any]:
dirty = run(["git", "status", "--porcelain"], cwd=REPOSITORY, check=True)["output"].strip()
if dirty:
raise RuntimeError("publish repository changes before creating a recovery kit")
head = run(["git", "rev-parse", "HEAD"], cwd=REPOSITORY, check=True)["output"].strip()
marker = (STACK / ".mike-ai-source-commit").read_text().strip()
if marker != head:
raise RuntimeError("deployed source marker and operator repository HEAD differ")
encrypted = Path(f"/data/athena-recovery-{label}.tar.age")
source_bundle = Path(f"/data/athena-source-{label}.git.bundle")
release = Path(f"/data/mike-ai-recovery-kit-{label}")
for target in (encrypted, source_bundle, release):
if target.exists():
raise FileExistsError(target)
git_bundle = run(["git", "bundle", "create", str(source_bundle), "--all"], cwd=REPOSITORY, timeout=1800, check=True)
encrypted_result = run([str(STACK / "platform/recovery/create-recovery-bundle.sh"), str(encrypted)], timeout=7200, check=True)
identity = Path("/data/mike-ai-recovery-kit/recovery.agekey")
if not identity.is_file():
raise RuntimeError("existing recovery identity is unavailable")
kit_result = run([str(STACK / "platform/recovery/create-self-contained-data-kit.sh"), str(encrypted), str(identity), str(source_bundle), str(release)], timeout=7200, check=True)
verify = run(["sha256sum", "-c", "SHA256SUMS"], cwd=release, timeout=1800, check=True)
return {"commit": head, "recovery_bundle": str(encrypted), "source_bundle": str(source_bundle), "self_contained_kit": str(release), "git_bundle": git_bundle, "encrypted_bundle": encrypted_result, "kit": kit_result, "verification": verify}
def execute_operation(ticket: str, operation: str, payload: dict[str, Any]) -> dict[str, Any]:
if operation in {"file_update", "patch_update"}:
backup = STATE / "backups" / f"{now()}-{ticket}"
@@ -689,8 +659,7 @@ def execute_operation(ticket: str, operation: str, payload: dict[str, Any]) -> d
hermes_synced = True
publication = publish_paths(payload["message"], payload["paths"])
published = True
recovery = perform_recovery(payload["recovery_label"]) if payload["create_recovery"] else None
return {"changed": changed, "checks": checks, "deploy": deploy, "openwebui_sync": sync, "hermes_sync": hermes_sync, "publication": publication, "recovery": recovery, "containers": run(["docker", "ps", "--format", "{{.Names}}\t{{.Status}}"])}
return {"changed": changed, "checks": checks, "deploy": deploy, "openwebui_sync": sync, "hermes_sync": hermes_sync, "publication": publication, "containers": run(["docker", "ps", "--format", "{{.Names}}\t{{.Status}}"])}
except Exception:
if not published:
restore_files(payload["files"], backup)
@@ -750,10 +719,14 @@ def execute_operation(ticket: str, operation: str, payload: dict[str, Any]) -> d
interpreter = "python3" if script.suffix == ".py" else "bash"
return run([interpreter, str(script), *payload["arguments"]], cwd=REPOSITORY, timeout=86400)
return start_job(ticket, operation, benchmark)
if operation == "recovery":
def recovery():
return perform_recovery(payload["label"])
return start_job(ticket, operation, recovery)
if operation == "backup":
def backup():
result = run(["docker", "exec", "mike-ai-backup", "backup"], timeout=7200, check=True)
latest = Path("/data/docker-backups/athena-latest.tar.gz")
if not latest.is_file():
raise RuntimeError("backup completed without latest archive")
return {"backup": result, "archive": str(latest), "bytes": latest.stat().st_size}
return start_job(ticket, operation, backup)
raise AssertionError(operation)
@@ -783,7 +756,7 @@ def execute(arguments: dict[str, Any]) -> dict[str, Any]:
json_write(completed, record)
path.unlink()
audit("executed", ticket=ticket, operation=record["operation"], binding=record["binding"])
return {"ticket": ticket, "operation": record["operation"], "result": result, "instruction": "Verify health and Git/recovery state before declaring the work complete."}
return {"ticket": ticket, "operation": record["operation"], "result": result, "instruction": "Verify service health and Git state before declaring the work complete."}
except Exception:
record["status"] = "failed"
json_write(path, record)