Files
pxe-server/tests/vm/create.sh
cproudlock 576955785d Faster shopfloor-setup sync, prompt spacing, XP-era test client
- Deploy shopfloor-setup with rsync instead of the copy module: the
  ~4 GB tree took about 4 minutes on every re-run because copy checksums
  each file. rsync transfers only changes; changed only when it did.
- iPXE drops a trailing space, so 'Machine number:' ran into the typed
  digits. The space now comes from a hex 20 setting.
- tests/vm: pxetest-xp is a permanent client (SeaBIOS, qemu32 32-bit CPU,
  rtl8139, 1 GiB, IDE disk) for the i686 Clonezilla path; start.sh xp.
  Only one client may run at a time across all three.
2026-10-15 21:43:33 -04:00

263 lines
8.9 KiB
Bash
Executable File

#!/bin/bash
# Define the pxetest network, volumes and VMs. Idempotent: existing
# pxetest-* objects are kept (run destroy.sh first to rebuild them).
#
# pxetest-imaging isolated libvirt network: no <forward>, no <ip>, so the
# host has no address on it and libvirt runs no DHCP.
# Bridge virbr-pxetest holds only the VMs' tap devices.
# pxetest-server 2 vCPU, 4 GiB, OVMF (Secure Boot, MS keys), 60G thin
# qcow2 + the USB image as a USB disk.
# NIC1 52:54:00:9e:10:01 on 'default' NAT ("GE/corp")
# NIC2 52:54:00:9e:10:02 on pxetest-imaging ("imaging")
# pxetest-client 2 vCPU, 2 GiB, OVMF Secure Boot with MS keys, 1G blank
# disk, one NIC 52:54:00:9e:10:03 on pxetest-imaging,
# network boot first (UEFI IPv4 PXE).
# pxetest-client-nosb OVMF without Secure Boot, e1000e, 64G SATA disk.
# pxetest-xp XP/Win7-era PC: SeaBIOS (legacy PXE), qemu32 CPU
# (32-bit only), 1 GiB, rtl8139 52:54:00:9e:10:05,
# 8G IDE disk.
#
# Usage: tests/vm/create.sh
source "$(dirname "$0")/common.sh"
TMP=$(mktemp -d)
trap 'rm -rf "$TMP"' EXIT
# --- isolated imaging network ---
if ! net_exists "$IMAGING_NET"; then
cat >"$TMP/net.xml" <<XML
<network>
<name>$IMAGING_NET</name>
<bridge name='$IMAGING_BRIDGE' stp='on' delay='0'/>
</network>
XML
virsh net-define "$TMP/net.xml" >/dev/null
log "defined network $IMAGING_NET (isolated, no forward, no ip)"
fi
virsh net-autostart --disable "$IMAGING_NET" >/dev/null 2>&1 || true
if ! net_active "$IMAGING_NET"; then
virsh net-start "$IMAGING_NET" >/dev/null
fi
assert_imaging_isolated
net_exists "$GE_NET" || die "libvirt network '$GE_NET' (NAT) does not exist"
net_active "$GE_NET" || virsh net-start "$GE_NET" >/dev/null
# --- volumes ---
if ! vol_exists "$SERVER_DISK_VOL"; then
virsh vol-create-as "$POOL" "$SERVER_DISK_VOL" "$SERVER_DISK_SIZE" --format qcow2 >/dev/null
log "created $SERVER_DISK_VOL ($SERVER_DISK_SIZE thin qcow2)"
fi
if ! vol_exists "$CLIENT2_DISK_VOL"; then
virsh vol-create-as "$POOL" "$CLIENT2_DISK_VOL" 64G --format qcow2 >/dev/null
log "created $CLIENT2_DISK_VOL (64G thin, blank)"
fi
if ! vol_exists "$CLIENT_DISK_VOL"; then
virsh vol-create-as "$POOL" "$CLIENT_DISK_VOL" 1G --format qcow2 >/dev/null
log "created $CLIENT_DISK_VOL (1G blank)"
fi
if ! vol_exists "$CLIENT3_DISK_VOL"; then
virsh vol-create-as "$POOL" "$CLIENT3_DISK_VOL" 8G --format qcow2 >/dev/null
log "created $CLIENT3_DISK_VOL (8G thin, blank)"
fi
vol_exists "$USB_VOL" || log "WARNING: no $USB_VOL volume yet; run build-image.sh before start.sh"
# Disks are type='file' with the pool's path: with type='volume' sources
# qemu got "Permission denied" opening them (AppArmor profile).
POOL_DIR=$(virsh pool-dumpxml "$POOL" | sed -n 's|.*<path>\(.*\)</path>.*|\1|p' | head -1)
[ -n "$POOL_DIR" ] || die "cannot read the path of pool $POOL"
# Secure Boot with Microsoft keys enrolled: libvirt picks the edk2
# descriptor (40-edk2-x86_64-secure-enrolled.json) and copies its
# OVMF_VARS_4M.ms.fd template into a per-VM nvram.
efi_os() {
cat <<XML
<os firmware='efi'>
<type arch='x86_64' machine='q35'>hvm</type>
<firmware>
<feature enabled='yes' name='secure-boot'/>
<feature enabled='yes' name='enrolled-keys'/>
</firmware>
</os>
<features><acpi/><apic/><smm state='on'/></features>
XML
}
# --- server VM ---
# Boot order: the installed disk first, the USB second. A blank disk falls
# through to the USB; after the install the disk boots, so autoinstall does
# not loop. CIDATA stays attached for the first-boot playbook.
if ! dom_exists "$SERVER_VM"; then
cat >"$TMP/server.xml" <<XML
<domain type='kvm'>
<name>$SERVER_VM</name>
<memory unit='MiB'>$SERVER_MEM_MIB</memory>
<vcpu>2</vcpu>
$(efi_os)
<cpu mode='host-passthrough'/>
<clock offset='utc'/>
<on_reboot>restart</on_reboot>
<devices>
<disk type='file' device='disk'>
<driver name='qemu' type='qcow2' discard='unmap'/>
<source file='$POOL_DIR/$SERVER_DISK_VOL'/>
<target dev='vda' bus='virtio'/>
<boot order='1'/>
</disk>
<disk type='file' device='disk'>
<driver name='qemu' type='raw'/>
<source file='$POOL_DIR/$USB_VOL'/>
<target dev='sda' bus='usb' removable='on'/>
<boot order='2'/>
</disk>
<controller type='usb' model='qemu-xhci'/>
<interface type='network'>
<source network='$GE_NET'/>
<mac address='$SERVER_GE_MAC'/>
<model type='virtio'/>
</interface>
<interface type='network'>
<source network='$IMAGING_NET'/>
<mac address='$SERVER_IMAGING_MAC'/>
<model type='virtio'/>
</interface>
<serial type='pty'/>
<console type='pty'/>
<graphics type='vnc' listen='127.0.0.1' autoport='yes'/>
<video><model type='virtio'/></video>
<memballoon model='virtio'/>
<rng model='virtio'><backend model='random'>/dev/urandom</backend></rng>
</devices>
</domain>
XML
virsh define "$TMP/server.xml" >/dev/null
log "defined $SERVER_VM"
fi
# --- client VM ---
if ! dom_exists "$CLIENT_VM"; then
cat >"$TMP/client.xml" <<XML
<domain type='kvm'>
<name>$CLIENT_VM</name>
<memory unit='MiB'>$CLIENT_MEM_MIB</memory>
<vcpu>2</vcpu>
$(efi_os)
<cpu mode='host-passthrough'/>
<clock offset='utc'/>
<devices>
<interface type='network'>
<source network='$IMAGING_NET'/>
<mac address='$CLIENT_MAC'/>
<model type='virtio'/>
<rom enabled='no'/>
<boot order='1'/>
</interface>
<disk type='file' device='disk'>
<driver name='qemu' type='qcow2'/>
<source file='$POOL_DIR/$CLIENT_DISK_VOL'/>
<target dev='vda' bus='virtio'/>
<boot order='2'/>
</disk>
<serial type='pty'/>
<console type='pty'/>
<graphics type='vnc' listen='127.0.0.1' autoport='yes'/>
<video><model type='virtio'/></video>
<memballoon model='virtio'/>
</devices>
</domain>
XML
virsh define "$TMP/client.xml" >/dev/null
log "defined $CLIENT_VM"
fi
# --- second client: Secure Boot off, e1000e + SATA (WinPE in-box drivers) ---
if ! dom_exists "$CLIENT2_VM"; then
cat >"$TMP/client2.xml" <<XML
<domain type='kvm'>
<name>$CLIENT2_VM</name>
<memory unit='MiB'>$CLIENT_MEM_MIB</memory>
<vcpu>2</vcpu>
<os firmware='efi'>
<type arch='x86_64' machine='q35'>hvm</type>
<firmware>
<feature enabled='no' name='secure-boot'/>
</firmware>
</os>
<features><acpi/><apic/></features>
<cpu mode='host-passthrough'/>
<clock offset='localtime'/>
<devices>
<interface type='network'>
<source network='$IMAGING_NET'/>
<mac address='$CLIENT2_MAC'/>
<model type='e1000e'/>
<boot order='1'/>
</interface>
<disk type='file' device='disk'>
<driver name='qemu' type='qcow2'/>
<source file='$POOL_DIR/$CLIENT2_DISK_VOL'/>
<target dev='sda' bus='sata'/>
<boot order='2'/>
</disk>
<serial type='pty'/>
<console type='pty'/>
<graphics type='vnc' listen='127.0.0.1' autoport='yes'/>
<video><model type='vga'/></video>
<memballoon model='virtio'/>
</devices>
</domain>
XML
virsh define "$TMP/client2.xml" >/dev/null
log "defined $CLIENT2_VM"
fi
# --- third client: XP/Win7-era PC, legacy BIOS, 32-bit CPU ---
# fallback='forbid': fail rather than quietly give the guest a 64-bit CPU,
# which would send it down the amd64 Clonezilla path.
if ! dom_exists "$CLIENT3_VM"; then
cat >"$TMP/client3.xml" <<XML
<domain type='kvm'>
<name>$CLIENT3_VM</name>
<memory unit='MiB'>$CLIENT3_MEM_MIB</memory>
<vcpu>1</vcpu>
<os>
<type arch='i686' machine='pc'>hvm</type>
</os>
<features><acpi/></features>
<cpu mode='custom' match='exact'><model fallback='forbid'>qemu32</model></cpu>
<clock offset='localtime'/>
<devices>
<interface type='network'>
<source network='$IMAGING_NET'/>
<mac address='$CLIENT3_MAC'/>
<model type='rtl8139'/>
<boot order='1'/>
</interface>
<disk type='file' device='disk'>
<driver name='qemu' type='qcow2'/>
<source file='$POOL_DIR/$CLIENT3_DISK_VOL'/>
<target dev='hda' bus='ide'/>
<boot order='2'/>
</disk>
<graphics type='vnc' listen='127.0.0.1' autoport='yes'/>
<video><model type='vga'/></video>
</devices>
</domain>
XML
virsh define "$TMP/client3.xml" >/dev/null
log "defined $CLIENT3_VM"
fi
# Never attach a pxetest VM anywhere but default NAT / pxetest-imaging.
for vm in "$SERVER_VM" "${ALL_CLIENTS[@]}"; do
while read -r _ type src _ _; do
case "$type:$src" in
network:"$GE_NET") [ "$vm" = "$SERVER_VM" ] || die "$vm is on $GE_NET" ;;
network:"$IMAGING_NET") ;;
*) die "$vm has an interface on $type:$src (only $GE_NET / $IMAGING_NET allowed)" ;;
esac
done < <(virsh domiflist "$vm" | tail -n +3 | grep -v '^$')
done
log "ready. Playbook NIC pins for this site:"
log " pxe_nic_mac: \"$SERVER_IMAGING_MAC\""
log " corp_nic_mac: \"$SERVER_GE_MAC\""