840 lines
44 KiB
Python
Executable File
840 lines
44 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Root-side executor for the single Athena Operator MCP.
|
|
|
|
The daemon exposes structured platform operations over a local Unix socket.
|
|
It offers both structured, confirmation-bound platform operations and one
|
|
bounded general terminal escape hatch. The latter keeps the platform useful
|
|
for unforeseen work while a small denylist protects remote reachability.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import difflib
|
|
import hashlib
|
|
import json
|
|
import os
|
|
import re
|
|
import shlex
|
|
import shutil
|
|
import socketserver
|
|
import stat
|
|
import subprocess
|
|
import tempfile
|
|
import threading
|
|
import time
|
|
import urllib.parse
|
|
import urllib.request
|
|
import uuid
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
|
|
VERSION = "2.3.4"
|
|
STACK = Path(os.environ.get("ATHENA_OPERATOR_STACK", "/opt/mike-ai/stack")).resolve()
|
|
REPOSITORY = Path(os.environ.get("ATHENA_OPERATOR_REPOSITORY", "/data/mike-ai-operator/repository")).resolve()
|
|
STATE = Path(os.environ.get("ATHENA_OPERATOR_STATE", "/data/mike-ai-operator/state")).resolve()
|
|
SOCKET = Path(os.environ.get("ATHENA_OPERATOR_SOCKET", "/run/mike-ai-operator/operator.sock"))
|
|
MODELS = Path(os.environ.get("ATHENA_OPERATOR_MODELS", "/data/models")).resolve()
|
|
TICKET_TTL = 1800
|
|
MAX_FILE_BYTES = 1_000_000
|
|
MAX_FILES = 24
|
|
MAX_OUTPUT = 30_000
|
|
LOCK = threading.RLock()
|
|
STAGING_ROOTS = tuple(
|
|
Path(value).resolve()
|
|
for value in os.environ.get(
|
|
"ATHENA_OPERATOR_STAGING_ROOTS",
|
|
"/data/mike-ai-operator/staging:/data/hermes/deemix-mcp-build",
|
|
).split(":")
|
|
if value
|
|
)
|
|
|
|
SAFE_PATH = re.compile(r"^[A-Za-z0-9_.+/-]{1,240}$")
|
|
SAFE_NAME = re.compile(r"^[A-Za-z0-9_.-]{1,100}$")
|
|
SAFE_COMMIT = re.compile(r"^[A-Za-z0-9ÄÖÜäöüß _.,:;()+/\-]{5,120}$")
|
|
BLOCKED_PATH_PARTS = {".git", ".ssh", "secrets", "credentials", "authorized_keys"}
|
|
PROTECTED_CONTAINERS = {
|
|
"mike-ai-wireguard-gateway",
|
|
}
|
|
ALLOWED_OPERATIONS = {
|
|
"file_update", "patch_update", "mcp_release", "run_checks", "compose_deploy", "container_action",
|
|
"openwebui_sync", "git_publish", "model_download", "benchmark", "recovery",
|
|
}
|
|
|
|
HUNK_HEADER = re.compile(r"^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@")
|
|
ALLOWED_CHECKS = {
|
|
"operator-tests": ["python3", "dev/test_athena_operator.py"],
|
|
"openwebui-filter-tests": ["python3", "dev/test_openwebui_filters.py"],
|
|
"platform-verify": ["bash", "platform/checks/verify-platform.sh"],
|
|
# Athena's Compose files deliberately require values from the root-only
|
|
# 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"],
|
|
}
|
|
|
|
# Athena is physically remote. The general terminal is intentionally broad,
|
|
# but these operations can strand the machine and therefore remain impossible
|
|
# through the AI operator. This is a reachability guard, not a general command
|
|
# allowlist: ordinary Docker, files, Git, HTTP, package, model and remote-SSH
|
|
# work stays available.
|
|
TERMINAL_BLOCK_PATTERNS = (
|
|
r"(?:^|[;&|()\s])(?:shutdown|poweroff|reboot|halt|kexec)(?:\s|$)",
|
|
r"(?:^|[;&|()\s])init\s+[06](?:\s|$)",
|
|
r"systemctl\s+(?:stop|restart|disable|mask|kill)\s+[^;&|]*(?:ssh|sshd|networking|networkmanager|systemd-networkd|wireguard|wg-quick)",
|
|
r"(?:^|[;&|()\s])(?:iptables|ip6tables|nft|ufw|firewall-cmd)(?:\s|$)",
|
|
r"(?:^|[;&|()\s])ip\s+(?:route|rule|link|addr(?:ess)?)(?:\s|$)",
|
|
r"(?:^|[;&|()\s])(?:nmcli|wg|wg-quick)(?:\s|$)",
|
|
r"(?:^|[;&|()\s])(?:mount|umount|fdisk|sfdisk|cfdisk|parted|mkfs(?:\.[a-z0-9]+)?|wipefs)(?:\s|$)",
|
|
r"(?:^|[;&|()\s])(?:grub-install|update-grub|update-initramfs|modprobe|rmmod|insmod)(?:\s|$)",
|
|
r"/(?:etc/(?:ssh|network|systemd/network|wireguard)|boot|proc/sys)(?:/|\b)",
|
|
r"docker\s+(?:stop|restart|rm|kill)\s+[^;&|]*mike-ai-wireguard-gateway",
|
|
)
|
|
|
|
|
|
def now() -> int:
|
|
return int(time.time())
|
|
|
|
|
|
def compact(text: str) -> str:
|
|
if len(text) <= MAX_OUTPUT:
|
|
return text
|
|
return text[:22_000] + f"\n...[truncated from {len(text)} chars]...\n" + text[-7_000:]
|
|
|
|
|
|
def run(argv: list[str], *, cwd: Path = STACK, timeout: int = 900, check: bool = False) -> dict[str, Any]:
|
|
# The systemd service deliberately exposes /root read-only. Docker Buildx
|
|
# still needs a tiny writable activity/config directory, so keep that
|
|
# disposable state inside the operator's dedicated state tree.
|
|
docker_config = STATE / "docker-config"
|
|
docker_config.mkdir(parents=True, exist_ok=True)
|
|
environment = {
|
|
"PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
|
"LANG": "C.UTF-8",
|
|
"HOME": str(STATE),
|
|
"DOCKER_CONFIG": str(docker_config),
|
|
}
|
|
git_key = Path(os.environ.get("ATHENA_OPERATOR_GIT_SSH_KEY", "/etc/mike-ai/athena-operator-git"))
|
|
known_hosts = Path(os.environ.get("ATHENA_OPERATOR_GIT_KNOWN_HOSTS", "/etc/mike-ai/athena-operator-known-hosts"))
|
|
if git_key.is_file():
|
|
environment["GIT_SSH_COMMAND"] = (
|
|
f"ssh -i {shlex.quote(str(git_key))} -o IdentitiesOnly=yes "
|
|
f"-o UserKnownHostsFile={shlex.quote(str(known_hosts))} "
|
|
"-o StrictHostKeyChecking=yes -o BatchMode=yes -o ConnectTimeout=8"
|
|
)
|
|
completed = subprocess.run(
|
|
argv, cwd=cwd, env=environment,
|
|
stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
|
text=True, errors="replace", timeout=timeout, check=False,
|
|
)
|
|
result = {"argv": argv, "exit_code": completed.returncode, "output": compact(completed.stdout)}
|
|
if check and completed.returncode != 0:
|
|
raise RuntimeError(json.dumps(result, ensure_ascii=False))
|
|
return result
|
|
|
|
|
|
def safe_relative(value: str) -> Path:
|
|
if not SAFE_PATH.fullmatch(value or "") or value.startswith("/"):
|
|
raise ValueError("invalid repository-relative path")
|
|
path = Path(value)
|
|
if ".." in path.parts or any(part.casefold() in BLOCKED_PATH_PARTS for part in path.parts):
|
|
raise PermissionError("protected path")
|
|
resolved = (REPOSITORY / path).resolve(strict=False)
|
|
resolved.relative_to(REPOSITORY)
|
|
return path
|
|
|
|
|
|
def source_file(root: Path, relative: Path) -> Path:
|
|
candidate = (root / relative).resolve(strict=False)
|
|
candidate.relative_to(root)
|
|
return candidate
|
|
|
|
|
|
def sha(data: bytes) -> str:
|
|
return hashlib.sha256(data).hexdigest()
|
|
|
|
|
|
def apply_unified_patch(before: str, patch: str) -> str:
|
|
"""Apply one ordinary unified diff without invoking a shell command."""
|
|
if not patch or len(patch.encode()) > MAX_FILE_BYTES:
|
|
raise ValueError("patch is empty or exceeds limit")
|
|
source = before.splitlines(keepends=True)
|
|
lines = patch.splitlines(keepends=True)
|
|
position = 0
|
|
output: list[str] = []
|
|
index = 0
|
|
if index < len(lines) and lines[index].startswith("--- "):
|
|
index += 1
|
|
if index < len(lines) and lines[index].startswith("+++ "):
|
|
index += 1
|
|
hunks = 0
|
|
while index < len(lines):
|
|
header = lines[index].rstrip("\r\n")
|
|
match = HUNK_HEADER.match(header)
|
|
if not match:
|
|
raise ValueError("patch must contain only standard unified-diff hunks")
|
|
old_start = int(match.group(1))
|
|
old_count = int(match.group(2) or "1")
|
|
new_count = int(match.group(4) or "1")
|
|
target = max(0, old_start - 1)
|
|
if target < position or target > len(source):
|
|
raise RuntimeError("patch hunk is outside the source file")
|
|
output.extend(source[position:target])
|
|
position = target
|
|
consumed = produced = 0
|
|
index += 1
|
|
while index < len(lines) and not lines[index].startswith("@@ "):
|
|
line = lines[index]
|
|
if line.startswith("\\ No newline at end of file"):
|
|
index += 1
|
|
continue
|
|
if not line or line[0] not in " +-":
|
|
raise ValueError("invalid unified-diff line")
|
|
marker, value = line[0], line[1:]
|
|
if marker in " -":
|
|
if position >= len(source) or source[position] != value:
|
|
raise RuntimeError("patch context does not match source")
|
|
if marker == " ":
|
|
output.append(source[position]); produced += 1
|
|
position += 1; consumed += 1
|
|
else:
|
|
output.append(value); produced += 1
|
|
index += 1
|
|
if consumed != old_count or produced != new_count:
|
|
raise RuntimeError("patch hunk line counts do not match its header")
|
|
hunks += 1
|
|
if not hunks:
|
|
raise ValueError("patch contains no hunks")
|
|
output.extend(source[position:])
|
|
return "".join(output)
|
|
|
|
|
|
def normalise_patches(files: Any) -> tuple[list[dict[str, Any]], str]:
|
|
if not isinstance(files, list) or not 1 <= len(files) <= MAX_FILES:
|
|
raise ValueError("files must contain 1..24 patch entries")
|
|
normal: list[dict[str, Any]] = []
|
|
previews: list[str] = []
|
|
for item in files:
|
|
if not isinstance(item, dict):
|
|
raise ValueError("each patch entry must be an object")
|
|
relative = safe_relative(str(item.get("path", "")))
|
|
target = source_file(REPOSITORY, relative)
|
|
before = target.read_text(encoding="utf-8", errors="strict") if target.is_file() else ""
|
|
before_sha = sha(before.encode())
|
|
expected = str(item.get("expected_sha256", ""))
|
|
if expected and expected != before_sha:
|
|
raise RuntimeError(f"source drift for {relative}")
|
|
patch = str(item.get("patch", ""))
|
|
after = apply_unified_patch(before, patch)
|
|
if len(after.encode()) > MAX_FILE_BYTES:
|
|
raise ValueError("patched file exceeds limit")
|
|
normal.append({"path": str(relative), "content": after, "before_sha256": before_sha, "after_sha256": sha(after.encode())})
|
|
previews.append(f"### {relative}\n{compact(patch)}")
|
|
return normal, "\n\n".join(previews)
|
|
|
|
|
|
def json_write(path: Path, value: Any) -> None:
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
temporary = path.with_suffix(path.suffix + ".tmp")
|
|
temporary.write_text(json.dumps(value, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
os.replace(temporary, path)
|
|
|
|
|
|
def audit(event: str, **fields: Any) -> None:
|
|
record = {"time": now(), "event": event, **fields}
|
|
path = STATE / "audit.jsonl"
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
with path.open("a", encoding="utf-8") as handle:
|
|
handle.write(json.dumps(record, ensure_ascii=False, separators=(",", ":")) + "\n")
|
|
|
|
|
|
def ensure_repository() -> None:
|
|
if not (REPOSITORY / ".git").is_dir():
|
|
bundle = Path("/data/mike-ai-recovery-kit/source.git.bundle")
|
|
if not bundle.is_file():
|
|
raise RuntimeError("operator repository missing and no recovery Git bundle is available")
|
|
REPOSITORY.parent.mkdir(parents=True, exist_ok=True)
|
|
run(["git", "clone", str(bundle), str(REPOSITORY)], cwd=Path("/data"), check=True)
|
|
current_file = STACK / ".mike-ai-source-commit"
|
|
current = current_file.read_text().strip() if current_file.is_file() else ""
|
|
if re.fullmatch(r"[0-9a-f]{40}", current):
|
|
run(["git", "switch", "-C", "main", current], cwd=REPOSITORY, check=True)
|
|
remote = os.environ.get("ATHENA_OPERATOR_GIT_REMOTE", "").strip()
|
|
if remote:
|
|
run(["git", "remote", "set-url", "origin", remote], cwd=REPOSITORY, check=True)
|
|
run(["git", "config", "user.name", os.environ.get("ATHENA_OPERATOR_GIT_NAME", "Athena Operator")], cwd=REPOSITORY, check=True)
|
|
run(["git", "config", "user.email", os.environ.get("ATHENA_OPERATOR_GIT_EMAIL", "athena-operator@localhost")], cwd=REPOSITORY, check=True)
|
|
|
|
|
|
def inspect(subject: str, arguments: dict[str, Any]) -> dict[str, Any]:
|
|
ensure_repository()
|
|
if subject == "overview":
|
|
return {
|
|
"version": VERSION,
|
|
"source_commit": (STACK / ".mike-ai-source-commit").read_text().strip(),
|
|
"git": run(["git", "status", "--short", "--branch"], cwd=REPOSITORY),
|
|
"containers": run(["docker", "ps", "--format", "{{.Names}}\t{{.Status}}\t{{.Image}}"]),
|
|
"storage": run(["df", "-h", "/", "/data", str(MODELS)]),
|
|
"gpus": run(["nvidia-smi", "--query-gpu=name,memory.total,memory.used,utilization.gpu", "--format=csv,noheader"]),
|
|
"boundary": "Athena AI-platform operator; no arbitrary shell, shutdown, reboot, SSH/network/firewall/kernel/driver/partition operations",
|
|
}
|
|
if subject == "git_status":
|
|
return {"status": run(["git", "status", "--short", "--branch"], cwd=REPOSITORY), "diff": run(["git", "diff", "--stat"], cwd=REPOSITORY)}
|
|
if subject == "containers":
|
|
return {"containers": run(["docker", "ps", "-a", "--format", "{{.Names}}\t{{.Status}}\t{{.Image}}"])}
|
|
if subject == "models":
|
|
entries = []
|
|
if MODELS.is_dir():
|
|
for path in sorted(MODELS.rglob("*.gguf")):
|
|
entries.append({"path": str(path.relative_to(MODELS)), "bytes": path.stat().st_size})
|
|
return {"models": entries[:500], "count": len(entries)}
|
|
if subject == "jobs":
|
|
jobs = []
|
|
for path in sorted((STATE / "jobs").glob("*.json"), reverse=True)[:30]:
|
|
jobs.append(json.loads(path.read_text()))
|
|
return {"jobs": jobs}
|
|
raise ValueError("unsupported inspection subject")
|
|
|
|
|
|
def read_source(arguments: dict[str, Any]) -> dict[str, Any]:
|
|
ensure_repository()
|
|
relative = safe_relative(str(arguments.get("path", "")))
|
|
target = source_file(REPOSITORY, relative)
|
|
if not target.is_file():
|
|
raise FileNotFoundError("source file not found")
|
|
text = target.read_text(encoding="utf-8", errors="replace")
|
|
start = max(1, int(arguments.get("start_line", 1)))
|
|
count = min(1000, max(1, int(arguments.get("line_count", 300))))
|
|
lines = text.splitlines()
|
|
return {"path": str(relative), "sha256": sha(target.read_bytes()), "start_line": start, "content": "\n".join(lines[start - 1:start - 1 + count]), "total_lines": len(lines)}
|
|
|
|
|
|
def search_source(arguments: dict[str, Any]) -> dict[str, Any]:
|
|
ensure_repository()
|
|
query = str(arguments.get("query", ""))
|
|
if not query or len(query) > 200 or any(x in query for x in ("\x00", "\n", "\r")):
|
|
raise ValueError("invalid query")
|
|
if shutil.which("rg"):
|
|
result = run(["rg", "-n", "--hidden", "--glob", "!.git/**", "--", query, "."], cwd=REPOSITORY, timeout=20)
|
|
return {"query": query, "matches": result["output"], "exit_code": result["exit_code"], "engine": "rg"}
|
|
try:
|
|
pattern = re.compile(query)
|
|
except re.error as exc:
|
|
raise ValueError(f"invalid search expression: {exc}") from exc
|
|
matches: list[str] = []
|
|
for path in sorted(REPOSITORY.rglob("*")):
|
|
if not path.is_file() or ".git" in path.parts or path.stat().st_size > MAX_FILE_BYTES:
|
|
continue
|
|
try:
|
|
lines = path.read_text(encoding="utf-8", errors="strict").splitlines()
|
|
except (UnicodeDecodeError, OSError):
|
|
continue
|
|
relative = path.relative_to(REPOSITORY)
|
|
for number, line in enumerate(lines, 1):
|
|
if pattern.search(line):
|
|
matches.append(f"{relative}:{number}:{line}")
|
|
if len(matches) >= 200:
|
|
return {"query": query, "matches": "\n".join(matches), "exit_code": 0, "engine": "python", "truncated": True}
|
|
return {"query": query, "matches": "\n".join(matches), "exit_code": 0 if matches else 1, "engine": "python", "truncated": False}
|
|
|
|
|
|
def staged_file(item: dict[str, Any]) -> dict[str, Any]:
|
|
source = Path(str(item.get("source", "")))
|
|
if not source.is_absolute() or not source.is_file():
|
|
raise FileNotFoundError("staged source file not found")
|
|
resolved = source.resolve()
|
|
if not any(resolved.is_relative_to(root) for root in STAGING_ROOTS):
|
|
raise PermissionError("staged source is outside approved staging roots")
|
|
raw = resolved.read_bytes()
|
|
if len(raw) > MAX_FILE_BYTES:
|
|
raise ValueError("staged source exceeds limit")
|
|
expected = str(item.get("expected_source_sha256", ""))
|
|
actual = sha(raw)
|
|
if not expected or expected != actual:
|
|
raise RuntimeError("staged source checksum is missing or does not match")
|
|
try:
|
|
content = raw.decode("utf-8", errors="strict")
|
|
except UnicodeDecodeError as exc:
|
|
raise ValueError("staged source must be UTF-8 text") from exc
|
|
relative = safe_relative(str(item.get("path", "")))
|
|
target = source_file(REPOSITORY, relative)
|
|
before = target.read_text(encoding="utf-8", errors="strict") if target.is_file() else ""
|
|
before_sha = sha(before.encode())
|
|
expected_target = str(item.get("expected_target_sha256", ""))
|
|
if expected_target and expected_target != before_sha:
|
|
raise RuntimeError(f"source drift for {relative}")
|
|
return {
|
|
"path": str(relative), "content": content, "before_sha256": before_sha,
|
|
"after_sha256": actual, "staged_source": str(resolved),
|
|
}
|
|
|
|
|
|
def terminal(arguments: dict[str, Any]) -> dict[str, Any]:
|
|
command = str(arguments.get("command", "")).strip()
|
|
if not command or len(command) > 8000 or "\x00" in command:
|
|
raise ValueError("invalid terminal command")
|
|
lowered = command.casefold()
|
|
for pattern in TERMINAL_BLOCK_PATTERNS:
|
|
if re.search(pattern, lowered, flags=re.IGNORECASE):
|
|
raise PermissionError(
|
|
"command blocked because it could break Athena power or remote reachability"
|
|
)
|
|
|
|
cwd_value = str(arguments.get("cwd", str(STACK)))
|
|
cwd = Path(cwd_value)
|
|
if not cwd.is_absolute() or not cwd.is_dir():
|
|
raise ValueError("cwd must be an existing absolute directory")
|
|
timeout = min(3600, max(1, int(arguments.get("timeout_seconds", 300))))
|
|
output_limit = min(30000, max(1000, int(arguments.get("max_output_chars", 12000))))
|
|
result = run(["/bin/bash", "-lc", command], cwd=cwd, timeout=timeout)
|
|
output = result.get("output", "")
|
|
if len(output) > output_limit:
|
|
result["output"] = (
|
|
output[: int(output_limit * 0.72)]
|
|
+ f"\n...[terminal output truncated from {len(output)} chars]...\n"
|
|
+ output[-int(output_limit * 0.25) :]
|
|
)
|
|
result["cwd"] = str(cwd)
|
|
result["reachability_guard"] = "active"
|
|
audit("terminal", command_sha256=sha(command.encode()), cwd=str(cwd), exit_code=result["exit_code"])
|
|
return result
|
|
|
|
|
|
def normalise_operation(operation: str, payload: dict[str, Any]) -> tuple[dict[str, Any], str]:
|
|
if operation not in ALLOWED_OPERATIONS:
|
|
raise ValueError("unsupported operation")
|
|
if not isinstance(payload, dict):
|
|
raise ValueError("payload must be an object")
|
|
if operation == "file_update":
|
|
files = payload.get("files")
|
|
if not isinstance(files, list) or not 1 <= len(files) <= MAX_FILES:
|
|
raise ValueError("files must contain 1..24 entries")
|
|
normal = []
|
|
previews = []
|
|
for item in files:
|
|
relative = safe_relative(str(item.get("path", "")))
|
|
content = str(item.get("content", ""))
|
|
raw = content.encode()
|
|
if len(raw) > MAX_FILE_BYTES:
|
|
raise ValueError("file content exceeds limit")
|
|
target = source_file(REPOSITORY, relative)
|
|
before = target.read_text(encoding="utf-8", errors="replace") if target.is_file() else ""
|
|
expected = str(item.get("expected_sha256", ""))
|
|
before_sha = sha(before.encode())
|
|
if expected and expected != before_sha:
|
|
raise RuntimeError(f"source drift for {relative}")
|
|
diff = "".join(difflib.unified_diff(before.splitlines(True), content.splitlines(True), fromfile=f"a/{relative}", tofile=f"b/{relative}"))
|
|
normal.append({"path": str(relative), "content": content, "before_sha256": before_sha, "after_sha256": sha(raw)})
|
|
previews.append(compact(diff))
|
|
return {"files": normal}, "\n".join(previews)
|
|
if operation == "patch_update":
|
|
files, preview = normalise_patches(payload.get("files"))
|
|
return {"files": files}, preview
|
|
if operation == "mcp_release":
|
|
patch_items = payload.get("files") or []
|
|
import_items = payload.get("imports") or []
|
|
if not isinstance(patch_items, list) or not isinstance(import_items, list):
|
|
raise ValueError("files and imports must be lists")
|
|
if not patch_items and not import_items:
|
|
raise ValueError("mcp_release requires at least one patch or staged import")
|
|
files, patch_preview = normalise_patches(patch_items) if patch_items else ([], "")
|
|
imports = [staged_file(item) for item in import_items]
|
|
files.extend(imports)
|
|
import_preview = "\n".join(
|
|
f"IMPORT {item['staged_source']} -> {item['path']} sha256={item['after_sha256']}"
|
|
for item in imports
|
|
)
|
|
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")
|
|
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")
|
|
message = str(payload.get("message", ""))
|
|
if not SAFE_COMMIT.fullmatch(message):
|
|
raise ValueError("invalid commit message")
|
|
paths = payload.get("paths") or [item["path"] for item in files]
|
|
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"
|
|
+ "\n".join(part for part in (import_preview, patch_preview) if part)
|
|
)
|
|
return normal, preview
|
|
if operation == "run_checks":
|
|
checks = payload.get("checks") or []
|
|
if not isinstance(checks, list) or not checks or any(name not in ALLOWED_CHECKS for name in checks):
|
|
raise ValueError("unknown check suite")
|
|
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"}:
|
|
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):
|
|
raise ValueError("invalid compose service list")
|
|
return {"compose_file": compose_file, "services": services, "build": bool(payload.get("build", True))}, f"docker compose -f {compose_file} up -d {'--build ' if payload.get('build', True) else ''}{' '.join(services)}"
|
|
if operation == "container_action":
|
|
action = str(payload.get("action", ""))
|
|
containers = payload.get("containers") or []
|
|
if action not in {"start", "stop", "restart"}:
|
|
raise ValueError("invalid container action")
|
|
if not isinstance(containers, list) or not 1 <= len(containers) <= 12:
|
|
raise ValueError("invalid container list")
|
|
for name in containers:
|
|
if not SAFE_NAME.fullmatch(str(name)) or not str(name).startswith("mike-ai-") or name in PROTECTED_CONTAINERS:
|
|
raise PermissionError(f"container is outside operator boundary: {name}")
|
|
return {"action": action, "containers": containers}, f"docker {action} {' '.join(containers)}"
|
|
if operation == "openwebui_sync":
|
|
return {}, (
|
|
"Synchronise the versioned Open WebUI model profiles, filters, tool connections "
|
|
"and system prompt with the running Open WebUI instance."
|
|
)
|
|
if operation == "git_publish":
|
|
message = str(payload.get("message", ""))
|
|
if not SAFE_COMMIT.fullmatch(message):
|
|
raise ValueError("invalid commit message")
|
|
paths = payload.get("paths")
|
|
if not isinstance(paths, list) or not 1 <= len(paths) <= MAX_FILES:
|
|
raise ValueError("paths must contain 1..24 repository files")
|
|
selected = [str(safe_relative(str(path))) for path in paths]
|
|
if len(set(selected)) != len(selected):
|
|
raise ValueError("paths must be unique")
|
|
status = run(["git", "status", "--short", "--", *selected], cwd=REPOSITORY, check=True)["output"].strip()
|
|
if not status:
|
|
raise RuntimeError("selected repository paths have no changes to publish")
|
|
diff = run(["git", "diff", "--stat", "--", *selected], cwd=REPOSITORY, check=True)["output"]
|
|
preview = f"Commit message: {message}\nSelected paths: {', '.join(selected)}\n\nSelected changes:\n{status}\n\nDiff summary:\n{diff}"
|
|
return {"message": message, "paths": selected, "reviewed_status": status}, preview
|
|
if operation == "model_download":
|
|
url = str(payload.get("url", ""))
|
|
parsed = urllib.parse.urlparse(url)
|
|
if parsed.scheme != "https" or parsed.hostname not in {"huggingface.co", "cdn-lfs.huggingface.co", "hf.co"}:
|
|
raise PermissionError("only HTTPS Hugging Face downloads are allowed")
|
|
destination = safe_relative(str(payload.get("destination", "")))
|
|
expected = str(payload.get("sha256", ""))
|
|
if expected and not re.fullmatch(r"[0-9a-f]{64}", expected):
|
|
raise ValueError("invalid sha256")
|
|
return {"url": url, "destination": str(destination), "sha256": expected}, f"Download {url} to models/{destination}"
|
|
if operation == "benchmark":
|
|
script = safe_relative(str(payload.get("script", "")))
|
|
if not (str(script).startswith("dev/") or str(script).startswith("platform/bench")) or script.suffix not in {".py", ".sh"}:
|
|
raise PermissionError("benchmark script must be versioned below dev/ or platform/bench*")
|
|
args = payload.get("arguments") or []
|
|
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}"
|
|
raise AssertionError(operation)
|
|
|
|
|
|
def prepare(arguments: dict[str, Any]) -> dict[str, Any]:
|
|
ensure_repository()
|
|
operation = str(arguments.get("operation", ""))
|
|
payload, preview = normalise_operation(operation, arguments.get("payload") or {})
|
|
ticket_id = uuid.uuid4().hex
|
|
record = {"id": ticket_id, "operation": operation, "payload": payload, "preview": preview, "created": now(), "expires": now() + TICKET_TTL, "status": "pending"}
|
|
record["binding"] = sha(json.dumps({"operation": operation, "payload": payload}, sort_keys=True, ensure_ascii=False).encode())
|
|
json_write(STATE / "pending" / f"{ticket_id}.json", record)
|
|
audit("prepared", ticket=ticket_id, operation=operation, binding=record["binding"])
|
|
return {"ticket": ticket_id, "operation": operation, "binding": record["binding"], "preview": preview, "expires_in_seconds": TICKET_TTL, "required_confirmation": f"EXECUTE {ticket_id}", "instruction": "Show the full preview to the user and wait for explicit confirmation in a later message."}
|
|
|
|
|
|
def sync_file(relative: Path, content: str, backup_root: Path) -> None:
|
|
for root in (REPOSITORY, STACK):
|
|
target = source_file(root, relative)
|
|
target_mode = stat.S_IMODE(target.stat().st_mode) if target.exists() else 0o644
|
|
if target.exists():
|
|
backup = backup_root / root.name / relative
|
|
backup.parent.mkdir(parents=True, exist_ok=True)
|
|
shutil.copy2(target, backup)
|
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
fd, temp_name = tempfile.mkstemp(prefix=f".{target.name}.", dir=target.parent)
|
|
with os.fdopen(fd, "w", encoding="utf-8") as handle:
|
|
handle.write(content)
|
|
os.chmod(temp_name, target_mode)
|
|
os.replace(temp_name, target)
|
|
|
|
|
|
def start_job(ticket: str, operation: str, worker) -> dict[str, Any]:
|
|
job_id = uuid.uuid4().hex
|
|
job_path = STATE / "jobs" / f"{job_id}.json"
|
|
json_write(job_path, {"id": job_id, "ticket": ticket, "operation": operation, "status": "running", "started": now()})
|
|
def wrapped():
|
|
try:
|
|
result = worker()
|
|
json_write(job_path, {"id": job_id, "ticket": ticket, "operation": operation, "status": "completed", "started": json.loads(job_path.read_text())["started"], "finished": now(), "result": result})
|
|
audit("job_completed", job=job_id, operation=operation)
|
|
except Exception as exc:
|
|
json_write(job_path, {"id": job_id, "ticket": ticket, "operation": operation, "status": "failed", "finished": now(), "error": compact(str(exc))})
|
|
audit("job_failed", job=job_id, operation=operation)
|
|
threading.Thread(target=wrapped, daemon=True).start()
|
|
return {"job_id": job_id, "status": "running", "instruction": "Poll athena_operator_job until completed or failed."}
|
|
|
|
|
|
def apply_files(files: list[dict[str, Any]], backup: Path) -> list[str]:
|
|
for item in files:
|
|
relative = safe_relative(item["path"])
|
|
current = source_file(REPOSITORY, relative)
|
|
current_sha = sha(current.read_bytes()) if current.is_file() else sha(b"")
|
|
if current_sha != item["before_sha256"]:
|
|
raise RuntimeError(f"source drift after preview: {relative}")
|
|
for item in files:
|
|
sync_file(safe_relative(item["path"]), item["content"], backup)
|
|
return [item["path"] for item in files]
|
|
|
|
|
|
def restore_files(files: list[dict[str, Any]], backup: Path) -> None:
|
|
for item in files:
|
|
relative = safe_relative(item["path"])
|
|
for root in (REPOSITORY, STACK):
|
|
target = source_file(root, relative)
|
|
saved = backup / root.name / relative
|
|
if saved.is_file():
|
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
shutil.copy2(saved, target)
|
|
elif target.exists():
|
|
target.unlink()
|
|
|
|
|
|
def publish_paths(message: str, selected: list[str]) -> dict[str, Any]:
|
|
run(["git", "add", "--", *selected], cwd=REPOSITORY, check=True)
|
|
run(["git", "diff", "--cached", "--check", "--", *selected], cwd=REPOSITORY, check=True)
|
|
commit = run(["git", "commit", "-m", message, "--", *selected], cwd=REPOSITORY, check=True)
|
|
pushed = run(["git", "push", "origin", "HEAD:main"], cwd=REPOSITORY, timeout=300, check=True)
|
|
head = run(["git", "rev-parse", "HEAD"], cwd=REPOSITORY, check=True)["output"].strip()
|
|
(STACK / ".mike-ai-source-commit").write_text(head + "\n")
|
|
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}"
|
|
changed = apply_files(payload["files"], backup)
|
|
return {"changed": changed, "backup": str(backup), "git_diff": run(["git", "diff", "--stat"], cwd=REPOSITORY)}
|
|
if operation == "mcp_release":
|
|
def release():
|
|
backup = STATE / "backups" / f"{now()}-{ticket}"
|
|
published = False
|
|
deployed = False
|
|
synced = False
|
|
hermes_synced = False
|
|
try:
|
|
changed = apply_files(payload["files"], backup)
|
|
checks = []
|
|
for name in payload["checks"]:
|
|
result = run(ALLOWED_CHECKS[name], cwd=REPOSITORY, timeout=1200, check=True)
|
|
checks.append({"name": name, **result})
|
|
run(["docker", "compose", "-f", payload["compose_file"], "config", "-q"], cwd=STACK, check=True)
|
|
argv = ["docker", "compose", "-f", payload["compose_file"], "up", "-d"]
|
|
if payload["build"]: argv.append("--build")
|
|
argv.extend(payload["services"])
|
|
deploy = run(argv, cwd=STACK, timeout=3600, check=True)
|
|
deployed = True
|
|
sync = None
|
|
if payload["openwebui_sync"]:
|
|
sync = run(["bash", str(STACK / "platform/openwebui/install-filters.sh")], cwd=STACK, timeout=1800, check=True)
|
|
synced = True
|
|
hermes_sync = None
|
|
if payload["hermes_sync"]:
|
|
hermes_sync = run(["bash", str(STACK / "platform/hermes/install-hermes.sh")], cwd=STACK, timeout=1800, check=True)
|
|
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}}"])}
|
|
except Exception:
|
|
if not published:
|
|
restore_files(payload["files"], backup)
|
|
run(["git", "reset", "--", *payload["paths"]], cwd=REPOSITORY)
|
|
if deployed:
|
|
rollback_argv = ["docker", "compose", "-f", payload["compose_file"], "up", "-d", "--build", *payload["services"]]
|
|
run(rollback_argv, cwd=STACK, timeout=3600)
|
|
if synced:
|
|
run(["bash", str(STACK / "platform/openwebui/install-filters.sh")], cwd=STACK, timeout=1800)
|
|
if hermes_synced:
|
|
run(["bash", str(STACK / "platform/hermes/install-hermes.sh")], cwd=STACK, timeout=1800)
|
|
raise
|
|
return start_job(ticket, operation, release)
|
|
if operation == "run_checks":
|
|
return {"checks": [{"name": name, **run(ALLOWED_CHECKS[name], cwd=REPOSITORY, timeout=1200)} for name in payload["checks"]]}
|
|
if operation == "compose_deploy":
|
|
compose = payload["compose_file"]
|
|
run(["docker", "compose", "-f", compose, "config", "-q"], cwd=STACK, check=True)
|
|
argv = ["docker", "compose", "-f", compose, "up", "-d"]
|
|
if payload["build"]:
|
|
argv.append("--build")
|
|
argv.extend(payload["services"])
|
|
return {"deploy": run(argv, cwd=STACK, timeout=3600, check=True), "containers": run(["docker", "ps", "--format", "{{.Names}}\t{{.Status}}"])}
|
|
if operation == "container_action":
|
|
return {"action": run(["docker", payload["action"], *payload["containers"]], timeout=300, check=True)}
|
|
if operation == "openwebui_sync":
|
|
installer = STACK / "platform/openwebui/install-filters.sh"
|
|
if not installer.is_file():
|
|
raise FileNotFoundError("versioned Open WebUI synchronisation script is missing")
|
|
return {"sync": run(["bash", str(installer)], cwd=STACK, timeout=1800, check=True)}
|
|
if operation == "git_publish":
|
|
selected = payload["paths"]
|
|
current_status = run(["git", "status", "--short", "--", *selected], cwd=REPOSITORY, check=True)["output"].strip()
|
|
if current_status != payload["reviewed_status"]:
|
|
raise RuntimeError("selected repository paths changed after the Git publish preview")
|
|
return publish_paths(payload["message"], selected)
|
|
if operation == "model_download":
|
|
def download():
|
|
destination = source_file(MODELS, Path(payload["destination"]))
|
|
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
partial = destination.with_suffix(destination.suffix + ".partial")
|
|
request = urllib.request.Request(payload["url"], headers={"User-Agent": "Athena-Operator/1"})
|
|
digest = hashlib.sha256()
|
|
total = 0
|
|
with urllib.request.urlopen(request, timeout=60) as response, partial.open("wb") as output:
|
|
while chunk := response.read(8 * 1024 * 1024):
|
|
output.write(chunk); digest.update(chunk); total += len(chunk)
|
|
actual = digest.hexdigest()
|
|
if payload["sha256"] and actual != payload["sha256"]:
|
|
partial.unlink(missing_ok=True); raise RuntimeError("model checksum mismatch")
|
|
os.replace(partial, destination)
|
|
return {"destination": str(destination), "bytes": total, "sha256": actual}
|
|
return start_job(ticket, operation, download)
|
|
if operation == "benchmark":
|
|
def benchmark():
|
|
script = source_file(REPOSITORY, safe_relative(payload["script"]))
|
|
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)
|
|
raise AssertionError(operation)
|
|
|
|
|
|
def execute(arguments: dict[str, Any]) -> dict[str, Any]:
|
|
ticket = str(arguments.get("ticket", ""))
|
|
confirmation = str(arguments.get("confirmation", ""))
|
|
if not re.fullmatch(r"[0-9a-f]{32}", ticket) or confirmation != f"EXECUTE {ticket}":
|
|
raise PermissionError("exact ticket confirmation required")
|
|
path = STATE / "pending" / f"{ticket}.json"
|
|
if not path.is_file():
|
|
raise ValueError("ticket not found or already consumed")
|
|
with LOCK:
|
|
record = json.loads(path.read_text())
|
|
if record["status"] != "pending" or now() > record["expires"]:
|
|
raise PermissionError("ticket expired or unavailable")
|
|
expected = sha(json.dumps({"operation": record["operation"], "payload": record["payload"]}, sort_keys=True, ensure_ascii=False).encode())
|
|
if expected != record["binding"]:
|
|
raise RuntimeError("ticket binding mismatch")
|
|
record["status"] = "executing"
|
|
json_write(path, record)
|
|
try:
|
|
result = execute_operation(ticket, record["operation"], record["payload"])
|
|
record["status"] = "executed"
|
|
record["executed"] = now()
|
|
record["result_summary"] = compact(json.dumps(result, ensure_ascii=False))
|
|
completed = STATE / "completed" / path.name
|
|
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."}
|
|
except Exception:
|
|
record["status"] = "failed"
|
|
json_write(path, record)
|
|
raise
|
|
|
|
|
|
def job(arguments: dict[str, Any]) -> dict[str, Any]:
|
|
job_id = str(arguments.get("job_id", ""))
|
|
if not re.fullmatch(r"[0-9a-f]{32}", job_id):
|
|
raise ValueError("invalid job id")
|
|
path = STATE / "jobs" / f"{job_id}.json"
|
|
if not path.is_file():
|
|
raise FileNotFoundError("job not found")
|
|
return json.loads(path.read_text())
|
|
|
|
|
|
def dispatch(request: dict[str, Any]) -> dict[str, Any]:
|
|
action = request.get("action")
|
|
arguments = request.get("arguments") or {}
|
|
if action == "inspect": return inspect(str(arguments.get("subject", "overview")), arguments)
|
|
if action == "read_source": return read_source(arguments)
|
|
if action == "search_source": return search_source(arguments)
|
|
if action == "terminal": return terminal(arguments)
|
|
if action == "prepare": return prepare(arguments)
|
|
if action == "execute": return execute(arguments)
|
|
if action == "job": return job(arguments)
|
|
raise ValueError("unsupported operator action")
|
|
|
|
|
|
class Handler(socketserver.StreamRequestHandler):
|
|
def handle(self) -> None:
|
|
line = self.rfile.readline(2_000_000)
|
|
try:
|
|
request = json.loads(line)
|
|
result = dispatch(request)
|
|
response = {"ok": True, "result": result}
|
|
except Exception as exc:
|
|
response = {"ok": False, "error": compact(str(exc))}
|
|
self.wfile.write((json.dumps(response, ensure_ascii=False, separators=(",", ":")) + "\n").encode())
|
|
|
|
|
|
def main() -> None:
|
|
STATE.mkdir(parents=True, exist_ok=True)
|
|
SOCKET.parent.mkdir(parents=True, exist_ok=True)
|
|
SOCKET.unlink(missing_ok=True)
|
|
server = socketserver.ThreadingUnixStreamServer(str(SOCKET), Handler)
|
|
os.chmod(SOCKET, 0o660)
|
|
os.chown(SOCKET, 0, int(os.environ.get("ATHENA_OPERATOR_GID", "10003")))
|
|
audit("daemon_started", version=VERSION)
|
|
server.serve_forever()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|