mirror of
https://github.com/NVIDIA/OpenShell.git
synced 2026-10-26 06:52:57 +08:00
feat(vm): add openshell-vm crate with libkrun microVM gateway (#611)
This commit is contained in:
Executable
+72
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env bash
|
||||
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# Shared helpers for openshell-vm build scripts.
|
||||
# Source this file from other scripts:
|
||||
# source "$(dirname "${BASH_SOURCE[0]}")/_lib.sh"
|
||||
|
||||
# ── Root directory ──────────────────────────────────────────────────────
|
||||
|
||||
vm_lib_root() {
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd
|
||||
}
|
||||
|
||||
# ── Platform detection ──────────────────────────────────────────────────
|
||||
|
||||
# Detect the current platform and echo one of:
|
||||
# darwin-aarch64, linux-aarch64, linux-x86_64
|
||||
# Exits with error on unsupported platforms.
|
||||
detect_platform() {
|
||||
case "$(uname -s)-$(uname -m)" in
|
||||
Darwin-arm64) echo "darwin-aarch64" ;;
|
||||
Linux-aarch64) echo "linux-aarch64" ;;
|
||||
Linux-x86_64) echo "linux-x86_64" ;;
|
||||
*)
|
||||
echo "Error: Unsupported platform: $(uname -s)-$(uname -m)" >&2
|
||||
echo "Supported: macOS ARM64, Linux ARM64, Linux x86_64" >&2
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ── Compression helpers ─────────────────────────────────────────────────
|
||||
|
||||
# Compress a single file with zstd level 19, reporting sizes.
|
||||
# Usage: compress_file <input> <output>
|
||||
compress_file() {
|
||||
local input="$1"
|
||||
local output="$2"
|
||||
local name
|
||||
name="$(basename "$input")"
|
||||
local original_size
|
||||
original_size="$(du -h "$input" | cut -f1)"
|
||||
|
||||
zstd -19 -f -q -T0 -o "$output" "$input"
|
||||
chmod 644 "$output"
|
||||
|
||||
local compressed_size
|
||||
compressed_size="$(du -h "$output" | cut -f1)"
|
||||
echo " ${name}: ${original_size} -> ${compressed_size}"
|
||||
}
|
||||
|
||||
# Compress all files in a directory (skipping provenance.json) into an
|
||||
# output directory, appending .zst to each filename.
|
||||
# Usage: compress_dir <source_dir> <output_dir>
|
||||
compress_dir() {
|
||||
local source_dir="$1"
|
||||
local output_dir="$2"
|
||||
|
||||
echo "==> Compressing with zstd (level 19)..."
|
||||
for file in "$source_dir"/*; do
|
||||
[ -f "$file" ] || continue
|
||||
local name
|
||||
name="$(basename "$file")"
|
||||
# Skip metadata files — not embedded
|
||||
if [ "$name" = "provenance.json" ]; then
|
||||
cp "$file" "${output_dir}/"
|
||||
continue
|
||||
fi
|
||||
compress_file "$file" "${output_dir}/${name}.zst"
|
||||
done
|
||||
}
|
||||
Executable
+271
@@ -0,0 +1,271 @@
|
||||
#!/usr/bin/env bash
|
||||
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# Build libkrun from source on macOS with portable rpath.
|
||||
#
|
||||
# This script builds libkrun WITHOUT GPU support (no virglrenderer/libepoxy/MoltenVK
|
||||
# dependencies), making the resulting binary fully portable and self-contained.
|
||||
#
|
||||
# For openshell-vm, we run headless k3s clusters, so GPU passthrough is not needed.
|
||||
#
|
||||
# Prerequisites:
|
||||
# - macOS ARM64 (Apple Silicon)
|
||||
# - Xcode Command Line Tools
|
||||
# - Homebrew: brew install rust lld dtc xz libkrunfw
|
||||
#
|
||||
# Usage:
|
||||
# ./build-libkrun-macos.sh
|
||||
#
|
||||
# Output:
|
||||
# target/libkrun-build/libkrun.dylib - portable dylib with @loader_path rpath
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
|
||||
BUILD_DIR="${ROOT}/target/libkrun-build"
|
||||
OUTPUT_DIR="${BUILD_DIR}"
|
||||
BREW_PREFIX="$(brew --prefix 2>/dev/null || echo /opt/homebrew)"
|
||||
CUSTOM_RUNTIME="${ROOT}/target/custom-runtime"
|
||||
|
||||
if [ "$(uname -s)" != "Darwin" ]; then
|
||||
echo "Error: This script only runs on macOS" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$(uname -m)" != "arm64" ]; then
|
||||
echo "Error: libkrun on macOS only supports ARM64 (Apple Silicon)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ARCH="$(uname -m)"
|
||||
echo "==> Building libkrun for macOS ${ARCH} (no GPU support)"
|
||||
echo " Build directory: ${BUILD_DIR}"
|
||||
echo ""
|
||||
|
||||
# ── Check dependencies ──────────────────────────────────────────────────
|
||||
|
||||
check_deps() {
|
||||
echo "==> Checking build dependencies..."
|
||||
|
||||
MISSING=""
|
||||
|
||||
# Check for Rust
|
||||
if ! command -v cargo &>/dev/null; then
|
||||
MISSING="$MISSING rust"
|
||||
fi
|
||||
|
||||
# Check for lld (LLVM linker)
|
||||
if ! command -v ld.lld &>/dev/null && ! [ -x "${BREW_PREFIX}/opt/llvm/bin/ld.lld" ]; then
|
||||
MISSING="$MISSING lld"
|
||||
fi
|
||||
|
||||
# Check for dtc (device tree compiler)
|
||||
if ! command -v dtc &>/dev/null; then
|
||||
MISSING="$MISSING dtc"
|
||||
fi
|
||||
|
||||
# Check for libkrunfw
|
||||
if [ ! -f "${BREW_PREFIX}/lib/libkrunfw.dylib" ] && \
|
||||
[ ! -f "${BREW_PREFIX}/lib/libkrunfw.5.dylib" ] && \
|
||||
[ ! -f "${CUSTOM_RUNTIME}/libkrunfw.dylib" ]; then
|
||||
MISSING="$MISSING libkrunfw"
|
||||
fi
|
||||
|
||||
if [ -n "$MISSING" ]; then
|
||||
echo "Error: Missing dependencies:$MISSING" >&2
|
||||
echo "" >&2
|
||||
echo "Install with: brew install$MISSING" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo " All dependencies found"
|
||||
}
|
||||
|
||||
check_deps
|
||||
|
||||
# ── Setup build directory ───────────────────────────────────────────────
|
||||
|
||||
mkdir -p "$BUILD_DIR"
|
||||
cd "$BUILD_DIR"
|
||||
|
||||
# ── Clone libkrun ───────────────────────────────────────────────────────
|
||||
|
||||
LIBKRUN_REF="${LIBKRUN_REF:-e5922f6}"
|
||||
|
||||
if [ ! -d libkrun ]; then
|
||||
echo "==> Cloning libkrun..."
|
||||
git clone https://github.com/containers/libkrun.git
|
||||
fi
|
||||
|
||||
echo "==> Checking out ${LIBKRUN_REF}..."
|
||||
cd libkrun
|
||||
git fetch origin --tags
|
||||
git checkout "${LIBKRUN_REF}" 2>/dev/null || git checkout "tags/${LIBKRUN_REF}" 2>/dev/null || {
|
||||
echo "Error: Could not checkout ${LIBKRUN_REF}" >&2
|
||||
exit 1
|
||||
}
|
||||
cd ..
|
||||
|
||||
LIBKRUN_COMMIT=$(git -C libkrun rev-parse HEAD)
|
||||
echo " Commit: ${LIBKRUN_COMMIT}"
|
||||
|
||||
cd libkrun
|
||||
|
||||
# ── Build libkrun ───────────────────────────────────────────────────────
|
||||
|
||||
echo ""
|
||||
echo "==> Building libkrun with NET=1 BLK=1 (no GPU)..."
|
||||
|
||||
# Find libkrunfw - prefer custom build with bridge support
|
||||
if [ -f "${CUSTOM_RUNTIME}/provenance.json" ] && [ -f "${CUSTOM_RUNTIME}/libkrunfw.dylib" ]; then
|
||||
LIBKRUNFW_DIR="${CUSTOM_RUNTIME}"
|
||||
echo " Using custom libkrunfw from ${LIBKRUNFW_DIR}"
|
||||
else
|
||||
LIBKRUNFW_DIR="${BREW_PREFIX}/lib"
|
||||
echo " Using Homebrew libkrunfw from ${LIBKRUNFW_DIR}"
|
||||
fi
|
||||
|
||||
# Set library search paths for build
|
||||
export LIBRARY_PATH="${LIBKRUNFW_DIR}:${BREW_PREFIX}/lib:${LIBRARY_PATH:-}"
|
||||
export DYLD_LIBRARY_PATH="${LIBKRUNFW_DIR}:${BREW_PREFIX}/lib:${DYLD_LIBRARY_PATH:-}"
|
||||
|
||||
# Set up LLVM/clang for bindgen (required by krun_display/krun_input if they get compiled)
|
||||
# Note: DYLD_LIBRARY_PATH is needed at runtime for the build scripts that use libclang
|
||||
LLVM_PREFIX="${BREW_PREFIX}/opt/llvm"
|
||||
if [ -d "$LLVM_PREFIX" ]; then
|
||||
export LIBCLANG_PATH="${LLVM_PREFIX}/lib"
|
||||
export DYLD_LIBRARY_PATH="${LLVM_PREFIX}/lib:${DYLD_LIBRARY_PATH:-}"
|
||||
fi
|
||||
|
||||
# Build with BLK and NET features only (no GPU)
|
||||
# This avoids the virglrenderer → libepoxy → MoltenVK dependency chain
|
||||
make clean 2>/dev/null || true
|
||||
make BLK=1 NET=1 -j"$(sysctl -n hw.ncpu)"
|
||||
|
||||
# ── Rewrite dylib paths for portability ─────────────────────────────────
|
||||
|
||||
echo ""
|
||||
echo "==> Making dylib portable with @loader_path..."
|
||||
|
||||
DYLIB="target/release/libkrun.dylib"
|
||||
if [ ! -f "$DYLIB" ]; then
|
||||
echo "Error: Build did not produce $DYLIB" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Copy to output
|
||||
cp "$DYLIB" "${OUTPUT_DIR}/libkrun.dylib"
|
||||
DYLIB="${OUTPUT_DIR}/libkrun.dylib"
|
||||
|
||||
# Show current dependencies
|
||||
echo " Original dependencies:"
|
||||
otool -L "$DYLIB" | grep -v "^/" | sed 's/^/ /'
|
||||
|
||||
# Rewrite the install name to use @loader_path (makes it relocatable)
|
||||
install_name_tool -id "@loader_path/libkrun.dylib" "$DYLIB"
|
||||
|
||||
# Rewrite libkrunfw path to @loader_path (will be bundled alongside)
|
||||
# Find what libkrunfw path is currently referenced
|
||||
# Note: grep may not find anything (libkrunfw is loaded via dlopen), so we use || true
|
||||
KRUNFW_PATH=$(otool -L "$DYLIB" | grep libkrunfw | awk '{print $1}' || true)
|
||||
if [ -n "$KRUNFW_PATH" ]; then
|
||||
install_name_tool -change "$KRUNFW_PATH" "@loader_path/libkrunfw.dylib" "$DYLIB"
|
||||
echo " Rewrote: $KRUNFW_PATH → @loader_path/libkrunfw.dylib"
|
||||
fi
|
||||
|
||||
# Re-codesign after modifications (required on macOS)
|
||||
codesign -f -s - "$DYLIB"
|
||||
|
||||
# Show final dependencies
|
||||
echo ""
|
||||
echo " Final dependencies:"
|
||||
otool -L "$DYLIB" | grep -v "^/" | sed 's/^/ /'
|
||||
|
||||
# Verify no hardcoded homebrew paths remain
|
||||
if otool -L "$DYLIB" | grep -q "/opt/homebrew"; then
|
||||
echo ""
|
||||
echo "Warning: Homebrew paths still present in dylib!" >&2
|
||||
otool -L "$DYLIB" | grep "/opt/homebrew" | sed 's/^/ /'
|
||||
else
|
||||
echo ""
|
||||
echo " ✓ No hardcoded Homebrew paths"
|
||||
fi
|
||||
|
||||
# ── Copy libkrunfw to output ────────────────────────────────────────────
|
||||
|
||||
echo ""
|
||||
echo "==> Bundling libkrunfw..."
|
||||
|
||||
# Find and copy libkrunfw
|
||||
KRUNFW_SRC=""
|
||||
for candidate in \
|
||||
"${CUSTOM_RUNTIME}/libkrunfw.dylib" \
|
||||
"${CUSTOM_RUNTIME}/libkrunfw.5.dylib" \
|
||||
"${BREW_PREFIX}/lib/libkrunfw.dylib" \
|
||||
"${BREW_PREFIX}/lib/libkrunfw.5.dylib"; do
|
||||
if [ -f "$candidate" ]; then
|
||||
# Resolve symlinks
|
||||
if [ -L "$candidate" ]; then
|
||||
KRUNFW_SRC=$(readlink -f "$candidate" 2>/dev/null || readlink "$candidate")
|
||||
if [[ "$KRUNFW_SRC" != /* ]]; then
|
||||
KRUNFW_SRC="$(dirname "$candidate")/${KRUNFW_SRC}"
|
||||
fi
|
||||
else
|
||||
KRUNFW_SRC="$candidate"
|
||||
fi
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "$KRUNFW_SRC" ]; then
|
||||
echo "Error: Could not find libkrunfw.dylib" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cp "$KRUNFW_SRC" "${OUTPUT_DIR}/libkrunfw.dylib"
|
||||
echo " Copied: $KRUNFW_SRC"
|
||||
|
||||
# Make libkrunfw portable too
|
||||
install_name_tool -id "@loader_path/libkrunfw.dylib" "${OUTPUT_DIR}/libkrunfw.dylib"
|
||||
codesign -f -s - "${OUTPUT_DIR}/libkrunfw.dylib"
|
||||
|
||||
# Check libkrunfw dependencies
|
||||
echo " libkrunfw dependencies:"
|
||||
otool -L "${OUTPUT_DIR}/libkrunfw.dylib" | grep -v "^/" | sed 's/^/ /'
|
||||
|
||||
# ── Summary ─────────────────────────────────────────────────────────────
|
||||
|
||||
cd "$BUILD_DIR"
|
||||
|
||||
echo ""
|
||||
echo "==> Build complete!"
|
||||
echo " Output directory: ${OUTPUT_DIR}"
|
||||
echo ""
|
||||
echo " Artifacts:"
|
||||
ls -lah "${OUTPUT_DIR}"/*.dylib
|
||||
|
||||
# Verify portability
|
||||
echo ""
|
||||
echo "==> Verifying portability..."
|
||||
ALL_GOOD=true
|
||||
|
||||
for lib in "${OUTPUT_DIR}"/*.dylib; do
|
||||
if otool -L "$lib" | grep -q "/opt/homebrew"; then
|
||||
echo " ✗ $(basename "$lib") has hardcoded paths"
|
||||
ALL_GOOD=false
|
||||
else
|
||||
echo " ✓ $(basename "$lib") is portable"
|
||||
fi
|
||||
done
|
||||
|
||||
if $ALL_GOOD; then
|
||||
echo ""
|
||||
echo "All libraries are portable!"
|
||||
echo ""
|
||||
echo "Next step: mise run vm:build"
|
||||
else
|
||||
echo ""
|
||||
echo "Warning: Some libraries have non-portable paths"
|
||||
echo "They may not work on machines without Homebrew"
|
||||
fi
|
||||
Executable
+248
@@ -0,0 +1,248 @@
|
||||
#!/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.
|
||||
#
|
||||
# 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
|
||||
|
||||
ARCH="$(uname -m)"
|
||||
echo "==> Building libkrun for Linux ${ARCH}"
|
||||
echo " Build directory: ${BUILD_DIR}"
|
||||
echo " Kernel config: ${KERNEL_CONFIG}"
|
||||
echo ""
|
||||
|
||||
# ── Install dependencies ────────────────────────────────────────────────
|
||||
|
||||
install_deps() {
|
||||
echo "==> Checking/installing build dependencies..."
|
||||
|
||||
if command -v apt-get &>/dev/null; then
|
||||
# Debian/Ubuntu
|
||||
DEPS="build-essential git python3 python3-pyelftools flex bison libelf-dev libssl-dev bc curl libclang-dev"
|
||||
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"
|
||||
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" >&2
|
||||
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_aarch64 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=arm64 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=arm64 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}/arch/arm64/boot/Image" \
|
||||
"${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' ' ')"
|
||||
|
||||
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*
|
||||
|
||||
echo ""
|
||||
echo "Next step: mise run vm:build"
|
||||
Executable
+116
@@ -0,0 +1,116 @@
|
||||
#!/usr/bin/env bash
|
||||
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# Build rootfs and compress to tarball for embedding in openshell-vm binary.
|
||||
#
|
||||
# This script:
|
||||
# 1. Builds the rootfs using build-rootfs.sh
|
||||
# 2. Compresses it to a zstd tarball for embedding
|
||||
#
|
||||
# Usage:
|
||||
# ./build-rootfs-tarball.sh [--base]
|
||||
#
|
||||
# Options:
|
||||
# --base Build a base rootfs (~200-300MB) without pre-loaded images.
|
||||
# First boot will be slower but binary size is much smaller.
|
||||
# Default: full rootfs with pre-loaded images (~2GB+).
|
||||
#
|
||||
# The resulting tarball is placed at target/vm-runtime-compressed/rootfs.tar.zst
|
||||
# for inclusion in the embedded binary build.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
|
||||
ROOTFS_BUILD_DIR="${ROOT}/target/rootfs-build"
|
||||
OUTPUT_DIR="${ROOT}/target/vm-runtime-compressed"
|
||||
OUTPUT="${OUTPUT_DIR}/rootfs.tar.zst"
|
||||
|
||||
# Parse arguments
|
||||
BASE_ONLY=false
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--base)
|
||||
BASE_ONLY=true
|
||||
;;
|
||||
--help|-h)
|
||||
echo "Usage: $0 [--base]"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " --base Build base rootfs (~200-300MB) without pre-loaded images"
|
||||
echo " First boot will be slower but binary size is much smaller"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $arg"
|
||||
echo "Use --help for usage information"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Check for Docker
|
||||
if ! command -v docker &>/dev/null; then
|
||||
echo "Error: Docker is required to build the rootfs" >&2
|
||||
echo "Please install Docker and try again" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if Docker daemon is running
|
||||
if ! docker info &>/dev/null; then
|
||||
echo "Error: Docker daemon is not running" >&2
|
||||
echo "Please start Docker and try again" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$BASE_ONLY" = true ]; then
|
||||
echo "==> Building BASE rootfs for embedding"
|
||||
echo " Build dir: ${ROOTFS_BUILD_DIR}"
|
||||
echo " Output: ${OUTPUT}"
|
||||
echo " Mode: base (no pre-loaded images, ~200-300MB)"
|
||||
echo ""
|
||||
|
||||
# Build base rootfs
|
||||
echo "==> Step 1/2: Building base rootfs..."
|
||||
"${ROOT}/crates/openshell-vm/scripts/build-rootfs.sh" --base "${ROOTFS_BUILD_DIR}"
|
||||
else
|
||||
echo "==> Building FULL rootfs for embedding"
|
||||
echo " Build dir: ${ROOTFS_BUILD_DIR}"
|
||||
echo " Output: ${OUTPUT}"
|
||||
echo " Mode: full (pre-loaded images, pre-initialized, ~2GB+)"
|
||||
echo ""
|
||||
|
||||
# Build full rootfs
|
||||
echo "==> Step 1/2: Building full rootfs (this may take 10-15 minutes)..."
|
||||
"${ROOT}/crates/openshell-vm/scripts/build-rootfs.sh" "${ROOTFS_BUILD_DIR}"
|
||||
fi
|
||||
|
||||
# Compress to tarball
|
||||
echo ""
|
||||
echo "==> Step 2/2: Compressing rootfs to tarball..."
|
||||
mkdir -p "${OUTPUT_DIR}"
|
||||
|
||||
# Remove existing tarball if present
|
||||
rm -f "${OUTPUT}"
|
||||
|
||||
# Get uncompressed size for display
|
||||
echo " Uncompressed size: $(du -sh "${ROOTFS_BUILD_DIR}" | cut -f1)"
|
||||
|
||||
# Create tarball with zstd compression
|
||||
# -19 = high compression (slower but smaller)
|
||||
# -T0 = use all available threads
|
||||
echo " Compressing with zstd (level 19, this may take a few minutes)..."
|
||||
tar -C "${ROOTFS_BUILD_DIR}" -cf - . | zstd -19 -T0 -o "${OUTPUT}"
|
||||
|
||||
# Report results
|
||||
echo ""
|
||||
echo "==> Rootfs tarball created successfully!"
|
||||
echo " Output: ${OUTPUT}"
|
||||
echo " Compressed: $(du -sh "${OUTPUT}" | cut -f1)"
|
||||
if [ "$BASE_ONLY" = true ]; then
|
||||
echo " Type: base (first boot ~30-60s, images pulled on demand)"
|
||||
else
|
||||
echo " Type: full (first boot ~3-5s, images pre-loaded)"
|
||||
fi
|
||||
echo ""
|
||||
echo "Next step: mise run vm:build"
|
||||
Executable
+65
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env bash
|
||||
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# Stage the openshell-vm sidecar runtime bundle next to local build outputs.
|
||||
#
|
||||
# Copies the uncompressed VM runtime libraries (libkrun, libkrunfw, gvproxy)
|
||||
# from target/vm-runtime/ into the .runtime sidecar directories alongside
|
||||
# each build output. This is required for:
|
||||
# - build-rootfs.sh pre-initialization (boots the real VM to pre-bake k3s state)
|
||||
# - Direct invocation of target/debug/openshell-vm without embedding
|
||||
#
|
||||
# The source artifacts are collected by compress-vm-runtime.sh into
|
||||
# target/vm-runtime/ before compression; this script re-uses that work dir.
|
||||
#
|
||||
# Usage:
|
||||
# ./tasks/scripts/vm/bundle-vm-runtime.sh
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" || ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
|
||||
SOURCE_DIR="${ROOT}/target/vm-runtime"
|
||||
|
||||
if [ ! -d "${SOURCE_DIR}" ]; then
|
||||
echo "ERROR: VM runtime source not found at ${SOURCE_DIR}"
|
||||
echo " Run: mise run vm:setup"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify required files are present
|
||||
for required in libkrun.so gvproxy; do
|
||||
if ! ls "${SOURCE_DIR}/${required}" >/dev/null 2>&1; then
|
||||
# Try platform-specific variants
|
||||
if [ "$required" = "libkrun.so" ] && ls "${SOURCE_DIR}"/libkrun.dylib >/dev/null 2>&1; then
|
||||
continue
|
||||
fi
|
||||
echo "ERROR: Required runtime file not found: ${SOURCE_DIR}/${required}"
|
||||
echo " Run: mise run vm:setup"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
TARGETS=(
|
||||
"${ROOT}/target/debug"
|
||||
"${ROOT}/target/release"
|
||||
)
|
||||
|
||||
for target_dir in "${TARGETS[@]}"; do
|
||||
# Only stage if the binary exists (avoid creating orphan runtime dirs)
|
||||
if [ ! -f "${target_dir}/openshell-vm" ] && [ ! -f "${target_dir}/openshell-vm.d" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
runtime_dir="${target_dir}/openshell-vm.runtime"
|
||||
mkdir -p "${runtime_dir}"
|
||||
|
||||
for file in "${SOURCE_DIR}"/*; do
|
||||
[ -f "$file" ] || continue
|
||||
name="$(basename "$file")"
|
||||
install -m 0755 "$file" "${runtime_dir}/${name}"
|
||||
done
|
||||
|
||||
echo "staged runtime bundle in ${runtime_dir}"
|
||||
done
|
||||
Executable
+12
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [ "$(uname -s)" != "Darwin" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
|
||||
codesign --entitlements "${ROOT}/crates/openshell-vm/entitlements.plist" --force -s - "${ROOT}/target/debug/openshell-vm"
|
||||
Executable
+246
@@ -0,0 +1,246 @@
|
||||
#!/usr/bin/env bash
|
||||
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# Gather VM runtime artifacts from local sources and compress for embedding.
|
||||
#
|
||||
# This script collects libkrun, libkrunfw, and gvproxy from local sources
|
||||
# (Homebrew on macOS, built from source on Linux) and compresses them with
|
||||
# zstd for embedding into the openshell-vm binary.
|
||||
#
|
||||
# Usage:
|
||||
# ./compress-vm-runtime.sh
|
||||
#
|
||||
# Environment:
|
||||
# OPENSHELL_VM_RUNTIME_COMPRESSED_DIR - Output directory (default: target/vm-runtime-compressed)
|
||||
# VM_RUNTIME_TARBALL - Path to a pre-built vm-runtime-*.tar.zst tarball.
|
||||
# When set, the script extracts and re-compresses
|
||||
# artifacts from this tarball instead of looking for
|
||||
# local builds. Used by CI and download-kernel-runtime.sh.
|
||||
#
|
||||
# The script sets OPENSHELL_VM_RUNTIME_COMPRESSED_DIR for use by build.rs.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/_lib.sh"
|
||||
ROOT="$(vm_lib_root)"
|
||||
|
||||
# Source pins for gvproxy version
|
||||
source "${ROOT}/crates/openshell-vm/pins.env" 2>/dev/null || true
|
||||
GVPROXY_VERSION="${GVPROXY_VERSION:-v0.8.8}"
|
||||
|
||||
# ── macOS dylib portability helpers ─────────────────────────────────────
|
||||
|
||||
# Make a dylib portable by rewriting paths to use @loader_path
|
||||
make_dylib_portable() {
|
||||
local dylib="$1"
|
||||
local dylib_name
|
||||
dylib_name="$(basename "$dylib")"
|
||||
|
||||
# Rewrite install name
|
||||
install_name_tool -id "@loader_path/${dylib_name}" "$dylib" 2>/dev/null || true
|
||||
|
||||
# Rewrite libkrunfw reference if present
|
||||
local krunfw_path
|
||||
krunfw_path=$(otool -L "$dylib" 2>/dev/null | grep libkrunfw | awk '{print $1}' || true)
|
||||
if [ -n "$krunfw_path" ] && [[ "$krunfw_path" != @* ]]; then
|
||||
install_name_tool -change "$krunfw_path" "@loader_path/libkrunfw.dylib" "$dylib"
|
||||
fi
|
||||
|
||||
# Re-codesign
|
||||
codesign -f -s - "$dylib" 2>/dev/null || true
|
||||
}
|
||||
|
||||
WORK_DIR="${ROOT}/target/vm-runtime"
|
||||
OUTPUT_DIR="${OPENSHELL_VM_RUNTIME_COMPRESSED_DIR:-${ROOT}/target/vm-runtime-compressed}"
|
||||
|
||||
rm -rf "$WORK_DIR"
|
||||
mkdir -p "$WORK_DIR" "$OUTPUT_DIR"
|
||||
|
||||
# ── Fast path: pre-built tarball from CI or download-kernel-runtime.sh ──
|
||||
|
||||
if [ -n "${VM_RUNTIME_TARBALL:-}" ]; then
|
||||
echo "==> Using pre-built runtime tarball: ${VM_RUNTIME_TARBALL}"
|
||||
|
||||
if [ ! -f "${VM_RUNTIME_TARBALL}" ]; then
|
||||
echo "Error: VM_RUNTIME_TARBALL not found: ${VM_RUNTIME_TARBALL}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract tarball contents
|
||||
zstd -d "${VM_RUNTIME_TARBALL}" --stdout | tar -xf - -C "$WORK_DIR"
|
||||
|
||||
echo " Extracted files:"
|
||||
ls -lah "$WORK_DIR"
|
||||
|
||||
echo ""
|
||||
compress_dir "$WORK_DIR" "$OUTPUT_DIR"
|
||||
|
||||
# Check for rootfs tarball (built separately)
|
||||
ROOTFS_TARBALL="${OUTPUT_DIR}/rootfs.tar.zst"
|
||||
if [ -f "$ROOTFS_TARBALL" ]; then
|
||||
echo " rootfs.tar.zst: $(du -h "$ROOTFS_TARBALL" | cut -f1) (pre-built)"
|
||||
else
|
||||
echo ""
|
||||
echo "Note: rootfs.tar.zst not found."
|
||||
echo " To build one, run: mise run vm:rootfs -- --base"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "==> Compressed artifacts in ${OUTPUT_DIR}:"
|
||||
ls -lah "$OUTPUT_DIR"
|
||||
TOTAL=$(du -sh "$OUTPUT_DIR" | cut -f1)
|
||||
echo ""
|
||||
echo "==> Total compressed size: ${TOTAL}"
|
||||
echo ""
|
||||
echo "Next step: mise run vm:build"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "==> Detecting platform..."
|
||||
|
||||
case "$(uname -s)-$(uname -m)" in
|
||||
Darwin-arm64)
|
||||
PLATFORM="darwin-aarch64"
|
||||
echo " Platform: macOS ARM64"
|
||||
|
||||
# Source priority for libkrun:
|
||||
# 1. Custom build from build-libkrun-macos.sh (portable, no GPU deps)
|
||||
# 2. Custom runtime with custom libkrunfw
|
||||
LIBKRUN_BUILD_DIR="${ROOT}/target/libkrun-build"
|
||||
CUSTOM_DIR="${ROOT}/target/custom-runtime"
|
||||
BREW_PREFIX="$(brew --prefix 2>/dev/null || echo /opt/homebrew)"
|
||||
|
||||
if [ -f "${LIBKRUN_BUILD_DIR}/libkrun.dylib" ]; then
|
||||
echo " Using portable libkrun from ${LIBKRUN_BUILD_DIR}"
|
||||
cp "${LIBKRUN_BUILD_DIR}/libkrun.dylib" "$WORK_DIR/"
|
||||
cp "${LIBKRUN_BUILD_DIR}/libkrunfw.dylib" "$WORK_DIR/"
|
||||
|
||||
# Verify portability
|
||||
if otool -L "${LIBKRUN_BUILD_DIR}/libkrun.dylib" | grep -q "/opt/homebrew"; then
|
||||
echo " Warning: libkrun has hardcoded Homebrew paths - may not be portable"
|
||||
else
|
||||
echo " ✓ libkrun is portable (no hardcoded paths)"
|
||||
fi
|
||||
elif [ -f "${CUSTOM_DIR}/provenance.json" ]; then
|
||||
echo " Using custom runtime from ${CUSTOM_DIR}"
|
||||
|
||||
# libkrun from Homebrew (needs path rewriting for portability)
|
||||
if [ -f "${CUSTOM_DIR}/libkrun.dylib" ]; then
|
||||
cp "${CUSTOM_DIR}/libkrun.dylib" "$WORK_DIR/"
|
||||
else
|
||||
cp "${BREW_PREFIX}/lib/libkrun.dylib" "$WORK_DIR/"
|
||||
make_dylib_portable "$WORK_DIR/libkrun.dylib"
|
||||
fi
|
||||
|
||||
# libkrunfw from custom build
|
||||
cp "${CUSTOM_DIR}/libkrunfw.dylib" "$WORK_DIR/"
|
||||
else
|
||||
echo "Error: No portable libkrun build found." >&2
|
||||
echo " Run: FROM_SOURCE=1 mise run vm:setup" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Normalize libkrunfw naming - ensure both names exist for build.rs
|
||||
# build.rs expects libkrunfw.5.dylib.zst; some builds produce libkrunfw.dylib
|
||||
if [ ! -f "$WORK_DIR/libkrunfw.dylib" ] && [ -f "$WORK_DIR/libkrunfw.5.dylib" ]; then
|
||||
cp "$WORK_DIR/libkrunfw.5.dylib" "$WORK_DIR/libkrunfw.dylib"
|
||||
fi
|
||||
if [ ! -f "$WORK_DIR/libkrunfw.5.dylib" ] && [ -f "$WORK_DIR/libkrunfw.dylib" ]; then
|
||||
cp "$WORK_DIR/libkrunfw.dylib" "$WORK_DIR/libkrunfw.5.dylib"
|
||||
fi
|
||||
|
||||
# gvproxy - prefer Podman, fall back to Homebrew
|
||||
if [ -x /opt/podman/bin/gvproxy ]; then
|
||||
cp /opt/podman/bin/gvproxy "$WORK_DIR/"
|
||||
echo " Using gvproxy from Podman"
|
||||
elif [ -x "${BREW_PREFIX}/bin/gvproxy" ]; then
|
||||
cp "${BREW_PREFIX}/bin/gvproxy" "$WORK_DIR/"
|
||||
echo " Using gvproxy from Homebrew"
|
||||
else
|
||||
echo "Error: gvproxy not found. Install Podman Desktop or run: brew install gvproxy" >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
|
||||
Linux-*)
|
||||
ARCH="$(uname -m)"
|
||||
case "$ARCH" in
|
||||
aarch64) GVPROXY_ARCH="arm64" ;;
|
||||
x86_64) GVPROXY_ARCH="amd64" ;;
|
||||
*)
|
||||
echo "Error: Unsupported Linux architecture: ${ARCH}" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
PLATFORM="linux-${ARCH}"
|
||||
echo " Platform: Linux ${ARCH}"
|
||||
|
||||
BUILD_DIR="${ROOT}/target/libkrun-build"
|
||||
if [ ! -f "${BUILD_DIR}/libkrun.so" ]; then
|
||||
echo "Error: libkrun not found. Run: FROM_SOURCE=1 mise run vm:setup" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cp "${BUILD_DIR}/libkrun.so" "$WORK_DIR/"
|
||||
|
||||
# Copy libkrunfw - find the versioned .so file
|
||||
for krunfw in "${BUILD_DIR}"/libkrunfw.so*; do
|
||||
[ -f "$krunfw" ] || continue
|
||||
cp "$krunfw" "$WORK_DIR/"
|
||||
done
|
||||
|
||||
# Ensure the soname symlink (libkrunfw.so.5) exists alongside the fully
|
||||
# versioned file (libkrunfw.so.5.x.y). libloading loads by soname.
|
||||
if [ ! -f "$WORK_DIR/libkrunfw.so.5" ]; then
|
||||
versioned=$(ls "$WORK_DIR"/libkrunfw.so.5.* 2>/dev/null | head -n1)
|
||||
if [ -n "$versioned" ]; then
|
||||
cp "$versioned" "$WORK_DIR/libkrunfw.so.5"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Download gvproxy if not present
|
||||
if [ ! -f "$WORK_DIR/gvproxy" ]; then
|
||||
echo " Downloading gvproxy for linux-${GVPROXY_ARCH}..."
|
||||
curl -fsSL -o "$WORK_DIR/gvproxy" \
|
||||
"https://github.com/containers/gvisor-tap-vsock/releases/download/${GVPROXY_VERSION}/gvproxy-linux-${GVPROXY_ARCH}"
|
||||
chmod +x "$WORK_DIR/gvproxy"
|
||||
fi
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Error: Unsupported platform: $(uname -s)-$(uname -m)" >&2
|
||||
echo "Supported platforms: Darwin-arm64, Linux-aarch64, Linux-x86_64" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
echo ""
|
||||
echo "==> Collected artifacts:"
|
||||
ls -lah "$WORK_DIR"
|
||||
|
||||
echo ""
|
||||
compress_dir "$WORK_DIR" "$OUTPUT_DIR"
|
||||
|
||||
# Check for rootfs tarball (built separately by build-rootfs-tarball.sh)
|
||||
ROOTFS_TARBALL="${OUTPUT_DIR}/rootfs.tar.zst"
|
||||
if [ -f "$ROOTFS_TARBALL" ]; then
|
||||
echo " rootfs.tar.zst: $(du -h "$ROOTFS_TARBALL" | cut -f1) (pre-built)"
|
||||
else
|
||||
echo ""
|
||||
echo "Note: rootfs.tar.zst not found."
|
||||
echo " To build one, run: mise run vm:rootfs -- --base"
|
||||
echo " Without it, the binary will still work but require the rootfs"
|
||||
echo " to be built separately on first run."
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "==> Compressed artifacts in ${OUTPUT_DIR}:"
|
||||
ls -lah "$OUTPUT_DIR"
|
||||
|
||||
TOTAL=$(du -sh "$OUTPUT_DIR" | cut -f1)
|
||||
echo ""
|
||||
echo "==> Total compressed size: ${TOTAL}"
|
||||
echo ""
|
||||
echo "Next step: mise run vm:build"
|
||||
Executable
+141
@@ -0,0 +1,141 @@
|
||||
#!/usr/bin/env bash
|
||||
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# Download pre-built VM kernel runtime artifacts from the vm-dev GitHub Release
|
||||
# and stage them for the openshell-vm cargo build.
|
||||
#
|
||||
# This script is used by CI (release-vm-dev.yml) and can also be used locally
|
||||
# to avoid building libkrun/libkrunfw from source.
|
||||
#
|
||||
# Usage:
|
||||
# ./download-kernel-runtime.sh [--platform PLATFORM]
|
||||
#
|
||||
# Environment:
|
||||
# VM_RUNTIME_RELEASE_TAG - GitHub Release tag (default: vm-dev)
|
||||
# GITHUB_REPOSITORY - owner/repo (default: NVIDIA/OpenShell)
|
||||
# OPENSHELL_VM_RUNTIME_COMPRESSED_DIR - Output directory (default: target/vm-runtime-compressed)
|
||||
#
|
||||
# Platforms: linux-aarch64, linux-x86_64, darwin-aarch64
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/_lib.sh"
|
||||
ROOT="$(vm_lib_root)"
|
||||
|
||||
RELEASE_TAG="${VM_RUNTIME_RELEASE_TAG:-vm-dev}"
|
||||
REPO="${GITHUB_REPOSITORY:-NVIDIA/OpenShell}"
|
||||
OUTPUT_DIR="${OPENSHELL_VM_RUNTIME_COMPRESSED_DIR:-${ROOT}/target/vm-runtime-compressed}"
|
||||
|
||||
# ── Auto-detect platform (detect_platform from _lib.sh) ─────────────────
|
||||
|
||||
PLATFORM=""
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--platform)
|
||||
PLATFORM="$2"; shift 2 ;;
|
||||
--help|-h)
|
||||
echo "Usage: $0 [--platform PLATFORM]"
|
||||
echo ""
|
||||
echo "Download pre-built VM kernel runtime from the vm-dev GitHub Release."
|
||||
echo ""
|
||||
echo "Platforms: linux-aarch64, linux-x86_64, darwin-aarch64"
|
||||
echo ""
|
||||
echo "Environment:"
|
||||
echo " VM_RUNTIME_RELEASE_TAG Release tag (default: vm-dev)"
|
||||
echo " GITHUB_REPOSITORY owner/repo (default: NVIDIA/OpenShell)"
|
||||
echo " OPENSHELL_VM_RUNTIME_COMPRESSED_DIR Output directory"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown argument: $1" >&2; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "$PLATFORM" ]; then
|
||||
PLATFORM="$(detect_platform)"
|
||||
fi
|
||||
|
||||
TARBALL_NAME="vm-runtime-${PLATFORM}.tar.zst"
|
||||
|
||||
echo "==> Downloading VM kernel runtime"
|
||||
echo " Repository: ${REPO}"
|
||||
echo " Release: ${RELEASE_TAG}"
|
||||
echo " Platform: ${PLATFORM}"
|
||||
echo " Artifact: ${TARBALL_NAME}"
|
||||
echo " Output: ${OUTPUT_DIR}"
|
||||
echo ""
|
||||
|
||||
# ── Check for gh CLI ────────────────────────────────────────────────────
|
||||
|
||||
if ! command -v gh &>/dev/null; then
|
||||
echo "Error: GitHub CLI (gh) is required." >&2
|
||||
echo " Install: https://cli.github.com/" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ── Download the runtime tarball ────────────────────────────────────────
|
||||
|
||||
DOWNLOAD_DIR="${ROOT}/target/vm-runtime-download"
|
||||
mkdir -p "$DOWNLOAD_DIR" "$OUTPUT_DIR"
|
||||
|
||||
echo "==> Downloading ${TARBALL_NAME} from ${RELEASE_TAG}..."
|
||||
gh release download "${RELEASE_TAG}" \
|
||||
--repo "${REPO}" \
|
||||
--pattern "${TARBALL_NAME}" \
|
||||
--dir "${DOWNLOAD_DIR}" \
|
||||
--clobber
|
||||
|
||||
if [ ! -f "${DOWNLOAD_DIR}/${TARBALL_NAME}" ]; then
|
||||
echo "Error: Download failed — ${TARBALL_NAME} not found." >&2
|
||||
echo "" >&2
|
||||
echo "The vm-dev release may not have kernel runtime artifacts yet." >&2
|
||||
echo "Run the 'Release VM Kernel' workflow first:" >&2
|
||||
echo " gh workflow run release-vm-kernel.yml" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo " Downloaded: $(du -sh "${DOWNLOAD_DIR}/${TARBALL_NAME}" | cut -f1)"
|
||||
|
||||
# ── Extract and stage for cargo build ───────────────────────────────────
|
||||
|
||||
echo ""
|
||||
echo "==> Extracting runtime artifacts..."
|
||||
|
||||
EXTRACT_DIR="${ROOT}/target/vm-runtime-extracted"
|
||||
rm -rf "$EXTRACT_DIR"
|
||||
mkdir -p "$EXTRACT_DIR"
|
||||
|
||||
zstd -d "${DOWNLOAD_DIR}/${TARBALL_NAME}" --stdout | tar -xf - -C "$EXTRACT_DIR"
|
||||
|
||||
echo " Extracted files:"
|
||||
ls -lah "$EXTRACT_DIR"
|
||||
|
||||
# ── Compress individual files for embedding ─────────────────────────────
|
||||
# The cargo build expects individual .zst files (libkrun.so.zst, etc.)
|
||||
# in OPENSHELL_VM_RUNTIME_COMPRESSED_DIR. The downloaded tarball contains
|
||||
# the raw libraries, so we re-compress each one.
|
||||
|
||||
echo ""
|
||||
compress_dir "$EXTRACT_DIR" "$OUTPUT_DIR"
|
||||
|
||||
# ── Check for rootfs (may already be present from a separate build step) ──
|
||||
|
||||
if [ -f "${OUTPUT_DIR}/rootfs.tar.zst" ]; then
|
||||
echo ""
|
||||
echo " rootfs.tar.zst: $(du -h "${OUTPUT_DIR}/rootfs.tar.zst" | cut -f1) (pre-existing)"
|
||||
else
|
||||
echo ""
|
||||
echo "Note: rootfs.tar.zst not found in ${OUTPUT_DIR}."
|
||||
echo " Build it with: mise run vm:rootfs -- --base"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "==> Staged artifacts in ${OUTPUT_DIR}:"
|
||||
ls -lah "$OUTPUT_DIR"
|
||||
|
||||
echo ""
|
||||
echo "==> Done."
|
||||
echo ""
|
||||
echo "Next step: mise run vm:build"
|
||||
Executable
+72
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env bash
|
||||
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
|
||||
GATEWAY_BIN="${ROOT}/target/debug/openshell-vm"
|
||||
|
||||
NAME="default"
|
||||
ROOTFS_ARGS=()
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--name)
|
||||
NAME="$2"
|
||||
shift 2
|
||||
;;
|
||||
--name=*)
|
||||
NAME="${1#--name=}"
|
||||
shift
|
||||
;;
|
||||
--rootfs)
|
||||
ROOTFS_ARGS=("$1" "$2")
|
||||
shift 2
|
||||
;;
|
||||
--rootfs=*)
|
||||
ROOTFS_ARGS=("$1")
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
echo "Unknown argument: $1" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ ! -x "${GATEWAY_BIN}" ]; then
|
||||
echo "ERROR: openshell-vm binary not found at ${GATEWAY_BIN}" >&2
|
||||
echo " Run: mise run vm:build" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
prepare_args=(--name "${NAME}")
|
||||
if [ "${#ROOTFS_ARGS[@]}" -gt 0 ]; then
|
||||
prepare_args=("${ROOTFS_ARGS[@]}" "${prepare_args[@]}")
|
||||
fi
|
||||
if [ "${OPENSHELL_VM_FORCE_ROOTFS_REBUILD:-}" = "1" ]; then
|
||||
prepare_args+=(prepare-rootfs --force)
|
||||
else
|
||||
prepare_args+=(prepare-rootfs)
|
||||
fi
|
||||
|
||||
if ROOTFS_PATH="$("${GATEWAY_BIN}" "${prepare_args[@]}" 2>/dev/null)"; then
|
||||
echo "using openshell-vm rootfs at ${ROOTFS_PATH}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# prepare-rootfs failed — no embedded rootfs in the binary.
|
||||
# Fall back to target/rootfs-build if it exists (rootfs was built separately
|
||||
# but not yet compressed for embedding via mise run vm:rootfs).
|
||||
if [ "${#ROOTFS_ARGS[@]}" -eq 0 ]; then
|
||||
FALLBACK_ROOTFS="${ROOT}/target/rootfs-build"
|
||||
if [ -d "${FALLBACK_ROOTFS}/srv" ]; then
|
||||
echo "using openshell-vm rootfs at ${FALLBACK_ROOTFS}"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "ERROR: No rootfs available." >&2
|
||||
echo " Run: mise run vm:rootfs -- --base # build rootfs (~5-10 min, requires Docker)" >&2
|
||||
exit 1
|
||||
Executable
+166
@@ -0,0 +1,166 @@
|
||||
#!/usr/bin/env bash
|
||||
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# Package VM runtime artifacts into a release tarball.
|
||||
#
|
||||
# Used by CI (release-vm-kernel.yml) to bundle libkrun, libkrunfw, and gvproxy
|
||||
# into a platform-specific tarball for the vm-dev GitHub Release. Handles
|
||||
# gvproxy download, provenance metadata generation, and tarball creation.
|
||||
#
|
||||
# Usage:
|
||||
# ./package-vm-runtime.sh --platform <PLATFORM> --build-dir <DIR> --output <FILE>
|
||||
#
|
||||
# Arguments:
|
||||
# --platform One of: linux-aarch64, linux-x86_64, darwin-aarch64
|
||||
# --build-dir Directory containing built libkrun and libkrunfw artifacts
|
||||
# --output Path for the output .tar.zst file
|
||||
#
|
||||
# Environment (optional, for provenance):
|
||||
# GITHUB_SHA - Git commit SHA
|
||||
# GITHUB_RUN_ID - GitHub Actions run ID
|
||||
# CUSTOM_PROVENANCE_DIR - Directory containing provenance.json from custom
|
||||
# libkrunfw build (macOS only)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/_lib.sh"
|
||||
ROOT="$(vm_lib_root)"
|
||||
|
||||
# Source pins for gvproxy version
|
||||
source "${ROOT}/crates/openshell-vm/pins.env" 2>/dev/null || true
|
||||
GVPROXY_VERSION="${GVPROXY_VERSION:-v0.8.8}"
|
||||
|
||||
PLATFORM=""
|
||||
BUILD_DIR=""
|
||||
OUTPUT=""
|
||||
CUSTOM_PROVENANCE_DIR="${CUSTOM_PROVENANCE_DIR:-}"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--platform) PLATFORM="$2"; shift 2 ;;
|
||||
--build-dir) BUILD_DIR="$2"; shift 2 ;;
|
||||
--output) OUTPUT="$2"; shift 2 ;;
|
||||
--help|-h)
|
||||
echo "Usage: $0 --platform <PLATFORM> --build-dir <DIR> --output <FILE>"
|
||||
echo ""
|
||||
echo "Package VM runtime artifacts into a release tarball."
|
||||
echo ""
|
||||
echo "Platforms: linux-aarch64, linux-x86_64, darwin-aarch64"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown argument: $1" >&2; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "$PLATFORM" ] || [ -z "$BUILD_DIR" ] || [ -z "$OUTPUT" ]; then
|
||||
echo "Error: --platform, --build-dir, and --output are all required" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "==> Packaging VM runtime"
|
||||
echo " Platform: ${PLATFORM}"
|
||||
echo " Build dir: ${BUILD_DIR}"
|
||||
echo " Output: ${OUTPUT}"
|
||||
echo ""
|
||||
|
||||
# ── Create staging directory ────────────────────────────────────────────
|
||||
|
||||
PACKAGE_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$PACKAGE_DIR"' EXIT
|
||||
|
||||
# ── Copy runtime libraries ──────────────────────────────────────────────
|
||||
|
||||
case "$PLATFORM" in
|
||||
linux-*)
|
||||
cp "${BUILD_DIR}/libkrun.so" "${PACKAGE_DIR}/"
|
||||
# Copy libkrunfw — find versioned .so and create soname symlink
|
||||
for f in "${BUILD_DIR}"/libkrunfw.so*; do
|
||||
[ -f "$f" ] && cp "$f" "${PACKAGE_DIR}/"
|
||||
done
|
||||
if [ ! -f "${PACKAGE_DIR}/libkrunfw.so.5" ]; then
|
||||
versioned="$(ls "${PACKAGE_DIR}"/libkrunfw.so.5.* 2>/dev/null | head -n1 || true)"
|
||||
[ -n "$versioned" ] && cp "$versioned" "${PACKAGE_DIR}/libkrunfw.so.5"
|
||||
fi
|
||||
;;
|
||||
darwin-aarch64)
|
||||
cp "${BUILD_DIR}/libkrun.dylib" "${PACKAGE_DIR}/"
|
||||
# libkrunfw — prefer build dir, fall back to custom runtime dir
|
||||
candidates=("${BUILD_DIR}/libkrunfw.dylib" "${BUILD_DIR}/libkrunfw.5.dylib")
|
||||
if [ -n "$CUSTOM_PROVENANCE_DIR" ]; then
|
||||
candidates+=("${CUSTOM_PROVENANCE_DIR}/libkrunfw.dylib" "${CUSTOM_PROVENANCE_DIR}/libkrunfw.5.dylib")
|
||||
fi
|
||||
for candidate in "${candidates[@]}"; do
|
||||
if [ -f "$candidate" ]; then
|
||||
cp "$candidate" "${PACKAGE_DIR}/"
|
||||
fi
|
||||
done
|
||||
;;
|
||||
*)
|
||||
echo "Error: Unknown platform: ${PLATFORM}" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# ── Download gvproxy ────────────────────────────────────────────────────
|
||||
|
||||
echo "==> Downloading gvproxy ${GVPROXY_VERSION} for ${PLATFORM}..."
|
||||
case "$PLATFORM" in
|
||||
linux-aarch64) GVPROXY_SUFFIX="linux-arm64" ;;
|
||||
linux-x86_64) GVPROXY_SUFFIX="linux-amd64" ;;
|
||||
darwin-aarch64) GVPROXY_SUFFIX="darwin" ;;
|
||||
esac
|
||||
|
||||
curl -fsSL -o "${PACKAGE_DIR}/gvproxy" \
|
||||
"https://github.com/containers/gvisor-tap-vsock/releases/download/${GVPROXY_VERSION}/gvproxy-${GVPROXY_SUFFIX}"
|
||||
chmod +x "${PACKAGE_DIR}/gvproxy"
|
||||
|
||||
# ── Write provenance metadata ───────────────────────────────────────────
|
||||
|
||||
echo "==> Writing provenance metadata..."
|
||||
|
||||
LIBKRUNFW_COMMIT="unknown"
|
||||
KERNEL_VERSION="unknown"
|
||||
|
||||
# Try custom provenance first (macOS builds produce this)
|
||||
if [ -n "$CUSTOM_PROVENANCE_DIR" ] && [ -f "${CUSTOM_PROVENANCE_DIR}/provenance.json" ]; then
|
||||
LIBKRUNFW_COMMIT="$(jq -r '.libkrunfw_commit // "unknown"' "${CUSTOM_PROVENANCE_DIR}/provenance.json" 2>/dev/null || echo unknown)"
|
||||
KERNEL_VERSION="$(jq -r '.kernel_version // "unknown"' "${CUSTOM_PROVENANCE_DIR}/provenance.json" 2>/dev/null || echo unknown)"
|
||||
fi
|
||||
|
||||
# Fall back to inspecting the build directory (Linux builds)
|
||||
if [ "$LIBKRUNFW_COMMIT" = "unknown" ] && [ -d "${BUILD_DIR}/libkrunfw/.git" ]; then
|
||||
LIBKRUNFW_COMMIT="$(git -C "${BUILD_DIR}/libkrunfw" rev-parse HEAD 2>/dev/null || echo unknown)"
|
||||
fi
|
||||
if [ "$KERNEL_VERSION" = "unknown" ] && [ -f "${BUILD_DIR}/libkrunfw/Makefile" ]; then
|
||||
KERNEL_VERSION="$(grep -oE 'KERNEL_VERSION\s*=\s*linux-[^\s]+' "${BUILD_DIR}/libkrunfw/Makefile" | head -1 | sed 's/.*= *//' || echo unknown)"
|
||||
fi
|
||||
|
||||
if ! command -v jq &>/dev/null; then
|
||||
echo "Error: jq is required for provenance generation" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
jq -n \
|
||||
--arg artifact "vm-runtime" \
|
||||
--arg platform "$PLATFORM" \
|
||||
--arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
||||
--arg kfw_commit "$LIBKRUNFW_COMMIT" \
|
||||
--arg kver "$KERNEL_VERSION" \
|
||||
--arg sha "${GITHUB_SHA:-unknown}" \
|
||||
--arg run "${GITHUB_RUN_ID:-unknown}" \
|
||||
'{artifact: $artifact, platform: $platform, build_timestamp: $ts, libkrunfw_commit: $kfw_commit, kernel_version: $kver, github_sha: $sha, github_run_id: $run}' \
|
||||
> "${PACKAGE_DIR}/provenance.json"
|
||||
|
||||
# ── Create tarball ──────────────────────────────────────────────────────
|
||||
|
||||
echo "==> Creating tarball..."
|
||||
mkdir -p "$(dirname "$OUTPUT")"
|
||||
tar -C "${PACKAGE_DIR}" -cf - . | zstd -19 -T0 -o "$OUTPUT"
|
||||
|
||||
echo ""
|
||||
echo "==> Packaged ${OUTPUT} ($(du -sh "$OUTPUT" | cut -f1))"
|
||||
echo " Contents:"
|
||||
ls -lah "${PACKAGE_DIR}"
|
||||
Executable
+75
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env bash
|
||||
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
|
||||
RUNTIME_DIR="${ROOT}/target/debug/openshell-vm.runtime"
|
||||
GATEWAY_BIN="${ROOT}/target/debug/openshell-vm"
|
||||
|
||||
if [ "$(uname -s)" = "Darwin" ]; then
|
||||
export DYLD_FALLBACK_LIBRARY_PATH="${RUNTIME_DIR}${DYLD_FALLBACK_LIBRARY_PATH:+:${DYLD_FALLBACK_LIBRARY_PATH}}"
|
||||
fi
|
||||
|
||||
args=("$@")
|
||||
name="default"
|
||||
rootfs_args=()
|
||||
expect_name=0
|
||||
expect_rootfs=0
|
||||
subcommand=""
|
||||
skip_prepare=0
|
||||
|
||||
for arg in "${args[@]}"; do
|
||||
if [ "${expect_name}" -eq 1 ]; then
|
||||
name="${arg}"
|
||||
expect_name=0
|
||||
continue
|
||||
fi
|
||||
|
||||
if [ "${expect_rootfs}" -eq 1 ]; then
|
||||
rootfs_args=(--rootfs "${arg}")
|
||||
expect_rootfs=0
|
||||
continue
|
||||
fi
|
||||
|
||||
case "${arg}" in
|
||||
--name)
|
||||
expect_name=1
|
||||
;;
|
||||
--name=*)
|
||||
name="${arg#--name=}"
|
||||
;;
|
||||
--rootfs)
|
||||
expect_rootfs=1
|
||||
;;
|
||||
--rootfs=*)
|
||||
rootfs_args=("${arg}")
|
||||
;;
|
||||
--help|-h|--version)
|
||||
skip_prepare=1
|
||||
;;
|
||||
exec|prepare-rootfs)
|
||||
subcommand="${arg}"
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ "${skip_prepare}" -eq 0 ] && [ -z "${subcommand}" ]; then
|
||||
prep_args=(--name "${name}")
|
||||
if [ "${#rootfs_args[@]}" -gt 0 ]; then
|
||||
prep_args=("${rootfs_args[@]}" "${prep_args[@]}")
|
||||
fi
|
||||
resolved_rootfs="$("${ROOT}/tasks/scripts/vm/ensure-vm-rootfs.sh" "${prep_args[@]}" \
|
||||
| tail -n 1 | sed 's/^using openshell-vm rootfs at //')"
|
||||
"${ROOT}/tasks/scripts/vm/sync-vm-rootfs.sh" "${prep_args[@]}"
|
||||
|
||||
# When no --rootfs was supplied by the caller, inject the resolved rootfs path
|
||||
# so the binary finds the rootfs regardless of whether it is embedded.
|
||||
if [ "${#rootfs_args[@]}" -eq 0 ] && [ -n "${resolved_rootfs}" ]; then
|
||||
args=(--rootfs "${resolved_rootfs}" "${args[@]}")
|
||||
fi
|
||||
fi
|
||||
|
||||
exec "${GATEWAY_BIN}" "${args[@]}"
|
||||
Executable
+182
@@ -0,0 +1,182 @@
|
||||
#!/usr/bin/env bash
|
||||
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# Sync mutable development artifacts into the existing VM rootfs.
|
||||
# Runs on every `mise run vm` so that script changes, helm chart
|
||||
# updates, manifest changes, and supervisor binary rebuilds are
|
||||
# picked up without a full rootfs rebuild.
|
||||
#
|
||||
# This is fast (<1s) — it only copies files, no Docker or VM boot.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
|
||||
SCRIPT_DIR="${ROOT}/crates/openshell-vm/scripts"
|
||||
IMAGE_REPO_BASE="${IMAGE_REPO_BASE:-openshell}"
|
||||
IMAGE_TAG="${IMAGE_TAG:-dev}"
|
||||
SERVER_IMAGE="${IMAGE_REPO_BASE}/gateway:${IMAGE_TAG}"
|
||||
NAME="default"
|
||||
ROOTFS_ARGS=()
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--name)
|
||||
NAME="$2"
|
||||
shift 2
|
||||
;;
|
||||
--name=*)
|
||||
NAME="${1#--name=}"
|
||||
shift
|
||||
;;
|
||||
--rootfs)
|
||||
ROOTFS_ARGS=("$1" "$2")
|
||||
shift 2
|
||||
;;
|
||||
--rootfs=*)
|
||||
ROOTFS_ARGS=("$1")
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
echo "Unknown argument: $1" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
ensure_args=(--name "${NAME}")
|
||||
if [ "${#ROOTFS_ARGS[@]}" -gt 0 ]; then
|
||||
ensure_args=("${ROOTFS_ARGS[@]}" "${ensure_args[@]}")
|
||||
fi
|
||||
|
||||
if ! ROOTFS_DIR="$("${ROOT}/tasks/scripts/vm/ensure-vm-rootfs.sh" "${ensure_args[@]}" | tail -n 1 | sed 's/^using openshell-vm rootfs at //')"; then
|
||||
echo "ERROR: ensure-vm-rootfs.sh failed — no rootfs available." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
patch_vm_helmchart() {
|
||||
local helmchart="$1"
|
||||
[ -f "${helmchart}" ] || return 0
|
||||
|
||||
sed_in_place() {
|
||||
local expr="$1"
|
||||
sed -i.bak -E "${expr}" "${helmchart}"
|
||||
rm -f "${helmchart}.bak"
|
||||
}
|
||||
|
||||
# Mirror the build-rootfs patching so the VM keeps using the locally
|
||||
# imported openshell/gateway:dev image after incremental rootfs syncs.
|
||||
sed_in_place 's|__IMAGE_PULL_POLICY__|IfNotPresent|g'
|
||||
sed_in_place 's|__SANDBOX_IMAGE_PULL_POLICY__|"IfNotPresent"|g'
|
||||
sed_in_place 's|__DB_URL__|"sqlite:/tmp/openshell.db"|g'
|
||||
sed_in_place "s|repository:[[:space:]]*[^[:space:]]+|repository: ${SERVER_IMAGE%:*}|"
|
||||
sed_in_place "s|tag:[[:space:]]*\"?[^\"[:space:]]+\"?|tag: \"${IMAGE_TAG}\"|"
|
||||
sed_in_place 's|sshGatewayHost: __SSH_GATEWAY_HOST__|sshGatewayHost: ""|g'
|
||||
sed_in_place 's|sshGatewayPort: __SSH_GATEWAY_PORT__|sshGatewayPort: 0|g'
|
||||
sed_in_place 's|__DISABLE_GATEWAY_AUTH__|false|g'
|
||||
sed_in_place 's|__DISABLE_TLS__|false|g'
|
||||
sed_in_place 's|hostGatewayIP: __HOST_GATEWAY_IP__|hostGatewayIP: ""|g'
|
||||
sed_in_place '/__CHART_CHECKSUM__/d'
|
||||
}
|
||||
|
||||
if [ ! -d "${ROOTFS_DIR}/srv" ]; then
|
||||
# Rootfs doesn't exist yet — nothing to sync. ensure-vm-rootfs.sh
|
||||
# or build-rootfs.sh will create it.
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Syncing development artifacts into rootfs..."
|
||||
|
||||
# ── Init scripts and utilities ─────────────────────────────────────────
|
||||
for script in openshell-vm-init.sh openshell-vm-exec-agent.py check-vm-capabilities.sh; do
|
||||
src="${SCRIPT_DIR}/${script}"
|
||||
dst="${ROOTFS_DIR}/srv/${script}"
|
||||
if [ -f "$src" ]; then
|
||||
if ! cmp -s "$src" "$dst" 2>/dev/null; then
|
||||
cp "$src" "$dst"
|
||||
chmod +x "$dst"
|
||||
echo " updated: /srv/${script}"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# ── Helm chart ─────────────────────────────────────────────────────────
|
||||
HELM_CHART_DIR="${ROOT}/deploy/helm/openshell"
|
||||
CHART_STAGING="${ROOTFS_DIR}/opt/openshell/charts"
|
||||
if [ -d "${HELM_CHART_DIR}" ]; then
|
||||
mkdir -p "${CHART_STAGING}"
|
||||
# Package into a temp dir and compare — only update if changed.
|
||||
TMP_CHART=$(mktemp -d)
|
||||
helm package "${HELM_CHART_DIR}" -d "${TMP_CHART}" >/dev/null 2>&1
|
||||
for tgz in "${TMP_CHART}"/*.tgz; do
|
||||
[ -f "$tgz" ] || continue
|
||||
base=$(basename "$tgz")
|
||||
if ! cmp -s "$tgz" "${CHART_STAGING}/${base}" 2>/dev/null; then
|
||||
cp "$tgz" "${CHART_STAGING}/${base}"
|
||||
echo " updated: /opt/openshell/charts/${base}"
|
||||
fi
|
||||
done
|
||||
rm -rf "${TMP_CHART}"
|
||||
fi
|
||||
|
||||
# ── Kubernetes manifests ───────────────────────────────────────────────
|
||||
MANIFEST_SRC="${ROOT}/deploy/kube/manifests"
|
||||
MANIFEST_DST="${ROOTFS_DIR}/opt/openshell/manifests"
|
||||
if [ -d "${MANIFEST_SRC}" ]; then
|
||||
mkdir -p "${MANIFEST_DST}"
|
||||
for manifest in "${MANIFEST_SRC}"/*.yaml; do
|
||||
[ -f "$manifest" ] || continue
|
||||
base=$(basename "$manifest")
|
||||
if ! cmp -s "$manifest" "${MANIFEST_DST}/${base}" 2>/dev/null; then
|
||||
cp "$manifest" "${MANIFEST_DST}/${base}"
|
||||
echo " updated: /opt/openshell/manifests/${base}"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
patch_vm_helmchart "${MANIFEST_DST}/openshell-helmchart.yaml"
|
||||
patch_vm_helmchart "${ROOTFS_DIR}/var/lib/rancher/k3s/server/manifests/openshell-helmchart.yaml"
|
||||
|
||||
# ── Gateway image tarball ──────────────────────────────────────────────
|
||||
# The VM rootfs airgap-imports openshell/gateway:dev from k3s/agent/images/.
|
||||
# Keep that tarball in sync with the local Docker image so `mise run e2e:vm`
|
||||
# validates the current openshell-server code, not whatever image happened to
|
||||
# be baked into the rootfs last time it was rebuilt.
|
||||
SERVER_IMAGE_TAR="${ROOTFS_DIR}/var/lib/rancher/k3s/agent/images/openshell-server.tar.zst"
|
||||
SERVER_IMAGE_ID_FILE="${ROOTFS_DIR}/opt/openshell/.gateway-image-id"
|
||||
if command -v docker >/dev/null 2>&1 && docker image inspect "${SERVER_IMAGE}" >/dev/null 2>&1; then
|
||||
current_image_id=$(docker image inspect --format '{{.Id}}' "${SERVER_IMAGE}")
|
||||
previous_image_id=""
|
||||
if [ -f "${SERVER_IMAGE_ID_FILE}" ]; then
|
||||
previous_image_id=$(cat "${SERVER_IMAGE_ID_FILE}")
|
||||
fi
|
||||
|
||||
if [ "${current_image_id}" != "${previous_image_id}" ] || [ ! -f "${SERVER_IMAGE_TAR}" ]; then
|
||||
mkdir -p "$(dirname "${SERVER_IMAGE_TAR}")" "$(dirname "${SERVER_IMAGE_ID_FILE}")"
|
||||
tmp_tar=$(mktemp /tmp/openshell-server-image.XXXXXX)
|
||||
docker save "${SERVER_IMAGE}" | zstd -f -T0 -3 -o "${tmp_tar}" >/dev/null
|
||||
mv "${tmp_tar}" "${SERVER_IMAGE_TAR}"
|
||||
printf '%s\n' "${current_image_id}" > "${SERVER_IMAGE_ID_FILE}"
|
||||
echo " updated: /var/lib/rancher/k3s/agent/images/openshell-server.tar.zst"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── Supervisor binary ─────────────────────────────────────────────────
|
||||
SUPERVISOR_TARGET="aarch64-unknown-linux-gnu"
|
||||
SUPERVISOR_BIN="${ROOT}/target/${SUPERVISOR_TARGET}/release/openshell-sandbox"
|
||||
SUPERVISOR_DST="${ROOTFS_DIR}/opt/openshell/bin/openshell-sandbox"
|
||||
if [ -f "${SUPERVISOR_BIN}" ]; then
|
||||
mkdir -p "$(dirname "${SUPERVISOR_DST}")"
|
||||
if ! cmp -s "${SUPERVISOR_BIN}" "${SUPERVISOR_DST}" 2>/dev/null; then
|
||||
cp "${SUPERVISOR_BIN}" "${SUPERVISOR_DST}"
|
||||
chmod +x "${SUPERVISOR_DST}"
|
||||
echo " updated: /opt/openshell/bin/openshell-sandbox"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── Fix execute permissions on k3s data binaries ──────────────────────
|
||||
# docker export and macOS virtio-fs can strip execute bits.
|
||||
chmod +x "${ROOTFS_DIR}"/var/lib/rancher/k3s/data/*/bin/* 2>/dev/null || true
|
||||
chmod +x "${ROOTFS_DIR}"/var/lib/rancher/k3s/data/*/bin/aux/* 2>/dev/null || true
|
||||
|
||||
echo "Sync complete."
|
||||
Executable
+92
@@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env bash
|
||||
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# Remove all openshell-vm cached artifacts.
|
||||
#
|
||||
# Use this when you need a clean slate — after running this, you will need to
|
||||
# re-run `mise run vm:setup` before building again.
|
||||
#
|
||||
# Usage:
|
||||
# ./vm-clean.sh # clean VM-specific artifacts
|
||||
# ./vm-clean.sh --all # also remove the compiled binary
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/_lib.sh"
|
||||
ROOT="$(vm_lib_root)"
|
||||
|
||||
CLEAN_ALL=0
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--all)
|
||||
CLEAN_ALL=1
|
||||
shift
|
||||
;;
|
||||
--help|-h)
|
||||
echo "Usage: $0 [--all]"
|
||||
echo ""
|
||||
echo "Remove all openshell-vm cached build artifacts."
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " --all Also remove compiled binaries (target/debug/openshell-vm)"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown argument: $1" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo "==> Cleaning openshell-vm artifacts..."
|
||||
|
||||
removed=0
|
||||
|
||||
remove_if_exists() {
|
||||
local path="$1"
|
||||
local label="$2"
|
||||
if [ -e "$path" ]; then
|
||||
local size
|
||||
size="$(du -sh "$path" 2>/dev/null | cut -f1 || echo "?")"
|
||||
rm -rf "$path"
|
||||
echo " Removed ${label} (${size}): ${path}"
|
||||
removed=$((removed + 1))
|
||||
fi
|
||||
}
|
||||
|
||||
# Build artifacts under target/
|
||||
remove_if_exists "${ROOT}/target/vm-runtime" "uncompressed staging"
|
||||
remove_if_exists "${ROOT}/target/vm-runtime-compressed" "compressed artifacts"
|
||||
remove_if_exists "${ROOT}/target/vm-runtime-download" "downloaded tarballs"
|
||||
remove_if_exists "${ROOT}/target/vm-runtime-extracted" "extraction temp"
|
||||
remove_if_exists "${ROOT}/target/libkrun-build" "libkrun source build"
|
||||
remove_if_exists "${ROOT}/target/custom-runtime" "custom libkrunfw"
|
||||
remove_if_exists "${ROOT}/target/rootfs-build" "rootfs directory"
|
||||
|
||||
# Named instance rootfs directories
|
||||
XDG_DATA="${XDG_DATA_HOME:-${HOME}/.local/share}"
|
||||
VM_DATA_DIR="${XDG_DATA}/openshell/openshell-vm"
|
||||
remove_if_exists "${VM_DATA_DIR}" "named instance rootfs"
|
||||
|
||||
# Embedded runtime cache
|
||||
VM_RUNTIME_CACHE="${XDG_DATA}/openshell/vm-runtime"
|
||||
remove_if_exists "${VM_RUNTIME_CACHE}" "embedded runtime cache"
|
||||
|
||||
if [ "$CLEAN_ALL" -eq 1 ]; then
|
||||
# Remove compiled binaries and sidecar bundles
|
||||
for profile in debug release; do
|
||||
remove_if_exists "${ROOT}/target/${profile}/openshell-vm" "${profile} binary"
|
||||
remove_if_exists "${ROOT}/target/${profile}/openshell-vm.runtime" "${profile} runtime bundle"
|
||||
done
|
||||
fi
|
||||
|
||||
echo ""
|
||||
if [ "$removed" -eq 0 ]; then
|
||||
echo " Nothing to clean."
|
||||
else
|
||||
echo " Removed ${removed} item(s)."
|
||||
fi
|
||||
echo ""
|
||||
echo "Next step: mise run vm:setup"
|
||||
Executable
+122
@@ -0,0 +1,122 @@
|
||||
#!/usr/bin/env bash
|
||||
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# One-time setup for the openshell-vm runtime.
|
||||
#
|
||||
# Downloads pre-built runtime artifacts (libkrun, libkrunfw, gvproxy) from the
|
||||
# vm-dev GitHub Release, or builds them from source when --from-source is set.
|
||||
# After obtaining the runtime, compresses the artifacts for embedding into the
|
||||
# openshell-vm binary.
|
||||
#
|
||||
# Usage:
|
||||
# ./vm-setup.sh # download pre-built (default, ~30s)
|
||||
# ./vm-setup.sh --from-source # build from source (~15-45min)
|
||||
#
|
||||
# Environment:
|
||||
# FROM_SOURCE=1 - Equivalent to --from-source
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/_lib.sh"
|
||||
ROOT="$(vm_lib_root)"
|
||||
|
||||
FROM_SOURCE="${FROM_SOURCE:-0}"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--from-source)
|
||||
FROM_SOURCE=1
|
||||
shift
|
||||
;;
|
||||
--help|-h)
|
||||
echo "Usage: $0 [--from-source]"
|
||||
echo ""
|
||||
echo "Set up the openshell-vm runtime (libkrun, libkrunfw, gvproxy)."
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " --from-source Build runtime from source instead of downloading (~15-45min)"
|
||||
echo ""
|
||||
echo "Environment:"
|
||||
echo " FROM_SOURCE=1 Equivalent to --from-source"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown argument: $1" >&2
|
||||
echo "Use --help for usage information" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
PLATFORM="$(detect_platform)"
|
||||
echo "==> openshell-vm setup"
|
||||
echo " Platform: ${PLATFORM}"
|
||||
echo " Mode: $([ "$FROM_SOURCE" = "1" ] && echo "build from source" || echo "download pre-built")"
|
||||
echo ""
|
||||
|
||||
# ── Obtain runtime artifacts ────────────────────────────────────────────
|
||||
|
||||
if [ "$FROM_SOURCE" = "1" ]; then
|
||||
echo "==> Building runtime from source..."
|
||||
echo ""
|
||||
|
||||
case "$PLATFORM" in
|
||||
darwin-aarch64)
|
||||
# macOS: build custom libkrunfw (kernel) then portable libkrun
|
||||
"${ROOT}/crates/openshell-vm/runtime/build-custom-libkrunfw.sh"
|
||||
echo ""
|
||||
"${ROOT}/tasks/scripts/vm/build-libkrun-macos.sh"
|
||||
;;
|
||||
linux-*)
|
||||
# Linux: build both libkrunfw and libkrun in one go
|
||||
"${ROOT}/tasks/scripts/vm/build-libkrun.sh"
|
||||
;;
|
||||
esac
|
||||
echo ""
|
||||
echo "==> Compressing runtime artifacts for embedding..."
|
||||
"${ROOT}/tasks/scripts/vm/compress-vm-runtime.sh"
|
||||
else
|
||||
echo "==> Downloading pre-built runtime..."
|
||||
"${ROOT}/tasks/scripts/vm/download-kernel-runtime.sh"
|
||||
fi
|
||||
|
||||
# ── Validate ────────────────────────────────────────────────────────────
|
||||
|
||||
OUTPUT_DIR="${OPENSHELL_VM_RUNTIME_COMPRESSED_DIR:-${ROOT}/target/vm-runtime-compressed}"
|
||||
|
||||
# Check that we have the essential compressed artifacts
|
||||
missing=0
|
||||
case "$PLATFORM" in
|
||||
darwin-aarch64)
|
||||
for f in libkrun.dylib.zst libkrunfw.5.dylib.zst gvproxy.zst; do
|
||||
if [ ! -f "${OUTPUT_DIR}/${f}" ]; then
|
||||
echo "ERROR: Missing ${OUTPUT_DIR}/${f}" >&2
|
||||
missing=1
|
||||
fi
|
||||
done
|
||||
;;
|
||||
linux-aarch64|linux-x86_64)
|
||||
for f in libkrun.so.zst libkrunfw.so.5.zst gvproxy.zst; do
|
||||
if [ ! -f "${OUTPUT_DIR}/${f}" ]; then
|
||||
echo "ERROR: Missing ${OUTPUT_DIR}/${f}" >&2
|
||||
missing=1
|
||||
fi
|
||||
done
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "$missing" -eq 1 ]; then
|
||||
echo "" >&2
|
||||
echo "Setup failed: some runtime artifacts are missing." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "==> Setup complete!"
|
||||
echo " Compressed artifacts in: ${OUTPUT_DIR}"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " mise run vm:rootfs --base # build rootfs (requires Docker)"
|
||||
echo " mise run vm # build and run the VM"
|
||||
Reference in New Issue
Block a user