feat(vm): add openshell-vm crate with libkrun microVM gateway (#611)

This commit is contained in:
Drew Newberry
2026-04-08 22:00:01 -07:00
committed by GitHub
parent c2e5256725
commit ddb85b1704
56 changed files with 10972 additions and 61 deletions
+72
View File
@@ -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
}
+271
View File
@@ -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
+248
View File
@@ -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"
+116
View File
@@ -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"
+65
View File
@@ -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
+12
View File
@@ -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"
+246
View File
@@ -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"
+141
View File
@@ -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"
+72
View File
@@ -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
+166
View File
@@ -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}"
+75
View File
@@ -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[@]}"
+182
View File
@@ -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."
+92
View File
@@ -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"
+122
View File
@@ -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"