name: ci on: push: branches: [main] pull_request: permissions: contents: read concurrency: group: ci-${{ github.ref }} cancel-in-progress: true env: CARGO_TERM_COLOR: always jobs: test: name: fmt, clippy, test runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: components: clippy, rustfmt - uses: Swatinem/rust-cache@v2 # Same order as the pre-commit list in AGENTS.md. - name: cargo fmt run: cargo fmt --all -- --check - name: cargo clippy run: cargo clippy --all-targets -- -D warnings - name: cargo test run: cargo test macos: name: macos — fmt, clippy, test, boot runs-on: macos-26 timeout-minutes: 45 steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: components: clippy, rustfmt - uses: Swatinem/rust-cache@v2 # The engine is dlopen'd from the brew kegs at runtime; e2fsprogs # supplies mke2fs and libarchive its tar path. - name: Install the engine and the image tooling run: brew install libkrun/homebrew-krun/libkrun e2fsprogs libarchive - name: cargo fmt run: cargo fmt --all -- --check - name: cargo clippy run: cargo clippy --all-targets -- -D warnings - name: cargo test run: cargo test - name: Build the CLI run: cargo build --release -p ecrou-cli # HVF grants VM creation only to binaries carrying the hypervisor # entitlement on current macOS; packaging signs the same way. - name: Sign the binary with the hypervisor entitlement run: codesign --force --sign - --entitlements packaging/macos/ecrou.entitlements target/release/ecrou - name: Probe run: ./target/release/ecrou probe - name: Boot alpine run: | set -euo pipefail guest="$(./target/release/ecrou run --quiet alpine -- uname -r | tail -n 1)" echo "guest kernel: $guest" test -n "$guest" t6: name: T6 strace audit runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - name: Install strace and the image tooling run: | sudo apt-get update -qq # mke2fs populates from a tarball through a runtime dlopen of # libarchive; the runner has e2fsprogs but not the lib. sudo apt-get install -y -qq strace libarchive13t64 \ || sudo apt-get install -y -qq strace libarchive13 # The runner exposes nested KVM; the runner user needs rw on the # device node for the probe to pass. - name: Open /dev/kvm run: | test -c /dev/kvm || { echo "::error::no /dev/kvm on this runner"; exit 1; } test -w /dev/kvm || sudo chmod 666 /dev/kvm # The engine is dlopen'd at runtime: build libkrun from the same # tag the snap bundles, and take libkrunfw from the pool the way # the snap's libkrunfw part does. - name: Build the engine run: | curl -fsSL -o /tmp/libkrunfw5.deb \ https://archive.ubuntu.com/ubuntu/pool/universe/libk/libkrunfw/libkrunfw5_5.5.0+ds-1_amd64.deb echo "924cd545080aaf5da66740d12a51b7a5cbd9a5c0b502d21cbef9bc82bf1c8b6b /tmp/libkrunfw5.deb" | sha256sum -c dpkg -x /tmp/libkrunfw5.deb /tmp/libkrunfw git clone --depth 1 --branch v1.19.7 https://github.com/containers/libkrun /tmp/libkrun cargo build --release --manifest-path /tmp/libkrun/Cargo.toml -p libkrun --features net,blk mkdir /tmp/engine cp /tmp/libkrun/target/release/libkrun.so /tmp/engine/libkrun.so.1 cp /tmp/libkrunfw/usr/lib/x86_64-linux-gnu/libkrunfw.so.5.5.0 /tmp/engine/ ln -s libkrunfw.so.5.5.0 /tmp/engine/libkrunfw.so.5 - name: Build the CLI run: cargo build --release -p ecrou-cli - name: T6 — no denied syscalls in the host process tree env: ECROU_BIN: ${{ github.workspace }}/target/release/ecrou LD_LIBRARY_PATH: /tmp/engine run: cargo test -p ecrou-cli -- --ignored snap: name: snap build runs-on: ubuntu-latest timeout-minutes: 30 steps: - uses: actions/checkout@v4 # The same pack the release workflow publishes. Building it on # every change keeps the snap from rotting between tags: the # cargo part, the libkrun build, and the libkrunfw digest pins # all break here first. - uses: snapcore/action-build@v1 id: build - uses: actions/upload-artifact@v4 with: name: snap-amd64 path: ${{ steps.build.outputs.snap }} e2e: name: snap e2e — apt install needs: snap runs-on: ubuntu-latest timeout-minutes: 45 steps: - uses: actions/download-artifact@v4 with: name: snap-amd64 path: snap - name: Install the snap run: | test -c /dev/kvm || { echo "::error::no /dev/kvm on this runner"; exit 1; } test -w /dev/kvm || sudo chmod 666 /dev/kvm sudo snap install --dangerous ./snap/*.snap sudo snap connect ecrou:kvm # Diagnostics for whatever fails below: KVM state, the engine # load from the snap's staged libraries, the cache directory. - name: Probe run: ecrou probe || true # T8's first half, on the packaged binary: a guest kernel that # differs from the host's, inside strict confinement. Fast, so a # broken environment fails here instead of deep in the apt run. - name: Smoke — boot alpine under confinement run: | set -euo pipefail guest="$(timeout 10m ecrou run --quiet alpine -- uname -r | tail -n 1)" host="$(uname -r)" echo "guest kernel: $guest / host kernel: $host" test -n "$guest" && test "$guest" != "$host" # T8's second half: a real package install end to end — apt # through the TSI proxy, mkfs and debugfs through the snap's # staged e2fsprogs, the payload's exit code back through the # control file. The ln driver's known throughput limit makes # this the long pole; how long it takes is the standing # re-measurement the Networking section calls for on every # engine update. - name: apt update && install fastfetch run: | set -euo pipefail # --pipe --logo none: the payload's stdout is a serial # console, which fastfetch reads as a tty and decorates with # cursor escapes (a first local run grepped nothing); plain # output keeps the receipt readable. timeout 35m ecrou run --quiet -e DEBIAN_FRONTEND=noninteractive ubuntu -- \ "apt update && apt install -y fastfetch && fastfetch --pipe --logo none" \ | tee /tmp/e2e.log # Insurance, not the primary check: strip any escape # sequences before anchoring on the fastfetch key. sed -e 's/\x1b\[[0-9;?]*[A-Za-z]//g' /tmp/e2e.log | grep -E "^OS:"