docs(skills): ship the a3s-box agent skill + cross-tool installer (#157) #571
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 | |
| on: | |
| push: | |
| branches: [main] | |
| tags: ['v*'] | |
| pull_request: | |
| branches: [main] | |
| workflow_dispatch: {} | |
| env: | |
| CARGO_TERM_COLOR: always | |
| A3S_DEPS_STUB: "1" | |
| jobs: | |
| # ── Lint & Format ────────────────────────────────────────────── | |
| fmt: | |
| name: Format | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Init submodules | |
| run: git -c http.extraheader= submodule update --init --recursive | |
| - uses: dtolnay/rust-toolchain@stable | |
| with: | |
| components: rustfmt | |
| - run: cd src && cargo fmt --all -- --check | |
| clippy: | |
| name: Clippy | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Init submodules | |
| run: git -c http.extraheader= submodule update --init --recursive | |
| - uses: dtolnay/rust-toolchain@stable | |
| with: | |
| components: clippy | |
| - name: Install protoc | |
| run: sudo apt-get update && sudo apt-get install -y protobuf-compiler | |
| - name: Create stub libkrun | |
| run: | | |
| echo "void krun_stub(void) {}" > /tmp/krun_stub.c | |
| gcc -shared -o /tmp/libkrun.so /tmp/krun_stub.c | |
| sudo cp /tmp/libkrun.so /usr/lib/ | |
| sudo ldconfig | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| workspaces: src | |
| - run: cd src && cargo clippy --workspace --all-targets -- -D warnings | |
| # ── Tests (stub mode, no VM) ─────────────────────────────────── | |
| test: | |
| name: Test | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Init submodules | |
| run: git -c http.extraheader= submodule update --init --recursive | |
| - uses: dtolnay/rust-toolchain@stable | |
| - name: Install protoc | |
| run: sudo apt-get update && sudo apt-get install -y protobuf-compiler | |
| - name: Create stub libkrun | |
| run: | | |
| echo "void krun_stub(void) {}" > /tmp/krun_stub.c | |
| gcc -shared -o /tmp/libkrun.so /tmp/krun_stub.c | |
| sudo cp /tmp/libkrun.so /usr/lib/ | |
| sudo ldconfig | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| workspaces: src | |
| - run: cd src && cargo test --workspace --lib | |
| # ── Build check (compile only, no artifacts) ──────────────────── | |
| build-check: | |
| name: Build Check (${{ matrix.target }}) | |
| needs: [fmt, clippy, test] | |
| strategy: | |
| matrix: | |
| include: | |
| - target: linux-x86_64 | |
| os: ubuntu-latest | |
| - target: linux-arm64 | |
| os: ubuntu-24.04-arm | |
| - target: macos-arm64 | |
| os: macos-14 | |
| runs-on: ${{ matrix.os }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Init submodules | |
| run: git -c http.extraheader= submodule update --init --recursive | |
| - uses: dtolnay/rust-toolchain@stable | |
| - name: Install dependencies (Linux) | |
| if: runner.os == 'Linux' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y \ | |
| build-essential pkg-config libssl-dev \ | |
| musl-tools libclang-dev protobuf-compiler | |
| - name: Install dependencies (macOS) | |
| if: runner.os == 'macOS' | |
| run: brew install lld llvm protobuf | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| workspaces: src | |
| key: ${{ matrix.target }} | |
| - name: Check compilation | |
| run: | | |
| cd src | |
| cargo check --release -p a3s-box-cli -p a3s-box-shim | |
| if [ "${{ runner.os }}" = "Linux" ]; then | |
| cargo check --release -p a3s-box-cri | |
| fi | |
| # ── Windows native WHPX build check ──────────────────────────── | |
| build-windows: | |
| name: Build Windows WHPX | |
| needs: [fmt, clippy, test] | |
| runs-on: windows-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Init submodules | |
| run: git -c http.extraheader= submodule update --init --recursive | |
| - uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: x86_64-pc-windows-msvc | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| workspaces: src | |
| key: windows-whpx | |
| - name: Build Windows native binaries | |
| run: | | |
| Remove-Item Env:A3S_DEPS_STUB -ErrorAction SilentlyContinue | |
| cd src | |
| cargo build --release -p a3s-box-cli -p a3s-box-shim --target x86_64-pc-windows-msvc | |
| - name: Upload artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: windows-whpx | |
| path: | | |
| src/target/x86_64-pc-windows-msvc/release/a3s-box.exe | |
| src/target/x86_64-pc-windows-msvc/release/a3s-box-shim.exe | |
| # ── Real microVM integration gate (self-hosted, /dev/kvm) ─────── | |
| # The fmt/clippy/test/build jobs above all run in STUB mode | |
| # (A3S_DEPS_STUB) and never boot a real microVM, so a runtime | |
| # regression (boot, exec, virtio-fs, CRI lifecycle, snapshot-fork) | |
| # cannot be caught by hosted CI — GitHub-hosted runners have no | |
| # /dev/kvm. This job links REAL libkrun and runs the `#[ignore]` | |
| # integration + crictl-smoke suites against a real microVM. | |
| # | |
| # ARMING THE GATE (one-time, operator — see docs/ci-kvm-runner.md): | |
| # 1. Register a self-hosted runner that has /dev/kvm, with labels: | |
| # self-hosted, linux, kvm | |
| # 2. Set repository variable KVM_CI = true | |
| # (Settings -> Secrets and variables -> Actions -> Variables) | |
| # Until both are done this job is SKIPPED (inert) and never blocks a PR. | |
| integration-kvm: | |
| name: Integration (real microVM, KVM) | |
| needs: [fmt, clippy, test] | |
| if: vars.KVM_CI == 'true' | |
| runs-on: [self-hosted, linux, kvm] | |
| timeout-minutes: 75 | |
| # The runner has no direct docker.io egress (CN network), so the smoke | |
| # tests pull alpine/busybox through a mirror. The image keeps its canonical | |
| # docker.io identity; only the fetch is redirected. Override per-runner with | |
| # the KVM_CI_REGISTRY_MIRRORS repo variable. | |
| env: | |
| A3S_REGISTRY_MIRRORS: ${{ vars.KVM_CI_REGISTRY_MIRRORS || 'docker.io=docker.m.daocloud.io' }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Init submodules | |
| run: git -c http.extraheader= submodule update --init --recursive | |
| # The self-hosted runner already has a working rustup/cargo. Using | |
| # dtolnay/rust-toolchain forces rustup to re-sync the whole `stable` | |
| # channel (7 components) on every run — ~20 min on this box's network, | |
| # which blew the job timeout. Use the runner's existing toolchain: add the | |
| # musl target (a fast no-op if already installed) and put cargo on PATH | |
| # for later steps (the runner service env does not source a login shell). | |
| - name: Use the runner's existing Rust toolchain | |
| run: | | |
| echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" | |
| "$HOME/.cargo/bin/rustup" target add x86_64-unknown-linux-musl | |
| # NOTE: no Swatinem/rust-cache here — fetching that action from | |
| # codeload.github.com times out on this runner's network, which fails | |
| # "Set up job". The self-hosted runner keeps a warm cargo registry, and | |
| # the 75-min budget covers a cold libkrun build. | |
| - name: Verify /dev/kvm is present | |
| run: | | |
| if [ ! -e /dev/kvm ]; then | |
| echo "::error::/dev/kvm missing — this runner cannot boot microVMs"; exit 1 | |
| fi | |
| echo "/dev/kvm present" | |
| # Build REAL binaries: unset the stub so libkrun-sys compiles the | |
| # vendored libkrun (build.rs gates on env::var("A3S_DEPS_STUB").is_ok(), | |
| # so the var must be UNSET, not just emptied). | |
| - name: Build real binaries + musl guest-init (no stub) | |
| run: | | |
| unset A3S_DEPS_STUB | |
| cd src | |
| cargo build --release -p a3s-box-guest-init --target x86_64-unknown-linux-musl | |
| cargo build --release -p a3s-box-cli -p a3s-box-cri -p a3s-box-shim | |
| - name: Core smoke — real boot + exec over virtio-fs | |
| run: | | |
| unset A3S_DEPS_STUB | |
| cd src | |
| cargo test --release -p a3s-box-cli --test core_smoke -- --ignored --nocapture --test-threads=1 | |
| - name: CRI crictl smoke — full pod/container lifecycle | |
| env: | |
| A3S_BOX_CRI_SMOKE: "1" | |
| A3S_BOX_CRI_SMOKE_AGENT_IMAGE: ${{ vars.KVM_CI_AGENT_IMAGE || 'docker.m.daocloud.io/library/alpine:latest' }} | |
| # A3S_REGISTRY_MIRRORS inherited from the job env (covers docker.io). | |
| run: | | |
| unset A3S_DEPS_STUB | |
| cd src | |
| cargo test --release -p a3s-box-cri --test crictl_smoke -- --ignored --nocapture --test-threads=1 | |
| # Gate leak-freeness under churn against REAL VMs: create/run/remove cycles | |
| # must return orphan shims / overlay mounts / box dirs to baseline. Exits | |
| # non-zero on a leak, so a resource-leak regression fails the gate. | |
| - name: Leak assertion — no orphan shims/mounts/box-dirs after churn | |
| env: | |
| A3S_BOX: ${{ github.workspace }}/src/target/release/a3s-box | |
| CHURN: "20" | |
| run: | | |
| unset A3S_DEPS_STUB | |
| chmod +x bench/bench.sh | |
| bench/bench.sh leak | |
| # Gate the cross-process advisory lock on boxes.json against REAL VMs: | |
| # concurrent `run -d` processes must NOT lose each other's state writes. | |
| # The unit tests only race in-process threads, so a broken/missing flock | |
| # across separate a3s-box processes is invisible to them — this boots a | |
| # fleet at once and asserts no lost update + intact JSON. Exits non-zero | |
| # on a lost update, failing the gate. | |
| - name: Race assertion — no lost update on boxes.json under concurrent run -d | |
| env: | |
| A3S_BOX: ${{ github.workspace }}/src/target/release/a3s-box | |
| RACE: "8" | |
| run: | | |
| unset A3S_DEPS_STUB | |
| chmod +x bench/bench.sh | |
| bench/bench.sh race |