diff --git a/playbook/ipxe/GetPxeScript.aspx b/playbook/ipxe/GetPxeScript.aspx
index 2c38988..aab88e7 100644
--- a/playbook/ipxe/GetPxeScript.aspx
+++ b/playbook/ipxe/GetPxeScript.aspx
@@ -69,7 +69,10 @@ goto cz_machine
# 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.
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
# Same CPU check as the manual entry below (cpuid --ext 29 = 64-bit).
cpuid --ext 29 && goto cz_auto64 || goto cz_auto32
diff --git a/playbook/pxe_server_setup.yml b/playbook/pxe_server_setup.yml
index 5d9bab5..736a4ad 100644
--- a/playbook/pxe_server_setup.yml
+++ b/playbook/pxe_server_setup.yml
@@ -986,12 +986,20 @@
ignore_errors: yes
- name: "Deploy shopfloor setup scripts to enrollment share"
- copy:
- src: "{{ usb_mount }}/shopfloor-setup/"
- dest: /srv/samba/enrollment/shopfloor-setup/
- mode: '0755'
- directory_mode: '0755'
- ignore_errors: yes
+ # rsync, not copy: the tree is ~4 GB and the copy module checksums every
+ # file one by one, so a re-run spent minutes here. rsync compares size
+ # and mtime and transfers only what changed. Merge, no --delete: files
+ # a site added on the share stay. Skipped if the USB has no tree.
+ shell: |
+ 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/"
copy:
diff --git a/tests/vm/README.md b/tests/vm/README.md
index 2d2c600..5f3809e 100644
--- a/tests/vm/README.md
+++ b/tests/vm/README.md
@@ -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-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-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):
@@ -21,6 +22,7 @@ NIC MACs (fixed):
| server | imaging | `52:54:00:9e:10:02` | `pxetest-imaging` | `pxe_nic_mac` |
| client | - | `52:54:00:9e:10:03` | `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
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/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/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/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/stop.sh client
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/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
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.
-2. Same as 6501 from a 32-bit BIOS client: a legacy-BIOS VM (SeaBIOS) with
- `-cpu` lacking long mode (e.g. `pentium3`
- or qemu32) so `cpuid --ext 29` fails and :cz_auto32 boots
- clonezilla-i686. Expect pxe-meta.txt bits=32, firmware=bios,
+2. Same as 6501 from the 32-bit BIOS client (`start.sh xp`, pxetest-xp):
+ `cpuid --ext 29` fails on its qemu32 CPU 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=clonezilla-live-3.2.0-5-i686. Also type `65 01` once:
Clonezilla says the number is not valid and asks again.
3. Webapp (Tools > Clonezilla backups): both images under 6501, newest
diff --git a/tests/vm/common.sh b/tests/vm/common.sh
index 143eb45..59ba8f8 100755
--- a/tests/vm/common.sh
+++ b/tests/vm/common.sh
@@ -52,6 +52,14 @@ CLIENT_MAC="52:54:00:9e:10:03" # on pxetest-imaging only
CLIENT2_VM="pxetest-client-nosb"
CLIENT2_DISK_VOL="pxetest-client-nosb.qcow2"
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' "$*"; }
die() { printf '[pxetest] ERROR: %s\n' "$*" >&2; exit 1; }
diff --git a/tests/vm/create.sh b/tests/vm/create.sh
index 357aaa7..7208aa0 100755
--- a/tests/vm/create.sh
+++ b/tests/vm/create.sh
@@ -12,6 +12,10 @@
# 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"
@@ -51,6 +55,10 @@ 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
@@ -202,8 +210,45 @@ XML
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" <
+ $CLIENT3_VM
+ $CLIENT3_MEM_MIB
+ 1
+
+ hvm
+
+
+ qemu32
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+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" "$CLIENT_VM" "$CLIENT2_VM"; do
+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" ;;
diff --git a/tests/vm/destroy.sh b/tests/vm/destroy.sh
index f4cffd3..0692bcd 100755
--- a/tests/vm/destroy.sh
+++ b/tests/vm/destroy.sh
@@ -5,13 +5,13 @@
# Usage: tests/vm/destroy.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_state "$vm")" = "shut off" ] || virsh destroy "$vm" >/dev/null
virsh undefine "$vm" --nvram >/dev/null
log "undefined $vm"
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
virsh vol-delete --pool "$POOL" "$v" >/dev/null
log "deleted volume $v"
diff --git a/tests/vm/start.sh b/tests/vm/start.sh
index c5bb65b..09dfebb 100755
--- a/tests/vm/start.sh
+++ b/tests/vm/start.sh
@@ -1,8 +1,9 @@
#!/bin/bash
# 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)
# 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
# client at a time: server + one client = 6 GiB.
source "$(dirname "$0")/common.sh"
@@ -12,8 +13,9 @@ case "$which" in
server) vms=("$SERVER_VM") ;;
client) vms=("$CLIENT_VM") ;;
client2|nosb) vms=("$CLIENT2_VM") ;;
+ xp|client3) vms=("$CLIENT3_VM") ;;
all) vms=("$SERVER_VM" "$CLIENT_VM") ;;
- *) die "usage: $0 [server|client|client2|all]" ;;
+ *) die "usage: $0 [server|client|client2|xp|all]" ;;
esac
net_exists "$IMAGING_NET" || die "run create.sh first"
@@ -23,13 +25,13 @@ assert_imaging_isolated
for vm in "${vms[@]}"; do
dom_exists "$vm" || die "$vm not defined; run create.sh"
# RAM: one client at a time.
- case "$vm" in
- "$CLIENT_VM") other="$CLIENT2_VM" ;;
- "$CLIENT2_VM") other="$CLIENT_VM" ;;
- *) other="" ;;
- esac
- if [ -n "$other" ] && [ "$(dom_state "$other")" = "running" ]; then
- die "$other is running; stop it first (tests/vm/stop.sh) to stay within RAM"
+ if [ "$vm" != "$SERVER_VM" ]; then
+ for other in "${ALL_CLIENTS[@]}"; do
+ [ "$other" != "$vm" ] || continue
+ if [ "$(dom_state "$other")" = "running" ]; then
+ die "$other is running; stop it first (tests/vm/stop.sh) to stay within RAM"
+ fi
+ done
fi
if [ "$vm" = "$SERVER_VM" ]; then
vol_exists "$USB_VOL" || die "no $USB_VOL; run build-image.sh"
diff --git a/tests/vm/status.sh b/tests/vm/status.sh
index 3480225..bbb63bc 100755
--- a/tests/vm/status.sh
+++ b/tests/vm/status.sh
@@ -12,14 +12,14 @@ echo "network $IMAGING_NET: $n"
if net_active "$IMAGING_NET"; then
(assert_imaging_isolated && echo " isolation: OK (no forward, no ip, no host IPv4, only vnet ports)")
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
echo "volume $v: $(virsh vol-info --pool "$POOL" "$v" | awk '/Capacity|Allocation/{printf "%s %s %s ", $1, $2, $3}')"
else
echo "volume $v: absent"
fi
done
-for vm in "$SERVER_VM" "$CLIENT_VM" "$CLIENT2_VM"; do
+for vm in "$SERVER_VM" "${ALL_CLIENTS[@]}"; do
if dom_exists "$vm"; then
echo "domain $vm: $(dom_state "$vm")"
virsh domiflist "$vm" | tail -n +3 | grep -v '^$' | sed 's/^/ /'
diff --git a/tests/vm/stop.sh b/tests/vm/stop.sh
index 0ef0f8b..f1cac71 100755
--- a/tests/vm/stop.sh
+++ b/tests/vm/stop.sh
@@ -1,7 +1,7 @@
#!/bin/bash
# Shut pxetest VMs down (ACPI, then force after a timeout). Keeps them
# 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"
which="${1:-all}"
@@ -9,8 +9,9 @@ case "$which" in
server) vms=("$SERVER_VM") ;;
client) vms=("$CLIENT_VM") ;;
client2|nosb) vms=("$CLIENT2_VM") ;;
- all) vms=("$CLIENT_VM" "$CLIENT2_VM" "$SERVER_VM") ;;
- *) die "usage: $0 [server|client|client2|all]" ;;
+ xp|client3) vms=("$CLIENT3_VM") ;;
+ all) vms=("${ALL_CLIENTS[@]}" "$SERVER_VM") ;;
+ *) die "usage: $0 [server|client|client2|xp|all]" ;;
esac
for vm in "${vms[@]}"; do
dom_exists "$vm" || continue