"""Embedded runtime resolver and diagnostics.""" from __future__ import annotations import hashlib import importlib.resources as resources import json import os import stat from dataclasses import dataclass from pathlib import Path from typing import Any from pyro_mcp.vm_network import TapNetworkManager DEFAULT_PLATFORM = "linux-x86_64" @dataclass(frozen=True) class RuntimePaths: """Resolved paths for bundled runtime components.""" bundle_root: Path manifest_path: Path firecracker_bin: Path jailer_bin: Path guest_agent_path: Path | None artifacts_dir: Path notice_path: Path manifest: dict[str, Any] @dataclass(frozen=True) class RuntimeCapabilities: """Feature flags inferred from the bundled runtime.""" supports_vm_boot: bool supports_guest_exec: bool supports_guest_network: bool reason: str | None = None def _sha256(path: Path) -> str: digest = hashlib.sha256() with path.open("rb") as fp: for block in iter(lambda: fp.read(1024 * 1024), b""): digest.update(block) return digest.hexdigest() def _ensure_executable(path: Path) -> None: mode = path.stat().st_mode if mode & stat.S_IXUSR: return path.chmod(mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) def _default_bundle_parent() -> Path: return Path(str(resources.files("pyro_mcp.runtime_bundle"))) def resolve_runtime_paths( *, platform: str = DEFAULT_PLATFORM, verify_checksums: bool = True, ) -> RuntimePaths: """Resolve and validate embedded runtime assets.""" bundle_parent = Path(os.environ.get("PYRO_RUNTIME_BUNDLE_DIR", _default_bundle_parent())) bundle_root = bundle_parent / platform manifest_path = bundle_root / "manifest.json" notice_path = bundle_parent / "NOTICE" if not manifest_path.exists(): raise RuntimeError( f"bundled runtime manifest not found at {manifest_path}; reinstall package or " "use a wheel that includes bundled runtime assets" ) if not notice_path.exists(): raise RuntimeError(f"runtime NOTICE file missing at {notice_path}") manifest = json.loads(manifest_path.read_text(encoding="utf-8")) if not isinstance(manifest, dict): raise RuntimeError("invalid runtime manifest format") binaries = manifest.get("binaries") if not isinstance(binaries, dict): raise RuntimeError("runtime manifest is missing `binaries`") firecracker_entry = binaries.get("firecracker") jailer_entry = binaries.get("jailer") if not isinstance(firecracker_entry, dict) or not isinstance(jailer_entry, dict): raise RuntimeError("runtime manifest does not define firecracker/jailer binaries") firecracker_bin = bundle_root / str(firecracker_entry.get("path", "")) jailer_bin = bundle_root / str(jailer_entry.get("path", "")) guest_agent_path: Path | None = None guest = manifest.get("guest") if isinstance(guest, dict): agent_entry = guest.get("agent") if isinstance(agent_entry, dict): raw_agent_path = agent_entry.get("path") if isinstance(raw_agent_path, str): guest_agent_path = bundle_root / raw_agent_path artifacts_dir = bundle_root / "profiles" required_paths = [firecracker_bin, jailer_bin] if guest_agent_path is not None: required_paths.append(guest_agent_path) for path in required_paths: if not path.exists(): raise RuntimeError(f"runtime asset missing: {path}") _ensure_executable(firecracker_bin) _ensure_executable(jailer_bin) if verify_checksums: for entry in (firecracker_entry, jailer_entry): raw_path = entry.get("path") raw_hash = entry.get("sha256") if not isinstance(raw_path, str) or not isinstance(raw_hash, str): raise RuntimeError("runtime binary manifest entry is malformed") full_path = bundle_root / raw_path actual = _sha256(full_path) if actual != raw_hash: raise RuntimeError( f"runtime checksum mismatch for {full_path}; expected {raw_hash}, got {actual}" ) if isinstance(guest, dict): agent_entry = guest.get("agent") if isinstance(agent_entry, dict): raw_path = agent_entry.get("path") raw_hash = agent_entry.get("sha256") if not isinstance(raw_path, str) or not isinstance(raw_hash, str): raise RuntimeError("runtime guest agent manifest entry is malformed") full_path = bundle_root / raw_path actual = _sha256(full_path) if actual != raw_hash: raise RuntimeError( f"runtime checksum mismatch for {full_path}; " f"expected {raw_hash}, got {actual}" ) return RuntimePaths( bundle_root=bundle_root, manifest_path=manifest_path, firecracker_bin=firecracker_bin, jailer_bin=jailer_bin, guest_agent_path=guest_agent_path, artifacts_dir=artifacts_dir, notice_path=notice_path, manifest=manifest, ) def runtime_capabilities(paths: RuntimePaths) -> RuntimeCapabilities: """Infer what the current bundled runtime can actually do.""" binary_text = paths.firecracker_bin.read_text(encoding="utf-8", errors="ignore") if "bundled firecracker shim" in binary_text: return RuntimeCapabilities( supports_vm_boot=False, supports_guest_exec=False, supports_guest_network=False, reason="bundled runtime uses shim firecracker/jailer binaries", ) capabilities = paths.manifest.get("capabilities") if not isinstance(capabilities, dict): return RuntimeCapabilities( supports_vm_boot=False, supports_guest_exec=False, supports_guest_network=False, reason="runtime manifest does not declare guest boot/exec/network capabilities", ) supports_vm_boot = bool(capabilities.get("vm_boot")) supports_guest_exec = bool(capabilities.get("guest_exec")) supports_guest_network = bool(capabilities.get("guest_network")) reason = None if not supports_vm_boot: reason = "runtime manifest does not advertise real VM boot support" return RuntimeCapabilities( supports_vm_boot=supports_vm_boot, supports_guest_exec=supports_guest_exec, supports_guest_network=supports_guest_network, reason=reason, ) def doctor_report(*, platform: str = DEFAULT_PLATFORM) -> dict[str, Any]: """Build a runtime diagnostics report.""" report: dict[str, Any] = { "platform": platform, "runtime_ok": False, "issues": [], "kvm": { "exists": Path("/dev/kvm").exists(), "readable": os.access("/dev/kvm", os.R_OK), "writable": os.access("/dev/kvm", os.W_OK), }, "networking": { "enabled_by_default": TapNetworkManager().enabled, }, } network = TapNetworkManager.diagnostics() report["networking"].update( { "tun_available": network.tun_available, "ip_binary": network.ip_binary, "nft_binary": network.nft_binary, "iptables_binary": network.iptables_binary, "ip_forward_enabled": network.ip_forward_enabled, } ) try: paths = resolve_runtime_paths(platform=platform, verify_checksums=True) except Exception as exc: # noqa: BLE001 report["issues"] = [str(exc)] return report capabilities = runtime_capabilities(paths) from pyro_mcp.vm_environments import EnvironmentStore environment_store = EnvironmentStore(runtime_paths=paths) report["runtime_ok"] = True report["runtime"] = { "bundle_root": str(paths.bundle_root), "manifest_path": str(paths.manifest_path), "firecracker_bin": str(paths.firecracker_bin), "jailer_bin": str(paths.jailer_bin), "guest_agent_path": str(paths.guest_agent_path) if paths.guest_agent_path else None, "artifacts_dir": str(paths.artifacts_dir), "artifacts_present": paths.artifacts_dir.exists(), "notice_path": str(paths.notice_path), "bundle_version": paths.manifest.get("bundle_version"), "component_versions": paths.manifest.get("component_versions", {}), "capabilities": { "supports_vm_boot": capabilities.supports_vm_boot, "supports_guest_exec": capabilities.supports_guest_exec, "supports_guest_network": capabilities.supports_guest_network, "reason": capabilities.reason, }, "catalog_version": environment_store.catalog_version, "cache_dir": str(environment_store.cache_dir), "environments": environment_store.list_environments(), } if not report["kvm"]["exists"]: report["issues"] = ["/dev/kvm is not available on this host"] return report