package agent import ( "context" "log/slog" "os" "os/exec" "path/filepath" "git.wntrmute.dev/mc/mcp/internal/config" "git.wntrmute.dev/mc/mcp/internal/runtime" ) // Isolated unikernel bridge parameters. The bridge (mcp-br0) is created by // the node's NixOS config; when present, the agent runs unikernels on it with // a host firewall confining each VM to reaching only the gateway (mc-proxy). const ( unikernelBridge = "mcp-br0" unikernelGateway = "10.99.0.1" unikernelSubnetPrefix = "10.99.0" ) // unikernelSupported reports whether this node can run Nanos unikernels: // it needs KVM (/dev/kvm) and the `ops` toolchain on PATH. func unikernelSupported() bool { if _, err := os.Stat("/dev/kvm"); err != nil { return false } if _, err := exec.LookPath("ops"); err != nil { return false } return true } // homeDir returns the agent's working directory (where images/ and vm/ live), // derived from the registry database path (e.g. /srv/mcp/mcp.db -> /srv/mcp). func homeDir(cfg *config.AgentConfig) string { if cfg != nil && cfg.Database.Path != "" { return filepath.Dir(cfg.Database.Path) } if h := os.Getenv("HOME"); h != "" { return h } return "/srv/mcp" } // runtimeFor selects the runtime backend for a component's declared runtime. // Unknown or empty runtimes fall back to the container runtime. If a service // requests "unikernel" but this node lacks the unikernel runtime, it falls // back to the container runtime (the master should not place it here). func (a *Agent) runtimeFor(rt string) runtime.Runtime { if rt == "unikernel" && a.Unikernel != nil { return a.Unikernel } return a.Runtime } // mergedLister lists observed containers across the container runtime and // (optionally) the unikernel runtime, so reconciliation, status, drift // detection, and the monitor see VMs and containers uniformly. It satisfies // the small lister interface the monitor consumes. type mergedLister struct { primary runtime.Runtime extra runtime.Runtime // unikernel runtime; may be nil logger *slog.Logger } // List returns containers from the primary runtime plus, when configured, // unikernel VMs. A failure to list VMs is logged but not fatal — the // container view still reconciles. func (m mergedLister) List(ctx context.Context) ([]runtime.ContainerInfo, error) { infos, err := m.primary.List(ctx) if err != nil { return nil, err } if m.extra != nil { vms, vmErr := m.extra.List(ctx) if vmErr == nil { infos = append(infos, vms...) } else if m.logger != nil { m.logger.Warn("list unikernel VMs failed", "err", vmErr) } } return infos, nil } // listAllContainers returns the observed state across every configured // runtime (containers + unikernel VMs) so reconciliation, status, and drift // detection see the whole picture. func (a *Agent) listAllContainers(ctx context.Context) ([]runtime.ContainerInfo, error) { return mergedLister{primary: a.Runtime, extra: a.Unikernel, logger: a.Logger}.List(ctx) }