Files
football/run.sh
Charles N Wyble 29654c6cf2 fix: pin distribution to trixie (Debian 13 stable)
Debian 13 (trixie) is now stable. Using --distribution testing
causes kernel module mismatch as testing now points to the next
release. Pin to trixie for stability.

Fixes kernel modules error during installation.

💘 Generated with Crush

Assisted-by: GLM-5 via Crush <crush@charm.land>
2026-02-19 21:27:34 -05:00

683 lines
23 KiB
Bash
Executable File

#!/bin/bash
# KNEL-Football ISO Builder - Main Entry Point
# Orchestrates Docker-based build process and VM testing
# Copyright © 2026 Known Element Enterprises LLC
# License: GNU Affero General Public License v3.0 only
set -euo pipefail
# Configuration variables
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly SCRIPT_DIR
readonly DOCKER_IMAGE="knel-football-dev:latest"
readonly OUTPUT_DIR="${SCRIPT_DIR}/output"
readonly BUILD_DIR="${SCRIPT_DIR}/tmp"
readonly BUILD_LOG="/tmp/knel-iso-build.log"
# VM Testing Configuration
readonly ISO_PATH="${SCRIPT_DIR}/output/knel-football-secure.iso"
readonly VM_NAME="knel-football-test"
readonly VM_RAM="2048"
readonly VM_CPUS="2"
readonly VM_DISK_SIZE="10"
readonly LIBVIRT_URI="qemu:///system"
readonly VM_DISK_PATH="/var/lib/libvirt/images/${VM_NAME}.qcow2"
VM_ISO_PATH="/var/lib/libvirt/images/$(basename "$ISO_PATH")"
readonly VM_ISO_PATH
# Colors for output
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly NC='\033[0m' # No Color
# Logging functions
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
# Create output and build directories if they don't exist
mkdir -p "${OUTPUT_DIR}" "${BUILD_DIR}"
# ============================================================================
# HOST FDE CHECK (MANDATORY)
# ============================================================================
# Check if host system has full disk encryption enabled
# This is MANDATORY - building or testing a secure OS on an unencrypted host
# defeats the entire security model
check_host_fde() {
log_info "Checking host system for Full Disk Encryption..."
local has_luks=false
local encrypted_root=false
# Method 1: Check for LUKS devices via lsblk
if lsblk -o TYPE,FSTYPE 2>/dev/null | grep -q "crypt"; then
has_luks=true
log_info "Found LUKS encrypted partitions"
fi
# Method 2: Check if root filesystem is on a dm-crypt device
if [[ -e /dev/mapper/root ]] || [[ -e /dev/mapper/rootfs ]]; then
encrypted_root=true
log_info "Root filesystem appears to be on encrypted device"
fi
# Method 3: Check /etc/crypttab for configured encrypted partitions
if [[ -f /etc/crypttab ]] && grep -qE "^[^#]" /etc/crypttab 2>/dev/null; then
has_luks=true
log_info "Found encrypted partitions in /etc/crypttab"
fi
# Method 4: Check for dm-crypt devices in /sys/block
if ls /sys/block/dm-* 2>/dev/null | head -1 | grep -q .; then
for dm_dev in /sys/block/dm-*; do
if [[ -f "${dm_dev}/dm/name" ]]; then
local dm_name
dm_name=$(cat "${dm_dev}/dm/name" 2>/dev/null)
# Check if this is a LUKS device
if [[ -f "${dm_dev}/dm/uuid" ]] && grep -qi "CRYPT-LUKS" "${dm_dev}/dm/uuid" 2>/dev/null; then
has_luks=true
log_info "Found LUKS device: ${dm_name}"
fi
fi
done
fi
# Method 5: Check root mount point for encryption
local root_device
root_device=$(findmnt -n -o SOURCE / 2>/dev/null || echo "")
if [[ "$root_device" == /dev/mapper/* ]] || [[ "$root_device" == *"crypt"* ]]; then
encrypted_root=true
log_info "Root filesystem is on encrypted device: $root_device"
fi
# Require at least one indicator of FDE
if [[ "$has_luks" == "true" || "$encrypted_root" == "true" ]]; then
log_info "Host FDE check PASSED"
return 0
fi
# FDE not detected - this is a FATAL error
log_error "============================================================"
log_error "SECURITY REQUIREMENT VIOLATION"
log_error "============================================================"
log_error "Host system does NOT have Full Disk Encryption enabled."
log_error ""
log_error "Building or testing KNEL-Football Secure OS requires the"
log_error "host system to be encrypted with LUKS. An unencrypted host"
log_error "defeats the entire security model."
log_error ""
log_error "To enable FDE on Debian/Ubuntu:"
log_error " 1. Backup all data"
log_error " 2. Reinstall with 'Guided - use entire disk and set up encrypted LVM'"
log_error " 3. Or use: https://github.com/The Firefoxlyer/encrypt-existing-debian"
log_error ""
log_error "This check is MANDATORY and cannot be bypassed."
log_error "============================================================"
return 1
}
# ============================================================================
# VM TESTING FUNCTIONS (merged from test-iso.sh)
# ============================================================================
# Check VM testing prerequisites
vm_check_prerequisites() {
log_info "Checking VM testing prerequisites..."
# Check for virsh command
if ! command -v virsh &> /dev/null; then
log_error "virsh command not found"
log_error "Install libvirt: sudo apt install libvirt-clients libvirt-daemon-system qemu-system-x86"
return 1
fi
# Check system libvirt access (required for virt-manager visibility)
if ! virsh -c "$LIBVIRT_URI" list &> /dev/null; then
log_error "Cannot connect to system libvirt ($LIBVIRT_URI)"
log_error "Ensure libvirtd is running and you have access"
log_error "Try: sudo usermod -aG libvirt \$USER && logout/login"
return 1
fi
# Check for qemu-img command (required for disk creation)
if ! command -v qemu-img &> /dev/null; then
log_error "qemu-img command not found"
log_error "Install qemu: sudo apt install qemu-utils"
return 1
fi
# Check ISO exists
if [[ ! -f "$ISO_PATH" ]]; then
log_error "ISO not found at: $ISO_PATH"
log_error "Build the ISO first: ./run.sh iso"
return 1
fi
# Check if libvirt images directory exists
if [[ ! -d "/var/lib/libvirt/images" ]]; then
log_error "Libvirt images directory not found"
log_error "Ensure libvirt is properly installed: sudo apt install libvirt-daemon-system"
return 1
fi
log_info "All prerequisites satisfied"
return 0
}
# Create and start VM using virsh define (virt-install requires storage pools)
vm_create() {
log_info "Creating VM: $VM_NAME (libvirt: $LIBVIRT_URI)"
# Destroy existing VM if present
virsh -c "$LIBVIRT_URI" destroy "$VM_NAME" 2>/dev/null || true
virsh -c "$LIBVIRT_URI" undefine "$VM_NAME" --nvram 2>/dev/null || true
# Copy ISO to system storage (libvirt needs access)
log_info "Copying ISO to libvirt storage (may require sudo)..."
if ! sudo cp -f "$ISO_PATH" "$VM_ISO_PATH" 2>/dev/null; then
log_error "Failed to copy ISO. Run this command from terminal to enter sudo password."
return 1
fi
sudo chown libvirt-qemu:libvirt-qemu "$VM_ISO_PATH" 2>/dev/null || true
sudo chmod 644 "$VM_ISO_PATH" 2>/dev/null || true
# Find UEFI firmware with Secure Boot support
local uefi_code=""
local uefi_vars=""
for fw_dir in /usr/share/OVMF /usr/share/qemu; do
if [[ -f "$fw_dir/OVMF_CODE_4M.secboot.fd" ]]; then
uefi_code="$fw_dir/OVMF_CODE_4M.secboot.fd"
uefi_vars="$fw_dir/OVMF_VARS_4M.fd"
break
elif [[ -f "$fw_dir/OVMF_CODE_4M.fd" ]]; then
uefi_code="$fw_dir/OVMF_CODE_4M.fd"
uefi_vars="$fw_dir/OVMF_VARS_4M.fd"
break
elif [[ -f "$fw_dir/OVMF_CODE.fd" ]]; then
uefi_code="$fw_dir/OVMF_CODE.fd"
uefi_vars="$fw_dir/OVMF_VARS.fd"
break
fi
done
if [[ -z "$uefi_code" || ! -f "$uefi_code" ]]; then
log_error "UEFI firmware (OVMF) not found"
log_error "Install required: sudo apt install ovmf"
return 1
fi
# Determine if Secure Boot is available
local secure_boot="no"
if [[ "$uefi_code" == *"secboot"* ]]; then
secure_boot="yes"
log_info "Using UEFI with Secure Boot: $uefi_code"
else
log_warn "Using UEFI WITHOUT Secure Boot: $uefi_code"
fi
# Pre-create disk image
log_info "Creating disk image: $VM_DISK_PATH"
sudo rm -f "$VM_DISK_PATH" 2>/dev/null || true
if ! sudo qemu-img create -f qcow2 "$VM_DISK_PATH" "${VM_DISK_SIZE}G"; then
log_error "Failed to create disk image"
return 1
fi
sudo chown libvirt-qemu:libvirt-qemu "$VM_DISK_PATH" 2>/dev/null || true
sudo chmod 644 "$VM_DISK_PATH" 2>/dev/null || true
# Use XML template for VM definition
local template="${SCRIPT_DIR}/vm/template.xml"
if [[ ! -f "$template" ]]; then
log_error "VM template not found: $template"
return 1
fi
# Generate UUID
local vm_uuid
vm_uuid=$(cat /proc/sys/kernel/random/uuid)
# Create VM XML from template
local vm_xml="/tmp/${VM_NAME}.xml"
sed -e "s|@VM_NAME@|${VM_NAME}|g" \
-e "s|@VM_UUID@|${vm_uuid}|g" \
-e "s|@VM_RAM@|${VM_RAM}|g" \
-e "s|@VM_CPUS@|${VM_CPUS}|g" \
-e "s|@SECURE_BOOT@|${secure_boot}|g" \
-e "s|@UEFI_CODE@|${uefi_code}|g" \
-e "s|@UEFI_VARS_TEMPLATE@|${uefi_vars}|g" \
-e "s|@VM_DISK@|${VM_DISK_PATH}|g" \
-e "s|@ISO_PATH@|${VM_ISO_PATH}|g" \
"$template" > "$vm_xml"
log_info "Defining VM from XML..."
# Define the VM
if ! virsh -c "$LIBVIRT_URI" define "$vm_xml"; then
log_error "Failed to define VM from XML"
cat "$vm_xml"
return 1
fi
# Start the VM
log_info "Starting VM..."
if ! virsh -c "$LIBVIRT_URI" start "$VM_NAME"; then
log_error "Failed to start VM"
return 1
fi
# Verify VM is running
sleep 2
if virsh -c "$LIBVIRT_URI" domstate "$VM_NAME" 2>/dev/null | grep -q "running"; then
log_info "VM created and STARTED successfully"
else
log_warn "VM created but may not be running - check virt-manager"
fi
# Get VNC display info
local vnc_display
vnc_display=$(virsh -c "$LIBVIRT_URI" vncdisplay "$VM_NAME" 2>/dev/null || echo "unknown")
log_info "VNC display: $vnc_display"
log_info ""
log_info "Open virt-manager - VM '$VM_NAME' should be visible under QEMU/KVM"
}
# Connect to VM console
vm_console() {
log_info "Connecting to VM console..."
virsh -c "$LIBVIRT_URI" console "$VM_NAME"
}
# Get VM status
vm_status() {
log_info "VM Status for: $VM_NAME"
virsh -c "$LIBVIRT_URI" dominfo "$VM_NAME" 2>/dev/null || log_error "VM not running"
}
# Check if VM is running
vm_is_running() {
if virsh -c "$LIBVIRT_URI" domstate "$VM_NAME" 2>/dev/null | grep -q "running"; then
return 0
fi
return 1
}
# Capture boot screenshot
vm_capture_screen() {
local output_dir="${SCRIPT_DIR}/tmp/vm-screenshots"
mkdir -p "$output_dir"
log_info "Capturing boot screen..."
virsh -c "$LIBVIRT_URI" screenshot "$VM_NAME" "${output_dir}/boot-screen.ppm" 2>/dev/null || {
log_warn "Could not capture screenshot"
}
}
# Destroy VM and cleanup
vm_destroy() {
log_info "Destroying VM: $VM_NAME"
virsh -c "$LIBVIRT_URI" destroy "$VM_NAME" 2>/dev/null || true
virsh -c "$LIBVIRT_URI" undefine "$VM_NAME" --nvram 2>/dev/null || true
sudo rm -f "$VM_DISK_PATH" "$VM_ISO_PATH" "/tmp/${VM_NAME}.xml"
log_info "Cleanup complete"
}
# Run automated boot test
vm_boot_test() {
log_info "Running automated boot test..."
if ! vm_check_prerequisites; then
return 1
fi
vm_create
log_info "Waiting for VM to boot (30 seconds)..."
sleep 30
if vm_is_running; then
log_info "VM is running - boot test PASSED"
vm_status
vm_capture_screen
return 0
else
log_error "VM not running - boot test FAILED"
return 1
fi
}
# Test Secure Boot
vm_test_secure_boot() {
log_info "Testing Secure Boot..."
if ! vm_is_running; then
log_error "VM not running, start it first"
return 1
fi
log_info "Secure Boot verification requires manual console inspection"
log_info "Use: ./run.sh test:iso console"
log_info "Then check: dmesg | grep -i secure"
}
# Test FDE passphrase prompt
vm_test_fde() {
log_info "Testing FDE passphrase prompt..."
if ! vm_is_running; then
log_error "VM not running, start it first"
return 1
fi
log_info "FDE prompt verification requires manual console inspection"
log_info "Use: ./run.sh test:iso console"
log_info "Watch for 'Please unlock disk' prompt during boot"
}
# ============================================================================
# BUILD MONITOR FUNCTION (merged from monitor-build.sh)
# ============================================================================
monitor_build() {
local check_interval="${1:-180}"
echo "=== ISO Build Monitor ==="
echo "Started: $(date)"
echo "Checking every ${check_interval}s"
echo "Log file: $BUILD_LOG"
echo ""
while true; do
if [ -f "$BUILD_LOG" ]; then
local lines
lines=$(wc -l < "$BUILD_LOG")
local last_stage
last_stage=$(grep -E "^\[.*\] lb (bootstrap|chroot|installer|binary|source)" "$BUILD_LOG" 2>/dev/null | tail -1)
local errors
errors=$(grep -ic "error\|failed\|fatal" "$BUILD_LOG" 2>/dev/null || echo "0")
echo "[$(date '+%H:%M:%S')] Lines: $lines | Errors: $errors"
[ -n "$last_stage" ] && echo " Stage: $last_stage"
# Check if build completed
if grep -q "ISO build completed" "$BUILD_LOG" 2>/dev/null; then
echo ""
echo "=== BUILD COMPLETED ==="
echo "Finished: $(date)"
ls -lh "${OUTPUT_DIR}"/*.iso 2>/dev/null || echo "No ISO found in output/"
break
fi
# Check if build failed
if grep -q "ISO build failed" "$BUILD_LOG" 2>/dev/null; then
echo ""
echo "=== BUILD FAILED ==="
echo "Check log: $BUILD_LOG"
tail -20 "$BUILD_LOG"
break
fi
else
echo "[$(date '+%H:%M:%S')] Waiting for build log..."
fi
sleep "$check_interval"
done
}
# ============================================================================
# USAGE AND MAIN
# ============================================================================
usage() {
cat <<EOF
KNEL-Football ISO Builder - Main Entry Point
Usage: $0 <command> [args]
Build Commands:
build Build Docker image
iso Build ISO (60-90 minutes)
monitor [secs] Monitor build progress (default: check every 180s)
clean Clean build artifacts
Test Commands:
test Run all tests
test:unit Run unit tests only
test:integration Run integration tests only
test:security Run security tests only
test:system Run system tests only (requires libvirt)
lint Run linting checks (shellcheck)
VM Testing Commands (requires libvirt on host):
test:iso check Check VM testing prerequisites
test:iso create Create and start test VM (UEFI/Secure Boot)
test:iso console Connect to VM console
test:iso status Show VM status
test:iso destroy Destroy VM and cleanup
test:iso boot-test Run automated boot test
test:iso secure-boot Test Secure Boot (manual verification)
test:iso fde-test Test FDE passphrase prompt (manual verification)
Other Commands:
shell Interactive shell in build container
help Show this help message
Prerequisites for VM Testing:
- User must be in libvirt group
- libvirtd service must be running
- OVMF must be installed (sudo apt install ovmf)
- ISO must exist in output/
Examples:
$0 build # Build Docker image
$0 iso # Build ISO (60-90 min)
$0 monitor # Monitor build progress
$0 test # Run all tests
$0 test:iso boot-test # Boot test in VM
$0 test:iso console # Connect to VM console
$0 test:iso destroy # Cleanup test VM
Note: After adding user to libvirt group, logout and login again.
EOF
exit 1
}
# Main entry point
main() {
local command="${1:-help}"
case "${command}" in
build)
echo "Building KNEL-Football Docker image..."
docker build -t "${DOCKER_IMAGE}" "${SCRIPT_DIR}"
;;
test)
echo "Running KNEL-Football test suite..."
docker run --rm \
-v "${SCRIPT_DIR}:/workspace:ro" \
-v "${BUILD_DIR}:/build" \
-e BATS_TMPDIR=/build/tmp \
"${DOCKER_IMAGE}" \
bash -c "cd /workspace && bats tests/simple_test.bats tests/unit/ tests/integration/ tests/security/ tests/system/"
;;
test:unit)
echo "Running unit tests..."
docker run --rm \
-v "${SCRIPT_DIR}:/workspace:ro" \
-v "${BUILD_DIR}:/build" \
-e BATS_TMPDIR=/build/tmp \
"${DOCKER_IMAGE}" \
bash -c "cd /workspace && bats tests/unit/"
;;
test:integration)
echo "Running integration tests..."
docker run --rm \
-v "${SCRIPT_DIR}:/workspace:ro" \
-v "${BUILD_DIR}:/build" \
-e BATS_TMPDIR=/build/tmp \
"${DOCKER_IMAGE}" \
bash -c "cd /workspace && bats tests/integration/"
;;
test:security)
echo "Running security tests..."
docker run --rm \
-v "${SCRIPT_DIR}:/workspace:ro" \
-v "${BUILD_DIR}:/build" \
-e BATS_TMPDIR=/build/tmp \
"${DOCKER_IMAGE}" \
bash -c "cd /workspace && bats tests/security/"
;;
test:system)
echo "Running system tests..."
docker run --rm \
-v "${SCRIPT_DIR}:/workspace:ro" \
-v "${BUILD_DIR}:/build" \
-e BATS_TMPDIR=/build/tmp \
"${DOCKER_IMAGE}" \
bash -c "cd /workspace && bats tests/system/"
;;
lint)
echo "Running linting checks..."
docker run --rm \
-v "${SCRIPT_DIR}:/workspace:ro" \
"${DOCKER_IMAGE}" \
bash -c "find /workspace -name '*.sh' -print0 | xargs -0 shellcheck"
;;
clean)
echo "Cleaning build artifacts..."
rm -rf "${OUTPUT_DIR:?}"/*
rm -rf "${BUILD_DIR:?}"/*
;;
shell)
echo "Starting interactive shell..."
docker run --rm -it \
-v "${SCRIPT_DIR}:/workspace:ro" \
-v "${OUTPUT_DIR}:/output" \
-v "${BUILD_DIR}:/build" \
-u "$(id -u):$(id -g)" \
-e TZ="America/Chicago" \
-e DEBIAN_FRONTEND="noninteractive" \
-e LC_ALL="C" \
"${DOCKER_IMAGE}" \
bash
;;
iso)
check_host_fde || exit 1
echo "Building KNEL-Football secure ISO..."
echo "ALL operations run inside Docker container"
echo "Timezone: America/Chicago"
echo "Mandatory: Full disk encryption with LUKS2"
docker run --rm \
--privileged \
--user root \
-v "${SCRIPT_DIR}:/workspace:ro" \
-v "${OUTPUT_DIR}:/output" \
-e TZ="America/Chicago" \
-e DEBIAN_FRONTEND="noninteractive" \
-e LC_ALL="C" \
-e USER_UID="$(id -u)" \
-e USER_GID="$(id -g)" \
"${DOCKER_IMAGE}" \
bash -c '
cd /tmp &&
rm -rf ./* &&
echo "Configuring live-build..." &&
lb config \
--distribution trixie \
--architectures amd64 \
--archive-areas "main contrib non-free" \
--mode debian \
--chroot-filesystem squashfs \
--binary-images iso-hybrid \
--iso-application "KNEL-Football Secure OS" \
--iso-publisher "KNEL-Football Security Team" \
--iso-volume "KNEL-Football Secure" \
--debian-installer netinst \
--debian-installer-gui true \
--source false \
--apt-indices false \
--apt-source-archives false &&
if [ -d /workspace/config ]; then
echo "Applying custom configuration..."
cp -r /workspace/config/* ./config/
fi &&
echo "Starting ISO build..." &&
timeout 3600 lb build &&
ISO_FILE=$(find . -name "*.iso" -type f | head -1) &&
if [ -n "$ISO_FILE" ]; then
echo "ISO created: $ISO_FILE"
sha256sum "$ISO_FILE" > "${ISO_FILE}.sha256"
md5sum "$ISO_FILE" > "${ISO_FILE}.md5"
FINAL_ISO="knel-football-secure.iso"
mv "$ISO_FILE" "$FINAL_ISO"
mv "${ISO_FILE}.sha256" "${FINAL_ISO}.sha256"
mv "${ISO_FILE}.md5" "${FINAL_ISO}.md5"
USER_UID=${USER_UID:-1000}
USER_GID=${USER_GID:-1000}
chown "$USER_UID:$USER_GID" "$FINAL_ISO" "${FINAL_ISO}.sha256" "${FINAL_ISO}.md5"
cp "$FINAL_ISO" "${FINAL_ISO}.sha256" "${FINAL_ISO}.md5" /output/
chown "$USER_UID:$USER_GID" /output/"$FINAL_ISO" /output/"${FINAL_ISO}.sha256" /output/"${FINAL_ISO}.md5"
echo "ISO build completed"
ls -lh /output/
else
echo "ISO build failed"
exit 1
fi
' 2>&1 | tee "$BUILD_LOG"
;;
monitor)
monitor_build "${2:-180}"
;;
test:iso)
check_host_fde || exit 1
shift # Remove 'test:iso' from args
local subcmd="${1:-help}"
case "$subcmd" in
check)
vm_check_prerequisites
;;
create)
vm_check_prerequisites && vm_create
;;
console)
vm_console
;;
status)
vm_status
;;
destroy)
vm_destroy
;;
boot-test)
vm_boot_test
;;
secure-boot)
vm_test_secure_boot
;;
fde-test)
vm_test_fde
;;
help|*)
echo "VM Testing Commands:"
echo " check Check prerequisites"
echo " create Create and start test VM"
echo " console Connect to VM console"
echo " status Show VM status"
echo " destroy Destroy VM and cleanup"
echo " boot-test Run automated boot test"
echo " secure-boot Test Secure Boot"
echo " fde-test Test FDE passphrase prompt"
;;
esac
;;
help|*)
usage
;;
esac
}
main "$@"