#!/usr/bin/env bash # # Build libkrunfw + libkrun for the HOST Linux architecture from smolvm's # patched submodules, and assemble them into lib/linux-/ in the layout # the release pipeline + smolvm runtime expect. # # Run this ON a native Linux host of the target arch — there is no cross-compile # here. The intended use is layer 3 of Arm cloud support: SSH into a GCP # C4A.metal (Axion arm64) worker and run this to produce lib/linux-aarch64/. # It also works on x86_64 Linux to refresh lib/linux-x86_64/. # # Why a native build: libkrunfw compiles a full Linux kernel (linux-6.12.87 + # patches) with the per-arch config (config-libkrunfw_), and libkrun # embeds a static init ELF of the guest arch. Both are far simpler and more # reliable built natively than cross-compiled. # # NOTE: smolvm's libkrun/libkrunfw are a PATCHED fork (init.c, virtiofs ioctl, # DNS egress, etc.) — upstream prebuilt .so files will not work. Always build # from the in-tree submodules. # # Usage: ./scripts/build-libkrun-linux.sh # GPU=0 skip the GPU feature (drops the virglrenderer dep) # SKIP_DEPS=1 assume build deps are already installed # SKIP_LIBKRUNFW=1 reuse the committed lib/linux-/libkrunfw.so and # rebuild ONLY libkrun. libkrunfw is host-glibc-independent # (it wraps a guest-kernel blob; its floor is GLIBC_2.2), # so when the goal is lowering libkrun's glibc floor there's # no need to recompile the kernel — skips the slow step. set -euo pipefail REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" cd "$REPO_ROOT" if [[ "$(uname -s)" != "Linux" ]]; then echo "ERROR: this script must run on Linux (native build). For macOS use 'make smolvm' in libkrun/." >&2 exit 1 fi ARCH="$(uname -m)" # aarch64 | x86_64 LIBDIR="lib/linux-${ARCH}" GPU="${GPU:-1}" GPU_FLAG="GPU=${GPU}" echo "=== building libkrun stack for linux-${ARCH} -> ${LIBDIR} ===" # --- 1. Build dependencies (Debian/Ubuntu) ----------------------------------- if [[ "${SKIP_DEPS:-0}" != "1" ]] && command -v apt-get >/dev/null 2>&1; then echo "--- installing build dependencies ---" sudo apt-get update -q # kernel build (libkrunfw) + libkrun (rust/bindgen) + git-lfs/runtime sudo apt-get install -y -q \ build-essential flex bison libelf-dev libssl-dev bc cpio rsync kmod \ python3 python3-pyelftools pkg-config clang llvm libclang-dev curl git git-lfs \ $([[ "$GPU" == "1" ]] && echo "libvirglrenderer-dev libepoxy-dev libdrm-dev libgbm-dev" || true) if ! command -v cargo >/dev/null 2>&1; then echo "--- installing rust toolchain ---" curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y # shellcheck disable=SC1091 source "$HOME/.cargo/env" fi fi command -v cargo >/dev/null 2>&1 || { source "$HOME/.cargo/env" 2>/dev/null || true; } # Ensure the submodules + LFS are present (the kernel config lives in-tree). # Tolerate running on rsync'd working trees (no parent .git) — the libkrun / # libkrunfw dirs just need to already contain the patched source. if [ -d .git ]; then git submodule update --init libkrun libkrunfw || true git lfs pull 2>/dev/null || true fi [ -d libkrun/src ] \ || { echo "ERROR: libkrun/ source must be present (clone or rsync it here)." >&2; exit 1; } # Only the libkrunfw kernel source is needed when we actually rebuild it — under # SKIP_LIBKRUNFW the committed lib/linux-/libkrunfw.so is reused, so a # light rsync of just libkrun/ + lib/ is enough (no multi-GB kernel tree). if [[ "${SKIP_LIBKRUNFW:-0}" != "1" ]]; then [ -f libkrunfw/Makefile ] \ || { echo "ERROR: libkrunfw/ source must be present (or set SKIP_LIBKRUNFW=1 to reuse the committed lib)." >&2; exit 1; } fi # bindgen needs to find libclang at runtime on some distros. if [[ -z "${LIBCLANG_PATH:-}" ]]; then LIBCLANG_PATH="$(dirname "$(find /usr/lib -name 'libclang.so*' 2>/dev/null | head -1)")" export LIBCLANG_PATH fi # --- 2. libkrunfw (guest kernel) --------------------------------------------- if [[ "${SKIP_LIBKRUNFW:-0}" == "1" ]]; then echo "--- SKIP_LIBKRUNFW=1: reusing committed ${LIBDIR}/libkrunfw.so.* (host-glibc-independent) ---" KRUNFW_SO="" # signals "do not re-assemble libkrunfw" below else echo "--- building libkrunfw (kernel ${ARCH}; this is the slow step) ---" # Build from INSIDE the dir, NOT `make -C`: `-C` auto-enables -w (print-dir), # which lands in MAKEFLAGS as a bare `w`; the kernel's `$(MAKE) $(MAKEFLAGS)` # recipe then dies with "No rule to make target 'w'". -j scales the kernel build. ( cd libkrunfw && make clean >/dev/null 2>&1 || true; make -j"$(nproc)" GUESTARCH="${ARCH}" ) KRUNFW_SO="$(ls -1 libkrunfw/libkrunfw.so.*.*.* 2>/dev/null | head -1)" [[ -n "$KRUNFW_SO" ]] || { echo "ERROR: libkrunfw build produced no .so" >&2; exit 1; } fi # --- 3. init (guest PID-1) --------------------------------------------------- # Two libkrun layouts, auto-detected by the presence of init/init.c: # - old (<= libkrun 1.x): PID-1 is a static C init compiled here and handed to # the build via KRUN_INIT_BINARY_PATH. # - libkrun 2.0+: PID-1 is a Rust crate (init/) that src/init_blob/build.rs # cross-compiles to musl ITSELF. `init/init` is only an empty placeholder, and # KRUN_INIT_BINARY_PATH must be LEFT UNSET — init_blob/build.rs uses that var # verbatim when set, so pointing it at the empty placeholder embeds a 0-byte # init and bricks the guest (PID-1 dies, VM never reaches agent-ready). The # musl std target must be present or the init links dynamically and can't run # as the guest's PID 1. INIT_ENV=() if [[ -f libkrun/init/init.c ]]; then echo "--- building guest init (legacy C; native ${ARCH} ELF) ---" cc -O2 -static -Wall -o libkrun/init/init libkrun/init/init.c libkrun/init/dhcp.c file libkrun/init/init | grep -q 'statically linked' \ || { echo "ERROR: init/init is not a static ELF" >&2; exit 1; } INIT_ENV=("KRUN_INIT_BINARY_PATH=$(realpath libkrun/init/init)") else echo "--- guest init: Rust crate (init_blob cross-compiles to musl) ---" rustup target add "${ARCH}-unknown-linux-musl" >/dev/null 2>&1 \ || echo "WARN: could not add ${ARCH}-unknown-linux-musl; init may link dynamically" fi # --- 4. libkrun (VMM) -------------------------------------------------------- echo "--- building libkrun (BLK=1 NET=1 ${GPU_FLAG}) ---" # Link with partial RELRO (lazy binding), NOT full RELRO. The GPU-enabled libkrun # carries a hard virglrenderer NEEDED that build-dist.sh strips via patchelf so # non-GPU hosts can dlopen it (paired with RTLD_LAZY in src/agent/krun.rs). That # strip only holds if symbols bind lazily — full RELRO's BIND_NOW would eagerly # resolve the now-unprovided virgl symbols and fail the load. Must match the # relro-level=partial that build-dist.sh applies on its own libkrun compiles. ( cd libkrun && make clean >/dev/null 2>&1 || true; \ env "${INIT_ENV[@]}" \ RUSTFLAGS="${RUSTFLAGS:+$RUSTFLAGS }-C relro-level=partial" \ make --no-print-directory BLK=1 NET=1 "${GPU_FLAG}" ) KRUN_SO="$(ls -1 libkrun/target/release/libkrun.so.*.*.* 2>/dev/null | head -1)" [[ -n "$KRUN_SO" ]] || { echo "ERROR: libkrun build produced no .so" >&2; exit 1; } # --- 5. Assemble lib/linux-/ (mirror the x86_64 layout + sonames) ------ echo "--- assembling ${LIBDIR} ---" mkdir -p "$LIBDIR" assemble() { # $1 = built .so.X.Y.Z $2 = base name (libkrun.so / libkrunfw.so) local built="$1" base="$2" ver fname major fname="$(basename "$built")" # e.g. libkrunfw.so.5.3.0 ver="${fname#"${base}."}" # 5.3.0 major="${ver%%.*}" # 5 cp -f "$built" "$LIBDIR/$fname" ln -sf "$fname" "$LIBDIR/${base}.${major}" # libkrunfw.so.5 -> .so.5.3.0 ln -sf "${base}.${major}" "$LIBDIR/${base}" # libkrunfw.so -> .so.5 } if [[ -n "$KRUNFW_SO" ]]; then assemble "$KRUNFW_SO" "libkrunfw.so" else echo "--- keeping committed ${LIBDIR}/libkrunfw.so (SKIP_LIBKRUNFW) ---" fi # libkrun: x86_64 keeps libkrun.so as a real file too — mirror that. KRUN_FNAME="$(basename "$KRUN_SO")"; KRUN_VER="${KRUN_FNAME#libkrun.so.}"; KRUN_MAJOR="${KRUN_VER%%.*}" cp -f "$KRUN_SO" "$LIBDIR/$KRUN_FNAME" cp -f "$KRUN_SO" "$LIBDIR/libkrun.so" ln -sf "libkrun.so" "$LIBDIR/libkrun.so.${KRUN_MAJOR}" # Record which submodule commits these libs came from, so CI can detect a stale # bundle (scripts/check-libkrun-provenance.sh). When libkrunfw was reused rather # than rebuilt, keep its recorded commit via --skip-libkrunfw. STAMP_ARGS=("$LIBDIR") [[ -z "$KRUNFW_SO" ]] && STAMP_ARGS+=(--skip-libkrunfw) "$(dirname "$0")/stamp-libkrun-provenance.sh" "${STAMP_ARGS[@]}" echo "" echo "=== done: ${LIBDIR} ===" ls -la "$LIBDIR" echo "" echo "Commit (these are git-LFS tracked via .gitattributes 'lib/linux-aarch64/*.so'):" echo " git add ${LIBDIR} && git commit -m 'feat: linux-${ARCH} libkrun/libkrunfw libs'"