#!/usr/bin/env bash set -euo pipefail # --- Configuration --- LAB_DIR="/var/lib/harmony_pxe_test" IMG_DIR="${LAB_DIR}/images" STATE_DIR="${LAB_DIR}/state" VM_OPN="opnsense-pxe" VM_PXE="pxe-node-1" NET_HARMONYLAN="harmonylan" # Network settings for the isolated LAN VLAN_CIDR="192.168.150.0/24" VLAN_GW="192.168.150.1" VLAN_MASK="255.255.255.0" # VM Specifications RAM_OPN="2048" VCPUS_OPN="2" DISK_OPN_GB="10" OS_VARIANT_OPN="freebsd14.0" # Updated to a more recent FreeBSD variant RAM_PXE="4096" VCPUS_PXE="2" DISK_PXE_GB="40" OS_VARIANT_LINUX="centos-stream9" OPN_IMG_URL="https://mirror.ams1.nl.leaseweb.net/opnsense/releases/25.7/OPNsense-25.7-serial-amd64.img.bz2" OPN_IMG_PATH="${IMG_DIR}/OPNsense-25.7-serial-amd64.img" CENTOS_ISO_URL="https://mirror.stream.centos.org/9-stream/BaseOS/x86_64/os/images/boot.iso" CENTOS_ISO_PATH="${IMG_DIR}/CentOS-Stream-9-latest-boot.iso" CONNECT_URI="qemu:///system" download_if_missing() { local url="$1" local dest="$2" if [[ ! -f "$dest" ]]; then echo "Downloading $url to $dest" mkdir -p "$(dirname "$dest")" local tmp tmp="$(mktemp)" curl -L --progress-bar "$url" -o "$tmp" case "$url" in *.bz2) bunzip2 -c "$tmp" > "$dest" && rm -f "$tmp" ;; *) mv "$tmp" "$dest" ;; esac else echo "Already present: $dest" fi } # Ensures a libvirt network is defined and active ensure_network() { local net_name="$1" local net_xml_path="$2" if virsh --connect "${CONNECT_URI}" net-info "${net_name}" >/dev/null 2>&1; then echo "Network ${net_name} already exists." else echo "Defining network ${net_name} from ${net_xml_path}" virsh --connect "${CONNECT_URI}" net-define "${net_xml_path}" fi if ! virsh --connect "${CONNECT_URI}" net-info "${net_name}" | grep "Active: *yes"; then echo "Starting network ${net_name}..." virsh --connect "${CONNECT_URI}" net-start "${net_name}" virsh --connect "${CONNECT_URI}" net-autostart "${net_name}" fi } # Destroys a VM completely destroy_vm() { local vm_name="$1" if virsh --connect "${CONNECT_URI}" dominfo "$vm_name" >/dev/null 2>&1; then echo "Destroying and undefining VM: ${vm_name}" virsh --connect "${CONNECT_URI}" destroy "$vm_name" || true virsh --connect "${CONNECT_URI}" undefine "$vm_name" --nvram fi } # Destroys a libvirt network destroy_network() { local net_name="$1" if virsh --connect "${CONNECT_URI}" net-info "$net_name" >/dev/null 2>&1; then echo "Destroying and undefining network: ${net_name}" virsh --connect "${CONNECT_URI}" net-destroy "$net_name" || true virsh --connect "${CONNECT_URI}" net-undefine "$net_name" fi } # --- Main Logic --- create_lab_environment() { # Create network definition files cat > "${STATE_DIR}/default.xml" < default EOF cat > "${STATE_DIR}/${NET_HARMONYLAN}.xml" < ${NET_HARMONYLAN} EOF # Ensure both networks exist and are active ensure_network "default" "${STATE_DIR}/default.xml" ensure_network "${NET_HARMONYLAN}" "${STATE_DIR}/${NET_HARMONYLAN}.xml" # --- Create OPNsense VM (MODIFIED SECTION) --- local disk_opn="${IMG_DIR}/${VM_OPN}.qcow2" if [[ ! -f "$disk_opn" ]]; then qemu-img create -f qcow2 "$disk_opn" "${DISK_OPN_GB}G" fi echo "Creating OPNsense VM using serial image..." virt-install \ --connect "${CONNECT_URI}" \ --name "${VM_OPN}" \ --ram "${RAM_OPN}" \ --vcpus "${VCPUS_OPN}" \ --cpu host-passthrough \ --os-variant "${OS_VARIANT_OPN}" \ --graphics none \ --noautoconsole \ --disk path="${disk_opn}",device=disk,bus=virtio,boot.order=1 \ --disk path="${OPN_IMG_PATH}",device=disk,bus=usb,readonly=on,boot.order=2 \ --network network=default,model=virtio \ --network network="${NET_HARMONYLAN}",model=virtio \ --boot uefi,menu=on echo "OPNsense VM created. Connect with: sudo virsh console ${VM_OPN}" echo "The VM will boot from the serial installation image." echo "Login with user 'installer' and password 'opnsense' to start the installation." echo "Install onto the VirtIO disk (vtbd0)." echo "After installation, shutdown the VM, then run 'sudo virsh edit ${VM_OPN}' and remove the USB disk block to boot from the installed system." # --- Create PXE Client VM --- local disk_pxe="${IMG_DIR}/${VM_PXE}.qcow2" if [[ ! -f "$disk_pxe" ]]; then qemu-img create -f qcow2 "$disk_pxe" "${DISK_PXE_GB}G" fi echo "Creating PXE client VM..." virt-install \ --connect "${CONNECT_URI}" \ --name "${VM_PXE}" \ --ram "${RAM_PXE}" \ --vcpus "${VCPUS_PXE}" \ --cpu host-passthrough \ --os-variant "${OS_VARIANT_LINUX}" \ --graphics none \ --noautoconsole \ --disk path="${disk_pxe}",format=qcow2,bus=virtio \ --network network="${NET_HARMONYLAN}",model=virtio \ --pxe \ --boot uefi,menu=on echo "PXE VM created. It will attempt to netboot on ${NET_HARMONYLAN}." } # --- Script Entrypoint --- case "${1:-}" in up) mkdir -p "${IMG_DIR}" "${STATE_DIR}" download_if_missing "$OPN_IMG_URL" "$OPN_IMG_PATH" download_if_missing "$CENTOS_ISO_URL" "$CENTOS_ISO_PATH" create_lab_environment echo "Lab setup complete. Use 'sudo virsh list --all' to see VMs." ;; clean) destroy_vm "${VM_PXE}" destroy_vm "${VM_OPN}" destroy_network "${NET_HARMONYLAN}" # Optionally destroy the default network if you want a full reset # destroy_network "default" echo "Cleanup complete." ;; *) echo "Usage: sudo $0 {up|clean}" exit 1 ;; esac