Files
football/scripts/validate-iso.sh
reachableceo 7887269c46 fix: correct $VERSION reference in build-iso.sh, fix QEMU networking
- src/build-iso.sh: Replace undefined $VERSION with correct filename.
  The success check referenced $PROJECT_NAME-v$VERSION.iso but $VERSION
  was never defined, causing the build to always report failure.
- scripts/validate-iso.sh: Replace deprecated `-net nic -net user` with
  modern `-nic user` QEMU networking syntax.

💘 Generated with Crush

Assisted-by: GLM-5.1 via Crush <crush@charm.land>
2026-05-07 08:42:56 -05:00

364 lines
12 KiB
Bash
Executable File

#!/bin/bash
# KNEL-Football Automated ISO Validation Harness
# Boots ISO in QEMU VM with serial console, runs automated checks
# Reference: PRD FR-001 through FR-012
# Copyright © 2026 Known Element Enterprises LLC
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
readonly SCRIPT_DIR
readonly ISO_PATH="${SCRIPT_DIR}/output/knel-football-secure.iso"
readonly VM_DISK="${SCRIPT_DIR}/tmp/validation-vm.qcow2"
readonly SERIAL_LOG="${SCRIPT_DIR}/tmp/validation-serial.log"
readonly SCREENSHOT_DIR="${SCRIPT_DIR}/tmp/validation-screenshots"
readonly TIMEOUT_BOOT=180
# shellcheck disable=SC2034
readonly VALIDATION_USER="football"
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
pass_count=0
fail_count=0
skip_count=0
log_pass() { echo -e "${GREEN}[PASS]${NC} $1"; pass_count=$((pass_count + 1)); }
log_fail() { echo -e "${RED}[FAIL]${NC} $1"; fail_count=$((fail_count + 1)); }
log_skip() { echo -e "${YELLOW}[SKIP]${NC} $1"; skip_count=$((skip_count + 1)); }
log_info() { echo -e "[INFO] $1"; }
cleanup() {
log_info "Cleaning up VM..."
if [ -n "${QEMU_PID:-}" ]; then
kill "$QEMU_PID" 2>/dev/null || true
wait "$QEMU_PID" 2>/dev/null || true
fi
rm -f "$VM_DISK"
}
trap cleanup EXIT
# =============================================================================
# Phase 0: Pre-flight checks
# =============================================================================
phase0_preflight() {
echo ""
echo "=========================================="
echo " Phase 0: Pre-flight Checks"
echo "=========================================="
if [ ! -f "$ISO_PATH" ]; then
log_fail "ISO not found at $ISO_PATH"
return 1
fi
log_pass "ISO exists: $(du -h "$ISO_PATH" | cut -f1)"
if [ -f "${ISO_PATH}.sha256" ]; then
log_info "Verifying SHA256 checksum..."
if (cd "$(dirname "$ISO_PATH")" && sha256sum -c "$(basename "$ISO_PATH").sha256") 2>/dev/null; then
log_pass "SHA256 checksum valid"
else
log_fail "SHA256 checksum INVALID"
fi
else
log_skip "No SHA256 checksum file"
fi
if [ -f "${ISO_PATH}.md5" ]; then
if (cd "$(dirname "$ISO_PATH")" && md5sum -c "$(basename "$ISO_PATH").md5") 2>/dev/null; then
log_pass "MD5 checksum valid"
else
log_fail "MD5 checksum INVALID"
fi
fi
# Check for QEMU
if ! command -v qemu-system-x86_64 >/dev/null 2>&1; then
log_fail "qemu-system-x86_64 not found"
return 1
fi
log_pass "QEMU available"
# Check for OVMF
local ovmf_code=""
for f in /usr/share/OVMF/OVMF_CODE_4M.secboot.fd /usr/share/OVMF/OVMF_CODE_4M.fd /usr/share/qemu/OVMF_CODE.fd; do
if [ -f "$f" ]; then
ovmf_code="$f"
break
fi
done
if [ -n "$ovmf_code" ]; then
log_pass "OVMF firmware: $ovmf_code"
else
log_fail "No OVMF firmware found"
return 1
fi
# Create disk image
rm -f "$VM_DISK"
qemu-img create -f qcow2 "$VM_DISK" 10G
log_pass "VM disk created"
# Create screenshot dir
mkdir -p "$SCREENSHOT_DIR"
}
# =============================================================================
# Phase 1: Static ISO analysis (no boot needed)
# =============================================================================
phase1_static_analysis() {
echo ""
echo "=========================================="
echo " Phase 1: Static ISO Analysis"
echo "=========================================="
local iso_size
iso_size=$(stat -f%z "$ISO_PATH" 2>/dev/null || stat -c%s "$ISO_PATH" 2>/dev/null || echo 0)
# Check ISO size is reasonable (200MB - 2GB)
if [ "$iso_size" -gt 200000000 ] && [ "$iso_size" -lt 2500000000 ]; then
log_pass "ISO size reasonable: $(echo "scale=0; $iso_size / 1048576" | bc)MB"
else
log_fail "ISO size unusual: $iso_size bytes"
fi
# Check ISO is a valid ISO9660 image
if file "$ISO_PATH" | grep -qi "ISO 9660\|DOS/MBR\|bootable"; then
log_pass "ISO is valid bootable image"
elif command -v isoinfo >/dev/null 2>&1 && isoinfo -d -i "$ISO_PATH" >/dev/null 2>&1; then
log_pass "ISO is valid ISO9660"
else
log_fail "ISO does not appear to be a valid bootable image"
fi
# Cache isoinfo listing for reuse
local iso_listing=""
if command -v isoinfo >/dev/null 2>&1; then
iso_listing=$(isoinfo -l -i "$ISO_PATH" 2>/dev/null || true)
fi
# Check ISO has EFI boot capability
if [ -n "$iso_listing" ]; then
if echo "$iso_listing" | grep -qi "EFI\|BOOT"; then
log_pass "ISO contains EFI boot files"
else
log_fail "ISO missing EFI boot files"
fi
else
log_skip "isoinfo not available for EFI check"
fi
# Check for Debian installer files in ISO
if [ -n "$iso_listing" ]; then
if echo "$iso_listing" | grep -qi "install\|d-i\|debian\|pool"; then
log_pass "ISO contains Debian installer/repository"
else
log_fail "ISO missing Debian installer/repository"
fi
else
log_skip "isoinfo not available for installer check"
fi
# Verify ISO contains config hooks (by mounting)
local mount_point="${SCRIPT_DIR}/tmp/iso-mount"
mkdir -p "$mount_point"
if mount -o loop,ro "$ISO_PATH" "$mount_point" 2>/dev/null; then
log_pass "ISO mounts successfully"
# Check for pool directory (live-build structure)
if [ -d "$mount_point/pool" ] || [ -d "$mount_point/dists" ]; then
log_pass "ISO has Debian repository structure"
fi
# Check for bootloader
if [ -d "$mount_point/boot" ] || [ -f "$mount_point/boot/grub/grub.cfg" ] || \
[ -f "$mount_point/EFI/BOOT/BOOTX64.EFI" ] || [ -d "$mount_point/EFI" ]; then
log_pass "ISO has bootloader"
else
log_fail "ISO missing bootloader"
fi
umount "$mount_point" 2>/dev/null || true
else
log_skip "Cannot mount ISO (needs root or fuse)"
fi
}
# =============================================================================
# Phase 2: Boot test in QEMU
# =============================================================================
phase2_boot_test() {
echo ""
echo "=========================================="
echo " Phase 2: QEMU Boot Test"
echo "=========================================="
local ovmf_code=""
local ovmf_vars=""
for f in /usr/share/OVMF/OVMF_CODE_4M.secboot.fd /usr/share/OVMF/OVMF_CODE_4M.fd; do
if [ -f "$f" ]; then
ovmf_code="$f"
ovmf_vars="/usr/share/OVMF/OVMF_VARS_4M.fd"
break
fi
done
# Copy OVMF vars for this VM (writable copy)
local vm_vars="${SCRIPT_DIR}/tmp/validation-ovmf-vars.fd"
cp "$ovmf_vars" "$vm_vars"
# Boot QEMU with serial console
log_info "Starting QEMU VM..."
rm -f "$SERIAL_LOG"
qemu-system-x86_64 \
-machine q35,accel=kvm \
-cpu host \
-m 2048 \
-smp 2 \
-drive if=pflash,format=raw,readonly=on,file="$ovmf_code" \
-drive if=pflash,format=raw,file="$vm_vars" \
-drive file="$VM_DISK",format=qcow2,if=virtio \
-cdrom "$ISO_PATH" \
-boot d \
-nic user \
-serial file:"$SERIAL_LOG" \
-display none \
-no-reboot \
&>/dev/null &
QEMU_PID=$!
log_info "QEMU PID: $QEMU_PID"
log_info "Waiting for boot (up to ${TIMEOUT_BOOT}s)..."
# Wait for boot activity on serial console
local elapsed=0
local booted=false
while [ $elapsed -lt $TIMEOUT_BOOT ]; do
if [ -f "$SERIAL_LOG" ] && [ -s "$SERIAL_LOG" ]; then
# Check for signs of successful boot
# UEFI BdsDxe messages confirm firmware loaded the boot device
# GRUB/Linux require serial console config to appear here
if grep -qi "GNU GRUB\|Linux version\|Debian GNU\|login:\|BdsDxe: starting" "$SERIAL_LOG" 2>/dev/null; then
booted=true
break
fi
fi
sleep 5
elapsed=$((elapsed + 5))
echo -n "."
done
echo ""
if $booted; then
# Distinguish UEFI-only boot from full OS boot
if grep -qi "GNU GRUB" "$SERIAL_LOG" 2>/dev/null; then
log_pass "GRUB bootloader loaded (serial console)"
elif grep -qi "BdsDxe: starting" "$SERIAL_LOG" 2>/dev/null; then
log_pass "UEFI firmware booted ISO (GRUB uses VGA, not serial)"
log_skip "GRUB/Linux serial output (add console=ttyS0 for serial)"
fi
if grep -qi "Linux version\|Debian GNU" "$SERIAL_LOG" 2>/dev/null; then
log_pass "Linux kernel loaded"
fi
if grep -qi "debian installer\|Install\|d-i" "$SERIAL_LOG" 2>/dev/null; then
log_pass "Debian Installer started"
fi
# Check for encryption prompt
if grep -qi "crypt\|LUKS\|unlock\|passphrase" "$SERIAL_LOG" 2>/dev/null; then
log_pass "Encryption prompt detected"
fi
# Check for secure boot
if grep -qi "secure boot\|secureboot" "$SERIAL_LOG" 2>/dev/null; then
log_pass "Secure Boot referenced in boot log"
fi
# Check for kernel lockdown
if grep -qi "lockdown\|module.sig" "$SERIAL_LOG" 2>/dev/null; then
log_pass "Kernel lockdown parameters detected"
fi
# Check for security hardening
if grep -qi "security hardening\|knel\|applying security" "$SERIAL_LOG" 2>/dev/null; then
log_pass "Security hardening hooks executed"
fi
# Check for firewall
if grep -qi "firewall\|nftables" "$SERIAL_LOG" 2>/dev/null; then
log_pass "Firewall setup detected"
fi
else
log_fail "VM did not boot within ${TIMEOUT_BOOT}s"
fi
# Dump serial log for analysis
if [ -f "$SERIAL_LOG" ] && [ -s "$SERIAL_LOG" ]; then
log_info "Serial log captured (${SERIAL_LOG}): $(wc -l < "$SERIAL_LOG") lines"
else
log_info "No serial output captured"
fi
}
# =============================================================================
# Phase 3: Report
# =============================================================================
phase3_report() {
echo ""
echo "=========================================="
echo " Validation Report"
echo "=========================================="
echo ""
echo " PASS: $pass_count"
echo " FAIL: $fail_count"
echo " SKIP: $skip_count"
echo " TOTAL: $((pass_count + fail_count + skip_count))"
echo ""
if [ $fail_count -eq 0 ]; then
echo -e " ${GREEN}STATUS: ALL CHECKS PASSED${NC}"
else
echo -e " ${RED}STATUS: $fail_count FAILURES DETECTED${NC}"
fi
echo ""
if [ -f "$SERIAL_LOG" ]; then
echo " Serial log: $SERIAL_LOG"
fi
echo " Screenshots: $SCREENSHOT_DIR"
echo "=========================================="
return $fail_count
}
# =============================================================================
# Main
# =============================================================================
main() {
echo ""
echo "=========================================="
echo " KNEL-Football ISO Validation Harness"
echo "=========================================="
echo " ISO: $ISO_PATH"
echo " Date: $(date)"
echo ""
phase0_preflight || { echo "Pre-flight failed. Aborting."; exit 1; }
phase1_static_analysis
phase2_boot_test
phase3_report
}
main "$@"