mirror of
https://github.com/hanzoai/vm.git
synced 2026-10-01 00:47:56 +00:00
140 lines
5.6 KiB
Bash
Executable File
140 lines
5.6 KiB
Bash
Executable File
#!/bin/bash
|
|
# Builds the guest image into the data dir: Image (kernel), initramfs.cpio.gz,
|
|
# rootfs.ext4 (Debian, with the guest binary at /usr/bin/vm-guest).
|
|
#
|
|
# Runs natively on an arm64 Linux host. Needs: debootstrap, e2fsprogs
|
|
# (mke2fs -d), busybox-static, pax-utils (lddtree), cpio, and for the kernel
|
|
# build-essential bc flex bison libelf-dev libssl-dev curl xz-utils. debootstrap
|
|
# needs root; sudo is used when not already root. On macOS the script re-runs
|
|
# itself inside a Debian arm64 container, so only Docker is needed there.
|
|
set -euo pipefail
|
|
|
|
DEBIAN_RELEASE="trixie"
|
|
DATA_DIR="${HANZO_VM_HOME:-$HOME/.hanzo/vm}"
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
REPO_DIR="$(dirname "$SCRIPT_DIR")"
|
|
# Matches the CLI's default --disk-size so the per-run clone is never
|
|
# extended (extending an APFS clone costs ~0.5s at truncate and ~0.7s at
|
|
# delete). The image is sparse; unwritten space costs nothing on disk.
|
|
ROOTFS_SIZE_MB=4096
|
|
|
|
# The guest image is built for the host architecture (macOS builds arm64
|
|
# in a container below).
|
|
case "$(uname -m)" in
|
|
x86_64) GUEST_TARGET="x86_64-unknown-linux-musl"; DEB_ARCH="amd64" ;;
|
|
*) GUEST_TARGET="aarch64-unknown-linux-musl"; DEB_ARCH="arm64" ;;
|
|
esac
|
|
GUEST_BINARY="${REPO_DIR}/target/${GUEST_TARGET}/release/vm-guest"
|
|
|
|
if [ ! -f "$GUEST_BINARY" ]; then
|
|
echo "ERROR: guest binary not found at ${GUEST_BINARY}"
|
|
echo " Run: cargo build -p vm-guest --target ${GUEST_TARGET} --release"
|
|
exit 1
|
|
fi
|
|
|
|
mkdir -p "$DATA_DIR"
|
|
|
|
if [ "$(uname)" = "Darwin" ]; then
|
|
command -v docker >/dev/null || { echo "ERROR: Docker is required on macOS"; exit 1; }
|
|
exec docker run --rm --platform linux/arm64/v8 \
|
|
-v "${REPO_DIR}:/src:ro" -v "${DATA_DIR}:/out" -e HANZO_VM_HOME=/out \
|
|
"debian:${DEBIAN_RELEASE}-slim" /src/scripts/prepare-rootfs.sh
|
|
fi
|
|
|
|
SUDO=""
|
|
[ "$(id -u)" = 0 ] || SUDO=sudo
|
|
|
|
for tool in debootstrap mke2fs busybox lddtree cpio gcc bc flex bison curl xz; do
|
|
if ! command -v "$tool" >/dev/null; then
|
|
$SUDO apt-get update -qq
|
|
$SUDO apt-get install -y -qq debootstrap e2fsprogs busybox-static pax-utils cpio \
|
|
build-essential bc flex bison libelf-dev libssl-dev curl xz-utils ca-certificates
|
|
break
|
|
fi
|
|
done
|
|
|
|
if [ -f "${DATA_DIR}/Image" ]; then
|
|
echo "==> Kernel already present."
|
|
else
|
|
"${SCRIPT_DIR}/build-kernel.sh"
|
|
fi
|
|
|
|
WORK="$(mktemp -d)"
|
|
trap '$SUDO rm -rf "$WORK"' EXIT
|
|
|
|
if [ -f "${DATA_DIR}/initramfs.cpio.gz" ]; then
|
|
echo "==> Initramfs already present."
|
|
else
|
|
echo "==> Building initramfs..."
|
|
IR="${WORK}/initramfs"
|
|
mkdir -p "$IR"/bin "$IR"/etc "$IR"/proc "$IR"/dev "$IR"/newroot
|
|
cp "$(command -v busybox)" "$IR/bin/busybox"
|
|
for cmd in sh mount umount switch_root cp chmod echo ifconfig route cat; do
|
|
ln -sf busybox "$IR/bin/$cmd"
|
|
done
|
|
lddtree -l /usr/sbin/e2fsck /usr/sbin/resize2fs | sort -u | cpio --quiet -pmdL "$IR"
|
|
install -m 755 "$GUEST_BINARY" "$IR/bin/vm-guest"
|
|
cat > "$IR/init" <<'INIT'
|
|
#!/bin/sh
|
|
mount -t proc none /proc
|
|
mount -t devtmpfs none /dev
|
|
/usr/sbin/e2fsck -p /dev/vda > /dev/null 2>&1 || true
|
|
/usr/sbin/resize2fs /dev/vda > /dev/null 2>&1 || true
|
|
mount -t ext4 /dev/vda /newroot
|
|
cp /bin/vm-guest /newroot/usr/bin/vm-guest
|
|
chmod 755 /newroot/usr/bin/vm-guest
|
|
if ifconfig eth0 up 2>/dev/null; then
|
|
ifconfig eth0 10.0.0.2 netmask 255.255.255.0 up
|
|
route add default gw 10.0.0.1
|
|
echo "nameserver 10.0.0.1" > /newroot/etc/resolv.conf
|
|
fi
|
|
umount /proc
|
|
exec switch_root /newroot /usr/bin/vm-guest
|
|
INIT
|
|
chmod 755 "$IR/init"
|
|
(cd "$IR" && find . | cpio -o -H newc 2>/dev/null | gzip > "${DATA_DIR}/initramfs.cpio.gz")
|
|
echo " Initramfs created: $(du -h "${DATA_DIR}/initramfs.cpio.gz" | cut -f1)"
|
|
fi
|
|
|
|
if [ -f "${DATA_DIR}/rootfs.ext4" ]; then
|
|
echo "==> Rootfs already present."
|
|
else
|
|
echo "==> Building Debian ${DEBIAN_RELEASE} rootfs (${ROOTFS_SIZE_MB}MB)..."
|
|
ROOT="${WORK}/rootfs"
|
|
$SUDO debootstrap --arch="$DEB_ARCH" --variant=minbase "$DEBIAN_RELEASE" "$ROOT" http://deb.debian.org/debian
|
|
|
|
$SUDO mkdir -p "${ROOT}/etc/dpkg/dpkg.cfg.d"
|
|
cat <<'DPKG' | $SUDO tee "${ROOT}/etc/dpkg/dpkg.cfg.d/01-nodoc" > /dev/null
|
|
path-exclude /usr/share/doc/*
|
|
path-exclude /usr/share/man/*
|
|
path-exclude /usr/share/info/*
|
|
path-exclude /usr/share/locale/*
|
|
path-include /usr/share/locale/en*
|
|
DPKG
|
|
|
|
$SUDO chroot "$ROOT" apt-get update -qq
|
|
$SUDO chroot "$ROOT" apt-get install -y -qq --no-install-recommends \
|
|
ca-certificates curl git iproute2 iptables nftables \
|
|
openssh-client jq less procps xz-utils libgomp1 libatomic1 > /dev/null 2>&1
|
|
$SUDO rm -rf "${ROOT}/usr/share/doc/"* "${ROOT}/usr/share/man/"* "${ROOT}/usr/share/info/"*
|
|
$SUDO find "${ROOT}/usr/share/locale" -mindepth 1 -maxdepth 1 ! -name "en*" -exec rm -rf {} + 2>/dev/null || true
|
|
$SUDO chroot "$ROOT" apt-get clean
|
|
$SUDO rm -rf "${ROOT}/var/lib/apt/lists/"*
|
|
|
|
$SUDO install -m 755 "$GUEST_BINARY" "${ROOT}/usr/bin/vm-guest"
|
|
$SUDO mkdir -p "${ROOT}/proc" "${ROOT}/sys" "${ROOT}/dev" "${ROOT}/tmp" "${ROOT}/run"
|
|
echo "hanzo-vm" | $SUDO tee "${ROOT}/etc/hostname" > /dev/null
|
|
printf '127.0.0.1\tlocalhost\n127.0.1.1\thanzo-vm\n::1\tlocalhost ip6-localhost ip6-loopback\n' | $SUDO tee "${ROOT}/etc/hosts" > /dev/null
|
|
echo "nameserver 8.8.8.8" | $SUDO tee "${ROOT}/etc/resolv.conf" > /dev/null
|
|
|
|
truncate -s "${ROOTFS_SIZE_MB}M" "${DATA_DIR}/rootfs.ext4"
|
|
$SUDO mke2fs -q -F -t ext4 -E lazy_itable_init=0 -d "$ROOT" "${DATA_DIR}/rootfs.ext4"
|
|
echo " Rootfs created: $(du -h "${DATA_DIR}/rootfs.ext4" | cut -f1)"
|
|
fi
|
|
|
|
echo ""
|
|
echo "==> Done!"
|
|
echo " Kernel: ${DATA_DIR}/Image"
|
|
echo " Initramfs: ${DATA_DIR}/initramfs.cpio.gz"
|
|
echo " Rootfs: ${DATA_DIR}/rootfs.ext4"
|