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.
This commit is contained in:
cproudlock
2026-10-15 21:43:33 -04:00
parent 23ee99a390
commit 576955785d
9 changed files with 98 additions and 29 deletions

View File

@@ -69,7 +69,10 @@ goto cz_machine
# iPXE cannot check characters: pxe-cz.sh accepts letters, digits and dash # iPXE cannot check characters: pxe-cz.sh accepts letters, digits and dash
# and asks again there. Empty: ask again. Esc / Ctrl-C: back to the menu. # and asks again there. Empty: ask again. Esc / Ctrl-C: back to the menu.
echo echo
echo -n Machine number: && read czmachine || goto menu # iPXE drops a trailing space from a command line, so the space after the
# colon comes from a setting (0x20) rather than the literal text.
set sp:hex 20
echo -n Machine number:${sp:string} && read czmachine || goto menu
isset ${czmachine} || goto cz_machine isset ${czmachine} || goto cz_machine
# Same CPU check as the manual entry below (cpuid --ext 29 = 64-bit). # Same CPU check as the manual entry below (cpuid --ext 29 = 64-bit).
cpuid --ext 29 && goto cz_auto64 || goto cz_auto32 cpuid --ext 29 && goto cz_auto64 || goto cz_auto32

View File

@@ -986,12 +986,20 @@
ignore_errors: yes ignore_errors: yes
- name: "Deploy shopfloor setup scripts to enrollment share" - name: "Deploy shopfloor setup scripts to enrollment share"
copy: # rsync, not copy: the tree is ~4 GB and the copy module checksums every
src: "{{ usb_mount }}/shopfloor-setup/" # file one by one, so a re-run spent minutes here. rsync compares size
dest: /srv/samba/enrollment/shopfloor-setup/ # and mtime and transfers only what changed. Merge, no --delete: files
mode: '0755' # a site added on the share stay. Skipped if the USB has no tree.
directory_mode: '0755' shell: |
ignore_errors: yes set -euo pipefail
SRC="{{ usb_mount }}/shopfloor-setup/"
[ -d "$SRC" ] || { echo "SKIP: no shopfloor-setup on the USB"; exit 0; }
mkdir -p /srv/samba/enrollment/shopfloor-setup
rsync -rlt -i --chmod=D0755,F0755 "$SRC" /srv/samba/enrollment/shopfloor-setup/
args:
executable: /bin/bash
register: shopfloor_rsync
changed_when: shopfloor_rsync.stdout | trim | length > 0 and 'SKIP:' not in shopfloor_rsync.stdout
- name: "Deploy preinstall.json to pre-install/" - name: "Deploy preinstall.json to pre-install/"
copy: copy:

View File

@@ -12,6 +12,7 @@ a laptop, without root and without touching a real network.
| `pxetest-server` | 2 vCPU, 4 GiB, q35 + OVMF Secure Boot (MS keys), 60G thin qcow2 (`vda`, boot 1), the USB image as a USB disk (`sda`, boot 2). | | `pxetest-server` | 2 vCPU, 4 GiB, q35 + OVMF Secure Boot (MS keys), 60G thin qcow2 (`vda`, boot 1), the USB image as a USB disk (`sda`, boot 2). |
| `pxetest-client` | 2 vCPU, 2 GiB, OVMF Secure Boot (MS keys), 1G blank disk, network boot first (UEFI IPv4 PXE, virtio-net, OVMF driver, no option ROM). Proves shim -> iPXE -> menu -> WinPE under Secure Boot; WinPE has no virtio driver, so it stops at "Waiting for network". | | `pxetest-client` | 2 vCPU, 2 GiB, OVMF Secure Boot (MS keys), 1G blank disk, network boot first (UEFI IPv4 PXE, virtio-net, OVMF driver, no option ROM). Proves shim -> iPXE -> menu -> WinPE under Secure Boot; WinPE has no virtio driver, so it stops at "Waiting for network". |
| `pxetest-client-nosb` | 2 vCPU, 2 GiB, OVMF without Secure Boot, e1000e NIC, 64G thin SATA disk. WinPE has in-box drivers for both, so startnet gets a network and maps the shares. | | `pxetest-client-nosb` | 2 vCPU, 2 GiB, OVMF without Secure Boot, e1000e NIC, 64G thin SATA disk. WinPE has in-box drivers for both, so startnet gets a network and maps the shares. |
| `pxetest-xp` | XP/Win7-era PC: 1 vCPU, 1 GiB, i440FX + SeaBIOS (legacy PXE, gets `ipxe.pxe`), `qemu32` CPU (no long mode, so the menu picks the i686 Clonezilla; `fallback='forbid'` so it never silently becomes 64-bit), rtl8139 NIC, 8G thin IDE disk. |
NIC MACs (fixed): NIC MACs (fixed):
@@ -21,6 +22,7 @@ NIC MACs (fixed):
| server | imaging | `52:54:00:9e:10:02` | `pxetest-imaging` | `pxe_nic_mac` | | server | imaging | `52:54:00:9e:10:02` | `pxetest-imaging` | `pxe_nic_mac` |
| client | - | `52:54:00:9e:10:03` | `pxetest-imaging` | - | | client | - | `52:54:00:9e:10:03` | `pxetest-imaging` | - |
| client-nosb | - | `52:54:00:9e:10:04` | `pxetest-imaging` | - | | client-nosb | - | `52:54:00:9e:10:04` | `pxetest-imaging` | - |
| xp | - | `52:54:00:9e:10:05` | `pxetest-imaging` | - |
`build-image.sh` writes `$PXETEST_CACHE/site-vars.yml` with these two `build-image.sh` writes `$PXETEST_CACHE/site-vars.yml` with these two
MACs (`pxe_nic_mac`, `corp_nic_mac`) and passes it to `build-usb.sh MACs (`pxe_nic_mac`, `corp_nic_mac`) and passes it to `build-usb.sh
@@ -48,12 +50,13 @@ IPv4 address or a non-tap bridge port.
scripts/download-packages.sh --iso "$PXETEST_CACHE/$UBUNTU_ISO_NAME" "$PXETEST_PAYLOAD_ROOT/offline-packages" scripts/download-packages.sh --iso "$PXETEST_CACHE/$UBUNTU_ISO_NAME" "$PXETEST_PAYLOAD_ROOT/offline-packages"
scripts/check-updates.py --debs "$PXETEST_PAYLOAD_ROOT/offline-packages" scripts/check-updates.py --debs "$PXETEST_PAYLOAD_ROOT/offline-packages"
tests/vm/build-image.sh # build-usb.sh --image, upload as volume pxetest-usb.img tests/vm/build-image.sh # build-usb.sh --image, upload as volume pxetest-usb.img
tests/vm/create.sh # network, volumes, both VMs (prints the MAC pins) tests/vm/create.sh # network, volumes, all VMs (prints the MAC pins)
tests/vm/start.sh server # autoinstall, reboot, first-boot playbook tests/vm/start.sh server # autoinstall, reboot, first-boot playbook
tests/vm/status.sh --shot /tmp/pxetest-shots # states, leases, screenshots tests/vm/status.sh --shot /tmp/pxetest-shots # states, leases, screenshots
tests/vm/start.sh client # once the server playbook is done (Secure Boot client) tests/vm/start.sh client # once the server playbook is done (Secure Boot client)
tests/vm/stop.sh client tests/vm/stop.sh client
tests/vm/start.sh client2 # Secure Boot off, e1000e: WinPE maps the shares tests/vm/start.sh client2 # Secure Boot off, e1000e: WinPE maps the shares
tests/vm/start.sh xp # legacy BIOS, 32-bit: i686 Clonezilla
tests/vm/stop.sh # ACPI shutdown all, keep them defined tests/vm/stop.sh # ACPI shutdown all, keep them defined
tests/vm/destroy.sh # remove every pxetest-* object tests/vm/destroy.sh # remove every pxetest-* object
@@ -120,10 +123,9 @@ the clonezilla user; `--with-boot-tools` is needed for Clonezilla itself
screen, the share mounts, one disk so no disk question, image screen, the share mounts, one disk so no disk question, image
6501-YYYY-MM-DD-HHMM (server time, Eastern) is saved, "Checking the saved 6501-YYYY-MM-DD-HHMM (server time, Eastern) is saved, "Checking the saved
image", SUCCESS banner with serial and model, power off after 60 s. image", SUCCESS banner with serial and model, power off after 60 s.
2. Same as 6501 from a 32-bit BIOS client: a legacy-BIOS VM (SeaBIOS) with 2. Same as 6501 from the 32-bit BIOS client (`start.sh xp`, pxetest-xp):
`-cpu` lacking long mode (e.g. `<cpu mode='custom'><model>pentium3</model></cpu>` `cpuid --ext 29` fails on its qemu32 CPU and :cz_auto32 boots
or qemu32) so `cpuid --ext 29` fails and :cz_auto32 boots clonezilla-i686. Give its IDE disk content first, as for step 1. Expect pxe-meta.txt bits=32, firmware=bios,
clonezilla-i686. Expect pxe-meta.txt bits=32, firmware=bios,
clonezilla=clonezilla-live-3.2.0-5-i686. Also type `65 01` once: clonezilla=clonezilla-live-3.2.0-5-i686. Also type `65 01` once:
Clonezilla says the number is not valid and asks again. Clonezilla says the number is not valid and asks again.
3. Webapp (Tools > Clonezilla backups): both images under 6501, newest 3. Webapp (Tools > Clonezilla backups): both images under 6501, newest

View File

@@ -52,6 +52,14 @@ CLIENT_MAC="52:54:00:9e:10:03" # on pxetest-imaging only
CLIENT2_VM="pxetest-client-nosb" CLIENT2_VM="pxetest-client-nosb"
CLIENT2_DISK_VOL="pxetest-client-nosb.qcow2" CLIENT2_DISK_VOL="pxetest-client-nosb.qcow2"
CLIENT2_MAC="52:54:00:9e:10:04" # on pxetest-imaging only CLIENT2_MAC="52:54:00:9e:10:04" # on pxetest-imaging only
# Third client: a 15-20-year-old XP/Win7-era PC. Legacy BIOS (SeaBIOS),
# 32-bit only CPU (qemu32: no long mode, so the menu picks the i686
# Clonezilla), rtl8139 NIC, IDE disk, 1 GiB.
CLIENT3_VM="pxetest-xp"
CLIENT3_DISK_VOL="pxetest-xp.qcow2"
CLIENT3_MAC="52:54:00:9e:10:05" # on pxetest-imaging only
CLIENT3_MEM_MIB="${CLIENT3_MEM_MIB:-1024}"
ALL_CLIENTS=("pxetest-client" "pxetest-client-nosb" "pxetest-xp")
log() { printf '[pxetest] %s\n' "$*"; } log() { printf '[pxetest] %s\n' "$*"; }
die() { printf '[pxetest] ERROR: %s\n' "$*" >&2; exit 1; } die() { printf '[pxetest] ERROR: %s\n' "$*" >&2; exit 1; }

View File

@@ -12,6 +12,10 @@
# pxetest-client 2 vCPU, 2 GiB, OVMF Secure Boot with MS keys, 1G blank # 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, # disk, one NIC 52:54:00:9e:10:03 on pxetest-imaging,
# network boot first (UEFI IPv4 PXE). # 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 # Usage: tests/vm/create.sh
source "$(dirname "$0")/common.sh" source "$(dirname "$0")/common.sh"
@@ -51,6 +55,10 @@ if ! vol_exists "$CLIENT_DISK_VOL"; then
virsh vol-create-as "$POOL" "$CLIENT_DISK_VOL" 1G --format qcow2 >/dev/null virsh vol-create-as "$POOL" "$CLIENT_DISK_VOL" 1G --format qcow2 >/dev/null
log "created $CLIENT_DISK_VOL (1G blank)" log "created $CLIENT_DISK_VOL (1G blank)"
fi 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" 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 # Disks are type='file' with the pool's path: with type='volume' sources
@@ -202,8 +210,45 @@ XML
log "defined $CLIENT2_VM" log "defined $CLIENT2_VM"
fi 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. # Never attach a pxetest VM anywhere but default NAT / pxetest-imaging.
for vm in "$SERVER_VM" "$CLIENT_VM" "$CLIENT2_VM"; do for vm in "$SERVER_VM" "${ALL_CLIENTS[@]}"; do
while read -r _ type src _ _; do while read -r _ type src _ _; do
case "$type:$src" in case "$type:$src" in
network:"$GE_NET") [ "$vm" = "$SERVER_VM" ] || die "$vm is on $GE_NET" ;; network:"$GE_NET") [ "$vm" = "$SERVER_VM" ] || die "$vm is on $GE_NET" ;;

View File

@@ -5,13 +5,13 @@
# Usage: tests/vm/destroy.sh # Usage: tests/vm/destroy.sh
source "$(dirname "$0")/common.sh" source "$(dirname "$0")/common.sh"
for vm in "$CLIENT_VM" "$CLIENT2_VM" "$SERVER_VM"; do for vm in "${ALL_CLIENTS[@]}" "$SERVER_VM"; do
dom_exists "$vm" || continue dom_exists "$vm" || continue
[ "$(dom_state "$vm")" = "shut off" ] || virsh destroy "$vm" >/dev/null [ "$(dom_state "$vm")" = "shut off" ] || virsh destroy "$vm" >/dev/null
virsh undefine "$vm" --nvram >/dev/null virsh undefine "$vm" --nvram >/dev/null
log "undefined $vm" log "undefined $vm"
done done
for v in "$SERVER_DISK_VOL" "$USB_VOL" "$CLIENT_DISK_VOL" "$CLIENT2_DISK_VOL"; do for v in "$SERVER_DISK_VOL" "$USB_VOL" "$CLIENT_DISK_VOL" "$CLIENT2_DISK_VOL" "$CLIENT3_DISK_VOL"; do
vol_exists "$v" || continue vol_exists "$v" || continue
virsh vol-delete --pool "$POOL" "$v" >/dev/null virsh vol-delete --pool "$POOL" "$v" >/dev/null
log "deleted volume $v" log "deleted volume $v"

View File

@@ -1,8 +1,9 @@
#!/bin/bash #!/bin/bash
# Start pxetest VMs. Idempotent (a running VM is left alone). # Start pxetest VMs. Idempotent (a running VM is left alone).
# Usage: tests/vm/start.sh [server|client|client2|all] (default: server) # Usage: tests/vm/start.sh [server|client|client2|xp|all] (default: server)
# client = Secure Boot client (virtio NIC: reaches the iPXE menu and WinPE) # client = Secure Boot client (virtio NIC: reaches the iPXE menu and WinPE)
# client2 = Secure Boot OFF client, e1000e NIC (WinPE gets a network) # client2 = Secure Boot OFF client, e1000e NIC (WinPE gets a network)
# xp = XP/Win7-era PC: legacy BIOS, 32-bit CPU (i686 Clonezilla)
# Start a client only once the server's playbook has finished, and only one # Start a client only once the server's playbook has finished, and only one
# client at a time: server + one client = 6 GiB. # client at a time: server + one client = 6 GiB.
source "$(dirname "$0")/common.sh" source "$(dirname "$0")/common.sh"
@@ -12,8 +13,9 @@ case "$which" in
server) vms=("$SERVER_VM") ;; server) vms=("$SERVER_VM") ;;
client) vms=("$CLIENT_VM") ;; client) vms=("$CLIENT_VM") ;;
client2|nosb) vms=("$CLIENT2_VM") ;; client2|nosb) vms=("$CLIENT2_VM") ;;
xp|client3) vms=("$CLIENT3_VM") ;;
all) vms=("$SERVER_VM" "$CLIENT_VM") ;; all) vms=("$SERVER_VM" "$CLIENT_VM") ;;
*) die "usage: $0 [server|client|client2|all]" ;; *) die "usage: $0 [server|client|client2|xp|all]" ;;
esac esac
net_exists "$IMAGING_NET" || die "run create.sh first" net_exists "$IMAGING_NET" || die "run create.sh first"
@@ -23,13 +25,13 @@ assert_imaging_isolated
for vm in "${vms[@]}"; do for vm in "${vms[@]}"; do
dom_exists "$vm" || die "$vm not defined; run create.sh" dom_exists "$vm" || die "$vm not defined; run create.sh"
# RAM: one client at a time. # RAM: one client at a time.
case "$vm" in if [ "$vm" != "$SERVER_VM" ]; then
"$CLIENT_VM") other="$CLIENT2_VM" ;; for other in "${ALL_CLIENTS[@]}"; do
"$CLIENT2_VM") other="$CLIENT_VM" ;; [ "$other" != "$vm" ] || continue
*) other="" ;; if [ "$(dom_state "$other")" = "running" ]; then
esac die "$other is running; stop it first (tests/vm/stop.sh) to stay within RAM"
if [ -n "$other" ] && [ "$(dom_state "$other")" = "running" ]; then fi
die "$other is running; stop it first (tests/vm/stop.sh) to stay within RAM" done
fi fi
if [ "$vm" = "$SERVER_VM" ]; then if [ "$vm" = "$SERVER_VM" ]; then
vol_exists "$USB_VOL" || die "no $USB_VOL; run build-image.sh" vol_exists "$USB_VOL" || die "no $USB_VOL; run build-image.sh"

View File

@@ -12,14 +12,14 @@ echo "network $IMAGING_NET: $n"
if net_active "$IMAGING_NET"; then if net_active "$IMAGING_NET"; then
(assert_imaging_isolated && echo " isolation: OK (no forward, no ip, no host IPv4, only vnet ports)") (assert_imaging_isolated && echo " isolation: OK (no forward, no ip, no host IPv4, only vnet ports)")
fi fi
for v in "$SERVER_DISK_VOL" "$USB_VOL" "$CLIENT_DISK_VOL" "$CLIENT2_DISK_VOL"; do for v in "$SERVER_DISK_VOL" "$USB_VOL" "$CLIENT_DISK_VOL" "$CLIENT2_DISK_VOL" "$CLIENT3_DISK_VOL"; do
if vol_exists "$v"; then if vol_exists "$v"; then
echo "volume $v: $(virsh vol-info --pool "$POOL" "$v" | awk '/Capacity|Allocation/{printf "%s %s %s ", $1, $2, $3}')" echo "volume $v: $(virsh vol-info --pool "$POOL" "$v" | awk '/Capacity|Allocation/{printf "%s %s %s ", $1, $2, $3}')"
else else
echo "volume $v: absent" echo "volume $v: absent"
fi fi
done done
for vm in "$SERVER_VM" "$CLIENT_VM" "$CLIENT2_VM"; do for vm in "$SERVER_VM" "${ALL_CLIENTS[@]}"; do
if dom_exists "$vm"; then if dom_exists "$vm"; then
echo "domain $vm: $(dom_state "$vm")" echo "domain $vm: $(dom_state "$vm")"
virsh domiflist "$vm" | tail -n +3 | grep -v '^$' | sed 's/^/ /' virsh domiflist "$vm" | tail -n +3 | grep -v '^$' | sed 's/^/ /'

View File

@@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# Shut pxetest VMs down (ACPI, then force after a timeout). Keeps them # Shut pxetest VMs down (ACPI, then force after a timeout). Keeps them
# defined. Idempotent. # defined. Idempotent.
# Usage: tests/vm/stop.sh [server|client|client2|all] (default: all) # Usage: tests/vm/stop.sh [server|client|client2|xp|all] (default: all)
source "$(dirname "$0")/common.sh" source "$(dirname "$0")/common.sh"
which="${1:-all}" which="${1:-all}"
@@ -9,8 +9,9 @@ case "$which" in
server) vms=("$SERVER_VM") ;; server) vms=("$SERVER_VM") ;;
client) vms=("$CLIENT_VM") ;; client) vms=("$CLIENT_VM") ;;
client2|nosb) vms=("$CLIENT2_VM") ;; client2|nosb) vms=("$CLIENT2_VM") ;;
all) vms=("$CLIENT_VM" "$CLIENT2_VM" "$SERVER_VM") ;; xp|client3) vms=("$CLIENT3_VM") ;;
*) die "usage: $0 [server|client|client2|all]" ;; all) vms=("${ALL_CLIENTS[@]}" "$SERVER_VM") ;;
*) die "usage: $0 [server|client|client2|xp|all]" ;;
esac esac
for vm in "${vms[@]}"; do for vm in "${vms[@]}"; do
dom_exists "$vm" || continue dom_exists "$vm" || continue