393 lines
12 KiB
Bash
Executable File
393 lines
12 KiB
Bash
Executable File
#!/bin/bash
|
|
set -e
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
|
|
# Parse arguments
|
|
AUTO_MODE=false
|
|
TARGET_ARCH="amd64"
|
|
|
|
for arg in "$@"; do
|
|
case $arg in
|
|
--auto)
|
|
AUTO_MODE=true
|
|
shift
|
|
;;
|
|
--arch=*)
|
|
TARGET_ARCH="${arg#*=}"
|
|
shift
|
|
;;
|
|
-h|--help)
|
|
echo "Usage: $0 [--arch=amd64|arm64|rpi] [--auto]"
|
|
echo ""
|
|
echo "Options:"
|
|
echo " --arch=ARCH Target architecture (default: amd64)"
|
|
echo " amd64 - x86_64 with UEFI/OVMF"
|
|
echo " arm64 - Generic ARM64 with UEFI"
|
|
echo " rpi - Raspberry Pi 3 emulation"
|
|
echo " --auto Run in automatic mode for CI/testing"
|
|
exit 0
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Validate architecture
|
|
case "$TARGET_ARCH" in
|
|
amd64|arm64|rpi)
|
|
;;
|
|
*)
|
|
echo "ERROR: Invalid architecture '$TARGET_ARCH'"
|
|
echo "Valid options: amd64, arm64, rpi"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
PROJECT_ROOT="$SCRIPT_DIR/.."
|
|
VM_DIR="$PROJECT_ROOT/.nogit/vm"
|
|
MONITOR_SOCK="$VM_DIR/qemu-monitor.sock"
|
|
SERIAL_SOCK="$VM_DIR/serial.sock"
|
|
SERIAL_LOG="$VM_DIR/serial.log"
|
|
PID_FILE="$VM_DIR/qemu.pid"
|
|
|
|
# Architecture-specific settings
|
|
case "$TARGET_ARCH" in
|
|
amd64)
|
|
ISO_PATH="$PROJECT_ROOT/.nogit/iso/ecoos.iso"
|
|
DISK_PATH="$VM_DIR/test-disk.qcow2"
|
|
QEMU_CMD="qemu-system-x86_64"
|
|
QEMU_MACHINE=""
|
|
QEMU_BIOS="-bios /usr/share/qemu/OVMF.fd"
|
|
KVM_CHECK="/dev/kvm"
|
|
DISK_IF="virtio"
|
|
;;
|
|
arm64)
|
|
ISO_PATH="$PROJECT_ROOT/.nogit/iso/ecoos-arm64.iso"
|
|
DISK_PATH="$VM_DIR/test-disk-arm64.qcow2"
|
|
QEMU_CMD="qemu-system-aarch64"
|
|
QEMU_MACHINE="-M virt -cpu cortex-a72"
|
|
QEMU_BIOS="-bios /usr/share/qemu-efi-aarch64/QEMU_EFI.fd"
|
|
KVM_CHECK="" # ARM KVM only works on ARM hosts
|
|
DISK_IF="virtio"
|
|
;;
|
|
rpi)
|
|
IMG_PATH="$PROJECT_ROOT/.nogit/iso/ecoos-rpi.img"
|
|
DISK_PATH="" # RPi uses the image directly
|
|
QEMU_CMD="qemu-system-aarch64"
|
|
QEMU_MACHINE="-M raspi3b -cpu cortex-a53"
|
|
QEMU_BIOS="" # RPi uses direct kernel boot
|
|
KVM_CHECK=""
|
|
DISK_IF="sd"
|
|
;;
|
|
esac
|
|
|
|
# Create VM directory if not exists
|
|
mkdir -p "$VM_DIR"
|
|
|
|
# Check if image exists
|
|
if [ "$TARGET_ARCH" = "rpi" ]; then
|
|
if [ ! -f "$IMG_PATH" ]; then
|
|
echo "ERROR: RPi image not found at $IMG_PATH"
|
|
echo "Run 'pnpm run build:rpi' first to create the image"
|
|
exit 1
|
|
fi
|
|
else
|
|
if [ ! -f "$ISO_PATH" ]; then
|
|
echo "ERROR: ISO not found at $ISO_PATH"
|
|
echo "Run 'pnpm run build:$TARGET_ARCH' first to create the ISO"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
# Create test disk if not exists (not needed for RPi)
|
|
if [ -n "$DISK_PATH" ] && [ ! -f "$DISK_PATH" ]; then
|
|
echo "Creating test disk (20GB)..."
|
|
qemu-img create -f qcow2 "$DISK_PATH" 20G
|
|
fi
|
|
|
|
# Check if already running
|
|
if [ -f "$PID_FILE" ]; then
|
|
PID=$(cat "$PID_FILE")
|
|
if kill -0 "$PID" 2>/dev/null; then
|
|
echo "QEMU already running (PID: $PID)"
|
|
echo "Run 'pnpm run test:stop' to stop it first"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
echo "Starting QEMU with EcoOS for $TARGET_ARCH..."
|
|
|
|
# Check if KVM is available (only for amd64 on x86 hosts)
|
|
KVM_OPTS=""
|
|
if [ -n "$KVM_CHECK" ] && [ -e "$KVM_CHECK" ] && [ -r "$KVM_CHECK" ] && [ -w "$KVM_CHECK" ]; then
|
|
KVM_OPTS="-enable-kvm -cpu host"
|
|
echo "Using KVM acceleration"
|
|
else
|
|
if [ "$TARGET_ARCH" = "amd64" ]; then
|
|
echo "KVM not available, using software emulation (slower)"
|
|
else
|
|
echo "Running ARM emulation on x86 host (slower)"
|
|
fi
|
|
fi
|
|
|
|
# Cleanup function
|
|
cleanup() {
|
|
echo ""
|
|
echo "Shutting down..."
|
|
if [ -n "$SCREENSHOT_LOOP_PID" ] && kill -0 "$SCREENSHOT_LOOP_PID" 2>/dev/null; then
|
|
kill "$SCREENSHOT_LOOP_PID" 2>/dev/null || true
|
|
fi
|
|
if [ -n "$ENABLE_PID" ] && kill -0 "$ENABLE_PID" 2>/dev/null; then
|
|
kill "$ENABLE_PID" 2>/dev/null || true
|
|
fi
|
|
if [ -n "$VIEWER_PID" ] && kill -0 "$VIEWER_PID" 2>/dev/null; then
|
|
kill "$VIEWER_PID" 2>/dev/null || true
|
|
fi
|
|
if [ -n "$TWM_PID" ] && kill -0 "$TWM_PID" 2>/dev/null; then
|
|
kill "$TWM_PID" 2>/dev/null || true
|
|
fi
|
|
if [ -n "$XORG_PID" ] && kill -0 "$XORG_PID" 2>/dev/null; then
|
|
kill "$XORG_PID" 2>/dev/null || true
|
|
fi
|
|
if [ -f "$PID_FILE" ]; then
|
|
PID=$(cat "$PID_FILE")
|
|
if kill -0 "$PID" 2>/dev/null; then
|
|
kill "$PID" 2>/dev/null || true
|
|
fi
|
|
rm -f "$PID_FILE"
|
|
fi
|
|
echo "Done"
|
|
}
|
|
trap cleanup EXIT INT TERM
|
|
|
|
# Start QEMU based on architecture
|
|
> "$SERIAL_LOG" # Clear old log
|
|
|
|
if [ "$TARGET_ARCH" = "amd64" ]; then
|
|
# AMD64 with multi-display support
|
|
$QEMU_CMD \
|
|
$KVM_OPTS \
|
|
-m 4G \
|
|
-smp 4 \
|
|
$QEMU_BIOS \
|
|
-drive file="$ISO_PATH",media=cdrom \
|
|
-drive file="$DISK_PATH",format=qcow2,if=virtio \
|
|
-device qxl-vga,id=video0,ram_size=67108864,vram_size=67108864,vgamem_mb=64 \
|
|
-device qxl,id=video1,ram_size=67108864,vram_size=67108864,vgamem_mb=64 \
|
|
-device qxl,id=video2,ram_size=67108864,vram_size=67108864,vgamem_mb=64 \
|
|
-display none \
|
|
-spice port=5930,disable-ticketing=on \
|
|
-device virtio-serial-pci \
|
|
-chardev spicevmc,id=vdagent,name=vdagent \
|
|
-device virtserialport,chardev=vdagent,name=com.redhat.spice.0 \
|
|
-serial unix:"$SERIAL_SOCK",server,nowait \
|
|
-monitor unix:"$MONITOR_SOCK",server,nowait \
|
|
-nic user,model=virtio-net-pci,hostfwd=tcp::3006-:3006,hostfwd=tcp::2222-:22 \
|
|
-pidfile "$PID_FILE" &
|
|
|
|
elif [ "$TARGET_ARCH" = "arm64" ]; then
|
|
# ARM64 with UEFI
|
|
$QEMU_CMD \
|
|
$QEMU_MACHINE \
|
|
-m 4G \
|
|
-smp 4 \
|
|
$QEMU_BIOS \
|
|
-drive file="$ISO_PATH",media=cdrom,if=none,id=cdrom \
|
|
-device virtio-blk-device,drive=cdrom \
|
|
-drive file="$DISK_PATH",format=qcow2,if=none,id=hd0 \
|
|
-device virtio-blk-device,drive=hd0 \
|
|
-device virtio-gpu-pci \
|
|
-display none \
|
|
-serial unix:"$SERIAL_SOCK",server,nowait \
|
|
-monitor unix:"$MONITOR_SOCK",server,nowait \
|
|
-device virtio-net-device,netdev=net0 \
|
|
-netdev user,id=net0,hostfwd=tcp::3006-:3006,hostfwd=tcp::2222-:22 \
|
|
-pidfile "$PID_FILE" &
|
|
|
|
elif [ "$TARGET_ARCH" = "rpi" ]; then
|
|
# Raspberry Pi 3B emulation
|
|
# Note: raspi3b machine has limited support, uses direct kernel boot
|
|
echo "NOTE: Raspberry Pi emulation is limited."
|
|
echo " For full testing, use real hardware."
|
|
|
|
# Extract kernel and initrd from image for direct boot
|
|
TEMP_MNT=$(mktemp -d)
|
|
LOOP_DEV=$(sudo losetup --find --show --partscan "$IMG_PATH")
|
|
sudo mount "${LOOP_DEV}p1" "$TEMP_MNT"
|
|
|
|
KERNEL="$TEMP_MNT/vmlinuz"
|
|
INITRD="$TEMP_MNT/initrd.img"
|
|
DTB="$TEMP_MNT/bcm2710-rpi-3-b.dtb"
|
|
|
|
if [ ! -f "$KERNEL" ]; then
|
|
echo "ERROR: Kernel not found in RPi image"
|
|
sudo umount "$TEMP_MNT"
|
|
sudo losetup -d "$LOOP_DEV"
|
|
rm -rf "$TEMP_MNT"
|
|
exit 1
|
|
fi
|
|
|
|
# Copy kernel/initrd to temp location for QEMU
|
|
cp "$KERNEL" "$VM_DIR/rpi-kernel"
|
|
cp "$INITRD" "$VM_DIR/rpi-initrd" 2>/dev/null || true
|
|
cp "$DTB" "$VM_DIR/rpi-dtb" 2>/dev/null || true
|
|
|
|
sudo umount "$TEMP_MNT"
|
|
sudo losetup -d "$LOOP_DEV"
|
|
rm -rf "$TEMP_MNT"
|
|
|
|
$QEMU_CMD \
|
|
$QEMU_MACHINE \
|
|
-m 1G \
|
|
-kernel "$VM_DIR/rpi-kernel" \
|
|
-initrd "$VM_DIR/rpi-initrd" \
|
|
-dtb "$VM_DIR/rpi-dtb" \
|
|
-append "console=ttyAMA0,115200 root=LABEL=EcoOS rootfstype=ext4 rootwait" \
|
|
-drive file="$IMG_PATH",format=raw,if=sd \
|
|
-serial unix:"$SERIAL_SOCK",server,nowait \
|
|
-display none \
|
|
-pidfile "$PID_FILE" &
|
|
fi
|
|
|
|
QEMU_PID=$!
|
|
|
|
echo ""
|
|
echo "=== EcoOS Test VM Started ($TARGET_ARCH) ==="
|
|
echo "QEMU PID: $QEMU_PID"
|
|
if [ "$TARGET_ARCH" != "rpi" ]; then
|
|
echo "Management UI: http://localhost:3006"
|
|
fi
|
|
echo ""
|
|
|
|
# AMD64-specific display setup
|
|
if [ "$TARGET_ARCH" = "amd64" ]; then
|
|
# Wait for QEMU to start and SPICE to be ready
|
|
echo "Waiting for SPICE server..."
|
|
sleep 3
|
|
|
|
# Check if remote-viewer is available
|
|
if ! command -v remote-viewer &> /dev/null; then
|
|
echo "WARNING: remote-viewer not installed"
|
|
echo "Install with: sudo apt install virt-viewer"
|
|
echo ""
|
|
echo "Running without display viewer. Press Ctrl-C to stop."
|
|
wait $QEMU_PID
|
|
exit 0
|
|
fi
|
|
|
|
# Set up virt-viewer settings for multi-display
|
|
VIRT_VIEWER_CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/virt-viewer"
|
|
mkdir -p "$VIRT_VIEWER_CONFIG_DIR"
|
|
if [ -f "$SCRIPT_DIR/virt-viewer-settings" ]; then
|
|
cp "$SCRIPT_DIR/virt-viewer-settings" "$VIRT_VIEWER_CONFIG_DIR/settings"
|
|
echo "Configured virt-viewer for 3 displays"
|
|
fi
|
|
|
|
# Detect DISPLAY if not set
|
|
if [ -z "$DISPLAY" ]; then
|
|
if [ -S /tmp/.X11-unix/X0 ]; then
|
|
export DISPLAY=:0
|
|
elif [ -S /tmp/.X11-unix/X1 ]; then
|
|
export DISPLAY=:1
|
|
fi
|
|
fi
|
|
|
|
# Detect WAYLAND_DISPLAY if not set
|
|
if [ -z "$WAYLAND_DISPLAY" ] && [ -z "$DISPLAY" ]; then
|
|
if [ -S "$XDG_RUNTIME_DIR/wayland-0" ]; then
|
|
export WAYLAND_DISPLAY=wayland-0
|
|
elif [ -S "/run/user/$(id -u)/wayland-0" ]; then
|
|
export XDG_RUNTIME_DIR="/run/user/$(id -u)"
|
|
export WAYLAND_DISPLAY=wayland-0
|
|
fi
|
|
fi
|
|
|
|
# Launch remote-viewer
|
|
if [ -z "$DISPLAY" ] && [ -z "$WAYLAND_DISPLAY" ]; then
|
|
echo "No display found, starting headless X server with 3 virtual monitors..."
|
|
|
|
XDISPLAY=99
|
|
while [ -S "/tmp/.X11-unix/X$XDISPLAY" ]; do
|
|
XDISPLAY=$((XDISPLAY + 1))
|
|
done
|
|
|
|
XORG_CONFIG="$SCRIPT_DIR/xorg-dummy.conf"
|
|
Xorg :$XDISPLAY -config "$XORG_CONFIG" -noreset +extension GLX +extension RANDR +extension RENDER &
|
|
XORG_PID=$!
|
|
sleep 2
|
|
|
|
export DISPLAY=:$XDISPLAY
|
|
|
|
xrandr --newmode "1920x1080" 173.00 1920 2048 2248 2576 1080 1083 1088 1120 -hsync +vsync 2>/dev/null || true
|
|
xrandr --addmode DUMMY1 "1920x1080" 2>/dev/null || true
|
|
xrandr --addmode DUMMY2 "1920x1080" 2>/dev/null || true
|
|
xrandr --output DUMMY0 --mode 1920x1080 --pos 0x0 --primary
|
|
xrandr --output DUMMY1 --mode 1920x1080 --pos 1920x0 2>/dev/null || true
|
|
xrandr --output DUMMY2 --mode 1920x1080 --pos 3840x0 2>/dev/null || true
|
|
|
|
echo "Headless X server started on :$XDISPLAY"
|
|
|
|
remote-viewer --full-screen spice://localhost:5930 &
|
|
VIEWER_PID=$!
|
|
echo "remote-viewer running headlessly (PID: $VIEWER_PID)"
|
|
else
|
|
echo "Launching remote-viewer with fullscreen for multi-display..."
|
|
remote-viewer --full-screen spice://localhost:5930 &
|
|
VIEWER_PID=$!
|
|
fi
|
|
echo ""
|
|
|
|
# Enable all 3 displays via SPICE protocol
|
|
if [ -f "$SCRIPT_DIR/enable-displays.py" ]; then
|
|
echo "Enabling displays (waiting for SPICE agent, up to 5 minutes)..."
|
|
python3 "$SCRIPT_DIR/enable-displays.py" --timeout 300 2>&1 &
|
|
ENABLE_PID=$!
|
|
fi
|
|
|
|
# Start screenshot loop in background
|
|
echo "Starting screenshot loop..."
|
|
(while true; do "$SCRIPT_DIR/screenshot.sh" 2>/dev/null; sleep 5; done) &
|
|
SCREENSHOT_LOOP_PID=$!
|
|
fi
|
|
|
|
echo "Tips:"
|
|
echo " - socat - UNIX-CONNECT:.nogit/vm/serial.sock - Serial console (login: ecouser/ecouser)"
|
|
if [ "$TARGET_ARCH" != "rpi" ]; then
|
|
echo " - http://localhost:3006 - Management UI"
|
|
fi
|
|
echo ""
|
|
|
|
if [ "$AUTO_MODE" = true ] && [ "$TARGET_ARCH" = "amd64" ]; then
|
|
echo "=== Auto mode: waiting for display setup ==="
|
|
|
|
if [ -n "$ENABLE_PID" ]; then
|
|
wait $ENABLE_PID
|
|
ENABLE_EXIT=$?
|
|
if [ $ENABLE_EXIT -ne 0 ]; then
|
|
echo "FAIL: Could not enable displays (exit code: $ENABLE_EXIT)"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
echo "Taking screenshot..."
|
|
"$SCRIPT_DIR/screenshot.sh"
|
|
|
|
SCREENSHOT="$PROJECT_ROOT/.nogit/screenshots/latest.png"
|
|
if [ -f "$SCREENSHOT" ]; then
|
|
WIDTH=$(identify -format "%w" "$SCREENSHOT" 2>/dev/null || echo "0")
|
|
if [ "$WIDTH" -ge 5760 ]; then
|
|
echo "SUCCESS: Multi-display test passed (width: ${WIDTH}px)"
|
|
exit 0
|
|
else
|
|
echo "FAIL: Screenshot width is ${WIDTH}px, expected >= 5760px"
|
|
exit 1
|
|
fi
|
|
else
|
|
echo "FAIL: Screenshot not found"
|
|
exit 1
|
|
fi
|
|
else
|
|
echo "=== Press Ctrl-C to stop ==="
|
|
echo ""
|
|
wait $QEMU_PID 2>/dev/null || true
|
|
fi
|