KVM-based microVMM for the Volt platform: - Sub-second VM boot times - Minimal memory footprint - Landlock LSM + seccomp security - Virtio device support - Custom kernel management Copyright (c) Armored Gates LLC. All rights reserved. Licensed under AGPSL v5.0
292 lines
7.1 KiB
Bash
Executable File
292 lines
7.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# build-rootfs.sh - Create a minimal Alpine rootfs for Volt testing
|
|
#
|
|
# This script creates a small, fast-booting root filesystem suitable
|
|
# for microVM testing. Uses Alpine Linux for its minimal footprint.
|
|
#
|
|
# Requirements:
|
|
# - curl, tar
|
|
# - e2fsprogs (mkfs.ext4) or squashfs-tools (mksquashfs)
|
|
# - Optional: sudo (for proper permissions)
|
|
#
|
|
# Output: images/alpine-rootfs.ext4 (or .squashfs)
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
|
BUILD_DIR="${PROJECT_DIR}/.build/rootfs"
|
|
OUTPUT_DIR="${PROJECT_DIR}/images"
|
|
|
|
# Alpine version
|
|
ALPINE_VERSION="${ALPINE_VERSION:-3.19}"
|
|
ALPINE_RELEASE="${ALPINE_RELEASE:-3.19.1}"
|
|
ALPINE_ARCH="x86_64"
|
|
ALPINE_URL="https://dl-cdn.alpinelinux.org/alpine/v${ALPINE_VERSION}/releases/${ALPINE_ARCH}/alpine-minirootfs-${ALPINE_RELEASE}-${ALPINE_ARCH}.tar.gz"
|
|
|
|
# Image settings
|
|
IMAGE_FORMAT="${IMAGE_FORMAT:-ext4}" # ext4 or squashfs
|
|
IMAGE_SIZE_MB="${IMAGE_SIZE_MB:-64}" # Size for ext4 images
|
|
IMAGE_NAME="alpine-rootfs"
|
|
|
|
# Colors
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
NC='\033[0m'
|
|
|
|
log() { echo -e "${GREEN}[+]${NC} $*"; }
|
|
warn() { echo -e "${YELLOW}[!]${NC} $*"; }
|
|
error() { echo -e "${RED}[✗]${NC} $*"; exit 1; }
|
|
|
|
check_dependencies() {
|
|
log "Checking dependencies..."
|
|
|
|
local deps=(curl tar)
|
|
|
|
case "$IMAGE_FORMAT" in
|
|
ext4) deps+=(mkfs.ext4) ;;
|
|
squashfs) deps+=(mksquashfs) ;;
|
|
*) error "Unknown format: $IMAGE_FORMAT" ;;
|
|
esac
|
|
|
|
for dep in "${deps[@]}"; do
|
|
if ! command -v "$dep" &>/dev/null; then
|
|
error "Missing dependency: $dep"
|
|
fi
|
|
done
|
|
}
|
|
|
|
download_alpine() {
|
|
log "Downloading Alpine minirootfs ${ALPINE_RELEASE}..."
|
|
|
|
mkdir -p "$BUILD_DIR"
|
|
|
|
local tarball="${BUILD_DIR}/alpine-minirootfs.tar.gz"
|
|
if [[ ! -f "$tarball" ]]; then
|
|
curl -L -o "$tarball" "$ALPINE_URL"
|
|
else
|
|
log "Using cached download"
|
|
fi
|
|
}
|
|
|
|
extract_rootfs() {
|
|
log "Extracting rootfs..."
|
|
|
|
local rootfs="${BUILD_DIR}/rootfs"
|
|
rm -rf "$rootfs"
|
|
mkdir -p "$rootfs"
|
|
|
|
# Extract (needs root for proper permissions, but works without)
|
|
if [[ $EUID -eq 0 ]]; then
|
|
tar xzf "${BUILD_DIR}/alpine-minirootfs.tar.gz" -C "$rootfs"
|
|
else
|
|
# Fakeroot alternative or just extract
|
|
tar xzf "${BUILD_DIR}/alpine-minirootfs.tar.gz" -C "$rootfs" 2>/dev/null || \
|
|
tar xzf "${BUILD_DIR}/alpine-minirootfs.tar.gz" -C "$rootfs" --no-same-owner
|
|
warn "Extracted without root - some permissions may be incorrect"
|
|
fi
|
|
}
|
|
|
|
customize_rootfs() {
|
|
log "Customizing rootfs for microVM boot..."
|
|
|
|
local rootfs="${BUILD_DIR}/rootfs"
|
|
|
|
# Create init script for fast boot
|
|
cat > "${rootfs}/init" << 'INIT'
|
|
#!/bin/sh
|
|
# Volt microVM init
|
|
|
|
# Mount essential filesystems
|
|
mount -t proc proc /proc
|
|
mount -t sysfs sys /sys
|
|
mount -t devtmpfs dev /dev
|
|
|
|
# Set hostname
|
|
hostname volt-vmm-vm
|
|
|
|
# Print boot message
|
|
echo ""
|
|
echo "======================================"
|
|
echo " Volt microVM booted!"
|
|
echo " Alpine Linux $(cat /etc/alpine-release)"
|
|
echo "======================================"
|
|
echo ""
|
|
|
|
# Show boot time if available
|
|
if [ -f /proc/uptime ]; then
|
|
uptime=$(cut -d' ' -f1 /proc/uptime)
|
|
echo "Boot time: ${uptime}s"
|
|
fi
|
|
|
|
# Start shell
|
|
exec /bin/sh
|
|
INIT
|
|
chmod +x "${rootfs}/init"
|
|
|
|
# Create minimal inittab
|
|
cat > "${rootfs}/etc/inittab" << 'EOF'
|
|
::sysinit:/etc/init.d/rcS
|
|
::respawn:-/bin/sh
|
|
ttyS0::respawn:/sbin/getty -L ttyS0 115200 vt100
|
|
::shutdown:/bin/umount -a -r
|
|
EOF
|
|
|
|
# Configure serial console
|
|
mkdir -p "${rootfs}/etc/init.d"
|
|
cat > "${rootfs}/etc/init.d/rcS" << 'EOF'
|
|
#!/bin/sh
|
|
mount -t proc proc /proc
|
|
mount -t sysfs sys /sys
|
|
mount -t devtmpfs dev /dev
|
|
hostname volt-vmm-vm
|
|
EOF
|
|
chmod +x "${rootfs}/etc/init.d/rcS"
|
|
|
|
# Set up basic networking config
|
|
mkdir -p "${rootfs}/etc/network"
|
|
cat > "${rootfs}/etc/network/interfaces" << 'EOF'
|
|
auto lo
|
|
iface lo inet loopback
|
|
|
|
auto eth0
|
|
iface eth0 inet dhcp
|
|
EOF
|
|
|
|
# Disable unnecessary services
|
|
rm -f "${rootfs}/etc/init.d/hwclock"
|
|
rm -f "${rootfs}/etc/init.d/hwdrivers"
|
|
|
|
# Create fstab
|
|
cat > "${rootfs}/etc/fstab" << 'EOF'
|
|
/dev/vda / ext4 defaults,noatime 0 1
|
|
proc /proc proc defaults 0 0
|
|
sys /sys sysfs defaults 0 0
|
|
devpts /dev/pts devpts defaults 0 0
|
|
EOF
|
|
|
|
log "Rootfs customized for fast boot"
|
|
}
|
|
|
|
create_ext4_image() {
|
|
log "Creating ext4 image (${IMAGE_SIZE_MB}MB)..."
|
|
|
|
mkdir -p "$OUTPUT_DIR"
|
|
local image="${OUTPUT_DIR}/${IMAGE_NAME}.ext4"
|
|
local rootfs="${BUILD_DIR}/rootfs"
|
|
|
|
# Create sparse file
|
|
dd if=/dev/zero of="$image" bs=1M count=0 seek="$IMAGE_SIZE_MB" 2>/dev/null
|
|
|
|
# Format
|
|
mkfs.ext4 -F -L rootfs -O ^metadata_csum "$image" >/dev/null
|
|
|
|
# Mount and copy (requires root)
|
|
if [[ $EUID -eq 0 ]]; then
|
|
local mnt="${BUILD_DIR}/mnt"
|
|
mkdir -p "$mnt"
|
|
mount -o loop "$image" "$mnt"
|
|
cp -a "${rootfs}/." "$mnt/"
|
|
umount "$mnt"
|
|
else
|
|
# Use debugfs to copy files (limited but works without root)
|
|
warn "Creating image without root - using alternative method"
|
|
|
|
# Create a tar and extract into image using e2tools or fuse
|
|
if command -v e2cp &>/dev/null; then
|
|
# Use e2tools
|
|
find "$rootfs" -type f | while read -r file; do
|
|
local dest="${file#$rootfs}"
|
|
e2cp "$file" "$image:$dest" 2>/dev/null || true
|
|
done
|
|
else
|
|
warn "e2fsprogs-extra not available - image will be empty"
|
|
warn "Install e2fsprogs-extra or run as root for full rootfs"
|
|
fi
|
|
fi
|
|
|
|
echo "$image"
|
|
}
|
|
|
|
create_squashfs_image() {
|
|
log "Creating squashfs image..."
|
|
|
|
mkdir -p "$OUTPUT_DIR"
|
|
local image="${OUTPUT_DIR}/${IMAGE_NAME}.squashfs"
|
|
local rootfs="${BUILD_DIR}/rootfs"
|
|
|
|
mksquashfs "$rootfs" "$image" \
|
|
-comp zstd \
|
|
-Xcompression-level 19 \
|
|
-noappend \
|
|
-quiet
|
|
|
|
echo "$image"
|
|
}
|
|
|
|
create_image() {
|
|
local image
|
|
|
|
case "$IMAGE_FORMAT" in
|
|
ext4) image=$(create_ext4_image) ;;
|
|
squashfs) image=$(create_squashfs_image) ;;
|
|
esac
|
|
|
|
echo "$image"
|
|
}
|
|
|
|
show_stats() {
|
|
local image="$1"
|
|
|
|
log "Rootfs image created successfully!"
|
|
echo ""
|
|
echo " Path: $image"
|
|
echo " Size: $(du -h "$image" | cut -f1)"
|
|
echo " Format: $IMAGE_FORMAT"
|
|
echo " Base: Alpine Linux ${ALPINE_RELEASE}"
|
|
echo ""
|
|
echo "To use with Volt:"
|
|
echo " volt-vmm --kernel kernels/vmlinux --rootfs $image"
|
|
}
|
|
|
|
# Parse arguments
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
--format)
|
|
IMAGE_FORMAT="$2"
|
|
shift 2
|
|
;;
|
|
--size)
|
|
IMAGE_SIZE_MB="$2"
|
|
shift 2
|
|
;;
|
|
--help)
|
|
echo "Usage: $0 [--format ext4|squashfs] [--size MB]"
|
|
exit 0
|
|
;;
|
|
*)
|
|
error "Unknown option: $1"
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Main
|
|
main() {
|
|
log "Building Volt test rootfs"
|
|
echo ""
|
|
|
|
check_dependencies
|
|
download_alpine
|
|
extract_rootfs
|
|
customize_rootfs
|
|
|
|
local image
|
|
image=$(create_image)
|
|
|
|
show_stats "$image"
|
|
}
|
|
|
|
main
|