summaryrefslogtreecommitdiff
path: root/scripts/testing/lib/vm-utils.sh
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2025-11-13 23:26:21 -0600
committerCraig Jennings <c@cjennings.net>2025-11-13 23:26:21 -0600
commit2e10a8856d0bdd4c8f77c53320221ad1b8deaa13 (patch)
tree95832c3b74fc523fe9d8319e25c5ea5bf1d40433 /scripts/testing/lib/vm-utils.sh
parentfd9cce59993556400b635256d712a65d87f5d72d (diff)
fix(archsetup): implement critical bug fixes and test improvements
This commit addresses several high-priority bugs and enhances the testing infrastructure: **Bug Fixes:** 1. Add root permission check at script start to fail fast with clear error message 2. Disable debug package installation by adding --nodebug flag to all yay calls 3. Replace unsafe `git pull --force` with safe rm + fresh clone to prevent data loss 4. Add geoclue package with correct systemd service configuration for geolocation 5. Add completion marker for reliable automated test detection **Testing Infrastructure:** - Add comprehensive VM-based testing framework in scripts/testing/ - Fix test script pgrep infinite loop using grep bracket self-exclusion pattern - Add network diagnostics and pre-flight checks - Support snapshot-based testing for reproducible test runs **Package Management:** - Remove anki (build hangs 98+ minutes) - Remove adwaita-color-schemes (CMake build issues) Test Results: 0 errors, 1,363 packages installed in 40 minutes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Diffstat (limited to 'scripts/testing/lib/vm-utils.sh')
-rwxr-xr-xscripts/testing/lib/vm-utils.sh321
1 files changed, 321 insertions, 0 deletions
diff --git a/scripts/testing/lib/vm-utils.sh b/scripts/testing/lib/vm-utils.sh
new file mode 100755
index 0000000..81aec33
--- /dev/null
+++ b/scripts/testing/lib/vm-utils.sh
@@ -0,0 +1,321 @@
+#!/bin/bash
+# VM management utilities for archsetup testing
+# Author: Craig Jennings <craigmartinjennings@gmail.com>
+# License: GNU GPLv3
+
+# 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
+}
+
+# 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
+}
+
+# Check if KVM is available
+check_kvm() {
+ if [ ! -e /dev/kvm ]; then
+ error "KVM is not available"
+ info "Check if virtualization is enabled in BIOS"
+ info "Load kvm module: sudo modprobe kvm-intel (or kvm-amd)"
+ return 1
+ fi
+ 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
+
+ progress "Waiting for VM $vm_name to boot..."
+
+ 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
+
+ error "Timeout waiting for VM $vm_name to boot"
+ return 1
+}
+
+# Check if VM exists
+vm_exists() {
+ local vm_name="$1"
+ virsh --connect "$LIBVIRT_URI" dominfo "$vm_name" &>/dev/null
+ return $?
+}
+
+# Check if VM is running
+vm_is_running() {
+ local vm_name="$1"
+ [ "$(virsh --connect "$LIBVIRT_URI" domstate "$vm_name" 2>/dev/null)" = "running" ]
+ return $?
+}
+
+# Start VM
+start_vm() {
+ local vm_name="$1"
+
+ if vm_is_running "$vm_name"; then
+ warn "VM $vm_name is already running"
+ 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"
+ return 1
+ fi
+}
+
+# Stop VM gracefully
+stop_vm() {
+ local vm_name="$1"
+ local timeout="${2:-60}"
+
+ if ! vm_is_running "$vm_name"; then
+ info "VM $vm_name is not running"
+ return 0
+ 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
+ fi
+
+ success "VM $vm_name stopped"
+ return 0
+}
+
+# Destroy VM (force stop)
+destroy_vm() {
+ local vm_name="$1"
+
+ if ! vm_exists "$vm_name"; then
+ info "VM $vm_name does not exist"
+ 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
+ fi
+
+ virsh --connect "$LIBVIRT_URI" undefine "$vm_name" --nvram >> "$LOGFILE" 2>&1
+ success "VM $vm_name destroyed"
+ return 0
+}
+
+# Create snapshot
+create_snapshot() {
+ local vm_name="$1"
+ local snapshot_name="$2"
+
+ 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"
+ return 0
+ else
+ error "Failed to create snapshot $snapshot_name"
+ return 1
+ fi
+}
+
+# Restore snapshot
+restore_snapshot() {
+ local vm_name="$1"
+ local snapshot_name="$2"
+
+ 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"
+ return 0
+ else
+ error "Failed to restore snapshot $snapshot_name"
+ return 1
+ fi
+}
+
+# Delete snapshot
+delete_snapshot() {
+ local vm_name="$1"
+ local snapshot_name="$2"
+
+ 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"
+ return 0
+ else
+ 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
+}
+
+# Get VM IP address (requires guest agent or DHCP lease)
+get_vm_ip() {
+ local vm_name="$1"
+
+ # Try guest agent first
+ local ip
+ ip=$(virsh --connect "$LIBVIRT_URI" domifaddr "$vm_name" 2>/dev/null | grep -oP '(\d+\.){3}\d+' | head -1)
+
+ 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)
+
+ 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"
+ return 0
+ fi
+ fi
+
+ return 1
+}
+
+# Execute command in VM via SSH
+vm_exec() {
+ local vm_name="$1"
+ 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"
+}
+
+# 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
+
+ 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
+ success "File copied to VM"
+ return 0
+ else
+ error "Failed to copy file to VM"
+ return 1
+ fi
+}
+
+# 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
+
+ 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
+ success "File copied from VM"
+ return 0
+ else
+ error "Failed to copy file from VM"
+ return 1
+ fi
+}