mirror of
https://github.com/NVIDIA/OpenShell.git
synced 2026-10-20 06:00:06 +08:00
291 lines
10 KiB
Bash
Executable File
291 lines
10 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
# Build libkrun and libkrunfw from source on Linux.
|
|
#
|
|
# This script builds libkrun (VMM) and libkrunfw (kernel firmware) from source
|
|
# with OpenShell's custom kernel configuration for bridge/netfilter support.
|
|
#
|
|
# In addition to the platform's native .so artifacts, this script exports
|
|
# kernel.c and ABI_VERSION metadata so that other platforms (e.g. macOS) can
|
|
# compile their own libkrunfw wrapper without rebuilding the kernel.
|
|
#
|
|
# Prerequisites:
|
|
# - Linux (aarch64 or x86_64)
|
|
# - Build tools: make, git, gcc, flex, bison, bc
|
|
# - Python 3 with pyelftools
|
|
# - Rust toolchain
|
|
#
|
|
# Usage:
|
|
# ./build-libkrun.sh
|
|
#
|
|
# The script will install missing dependencies on Debian/Ubuntu and Fedora.
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
source "${SCRIPT_DIR}/_lib.sh"
|
|
ROOT="$(vm_lib_root)"
|
|
|
|
# Source pinned dependency versions
|
|
source "${ROOT}/crates/openshell-vm/pins.env" 2>/dev/null || true
|
|
|
|
BUILD_DIR="${ROOT}/target/libkrun-build"
|
|
OUTPUT_DIR="${BUILD_DIR}"
|
|
KERNEL_CONFIG="${ROOT}/crates/openshell-vm/runtime/kernel/openshell.kconfig"
|
|
|
|
if [ "$(uname -s)" != "Linux" ]; then
|
|
echo "Error: This script only runs on Linux" >&2
|
|
exit 1
|
|
fi
|
|
|
|
HOST_ARCH="$(uname -m)"
|
|
echo "==> Building libkrun for Linux ${HOST_ARCH}"
|
|
echo " Build directory: ${BUILD_DIR}"
|
|
echo " Kernel config: ${KERNEL_CONFIG}"
|
|
echo ""
|
|
|
|
# Map host arch to kernel ARCH value
|
|
case "$HOST_ARCH" in
|
|
aarch64) KARCH="arm64"; KERNEL_IMAGE_PATH="arch/arm64/boot/Image" ;;
|
|
x86_64) KARCH="x86"; KERNEL_IMAGE_PATH="arch/x86/boot/bzImage" ;;
|
|
*) echo "Error: Unsupported architecture: ${HOST_ARCH}" >&2; exit 1 ;;
|
|
esac
|
|
|
|
# ── Install dependencies ────────────────────────────────────────────────
|
|
|
|
# Use sudo only when not already running as root (e.g. inside CI containers).
|
|
SUDO=""
|
|
if [ "$(id -u)" -ne 0 ]; then
|
|
SUDO="sudo"
|
|
fi
|
|
|
|
install_deps() {
|
|
echo "==> Checking/installing build dependencies..."
|
|
|
|
if command -v apt-get &>/dev/null; then
|
|
# Debian/Ubuntu
|
|
DEPS="build-essential git python3 python3-pip python3-pyelftools flex bison libelf-dev libssl-dev bc curl libclang-dev cpio zstd jq"
|
|
MISSING=""
|
|
for dep in $DEPS; do
|
|
if ! dpkg -s "$dep" &>/dev/null; then
|
|
MISSING="$MISSING $dep"
|
|
fi
|
|
done
|
|
if [ -n "$MISSING" ]; then
|
|
echo " Installing:$MISSING"
|
|
$SUDO apt-get update
|
|
$SUDO apt-get install -y $MISSING
|
|
else
|
|
echo " All dependencies installed"
|
|
fi
|
|
|
|
elif command -v dnf &>/dev/null; then
|
|
# Fedora/RHEL
|
|
DEPS="make git python3 python3-pyelftools gcc flex bison elfutils-libelf-devel openssl-devel bc glibc-static curl clang-devel cpio zstd jq"
|
|
echo " Installing dependencies via dnf..."
|
|
$SUDO dnf install -y $DEPS
|
|
|
|
else
|
|
echo "Warning: Unknown package manager. Please install manually:" >&2
|
|
echo " build-essential git python3 python3-pyelftools flex bison" >&2
|
|
echo " libelf-dev libssl-dev bc curl cpio" >&2
|
|
fi
|
|
|
|
# Ensure pyelftools is importable by the Python that will run bin2cbundle.py.
|
|
# The apt package may install to a different Python than the default python3.
|
|
if ! python3 -c "import elftools" &>/dev/null; then
|
|
echo " pyelftools not importable, installing via pip..."
|
|
python3 -m pip install --break-system-packages pyelftools 2>/dev/null || \
|
|
python3 -m pip install pyelftools || true
|
|
fi
|
|
}
|
|
|
|
install_deps
|
|
|
|
# ── Setup build directory ───────────────────────────────────────────────
|
|
|
|
mkdir -p "$BUILD_DIR"
|
|
cd "$BUILD_DIR"
|
|
|
|
# ── Build libkrunfw (kernel firmware) ───────────────────────────────────
|
|
|
|
echo ""
|
|
echo "==> Building libkrunfw with custom kernel config..."
|
|
|
|
if [ ! -d libkrunfw ]; then
|
|
echo " Cloning libkrunfw (pinned: ${LIBKRUNFW_REF:-HEAD})..."
|
|
git clone https://github.com/containers/libkrunfw.git
|
|
fi
|
|
|
|
cd libkrunfw
|
|
|
|
# Ensure we're on the pinned commit for reproducible builds
|
|
if [ -n "${LIBKRUNFW_REF:-}" ]; then
|
|
echo " Checking out pinned ref: ${LIBKRUNFW_REF}"
|
|
git fetch origin
|
|
git checkout "${LIBKRUNFW_REF}"
|
|
fi
|
|
|
|
# Copy custom kernel config fragment
|
|
if [ -f "$KERNEL_CONFIG" ]; then
|
|
cp "$KERNEL_CONFIG" openshell.kconfig
|
|
echo " Applied custom kernel config fragment: openshell.kconfig"
|
|
else
|
|
echo "Warning: Custom kernel config not found at ${KERNEL_CONFIG}" >&2
|
|
echo " Building with default config (k3s networking may not work)" >&2
|
|
fi
|
|
|
|
echo " Building kernel and libkrunfw (this may take 15-20 minutes)..."
|
|
|
|
# The libkrunfw Makefile does not support a config fragment — it copies the
|
|
# base config and runs olddefconfig, then builds the kernel image in one
|
|
# make invocation. We cannot inject the fragment mid-build via make flags.
|
|
#
|
|
# Instead we drive the build in two phases:
|
|
#
|
|
# Phase 1: Run the Makefile's $(KERNEL_SOURCES) target, which:
|
|
# - downloads and extracts the kernel tarball (if needed)
|
|
# - applies patches
|
|
# - copies config-libkrunfw_{arch} to $(KERNEL_SOURCES)/.config
|
|
# - runs olddefconfig
|
|
#
|
|
# Phase 2: Merge our fragment on top of the .config produced by Phase 1
|
|
# using the kernel's own merge_config.sh, then re-run olddefconfig
|
|
# to resolve new dependency chains (e.g. CONFIG_BRIDGE pulls in
|
|
# CONFIG_BRIDGE_NETFILTER which needs CONFIG_NETFILTER etc).
|
|
#
|
|
# Phase 3: Let the Makefile build everything (kernel + kernel.c + .so),
|
|
# skipping the $(KERNEL_SOURCES) target since it already exists.
|
|
|
|
KERNEL_VERSION="$(grep '^KERNEL_VERSION' Makefile | head -1 | awk '{print $3}')"
|
|
KERNEL_SOURCES="${KERNEL_VERSION}"
|
|
|
|
# Phase 1: prepare kernel source tree + base .config.
|
|
# Run the Makefile's $(KERNEL_SOURCES) target whenever the .config is absent
|
|
# (either because the tree was never extracted, or because it was cleaned).
|
|
# The target is idempotent: if the directory already exists make skips the
|
|
# tarball extraction but still copies the base config and runs olddefconfig.
|
|
if [ ! -f "${KERNEL_SOURCES}/.config" ]; then
|
|
echo " Phase 1: preparing kernel source tree and base .config..."
|
|
# Remove the directory so make re-runs the full $(KERNEL_SOURCES) recipe
|
|
# (extract + patch + config copy + olddefconfig).
|
|
rm -rf "${KERNEL_SOURCES}"
|
|
make "${KERNEL_SOURCES}"
|
|
else
|
|
echo " Phase 1: kernel source tree and .config already present, skipping"
|
|
fi
|
|
|
|
# Phase 2: merge the openshell fragment on top
|
|
if [ -f openshell.kconfig ]; then
|
|
echo " Phase 2: merging openshell.kconfig fragment..."
|
|
|
|
# merge_config.sh must be called with ARCH set so it finds the right Kconfig
|
|
# entry points. -m means "merge into existing .config" (vs starting fresh).
|
|
ARCH="${KARCH}" KCONFIG_CONFIG="${KERNEL_SOURCES}/.config" \
|
|
"${KERNEL_SOURCES}/scripts/kconfig/merge_config.sh" \
|
|
-m -O "${KERNEL_SOURCES}" \
|
|
"${KERNEL_SOURCES}/.config" \
|
|
openshell.kconfig
|
|
|
|
# Re-run olddefconfig to fill in any new symbols introduced by the fragment.
|
|
make -C "${KERNEL_SOURCES}" ARCH="${KARCH}" olddefconfig
|
|
|
|
# Verify that the key options were actually applied.
|
|
all_ok=true
|
|
for opt in CONFIG_BRIDGE CONFIG_NETFILTER CONFIG_NF_NAT; do
|
|
val="$(grep "^${opt}=" "${KERNEL_SOURCES}/.config" 2>/dev/null || true)"
|
|
if [ -n "$val" ]; then
|
|
echo " ${opt}: ${val#*=}"
|
|
else
|
|
echo " WARNING: ${opt} not set after merge!" >&2
|
|
all_ok=false
|
|
fi
|
|
done
|
|
if [ "$all_ok" = false ]; then
|
|
echo "ERROR: kernel config fragment merge failed — required options missing" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# The kernel binary and kernel.c from the previous (bad) build must be
|
|
# removed so make rebuilds them with the updated .config.
|
|
rm -f kernel.c "${KERNEL_SOURCES}/${KERNEL_IMAGE_PATH}" \
|
|
"${KERNEL_SOURCES}/vmlinux" libkrunfw.so*
|
|
fi
|
|
|
|
# Phase 3: build kernel image, kernel.c bundle, and the shared library
|
|
make -j"$(nproc)"
|
|
|
|
# Copy output
|
|
cp libkrunfw.so* "$OUTPUT_DIR/"
|
|
echo " Built: $(ls "$OUTPUT_DIR"/libkrunfw.so* | xargs -n1 basename | tr '\n' ' ')"
|
|
|
|
# ── Export kernel.c for cross-platform builds ───────────────────────────
|
|
# kernel.c is a C source file containing the compiled Linux kernel as a byte
|
|
# array. It is architecture-specific (aarch64 vs x86_64) but OS-agnostic —
|
|
# any C compiler can turn it into a .so or .dylib. We export it so the macOS
|
|
# job can produce libkrunfw.dylib without rebuilding the kernel.
|
|
|
|
ABI_VERSION="$(grep -oE 'ABI_VERSION\s*=\s*[0-9]+' Makefile | head -1 | sed 's/[^0-9]//g')"
|
|
|
|
if [ -f kernel.c ]; then
|
|
cp kernel.c "$OUTPUT_DIR/kernel.c"
|
|
echo "${ABI_VERSION}" > "$OUTPUT_DIR/ABI_VERSION"
|
|
echo " Exported kernel.c ($(du -sh kernel.c | cut -f1)) and ABI_VERSION=${ABI_VERSION}"
|
|
else
|
|
echo "Warning: kernel.c not found — cross-platform builds will not work" >&2
|
|
fi
|
|
|
|
cd "$BUILD_DIR"
|
|
|
|
# ── Build libkrun (VMM) ─────────────────────────────────────────────────
|
|
|
|
echo ""
|
|
echo "==> Building libkrun..."
|
|
|
|
if [ ! -d libkrun ]; then
|
|
echo " Cloning libkrun..."
|
|
git clone --depth 1 https://github.com/containers/libkrun.git
|
|
fi
|
|
|
|
cd libkrun
|
|
|
|
# Build with NET support for gvproxy networking and BLK support for the
|
|
# host-backed state disk.
|
|
echo " Building libkrun with NET=1 BLK=1..."
|
|
|
|
# Locate libclang for clang-sys if LIBCLANG_PATH isn't already set.
|
|
# clang-sys looks for libclang.so or libclang-*.so; on Debian/Ubuntu the
|
|
# versioned file (e.g. libclang-18.so.18) lives under the LLVM lib dir.
|
|
if [ -z "${LIBCLANG_PATH:-}" ]; then
|
|
for llvm_lib in /usr/lib/llvm-*/lib; do
|
|
if ls "$llvm_lib"/libclang*.so* &>/dev/null; then
|
|
export LIBCLANG_PATH="$llvm_lib"
|
|
echo " LIBCLANG_PATH=$LIBCLANG_PATH"
|
|
break
|
|
fi
|
|
done
|
|
fi
|
|
|
|
make NET=1 BLK=1 -j"$(nproc)"
|
|
|
|
# Copy output
|
|
cp target/release/libkrun.so "$OUTPUT_DIR/"
|
|
echo " Built: libkrun.so"
|
|
|
|
cd "$BUILD_DIR"
|
|
|
|
# ── Summary ─────────────────────────────────────────────────────────────
|
|
|
|
echo ""
|
|
echo "==> Build complete!"
|
|
echo " Output directory: ${OUTPUT_DIR}"
|
|
echo ""
|
|
echo " Artifacts:"
|
|
ls -lah "$OUTPUT_DIR"/*.so* "$OUTPUT_DIR"/kernel.c "$OUTPUT_DIR"/ABI_VERSION 2>/dev/null || \
|
|
ls -lah "$OUTPUT_DIR"/*.so*
|
|
|
|
echo ""
|
|
echo "Next step: mise run vm:build"
|