feat(minvmd): source libkrun from the upstream package - #466
Conversation
Un-gate the real (libkrun-linking) implementation so minvmd builds and runs on Linux over the KVM backend, not just macOS over Hypervisor.framework. The build script now detects libkrun (always linked on macOS; on Linux when present via LIBKRUN_PREFIX or a scan of the usual lib dirs) and emits a `minvmd_libkrun` cfg. That single cfg replaces every `target_os = "macos"` gate across the krun module, image/vm helpers, the boot/run/__krun-vmm subcommands, and the three e2e tests. Stock Linux CI has no libkrun, so it still builds the no-op stub and stays green; a Linux host with libkrun builds the real daemon with a plain `cargo build -p minvmd`. The Linux boot path mirrors macOS: libkrun abstracts HVF vs KVM, so the supervisor fork-execs the same `__krun-vmm` child, waits for the same READY marker, and serves the same UDS↔vsock bridge. The platform-specific supervisor functions are renamed to neutral names (run_supervisor / run_boot / run_vmm) since they now cover both OSes. Add an opt-in, non-gating `minvmd-linux-kvm-e2e` CI job on a self-hosted `[self-hosted, linux, kvm]` runner: it materializes the guest kernel + rootfs + initramfs natively, builds minvmd against the runner's libkrun, runs the boot / session / bridge e2e tests, and reports boot-to-READY latency. Gated on RUN_LINUX_KVM_CI == 'true' and absent from ci-success so an unavailable runner never blocks PRs. Closes: #411 Closes: #410 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
R2.4: before krun_start_enter, verify /dev/kvm is accessible on Linux so `boot`/`run` fail fast with an actionable message instead of an opaque libkrun error. ENOENT maps to "KVM module not loaded / no hardware virtualization"; EACCES maps to "add your user to the `kvm` group". The check runs in run_supervisor (covering --detach from the foreground caller) and in run_boot. No-op on macOS, where Hypervisor.framework availability is verified by krun_create_ctx itself. The error-mapping helper is platform-independent and unit-tested on every libkrun build. Also fix two misattributed (R2.4) comments on READY-marker code (the READY round-trip is R2.1/R2.2; boot_e2e is R3.1) and update the bench-minvmd-boot.sh header to document dual macOS/Linux use (codesigning is a macOS-only prerequisite; Linux/KVM needs none). Refs: #411 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the opt-in self-hosted minvmd-linux-kvm-e2e job (which needed a preprovisioned [self-hosted, linux, kvm] runner with libkrun already installed) with a path-scoped ci-linux-kvm.yml that runs on GitHub-hosted runners across an x86_64 (ubuntu-latest) + aarch64 (ubuntu-24.04-arm) matrix. - Add scripts/build-libkrun.sh: builds libkrunfw v5.5.0 + libkrun v1.19.0 (BLK=1, so krun_add_disk2 is exported) from source into a cacheable prefix; idempotent so a cache hit skips the kernel compile. - Enable /dev/kvm for the runner user via a udev rule, failing fast if the runner has no KVM. - Exercise the run/daemon path (run --detach -> status -> stop) alongside the existing boot/session/bridge e2e tests and the boot latency bench. - Non-gating and path-scoped, mirroring ci-macos.yml. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The first CI (Linux KVM) run failed on both legs: - x86_64: libkrunfw's Makefile forwards $(MAKEFLAGS) verbatim into the kernel's recursive make. Building it via `make -C` enables print-directory mode, which injects a bare `w` token into MAKEFLAGS that the kernel make then treats as a goal (`make w` -> "No rule to make target 'w'"). Build libkrunfw/libkrun with an in-dir `cd` + explicit `-jN` and no exported MAKEFLAGS, the upstream-standard invocation. - aarch64: GitHub-hosted ubuntu-24.04-arm has no /dev/kvm, so the Linux/KVM e2e cannot run there. Drop the matrix and run x86_64-only; aarch64 + libkrun boot stays covered by the macOS lane (Apple Silicon). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The libkrun build now succeeds, but `fetch-artifact.sh` (cargo build -p minimal) failed compiling remote-proto: protoc failed: google/protobuf/timestamp.proto: File not found. The well-known protos live in libprotobuf-dev, which protobuf-compiler only pulls via recommends. Since the dependency install uses --no-install-recommends (to stay lean for the libkrun toolchain), name libprotobuf-dev explicitly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
actions/cache's default post-job save is skipped when the job fails, so a failure after the libkrun step forced a ~15 min kernel recompile on every iteration. Split into cache/restore + an explicit cache/save placed right after the build, so the expensive prefix is cached as soon as it exists — before any later step (e2e/bench) can fail. Gated on a cache miss since cache keys are immutable. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Linux/KVM boot e2e failed on x86_64 with `krun_start_enter ... Invalid argument (EINVAL)`. The x86_64 `virtio-kernel` artifact is a bzImage: a PE/`MZ` container that embeds the `vmlinux` ELF as a gzip member. minvmd passed `KernelFormat::Elf`, so libkrun handed the PE container straight to its ELF loader, which cannot parse it. Use `KernelFormat::ImageGz` (already in the enum): libkrun scans for the gzip magic (1f 8b 08), inflates the single member to the vmlinux ELF, and ELF-loads it. Verified against the materialized artifact — the gzip member at the first magic match decompresses to a 46 MB x86-64 ELF vmlinux. aarch64 is unaffected (ships an uncompressed Image → Raw). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
With the ImageGz kernel fix the guest now boots on x86_64 KVM and Boot E2E + Session E2E pass, but Bridge E2E failed: it read back "SSH-2.0-russh_0.6" instead of its echo payload. bridge_e2e targets the Stage-1 guest "vsock stub" — a socat/cat echo on port 2222 — which minimald-as-pid1 replaced with a direct SSH session server. The test is obsolete against the current rootfs. The macOS lane already runs only boot_e2e + minimald_session_e2e; match it. Bridge session coverage comes from Session E2E + the daemon lifecycle step. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Address CodeRabbit review feedback: - build-libkrun.sh: gate the skip-rebuild on a version stamp, not mere presence of libkrun.so. A restored cache (or local prefix reuse) built from different pins is now rebuilt instead of silently accepted, so a pin bump can't yield a stale install. - ci-linux-kvm.yml: assert the daemon state with `jq -e` (preinstalled on GitHub runners) instead of grepping the raw JSON, so the lifecycle check doesn't break on harmless formatting changes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The previous cache was saved before build-libkrun.sh wrote its version stamp, so the new stamp-gated idempotency check would treat the restored prefix as stale and rebuild — yet the save step is gated on a cache miss, so the rebuilt (stamped) prefix would never be re-saved. Every run would then rebuild libkrun from scratch. Bump the key (`-blk` → `-blk-stamped`) so the stamped prefix is saved fresh once, after which restore hits and the build skips. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The daemon lifecycle step failed asserting state=="running" right after
`run --detach`: status showed `{"state":"starting","vmm_pid":null}`.
`run --detach` returns once the host UDS accepts connections, which libkrun
opens early in VM setup — ahead of the supervisor's Starting->Running
transition (set after the guest READY marker). Poll status for up to ~15s
for Running instead of asserting immediately, removing the race.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The daemon lifecycle step reached Running (the poll fixed the earlier race) and stopped cleanly, but still exited 1: `minvmd status --json` exits 1 when stopped (documented), and `status --json | tee status-after.json` tripped `set -o pipefail` under `set -e`. The bare `status` was already guarded with `if`; the piped `--json` query was not. Capture it with `> status-after.json || true`, then assert state==stopped via jq and re-confirm the non-zero exit separately. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Consume the libkrun KVM backend from the upstream `libkrun` package (gominimal/pkgs#271) instead of compiling it from source in CI. The from-source path built libkrunfw, which compiles a guest kernel on every run — far too heavy. - Bump the upstream pkgs pin to the merge commit of #271, which also carries the new microvm-rootfs package. - Add a `libkrun` oci-image output backed by the upstream package. - Add scripts/fetch-libkrun.sh: a cache fetch (keyed by the pinned commit) that materializes the libkrun OCI image and extracts only libkrun.so* + libkrunfw.so* into a flat link/runtime prefix. The package's glibc/libgcc layers are left out so they cannot shadow the host glibc the minvmd binary is linked against. - Rewrite the ci-linux-kvm.yml libkrun steps to fetch the prefix and drop the kernel-compile apt toolchain (flex/bison/clang/patchelf/…). - Remove scripts/build-libkrun.sh. Validated on aarch64: the upstream cache served a prebuilt libkrun (no kernel compile), the extracted prefix satisfies both the build.rs link + rpath and the runtime dlopen of libkrunfw.so.5, and minvmd builds as the real (non-stub) impl. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Document the Linux/KVM flow for the next dev: fetch libkrun + guest artifacts, build the initramfs (cross or native musl), build and boot minvmd, and run the gated e2e tests. Covers the kvm-group durability fix and the `boot` vs `run` state.toml lifecycle gotcha. Fix the stale "macOS-only" opener and retitle the existing E2E section to "(macOS)". Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughReplaces the from-source Changeslibkrun upstream fetch + Linux autospawn
Sequence DiagramsequenceDiagram
participant Caller as CI/build caller
participant fetch as fetch-libkrun.sh
participant minimal as minimal CLI
participant registry as Upstream OCI registry
participant PREFIX as PREFIX directory
Caller->>fetch: fetch-libkrun.sh PREFIX [arch]
fetch->>fetch: cargo build minimal CLI
fetch->>minimal: minimal materialize libkrun artifact
minimal->>registry: download pinned libkrun OCI image
registry-->>minimal: OCI archive (index/manifest/layers)
minimal-->>fetch: extracted to temp workspace
fetch->>fetch: unpack layers into rootfs
fetch->>PREFIX: cp -a libkrun.so* libkrunfw.so*
fetch-->>Caller: success + file listing
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related issues
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
The R4.5 auto-spawn path (check state.toml, spawn `minvmd run --detach` if not running, wait for the UDS) was a macOS-only real impl with a Linux no-op stub — Linux had no minvmd backend. Now that minvmd boots on Linux/KVM, enable the same path there. The decision logic was already OS-agnostic, so share one implementation across macOS + Linux (no-op only on targets with no minvmd backend), and extract a pure `classify(Lifecycle) -> Decision` helper so the state-to-action mapping is exhaustively unit-tested without I/O. Behaviour change on Linux: `minimal2 ls` with no running minvmd now spawns `minvmd run --detach` (and errors if `minvmd` is not on PATH) rather than silently printing the `[]` placeholder — exact parity with macOS. Real session listing over the minimald UDS is still #311. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
# Conflicts: # .github/workflows/ci-linux-kvm.yml
scripts/build-libkrun.sh is no longer referenced by any workflow now that CI materializes libkrun from the upstream package via scripts/fetch-libkrun.sh. Drop the dead script and its dangling reference in .minimal/minimal.toml. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
.github/workflows/ci-linux-kvm.yml (1)
65-77:⚠️ Potential issue | 🟠 Major | ⚡ Quick winInstall
jqexplicitly in the dependency step.
fetch-libkrun.sh(and the later daemon lifecycle assertions) depend onjq, but this step does not install it. That makes CI behavior depend on runner image preinstalls.Suggested fix
sudo apt-get install -y --no-install-recommends \ protobuf-compiler libprotobuf-dev \ - cpio git + cpio git jq🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/ci-linux-kvm.yml around lines 65 - 77, The Install build dependencies step does not explicitly install jq, which is required by fetch-libkrun.sh and daemon lifecycle assertion scripts, causing CI behavior to depend on pre-installed packages in the runner image. Add jq to the list of packages being installed in the apt-get install command alongside the existing packages protobuf-compiler, libprotobuf-dev, cpio, and git.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@crates/minvmd/README.md`:
- Around line 130-136: The current command applies the --exact
minimald_exec_over_bridge filter to both boot_e2e and minimald_session_e2e test
binaries, but this test name only exists in minimald_session_e2e, causing
boot_e2e tests to be skipped. Split this into two separate sg kvm invocations:
one that runs cargo test for boot_e2e without the --exact filter to run all
tests in that binary, and another separate invocation that runs cargo test for
minimald_session_e2e with the --exact minimald_exec_over_bridge filter. Both
invocations should keep the environment variables and other flags like
--include-ignored and --nocapture.
In `@docs/specs/01-spec-minvmd-host-daemon/01-spec-minvmd-host-daemon.md`:
- Around line 289-293: R4.5 now specifies behavior for both macOS and Linux, but
other sections in the specification (around lines 16, 33, 51, 330, and 436)
still contain conflicting statements treating Linux as a no-op or macOS-only.
Review each of these sections and update them to align with the platform
contract established in R4.5. Ensure that all mentions of platform-specific
behavior, particularly regarding minvmd backend availability and Linux support,
are consistent throughout the document and clearly reflect when features apply
to both macOS and Linux versus when they are platform-specific no-ops.
---
Outside diff comments:
In @.github/workflows/ci-linux-kvm.yml:
- Around line 65-77: The Install build dependencies step does not explicitly
install jq, which is required by fetch-libkrun.sh and daemon lifecycle assertion
scripts, causing CI behavior to depend on pre-installed packages in the runner
image. Add jq to the list of packages being installed in the apt-get install
command alongside the existing packages protobuf-compiler, libprotobuf-dev,
cpio, and git.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 46d2f45e-3be4-48b4-90f4-3c0e5e55a6b5
📒 Files selected for processing (8)
.github/workflows/ci-linux-kvm.yml.minimal/minimal.tomlcrates/minimal2/src/autospawn.rscrates/minimal2/src/main.rscrates/minvmd/README.mddocs/specs/01-spec-minvmd-host-daemon/01-spec-minvmd-host-daemon.mdscripts/build-libkrun.shscripts/fetch-libkrun.sh
💤 Files with no reviewable changes (1)
- scripts/build-libkrun.sh
A single `cargo test --test boot_e2e --test minimald_session_e2e -- --exact minimald_exec_over_bridge` passes the filter to every selected binary; boot_e2e has no test by that name so it runs zero tests. Split into two invocations mirroring the CI lane's separate steps. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
R4.5 enabled minvmd auto-spawn on Linux but the rest of the spec still described minvmd as macOS-only with a Linux no-op shim. minvmd now builds and boots on Linux via libkrun's KVM backend (ci-linux-kvm.yml), and minimal2 auto-spawn is gated cfg(any(macos, linux)). Update Context, Networking note, Goals, Non-goals, R1.1, the build matrix, the CI gate, and the auto-spawn detail to one consistent contract. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Stacked on #414 (
minvmd-linux-kvm). Consume the libkrun KVM backend from the upstreamlibkrunpackage (gominimal/pkgs#271, merged) instead of compiling it — and its kernel-bundling libkrunfw — from source in CI.What changed
.minimal/minimal.tomllocked_commit→ the merge commit of pkgs#271 (also carries themicrovm-rootfspackage).libkrunoutput — anoci-imageoutput backed by the upstreamlibkrunpackage.scripts/fetch-libkrun.sh(new) — a cache fetch (keyed by the pinned commit, not a from-source build) that materializes the libkrun OCI image and extracts onlylibkrun.so*+libkrunfw.so*into a flat link/runtime prefix. The package's glibc/libgcc layers are deliberately left out so they can't shadow the host glibc that the minvmd binary links against.ci-linux-kvm.yml— swap the three source-build steps for the fetch; drop the kernel-compile apt toolchain (flex/bison/bc/libelf/clang/libclang/patchelf/pyelftools).scripts/build-libkrun.sh.crates/minvmd/README.mdgains a Linux/KVM local build + e2e guide.Why
The old CI path ran
build-libkrun.sh, which compiles a guest kernel via libkrunfw on every run. Pulling the prebuilt package from the upstream cache removes that compile entirely.Validation (local, aarch64)
Ran steps 1–5 of the new README flow:
fetch-libkrun.shhit the upstream cache — prebuilt libkrun served, no kernel compile.build.rslink + rpath;minvmdbuilds as the real (non-stub) impl:RUNPATH=~/.krun,NEEDED libkrun.so.1resolving to the fetched prefix.lddclean; binary loads and runs.libkrunfw.so.5correctly resolves at runtime viadlopen+LD_LIBRARY_PATH(not a link-timeDT_NEEDED).The KVM boot itself (needs
usermod -aG kvm) was not run; everything up to it passes.Note for reviewers
The win depends on the upstream public cache having a prebuilt
libkrunfor the target arch at the pinned commit. Confirmed present for aarch64; on a miss,materializewould fall back to a native source build (the cost we're avoiding).🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes
New Features
Documentation
Chores