Add guest init binary, hooks, and wiring for go-microvm runtime#4358
Add guest init binary, hooks, and wiring for go-microvm runtime#4358
Conversation
There was a problem hiding this comment.
Large PR Detected
This PR exceeds 1000 lines of changes and requires justification before it can be reviewed.
How to unblock this PR:
Add a section to your PR description with the following format:
## Large PR Justification
[Explain why this PR must be large, such as:]
- Generated code that cannot be split
- Large refactoring that must be atomic
- Multiple related changes that would break if separated
- Migration or data transformationAlternative:
Consider splitting this PR into smaller, focused changes (< 1000 lines each) for easier review and reduced risk.
See our Contributing Guidelines for more details.
This review will be automatically dismissed once you add the justification section.
Code Review NotesNice work on the go-microvm runtime skeleton — the architecture is clean, the init binary design is elegant, and the test coverage for hooks/permissions/state/lifecycle is solid. A few things I noticed that are worth discussing: 1. VirtioFS mounts silently drop ReadOnly flag
I checked upstream and In the meantime, it might be worth either logging a warning when read-only mounts are requested, or rejecting them with a clear error so users aren't silently getting weaker isolation than they expect. No catalog entries currently use read mounts, so this is low practical impact today — but it's a correctness gap relative to how Docker handles the same permission profile. 2. doc.go has wrong runtime name
3. Egress policy: empty AllowHost + InsecureAllowAll=false = unrestricted
Removing the 4. Toolhive runtime env vars don't reach the child MCP server processThis one is more significant than it looks on the surface. In cmd.Env = append(os.Environ(), ep.Env...)The Toolhive runtime vars ( The init binary likely needs to load
5. SSH private key persists on disk after VM stop
6. Minor:
|
4b58bff to
1f6d0c7
Compare
1f6d0c7 to
1f1278b
Compare
The go-microvm runtime skeleton was solid but couldn't boot MCP server images because it lacked guest-side infrastructure. go-microvm uses gvproxy networking (virtio-net), so the guest must configure its own network via DHCP — libkrun's built-in init doesn't do this. This adds: - thv-vm-init: PID 1 guest binary that boots (mounts, DHCP, SSH via go-microvm/guest/boot), reads /etc/thv-entrypoint.json for the original OCI cmd/env/workdir, execs the MCP server as a child, forwards signals, and halts the VM cleanly on exit - RootFS hooks: InjectInitBinary (embeds compiled binary), InjectEntrypoint (captures OCI config before init override), InjectEntrypointOverride (explicit command), InjectSSHKeys - libkrun backend with WithUserNamespaceUID(1000,1000) for virtiofs passthrough, WithCleanDataDir, WithLogLevel, WithImageCache - HTTP readiness probe (exponential backoff, accepts any response) - Recovered VM lifecycle fix: StopWorkload/RemoveWorkload now kill orphaned runner processes for VMs without handles - Build infrastructure: build-vm-init task with CGO_ENABLED=0 GOOS=linux, wired as dependency of the main build task Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix runtime name in doc.go (microvm -> go-microvm) - Warn when ReadOnly VirtioFS mounts are requested (upstream limitation) - Warn when empty AllowHost with InsecureAllowAll=false gives unrestricted egress instead of deny-all (upstream limitation) - Load /etc/environment in init binary so ToolHive runtime vars (MCP_TRANSPORT, API keys, etc.) reach the child MCP server process - Keep SSH private key for debugging access, update comment - Use filepath.Join instead of fmt.Sprintf for path construction - Use errors.As instead of type assertion for exec.ExitError Upstream issues filed: go-microvm#53 (ReadOnly), go-microvm#54 (deny-all) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The go-microvm library provides no direct stdin/stdout access to running VMs. Bridge stdio over TCP using port forwarding: a relay inside the guest VM accepts connections and pipes them to the child MCP server's stdin/stdout, while the host dials through an additional port forward. Guest side (thv-vm-init): - Detect stdio mode via MCP_TRANSPORT and THV_STDIO_RELAY_PORT env vars - TCP relay with accept loop and first-byte probe detection to distinguish readiness probes from real data connections - Signal forwarding (SIGTERM/SIGINT) to child process Host side (gomicrovm): - Remove stdio transport rejection from DeployWorkload - Allocate relay port, add second port forward, inject env var - AttachToWorkload dials TCP relay with half-close wrapper - TCP readiness probe for post-boot verification - Persist relay port in VM state for recovery Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
b09a2fe to
ab3231f
Compare
The go-microvm runtime skeleton was solid but couldn't boot MCP server
images because it lacked guest-side infrastructure. go-microvm uses
gvproxy networking (virtio-net), so the guest must configure its own
network via DHCP — libkrun's built-in init doesn't do this.
This adds:
go-microvm/guest/boot), reads /etc/thv-entrypoint.json for the original
OCI cmd/env/workdir, execs the MCP server as a child, forwards
signals, and halts the VM cleanly on exit
InjectEntrypoint (captures OCI config before init override),
InjectEntrypointOverride (explicit command), InjectSSHKeys
passthrough, WithCleanDataDir, WithLogLevel, WithImageCache
orphaned runner processes for VMs without handles
wired as dependency of the main build task