summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--assets/archangel.conf.example96
-rw-r--r--scripts/testing/README.org379
-rw-r--r--scripts/testing/archinstall-config.json117
-rw-r--r--scripts/testing/archsetup-test.conf14
-rw-r--r--scripts/testing/archsetup-vm.conf11
-rwxr-xr-xscripts/testing/cleanup-tests.sh125
-rwxr-xr-xscripts/testing/create-base-vm.sh249
-rwxr-xr-xscripts/testing/debug-vm.sh122
-rwxr-xr-xscripts/testing/finalize-base-vm.sh31
-rw-r--r--scripts/testing/lib/network-diagnostics.sh24
-rw-r--r--scripts/testing/lib/validation.sh11
-rwxr-xr-xscripts/testing/lib/vm-utils.sh471
-rwxr-xr-xscripts/testing/run-test.sh173
-rwxr-xr-xscripts/testing/setup-testing-env.sh104
14 files changed, 816 insertions, 1111 deletions
diff --git a/assets/archangel.conf.example b/assets/archangel.conf.example
new file mode 100644
index 0000000..dacf340
--- /dev/null
+++ b/assets/archangel.conf.example
@@ -0,0 +1,96 @@
+# archangel.conf - Unattended Installation Configuration
+#
+# Copy this file and edit values.
+# Usage: archangel --config-file /path/to/your-config.conf
+#
+# Required fields: HOSTNAME, TIMEZONE, DISKS, ROOT_PASSWORD
+# For ZFS: also need ZFS_PASSPHRASE or NO_ENCRYPT=yes
+# All other fields have sensible defaults.
+
+#############################
+# Filesystem Selection
+#############################
+
+# Filesystem type (optional, default: zfs)
+# Options: zfs, btrfs
+# Note: btrfs support coming soon
+FILESYSTEM=zfs
+
+#############################
+# System Configuration
+#############################
+
+# Hostname for the installed system (required)
+HOSTNAME=archangel
+
+# Timezone (required) - Use format: Region/City
+# Examples: America/Los_Angeles, Europe/London, Asia/Tokyo
+TIMEZONE=America/Los_Angeles
+
+# Locale (optional, default: en_US.UTF-8)
+LOCALE=en_US.UTF-8
+
+# Console keymap (optional, default: us)
+KEYMAP=us
+
+#############################
+# Disk Configuration
+#############################
+
+# Disks to use for installation (required)
+# Single disk: DISKS=/dev/vda
+# Multiple disks: DISKS=/dev/vda,/dev/vdb,/dev/vdc
+DISKS=/dev/vda
+
+# RAID level for multi-disk setups (optional)
+# Options: mirror, stripe, raidz1, raidz2, raidz3
+# Default: mirror (when multiple disks specified)
+# Leave empty for single disk
+RAID_LEVEL=
+
+#############################
+# Security
+#############################
+
+# ZFS encryption passphrase (required unless NO_ENCRYPT=yes)
+# This will be required at every boot to unlock the pool
+ZFS_PASSPHRASE=changeme
+
+# Skip ZFS encryption (optional, default: no)
+# Set to "yes" to create an unencrypted pool
+# Use cases:
+# - VMs or test environments
+# - Systems with hardware encryption (SED drives)
+# - Data that doesn't require encryption
+# WARNING: Without encryption, anyone with physical access can read your data
+#NO_ENCRYPT=no
+
+# Root password (required)
+ROOT_PASSWORD=changeme
+
+#############################
+# Network Configuration
+#############################
+
+# Enable SSH with root login (optional, default: yes)
+# Set to "no" to disable SSH
+ENABLE_SSH=yes
+
+# WiFi configuration (optional)
+# Leave empty for ethernet-only or to skip WiFi setup
+WIFI_SSID=
+WIFI_PASSWORD=
+
+#############################
+# Advanced ZFS Options
+#############################
+
+# Pool name (optional, default: zroot)
+#POOL_NAME=zroot
+
+# Compression algorithm (optional, default: zstd)
+#COMPRESSION=zstd
+
+# Sector size shift (optional, default: 12 for 4K sectors)
+# Use 13 for 8K sector drives
+#ASHIFT=12
diff --git a/scripts/testing/README.org b/scripts/testing/README.org
index a52dbea..e58a89e 100644
--- a/scripts/testing/README.org
+++ b/scripts/testing/README.org
@@ -10,20 +10,23 @@ This directory contains the complete testing infrastructure for archsetup, built
*Realism over speed.* We use full VMs (not containers) to test everything archsetup does: user creation, systemd services, X11/DWM, hardware drivers, and boot process.
+** Architecture
+
+Uses direct QEMU with user-mode networking and SSH port forwarding. No libvirt/virsh dependency. Base VM creation is fully automated via the archangel ISO (unattended Arch Linux installer).
+
* Quick Start
** One-Time Setup
#+begin_src bash
-# Install required packages and configure environment
+# Install required packages (qemu-full, sshpass, edk2-ovmf, socat)
./scripts/testing/setup-testing-env.sh
-# Log out and back in (for libvirt group membership)
+# Copy an archangel ISO to vm-images/
+cp /path/to/archzfs-*.iso vm-images/
-# Create the base VM (minimal Arch installation)
+# Create the base VM (fully automated, no manual steps)
./scripts/testing/create-base-vm.sh
-# Follow on-screen instructions to complete installation
-# Run finalize-base-vm.sh when done
#+end_src
** Run a Test
@@ -42,14 +45,11 @@ This directory contains the complete testing infrastructure for archsetup, built
** Debug Interactively
#+begin_src bash
-# Clone base VM for debugging
-./scripts/testing/debug-vm.sh
-
-# Use existing test disk
-./scripts/testing/debug-vm.sh test-results/20251108-143000/test.qcow2
-
-# Use base VM (read-only)
+# Launch base VM with graphical display (copy-on-write overlay, safe)
./scripts/testing/debug-vm.sh --base
+
+# Use existing test disk directly
+./scripts/testing/debug-vm.sh vm-images/some-disk.qcow2
#+end_src
** Clean Up
@@ -72,82 +72,44 @@ This directory contains the complete testing infrastructure for archsetup, built
*Purpose:* One-time setup of testing infrastructure
*What it does:*
-- Installs QEMU/KVM, libvirt, and related tools
-- Configures libvirt networking
-- Adds user to libvirt group
+- Installs QEMU/KVM, sshpass, OVMF firmware, and socat
- Verifies KVM support
- Creates directories for artifacts
*When to run:* Once per development machine
-*Usage:*
-#+begin_src bash
-./scripts/testing/setup-testing-env.sh
-#+end_src
-
** create-base-vm.sh
*Purpose:* Create the "golden image" minimal Arch VM
*What it does:*
-- Downloads latest Arch ISO
-- Creates VM and boots from ISO
-- Opens virt-viewer for you to complete installation manually
+- Boots an archangel ISO in QEMU with SSH port forwarding
+- Waits for SSH to become available on the live ISO
+- Copies archangel config file (=archsetup-test.conf=) into the VM
+- Runs =archangel --config-file= for fully unattended installation
+- Boots the installed system and verifies SSH/services
+- Creates a "clean-install" qemu-img snapshot
*When to run:* Once (or when you want to refresh base image)
-*Usage:*
-#+begin_src bash
-./scripts/testing/create-base-vm.sh
-#+end_src
-
-*Process:*
-1. Script creates VM and boots from Arch ISO
-2. virt-viewer opens automatically showing VM display
-3. You complete installation manually using archinstall:
- - Login as root (no password)
- - Run =archinstall=
- - Configure: hostname=archsetup-test, root password=archsetup
- - Install packages: openssh git vim sudo
- - Enable services: sshd, dhcpcd
- - Configure SSH to allow root login
- - Poweroff when done
-4. Run =./scripts/testing/finalize-base-vm.sh= to complete
-
-*See also:* [[file:../../docs/base-vm-installation-checklist.org][Base VM Installation Checklist]]
-
-*Result:* Base VM image at =vm-images/archsetup-base.qcow2=
+*Result:* Base VM image at =vm-images/archsetup-base.qcow2= with "clean-install" snapshot
** run-test.sh
*Purpose:* Execute a full test run of archsetup
*What it does:*
-- Clones base VM (copy-on-write, fast)
-- Starts test VM
-- Transfers archsetup script and dotfiles
+- Reverts base VM to clean-install snapshot
+- Boots the VM via QEMU
+- Transfers archsetup script and dotfiles via git bundle
- Executes archsetup inside VM
- Captures logs and results
-- Runs validation checks
+- Runs comprehensive validation checks
- Generates test report
-- Cleans up (unless =--keep=)
+- Reverts to clean snapshot (unless =--keep=)
*When to run:* Every time you want to test archsetup
-*Usage:*
-#+begin_src bash
-# Test current archsetup
-./scripts/testing/run-test.sh
-
-# Test specific version
-./scripts/testing/run-test.sh --script /path/to/archsetup
-
-# Keep VM for debugging
-./scripts/testing/run-test.sh --keep
-#+end_src
-
-*Time:* 30-60 minutes (mostly downloading packages)
-
*Results:* Saved to =test-results/TIMESTAMP/=
- =test.log= - Complete log output
- =test-report.txt= - Summary of results
@@ -159,51 +121,34 @@ This directory contains the complete testing infrastructure for archsetup, built
*Purpose:* Launch VM for interactive debugging
*What it does:*
-- Creates VM from base image or existing test disk
-- Configures console and SSH access
-- Provides connection instructions
+- Creates a copy-on-write overlay disk (instant, protects base image)
+- Boots QEMU with GTK graphical display
+- Provides SSH connection info
+- Cleans up overlay when the GTK window is closed
*When to run:* When you need to manually test or debug
-*Usage:*
-#+begin_src bash
-# Clone base VM for debugging
-./scripts/testing/debug-vm.sh
-
-# Use existing test disk
-./scripts/testing/debug-vm.sh vm-images/archsetup-test-20251108-143000.qcow2
-
-# Use base VM (read-only)
-./scripts/testing/debug-vm.sh --base
-#+end_src
-
*Connect via:*
-- Console: =virsh console archsetup-debug-TIMESTAMP=
-- SSH: =ssh root@IP_ADDRESS= (password: archsetup)
-- VNC: =virt-viewer archsetup-debug-TIMESTAMP=
+- GTK window (opens automatically)
+- SSH: =sshpass -p 'archsetup' ssh -p 2222 root@localhost=
** cleanup-tests.sh
*Purpose:* Clean up old test VMs and artifacts
*What it does:*
-- Lists all test VMs and destroys them
-- Removes test disk images
+- Stops running QEMU processes
+- Kills orphaned QEMU processes
+- Removes temporary disk images (overlays, test clones)
- Keeps last N test results, deletes rest
*When to run:* Periodically to free disk space
-*Usage:*
-#+begin_src bash
-# Interactive cleanup
-./scripts/testing/cleanup-tests.sh
+** archsetup-test.conf
-# Keep last 3 test results
-./scripts/testing/cleanup-tests.sh --keep 3
+*Purpose:* Archangel config file for automated base VM creation
-# Force without prompts
-./scripts/testing/cleanup-tests.sh --force
-#+end_src
+Defines the base system: btrfs filesystem, no encryption, SSH enabled, root password "archsetup". Used by =create-base-vm.sh=.
* Directory Structure
@@ -213,27 +158,30 @@ archsetup/
│ └── testing/
│ ├── README.org # This file
│ ├── setup-testing-env.sh # Setup infrastructure
-│ ├── create-base-vm.sh # Create base VM
+│ ├── create-base-vm.sh # Create base VM (automated)
│ ├── run-test.sh # Run tests
+│ ├── run-test-baremetal.sh # Bare-metal testing
│ ├── debug-vm.sh # Interactive debugging
│ ├── cleanup-tests.sh # Clean up
-│ ├── finalize-base-vm.sh # Finalize base (generated)
-│ ├── archinstall-config.json # Archinstall config
+│ ├── archsetup-test.conf # Archangel config for test VMs
│ └── lib/
│ ├── logging.sh # Logging utilities
-│ └── vm-utils.sh # VM management
+│ ├── vm-utils.sh # QEMU VM management
+│ ├── validation.sh # Test validation checks
+│ └── network-diagnostics.sh # Network pre-flight checks
├── vm-images/ # VM disk images (gitignored)
-│ ├── archsetup-base.qcow2 # Golden image
-│ ├── arch-latest.iso # Arch ISO
-│ └── archsetup-test-*.qcow2 # Test VMs
+│ ├── archsetup-base.qcow2 # Golden image with snapshot
+│ ├── archzfs-*.iso # Archangel ISO
+│ ├── OVMF_VARS.fd # UEFI variables (per-VM)
+│ └── debug-overlay-*.qcow2 # Temp debug overlays
├── test-results/ # Test results (gitignored)
-│ ├── TIMESTAMP/
-│ │ ├── test.log
-│ │ ├── test-report.txt
-│ │ └── archsetup-*.log
-│ └── latest -> TIMESTAMP/ # Symlink to latest
+│ └── TIMESTAMP/
+│ ├── test.log
+│ ├── test-report.txt
+│ └── archsetup-*.log
└── docs/
- └── testing-strategy.org # Complete strategy doc
+ ├── testing-strategy.org # Complete strategy doc
+ └── archangel-vm-testing-guide.org # Archangel integration guide
#+end_example
* Configuration
@@ -241,30 +189,27 @@ archsetup/
** VM Specifications
All test VMs use:
-- *CPUs:* 2 vCPUs
-- *RAM:* 4GB (matches archsetup tmpfs build directory)
+- *CPUs:* 4 vCPUs
+- *RAM:* 4GB
- *Disk:* 50GB (thin provisioned qcow2)
-- *Network:* NAT via libvirt default network
-- *Boot:* UEFI (systemd-boot bootloader)
-- *Display:* Serial console + VNC
+- *Network:* User-mode networking with SSH port forwarding (localhost:2222)
+- *Boot:* UEFI (via OVMF firmware)
+- *Display:* Headless (automated) or GTK (debug)
Set environment variables to customize:
#+begin_src bash
-VM_CPUS=4 VM_RAM=8192 ./scripts/testing/run-test.sh
+VM_CPUS=8 VM_RAM=8192 ./scripts/testing/run-test.sh
#+end_src
** Base VM Specifications
-The base VM contains a minimal Arch installation:
-- Base system packages
-- Linux kernel and firmware
-- OpenSSH server (for automation)
-- dhcpcd (for networking)
-- git, vim, sudo (essentials)
+The base VM is created automatically by archangel with:
+- Btrfs filesystem (no encryption)
+- GRUB bootloader
+- OpenSSH server (root login enabled)
+- NetworkManager
- Root password: "archsetup"
-This matches the documented prerequisites for archsetup.
-
* Validation Checks
Each test run performs these validation checks:
@@ -274,62 +219,37 @@ Each test run performs these validation checks:
2. User 'cjennings' was created
3. Dotfiles are stowed (symlinks exist)
4. yay (AUR helper) is installed
-5. DWM is built and installed
+5. DWM or Hyprland is configured
-** Additional (Future)
+** Additional
- All expected packages installed
-- X11 can start
- systemd services enabled
- Firewall configured
+- Developer tools present
* Troubleshooting
** VM fails to start
-Check if libvirtd is running:
-#+begin_src bash
-sudo systemctl status libvirtd
-sudo systemctl start libvirtd
-#+end_src
-
-** Cannot get VM IP address
-
-The VM may not have booted completely or networking failed:
+Check if KVM is available and port 2222 is free:
#+begin_src bash
-# Check VM status
-virsh domstate VM_NAME
-
-# Connect to console to debug
-virsh console VM_NAME
-
-# Check if dhcpcd is running in VM
-systemctl status dhcpcd
+ls -l /dev/kvm
+ss -tln | grep 2222
#+end_src
** SSH connection refused
-Wait longer for VM to boot, or check if sshd is enabled:
+Wait longer for VM to boot, or check serial log:
#+begin_src bash
-virsh console VM_NAME
-# Inside VM:
-systemctl status sshd
-systemctl start sshd
+cat vm-images/qemu-serial.log
#+end_src
** KVM not available
Check if virtualization is enabled in BIOS and KVM modules loaded:
#+begin_src bash
-# Check for /dev/kvm
ls -l /dev/kvm
-
-# Load KVM module (Intel)
-sudo modprobe kvm-intel
-
-# Or for AMD
-sudo modprobe kvm-amd
-
-# Verify
+sudo modprobe kvm-amd # or kvm-intel
lsmod | grep kvm
#+end_src
@@ -338,160 +258,27 @@ lsmod | grep kvm
Clean up old tests:
#+begin_src bash
./scripts/testing/cleanup-tests.sh --force
-#+end_src
-
-Check disk usage:
-#+begin_src bash
du -sh vm-images/ test-results/
#+end_src
-** Base VM Installation Issues
-
-*** Firewall blocking VM network
-
-If the VM cannot reach the internet (100% packet loss when pinging), check the host firewall:
-
-#+begin_src bash
-# Check UFW status on host
-sudo ufw status
-
-# Check libvirt NAT rules on host
-sudo iptables -t nat -L -n -v | grep -i libvirt
-#+end_src
-
-*Solution:* Temporarily disable UFW during base VM creation:
-#+begin_src bash
-sudo ufw disable
-# Create base VM
-sudo ufw enable
-#+end_src
-
-Or add libvirt rules to UFW:
-#+begin_src bash
-sudo ufw allow in on virbr0
-sudo ufw allow out on virbr0
-#+end_src
-
-*** VM network not working after boot
-
-If =dhcpcd= isn't running or network isn't configured:
-
-#+begin_src bash
-# In the VM - restart network from scratch
-killall dhcpcd
-
-# Bring interface down and up (replace enp1s0 with your interface)
-ip link set enp1s0 down
-ip link set enp1s0 up
-
-# Start dhcpcd
-dhcpcd enp1s0
-
-# Wait and verify
-sleep 3
-ip addr show enp1s0
-ip route
-
-# Test connectivity
-ping -c 3 192.168.122.1 # Gateway
-ping -c 3 8.8.8.8 # Google DNS
-#+end_src
-
-*** DNS not working (127.0.0.53 in resolv.conf)
-
-The Live ISO uses systemd-resolved stub resolver which may not work:
-
-#+begin_src bash
-# In the VM - set real DNS servers
-echo "nameserver 8.8.8.8" > /etc/resolv.conf
-echo "nameserver 1.1.1.1" >> /etc/resolv.conf
-
-# Test
-ping -c 3 archlinux.org
-#+end_src
-
-*** Cannot paste into virt-viewer terminal
-
-Clipboard integration doesn't work well with virt-viewer. Use HTTP server instead:
-
-#+begin_src bash
-# On host - serve the installation script
-cd vm-images
-python -m http.server 8000
-
-# In VM - download and run
-curl http://192.168.122.1:8000/auto-install.sh | bash
-
-# Or download first to review
-curl http://192.168.122.1:8000/auto-install.sh -o install.sh
-cat install.sh
-bash install.sh
-#+end_src
-
-*** Partitions "in use" error
-
-If re-running installation after a failed attempt:
+** Port 2222 already in use
+Another QEMU instance may be running:
#+begin_src bash
-# In the VM - unmount and wipe partitions
-mount | grep vda
-umount /mnt/boot 2>/dev/null
-umount /mnt 2>/dev/null
-umount -l /mnt # Lazy unmount if still busy
-
-# Wipe partition table completely
-wipefs -a /dev/vda
-
-# Run install script again
-bash install.sh
-#+end_src
-
-*** Alternative: Use archinstall
-
-Instead of the auto-install.sh script, you can use Arch's built-in installer:
-
-#+begin_src bash
-# In the VM
-archinstall
-#+end_src
-
-*Recommended settings:*
-- Disk: =/dev/vda=
-- Filesystem: =ext4=
-- Bootloader: =systemd-boot=
-- Hostname: =archsetup-test=
-- Root password: =archsetup=
-- Profile: =minimal=
-- Additional packages: =openssh dhcpcd git vim sudo=
-- Network: =NetworkManager= or =systemd-networkd=
-
-*After installation, before rebooting:*
-#+begin_src bash
-# Chroot into new system
-arch-chroot /mnt
-
-# Enable services
-systemctl enable sshd
-systemctl enable dhcpcd # or NetworkManager
-
-# Allow root SSH login
-sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config
-sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config
+# Find what's using the port
+ss -tlnp | grep 2222
-# Exit and poweroff
-exit
-poweroff
+# Kill orphaned QEMU processes
+pkill -f "qemu-system.*archsetup-test"
#+end_src
* Future Enhancements
** Planned Improvements
-- [ ] Fully automated base VM creation (expect-based console automation)
-- [ ] Parallel test execution (multiple VMs)
-- [ ] Screenshot capture of X11 desktop
+- [ ] Parallel test execution (multiple VMs on different ports)
+- [ ] Screenshot capture of X11/Wayland desktop
- [ ] CI/CD integration (GitHub Actions / GitLab CI)
- [ ] Performance benchmarking over time
-- [ ] Cloud-init based base image (faster provisioning)
** Test Scenarios
- [ ] Idempotency test (run archsetup twice)
@@ -502,7 +289,7 @@ poweroff
* References
- [[file:../../docs/testing-strategy.org][Testing Strategy Document]]
-- [[https://wiki.archlinux.org/title/Libvirt][Arch Wiki: libvirt]]
+- [[file:../../docs/archangel-vm-testing-guide.org][Archangel VM Testing Guide]]
- [[https://wiki.archlinux.org/title/QEMU][Arch Wiki: QEMU]]
- [[file:../../archsetup][Main archsetup script]]
-- [[file:../../TODO.org][Project TODO]]
+- [[file:../../todo.org][Project TODO]]
diff --git a/scripts/testing/archinstall-config.json b/scripts/testing/archinstall-config.json
deleted file mode 100644
index a55e2a1..0000000
--- a/scripts/testing/archinstall-config.json
+++ /dev/null
@@ -1,117 +0,0 @@
-{
- "!users": {
- "0": {
- "!password": "archsetup",
- "username": "root",
- "sudo": false
- }
- },
- "archinstall-language": "English",
- "audio_config": null,
- "bootloader": "systemd-bootctl",
- "config_version": "2.8.0",
- "debug": false,
- "disk_config": {
- "config_type": "default_layout",
- "device_modifications": [
- {
- "device": "/dev/vda",
- "partitions": [
- {
- "btrfs": [],
- "flags": [
- "Boot"
- ],
- "fs_type": "fat32",
- "length": {
- "sector_size": null,
- "total_size": null,
- "unit": "MiB",
- "value": 512
- },
- "mount_options": [],
- "mountpoint": "/boot",
- "obj_id": "boot_partition",
- "start": {
- "sector_size": null,
- "total_size": null,
- "unit": "MiB",
- "value": 1
- },
- "status": "create",
- "type": "primary"
- },
- {
- "btrfs": [],
- "flags": [],
- "fs_type": "ext4",
- "length": {
- "sector_size": null,
- "total_size": null,
- "unit": "MiB",
- "value": 100
- },
- "mount_options": [],
- "mountpoint": "/",
- "obj_id": "root_partition",
- "start": {
- "sector_size": null,
- "total_size": null,
- "unit": "MiB",
- "value": 513
- },
- "status": "create",
- "type": "primary"
- }
- ],
- "wipe": true
- }
- ]
- },
- "disk_encryption": null,
- "hostname": "archsetup-test",
- "kernels": [
- "linux"
- ],
- "locale_config": {
- "kb_layout": "us",
- "sys_enc": "UTF-8",
- "sys_lang": "en_US"
- },
- "mirror_config": {
- "custom_mirrors": [],
- "mirror_regions": {
- "United States": [
- "https://mirror.rackspace.com/archlinux/$repo/os/$arch",
- "https://mirror.leaseweb.com/archlinux/$repo/os/$arch"
- ]
- }
- },
- "network_config": {
- "type": "nm"
- },
- "no_pkg_lookups": false,
- "ntp": true,
- "offline": false,
- "packages": [
- "openssh",
- "dhcpcd",
- "git",
- "vim"
- ],
- "parallel downloads": 5,
- "profile_config": {
- "gfx_driver": "All open-source",
- "greeter": null,
- "profile": {
- "custom_settings": {},
- "details": [],
- "main": "Minimal"
- }
- },
- "script": "guided",
- "silent": false,
- "swap": false,
- "timezone": "America/Chicago",
- "version": "2.8.0"
-}
diff --git a/scripts/testing/archsetup-test.conf b/scripts/testing/archsetup-test.conf
new file mode 100644
index 0000000..d6a8d69
--- /dev/null
+++ b/scripts/testing/archsetup-test.conf
@@ -0,0 +1,14 @@
+# archsetup-test.conf - Archangel config for archsetup test VMs
+# Used by create-base-vm.sh for fully automated base VM creation
+#
+# Usage: archangel --config-file /root/archsetup-test.conf
+
+FILESYSTEM=btrfs
+HOSTNAME=archsetup-test
+TIMEZONE=America/Chicago
+LOCALE=en_US.UTF-8
+KEYMAP=us
+DISKS=/dev/vda
+NO_ENCRYPT=yes
+ROOT_PASSWORD=archsetup
+ENABLE_SSH=yes
diff --git a/scripts/testing/archsetup-vm.conf b/scripts/testing/archsetup-vm.conf
new file mode 100644
index 0000000..4117278
--- /dev/null
+++ b/scripts/testing/archsetup-vm.conf
@@ -0,0 +1,11 @@
+# archsetup-vm.conf - Config for running archsetup in test VMs
+# Used by run-test.sh for unattended archsetup execution
+#
+# Usage: ./archsetup --config-file /tmp/archsetup-test/archsetup-vm.conf
+
+USERNAME=cjennings
+PASSWORD=archsetup
+LOCALE=en_US.UTF-8
+DESKTOP_ENV=hyprland
+NO_GPU_DRIVERS=yes
+AUTOLOGIN=yes
diff --git a/scripts/testing/cleanup-tests.sh b/scripts/testing/cleanup-tests.sh
index e4289a7..fd2f8de 100755
--- a/scripts/testing/cleanup-tests.sh
+++ b/scripts/testing/cleanup-tests.sh
@@ -36,85 +36,86 @@ while [[ $# -gt 0 ]]; do
esac
done
-# Initialize logging
+# Initialize logging and VM paths
LOGFILE="/tmp/cleanup-tests-$(date +'%Y%m%d-%H%M%S').log"
init_logging "$LOGFILE"
+init_vm_paths "$PROJECT_ROOT/vm-images"
section "Cleaning Up Test Artifacts"
-# Find all test VMs
-step "Finding test VMs"
-TEST_VMS=$(virsh --connect qemu:///system list --all | grep "archsetup-test-" | awk '{print $2}' || true)
+# Find and stop running QEMU processes
+step "Checking for running QEMU processes"
-if [ -z "$TEST_VMS" ]; then
- info "No test VMs found"
-else
- VM_COUNT=$(echo "$TEST_VMS" | wc -l)
- info "Found $VM_COUNT test VM(s)"
-
- if ! $FORCE; then
- echo ""
- echo "$TEST_VMS"
- echo ""
- read -p "Destroy these VMs? [y/N] " -n 1 -r
+if vm_is_running; then
+ info "Found running QEMU test VM (PID: $(cat "$PID_FILE"))"
+ if $FORCE; then
+ stop_qemu
+ else
+ read -p "Stop running VM? [y/N] " -n 1 -r
echo ""
- if [[ ! $REPLY =~ ^[Yy]$ ]]; then
- info "Skipping VM cleanup"
+ if [[ $REPLY =~ ^[Yy]$ ]]; then
+ stop_qemu
else
- for vm in $TEST_VMS; do
- step "Destroying VM: $vm"
- if vm_is_running "$vm"; then
- virsh --connect qemu:///system destroy "$vm" >> "$LOGFILE" 2>&1
- fi
- virsh --connect qemu:///system undefine "$vm" --nvram >> "$LOGFILE" 2>&1 || true
- success "VM destroyed: $vm"
- done
+ info "Skipping VM shutdown"
fi
+ fi
+else
+ info "No running VM found"
+fi
+
+# Check for orphaned QEMU processes
+QEMU_PIDS=$(pgrep -f "qemu-system.*archsetup-test" 2>/dev/null || true)
+if [ -n "$QEMU_PIDS" ]; then
+ info "Found orphaned QEMU processes: $QEMU_PIDS"
+ if $FORCE; then
+ echo "$QEMU_PIDS" | xargs kill -9 2>/dev/null || true
+ success "Orphaned processes killed"
else
- for vm in $TEST_VMS; do
- step "Destroying VM: $vm"
- if vm_is_running "$vm"; then
- virsh --connect qemu:///system destroy "$vm" >> "$LOGFILE" 2>&1
- fi
- virsh --connect qemu:///system undefine "$vm" --nvram >> "$LOGFILE" 2>&1 || true
- success "VM destroyed: $vm"
- done
+ read -p "Kill orphaned QEMU processes? [y/N] " -n 1 -r
+ echo ""
+ if [[ $REPLY =~ ^[Yy]$ ]]; then
+ echo "$QEMU_PIDS" | xargs kill -9 2>/dev/null || true
+ success "Orphaned processes killed"
+ fi
fi
fi
-# Clean up test disk images
+# Clean up QEMU runtime files
+rm -f "$PID_FILE" "$MONITOR_SOCK"
+
+# Clean up debug overlay and test disk images
section "Cleaning Up Disk Images"
-step "Finding test disk images"
+step "Finding temporary disk images"
if [ -d "$PROJECT_ROOT/vm-images" ]; then
- TEST_DISKS=$(find "$PROJECT_ROOT/vm-images" -name "archsetup-test-*.qcow2" 2>/dev/null || true)
+ TEMP_DISKS=$(find "$PROJECT_ROOT/vm-images" -name "debug-overlay-*.qcow2" -o -name "archsetup-test-*.qcow2" 2>/dev/null || true)
- if [ -z "$TEST_DISKS" ]; then
- info "No test disk images found"
+ if [ -z "$TEMP_DISKS" ]; then
+ info "No temporary disk images found"
else
- DISK_COUNT=$(echo "$TEST_DISKS" | wc -l)
- DISK_SIZE=$(du -ch $TEST_DISKS | tail -1 | awk '{print $1}')
- info "Found $DISK_COUNT test disk image(s) totaling $DISK_SIZE"
+ DISK_COUNT=$(echo "$TEMP_DISKS" | wc -l)
+ DISK_SIZE=$(du -ch $TEMP_DISKS 2>/dev/null | tail -1 | awk '{print $1}')
+ info "Found $DISK_COUNT temporary disk image(s) totaling $DISK_SIZE"
- if ! $FORCE; then
+ if $FORCE; then
+ echo "$TEMP_DISKS" | while read disk; do
+ rm -f "$disk"
+ done
+ success "Temporary disk images deleted"
+ else
echo ""
- echo "$TEST_DISKS"
+ echo "$TEMP_DISKS"
echo ""
read -p "Delete these disk images? [y/N] " -n 1 -r
echo ""
- if [[ ! $REPLY =~ ^[Yy]$ ]]; then
- info "Skipping disk cleanup"
- else
- echo "$TEST_DISKS" | while read disk; do
+ if [[ $REPLY =~ ^[Yy]$ ]]; then
+ echo "$TEMP_DISKS" | while read disk; do
rm -f "$disk"
done
- success "Test disk images deleted"
+ success "Temporary disk images deleted"
+ else
+ info "Skipping disk cleanup"
fi
- else
- echo "$TEST_DISKS" | while read disk; do
- rm -f "$disk"
- done
- success "Test disk images deleted"
fi
fi
fi
@@ -141,26 +142,26 @@ else
DELETE_COUNT=$(echo "$TO_DELETE" | wc -l)
info "Keeping last $KEEP_LAST, deleting $DELETE_COUNT old result(s)"
- if ! $FORCE; then
+ if $FORCE; then
+ echo "$TO_DELETE" | while read dir; do
+ rm -rf "$dir"
+ done
+ success "Old test results deleted"
+ else
echo ""
echo "Will delete:"
echo "$TO_DELETE"
echo ""
read -p "Delete these test results? [y/N] " -n 1 -r
echo ""
- if [[ ! $REPLY =~ ^[Yy]$ ]]; then
- info "Skipping results cleanup"
- else
+ if [[ $REPLY =~ ^[Yy]$ ]]; then
echo "$TO_DELETE" | while read dir; do
rm -rf "$dir"
done
success "Old test results deleted"
+ else
+ info "Skipping results cleanup"
fi
- else
- echo "$TO_DELETE" | while read dir; do
- rm -rf "$dir"
- done
- success "Old test results deleted"
fi
fi
fi
diff --git a/scripts/testing/create-base-vm.sh b/scripts/testing/create-base-vm.sh
index 03409fe..7979bd2 100755
--- a/scripts/testing/create-base-vm.sh
+++ b/scripts/testing/create-base-vm.sh
@@ -1,10 +1,11 @@
#!/bin/bash
-# Create base VM for archsetup testing - Manual Installation
+# Create base VM for archsetup testing - Automated via Archangel ISO
# Author: Craig Jennings <craigmartinjennings@gmail.com>
# License: GNU GPLv3
#
-# This script creates a VM booted from Arch ISO, then waits for you to
-# manually install Arch using archinstall.
+# This script boots an archangel ISO in QEMU, copies a config file into the
+# live environment via SSH, and runs a fully unattended Arch Linux installation.
+# The result is a base VM disk with a "clean-install" snapshot ready for testing.
set -e
@@ -17,157 +18,139 @@ source "$SCRIPT_DIR/lib/logging.sh"
source "$SCRIPT_DIR/lib/vm-utils.sh"
# Configuration
-VM_NAME="archsetup-base"
-VM_CPUS="${VM_CPUS:-4}"
-VM_RAM="${VM_RAM:-8192}" # MB
-VM_DISK="${VM_DISK:-50}" # GB
VM_IMAGES_DIR="$PROJECT_ROOT/vm-images"
-ISO_URL="https://mirrors.kernel.org/archlinux/iso/latest/archlinux-x86_64.iso"
-ISO_PATH="$VM_IMAGES_DIR/arch-latest.iso"
-DISK_PATH="$VM_IMAGES_DIR/archsetup-base.qcow2"
+CONFIG_FILE="$SCRIPT_DIR/archsetup-test.conf"
+LIVE_ISO_PASSWORD="archzfs"
+SNAPSHOT_NAME="clean-install"
# Initialize logging
+mkdir -p "$PROJECT_ROOT/test-results"
LOGFILE="$PROJECT_ROOT/test-results/create-base-vm-$(date +'%Y%m%d-%H%M%S').log"
init_logging "$LOGFILE"
+init_vm_paths "$VM_IMAGES_DIR"
section "Creating Base VM for ArchSetup Testing"
-# Verify prerequisites
+# ─── Prerequisites ────────────────────────────────────────────────────
+
step "Checking prerequisites"
-check_libvirt || fatal "libvirt not running"
-check_libvirt_group || fatal "User not in libvirt group"
-check_kvm || fatal "KVM not available"
+check_prerequisites || fatal "Missing prerequisites"
success "Prerequisites satisfied"
-# Create vm-images directory
-mkdir -p "$VM_IMAGES_DIR"
+# Verify config file exists
+if [ ! -f "$CONFIG_FILE" ]; then
+ fatal "Config file not found: $CONFIG_FILE"
+fi
+
+# Find archangel ISO in vm-images/
+ISO_PATH=$(find "$VM_IMAGES_DIR" -maxdepth 1 -name "archzfs-*.iso" -type f 2>/dev/null | sort -V | tail -1)
+if [ -z "$ISO_PATH" ]; then
+ fatal "No archangel ISO found in $VM_IMAGES_DIR/"
+ info "Copy an archzfs-*.iso file to: $VM_IMAGES_DIR/"
+fi
+info "Using ISO: $(basename "$ISO_PATH")"
-# Download Arch ISO if needed
-section "Preparing Arch Linux ISO"
+# ─── Prepare Disk ─────────────────────────────────────────────────────
-if [ -f "$ISO_PATH" ]; then
- info "Arch ISO exists: $ISO_PATH"
+section "Preparing VM Disk"
- # Check if ISO is older than 30 days
- if [ $(find "$ISO_PATH" -mtime +30 | wc -l) -gt 0 ]; then
- warn "ISO is older than 30 days"
- info "Downloading latest version..."
- rm -f "$ISO_PATH"
- else
- success "Using existing ISO"
- fi
+# Remove old disk and OVMF vars if they exist
+if [ -f "$DISK_PATH" ]; then
+ warn "Removing existing disk: $DISK_PATH"
+ rm -f "$DISK_PATH"
fi
+rm -f "$OVMF_VARS"
+
+# Create fresh disk
+step "Creating ${VM_DISK_SIZE}G qcow2 disk"
+if qemu-img create -f qcow2 "$DISK_PATH" "${VM_DISK_SIZE}G" >> "$LOGFILE" 2>&1; then
+ success "Disk created: $DISK_PATH"
+else
+ fatal "Failed to create disk image"
+fi
+
+# ─── Phase 1: Install from ISO ───────────────────────────────────────
+
+section "Phase 1: Archangel Installation"
-if [ ! -f "$ISO_PATH" ]; then
- step "Downloading latest Arch ISO"
- info "URL: $ISO_URL"
- info "This may take several minutes..."
+start_timer "install"
- if wget --progress=dot:giga -O "$ISO_PATH" "$ISO_URL" 2>&1 | tee -a "$LOGFILE"; then
- success "ISO downloaded"
- else
- fatal "ISO download failed"
- fi
+# Boot from ISO
+start_qemu "$DISK_PATH" "iso" "$ISO_PATH" "none" || fatal "Failed to start QEMU"
+
+# Wait for live ISO SSH
+wait_for_ssh "$LIVE_ISO_PASSWORD" 120 || fatal "Live ISO SSH not available"
+
+# Copy config file into VM
+copy_to_vm "$CONFIG_FILE" "/root/archsetup-test.conf" "$LIVE_ISO_PASSWORD" || \
+ fatal "Failed to copy config to VM"
+
+# Run archangel installer (synchronous - typically 5-10 minutes)
+step "Running archangel installer (unattended)..."
+info "This will partition, install, and configure the base system"
+
+if vm_exec "$LIVE_ISO_PASSWORD" "archangel --config-file /root/archsetup-test.conf"; then
+ success "Archangel installation completed"
+else
+ error "Archangel installation failed"
+ step "Capturing serial log for debugging"
+ info "Serial log: $SERIAL_LOG"
+ fatal "Base VM installation failed - check logs"
fi
-# Remove existing VM and disk
-if vm_exists "$VM_NAME"; then
- warn "VM $VM_NAME already exists - destroying it"
- if vm_is_running "$VM_NAME"; then
- virsh destroy "$VM_NAME" >> "$LOGFILE" 2>&1
- fi
- virsh undefine "$VM_NAME" --nvram >> "$LOGFILE" 2>&1 || true
+# Power off
+stop_qemu
+stop_timer "install"
+
+# ─── Phase 2: Verify Installation ────────────────────────────────────
+
+section "Phase 2: Verifying Installation"
+
+start_timer "verify"
+
+# Boot from installed disk
+start_qemu "$DISK_PATH" "disk" "" "none" || fatal "Failed to boot installed system"
+
+# Wait for SSH on installed system
+wait_for_ssh "$ROOT_PASSWORD" 120 || fatal "Installed system SSH not available"
+
+# Basic verification
+step "Verifying base system"
+
+KERNEL=$(vm_exec "$ROOT_PASSWORD" "uname -r" 2>/dev/null)
+success "Kernel: $KERNEL"
+
+if vm_exec "$ROOT_PASSWORD" "systemctl is-active sshd" &>/dev/null; then
+ success "SSH service active"
+else
+ warn "SSH service not active"
fi
-[ -f "$DISK_PATH" ] && rm -f "$DISK_PATH"
-
-# Create and start VM
-section "Creating and Starting VM"
-
-info "Creating VM: $VM_NAME"
-info " CPUs: $VM_CPUS | RAM: ${VM_RAM}MB | Disk: ${VM_DISK}GB"
-
-virt-install \
- --connect qemu:///system \
- --name "$VM_NAME" \
- --memory "$VM_RAM" \
- --vcpus "$VM_CPUS" \
- --disk path="$DISK_PATH",size="$VM_DISK",format=qcow2,bus=virtio \
- --cdrom "$ISO_PATH" \
- --os-variant archlinux \
- --network network=default,model=virtio \
- --graphics vnc,listen=127.0.0.1 \
- --console pty,target.type=serial \
- --boot uefi \
- --noreboot \
- --check path_in_use=off \
- --filesystem type=mount,mode=mapped,source="$PROJECT_ROOT/scripts",target=host-scripts \
- >> "$LOGFILE" 2>&1 &
-
-VIRT_INSTALL_PID=$!
-
-progress "Waiting for VM to boot from ISO"
-sleep 30
-
-# Check if VM started
-if ! vm_is_running "$VM_NAME"; then
- wait $VIRT_INSTALL_PID
- EXIT_CODE=$?
- fatal "VM failed to start (exit code: $EXIT_CODE)"
+if vm_exec "$ROOT_PASSWORD" "systemctl is-active NetworkManager" &>/dev/null; then
+ success "NetworkManager active"
+else
+ warn "NetworkManager not active"
fi
-success "VM started successfully"
-
-# Display manual installation instructions
-section "Manual Installation Required"
-
-cat << 'EOF'
-
-[i]
-[i] Base VM is running from Arch ISO
-[i]
-[i] NEXT STEPS - Complete installation manually:
-[i]
-[i] 1. Open virt-viewer (should already be open):
-[i] virt-viewer --connect qemu:///system archsetup-base
-[i]
-[i] 2. Login as 'root' (no password)
-[i]
-[i] 3. Run: archinstall
-[i]
-[i] 4. Configure with these settings:
-[i] - Hostname: archsetup-test
-[i] - Root password: archsetup
-[i] - Profile: minimal
-[i] - Network: dhcpcd (or NetworkManager)
-[i] - Additional packages: openssh git vim sudo iperf3 mtr traceroute bind net-tools sshfs
-[i] - Enable: sshd, dhcpcd (or NetworkManager)
-[i]
-[i] 5. After archinstall completes:
-[i] - Chroot into /mnt: arch-chroot /mnt
-[i] - Edit /etc/ssh/sshd_config:
-[i] sed -i 's/#PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config
-[i] sed -i 's/#PasswordAuthentication.*/PasswordAuthentication yes/' /etc/ssh/sshd_config
-[i] - Set up shared folder mount (9p filesystem):
-[i] mkdir -p /mnt/host-scripts
-[i] echo 'host-scripts /mnt/host-scripts 9p trans=virtio,version=9p2000.L,rw 0 0' >> /etc/fstab
-[i] - Exit chroot: exit
-[i] - Poweroff: poweroff
-[i]
-[i] 6. After VM powers off, run:
-[i] ./scripts/testing/finalize-base-vm.sh
-[i]
-[i] Log file: $LOGFILE
-[i]
-
-EOF
-
-info "Waiting for VM to power off..."
-info "(This script will exit when you manually power off the VM)"
-
-# Wait for virt-install to finish (VM powers off)
-wait $VIRT_INSTALL_PID || true
-
-success "VM has powered off"
+# Power off for snapshot
+stop_qemu
+stop_timer "verify"
+
+# ─── Phase 3: Create Snapshot ────────────────────────────────────────
+
+section "Phase 3: Creating Clean-Install Snapshot"
+
+create_snapshot "$DISK_PATH" "$SNAPSHOT_NAME" || fatal "Failed to create snapshot"
+
+# ─── Done ─────────────────────────────────────────────────────────────
+
+section "Base VM Created Successfully"
+
+info ""
+info " Disk: $DISK_PATH"
+info " Snapshot: $SNAPSHOT_NAME"
+info " Config: $(basename "$CONFIG_FILE")"
+info " Log: $LOGFILE"
+info ""
+info "Next step: Run ./scripts/testing/run-test.sh"
info ""
-info "Next step: Run ./scripts/testing/finalize-base-vm.sh"
diff --git a/scripts/testing/debug-vm.sh b/scripts/testing/debug-vm.sh
index a442850..5b2b197 100755
--- a/scripts/testing/debug-vm.sh
+++ b/scripts/testing/debug-vm.sh
@@ -2,6 +2,9 @@
# Launch VM for interactive debugging
# Author: Craig Jennings <craigmartinjennings@gmail.com>
# License: GNU GPLv3
+#
+# Launches a QEMU VM with a graphical display for interactive debugging.
+# Uses a copy-on-write overlay when using --base to protect the base image.
set -e
@@ -27,21 +30,23 @@ else
echo "Usage: $0 [disk-image.qcow2 | --base]"
echo ""
echo "Options:"
- echo " --base Use base VM (read-only, safe for testing)"
- echo " disk-image.qcow2 Use existing test disk image"
- echo " (no args) Clone base VM for debugging"
+ echo " --base Use base VM via copy-on-write overlay (safe)"
+ echo " disk-image.qcow2 Use existing test disk image directly"
+ echo " (no args) Same as --base"
exit 1
fi
# Configuration
TIMESTAMP=$(date +'%Y%m%d-%H%M%S')
-DEBUG_VM_NAME="archsetup-debug-$TIMESTAMP"
-BASE_DISK="$PROJECT_ROOT/vm-images/archsetup-base.qcow2"
+VM_IMAGES_DIR="$PROJECT_ROOT/vm-images"
+BASE_DISK="$VM_IMAGES_DIR/archsetup-base.qcow2"
ROOT_PASSWORD="archsetup"
+OVERLAY_DISK=""
-# Initialize logging
+# Initialize logging and VM paths
LOGFILE="/tmp/debug-vm-$TIMESTAMP.log"
init_logging "$LOGFILE"
+init_vm_paths "$VM_IMAGES_DIR"
section "Launching Debug VM"
@@ -50,84 +55,55 @@ if $USE_BASE; then
if [ ! -f "$BASE_DISK" ]; then
fatal "Base disk not found: $BASE_DISK"
fi
- VM_DISK="$BASE_DISK"
- info "Using base VM (read-only snapshot mode)"
-else
- if [ -z "$VM_DISK" ]; then
- # Clone base VM
- VM_DISK="$PROJECT_ROOT/vm-images/$DEBUG_VM_NAME.qcow2"
- step "Cloning base VM for debugging"
- clone_disk "$BASE_DISK" "$VM_DISK" || fatal "Failed to clone base VM"
- success "Debug disk created: $VM_DISK"
+
+ # Create a copy-on-write overlay (instant, protects base image)
+ OVERLAY_DISK="$VM_IMAGES_DIR/debug-overlay-$TIMESTAMP.qcow2"
+ step "Creating copy-on-write overlay"
+ if qemu-img create -f qcow2 -b "$BASE_DISK" -F qcow2 "$OVERLAY_DISK" >> "$LOGFILE" 2>&1; then
+ success "Overlay created: $(basename "$OVERLAY_DISK")"
else
- info "Using existing disk: $VM_DISK"
+ fatal "Failed to create overlay disk"
fi
+ VM_DISK="$OVERLAY_DISK"
+else
+ info "Using existing disk: $VM_DISK"
fi
-# Create debug VM
-step "Creating debug VM: $DEBUG_VM_NAME"
-virt-install \
- --connect qemu:///system \
- --name "$DEBUG_VM_NAME" \
- --memory 4096 \
- --vcpus 2 \
- --disk path="$VM_DISK",format=qcow2,bus=virtio \
- --os-variant archlinux \
- --network network=default,model=virtio \
- --graphics vnc,listen=127.0.0.1 \
- --console pty,target.type=serial \
- --boot uefi \
- --import \
- --noautoconsole \
- >> "$LOGFILE" 2>&1
-
-success "Debug VM created"
-
-# Wait for boot
-step "Waiting for VM to boot..."
-sleep 20
-
-# Get VM IP
-VM_IP=$(get_vm_ip "$DEBUG_VM_NAME" 2>/dev/null || true)
+# If snapshot exists, restore it first (only for non-overlay disks)
+if [ -z "$OVERLAY_DISK" ] && snapshot_exists "$VM_DISK" "clean-install"; then
+ step "Restoring clean-install snapshot"
+ restore_snapshot "$VM_DISK" "clean-install"
+fi
+
+# Launch QEMU with graphical display
+step "Starting QEMU with graphical display"
+start_qemu "$VM_DISK" "disk" "" "gtk" || fatal "Failed to start QEMU"
# Display connection information
section "Debug VM Ready"
info ""
-info "VM Name: $DEBUG_VM_NAME"
-if [ -n "$VM_IP" ]; then
- info "IP Address: $VM_IP"
-fi
-info "Disk: $VM_DISK"
+info " Disk: $(basename "$VM_DISK")"
+info " SSH: sshpass -p '$ROOT_PASSWORD' ssh -p $SSH_PORT root@localhost"
+info " Root password: $ROOT_PASSWORD"
info ""
-info "Connect via:"
-info " Console: virsh console $DEBUG_VM_NAME"
-if [ -n "$VM_IP" ]; then
- info " SSH: ssh root@$VM_IP"
-fi
-info " VNC: virt-viewer $DEBUG_VM_NAME"
-info ""
-info "Root password: $ROOT_PASSWORD"
-info ""
-info "When done debugging:"
-info " virsh destroy $DEBUG_VM_NAME"
-info " virsh undefine $DEBUG_VM_NAME"
-if [ ! "$VM_DISK" = "$BASE_DISK" ] && [ -z "$1" ]; then
- info " rm $VM_DISK"
-fi
-info ""
-info "Log file: $LOGFILE"
+info " The GTK window should be open. Close it to stop the VM."
+info " Log file: $LOGFILE"
info ""
-# Offer to connect to console
-read -p "Connect to console now? [Y/n] " -n 1 -r
-echo ""
-if [[ $REPLY =~ ^[Nn]$ ]]; then
- info "VM is running in background"
- info "Connect later with: virsh console $DEBUG_VM_NAME"
-else
- info "Connecting to console..."
- info "Press Ctrl+] to disconnect from console"
+# Wait for QEMU to exit (user closes GTK window)
+step "Waiting for VM to exit..."
+while vm_is_running; do
sleep 2
- virsh console "$DEBUG_VM_NAME"
+done
+success "VM has stopped"
+
+# Clean up overlay disk
+if [ -n "$OVERLAY_DISK" ] && [ -f "$OVERLAY_DISK" ]; then
+ step "Removing overlay disk"
+ rm -f "$OVERLAY_DISK"
+ success "Overlay cleaned up"
fi
+
+_cleanup_qemu_files
+info "Debug session complete"
diff --git a/scripts/testing/finalize-base-vm.sh b/scripts/testing/finalize-base-vm.sh
deleted file mode 100755
index 225ffae..0000000
--- a/scripts/testing/finalize-base-vm.sh
+++ /dev/null
@@ -1,31 +0,0 @@
-#!/bin/bash
-# Finalize base VM after installation
-VM_NAME="archsetup-base"
-SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
-PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
-BASE_DISK="$PROJECT_ROOT/vm-images/archsetup-base.qcow2"
-
-echo "[i] Removing ISO from VM..."
-virsh --connect qemu:///system change-media $VM_NAME sda --eject 2>/dev/null || true
-virsh --connect qemu:///system change-media $VM_NAME hda --eject 2>/dev/null || true
-echo "[✓] ISO removed"
-
-echo "[i] Fixing base disk permissions..."
-sudo chown $USER:$USER "$BASE_DISK"
-sudo chmod 644 "$BASE_DISK"
-echo "[✓] Permissions fixed"
-
-echo "[i] Starting VM from installed system..."
-virsh --connect qemu:///system start $VM_NAME
-echo "[i] Waiting for boot..."
-sleep 30
-IP=$(virsh --connect qemu:///system domifaddr $VM_NAME 2>/dev/null | grep -oP '(\d+\.){3}\d+' | head -1)
-echo "[✓] Base VM is ready!"
-echo ""
-echo "Connect via:"
-echo " Console: virsh console $VM_NAME"
-echo " SSH: ssh root@$IP"
-echo " Password: archsetup"
-echo ""
-echo "To create a test clone:"
-echo " ./scripts/testing/run-test.sh"
diff --git a/scripts/testing/lib/network-diagnostics.sh b/scripts/testing/lib/network-diagnostics.sh
index 3f9735b..f7cae11 100644
--- a/scripts/testing/lib/network-diagnostics.sh
+++ b/scripts/testing/lib/network-diagnostics.sh
@@ -3,27 +3,30 @@
# Author: Craig Jennings <craigmartinjennings@gmail.com>
# License: GNU GPLv3
-# Note: logging.sh should already be sourced by the calling script
+# Note: logging.sh and vm-utils.sh should already be sourced by the calling script
+# Uses globals: ROOT_PASSWORD, SSH_PORT, SSH_OPTS, VM_IP (from vm-utils.sh or calling script)
# Run quick network diagnostics
-# Args: $1 = VM IP address or hostname
run_network_diagnostics() {
- local vm_host="$1"
+ local password="${ROOT_PASSWORD:-archsetup}"
+ local port="${SSH_PORT:-22}"
+ local host="${VM_IP:-localhost}"
+ local ssh_base="sshpass -p $password ssh $SSH_OPTS -p $port root@$host"
section "Pre-flight Network Diagnostics"
- # Test 1: Basic connectivity
+ # Test 1: Basic connectivity (use curl instead of ping - SLIRP may not handle ICMP)
step "Testing internet connectivity"
- if sshpass -p 'archsetup' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@$vm_host "ping -c 3 8.8.8.8 >/dev/null 2>&1"; then
+ if $ssh_base "curl -s --connect-timeout 5 -o /dev/null http://archlinux.org" 2>/dev/null; then
success "Internet connectivity OK"
else
error "No internet connectivity"
return 1
fi
- # Test 2: DNS resolution
+ # Test 2: DNS resolution (use getent which is always available, unlike nslookup/dig)
step "Testing DNS resolution"
- if sshpass -p 'archsetup' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@$vm_host "nslookup archlinux.org >/dev/null 2>&1"; then
+ if $ssh_base "getent hosts archlinux.org >/dev/null 2>&1" 2>/dev/null; then
success "DNS resolution OK"
else
error "DNS resolution failed"
@@ -32,7 +35,7 @@ run_network_diagnostics() {
# Test 3: Arch mirror accessibility
step "Testing Arch mirror access"
- if sshpass -p 'archsetup' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@$vm_host "curl -s -I https://mirrors.kernel.org/archlinux/ | head -1 | grep -qE '(200|301)'"; then
+ if $ssh_base "curl -s -I https://mirrors.kernel.org/archlinux/ | head -1 | grep -qE '(200|301)'" 2>/dev/null; then
success "Arch mirrors accessible"
else
error "Cannot reach Arch mirrors"
@@ -41,7 +44,7 @@ run_network_diagnostics() {
# Test 4: AUR accessibility
step "Testing AUR access"
- if sshpass -p 'archsetup' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@$vm_host "curl -s -I https://aur.archlinux.org/ | head -1 | grep -qE '(200|405)'"; then
+ if $ssh_base "curl -s -I https://aur.archlinux.org/ | head -1 | grep -qE '(200|405)'" 2>/dev/null; then
success "AUR accessible"
else
error "Cannot reach AUR"
@@ -50,8 +53,7 @@ run_network_diagnostics() {
# Show network info
info "Network configuration:"
- sshpass -p 'archsetup' ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@$vm_host \
- "ip addr show | grep 'inet ' | grep -v '127.0.0.1'" 2>/dev/null | while read line; do
+ $ssh_base "ip addr show | grep 'inet ' | grep -v '127.0.0.1'" 2>/dev/null | while read line; do
info " $line"
done
diff --git a/scripts/testing/lib/validation.sh b/scripts/testing/lib/validation.sh
index 8c4787e..3191c64 100644
--- a/scripts/testing/lib/validation.sh
+++ b/scripts/testing/lib/validation.sh
@@ -20,7 +20,7 @@ declare -a UNKNOWN_ISSUES
# SSH helper (uses globals: VM_IP, ROOT_PASSWORD)
ssh_cmd() {
sshpass -p "$ROOT_PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
- -o ConnectTimeout=10 "root@$VM_IP" "$@" 2>/dev/null
+ -o ConnectTimeout=10 -p "${SSH_PORT:-22}" "root@$VM_IP" "$@" 2>/dev/null
}
# Validation result helpers
@@ -458,17 +458,12 @@ validate_hyprland_tools() {
local missing=""
# Check core Hyprland packages
- for pkg in hyprland hypridle hyprlock waybar wofi swww grim slurp gammastep; do
+ for pkg in hyprland hypridle hyprlock waybar fuzzel swww grim slurp gammastep foot; do
if ! ssh_cmd "pacman -Q $pkg &>/dev/null"; then
missing="$missing $pkg"
fi
done
- # st should still be installed (via XWayland)
- if ! ssh_cmd "test -f /usr/local/bin/st"; then
- missing="$missing st"
- fi
-
if [ -z "$missing" ]; then
validation_pass "All Hyprland tools installed"
else
@@ -483,7 +478,7 @@ validate_hyprland_config() {
for config in ".config/hypr/hyprland.conf" ".config/hypr/hypridle.conf" \
".config/hypr/hyprlock.conf" ".config/waybar/config" \
- ".config/wofi/config" ".config/gammastep/config.ini"; do
+ ".config/fuzzel/fuzzel.ini" ".config/gammastep/config.ini"; do
if ! ssh_cmd "test -f /home/cjennings/$config"; then
missing="$missing $config"
fi
diff --git a/scripts/testing/lib/vm-utils.sh b/scripts/testing/lib/vm-utils.sh
index 81aec33..47bd391 100755
--- a/scripts/testing/lib/vm-utils.sh
+++ b/scripts/testing/lib/vm-utils.sh
@@ -1,39 +1,52 @@
#!/bin/bash
-# VM management utilities for archsetup testing
+# VM management utilities for archsetup testing (direct QEMU)
# Author: Craig Jennings <craigmartinjennings@gmail.com>
# License: GNU GPLv3
+#
+# Manages QEMU VMs directly without libvirt. Uses user-mode networking
+# with port forwarding for SSH access and qemu-img for snapshots.
# Note: logging.sh should already be sourced by the calling script
# VM configuration defaults
VM_CPUS="${VM_CPUS:-4}"
-VM_RAM="${VM_RAM:-8192}" # MB
-VM_DISK="${VM_DISK:-50}" # GB
-VM_NETWORK="${VM_NETWORK:-default}"
-LIBVIRT_URI="qemu:///system" # Use system session, not user session
-
-# Check if libvirt is running
-check_libvirt() {
- if ! systemctl is-active --quiet libvirtd; then
- error "libvirtd service is not running"
- info "Start it with: sudo systemctl start libvirtd"
- return 1
- fi
- return 0
+VM_RAM="${VM_RAM:-4096}" # MB
+VM_DISK_SIZE="${VM_DISK_SIZE:-50}" # GB
+
+# SSH configuration
+SSH_PORT="${SSH_PORT:-2222}"
+SSH_OPTS="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=10"
+ROOT_PASSWORD="${ROOT_PASSWORD:-archsetup}"
+
+# OVMF firmware paths
+OVMF_CODE="/usr/share/edk2/x64/OVMF_CODE.4m.fd"
+OVMF_VARS_TEMPLATE="/usr/share/edk2/x64/OVMF_VARS.4m.fd"
+
+# VM runtime paths (set by init_vm_paths)
+VM_IMAGES_DIR=""
+DISK_PATH=""
+OVMF_VARS=""
+PID_FILE=""
+MONITOR_SOCK=""
+SERIAL_LOG=""
+
+# Initialize all VM paths from images directory
+# Must be called before any other vm-utils function
+init_vm_paths() {
+ local images_dir="${1:-$VM_IMAGES_DIR}"
+ [ -z "$images_dir" ] && fatal "VM_IMAGES_DIR not set"
+
+ VM_IMAGES_DIR="$images_dir"
+ DISK_PATH="$VM_IMAGES_DIR/archsetup-base.qcow2"
+ OVMF_VARS="$VM_IMAGES_DIR/OVMF_VARS.fd"
+ PID_FILE="$VM_IMAGES_DIR/qemu.pid"
+ MONITOR_SOCK="$VM_IMAGES_DIR/qemu-monitor.sock"
+ SERIAL_LOG="$VM_IMAGES_DIR/qemu-serial.log"
+ mkdir -p "$VM_IMAGES_DIR"
}
-# Check if user is in libvirt group
-check_libvirt_group() {
- if ! groups | grep -q libvirt; then
- warn "Current user is not in libvirt group"
- info "Add yourself with: sudo usermod -a -G libvirt $USER"
- info "Then log out and back in for changes to take effect"
- return 1
- fi
- return 0
-}
+# ─── Prerequisite Checks ─────────────────────────────────────────────
-# Check if KVM is available
check_kvm() {
if [ ! -e /dev/kvm ]; then
error "KVM is not available"
@@ -44,249 +57,311 @@ check_kvm() {
return 0
}
-# Wait for VM to boot (check for SSH or serial console)
-wait_for_vm() {
- local vm_name="$1"
- local timeout="${2:-300}" # 5 minutes default
- local elapsed=0
+check_qemu() {
+ if ! command -v qemu-system-x86_64 &>/dev/null; then
+ error "qemu-system-x86_64 not found"
+ info "Install with: sudo pacman -S qemu-full"
+ return 1
+ fi
+ return 0
+}
- progress "Waiting for VM $vm_name to boot..."
+check_ovmf() {
+ if [ ! -f "$OVMF_CODE" ]; then
+ error "OVMF firmware not found: $OVMF_CODE"
+ info "Install with: sudo pacman -S edk2-ovmf"
+ return 1
+ fi
+ return 0
+}
- while [ $elapsed -lt $timeout ]; do
- if virsh --connect "$LIBVIRT_URI" domstate "$vm_name" 2>/dev/null | grep -q "running"; then
- sleep 5
- complete "VM $vm_name is running"
- return 0
- fi
- sleep 2
- elapsed=$((elapsed + 2))
- done
+check_sshpass() {
+ if ! command -v sshpass &>/dev/null; then
+ error "sshpass not found"
+ info "Install with: sudo pacman -S sshpass"
+ return 1
+ fi
+ return 0
+}
- error "Timeout waiting for VM $vm_name to boot"
- return 1
+check_socat() {
+ if ! command -v socat &>/dev/null; then
+ error "socat not found"
+ info "Install with: sudo pacman -S socat"
+ return 1
+ fi
+ return 0
}
-# Check if VM exists
-vm_exists() {
- local vm_name="$1"
- virsh --connect "$LIBVIRT_URI" dominfo "$vm_name" &>/dev/null
- return $?
+check_prerequisites() {
+ local failed=0
+ check_kvm || failed=1
+ check_qemu || failed=1
+ check_ovmf || failed=1
+ check_sshpass || failed=1
+ check_socat || failed=1
+ return $failed
}
-# Check if VM is running
+# ─── VM Lifecycle ─────────────────────────────────────────────────────
+
+# Check if a QEMU VM is running via PID file
vm_is_running() {
- local vm_name="$1"
- [ "$(virsh --connect "$LIBVIRT_URI" domstate "$vm_name" 2>/dev/null)" = "running" ]
- return $?
-}
+ [ -f "$PID_FILE" ] || return 1
-# Start VM
-start_vm() {
- local vm_name="$1"
+ local pid
+ pid=$(cat "$PID_FILE" 2>/dev/null) || return 1
- if vm_is_running "$vm_name"; then
- warn "VM $vm_name is already running"
+ if kill -0 "$pid" 2>/dev/null && grep -q "qemu" "/proc/$pid/cmdline" 2>/dev/null; then
return 0
fi
- step "Starting VM: $vm_name"
- if virsh --connect "$LIBVIRT_URI" start "$vm_name" >> "$LOGFILE" 2>&1; then
- success "VM $vm_name started"
- return 0
- else
- error "Failed to start VM $vm_name"
+ # Stale PID file
+ rm -f "$PID_FILE"
+ return 1
+}
+
+# Start a QEMU VM
+# Args: $1 = disk path
+# $2 = boot mode: "iso" or "disk" (default: disk)
+# $3 = ISO path (required if mode=iso)
+# $4 = display: "none" (headless) or "gtk" (graphical, default: none)
+start_qemu() {
+ local disk="$1"
+ local mode="${2:-disk}"
+ local iso_path="${3:-}"
+ local display="${4:-none}"
+
+ # Stop any existing instance
+ stop_qemu 2>/dev/null || true
+
+ # Check port availability
+ if ss -tln 2>/dev/null | grep -q ":${SSH_PORT} "; then
+ error "Port $SSH_PORT is already in use"
+ info "Another QEMU instance or service may be running"
return 1
fi
-}
-# Stop VM gracefully
-stop_vm() {
- local vm_name="$1"
- local timeout="${2:-60}"
+ # Ensure OVMF_VARS exists
+ if [ ! -f "$OVMF_VARS" ]; then
+ cp "$OVMF_VARS_TEMPLATE" "$OVMF_VARS"
+ fi
+
+ # Truncate serial log
+ : > "$SERIAL_LOG"
+
+ # Build QEMU command
+ local qemu_cmd=(
+ qemu-system-x86_64
+ -name "archsetup-test"
+ -machine "q35,accel=kvm"
+ -cpu host
+ -smp "$VM_CPUS"
+ -m "$VM_RAM"
+ -drive "if=pflash,format=raw,readonly=on,file=$OVMF_CODE"
+ -drive "if=pflash,format=raw,file=$OVMF_VARS"
+ -drive "file=$disk,format=qcow2,if=virtio"
+ -netdev "user,id=net0,hostfwd=tcp::${SSH_PORT}-:22"
+ -device "virtio-net-pci,netdev=net0"
+ -monitor "unix:$MONITOR_SOCK,server,nowait"
+ -pidfile "$PID_FILE"
+ -serial "file:$SERIAL_LOG"
+ -usb
+ -device usb-tablet
+ )
+
+ # Boot mode
+ if [ "$mode" = "iso" ]; then
+ [ -z "$iso_path" ] && fatal "ISO path required for iso boot mode"
+ qemu_cmd+=(-cdrom "$iso_path" -boot d)
+ else
+ qemu_cmd+=(-boot c)
+ fi
- if ! vm_is_running "$vm_name"; then
- info "VM $vm_name is not running"
- return 0
+ # Display mode
+ if [ "$display" = "gtk" ]; then
+ qemu_cmd+=(-device virtio-vga-gl -display "gtk,gl=on")
+ else
+ qemu_cmd+=(-display none)
fi
- step "Shutting down VM: $vm_name"
- if virsh --connect "$LIBVIRT_URI" shutdown "$vm_name" >> "$LOGFILE" 2>&1; then
- # Wait for graceful shutdown
- local elapsed=0
- while [ $elapsed -lt $timeout ]; do
- if ! vm_is_running "$vm_name"; then
- success "VM $vm_name stopped gracefully"
- return 0
- fi
- sleep 2
- elapsed=$((elapsed + 2))
- done
-
- warn "VM $vm_name did not stop gracefully, forcing..."
- virsh --connect "$LIBVIRT_URI" destroy "$vm_name" >> "$LOGFILE" 2>&1
+ step "Starting QEMU (mode=$mode, display=$display)"
+
+ # Launch in background
+ "${qemu_cmd[@]}" &>> "$LOGFILE" &
+
+ # Wait for PID file to appear
+ local wait=0
+ while [ ! -f "$PID_FILE" ] && [ $wait -lt 10 ]; do
+ sleep 1
+ wait=$((wait + 1))
+ done
+
+ if ! vm_is_running; then
+ error "QEMU failed to start"
+ return 1
fi
- success "VM $vm_name stopped"
+ success "QEMU started (PID: $(cat "$PID_FILE"))"
return 0
}
-# Destroy VM (force stop)
-destroy_vm() {
- local vm_name="$1"
+# Stop VM gracefully via ACPI powerdown, fallback to kill
+stop_qemu() {
+ local timeout="${1:-60}"
- if ! vm_exists "$vm_name"; then
- info "VM $vm_name does not exist"
+ if ! vm_is_running; then
return 0
fi
- step "Destroying VM: $vm_name"
- if vm_is_running "$vm_name"; then
- virsh --connect "$LIBVIRT_URI" destroy "$vm_name" >> "$LOGFILE" 2>&1
+ step "Sending shutdown signal to VM"
+
+ # Send ACPI powerdown via monitor socket
+ if [ -S "$MONITOR_SOCK" ]; then
+ echo "system_powerdown" | socat - "UNIX-CONNECT:$MONITOR_SOCK" >> "$LOGFILE" 2>&1 || true
fi
- virsh --connect "$LIBVIRT_URI" undefine "$vm_name" --nvram >> "$LOGFILE" 2>&1
- success "VM $vm_name destroyed"
+ # Wait for graceful shutdown
+ local elapsed=0
+ while [ $elapsed -lt $timeout ]; do
+ if ! vm_is_running; then
+ success "VM stopped gracefully"
+ _cleanup_qemu_files
+ return 0
+ fi
+ sleep 2
+ elapsed=$((elapsed + 2))
+ done
+
+ # Force kill
+ warn "VM did not stop gracefully after ${timeout}s, force killing"
+ kill_qemu
return 0
}
-# Create snapshot
+# Force kill VM immediately
+kill_qemu() {
+ if [ -f "$PID_FILE" ]; then
+ local pid
+ pid=$(cat "$PID_FILE" 2>/dev/null)
+ if [ -n "$pid" ]; then
+ kill -9 "$pid" 2>/dev/null || true
+ fi
+ fi
+ _cleanup_qemu_files
+}
+
+# Clean up runtime files
+_cleanup_qemu_files() {
+ rm -f "$PID_FILE" "$MONITOR_SOCK"
+}
+
+# ─── Snapshot Operations (qemu-img) ──────────────────────────────────
+# All snapshot operations require the VM to be stopped.
+
create_snapshot() {
- local vm_name="$1"
- local snapshot_name="$2"
+ local disk="${1:-$DISK_PATH}"
+ local snapshot_name="${2:-clean-install}"
+
+ if vm_is_running; then
+ error "Cannot create snapshot while VM is running"
+ return 1
+ fi
step "Creating snapshot: $snapshot_name"
- if virsh --connect "$LIBVIRT_URI" snapshot-create-as "$vm_name" "$snapshot_name" >> "$LOGFILE" 2>&1; then
- success "Snapshot $snapshot_name created"
+ if qemu-img snapshot -c "$snapshot_name" "$disk" >> "$LOGFILE" 2>&1; then
+ success "Snapshot '$snapshot_name' created"
return 0
else
- error "Failed to create snapshot $snapshot_name"
+ error "Failed to create snapshot '$snapshot_name'"
return 1
fi
}
-# Restore snapshot
restore_snapshot() {
- local vm_name="$1"
- local snapshot_name="$2"
+ local disk="${1:-$DISK_PATH}"
+ local snapshot_name="${2:-clean-install}"
+
+ if vm_is_running; then
+ error "Cannot restore snapshot while VM is running"
+ return 1
+ fi
step "Restoring snapshot: $snapshot_name"
- if virsh --connect "$LIBVIRT_URI" snapshot-revert "$vm_name" "$snapshot_name" >> "$LOGFILE" 2>&1; then
- success "Snapshot $snapshot_name restored"
+ if qemu-img snapshot -a "$snapshot_name" "$disk" >> "$LOGFILE" 2>&1; then
+ success "Snapshot '$snapshot_name' restored"
return 0
else
- error "Failed to restore snapshot $snapshot_name"
+ error "Failed to restore snapshot '$snapshot_name'"
return 1
fi
}
-# Delete snapshot
delete_snapshot() {
- local vm_name="$1"
- local snapshot_name="$2"
+ local disk="${1:-$DISK_PATH}"
+ local snapshot_name="${2:-clean-install}"
step "Deleting snapshot: $snapshot_name"
- if virsh --connect "$LIBVIRT_URI" snapshot-delete "$vm_name" "$snapshot_name" >> "$LOGFILE" 2>&1; then
- success "Snapshot $snapshot_name deleted"
+ if qemu-img snapshot -d "$snapshot_name" "$disk" >> "$LOGFILE" 2>&1; then
+ success "Snapshot '$snapshot_name' deleted"
return 0
else
- error "Failed to delete snapshot $snapshot_name"
+ error "Failed to delete snapshot '$snapshot_name'"
return 1
fi
}
-# Clone disk image (copy-on-write)
-clone_disk() {
- local base_image="$1"
- local new_image="$2"
-
- if [ ! -f "$base_image" ]; then
- error "Base image not found: $base_image"
- return 1
- fi
-
- step "Cloning disk image (full copy)"
- if qemu-img convert -f qcow2 -O qcow2 "$base_image" "$new_image" >> "$LOGFILE" 2>&1; then
- success "Disk cloned: $new_image"
- else
- error "Failed to clone disk"
- return 1
- fi
-
- # Truncate machine-id so systemd generates a new one on boot (avoids DHCP conflicts)
- step "Clearing machine-id for unique network identity"
- if guestfish -a "$new_image" -i truncate /etc/machine-id >> "$LOGFILE" 2>&1; then
- success "Machine-ID cleared (will regenerate on boot)"
- return 0
- else
- warn "Failed to clear machine-ID (guestfish failed)"
- info "Network may conflict with base VM if both run simultaneously"
- return 0 # Don't fail the whole operation
- fi
+list_snapshots() {
+ local disk="${1:-$DISK_PATH}"
+ qemu-img snapshot -l "$disk" 2>/dev/null
}
-# Get VM IP address (requires guest agent or DHCP lease)
-get_vm_ip() {
- local vm_name="$1"
+snapshot_exists() {
+ local disk="${1:-$DISK_PATH}"
+ local snapshot_name="${2:-clean-install}"
+ qemu-img snapshot -l "$disk" 2>/dev/null | grep -q "$snapshot_name"
+}
- # Try guest agent first
- local ip
- ip=$(virsh --connect "$LIBVIRT_URI" domifaddr "$vm_name" 2>/dev/null | grep -oP '(\d+\.){3}\d+' | head -1)
+# ─── SSH Operations ───────────────────────────────────────────────────
- if [ -n "$ip" ]; then
- echo "$ip"
- return 0
- fi
-
- # Fall back to DHCP leases
- local mac
- mac=$(virsh --connect "$LIBVIRT_URI" domiflist "$vm_name" | grep -oP '([0-9a-f]{2}:){5}[0-9a-f]{2}' | head -1)
+# Wait for SSH to become available on localhost:$SSH_PORT
+wait_for_ssh() {
+ local password="${1:-$ROOT_PASSWORD}"
+ local timeout="${2:-120}"
+ local elapsed=0
- if [ -n "$mac" ]; then
- ip=$(grep "$mac" /var/lib/libvirt/dnsmasq/default.leases 2>/dev/null | awk '{print $3}')
- if [ -n "$ip" ]; then
- echo "$ip"
+ progress "Waiting for SSH on localhost:$SSH_PORT..."
+ while [ $elapsed -lt $timeout ]; do
+ if sshpass -p "$password" ssh $SSH_OPTS -p "$SSH_PORT" root@localhost true 2>/dev/null; then
+ success "SSH is available"
return 0
fi
- fi
+ sleep 5
+ elapsed=$((elapsed + 5))
+ done
+ error "SSH timeout after ${timeout}s"
return 1
}
# Execute command in VM via SSH
vm_exec() {
- local vm_name="$1"
+ local password="${1:-$ROOT_PASSWORD}"
shift
- local cmd="$*"
-
- local ip
- ip=$(get_vm_ip "$vm_name")
-
- if [ -z "$ip" ]; then
- error "Could not get IP address for VM $vm_name"
- return 1
- fi
-
- ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
- "root@$ip" "$cmd" 2>> "$LOGFILE"
+ sshpass -p "$password" ssh $SSH_OPTS \
+ -o ServerAliveInterval=30 -o ServerAliveCountMax=10 \
+ -p "$SSH_PORT" root@localhost "$@" 2>> "$LOGFILE"
}
# Copy file to VM
copy_to_vm() {
- local vm_name="$1"
- local local_file="$2"
- local remote_path="$3"
-
- local ip
- ip=$(get_vm_ip "$vm_name")
-
- if [ -z "$ip" ]; then
- error "Could not get IP address for VM $vm_name"
- return 1
- fi
+ local local_file="$1"
+ local remote_path="$2"
+ local password="${3:-$ROOT_PASSWORD}"
- step "Copying $local_file to VM"
- if scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
- "$local_file" "root@$ip:$remote_path" >> "$LOGFILE" 2>&1; then
+ step "Copying $(basename "$local_file") to VM:$remote_path"
+ if sshpass -p "$password" scp $SSH_OPTS -P "$SSH_PORT" \
+ "$local_file" "root@localhost:$remote_path" >> "$LOGFILE" 2>&1; then
success "File copied to VM"
return 0
else
@@ -297,21 +372,13 @@ copy_to_vm() {
# Copy file from VM
copy_from_vm() {
- local vm_name="$1"
- local remote_file="$2"
- local local_path="$3"
-
- local ip
- ip=$(get_vm_ip "$vm_name")
-
- if [ -z "$ip" ]; then
- error "Could not get IP address for VM $vm_name"
- return 1
- fi
+ local remote_file="$1"
+ local local_path="$2"
+ local password="${3:-$ROOT_PASSWORD}"
step "Copying $remote_file from VM"
- if scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
- "root@$ip:$remote_file" "$local_path" >> "$LOGFILE" 2>&1; then
+ if sshpass -p "$password" scp $SSH_OPTS -P "$SSH_PORT" \
+ "root@localhost:$remote_file" "$local_path" >> "$LOGFILE" 2>&1; then
success "File copied from VM"
return 0
else
diff --git a/scripts/testing/run-test.sh b/scripts/testing/run-test.sh
index 4c41cc3..a600870 100755
--- a/scripts/testing/run-test.sh
+++ b/scripts/testing/run-test.sh
@@ -5,7 +5,7 @@
#
# This script:
# 1. Reverts base VM to clean snapshot
-# 2. Starts the base VM
+# 2. Boots the VM via QEMU
# 3. Transfers archsetup and dotfiles
# 4. Executes archsetup in the VM
# 5. Captures logs and validates results
@@ -55,15 +55,20 @@ done
# Configuration
TIMESTAMP=$(date +'%Y%m%d-%H%M%S')
-VM_NAME="archsetup-base"
+VM_IMAGES_DIR="$PROJECT_ROOT/vm-images"
TEST_RESULTS_DIR="$PROJECT_ROOT/test-results/$TIMESTAMP"
ROOT_PASSWORD="archsetup"
ARCHZFS_INBOX="$HOME/code/archzfs/inbox"
+ARCHSETUP_VM_CONF="$SCRIPT_DIR/archsetup-vm.conf"
-# Initialize logging
+# Set VM_IP for validation.sh backward compatibility
+VM_IP="localhost"
+
+# Initialize logging and VM paths
mkdir -p "$TEST_RESULTS_DIR"
LOGFILE="$TEST_RESULTS_DIR/test.log"
init_logging "$LOGFILE"
+init_vm_paths "$VM_IMAGES_DIR"
section "ArchSetup Test Run: $TIMESTAMP"
@@ -72,9 +77,9 @@ if [ ! -f "$ARCHSETUP_SCRIPT" ]; then
fatal "ArchSetup script not found: $ARCHSETUP_SCRIPT"
fi
-# Check if VM exists
-if ! vm_exists "$VM_NAME"; then
- fatal "Base VM not found: $VM_NAME"
+# Check disk exists
+if [ ! -f "$DISK_PATH" ]; then
+ fatal "Base disk not found: $DISK_PATH"
info "Create it first: ./scripts/testing/create-base-vm.sh"
fi
@@ -82,72 +87,34 @@ fi
section "Preparing Test Environment"
step "Checking for snapshot: $SNAPSHOT_NAME"
-if ! virsh --connect "$LIBVIRT_URI" snapshot-list "$VM_NAME" --name 2>/dev/null | grep -q "^$SNAPSHOT_NAME$"; then
- fatal "Snapshot '$SNAPSHOT_NAME' not found on VM $VM_NAME"
+if ! snapshot_exists "$DISK_PATH" "$SNAPSHOT_NAME"; then
+ fatal "Snapshot '$SNAPSHOT_NAME' not found on $DISK_PATH"
info "Available snapshots:"
- virsh --connect "$LIBVIRT_URI" snapshot-list "$VM_NAME" 2>/dev/null || info " (none)"
+ list_snapshots "$DISK_PATH"
info ""
- info "Create snapshot with:"
- info " virsh snapshot-create-as $VM_NAME $SNAPSHOT_NAME --description 'Clean Arch install'"
+ info "Create base VM with: ./scripts/testing/create-base-vm.sh"
fi
success "Snapshot $SNAPSHOT_NAME exists"
-# Shut down VM if running
-if vm_is_running "$VM_NAME"; then
- warn "VM $VM_NAME is currently running - shutting down for snapshot revert"
- stop_vm "$VM_NAME"
-fi
+# Stop VM if running and restore snapshot
+stop_qemu 2>/dev/null || true
-# Revert to clean snapshot
step "Reverting to snapshot: $SNAPSHOT_NAME"
-if restore_snapshot "$VM_NAME" "$SNAPSHOT_NAME"; then
+if restore_snapshot "$DISK_PATH" "$SNAPSHOT_NAME"; then
success "Reverted to clean state"
else
fatal "Failed to revert snapshot"
fi
-# Start VM
+# Start VM and wait for SSH
start_timer "boot"
step "Starting VM and waiting for SSH..."
-if ! start_vm "$VM_NAME"; then
- fatal "Failed to start VM"
-fi
-
-sleep 10 # Give VM time to boot
-
-# Get VM IP address
-VM_IP=""
-for i in {1..30}; do
- VM_IP=$(get_vm_ip "$VM_NAME" 2>/dev/null || true)
- if [ -n "$VM_IP" ]; then
- break
- fi
- sleep 2
-done
-
-if [ -z "$VM_IP" ]; then
- error "Could not get VM IP address"
- info "VM may not have booted correctly"
- fatal "VM boot failed"
-fi
-
-success "VM is running at $VM_IP"
+start_qemu "$DISK_PATH" "disk" "" "none" || fatal "Failed to start VM"
+wait_for_ssh "$ROOT_PASSWORD" 120 || fatal "VM SSH not available"
stop_timer "boot"
-# Wait for SSH
-step "Waiting for SSH to become available..."
-for i in {1..60}; do
- if sshpass -p "$ROOT_PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
- -o ConnectTimeout=2 "root@$VM_IP" "echo connected" &>/dev/null; then
- break
- fi
- sleep 2
-done
-
-success "SSH is available"
-
# Run network diagnostics
-if ! run_network_diagnostics "$VM_IP"; then
+if ! run_network_diagnostics; then
fatal "Network diagnostics failed - aborting test"
fi
@@ -165,39 +132,59 @@ BUNDLE_FILE=$(mktemp)
git bundle create "$BUNDLE_FILE" HEAD >> "$LOGFILE" 2>&1
# Transfer bundle and extract on VM
-sshpass -p "$ROOT_PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
- "root@$VM_IP" "rm -rf /tmp/archsetup-test && mkdir -p /tmp/archsetup-test" >> "$LOGFILE" 2>&1
+vm_exec "$ROOT_PASSWORD" "rm -rf /tmp/archsetup-test && mkdir -p /tmp/archsetup-test" >> "$LOGFILE" 2>&1
-sshpass -p "$ROOT_PASSWORD" scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
- "$BUNDLE_FILE" "root@$VM_IP:/tmp/archsetup.bundle" >> "$LOGFILE" 2>&1
+copy_to_vm "$BUNDLE_FILE" "/tmp/archsetup.bundle" "$ROOT_PASSWORD"
# Clone from bundle on VM (simulates git clone)
-sshpass -p "$ROOT_PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
- "root@$VM_IP" "cd /tmp && git clone --depth 1 /tmp/archsetup.bundle archsetup-test && rm /tmp/archsetup.bundle" >> "$LOGFILE" 2>&1
+vm_exec "$ROOT_PASSWORD" \
+ "cd /tmp && git clone --depth 1 /tmp/archsetup.bundle archsetup-test && rm /tmp/archsetup.bundle" \
+ >> "$LOGFILE" 2>&1
rm -f "$BUNDLE_FILE"
success "Repository cloned to VM (simulating git clone --depth 1)"
+# Transfer archsetup VM config file
+step "Copying archsetup VM config"
+copy_to_vm "$ARCHSETUP_VM_CONF" "/tmp/archsetup-test/archsetup-vm.conf" "$ROOT_PASSWORD"
+
+# Pre-archsetup VM setup
+section "Pre-ArchSetup VM Setup"
+
+step "Importing archzfs PGP key"
+vm_exec "$ROOT_PASSWORD" \
+ "curl -sL https://archzfs.com/archzfs.gpg | pacman-key --add - && pacman-key --lsign-key DDF7DB817396A49B2A2723F7403BD972F75D9D76" \
+ >> "$LOGFILE" 2>&1 && success "archzfs PGP key imported" || warn "Failed to import archzfs key (may already be present)"
+
+step "Syncing package databases"
+vm_exec "$ROOT_PASSWORD" "pacman -Sy --noconfirm" >> "$LOGFILE" 2>&1 && \
+ success "Package databases synced" || warn "Package database sync had issues"
+
# Execute archsetup
section "Executing ArchSetup"
start_timer "archsetup"
step "Starting archsetup script in detached session on VM..."
-info "This will take 30-60 minutes depending on network speed"
info "Log file: $LOGFILE"
-# Start archsetup in a detached session on the VM (resilient to SSH disconnections)
+# Start archsetup fully detached.
+# Use ssh -T -n to prevent PTY allocation and stdin forwarding, which allows
+# the SSH session to close immediately after the remote shell exits.
REMOTE_LOG="/tmp/archsetup-test/archsetup-output.log"
-sshpass -p "$ROOT_PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
- "root@$VM_IP" "cd /tmp/archsetup-test && nohup bash archsetup > $REMOTE_LOG 2>&1 & echo \$!" \
+
+sshpass -p "$ROOT_PASSWORD" ssh -T -n $SSH_OPTS \
+ -p "$SSH_PORT" root@localhost \
+ "setsid bash -c 'cd /tmp/archsetup-test && bash archsetup --config-file /tmp/archsetup-test/archsetup-vm.conf > $REMOTE_LOG 2>&1' < /dev/null > /dev/null 2>&1 &" \
>> "$LOGFILE" 2>&1
-if [ $? -ne 0 ]; then
- fatal "Failed to start archsetup on VM"
+# Verify the process started
+sleep 3
+if vm_exec "$ROOT_PASSWORD" "pgrep -f 'bash archsetup'" >> "$LOGFILE" 2>/dev/null; then
+ success "ArchSetup started in background on VM"
+else
+ fatal "ArchSetup process not found after launch"
fi
-success "ArchSetup started in background on VM"
-
# Poll for completion
step "Monitoring archsetup progress (polling every 30 seconds)..."
POLL_COUNT=0
@@ -205,9 +192,7 @@ MAX_POLLS=180 # 90 minutes max (180 * 30 seconds)
while [ $POLL_COUNT -lt $MAX_POLLS ]; do
# Check if archsetup process is still running
- # Use ps to avoid pgrep matching its own SSH command
- if sshpass -p "$ROOT_PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
- "root@$VM_IP" "ps aux | grep '[b]ash archsetup' > /dev/null" 2>/dev/null; then
+ if vm_exec "$ROOT_PASSWORD" "ps aux | grep '[b]ash archsetup' > /dev/null" 2>/dev/null; then
# Still running, wait and continue
sleep 30
POLL_COUNT=$((POLL_COUNT + 1))
@@ -229,8 +214,9 @@ if [ $POLL_COUNT -ge $MAX_POLLS ]; then
else
# Get exit code from the remote log
step "Retrieving archsetup exit status..."
- ARCHSETUP_EXIT_CODE=$(sshpass -p "$ROOT_PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
- "root@$VM_IP" "grep -q 'ARCHSETUP_EXECUTION_COMPLETE' /var/log/archsetup-*.log 2>/dev/null && echo 0 || echo 1" 2>/dev/null)
+ ARCHSETUP_EXIT_CODE=$(vm_exec "$ROOT_PASSWORD" \
+ "grep -q 'ARCHSETUP_EXECUTION_COMPLETE' /var/log/archsetup-*.log 2>/dev/null && echo 0 || echo 1" \
+ 2>/dev/null)
if [ "$ARCHSETUP_EXIT_CODE" = "0" ]; then
success "ArchSetup completed successfully"
@@ -241,8 +227,7 @@ fi
# Copy the remote output log
step "Retrieving archsetup output from VM..."
-sshpass -p "$ROOT_PASSWORD" scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
- "root@$VM_IP:$REMOTE_LOG" "$TEST_RESULTS_DIR/archsetup-output.log" 2>> "$LOGFILE" || \
+copy_from_vm "$REMOTE_LOG" "$TEST_RESULTS_DIR/archsetup-output.log" "$ROOT_PASSWORD" || \
warn "Could not copy remote output log"
# Append remote output to main test log
@@ -256,15 +241,17 @@ stop_timer "archsetup"
section "Capturing Test Artifacts"
step "Copying archsetup log from VM"
-sshpass -p "$ROOT_PASSWORD" scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
- "root@$VM_IP:/var/log/archsetup-*.log" "$TEST_RESULTS_DIR/" 2>> "$LOGFILE" || \
+copy_from_vm "/var/log/archsetup-*.log" "$TEST_RESULTS_DIR/" "$ROOT_PASSWORD" || \
warn "Could not copy archsetup log"
step "Copying package lists from VM"
-sshpass -p "$ROOT_PASSWORD" scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
- "root@$VM_IP:/root/.local/src/archsetup-*.txt" "$TEST_RESULTS_DIR/" 2>> "$LOGFILE" || \
+copy_from_vm "/var/log/archsetup-*-package-list.txt" "$TEST_RESULTS_DIR/" "$ROOT_PASSWORD" || \
warn "Could not copy package lists"
+step "Copying installed packages list"
+copy_from_vm "/var/log/archsetup-installed-packages.txt" "$TEST_RESULTS_DIR/" "$ROOT_PASSWORD" || \
+ warn "Could not copy installed packages list"
+
# Capture post-install state
capture_post_install_state "$TEST_RESULTS_DIR"
@@ -282,9 +269,9 @@ generate_issue_report "$TEST_RESULTS_DIR" "$ARCHZFS_INBOX"
# Set validation result based on failure count
if [ $VALIDATION_FAILED -eq 0 ]; then
- VALIDATION_PASSED=true
+ TEST_PASSED=true
else
- VALIDATION_PASSED=false
+ TEST_PASSED=false
fi
# Generate test report
@@ -298,16 +285,16 @@ ArchSetup Test Report
Test ID: $TIMESTAMP
Date: $(date +'%Y-%m-%d %H:%M:%S')
-Test Method: Snapshot-based
+Test Method: QEMU snapshot-based
VM Configuration:
- Name: $VM_NAME
- IP: $VM_IP
+ Disk: $DISK_PATH
Snapshot: $SNAPSHOT_NAME
+ SSH: localhost:$SSH_PORT
Results:
ArchSetup Exit Code: $ARCHSETUP_EXIT_CODE
- Validation: $(if $VALIDATION_PASSED; then echo "PASSED"; else echo "FAILED"; fi)
+ Validation: $(if $TEST_PASSED; then echo "PASSED"; else echo "FAILED"; fi)
Validation Summary:
Passed: $VALIDATION_PASSED
@@ -338,16 +325,14 @@ section "Cleanup"
if $KEEP_VM; then
info "VM is still running in post-test state (--keep flag was used)"
info "Connect with:"
- info " Console: virsh console $VM_NAME"
- info " SSH: ssh root@$VM_IP"
+ info " SSH: sshpass -p '$ROOT_PASSWORD' ssh -p $SSH_PORT root@localhost"
info ""
- info "To revert to clean state when done:"
- info " virsh shutdown $VM_NAME"
- info " virsh snapshot-revert $VM_NAME $SNAPSHOT_NAME"
+ info "To stop VM: kill \$(cat $PID_FILE)"
+ info "To revert: qemu-img snapshot -a $SNAPSHOT_NAME $DISK_PATH"
else
step "Shutting down VM and reverting to clean snapshot"
- stop_vm "$VM_NAME"
- if restore_snapshot "$VM_NAME" "$SNAPSHOT_NAME"; then
+ stop_qemu
+ if restore_snapshot "$DISK_PATH" "$SNAPSHOT_NAME"; then
success "VM reverted to clean state"
else
warn "Failed to revert snapshot - VM may be in modified state"
@@ -357,7 +342,7 @@ fi
# Final summary
section "Test Complete"
-if [ $ARCHSETUP_EXIT_CODE -eq 0 ] && $VALIDATION_PASSED; then
+if [ "$ARCHSETUP_EXIT_CODE" = "0" ] && $TEST_PASSED; then
success "TEST PASSED"
exit 0
else
diff --git a/scripts/testing/setup-testing-env.sh b/scripts/testing/setup-testing-env.sh
index e682553..f0e63aa 100755
--- a/scripts/testing/setup-testing-env.sh
+++ b/scripts/testing/setup-testing-env.sh
@@ -4,9 +4,7 @@
# License: GNU GPLv3
#
# This script performs one-time setup of the testing infrastructure:
-# - Installs QEMU/KVM and libvirt
-# - Configures libvirt networking
-# - Adds user to libvirt group
+# - Installs QEMU/KVM, sshpass, OVMF firmware, and socat
# - Verifies KVM support
# - Creates directories for test artifacts
@@ -20,6 +18,7 @@ PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
source "$SCRIPT_DIR/lib/logging.sh"
# Initialize logging
+mkdir -p "$PROJECT_ROOT/test-results"
LOGFILE="$PROJECT_ROOT/test-results/setup-$(date +'%Y%m%d-%H%M%S').log"
init_logging "$LOGFILE"
@@ -41,13 +40,9 @@ section "Installing Required Packages"
PACKAGES=(
qemu-full
- libvirt
- virt-manager
- dnsmasq
- bridge-utils
- iptables
- virt-install
- libguestfs
+ sshpass
+ edk2-ovmf
+ socat
)
for pkg in "${PACKAGES[@]}"; do
@@ -64,46 +59,6 @@ for pkg in "${PACKAGES[@]}"; do
fi
done
-# Enable and start libvirt service
-section "Configuring libvirt Service"
-
-step "Enabling libvirtd service"
-if sudo systemctl enable libvirtd.service >> "$LOGFILE" 2>&1; then
- success "libvirtd service enabled"
-else
- warn "Failed to enable libvirtd service (may already be enabled)"
-fi
-
-step "Starting libvirtd service"
-if sudo systemctl start libvirtd.service >> "$LOGFILE" 2>&1; then
- success "libvirtd service started"
-else
- if sudo systemctl is-active --quiet libvirtd.service; then
- info "libvirtd service is already running"
- else
- error "Failed to start libvirtd service"
- fatal "Service startup failed"
- fi
-fi
-
-# Add user to libvirt group
-section "Configuring User Permissions"
-
-if groups | grep -q libvirt; then
- success "User $USER is already in libvirt group"
-else
- step "Adding user $USER to libvirt group"
- if sudo usermod -a -G libvirt "$USER" >> "$LOGFILE" 2>&1; then
- success "User added to libvirt group"
- warn "You must log out and back in for group membership to take effect"
- warn "After logging back in, re-run this script to continue"
- exit 0
- else
- error "Failed to add user to libvirt group"
- fatal "User configuration failed"
- fi
-fi
-
# Verify KVM support
section "Verifying KVM Support"
@@ -126,6 +81,18 @@ else
info "Load with: sudo modprobe kvm-intel (or kvm-amd)"
fi
+# Verify OVMF firmware
+section "Verifying OVMF Firmware"
+
+OVMF_CODE="/usr/share/edk2/x64/OVMF_CODE.4m.fd"
+OVMF_VARS="/usr/share/edk2/x64/OVMF_VARS.4m.fd"
+
+if [ -f "$OVMF_CODE" ] && [ -f "$OVMF_VARS" ]; then
+ success "OVMF firmware files present"
+else
+ fatal "OVMF firmware files not found (expected at $OVMF_CODE)"
+fi
+
# Create directory structure
section "Creating Directory Structure"
@@ -147,45 +114,14 @@ for dir in "${DIRS[@]}"; do
fi
done
-# Configure default libvirt network
-section "Configuring libvirt Network"
-
-if virsh net-info default &>/dev/null; then
- info "Default network exists"
-
- if virsh net-info default | grep -q "Active:.*yes"; then
- success "Default network is active"
- else
- step "Starting default network"
- if virsh net-start default >> "$LOGFILE" 2>&1; then
- success "Default network started"
- else
- error "Failed to start default network"
- fi
- fi
-
- if virsh net-info default | grep -q "Autostart:.*yes"; then
- info "Default network autostart is enabled"
- else
- step "Enabling default network autostart"
- if virsh net-autostart default >> "$LOGFILE" 2>&1; then
- success "Default network autostart enabled"
- else
- warn "Failed to enable default network autostart"
- fi
- fi
-else
- error "Default network not found"
- info "This is unusual - libvirt should create it automatically"
-fi
-
# Summary
section "Setup Complete"
success "Testing environment is ready"
info ""
info "Next steps:"
-info " 1. Create base VM: ./scripts/testing/create-base-vm.sh"
-info " 2. Run a test: ./scripts/testing/run-test.sh"
+info " 1. Copy an archangel ISO to: $PROJECT_ROOT/vm-images/"
+info " 2. Create base VM: ./scripts/testing/create-base-vm.sh"
+info " 3. Run a test: ./scripts/testing/run-test.sh"
info ""
info "Log file: $LOGFILE"