Files
kubesolo-os/test/qemu/test-boot-arm64-disk.sh
Adolfo Delorenzo 80aca5e372
Some checks failed
CI / Go Tests (push) Successful in 2m38s
CI / Shellcheck (push) Failing after 37s
CI / Build Go Binaries (amd64, linux, linux-amd64) (push) Failing after 1m22s
CI / Build Go Binaries (arm64, linux, linux-arm64) (push) Failing after 1m11s
feat: ARM64 generic UEFI disk image (GPT + GRUB A/B)
Produces a UEFI-bootable raw disk image for generic ARM64 hosts (QEMU virt,
Ampere/Graviton cloud, ARM64 SBCs with UEFI). Reuses the existing 4-partition
A/B layout from x86 (EFI 256 MB FAT32 + System A 512 MB ext4 + System B 512 MB
ext4 + Data ext4 remainder).

Changes:
- build/scripts/create-disk-image.sh: TARGET_ARCH env var (amd64 default,
  arm64). Selects kernel source path, grub-mkimage target (x86_64-efi vs
  arm64-efi), EFI binary name (bootx64.efi vs BOOTAA64.EFI), grub.cfg variant,
  and whether to also install BIOS GRUB (x86 only).
- build/grub/grub-arm64.cfg: ARM64 variant of grub.cfg. Identical A/B logic;
  console=ttyAMA0+ttyS0 to cover QEMU virt PL011, Ampere PL011, and Graviton
  16550-compat.
- build/Dockerfile.builder: add grub-efi-amd64-bin, grub-efi-arm64-bin,
  grub-pc-bin, grub-common, grub2-common so the builder container can produce
  EFI images for both architectures.
- hack/dev-vm-arm64.sh: split into kernel mode (direct -kernel/-initrd, fast
  iteration) and --disk mode (UEFI firmware + GRUB + disk image, full
  integration test). Probes common UEFI firmware paths on Ubuntu/Fedora/macOS.
  Default kernel path now points at kernel-arm64-generic/Image with fallback
  to the renamed custom-kernel-rpi/Image.
- test/qemu/test-boot-arm64-disk.sh: new CI test for the full UEFI -> GRUB ->
  kernel -> stage-90 boot chain. Uses a scratch copy of the disk so grubenv
  writes don't mutate the source artifact.
- Makefile: new disk-image-arm64 target (depends on rootfs-arm64 + kernel-arm64),
  new test-boot-arm64-disk target, .PHONY + help updates.

Phase 3 scaffold is in place. First real end-to-end ARM64 build runs in the
next step on the Odroid runner — that's where we find out what's actually
broken.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 10:36:08 -06:00

130 lines
3.7 KiB
Bash
Executable File

#!/bin/bash
# test-boot-arm64-disk.sh — Boot the ARM64 .arm64.img via UEFI + GRUB and
# verify the init system reaches stage 90.
#
# This is the full-stack integration test: UEFI firmware -> GRUB -> kernel ->
# initramfs -> staged init. Contrast with test-boot-arm64.sh which skips the
# bootloader and loads kernel/initramfs directly.
#
# Exit 0 = PASS, Exit 1 = FAIL.
#
# Usage: ./test/qemu/test-boot-arm64-disk.sh [disk.img]
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
VERSION="$(cat "$PROJECT_ROOT/VERSION")"
DISK_IMAGE="${1:-$PROJECT_ROOT/output/kubesolo-os-${VERSION}.arm64.img}"
TIMEOUT=180
echo "==> ARM64 UEFI Disk Boot Test"
echo " Disk image: $DISK_IMAGE"
echo " Timeout: ${TIMEOUT}s"
if [ ! -f "$DISK_IMAGE" ]; then
echo "ERROR: Disk image not found: $DISK_IMAGE"
echo " Run 'make disk-image-arm64' to build it."
exit 1
fi
if ! command -v qemu-system-aarch64 >/dev/null 2>&1; then
echo "ERROR: qemu-system-aarch64 not found."
echo " apt install qemu-system-arm # Debian/Ubuntu"
echo " dnf install qemu-system-aarch64 # Fedora/RHEL"
exit 1
fi
# --- Locate UEFI firmware ---
UEFI_FW=""
for candidate in \
/usr/share/qemu-efi-aarch64/QEMU_EFI.fd \
/usr/share/AAVMF/AAVMF_CODE.fd \
/usr/share/edk2/aarch64/QEMU_EFI.fd \
/usr/share/qemu/edk2-aarch64-code.fd \
/opt/homebrew/share/qemu/edk2-aarch64-code.fd \
/usr/local/share/qemu/edk2-aarch64-code.fd
do
if [ -f "$candidate" ]; then
UEFI_FW="$candidate"
break
fi
done
if [ -z "$UEFI_FW" ]; then
echo "ERROR: No ARM64 UEFI firmware found."
echo " apt install qemu-efi-aarch64"
exit 1
fi
echo " UEFI fw: $UEFI_FW"
# Copy disk image to a scratch file so the test doesn't mutate the source.
# UEFI will write to grubenv on the EFI partition; we don't want to bake those
# changes into the canonical build artifact.
SCRATCH_DISK=$(mktemp /tmp/kubesolo-arm64-disk-test-XXXXXX.img)
SERIAL_LOG=$(mktemp /tmp/kubesolo-arm64-disk-serial-XXXXXX.log)
QEMU_PID=""
cleanup() {
[ -n "$QEMU_PID" ] && kill "$QEMU_PID" 2>/dev/null || true
rm -f "$SCRATCH_DISK" "$SERIAL_LOG"
}
trap cleanup EXIT
cp --reflink=auto "$DISK_IMAGE" "$SCRATCH_DISK" 2>/dev/null || cp "$DISK_IMAGE" "$SCRATCH_DISK"
# --- Launch QEMU ---
qemu-system-aarch64 \
-machine virt \
-cpu cortex-a72 \
-m 2048 \
-smp 2 \
-nographic \
-bios "$UEFI_FW" \
-drive "file=$SCRATCH_DISK,format=raw,if=virtio,media=disk" \
-net nic,model=virtio \
-net user \
-serial "file:$SERIAL_LOG" &
QEMU_PID=$!
echo " Waiting for boot (PID $QEMU_PID)..."
ELAPSED=0
SUCCESS=0
while [ "$ELAPSED" -lt "$TIMEOUT" ]; do
if grep -q "\[kubesolo-init\] \[OK\] Stage 90-kubesolo.sh complete" "$SERIAL_LOG" 2>/dev/null; then
SUCCESS=1
break
fi
if grep -q "KubeSolo is running" "$SERIAL_LOG" 2>/dev/null; then
SUCCESS=1
break
fi
if ! kill -0 "$QEMU_PID" 2>/dev/null; then
echo ""
echo "==> FAIL: QEMU exited prematurely"
echo " Last 30 lines of serial output:"
tail -30 "$SERIAL_LOG" 2>/dev/null || echo " (no output)"
exit 1
fi
sleep 2
ELAPSED=$((ELAPSED + 2))
printf "\r Elapsed: %ds / %ds" "$ELAPSED" "$TIMEOUT"
done
echo ""
kill "$QEMU_PID" 2>/dev/null || true
wait "$QEMU_PID" 2>/dev/null || true
QEMU_PID=""
if [ "$SUCCESS" = "1" ]; then
echo "==> ARM64 UEFI Disk Boot Test PASSED (${ELAPSED}s)"
exit 0
fi
echo "==> ARM64 UEFI Disk Boot Test FAILED (timeout ${TIMEOUT}s)"
echo ""
echo "==> Last 50 lines of serial output:"
tail -50 "$SERIAL_LOG" 2>/dev/null || echo " (no output)"
exit 1