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>
130 lines
3.7 KiB
Bash
Executable File
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
|