Merge branch 'b2-aarch64-binary' into integration-7feat

This commit is contained in:
Evgeny Boger 2026-06-01 11:20:51 +00:00
commit 45655fb773
10 changed files with 262 additions and 18 deletions

15
.cargo/config.toml Normal file
View file

@ -0,0 +1,15 @@
# Cross-compilation linkers.
#
# The `agent-vm` binary and the patched `msb` link a handful of C
# libraries (libcap-ng, libdbus, libsqlite3) plus the usual libc, so a
# cross build needs the matching cross linker — the default `cc` only
# knows how to emit host (x86_64) objects.
#
# CI (release-npm.yml, build-agent-vm/build-msb aarch64 matrix legs)
# installs `gcc-aarch64-linux-gnu` for the linker and the arm64 dev
# libraries (`libcap-ng-dev:arm64 libdbus-1-dev:arm64
# libsqlite3-dev:arm64`, enabled via `dpkg --add-architecture arm64`)
# so the final link resolves. Locally, install the same Debian/Ubuntu
# packages to reproduce.
[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"

View file

@ -62,6 +62,16 @@ jobs:
- platform: linux-x64 - platform: linux-x64
runner: ubuntu-latest runner: ubuntu-latest
cargo_target: x86_64-unknown-linux-gnu cargo_target: x86_64-unknown-linux-gnu
# linux-arm64 is cross-compiled on the x64 runner (GitHub's
# hosted arm64 Linux runners aren't on the free tier).
# `gcc-aarch64-linux-gnu` provides the cross linker (wired
# up by the repo .cargo/config.toml); the arm64 dev libs the
# final link needs come in via `dpkg --add-architecture
# arm64` (see the install step).
- platform: linux-arm64
runner: ubuntu-latest
cargo_target: aarch64-unknown-linux-gnu
cross: true
runs-on: ${{ matrix.runner }} runs-on: ${{ matrix.runner }}
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v5
@ -75,12 +85,28 @@ jobs:
submodules: 'recursive' submodules: 'recursive'
- name: Install Rust toolchain + linux deps - name: Install Rust toolchain + linux deps
env:
CROSS: ${{ matrix.cross && '1' || '' }}
run: | run: |
set -euo pipefail
rustup toolchain install stable --profile minimal --no-self-update rustup toolchain install stable --profile minimal --no-self-update
rustup target add ${{ matrix.cargo_target }} rustup target add ${{ matrix.cargo_target }}
sudo apt-get update if [[ -n "$CROSS" ]]; then
sudo apt-get install -y --no-install-recommends \ # Enable the arm64 multiarch repo, then pull the cross
libcap-ng-dev libdbus-1-dev libsqlite3-dev pkg-config # linker + arm64 builds of the C libs agent-vm /
# microsandbox link (libcap-ng, libdbus, libsqlite3).
# Without the `:arm64` dev libs the final link can't
# resolve -lcap-ng / -ldbus-1 / -lsqlite3 for the target.
sudo dpkg --add-architecture arm64
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
gcc-aarch64-linux-gnu pkg-config \
libcap-ng-dev:arm64 libdbus-1-dev:arm64 libsqlite3-dev:arm64
else
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
libcap-ng-dev libdbus-1-dev libsqlite3-dev pkg-config
fi
- name: Cargo cache (rust-cache) - name: Cargo cache (rust-cache)
uses: Swatinem/rust-cache@v2 uses: Swatinem/rust-cache@v2
@ -110,7 +136,20 @@ jobs:
cache-workspace-crates: "true" cache-workspace-crates: "true"
- name: cargo build --release -p agent-vm - name: cargo build --release -p agent-vm
run: cargo build --release --target ${{ matrix.cargo_target }} -p agent-vm env:
# Cross pkg-config: point at the arm64 .pc files and let
# pkg-config run host→target (the `pkg-config` crate refuses
# cross queries unless PKG_CONFIG_ALLOW_CROSS=1). No-ops on
# the native x64 leg (CROSS unset).
CROSS: ${{ matrix.cross && '1' || '' }}
run: |
set -euo pipefail
if [[ -n "$CROSS" ]]; then
export PKG_CONFIG_ALLOW_CROSS=1
export PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig
export PKG_CONFIG_SYSROOT_DIR=/
fi
cargo build --release --target ${{ matrix.cargo_target }} -p agent-vm
- name: Upload agent-vm binary - name: Upload agent-vm binary
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v7
@ -129,6 +168,12 @@ jobs:
- platform: linux-x64 - platform: linux-x64
runner: ubuntu-latest runner: ubuntu-latest
cargo_target: x86_64-unknown-linux-gnu cargo_target: x86_64-unknown-linux-gnu
# linux-arm64 cross-build — see build-agent-vm for the
# rationale (no free hosted arm64 Linux runner).
- platform: linux-arm64
runner: ubuntu-latest
cargo_target: aarch64-unknown-linux-gnu
cross: true
runs-on: ${{ matrix.runner }} runs-on: ${{ matrix.runner }}
env: env:
# Override vendor/microsandbox's `lto=true, codegen-units=1` # Override vendor/microsandbox's `lto=true, codegen-units=1`
@ -138,18 +183,34 @@ jobs:
# runtime cost on a TLS-proxy workload. # runtime cost on a TLS-proxy workload.
CARGO_PROFILE_RELEASE_LTO: "thin" CARGO_PROFILE_RELEASE_LTO: "thin"
CARGO_PROFILE_RELEASE_CODEGEN_UNITS: "16" CARGO_PROFILE_RELEASE_CODEGEN_UNITS: "16"
# The superproject's .cargo/config.toml sets the aarch64 linker,
# but it is NOT on the config search path when building from
# vendor/microsandbox (a separate workspace), so set the linker
# via env here too. No-op on the native x64 leg.
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v5
with: with:
submodules: 'recursive' submodules: 'recursive'
- name: Install Rust toolchain + linux deps - name: Install Rust toolchain + linux deps
env:
CROSS: ${{ matrix.cross && '1' || '' }}
run: | run: |
set -euo pipefail
rustup toolchain install stable --profile minimal --no-self-update rustup toolchain install stable --profile minimal --no-self-update
rustup target add ${{ matrix.cargo_target }} rustup target add ${{ matrix.cargo_target }}
sudo apt-get update if [[ -n "$CROSS" ]]; then
sudo apt-get install -y --no-install-recommends \ sudo dpkg --add-architecture arm64
libcap-ng-dev libdbus-1-dev libsqlite3-dev pkg-config sudo apt-get update
sudo apt-get install -y --no-install-recommends \
gcc-aarch64-linux-gnu pkg-config \
libcap-ng-dev:arm64 libdbus-1-dev:arm64 libsqlite3-dev:arm64
else
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
libcap-ng-dev libdbus-1-dev libsqlite3-dev pkg-config
fi
- name: Cargo cache (rust-cache) - name: Cargo cache (rust-cache)
uses: Swatinem/rust-cache@v2 uses: Swatinem/rust-cache@v2
@ -171,7 +232,15 @@ jobs:
cache-workspace-crates: "true" cache-workspace-crates: "true"
- name: cargo build --release -p microsandbox-cli --bin msb - name: cargo build --release -p microsandbox-cli --bin msb
env:
CROSS: ${{ matrix.cross && '1' || '' }}
run: | run: |
set -euo pipefail
if [[ -n "$CROSS" ]]; then
export PKG_CONFIG_ALLOW_CROSS=1
export PKG_CONFIG_PATH=/usr/lib/aarch64-linux-gnu/pkgconfig
export PKG_CONFIG_SYSROOT_DIR=/
fi
cargo build --release --target ${{ matrix.cargo_target }} \ cargo build --release --target ${{ matrix.cargo_target }} \
--manifest-path vendor/microsandbox/Cargo.toml \ --manifest-path vendor/microsandbox/Cargo.toml \
-p microsandbox-cli --bin msb -p microsandbox-cli --bin msb
@ -199,6 +268,15 @@ jobs:
- platform: linux-x64 - platform: linux-x64
runner: ubuntu-latest runner: ubuntu-latest
arch: x86_64 arch: x86_64
# linux-arm64: cross-build the kernel with ARCH=arm64 +
# CROSS_COMPILE=aarch64-linux-gnu-. Needs the arm64 kbuild
# .config seed patch (libkrunfw-overrides/
# config-libkrunfw_aarch64.patch); the build step fails fast
# with a clear message if that seed hasn't been ported yet.
- platform: linux-arm64
runner: ubuntu-latest
arch: aarch64
cross: true
runs-on: ${{ matrix.runner }} runs-on: ${{ matrix.runner }}
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v5
@ -231,12 +309,20 @@ jobs:
- name: Install kernel build deps - name: Install kernel build deps
if: steps.lk-cache.outputs.cache-hit != 'true' if: steps.lk-cache.outputs.cache-hit != 'true'
env:
CROSS: ${{ matrix.cross && '1' || '' }}
run: | run: |
set -euo pipefail
sudo apt-get update sudo apt-get update
sudo apt-get install -y --no-install-recommends \ sudo apt-get install -y --no-install-recommends \
build-essential bc flex bison libelf-dev libssl-dev \ build-essential bc flex bison libelf-dev libssl-dev \
kmod cpio bzip2 xz-utils python3 python3-pyelftools \ kmod cpio bzip2 xz-utils python3 python3-pyelftools \
curl ca-certificates patch curl ca-certificates patch
if [[ -n "$CROSS" ]]; then
# Cross toolchain for the arm64 kernel build.
sudo apt-get install -y --no-install-recommends \
gcc-aarch64-linux-gnu
fi
- name: Clone + patch + build libkrunfw - name: Clone + patch + build libkrunfw
if: steps.lk-cache.outputs.cache-hit != 'true' if: steps.lk-cache.outputs.cache-hit != 'true'
@ -244,11 +330,24 @@ jobs:
env: env:
LK_V: ${{ steps.lkv.outputs.libkrunfw_version }} LK_V: ${{ steps.lkv.outputs.libkrunfw_version }}
ARCH: ${{ matrix.arch }} ARCH: ${{ matrix.arch }}
CROSS: ${{ matrix.cross && '1' || '' }}
run: | run: |
set -euo pipefail set -euo pipefail
# The arm64 leg cross-compiles the kernel. It needs an arm64
# kbuild .config seed; bail early with a clear message rather
# than silently emit a kernel built with the wrong config.
if [[ -n "$CROSS" && ! -f "libkrunfw-overrides/config-libkrunfw_${ARCH}.patch" ]]; then
echo "::error::libkrunfw-overrides/config-libkrunfw_${ARCH}.patch is missing — the arm64 libkrunfw kernel config seed has not been ported yet (see B2 notes)."
exit 1
fi
git clone --branch "v${LK_V}" --depth 1 \ git clone --branch "v${LK_V}" --depth 1 \
https://github.com/containers/libkrunfw.git libkrunfw-src https://github.com/containers/libkrunfw.git libkrunfw-src
cd libkrunfw-src cd libkrunfw-src
# Cross build env for arm64: make picks up ARCH/CROSS_COMPILE.
if [[ -n "$CROSS" ]]; then
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
fi
# 1) kbuild .config seed (CONFIG_KVM=y + Intel/AMD backends). # 1) kbuild .config seed (CONFIG_KVM=y + Intel/AMD backends).
patch -p1 < ../libkrunfw-overrides/config-libkrunfw_${ARCH}.patch patch -p1 < ../libkrunfw-overrides/config-libkrunfw_${ARCH}.patch
# 2) Kernel source patches: libkrunfw's Makefile runs # 2) Kernel source patches: libkrunfw's Makefile runs
@ -303,6 +402,9 @@ jobs:
- platform: linux-x64 - platform: linux-x64
runner: ubuntu-latest runner: ubuntu-latest
libkrunfw_arch: x86_64 libkrunfw_arch: x86_64
- platform: linux-arm64
runner: ubuntu-latest
libkrunfw_arch: aarch64
runs-on: ${{ matrix.runner }} runs-on: ${{ matrix.runner }}
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v5
@ -419,6 +521,12 @@ jobs:
name: agent-vm-linux-x64 name: agent-vm-linux-x64
path: npm-dist/agent-vm-linux-x64/ path: npm-dist/agent-vm-linux-x64/
- name: Download linux-arm64 artifact
uses: actions/download-artifact@v8
with:
name: agent-vm-linux-arm64
path: npm-dist/agent-vm-linux-arm64/
- name: Verify artifact layout + restore executable bits - name: Verify artifact layout + restore executable bits
# `actions/upload-artifact@v7` packs files into a zip that # `actions/upload-artifact@v7` packs files into a zip that
# doesn't preserve POSIX permissions. After download, the # doesn't preserve POSIX permissions. After download, the

View file

@ -9,15 +9,14 @@ Templates and tooling for distributing `agent-vm` via npm.
and `execve`s the prebuilt native binary from the matching and `execve`s the prebuilt native binary from the matching
per-platform subpackage. Declares per-platform subpackages as per-platform subpackage. Declares per-platform subpackages as
`optionalDependencies` so npm installs only the right one. `optionalDependencies` so npm installs only the right one.
- `agent-vm-linux-x64/` — per-platform subpackage. Ships the - `agent-vm-linux-x64/`, `agent-vm-linux-arm64/` — per-platform
prebuilt `bin/agent-vm`, the patched `bin/msb`, and subpackages. Each ships the prebuilt `bin/agent-vm`, the patched
`lib/libkrunfw.so.5.2.1`. agent-vm finds `msb` and `libkrunfw` via `bin/msb`, and `lib/libkrunfw.so.5.2.1`. agent-vm finds `msb` and
`current_exe()`-relative paths so a user's separate microsandbox `libkrunfw` via `current_exe()`-relative paths so a user's separate
install never shadows them. microsandbox install never shadows them.
- Future per-platform subpackages: `-linux-arm64`, `-darwin-arm64`, - Future per-platform subpackages: `-darwin-arm64`, `-darwin-x64`,
`-darwin-x64`, `-win32-x64`. Add to the launcher's `-win32-x64`. Add to the launcher's `PLATFORM_PACKAGES` map and to
`PLATFORM_PACKAGES` map and to the main package's the main package's `optionalDependencies`.
`optionalDependencies`.
## How releases happen ## How releases happen

View file

@ -0,0 +1,9 @@
# @wirenboard/agent-vm-linux-arm64
Prebuilt `agent-vm` binary + patched `msb` + libkrunfw for linux/arm64.
You don't install this directly. The user-facing package is
[`@wirenboard/agent-vm`](https://www.npmjs.com/package/@wirenboard/agent-vm),
which pulls this in as an `optionalDependency` on linux/arm64 hosts.
Source: <https://github.com/wirenboard/agent-vm>.

View file

@ -0,0 +1,22 @@
{
"name": "@wirenboard/agent-vm-linux-arm64",
"version": "0.0.0",
"description": "agent-vm prebuilt binary + patched msb + libkrunfw for linux-arm64.",
"homepage": "https://github.com/wirenboard/agent-vm",
"repository": {
"type": "git",
"url": "git+https://github.com/wirenboard/agent-vm.git"
},
"license": "MIT",
"os": ["linux"],
"cpu": ["arm64"],
"files": [
"bin/agent-vm",
"bin/msb",
"lib/libkrunfw.so.*",
"README.md"
],
"publishConfig": {
"access": "public"
}
}

View file

@ -0,0 +1,90 @@
#!/usr/bin/env node
// Deterministic dispatch test for the npm launcher (bin/agent-vm.js).
//
// Why this exists: the launcher's job is to map the running
// platform/arch to a per-platform subpackage and then build the
// `bin/agent-vm` path inside it. arm64 was added to that mapping in
// B2, but arm64 dispatch cannot be exercised on an x86_64 CI host —
// `node` reports the host's real process.arch, so simply running the
// launcher there only ever exercises the linux-x64 leg. This test
// closes that gap without a real arm64 runtime by re-deriving the
// mapping + path logic from the launcher source and asserting the
// linux-arm64 key resolves to the expected package and to the same
// bin/ layout as linux-x64.
//
// Run standalone: `node bin/agent-vm.dispatch.test.js` (no test
// harness / deps — matches the repo convention of sanity-checking the
// launcher with plain `node`/`node --check`; there is no Node test
// runner or root package.json in this repo).
//
// It must stay in lockstep with the real launcher: it extracts the
// live PLATFORM_PACKAGES object out of agent-vm.js (rather than
// hard-coding a copy) so a future edit to the mapping that forgets
// arm64 — or renames the package — fails here instead of silently
// shipping a launcher that falls through to "unsupported platform"
// on arm64 hardware.
"use strict";
const assert = require("node:assert");
const path = require("node:path");
const fs = require("node:fs");
const vm = require("node:vm");
const launcherPath = path.join(__dirname, "agent-vm.js");
const src = fs.readFileSync(launcherPath, "utf8");
// Pull the `PLATFORM_PACKAGES = { ... };` object-literal out of the
// launcher source and evaluate just that literal in an isolated VM
// context. We deliberately do NOT `require()` the launcher: it runs
// its dispatch + process.exit() at module load, so requiring it would
// terminate this test process.
const m = src.match(/const\s+PLATFORM_PACKAGES\s*=\s*(\{[\s\S]*?\});/);
assert.ok(m, "could not locate PLATFORM_PACKAGES object literal in agent-vm.js");
const PLATFORM_PACKAGES = vm.runInNewContext(`(${m[1]})`);
// Mirror the launcher's binPath construction (the
// `path.join(dir, "bin", "agent-vm"+ext)` line) so we assert the SAME
// layout the launcher actually uses.
function binPathFor(pkgDir, platform) {
const ext = platform === "win32" ? ".exe" : "";
return path.join(pkgDir, "bin", `agent-vm${ext}`);
}
// 1) The new arm64 key must resolve to the arm64 subpackage.
assert.strictEqual(
PLATFORM_PACKAGES["linux-arm64"],
"@wirenboard/agent-vm-linux-arm64",
"linux-arm64 must map to @wirenboard/agent-vm-linux-arm64",
);
// 2) x64 must still resolve (guards against an accidental clobber).
assert.strictEqual(
PLATFORM_PACKAGES["linux-x64"],
"@wirenboard/agent-vm-linux-x64",
"linux-x64 must map to @wirenboard/agent-vm-linux-x64",
);
// 3) Both linux platforms must produce the identical bin/ layout
// (only the package dir differs) — the arm64 subpackage ships its
// binary at bin/agent-vm exactly like x64 (see its package.json
// `files` list + the bin/.gitkeep placeholder).
const x64Bin = binPathFor("/pkg/agent-vm-linux-x64", "linux");
const arm64Bin = binPathFor("/pkg/agent-vm-linux-arm64", "linux");
assert.strictEqual(path.basename(x64Bin), "agent-vm");
assert.strictEqual(path.basename(arm64Bin), "agent-vm");
assert.strictEqual(
path.relative("/pkg/agent-vm-linux-x64", x64Bin),
path.relative("/pkg/agent-vm-linux-arm64", arm64Bin),
"arm64 and x64 must use the same bin/ layout",
);
// 4) A genuinely unsupported platform key must be absent so the
// launcher hits its "no prebuilt binary" error path cleanly.
assert.strictEqual(
PLATFORM_PACKAGES["sunos-sparc"],
undefined,
"unsupported platform keys must be absent (no fall-through entry)",
);
console.log("agent-vm dispatch test: OK (linux-x64, linux-arm64)");

View file

@ -22,7 +22,7 @@ const fs = require("node:fs");
const PLATFORM_PACKAGES = { const PLATFORM_PACKAGES = {
"linux-x64": "@wirenboard/agent-vm-linux-x64", "linux-x64": "@wirenboard/agent-vm-linux-x64",
// "linux-arm64": "@wirenboard/agent-vm-linux-arm64", "linux-arm64": "@wirenboard/agent-vm-linux-arm64",
// "darwin-arm64": "@wirenboard/agent-vm-darwin-arm64", // "darwin-arm64": "@wirenboard/agent-vm-darwin-arm64",
// "darwin-x64": "@wirenboard/agent-vm-darwin-x64", // "darwin-x64": "@wirenboard/agent-vm-darwin-x64",
// "win32-x64": "@wirenboard/agent-vm-win32-x64", // "win32-x64": "@wirenboard/agent-vm-win32-x64",

View file

@ -22,7 +22,8 @@
"node": ">=18" "node": ">=18"
}, },
"optionalDependencies": { "optionalDependencies": {
"@wirenboard/agent-vm-linux-x64": "0.0.0" "@wirenboard/agent-vm-linux-x64": "0.0.0",
"@wirenboard/agent-vm-linux-arm64": "0.0.0"
}, },
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"