fix: Hide -c/--command on min session attach from the CLI help surface
#1197
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI (Linux KVM) | |
| # minvmd Linux/KVM end-to-end on a GitHub-hosted runner. | |
| # | |
| # It needs no preprovisioned runner — KVM is available on GitHub-hosted | |
| # x86_64 Linux runners, and libkrun is materialized from the upstream package | |
| # cache (a cache fetch keyed by the pinned locked_commit, NOT a from-source | |
| # build) alongside the guest kernel + rootfs. | |
| # | |
| # x86_64 ONLY: GitHub-hosted arm64 runners (ubuntu-24.04-arm) do not expose | |
| # /dev/kvm, so the Linux/KVM tests can only run on x86_64. aarch64 + libkrun boot | |
| # is already covered by the macOS lane (ci-macos.yml, Apple Silicon / HVF). | |
| # | |
| # GATE-READY: ALWAYS-TRIGGERED, path-scoped INSIDE the workflow — the | |
| # `changes` job gates the e2e, and the `if: always()` aggregator | |
| # (ci-linux-kvm-success) reports success even when it was path-skipped, so | |
| # the context can join the required checks after this lane soaks green. A | |
| # required context that a trigger-level path filter skips would stay | |
| # "Expected" forever and wedge the PR; do NOT re-add trigger-level `paths:`. | |
| # Until the ruleset flip the lane remains advisory, as before. | |
| on: | |
| push: | |
| branches: ["main"] | |
| pull_request: | |
| branches: ["main"] | |
| workflow_dispatch: | |
| env: | |
| CARGO_TERM_COLOR: always | |
| # Cancel superseded runs so the e2e job isn't queued on stale commits. | |
| concurrency: | |
| group: ci-linux-kvm-${{ github.ref }}-${{ github.workflow }} | |
| cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} | |
| permissions: | |
| contents: read | |
| # dorny/paths-filter reads the PR file list on pull_request events. | |
| pull-requests: read | |
| jobs: | |
| changes: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| outputs: | |
| kvm: ${{ steps.filter.outputs.kvm }} | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 | |
| with: | |
| persist-credentials: false | |
| - name: Detect relevant changes | |
| id: filter | |
| # v3.0.2, SHA-pinned. Runs ONLY on pull_request, where dorny reads | |
| # the PR file list via the API. On push it would need a local diff | |
| # against event.before, which our shallow, credential-free checkout | |
| # (persist-credentials: false) cannot provide — and pushes to main | |
| # SHOULD run everything anyway: they write the main-only caches and | |
| # provide the soak signal. Dispatch likewise runs everything. | |
| if: github.event_name == 'pull_request' | |
| uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 | |
| with: | |
| filters: | | |
| kvm: | |
| - "crates/minvmd/**" | |
| - "crates/minimald/**" | |
| - "scripts/**" | |
| - "Cargo.toml" | |
| - "Cargo.lock" | |
| # The toolchain pin: a rustc bump must re-run the Rust lanes. | |
| - "rust-toolchain.toml" | |
| - ".minimal/minimal.toml" | |
| - ".github/workflows/ci-linux-kvm.yml" | |
| # Composite actions this lane's setup runs through — without | |
| # these, a composite-only edit would skip this lane and break | |
| # it post-merge. | |
| - ".github/actions/setup-libkrun-linux/**" | |
| - ".github/actions/materialize/**" | |
| build-linux: | |
| # BUILD phase: compile everything the KVM tests need and ship it as one | |
| # artifact, so a flaky e2e rerun replays only the ~15-minute test job | |
| # instead of this full build. No /dev/kvm needed here. | |
| needs: [changes] | |
| if: github.event_name != 'pull_request' || needs.changes.outputs.kvm == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 60 | |
| env: | |
| # Slim debug info (usable backtraces, smaller caches, faster links); | |
| # this job doesn't run the setup-rust composite that sets this on the | |
| # other Linux lanes. | |
| CARGO_PROFILE_DEV_DEBUG: line-tables-only | |
| steps: | |
| - name: Free Disk Space | |
| # The workspace build + libkrun prefix routinely exceed the ~14 GB | |
| # free on a fresh GitHub-hosted Ubuntu runner. `remove_tool_cache: | |
| # true` reclaims /opt/hostedtoolcache (~14 GB); the dtolnay step below | |
| # reinstalls the Rust toolchain we actually need. | |
| uses: endersonmenezes/free-disk-space@7901478139cff6e9d44df5972fd8ab8fcade4db1 # v3 | |
| with: | |
| remove_android: true | |
| remove_dotnet: true | |
| remove_haskell: true | |
| remove_tool_cache: true | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 | |
| with: | |
| persist-credentials: false | |
| - name: Toolchain | |
| uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable | |
| with: | |
| toolchain: stable | |
| - name: Install build dependencies | |
| # protoc + libprotobuf-dev: the `minimal` CLI (materialize) build. The | |
| # well-known protos (google/protobuf/*.proto) live in libprotobuf-dev, | |
| # which protobuf-compiler only pulls via recommends — so name it | |
| # explicitly since we use --no-install-recommends. cpio: packs the | |
| # guest initramfs (scripts/build-initramfs.sh). git: source fetches | |
| # during the CLI build. | |
| run: | | |
| sudo sh -c \ | |
| 'apt-get update && apt-get install -y --no-install-recommends \ | |
| protobuf-compiler libprotobuf-dev cpio git' | |
| - name: Install cross + nextest | |
| uses: taiki-e/install-action@3d7d7cd5ac7f994c1892ae0c06165095b9139094 # v2 | |
| with: | |
| tool: cross,nextest | |
| - name: Cache cargo + build artifacts | |
| # Restore-only; the save half (main only) runs at the end of the job. | |
| id: cargo-cache | |
| uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 | |
| with: | |
| path: | | |
| ~/.cargo/bin/ | |
| ~/.cargo/registry/index/ | |
| ~/.cargo/registry/cache/ | |
| ~/.cargo/git/db/ | |
| target/ | |
| key: ${{ runner.os }}-x86_64-minvmd-kvm-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: ${{ runner.os }}-x86_64-minvmd-kvm- | |
| - name: Materialize libkrun prefix (upstream package) | |
| # Needed at BUILD time: minvmd links libkrun.so (build.rs link search | |
| # + the minvmd_libkrun cfg for the real, non-stub impl). The prefix is | |
| # shipped in the testbed so the test job runs against the exact | |
| # libraries this job linked. Runs after the cargo cache restore so the | |
| # CLI build the cache fetch drives is incremental. | |
| uses: ./.github/actions/setup-libkrun-linux | |
| with: | |
| arch: x86_64 | |
| - name: Build guest initramfs (minimald as /init) | |
| run: ./scripts/build-initramfs.sh "$RUNNER_TEMP/initramfs.cpio" x86_64-unknown-linux-musl | |
| - name: Build minvmd | |
| run: cargo build -p minvmd --bin minvmd --locked | |
| - name: Build the minimal CLI | |
| # SEPARATE invocation from the minvmd build: a combined `cargo build | |
| # -p minvmd -p minimal` unifies minvmd's default `libkrun` feature | |
| # into the CLI's default-features=false opt-out, linking libkrun into | |
| # `minimal` (the regression the macOS lane hit and now guards). The | |
| # CLI drives the unified session e2e in the test job. | |
| run: cargo build -p minimal --bin min --locked | |
| - name: Archive integration harnesses + stage the testbed | |
| # `cargo nextest archive` replaces the hand-rolled testbins.json | |
| # manifest: the archive carries every minvmd test binary with exec | |
| # bits intact, and the test job selects harnesses with filtersets. | |
| # The sidecar files stay explicit — libkrun lives outside target/ (an | |
| # archive cannot carry it), and the harnesses/CLI locate minvmd via | |
| # MINVMD_BIN / PATH (their compile-time CARGO_BIN_EXE path does not | |
| # exist on the test runner). | |
| run: | | |
| set -euo pipefail | |
| mkdir -p testbed | |
| cargo nextest archive -p minvmd --locked --archive-file testbed/nextest-archive.tar.zst | |
| cp target/debug/minvmd testbed/minvmd | |
| cp target/debug/min testbed/min | |
| cp "$RUNNER_TEMP/initramfs.cpio" testbed/initramfs.cpio | |
| # Ship the exact libkrun prefix the binaries were linked against. | |
| tar czf testbed/libkrun-prefix.tgz -C "$LIBKRUN_PREFIX" . | |
| - name: Upload testbed | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 | |
| with: | |
| name: kvm-testbed | |
| path: testbed/ | |
| if-no-files-found: error | |
| retention-days: 3 | |
| - name: Save cargo + build artifacts cache (main only) | |
| # PR branches restore but never write: multi-GB caches written by | |
| # every branch LRU-evict the main entries all PRs restore from. | |
| if: github.ref == 'refs/heads/main' && steps.cargo-cache.outputs.cache-hit != 'true' | |
| uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6 | |
| with: | |
| path: | | |
| ~/.cargo/bin/ | |
| ~/.cargo/registry/index/ | |
| ~/.cargo/registry/cache/ | |
| ~/.cargo/git/db/ | |
| target/ | |
| key: ${{ steps.cargo-cache.outputs.cache-primary-key }} | |
| test-kvm: | |
| # TEST phase: no Rust toolchain, no cargo build — download the testbed, | |
| # pull the guest images, and run the archived harnesses with the | |
| # standalone cargo-nextest binary. A flake rerun of this job alone costs | |
| # ~15 minutes instead of the old 60-minute build+test monolith. | |
| needs: [changes, build-linux] | |
| if: github.event_name != 'pull_request' || needs.changes.outputs.kvm == 'true' | |
| runs-on: ubuntu-latest # x86_64; KVM-capable | |
| timeout-minutes: 30 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 | |
| with: | |
| persist-credentials: false | |
| - name: Install nextest | |
| uses: taiki-e/install-action@3d7d7cd5ac7f994c1892ae0c06165095b9139094 # v2 | |
| with: | |
| tool: nextest | |
| - name: Enable /dev/kvm access | |
| # GitHub-hosted x86_64 Linux runners expose /dev/kvm but the runner user | |
| # is not in the `kvm` group; widen the node so libkrun can open it. Fail | |
| # fast with a clear message if this runner has no KVM at all. | |
| run: | | |
| if [ ! -e /dev/kvm ]; then | |
| echo "::error::/dev/kvm not present on this runner; cannot run the Linux/KVM tests." >&2 | |
| exit 1 | |
| fi | |
| echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' \ | |
| | sudo tee /etc/udev/rules.d/99-kvm4all.rules | |
| sudo udevadm control --reload-rules | |
| sudo udevadm trigger --name-match=kvm | |
| ls -l /dev/kvm | |
| - name: Download testbed | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 | |
| with: | |
| name: kvm-testbed | |
| path: ${{ runner.temp }}/testbed | |
| - name: Unpack testbed + resolve paths | |
| # Artifact transfer strips exec bits on the sidecar binaries; restore | |
| # them (the nextest archive preserves its own). MINVMD_BIN points the | |
| # harnesses at the shipped minvmd (their baked CARGO_BIN_EXE path only | |
| # exists on the build runner); the testbed joins PATH so the CLI's | |
| # autospawn and the lifecycle script resolve minvmd/minimal by name. | |
| # The libkrun prefix is unpacked to the same $HOME/.krun path the | |
| # build linked (rpath) and exported for the dynamic loader; its | |
| # krun_add_disk3 export was verified at build time by the | |
| # setup-libkrun-linux composite — minvmd loading it here is the | |
| # runtime backstop. | |
| run: | | |
| set -euo pipefail | |
| TESTBED="$RUNNER_TEMP/testbed" | |
| chmod +x "$TESTBED/minvmd" "$TESTBED/min" | |
| mkdir -p "$HOME/.krun" | |
| tar xzf "$TESTBED/libkrun-prefix.tgz" -C "$HOME/.krun" | |
| echo "$TESTBED" >> "$GITHUB_PATH" | |
| { | |
| echo "TESTBED=$TESTBED" | |
| echo "MINVMD_BIN=$TESTBED/minvmd" | |
| echo "LIBKRUN_PREFIX=$HOME/.krun" | |
| echo "LD_LIBRARY_PATH=$HOME/.krun${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" | |
| echo "MINVMD_E2E=1" | |
| echo "MINVMD_KERNEL_PATH=$RUNNER_TEMP/vmlinuz" | |
| echo "MINVMD_ROOTFS_PATH=$RUNNER_TEMP/rootfs.img" | |
| echo "MINVMD_INITRAMFS=$TESTBED/initramfs.cpio" | |
| } >> "$GITHUB_ENV" | |
| - name: Materialize guest kernel (raw Image) | |
| uses: ./.github/actions/materialize | |
| with: | |
| output: virtio-kernel | |
| dest: ${{ runner.temp }}/vmlinuz | |
| arch: x86_64 | |
| channel: unstable | |
| - name: Materialize guest rootfs (ext4 image) | |
| uses: ./.github/actions/materialize | |
| with: | |
| output: minvmd-rootfs | |
| dest: ${{ runner.temp }}/rootfs.img | |
| arch: x86_64 | |
| channel: unstable | |
| - name: VM integration harnesses (auto-discovered) | |
| # Convention over enumeration: run every harness the archive carries by | |
| # the `_integration` suffix, so a new crates/minvmd/tests/*_integration.rs | |
| # file is picked up here with zero workflow edit (the auto-discovery | |
| # contract; a normal unit test in minvmd guards the suffix). | |
| # `--run-ignored all` runs | |
| # the `#[ignore]`-gated bodies; `--no-tests=fail` reds the job if the | |
| # filterset ever matches nothing. MINVMD_E2E=1 is set job-wide; profile | |
| # vm is serial with fail-fast, so a failure preserves its own boot.log. | |
| # The `not binary(/_root_integration$/)` guard is defensive: minvmd has no root | |
| # harness today (its host-side tap relay moved into the guest minimald), | |
| # so if one is ever added it must get its own sudo-wrapped step rather | |
| # than run unprivileged here. | |
| env: | |
| MINVMD_BOOT_LOG: ${{ runner.temp }}/boot.log | |
| run: | | |
| cargo-nextest nextest run --archive-file "$TESTBED/nextest-archive.tar.zst" \ | |
| --workspace-remap "$GITHUB_WORKSPACE" --profile vm \ | |
| --run-ignored all --no-tests=fail \ | |
| -E 'binary(/_integration$/) and not binary(/_root_integration$/)' | |
| # NOTE: there is no separate concurrent-bridge harness. The retired | |
| # Stage-1 vsock stub (a socat/cat echo on port 2222) that one exercised was | |
| # superseded by minimald's direct SSH session server; session coverage of | |
| # the bridge is minimald_session_integration (in the harness step above) | |
| # plus the daemon lifecycle step below. | |
| - name: Reap leftover VM processes | |
| # The harness runs above can leak __krun-vmm children on failure | |
| # (Guest::drop kills only its own minvmd), and a leftover wedges the | |
| # vsock bridge for the fresh daemons the next two steps spawn | |
| # (lifecycle, then the CLI session e2e). The harnesses run as the | |
| # runner, so leftovers are runner-owned; sudo is kept as a defensive | |
| # backstop. Ported from the macOS lane; the proper fix is for the | |
| # harness to reap its own process group, tracked separately. | |
| run: | | |
| sudo pkill -x minvmd || true | |
| sudo pkill -f __krun-vmm || true | |
| - name: Daemon lifecycle (run --detach → status → stop) | |
| # Exercises the supervised daemon path that manages the lifecycle | |
| # state, which the integration harnesses above do not cover. The script | |
| # resolves minvmd from PATH (the testbed dir, added at unpack). | |
| env: | |
| MINVMD_BOOT_LOG: ${{ runner.temp }}/boot-daemon.log | |
| run: ./scripts/minvmd-lifecycle.sh | |
| - name: Fetch pinned gvproxy switch binary | |
| # minvmd spawns this as the per-host-VM switch that provides the guest's | |
| # egress (NAT + DNS). Without it minvmd boots the VM switchless | |
| # (gvproxy resolution is best-effort and never errors), so the guest's | |
| # overlay has no other end and every egress — including the in-guest | |
| # `pkgs` clone during session mint — dies with "Could not resolve host". | |
| run: ./scripts/fetch-gvproxy.sh "$RUNNER_TEMP/gvproxy" | |
| - name: Session e2e via the CLI (activate/exec/destroy; autospawns minvmd) | |
| # The SAME unified proof every target lane runs (scripts/session-e2e.sh) | |
| # against Deployment Model 3 (native Linux + VM — the target this | |
| # lane owns; see docs/specs/03-spec-networking): from a clean state | |
| # `minimal --provider local-minvmd activate` must auto-spawn minvmd, boot the microVM, | |
| # and round-trip a session exec over the vsock bridge — the Linux/KVM | |
| # counterpart of the macOS lane's CLI gate. Since #748 activate uploads | |
| # the project into the guest; E2E_PROJECT_DIR=/tmp still names a guest | |
| # path. minimal/minvmd resolve from the testbed on PATH. | |
| env: | |
| MINVMD_BOOT_LOG: ${{ runner.temp }}/cli-e2e-boot.log | |
| MINVMD_GVPROXY_BIN: ${{ runner.temp }}/gvproxy | |
| run: E2E_VM=1 E2E_MINIMAL_ARGS="--provider local-minvmd" E2E_PROJECT_DIR=/tmp ./scripts/session-e2e.sh | |
| - name: Upload guest boot console logs | |
| if: always() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 | |
| with: | |
| name: minvmd-linux-boot-log-x86_64 | |
| path: | | |
| ${{ runner.temp }}/boot.log | |
| ${{ runner.temp }}/boot-daemon.log | |
| ${{ runner.temp }}/cli-e2e-boot.log | |
| if-no-files-found: ignore | |
| # Single required status check for this lane (branch-protection context: | |
| # ci-linux-kvm-success, joins the ruleset after the soak). Green when | |
| # build + tests succeeded OR were path-skipped; red only on failure/cancellation. | |
| ci-linux-kvm-success: | |
| if: always() | |
| needs: [changes, build-linux, test-kvm] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - name: Verify all gating jobs succeeded or were skipped | |
| if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') | |
| run: exit 1 | |
| - run: echo "Linux-KVM lane green" |