diff --git a/.gitignore b/.gitignore index fa3ec08..478c387 100644 --- a/.gitignore +++ b/.gitignore @@ -50,6 +50,8 @@ mok-keys/ # Secrets secrets.md **/eMxInfo*.txt -*.ppkg -enrollment/ -drivers-staging/ +*.ppkg +enrollment/ +drivers-staging/ +bios-staging/ +.claude/ diff --git a/README.md b/README.md index 8a141c7..04628e7 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ Client PXE boot (UEFI Secure Boot) ### Step 1: Download Offline Packages ```bash -./download-packages.sh +./scripts/download-packages.sh ``` Downloads all .deb packages and Python wheels for offline installation (~140 MB of debs, ~20 MB of wheels). @@ -62,7 +62,7 @@ Downloads all .deb packages and Python wheels for offline installation (~140 MB ### Step 2: Prepare Boot Tools (optional) ```bash -./prepare-boot-tools.sh /path/to/blancco.iso /path/to/clonezilla.zip /path/to/memtest.bin +./scripts/prepare-boot-tools.sh /path/to/blancco.iso /path/to/clonezilla.zip /path/to/memtest.bin ``` Extracts and configures boot tool files (Blancco, Clonezilla, Memtest86+). Automatically patches Blancco's config.img to auto-save erasure reports to the PXE server's Samba share. @@ -70,7 +70,7 @@ Extracts and configures boot tool files (Blancco, Clonezilla, Memtest86+). Autom ### Step 3: Build the USB ```bash -sudo ./build-usb.sh /dev/sdX /path/to/ubuntu-24.04-live-server-amd64.iso +sudo ./scripts/build-usb.sh /dev/sdX /path/to/ubuntu-24.04-live-server-amd64.iso ``` Creates a bootable USB with two partitions: @@ -158,50 +158,32 @@ pxe-server/ │ └── audit.html # Activity audit log ├── docs/ │ └── shopfloor-display-imaging-guide.md # End-user imaging guide -├── unattend/ -│ └── FlatUnattendW10.xml # Windows unattend.xml template ├── boot-tools/ # Extracted boot tool files (gitignored) -│ ├── blancco/ # Blancco Drive Eraser (Arch Linux-based) +│ ├── blancco/ # Blancco Drive Eraser │ ├── clonezilla/ # Clonezilla Live │ └── memtest/ # Memtest86+ +├── boot-files/ # WinPE boot files (boot.wim, wimboot, ipxe.efi, BCD) ├── offline-packages/ # .deb files (gitignored, built by download-packages.sh) ├── pip-wheels/ # Python wheels (gitignored, built by download-packages.sh) -├── download-packages.sh # Downloads offline .debs + pip wheels -├── build-usb.sh # Builds the installer USB (2-partition) -├── prepare-boot-tools.sh # Extracts and patches boot tool files -├── build-proxmox-iso.sh # Builds self-contained Proxmox installer ISO -├── test-vm.sh # KVM test environment for validation -├── test-lab.sh # Full PXE lab with server + client VMs +├── enrollment/ # PPKGs and run-enrollment.ps1 (gitignored) +├── bios-staging/ # Dell BIOS update binaries (gitignored) +├── scripts/ # Build, deploy, and helper scripts +│ ├── build-usb.sh # Builds the installer USB (2-partition) +│ ├── build-proxmox-iso.sh # Builds self-contained Proxmox installer ISO +│ ├── prepare-boot-tools.sh # Extracts and patches boot tool files +│ ├── download-packages.sh # Downloads offline .debs + pip wheels +│ ├── download-drivers.py # Downloads Dell drivers directly from dell.com +│ ├── deploy-bios.sh # Pushes BIOS updates to enrollment share +│ ├── pull-bios.sh # Pulls BIOS binaries from upstream cache +│ ├── sync_hardware_models.py # Syncs hardware model configs across images +│ ├── Upload-Image.ps1 # Windows: upload MCL cache to PXE via SMB +│ └── Download-Drivers.ps1 # Windows: download hardware drivers from GE CDN +├── config/ # Site-specific configuration overrides ├── startnet-template.cmd # startnet.cmd template (synced with playbook copy) -├── Download-Drivers.ps1 # Download hardware drivers from GE CDN (Windows) -├── Upload-Image.ps1 # Upload MCL cache to PXE server via SMB (Windows) -├── download-drivers.py # Download Dell drivers directly from dell.com -├── sync_hardware_models.py # Sync hardware model configs across images -├── SETUP.md # Detailed setup guide -└── setup-guide-original.txt # Original manual setup notes (reference) +├── README.md # This file +└── SETUP.md # Detailed setup guide ``` -## Testing with KVM - -A test VM script is included for validating the full provisioning pipeline without dedicated hardware: - -```bash -# Download Ubuntu Server ISO -wget -O ~/Downloads/ubuntu-24.04.3-live-server-amd64.iso \ - https://releases.ubuntu.com/noble/ubuntu-24.04.3-live-server-amd64.iso - -# Launch test VM (requires libvirt/KVM) -sudo ./test-vm.sh ~/Downloads/ubuntu-24.04.3-live-server-amd64.iso - -# Watch install progress -sudo virsh console pxe-test - -# Clean up when done -sudo ./test-vm.sh --destroy -``` - -The test VM creates an isolated libvirt network (10.9.100.0/24) and runs the full autoinstall + Ansible provisioning. - ## Proxmox Deployment A single ISO can be built for deploying the PXE server in a Proxmox VM: @@ -213,7 +195,7 @@ A single ISO can be built for deploying the PXE server in a Proxmox VM: sudo apt install xorriso p7zip-full # Build the installer ISO -./build-proxmox-iso.sh /path/to/ubuntu-24.04-live-server-amd64.iso +./scripts/build-proxmox-iso.sh /path/to/ubuntu-24.04-live-server-amd64.iso ``` This creates `pxe-server-proxmox.iso` containing the Ubuntu installer, autoinstall config, all offline packages, the Ansible playbook, webapp, and boot tools. diff --git a/SETUP.md b/SETUP.md index bba0bcf..edcaab9 100644 --- a/SETUP.md +++ b/SETUP.md @@ -46,7 +46,7 @@ Client PXE boot ### Step 1: Download Offline Packages (one-time, requires internet) ```bash -./download-packages.sh +./scripts/download-packages.sh ``` Downloads all .deb packages (ansible, dnsmasq, apache2, samba, wimtools, etc.) into `offline-packages/` and Python wheels (flask, lxml) into `pip-wheels/`. Approximately 252 packages (~140 MB) + 8 Python wheels. @@ -61,7 +61,7 @@ Downloads all .deb packages (ansible, dnsmasq, apache2, samba, wimtools, etc.) i ### Step 2: Prepare Boot Tools (optional) ```bash -./prepare-boot-tools.sh /path/to/blancco.iso /path/to/clonezilla.zip /path/to/memtest.bin +./scripts/prepare-boot-tools.sh /path/to/blancco.iso /path/to/clonezilla.zip /path/to/memtest.bin ``` Extracts boot files for Blancco, Clonezilla, and Memtest86+ into the `boot-tools/` directory. Automatically patches Blancco's `config.img` to auto-save erasure reports to the PXE server's Samba share. @@ -70,10 +70,10 @@ Extracts boot files for Blancco, Clonezilla, and Memtest86+ into the `boot-tools ```bash # Basic — server only (import WinPE images later) -sudo ./build-usb.sh /dev/sdX /path/to/ubuntu-24.04-live-server-amd64.iso +sudo ./scripts/build-usb.sh /dev/sdX /path/to/ubuntu-24.04-live-server-amd64.iso # With WinPE images bundled (single USB, larger drive needed) -sudo ./build-usb.sh /dev/sdX /path/to/ubuntu-24.04.iso /path/to/winpe-images +sudo ./scripts/build-usb.sh /dev/sdX /path/to/ubuntu-24.04.iso /path/to/winpe-images ``` This creates a bootable USB with: @@ -175,27 +175,30 @@ pxe-server/ │ └── templates/ # Jinja2 HTML templates (10 pages) ├── docs/ │ └── shopfloor-display-imaging-guide.md # End-user imaging guide -├── unattend/ -│ └── FlatUnattendW10.xml # Windows unattend.xml template ├── boot-tools/ # Extracted boot files (gitignored, built by prepare-boot-tools.sh) │ ├── blancco/ # Blancco Drive Eraser │ ├── clonezilla/ # Clonezilla Live │ └── memtest/ # Memtest86+ +├── boot-files/ # WinPE boot files (boot.wim, wimboot, ipxe.efi, BCD) ├── offline-packages/ # .deb files (gitignored, built by download-packages.sh) ├── pip-wheels/ # Python wheels (gitignored, built by download-packages.sh) -├── download-packages.sh # Downloads all offline packages -├── build-usb.sh # Builds the 2-partition installer USB -├── prepare-boot-tools.sh # Extracts/patches boot tools from ISOs -├── build-proxmox-iso.sh # Builds self-contained Proxmox installer ISO -├── test-vm.sh # KVM test environment -├── test-lab.sh # Full PXE lab with server + client VMs +├── enrollment/ # PPKGs and run-enrollment.ps1 (gitignored) +├── bios-staging/ # Dell BIOS update binaries (gitignored) +├── scripts/ # Build, deploy, and helper scripts +│ ├── build-usb.sh # Builds the 2-partition installer USB +│ ├── build-proxmox-iso.sh # Builds self-contained Proxmox installer ISO +│ ├── prepare-boot-tools.sh # Extracts/patches boot tools from ISOs +│ ├── download-packages.sh # Downloads all offline packages +│ ├── download-drivers.py # Downloads Dell drivers from dell.com +│ ├── deploy-bios.sh # Pushes BIOS updates to enrollment share +│ ├── pull-bios.sh # Pulls BIOS binaries from upstream cache +│ ├── sync_hardware_models.py # Syncs hardware model configs across images +│ ├── Upload-Image.ps1 # Windows: upload MCL cache to PXE via SMB +│ └── Download-Drivers.ps1 # Windows: download hardware drivers from GE CDN +├── config/ # Site-specific configuration overrides ├── startnet-template.cmd # startnet.cmd template (synced with playbook copy) -├── Download-Drivers.ps1 # Download hardware drivers from GE CDN (Windows) -├── Upload-Image.ps1 # Upload MCL cache to PXE server via SMB (Windows) -├── download-drivers.py # Download Dell drivers directly from dell.com -├── sync_hardware_models.py # Sync hardware model configs across images ├── README.md # Project overview -└── setup-guide-original.txt # Original manual setup notes (reference) +└── SETUP.md # Detailed setup guide (this file) ``` ## Image Types diff --git a/mok-keys/MOK.crt b/mok-keys/MOK.crt deleted file mode 100644 index 5bde37e..0000000 Binary files a/mok-keys/MOK.crt and /dev/null differ diff --git a/mok-keys/MOK.der b/mok-keys/MOK.der deleted file mode 100644 index f87dde1..0000000 Binary files a/mok-keys/MOK.der and /dev/null differ diff --git a/mok-keys/MOK.key b/mok-keys/MOK.key deleted file mode 100644 index 5170d40..0000000 Binary files a/mok-keys/MOK.key and /dev/null differ diff --git a/unattend/FlatUnattendW10.xml b/playbook/FlatUnattendW10.xml similarity index 100% rename from unattend/FlatUnattendW10.xml rename to playbook/FlatUnattendW10.xml diff --git a/playbook/busybox-static b/playbook/busybox-static new file mode 100755 index 0000000..a68df50 Binary files /dev/null and b/playbook/busybox-static differ diff --git a/playbook/pxe_server_setup.yml b/playbook/pxe_server_setup.yml index 5289af0..ff84a25 100644 --- a/playbook/pxe_server_setup.yml +++ b/playbook/pxe_server_setup.yml @@ -300,32 +300,72 @@ state: directory mode: '0777' - - name: "Create enrollment packages directory" + - name: "Create enrollment share with internal taxonomy" file: - path: /srv/samba/enrollment + path: "/srv/samba/enrollment/{{ item }}" state: directory mode: '0777' + loop: + - "" + - ppkgs + - scripts + - config + - shopfloor-setup + - pre-install + - pre-install/bios + - pre-install/installers + - installers-post + - installers-post/cmm + - blancco + - logs - - name: "Deploy PPKG enrollment packages to enrollment share" + - name: "Deploy enrollment share README" + copy: + dest: /srv/samba/enrollment/README.md + mode: '0644' + content: | + # Enrollment Share Layout + Single SMB share mounted by WinPE as Y: during imaging. Subdir layout: + - ppkgs/ GCCH bulk-enrollment PPKGs + - scripts/ run-enrollment.ps1, wait-for-internet.ps1, migrate-to-wifi.ps1 + - config/ site-config.json, FlatUnattendW10*.xml, per-site overrides + - shopfloor-setup/ Per-PC-type post-imaging scripts + - pre-install/ WinPE-phase content (bios/, installers/, preinstall.json) + - installers-post/ Post-OOBE app installers (cmm/PCDMIS, etc.) + - blancco/ Blancco custom images / configs + - logs/ Client log uploads + + - name: "Deploy PPKG enrollment packages to ppkgs/" shell: | set +e # Copy any whole PPKGs (small enough to fit on FAT32) - cp -f {{ usb_root }}/enrollment/*.ppkg /srv/samba/enrollment/ 2>/dev/null + cp -f {{ usb_root }}/enrollment/*.ppkg /srv/samba/enrollment/ppkgs/ 2>/dev/null # Reassemble any split files (foo.ppkg.part.00, .01, ... -> foo.ppkg) for first in {{ usb_root }}/enrollment/*.part.00; do [ -e "$first" ] || continue base="${first%.part.00}" name="$(basename "$base")" echo "Reassembling $name from chunks..." - cat "${base}.part."* > "/srv/samba/enrollment/$name" + cat "${base}.part."* > "/srv/samba/enrollment/ppkgs/$name" done - ls -lh /srv/samba/enrollment/*.ppkg 2>/dev/null + ls -lh /srv/samba/enrollment/ppkgs/*.ppkg 2>/dev/null ignore_errors: yes - - name: "Deploy run-enrollment.ps1 to enrollment share" + - name: "Deploy enrollment scripts to scripts/" copy: - src: "{{ usb_mount }}/shopfloor-setup/run-enrollment.ps1" - dest: /srv/samba/enrollment/run-enrollment.ps1 + src: "{{ item.src }}" + dest: "/srv/samba/enrollment/scripts/{{ item.dest }}" + mode: '0644' + loop: + - { src: "{{ usb_mount }}/shopfloor-setup/run-enrollment.ps1", dest: "run-enrollment.ps1" } + - { src: "{{ usb_mount }}/wait-for-internet.ps1", dest: "wait-for-internet.ps1" } + - { src: "{{ usb_mount }}/migrate-to-wifi.ps1", dest: "migrate-to-wifi.ps1" } + ignore_errors: yes + + - name: "Deploy site-config.json to config/" + copy: + src: "{{ usb_mount }}/shopfloor-setup/site-config.json" + dest: /srv/samba/enrollment/config/site-config.json mode: '0644' ignore_errors: yes @@ -363,43 +403,28 @@ directory_mode: '0755' ignore_errors: yes - - name: "Create preinstall bundle directory on enrollment share" - file: - path: "{{ item }}" - state: directory - mode: '0755' - loop: - - /srv/samba/enrollment/preinstall - - /srv/samba/enrollment/preinstall/installers - - - name: "Deploy preinstall.json (installer binaries staged separately)" + - name: "Deploy preinstall.json to pre-install/" copy: src: "{{ usb_mount }}/preinstall/preinstall.json" - dest: /srv/samba/enrollment/preinstall/preinstall.json + dest: /srv/samba/enrollment/pre-install/preinstall.json mode: '0644' ignore_errors: yes - - name: "Create BIOS update directory on enrollment share" - file: - path: /srv/samba/enrollment/BIOS - state: directory - mode: '0755' - - - name: "Deploy BIOS check script and manifest" + - name: "Deploy BIOS check script and manifest to pre-install/bios/" copy: src: "{{ usb_mount }}/shopfloor-setup/BIOS/{{ item }}" - dest: /srv/samba/enrollment/BIOS/{{ item }} + dest: "/srv/samba/enrollment/pre-install/bios/{{ item }}" mode: '0644' loop: - check-bios.cmd - models.txt ignore_errors: yes - - name: "Deploy BIOS update binaries from USB" + - name: "Deploy BIOS update binaries from USB to pre-install/bios/" shell: > if [ -d "{{ usb_root }}/bios" ]; then - cp -f {{ usb_root }}/bios/*.exe /srv/samba/enrollment/BIOS/ 2>/dev/null || true - count=$(find /srv/samba/enrollment/BIOS -name '*.exe' | wc -l) + cp -f {{ usb_root }}/bios/*.exe /srv/samba/enrollment/pre-install/bios/ 2>/dev/null || true + count=$(find /srv/samba/enrollment/pre-install/bios -name '*.exe' | wc -l) echo "Deployed $count BIOS binaries" else echo "No bios/ on USB - skipping" @@ -668,6 +693,29 @@ remote_src: yes mode: '0644' + - name: "Create TFTP blancco directory" + file: + path: "{{ tftp_dir }}/blancco" + state: directory + owner: nobody + group: nogroup + mode: '0755' + + - name: "Create TFTP symlinks for Blancco kernel/initrd (GRUB HTTP times out on large files; TFTP is reliable)" + file: + src: "{{ web_root }}/blancco/{{ item }}" + dest: "{{ tftp_dir }}/blancco/{{ item }}" + state: link + force: yes + owner: nobody + group: nogroup + loop: + - vmlinuz-bde-linux + - initramfs-bde-linux.img + - intel-ucode.img + - amd-ucode.img + - config.img + - name: "Build Ubuntu kernel modules tarball for Blancco" shell: | set -e diff --git a/playbook/shopfloor-setup/BIOS/models.txt b/playbook/shopfloor-setup/BIOS/models.txt new file mode 100644 index 0000000..62cf512 --- /dev/null +++ b/playbook/shopfloor-setup/BIOS/models.txt @@ -0,0 +1,47 @@ +# ModelSubstring|BIOSFile|Version +13 Plus PB13250|Dell_Pro_PA13250_PA14250_PB13250_PB14250_PB16250_2.8.1.exe|2.8.1 +14 MC14250|Dell_Pro_Max_MC16250_MC14250_1.9.0.exe|1.9.0 +14 PC14250|Dell_Pro_PC14250_PC16250_1.7.0.exe|1.7.0 +14 Premium MA14250|Dell_Pro_Max_MA14250_MA16250_1.7.1.exe|1.7.1 +16 Plus MB16250|Dell_Pro_Max_MB16250_MB18250_2.2.2.exe|2.2.2 +24250|Dell_Pro_Plus_QB24250_QC24250_QC24251_1.9.2.exe|1.9.2 +5430|Latitude_5430_7330_Rugged_1.41.0.exe|1.41.0 +7090|OptiPlex_7090_1.40.0.exe|1.40.0 +7220 Rugged|Latitude_7220_Rugged_Extreme_Tablet_1.50.0.exe|1.50.0 +7230 Rugged Extreme Tablet|Latitude_7230_1.30.1.exe|1.30.1 +7320 2-in-1 Detachable|Dell_Latitude_7320_Detachable_1.45.0_64.exe|1.45.0 +7350 Detachable|Latitude_7350_Detachable_1.9.1.exe|1.9.1 +7400 AIO|Latitude_7X00_1.43.0.exe|1.43.0 +AIO Plus 7410|Latitude_7X10_1.43.0.exe|1.43.0 +AIO Plus 7420|Latitude_7X20_1.48.0.exe|1.48.0 +Latitude 5330|Latitude_5330_1.33.0.exe|1.33.0 +Latitude 5340|Latitude_5340_1.27.1.exe|1.27.1 +Latitude 5350|Latitude_5350_1.20.0.exe|1.20.0 +Latitude 5440|Latitude_5440_Precision_3480_1.28.1.exe|1.28.1 +Latitude 5450|Latitude_5450_Precision_3490_1.20.1.exe|1.20.1 +Latitude 5530|Precision_5530_1.42.0.exe|1.42.0 +Latitude 5540|Precision_5540_1.42.0.exe|1.42.0 +Latitude 7430|Latitude_7X30_1.38.0.exe|1.38.0 +Latitude 7440|Latitude_7X40_1.28.1.exe|1.28.1 +Latitude 7450|OptiPlex_7450_1.34.0.exe|1.34.0 +Micro 7010|OptiPlex_7010_1.33.0_SEMB.exe|1.33.0 +Micro QCM1250|Dell_Pro_QBT1250_QBS1250_QBM1250_QCT1250_QCS1250_QCM1250_SEMB_1.12.2.exe|1.12.2 +OptiPlex 3000|OptiPlex_3000_1.38.0.exe|1.38.0 +OptiPlex 7000|OptiPlex_7000_1.38.0.exe|1.38.0 +Precision 5490|OptiPlex_5490_AIO_1.45.0.exe|1.45.0 +Precision 5550|Precision_3590_3591_Latitude_5550_1.8.0.exe|1.8.0 +Precision 5570|XPS9520_Precision5570_1.39.0_QSL0.exe|1.39.0 +Precision 5680|Precision_5680_1_27_0.exe|1.27.0 +Precision 5690|Precision_5690_1.18.0.exe|1.18.0 +Precision 5820 Tower|Precision_5820_2.48.0.exe|2.48.0 +Precision 5860 Tower|Precision_5860_3.5.0.exe|3.5.0 +Precision 7560|Precision_7X60_1.44.1.exe|1.44.1 +Precision 7670|Precision_7X70_1.8.0.exe|1.8.0 +Precision 7680|Precision_7X80_1.9.0.exe|1.9.0 +Precision 7770|OptiPlex_7770_7470_1.40.0.exe|1.40.0 +Precision 7780|OptiPlex_7780_7480_1.43.0.exe|1.43.0 +Precision 7820 Tower|Precision_7820_7920_2.50.0.exe|2.50.0 +Precision 7865 Tower|Precision_7865_1.6.1.exe|1.6.1 +Precision 7875 Tower|Precision_7875_SHP_02.07.03.exe|2.7.3 +Rugged 14 RB14250|Dell_Pro_Rugged_RB14250_RA13250_1.13.1.exe|1.13.1 +Tower Plus 7020|OptiPlex_7020_1.22.1_SEMB.exe|1.22.1 diff --git a/playbook/shopfloor-setup/run-enrollment.ps1 b/playbook/shopfloor-setup/run-enrollment.ps1 index 7af5a53..f2820fa 100755 --- a/playbook/shopfloor-setup/run-enrollment.ps1 +++ b/playbook/shopfloor-setup/run-enrollment.ps1 @@ -33,30 +33,38 @@ $newName = "E$serial" Log "Setting computer name to $newName" Rename-Computer -NewName $newName -Force -ErrorAction SilentlyContinue -# --- Set OOBE complete (must happen before PPKG reboot) --- -Log "Setting OOBE as complete..." -reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Setup\OOBE" /v OOBEComplete /t REG_DWORD /d 1 /f | Out-Null -reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Setup\OOBE" /v SetupDisplayedEula /t REG_DWORD /d 1 /f | Out-Null - # --- Install provisioning package --- -# This triggers an IMMEDIATE reboot. Nothing below this line executes. -# BPRT app installs (Chrome, Office, Tanium, etc.) happen on the next boot. -# The sync_intune scheduled task (registered by Run-ShopfloorSetup.ps1 -# before calling us) fires at the next logon to monitor Intune enrollment. +# IMPORTANT: The PPKG must be installed BEFORE OOBEComplete is set. Bulk +# enrollment PPKGs are designed to run during OOBE; on Windows 11 22H2+ they +# can hang indefinitely if OOBE is already marked complete. +# +# Install-ProvisioningPackage triggers an IMMEDIATE reboot. Nothing below +# this line executes. BPRT app installs (Chrome, Office, Tanium, etc.) happen +# on the next boot. The sync_intune scheduled task (registered by +# Run-ShopfloorSetup.ps1 before calling us) fires at the next logon to +# monitor Intune enrollment. +$ppkgLogDir = "C:\Logs\PPKG" +New-Item -ItemType Directory -Path $ppkgLogDir -Force -ErrorAction SilentlyContinue | Out-Null Log "Installing provisioning package (PPKG will reboot immediately)..." +Log "PPKG diagnostic logs -> $ppkgLogDir" try { - Install-ProvisioningPackage -PackagePath $ppkgFile.FullName -ForceInstall -QuietInstall + Install-ProvisioningPackage -PackagePath $ppkgFile.FullName -ForceInstall -QuietInstall -LogsDirectoryPath $ppkgLogDir Log "Install-ProvisioningPackage returned (reboot may be imminent)." } catch { Log "ERROR: Install-ProvisioningPackage failed: $_" Log "Attempting fallback with Add-ProvisioningPackage..." try { - Add-ProvisioningPackage -PackagePath $ppkgFile.FullName -ForceInstall -QuietInstall + Add-ProvisioningPackage -PackagePath $ppkgFile.FullName -ForceInstall -QuietInstall -LogsDirectoryPath $ppkgLogDir Log "Add-ProvisioningPackage returned." } catch { Log "ERROR: Fallback also failed: $_" } } +# --- Set OOBE complete (only reached if PPKG didn't trigger immediate reboot) --- +Log "Setting OOBE as complete..." +reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Setup\OOBE" /v OOBEComplete /t REG_DWORD /d 1 /f | Out-Null +reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Setup\OOBE" /v SetupDisplayedEula /t REG_DWORD /d 1 /f | Out-Null + # If we get here, the PPKG didn't reboot immediately. Unlikely but handle it. Log "PPKG did not trigger immediate reboot. Returning to caller." diff --git a/playbook/startnet.cmd b/playbook/startnet.cmd index 6cbcfdf..df1df23 100644 --- a/playbook/startnet.cmd +++ b/playbook/startnet.cmd @@ -66,11 +66,11 @@ echo 5. Pro Plus Office (x64) with Access echo 6. Skip enrollment echo. set /p enroll=Enter your choice (1-6): -if "%enroll%"=="1" set PPKG=GCCH_Prod_SFLD_NoOffice_US_Exp_20260430_v4.8.ppkg -if "%enroll%"=="2" set PPKG=GCCH_Prod_SFLD_StdOffice-x86_US_Exp_20260430_v4.8.ppkg -if "%enroll%"=="3" set PPKG=GCCH_Prod_SFLD_StdOffice-x64_US_Exp_20260430_v4.8.ppkg -if "%enroll%"=="4" set PPKG=GCCH_Prod_SFLD_ProPlusOffice-x86_US_Exp_20260430_v4.8.ppkg -if "%enroll%"=="5" set PPKG=GCCH_Prod_SFLD_ProPlusOffice-x64_US_Exp_20260430_v4.8.ppkg +if "%enroll%"=="1" set PPKG=GCCH_Prod_SFLD_NoOffice_US_Exp_20260430_v4.10.ppkg +if "%enroll%"=="2" set PPKG=GCCH_Prod_SFLD_StdOffice-x86_US_Exp_20260430_v4.10.ppkg +if "%enroll%"=="3" set PPKG=GCCH_Prod_SFLD_StdOffice-x64_US_Exp_20260430_v4.10.ppkg +if "%enroll%"=="4" set PPKG=GCCH_Prod_SFLD_ProPlusOffice-x86_US_Exp_20260430_v4.10.ppkg +if "%enroll%"=="5" set PPKG=GCCH_Prod_SFLD_ProPlusOffice-x64_US_Exp_20260430_v4.10.ppkg if "%enroll%"=="6" set PPKG= if "%enroll%"=="" goto enroll_menu @@ -158,7 +158,7 @@ if not "%PCTYPE%"=="" set NEED_ENROLL=1 if "%NEED_ENROLL%"=="0" goto enroll_staged net use Y: \\10.9.100.1\enrollment /user:pxe-upload pxe /persistent:no if "%PPKG%"=="" goto enroll_staged -if not exist "Y:\%PPKG%" ( +if not exist "Y:\ppkgs\%PPKG%" ( echo WARNING: %PPKG% not found on server. Enrollment will be skipped. set PPKG= ) @@ -251,8 +251,8 @@ echo Found Windows at W: mkdir W:\Enrollment 2>NUL REM --- Copy site config (drives site-specific values in all setup scripts) --- -if exist "Y:\site-config.json" ( - copy /Y "Y:\site-config.json" "W:\Enrollment\site-config.json" +if exist "Y:\config\site-config.json" ( + copy /Y "Y:\config\site-config.json" "W:\Enrollment\site-config.json" echo Copied site-config.json. ) else ( echo WARNING: site-config.json not found on enrollment share. @@ -260,14 +260,14 @@ if exist "Y:\site-config.json" ( REM --- Copy PPKG if selected --- if "%PPKG%"=="" goto copy_pctype -copy /Y "Y:\%PPKG%" "W:\Enrollment\%PPKG%" +copy /Y "Y:\ppkgs\%PPKG%" "W:\Enrollment\%PPKG%" if errorlevel 1 ( echo WARNING: Failed to copy enrollment package. goto copy_pctype ) -copy /Y "Y:\run-enrollment.ps1" "W:\Enrollment\run-enrollment.ps1" -copy /Y "Y:\wait-for-internet.ps1" "W:\Enrollment\wait-for-internet.ps1" -copy /Y "Y:\migrate-to-wifi.ps1" "W:\Enrollment\migrate-to-wifi.ps1" +copy /Y "Y:\scripts\run-enrollment.ps1" "W:\Enrollment\run-enrollment.ps1" +copy /Y "Y:\scripts\wait-for-internet.ps1" "W:\Enrollment\wait-for-internet.ps1" +copy /Y "Y:\scripts\migrate-to-wifi.ps1" "W:\Enrollment\migrate-to-wifi.ps1" REM --- Create enroll.cmd at drive root as manual fallback --- > W:\enroll.cmd ( @@ -307,15 +307,15 @@ if exist "Y:\shopfloor-setup\%PCTYPE%" ( ) REM --- Stage preinstall bundle (apps installed locally to save Azure bandwidth) --- -if exist "Y:\preinstall\preinstall.json" ( +if exist "Y:\pre-install\preinstall.json" ( mkdir W:\PreInstall 2>NUL mkdir W:\PreInstall\installers 2>NUL - copy /Y "Y:\preinstall\preinstall.json" "W:\PreInstall\preinstall.json" - if exist "Y:\preinstall\installers" ( - xcopy /E /Y /I "Y:\preinstall\installers" "W:\PreInstall\installers\" + copy /Y "Y:\pre-install\preinstall.json" "W:\PreInstall\preinstall.json" + if exist "Y:\pre-install\installers" ( + xcopy /E /Y /I "Y:\pre-install\installers" "W:\PreInstall\installers\" echo Staged preinstall bundle to W:\PreInstall. ) else ( - echo WARNING: Y:\preinstall\installers not found - preinstall.json staged without installers. + echo WARNING: Y:\pre-install\installers not found - preinstall.json staged without installers. ) ) else ( echo No preinstall bundle on PXE server - skipping. @@ -329,9 +329,9 @@ REM during shopfloor-setup (Azure DSC provisions those creds later), so this REM bootstrap exists to get the first-install through. Post-imaging, the logon- REM triggered CMM-Enforce.ps1 takes over from the share. if /i not "%PCTYPE%"=="CMM" goto skip_cmm_stage -if exist "Y:\cmm-installers\cmm-manifest.json" ( +if exist "Y:\installers-post\cmm\cmm-manifest.json" ( mkdir W:\CMM-Install 2>NUL - xcopy /E /Y /I "Y:\cmm-installers" "W:\CMM-Install\" + xcopy /E /Y /I "Y:\installers-post\cmm" "W:\CMM-Install\" echo Staged CMM bootstrap to W:\CMM-Install. ) else ( echo WARNING: Y:\cmm-installers not found - CMM PC cannot install Hexagon apps at imaging time. diff --git a/Download-Drivers.ps1 b/scripts/Download-Drivers.ps1 similarity index 100% rename from Download-Drivers.ps1 rename to scripts/Download-Drivers.ps1 diff --git a/Upload-Image.ps1 b/scripts/Upload-Image.ps1 similarity index 100% rename from Upload-Image.ps1 rename to scripts/Upload-Image.ps1 diff --git a/build-proxmox-iso.sh b/scripts/build-proxmox-iso.sh similarity index 96% rename from build-proxmox-iso.sh rename to scripts/build-proxmox-iso.sh index 353f248..2b27707 100755 --- a/build-proxmox-iso.sh +++ b/scripts/build-proxmox-iso.sh @@ -22,13 +22,13 @@ set -euo pipefail -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -AUTOINSTALL_DIR="$SCRIPT_DIR/autoinstall" -PLAYBOOK_DIR="$SCRIPT_DIR/playbook" -OFFLINE_PKG_DIR="$SCRIPT_DIR/offline-packages" -WEBAPP_DIR="$SCRIPT_DIR/webapp" -PIP_WHEELS_DIR="$SCRIPT_DIR/pip-wheels" -BOOT_TOOLS_DIR="$SCRIPT_DIR/boot-tools" +REPO_ROOT="$(cd "$(dirname "$0")"/.. && pwd)" +AUTOINSTALL_DIR="$REPO_ROOT/autoinstall" +PLAYBOOK_DIR="$REPO_ROOT/playbook" +OFFLINE_PKG_DIR="$REPO_ROOT/offline-packages" +WEBAPP_DIR="$REPO_ROOT/webapp" +PIP_WHEELS_DIR="$REPO_ROOT/pip-wheels" +BOOT_TOOLS_DIR="$REPO_ROOT/boot-tools" # --- Validate arguments --- if [ $# -lt 1 ]; then @@ -43,7 +43,7 @@ if [ $# -lt 1 ]; then fi UBUNTU_ISO="$(realpath "$1")" -OUTPUT_ISO="${2:-$SCRIPT_DIR/pxe-server-proxmox.iso}" +OUTPUT_ISO="${2:-$REPO_ROOT/pxe-server-proxmox.iso}" # --- Validate prerequisites --- echo "============================================" @@ -88,7 +88,7 @@ fi echo "Ubuntu ISO : $UBUNTU_ISO" echo "Output ISO : $OUTPUT_ISO" -echo "Source Dir : $SCRIPT_DIR" +echo "Source Dir : $REPO_ROOT" echo "" # --- Setup work directory with cleanup trap --- @@ -249,7 +249,7 @@ if [ -d "$PIP_WHEELS_DIR" ]; then fi # WinPE boot files (wimboot, boot.wim, BCD, ipxe.efi, etc.) -BOOT_FILES_DIR="$SCRIPT_DIR/boot-files" +BOOT_FILES_DIR="$REPO_ROOT/boot-files" if [ -d "$BOOT_FILES_DIR" ]; then BOOT_FILE_COUNT=0 for bf in "$BOOT_FILES_DIR"/*; do diff --git a/build-usb.sh b/scripts/build-usb.sh similarity index 79% rename from build-usb.sh rename to scripts/build-usb.sh index b1c5c67..4a1b0e9 100755 --- a/build-usb.sh +++ b/scripts/build-usb.sh @@ -17,10 +17,10 @@ set -euo pipefail -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -AUTOINSTALL_DIR="$SCRIPT_DIR/autoinstall" -PLAYBOOK_DIR="$SCRIPT_DIR/playbook" -OFFLINE_PKG_DIR="$SCRIPT_DIR/offline-packages" +REPO_ROOT="$(cd "$(dirname "$0")"/.. && pwd)" +AUTOINSTALL_DIR="$REPO_ROOT/autoinstall" +PLAYBOOK_DIR="$REPO_ROOT/playbook" +OFFLINE_PKG_DIR="$REPO_ROOT/offline-packages" # --- Validate arguments --- if [ $# -lt 2 ]; then @@ -87,7 +87,7 @@ echo "PXE Server USB Builder" echo "============================================" echo "USB Device : $USB_DEV" echo "ISO : $ISO_PATH" -echo "Source Dir : $SCRIPT_DIR" +echo "Source Dir : $REPO_ROOT" echo "" echo "This will ERASE all data on $USB_DEV." read -rp "Continue? (y/N): " PROCEED @@ -139,7 +139,7 @@ xorriso -as mkisofs -r \ -no-emul-boot \ -boot-load-size 10160 \ . 2>/dev/null -cd "$SCRIPT_DIR" +cd "$REPO_ROOT" echo " ISO rebuilt with 'autoinstall' kernel param and 5s GRUB timeout" echo " Writing patched ISO to $USB_DEV..." @@ -216,7 +216,7 @@ cp -r "$PLAYBOOK_DIR" "$MOUNT_POINT/playbook" echo " Copied playbook/" # Copy webapp -WEBAPP_DIR="$SCRIPT_DIR/webapp" +WEBAPP_DIR="$REPO_ROOT/webapp" if [ -d "$WEBAPP_DIR" ]; then mkdir -p "$MOUNT_POINT/webapp" cp -r "$WEBAPP_DIR/app.py" "$WEBAPP_DIR/requirements.txt" "$MOUNT_POINT/webapp/" @@ -225,7 +225,7 @@ if [ -d "$WEBAPP_DIR" ]; then fi # Copy pip wheels for offline Flask install -PIP_WHEELS_DIR="$SCRIPT_DIR/pip-wheels" +PIP_WHEELS_DIR="$REPO_ROOT/pip-wheels" if [ ! -d "$PIP_WHEELS_DIR" ]; then echo " pip-wheels/ not found — downloading now..." mkdir -p "$PIP_WHEELS_DIR" @@ -245,7 +245,7 @@ if [ -d "$PIP_WHEELS_DIR" ]; then fi # Copy WinPE boot files (wimboot, boot.wim, BCD, ipxe.efi, etc.) -BOOT_FILES_DIR="$SCRIPT_DIR/boot-files" +BOOT_FILES_DIR="$REPO_ROOT/boot-files" if [ -d "$BOOT_FILES_DIR" ]; then BOOT_FILE_COUNT=0 for bf in "$BOOT_FILES_DIR"/*; do @@ -261,7 +261,7 @@ else fi # Copy boot tools (Clonezilla, Blancco, Memtest) if prepared -BOOT_TOOLS_DIR="$SCRIPT_DIR/boot-tools" +BOOT_TOOLS_DIR="$REPO_ROOT/boot-tools" if [ -d "$BOOT_TOOLS_DIR" ]; then cp -r "$BOOT_TOOLS_DIR" "$MOUNT_POINT/boot-tools" TOOLS_SIZE=$(du -sh "$MOUNT_POINT/boot-tools" | cut -f1) @@ -271,25 +271,68 @@ else fi # Copy enrollment directory (PPKGs, run-enrollment.ps1) if present -ENROLLMENT_DIR="$SCRIPT_DIR/enrollment" +# FAT32 has a 4GB max file size; files larger than that are split into chunks +# that the playbook reassembles with `cat`. +ENROLLMENT_DIR="$REPO_ROOT/enrollment" +FAT32_MAX=$((3500 * 1024 * 1024)) # 3500 MiB chunks, safely under 4GiB FAT32 limit if [ -d "$ENROLLMENT_DIR" ]; then mkdir -p "$MOUNT_POINT/enrollment" - cp -r "$ENROLLMENT_DIR"/* "$MOUNT_POINT/enrollment/" 2>/dev/null || true - PPKG_COUNT=$(find "$MOUNT_POINT/enrollment" -name '*.ppkg' 2>/dev/null | wc -l) + SPLIT_COUNT=0 + for f in "$ENROLLMENT_DIR"/*; do + [ -e "$f" ] || continue + bn="$(basename "$f")" + if [ -f "$f" ] && [ "$(stat -c%s "$f")" -gt "$FAT32_MAX" ]; then + echo " Splitting $bn (>$((FAT32_MAX / 1024 / 1024))M) into chunks..." + split -b "$FAT32_MAX" -d -a 2 "$f" "$MOUNT_POINT/enrollment/${bn}.part." + SPLIT_COUNT=$((SPLIT_COUNT + 1)) + else + cp -r "$f" "$MOUNT_POINT/enrollment/" + fi + done + PPKG_COUNT=$(find "$ENROLLMENT_DIR" -maxdepth 1 -name '*.ppkg' 2>/dev/null | wc -l) ENROLL_SIZE=$(du -sh "$MOUNT_POINT/enrollment" | cut -f1) - echo " Copied enrollment/ ($ENROLL_SIZE, $PPKG_COUNT PPKGs)" + echo " Copied enrollment/ ($ENROLL_SIZE, $PPKG_COUNT PPKGs, $SPLIT_COUNT split)" else echo " No enrollment/ directory found (PPKGs can be uploaded via webapp later)" fi +# Copy BIOS update binaries if staged +BIOS_DIR="$REPO_ROOT/bios-staging" +if [ -d "$BIOS_DIR" ] && [ "$(ls -A "$BIOS_DIR" 2>/dev/null)" ]; then + echo " Copying BIOS update binaries from bios-staging/..." + mkdir -p "$MOUNT_POINT/bios" + cp -r "$BIOS_DIR"/* "$MOUNT_POINT/bios/" 2>/dev/null || true + BIOS_COUNT=$(find "$MOUNT_POINT/bios" -name '*.exe' 2>/dev/null | wc -l) + BIOS_SIZE=$(du -sh "$MOUNT_POINT/bios" | cut -f1) + echo " Copied bios/ ($BIOS_SIZE, $BIOS_COUNT files)" +else + echo " No bios-staging/ found (BIOS updates can be pushed via download-drivers.py later)" +fi + # Copy Dell driver packs if staged -DRIVERS_DIR="$SCRIPT_DIR/drivers-staging" +# Files larger than the FAT32 4GB limit are split into chunks; the playbook +# reassembles them on the server. +DRIVERS_DIR="$REPO_ROOT/drivers-staging" if [ -d "$DRIVERS_DIR" ] && [ "$(ls -A "$DRIVERS_DIR" 2>/dev/null)" ]; then echo " Copying Dell driver packs from drivers-staging/..." mkdir -p "$MOUNT_POINT/drivers" - cp -r "$DRIVERS_DIR"/* "$MOUNT_POINT/drivers/" 2>/dev/null || true + DRV_SPLIT=0 + # Mirror directory tree first (fast) + (cd "$DRIVERS_DIR" && find . -type d -exec mkdir -p "$MOUNT_POINT/drivers/{}" \;) + # Copy files <4GB directly, split files >=4GB into chunks + while IFS= read -r f; do + rel="${f#$DRIVERS_DIR/}" + dest="$MOUNT_POINT/drivers/$rel" + if [ "$(stat -c%s "$f")" -gt "$FAT32_MAX" ]; then + echo " Splitting $rel..." + split -b "$FAT32_MAX" -d -a 2 "$f" "${dest}.part." + DRV_SPLIT=$((DRV_SPLIT + 1)) + else + cp "$f" "$dest" + fi + done < <(find "$DRIVERS_DIR" -type f) DRIVERS_SIZE=$(du -sh "$MOUNT_POINT/drivers" | cut -f1) - echo " Copied drivers/ ($DRIVERS_SIZE)" + echo " Copied drivers/ ($DRIVERS_SIZE, $DRV_SPLIT split)" else echo " No drivers-staging/ found (drivers can be downloaded later)" fi diff --git a/scripts/deploy-bios.sh b/scripts/deploy-bios.sh new file mode 100755 index 0000000..d461e7a --- /dev/null +++ b/scripts/deploy-bios.sh @@ -0,0 +1,50 @@ +#!/bin/bash +# deploy-bios.sh - Deploy BIOS update files to a running PXE server +# Copies Flash64W.exe, BIOS binaries, models.txt, and check-bios.cmd +# +# Usage: ./deploy-bios.sh [server-ip] +# Default server: 10.9.100.1 + +set -e + +REPO_ROOT="$(cd "$(dirname "$0")"/.. && pwd)" +PXE_SERVER="${1:-10.9.100.1}" +PXE_USER="pxe" +PXE_PASS="pxe" +REMOTE_DIR="/srv/samba/enrollment/BIOS" +BIOS_DIR="$REPO_ROOT/bios-staging" +MANIFEST="$REPO_ROOT/playbook/shopfloor-setup/BIOS/models.txt" +CHECK_SCRIPT="$REPO_ROOT/playbook/shopfloor-setup/BIOS/check-bios.cmd" + +SSH="sshpass -p $PXE_PASS ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 $PXE_USER@$PXE_SERVER" +SCP="sshpass -p $PXE_PASS scp -o StrictHostKeyChecking=no -o ConnectTimeout=10" + +# Verify sources exist +if [ ! -d "$BIOS_DIR" ] || [ -z "$(ls -A "$BIOS_DIR" 2>/dev/null)" ]; then + echo "ERROR: bios-staging/ is empty or missing. Run ./pull-bios.sh first." + exit 1 +fi + +if [ ! -f "$MANIFEST" ]; then + echo "ERROR: playbook/shopfloor-setup/BIOS/models.txt not found." + exit 1 +fi + +echo "Deploying BIOS files to $PXE_SERVER..." + +# Create remote directory +$SSH "sudo mkdir -p '$REMOTE_DIR' && sudo chown $PXE_USER:$PXE_USER '$REMOTE_DIR'" + +# Copy check-bios.cmd and models.txt +echo " Copying check-bios.cmd + models.txt..." +$SCP "$CHECK_SCRIPT" "$MANIFEST" "$PXE_USER@$PXE_SERVER:$REMOTE_DIR/" + +# Copy BIOS binaries +COUNT=$(find "$BIOS_DIR" -name '*.exe' | wc -l) +SIZE=$(du -sh "$BIOS_DIR" | cut -f1) +echo " Copying $COUNT BIOS binaries ($SIZE)..." +$SCP "$BIOS_DIR"/*.exe "$PXE_USER@$PXE_SERVER:$REMOTE_DIR/" + +# Verify +REMOTE_COUNT=$($SSH "find '$REMOTE_DIR' -name '*.exe' | wc -l") +echo "Done: $REMOTE_COUNT files on $PXE_SERVER:$REMOTE_DIR" diff --git a/download-drivers.py b/scripts/download-drivers.py similarity index 100% rename from download-drivers.py rename to scripts/download-drivers.py diff --git a/download-packages.sh b/scripts/download-packages.sh similarity index 95% rename from download-packages.sh rename to scripts/download-packages.sh index 507c7d5..d74807d 100755 --- a/download-packages.sh +++ b/scripts/download-packages.sh @@ -30,7 +30,7 @@ if [ "${IN_DOCKER:-}" != "1" ] && [ "$HOST_CODENAME" != "noble" ]; then fi SCRIPT_PATH="$(readlink -f "$0")" - REPO_DIR="$(dirname "$SCRIPT_PATH")" + REPO_DIR="$(cd "$(dirname "$SCRIPT_PATH")"/.. && pwd)" mkdir -p "$OUT_DIR_ABS" docker run --rm -i \ @@ -39,7 +39,7 @@ if [ "${IN_DOCKER:-}" != "1" ] && [ "$HOST_CODENAME" != "noble" ]; then -e IN_DOCKER=1 \ -w /repo \ ubuntu:24.04 \ - bash -c "apt-get update -qq && apt-get install -y --no-install-recommends sudo python3-pip python3-setuptools python3-wheel ca-certificates >/dev/null && /repo/download-packages.sh /out" + bash -c "apt-get update -qq && apt-get install -y --no-install-recommends sudo python3-pip python3-setuptools python3-wheel ca-certificates >/dev/null && /repo/scripts/download-packages.sh /out" echo "" echo "============================================" @@ -127,8 +127,8 @@ echo " $DEB_COUNT packages ($TOTAL_SIZE)" # Download pip wheels for Flask webapp (offline install) echo "[4/4] Downloading Python wheels for webapp..." # Place pip-wheels next to the script (or /repo when in docker), not next to OUT_DIR -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PIP_DIR="$SCRIPT_DIR/pip-wheels" +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")"/.. && pwd)" +PIP_DIR="$REPO_ROOT/pip-wheels" mkdir -p "$PIP_DIR" pip3 download -d "$PIP_DIR" flask lxml 2>&1 | tail -5 diff --git a/prepare-boot-tools.sh b/scripts/prepare-boot-tools.sh similarity index 97% rename from prepare-boot-tools.sh rename to scripts/prepare-boot-tools.sh index 6110b9e..5c728a8 100755 --- a/prepare-boot-tools.sh +++ b/scripts/prepare-boot-tools.sh @@ -15,13 +15,13 @@ set -euo pipefail -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -OUT_DIR="$SCRIPT_DIR/boot-tools" +REPO_ROOT="$(cd "$(dirname "$0")"/.. && pwd)" +OUT_DIR="$REPO_ROOT/boot-tools" BLANCCO_ISO="${1:-}" # Auto-detect Blancco ISO in project directory if [ -z "$BLANCCO_ISO" ]; then - BLANCCO_ISO=$(find "$SCRIPT_DIR" -maxdepth 1 -name '*DriveEraser*.iso' -o -name '*blancco*.iso' 2>/dev/null | head -1) + BLANCCO_ISO=$(find "$REPO_ROOT" -maxdepth 1 -name '*DriveEraser*.iso' -o -name '*blancco*.iso' 2>/dev/null | head -1) fi mkdir -p "$OUT_DIR"/{clonezilla,blancco,memtest} @@ -192,7 +192,7 @@ PYEOF dd bs=512 conv=sync 2>/dev/null > "$OUT_DIR/blancco/config.img" echo " Reports: SMB blancco@10.9.100.1/blancco-reports, bootable report enabled" fi - cd "$SCRIPT_DIR" + cd "$REPO_ROOT" rm -rf "$CFGTMP" fi @@ -241,7 +241,7 @@ PYEOF # --- Build GRUB EFI binary for Blancco chainload --- # Broadcom iPXE can't pass initrd to Linux kernels in UEFI mode. # Solution: iPXE chains to grubx64.efi, which loads kernel+initrd via TFTP. - GRUB_CFG="$SCRIPT_DIR/boot-tools/blancco/grub-blancco.cfg" + GRUB_CFG="$REPO_ROOT/boot-tools/blancco/grub-blancco.cfg" if [ -f "$GRUB_CFG" ]; then if command -v grub-mkstandalone &>/dev/null; then echo " Building grubx64.efi (GRUB chainload for Blancco)..." diff --git a/scripts/pull-bios.sh b/scripts/pull-bios.sh new file mode 100755 index 0000000..be43054 --- /dev/null +++ b/scripts/pull-bios.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# pull-bios.sh - Pull BIOS update binaries from prod PXE server to bios-staging/ +# Run this with the USB NIC plugged in, before building the USB. + +set -e + +REPO_ROOT="$(cd "$(dirname "$0")"/.. && pwd)" +DEST="$REPO_ROOT/bios-staging" +PXE_SERVER="10.9.100.1" +PXE_USER="pxe" +PXE_PASS="pxe" + +mkdir -p "$DEST" + +echo "Pulling BIOS binaries from $PXE_SERVER..." +sshpass -p "$PXE_PASS" scp -o StrictHostKeyChecking=no -o ConnectTimeout=10 \ + "$PXE_USER@$PXE_SERVER:/srv/samba/enrollment/BIOS/*.exe" "$DEST/" + +COUNT=$(find "$DEST" -name '*.exe' | wc -l) +SIZE=$(du -sh "$DEST" | cut -f1) +echo "Done: $COUNT files ($SIZE) in bios-staging/" diff --git a/sync_hardware_models.py b/scripts/sync_hardware_models.py similarity index 100% rename from sync_hardware_models.py rename to scripts/sync_hardware_models.py diff --git a/setup-guide-original.txt b/setup-guide-original.txt deleted file mode 100644 index 2eaa0f6..0000000 --- a/setup-guide-original.txt +++ /dev/null @@ -1,129 +0,0 @@ -Purpose -Document a repeatable, “build-from-scratch” procedure for deploying an Ubuntu-based PXE boot server that can host GE Aerospace Windows PE images. - -Prerequisites -Hardware: Server or PC with ≥ 8 GB RAM, ≥ 250 GB disk, and one NIC (one for build / Internet, one for isolated PXE LAN) - -https://myaccess.microsoft.us/@ge.onmicrosoft.us#/access-packages/active - -EPM Rufus Exception Request -EPM DT Functions -DLP - Encrypted Removable (USB) Long Term Access - -Software: - -Ubuntu Server 24.04 ISO - -Rufus (latest) - -playbook folder containing pxe_server_setup.yml and supporting files - -GE Aerospace Media Creator LITE (for caching WinPE images) - -Two USB thumb drives (one ≥ 8 GB for Ubuntu install; one ≥ 32 GB for WinPE media) - -Step-by-Step Procedure -Create the Ubuntu Server installer USB -1.1 Download Ubuntu Server 24.04 from https://ubuntu.com/download/server. -1.2 Download and run Rufus (https://rufus.ie/en/). -1.3 Insert an empty USB, select it in Rufus. -1.4 Click Select, browse to the Ubuntu ISO, then click Start. -1.5 When Rufus finishes, copy your playbook folder to the root of that same USB, then eject it safely. - -Install Ubuntu on the PXE server -2.1 Insert the USB into the target machine and power on. -2.2 Press F12 (or the vendor’s one-time boot key) and choose the USB device. -2.3 Follow Ubuntu’s installer; -Network configuration screen. -Select the fist option select give it random network and IPv4 address -Then select WiFi and choose the guest network. -Follow the prompts and enter the information for your network. -Click done. - -You do not need a proxy hit done. -For mirror address add nothing and hit done. The download should start. - -After that select next -You'll be in file system summary: Hit done, box will pop up "confirm destructive action" select "continue" - -Configure your profile. Done -Skip the upgrade to ubuntu pro -No ssh -Don't select featured server snaps just select done - -Ubuntu will install…..then reboot your system -2.4 Create a user (e.g., pxe) with a simple, temporary password (change later). - -Prepare the OS -3.1 Log in as the user you created. - -3.2 Update the system: - -bash -Copy -sudo apt update && sudo apt upgrade -y - -3.3 Install Ansible: - -bash -Copy -sudo apt install ansible -y -Mount the installer USB and run the playbook - -4.1 Identify the USB device: - -bash -Copy -lsblk -Note the device (e.g., /dev/sda1). - -4.2 Mount it and run the playbook: - -bash -Copy -sudo mkdir -p /mnt/usb -sudo mount /dev/sda1 /mnt/usb -cd /mnt/usb/playbook -ansible-playbook pxe_server_setup.yml - - -4.3 When Ansible finishes, umount the USB: - -bash -Copy -cd ~ -sudo umount /mnt/usb - -Cache Windows PE images -5.1 On a separate workstation, use GE Aerospace Media Creator LITE to cache all desired images (or start with one). -5.2 Create a WinPE USB using the same tool and eject it safely. - -Import WinPE content to the PXE share -6.1 Insert the WinPE USB into the PXE server. -6.2 Find the new device (e.g., /dev/sdb2) with lsblk. -6.3 Mount it and copy files: - -bash -Copy -sudo mkdir -p /mnt/usb2 -sudo mount /dev/sdb2 /mnt/usb2 -sudo cp -r /mnt/usb2/. /srv/samba/winpeapps/standard -sudo umount /mnt/usb2 -Finalise and isolate - -7.1 Reboot the server: - -bash -Copy -sudo reboot - -7.2 After it comes back up, move the primary NIC from the Internet-enabled network to the isolated switch that will serve PXE clients. - -6. Verification -Connect a test workstation to the isolated switch. - -In BIOS/UEFI, set Network Boot (PXE) as first boot, then boot. - -Confirm the client pulls an IP from the PXE server and sees the WinPE menu. - -Launch a WinPE image to ensure TFTP, HTTP (NBD), and SMB shares respond correctly. diff --git a/startnet-template.cmd b/startnet-template.cmd index 6cbcfdf..df1df23 100644 --- a/startnet-template.cmd +++ b/startnet-template.cmd @@ -66,11 +66,11 @@ echo 5. Pro Plus Office (x64) with Access echo 6. Skip enrollment echo. set /p enroll=Enter your choice (1-6): -if "%enroll%"=="1" set PPKG=GCCH_Prod_SFLD_NoOffice_US_Exp_20260430_v4.8.ppkg -if "%enroll%"=="2" set PPKG=GCCH_Prod_SFLD_StdOffice-x86_US_Exp_20260430_v4.8.ppkg -if "%enroll%"=="3" set PPKG=GCCH_Prod_SFLD_StdOffice-x64_US_Exp_20260430_v4.8.ppkg -if "%enroll%"=="4" set PPKG=GCCH_Prod_SFLD_ProPlusOffice-x86_US_Exp_20260430_v4.8.ppkg -if "%enroll%"=="5" set PPKG=GCCH_Prod_SFLD_ProPlusOffice-x64_US_Exp_20260430_v4.8.ppkg +if "%enroll%"=="1" set PPKG=GCCH_Prod_SFLD_NoOffice_US_Exp_20260430_v4.10.ppkg +if "%enroll%"=="2" set PPKG=GCCH_Prod_SFLD_StdOffice-x86_US_Exp_20260430_v4.10.ppkg +if "%enroll%"=="3" set PPKG=GCCH_Prod_SFLD_StdOffice-x64_US_Exp_20260430_v4.10.ppkg +if "%enroll%"=="4" set PPKG=GCCH_Prod_SFLD_ProPlusOffice-x86_US_Exp_20260430_v4.10.ppkg +if "%enroll%"=="5" set PPKG=GCCH_Prod_SFLD_ProPlusOffice-x64_US_Exp_20260430_v4.10.ppkg if "%enroll%"=="6" set PPKG= if "%enroll%"=="" goto enroll_menu @@ -158,7 +158,7 @@ if not "%PCTYPE%"=="" set NEED_ENROLL=1 if "%NEED_ENROLL%"=="0" goto enroll_staged net use Y: \\10.9.100.1\enrollment /user:pxe-upload pxe /persistent:no if "%PPKG%"=="" goto enroll_staged -if not exist "Y:\%PPKG%" ( +if not exist "Y:\ppkgs\%PPKG%" ( echo WARNING: %PPKG% not found on server. Enrollment will be skipped. set PPKG= ) @@ -251,8 +251,8 @@ echo Found Windows at W: mkdir W:\Enrollment 2>NUL REM --- Copy site config (drives site-specific values in all setup scripts) --- -if exist "Y:\site-config.json" ( - copy /Y "Y:\site-config.json" "W:\Enrollment\site-config.json" +if exist "Y:\config\site-config.json" ( + copy /Y "Y:\config\site-config.json" "W:\Enrollment\site-config.json" echo Copied site-config.json. ) else ( echo WARNING: site-config.json not found on enrollment share. @@ -260,14 +260,14 @@ if exist "Y:\site-config.json" ( REM --- Copy PPKG if selected --- if "%PPKG%"=="" goto copy_pctype -copy /Y "Y:\%PPKG%" "W:\Enrollment\%PPKG%" +copy /Y "Y:\ppkgs\%PPKG%" "W:\Enrollment\%PPKG%" if errorlevel 1 ( echo WARNING: Failed to copy enrollment package. goto copy_pctype ) -copy /Y "Y:\run-enrollment.ps1" "W:\Enrollment\run-enrollment.ps1" -copy /Y "Y:\wait-for-internet.ps1" "W:\Enrollment\wait-for-internet.ps1" -copy /Y "Y:\migrate-to-wifi.ps1" "W:\Enrollment\migrate-to-wifi.ps1" +copy /Y "Y:\scripts\run-enrollment.ps1" "W:\Enrollment\run-enrollment.ps1" +copy /Y "Y:\scripts\wait-for-internet.ps1" "W:\Enrollment\wait-for-internet.ps1" +copy /Y "Y:\scripts\migrate-to-wifi.ps1" "W:\Enrollment\migrate-to-wifi.ps1" REM --- Create enroll.cmd at drive root as manual fallback --- > W:\enroll.cmd ( @@ -307,15 +307,15 @@ if exist "Y:\shopfloor-setup\%PCTYPE%" ( ) REM --- Stage preinstall bundle (apps installed locally to save Azure bandwidth) --- -if exist "Y:\preinstall\preinstall.json" ( +if exist "Y:\pre-install\preinstall.json" ( mkdir W:\PreInstall 2>NUL mkdir W:\PreInstall\installers 2>NUL - copy /Y "Y:\preinstall\preinstall.json" "W:\PreInstall\preinstall.json" - if exist "Y:\preinstall\installers" ( - xcopy /E /Y /I "Y:\preinstall\installers" "W:\PreInstall\installers\" + copy /Y "Y:\pre-install\preinstall.json" "W:\PreInstall\preinstall.json" + if exist "Y:\pre-install\installers" ( + xcopy /E /Y /I "Y:\pre-install\installers" "W:\PreInstall\installers\" echo Staged preinstall bundle to W:\PreInstall. ) else ( - echo WARNING: Y:\preinstall\installers not found - preinstall.json staged without installers. + echo WARNING: Y:\pre-install\installers not found - preinstall.json staged without installers. ) ) else ( echo No preinstall bundle on PXE server - skipping. @@ -329,9 +329,9 @@ REM during shopfloor-setup (Azure DSC provisions those creds later), so this REM bootstrap exists to get the first-install through. Post-imaging, the logon- REM triggered CMM-Enforce.ps1 takes over from the share. if /i not "%PCTYPE%"=="CMM" goto skip_cmm_stage -if exist "Y:\cmm-installers\cmm-manifest.json" ( +if exist "Y:\installers-post\cmm\cmm-manifest.json" ( mkdir W:\CMM-Install 2>NUL - xcopy /E /Y /I "Y:\cmm-installers" "W:\CMM-Install\" + xcopy /E /Y /I "Y:\installers-post\cmm" "W:\CMM-Install\" echo Staged CMM bootstrap to W:\CMM-Install. ) else ( echo WARNING: Y:\cmm-installers not found - CMM PC cannot install Hexagon apps at imaging time. diff --git a/test-lab.sh b/test-lab.sh deleted file mode 100755 index 637febf..0000000 --- a/test-lab.sh +++ /dev/null @@ -1,332 +0,0 @@ -#!/bin/bash -# -# test-lab.sh — Full PXE lab: server + client VMs on an isolated network -# -# Creates an isolated libvirt network, boots the PXE server from the -# Proxmox installer ISO, then launches a UEFI PXE client to test the -# full boot chain (DHCP -> TFTP -> iPXE -> boot menu). -# -# Usage: -# ./test-lab.sh /path/to/ubuntu-24.04.iso # Launch server -# ./test-lab.sh --client # Launch PXE client -# ./test-lab.sh --status # Check if server is ready -# ./test-lab.sh --destroy # Remove everything -# -# Workflow: -# 1. Run the script with the Ubuntu ISO — server VM starts installing -# 2. Wait ~15 minutes (monitor with: sudo virsh console pxe-lab-server) -# 3. Run --status to check if PXE services are up -# 4. Run --client to launch a PXE client VM -# 5. Open virt-viewer to watch the client PXE boot: -# virt-viewer pxe-lab-client - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -NET_NAME="pxe-lab" -SERVER_NAME="pxe-lab-server" -CLIENT_NAME="pxe-lab-client" -SERVER_DISK="/var/lib/libvirt/images/${SERVER_NAME}.qcow2" -PROXMOX_ISO="$SCRIPT_DIR/pxe-server-proxmox.iso" -VM_RAM=4096 -VM_CPUS=2 -VM_DISK_SIZE=40 # GB - -# --- Helper: check if we can run virsh --- -check_virsh() { - if ! virsh net-list &>/dev/null; then - echo "ERROR: Cannot connect to libvirt. Are you in the 'libvirt' group?" - echo " sudo usermod -aG libvirt $USER && newgrp libvirt" - exit 1 - fi -} - -# --- Helper: ensure network exists --- -ensure_network() { - if ! virsh net-info "$NET_NAME" &>/dev/null; then - echo "Creating isolated network ($NET_NAME)..." - NET_XML=$(mktemp) - cat > "$NET_XML" << EOF - - $NET_NAME - - -EOF - virsh net-define "$NET_XML" >/dev/null - rm "$NET_XML" - fi - if ! virsh net-info "$NET_NAME" 2>/dev/null | grep -q "Active:.*yes"; then - virsh net-start "$NET_NAME" >/dev/null - fi -} - -# ===================================================================== -# --destroy: Remove everything -# ===================================================================== -if [ "${1:-}" = "--destroy" ]; then - check_virsh - echo "Destroying PXE lab environment..." - virsh destroy "$CLIENT_NAME" 2>/dev/null || true - virsh undefine "$CLIENT_NAME" --nvram 2>/dev/null || true - virsh vol-delete "${CLIENT_NAME}.qcow2" --pool default 2>/dev/null || true - virsh destroy "$SERVER_NAME" 2>/dev/null || true - virsh undefine "$SERVER_NAME" 2>/dev/null || true - virsh vol-delete "${SERVER_NAME}.qcow2" --pool default 2>/dev/null || true - rm -f "/tmp/${SERVER_NAME}-vmlinuz" "/tmp/${SERVER_NAME}-initrd" - virsh net-destroy "$NET_NAME" 2>/dev/null || true - virsh net-undefine "$NET_NAME" 2>/dev/null || true - echo "Done." - exit 0 -fi - -# ===================================================================== -# --status: Check if PXE server is ready -# ===================================================================== -if [ "${1:-}" = "--status" ]; then - check_virsh - echo "PXE Lab Status" - echo "============================================" - - # Network - if virsh net-info "$NET_NAME" &>/dev/null; then - echo " Network ($NET_NAME): $(virsh net-info "$NET_NAME" 2>/dev/null | grep Active | awk '{print $2}')" - else - echo " Network ($NET_NAME): not defined" - fi - - # Server VM - if virsh dominfo "$SERVER_NAME" &>/dev/null; then - STATE=$(virsh domstate "$SERVER_NAME" 2>/dev/null) - echo " Server ($SERVER_NAME): $STATE" - else - echo " Server ($SERVER_NAME): not defined" - fi - - # Client VM - if virsh dominfo "$CLIENT_NAME" &>/dev/null; then - STATE=$(virsh domstate "$CLIENT_NAME" 2>/dev/null) - echo " Client ($CLIENT_NAME): $STATE" - else - echo " Client ($CLIENT_NAME): not defined" - fi - - # Try to check PXE services on the server - # Add a temporary IP to the bridge so we can reach the server - echo "" - echo "Checking PXE server services..." - BRIDGE_HAS_IP=false - ADDED_IP=false - if ip addr show virbr-pxe 2>/dev/null | grep -q "10.9.100.254"; then - BRIDGE_HAS_IP=true - else - # Need sudo for IP manipulation — try it, skip if unavailable - if sudo -n ip addr add 10.9.100.254/24 dev virbr-pxe 2>/dev/null; then - BRIDGE_HAS_IP=true - ADDED_IP=true - fi - fi - - if [ "$BRIDGE_HAS_IP" = true ]; then - # Check each service with a short timeout - for check in \ - "DHCP/TFTP (dnsmasq):10.9.100.1:69:udp" \ - "HTTP (Apache):10.9.100.1:80:tcp" \ - "iPXE boot script:10.9.100.1:4433:tcp" \ - "Samba:10.9.100.1:445:tcp" \ - "Webapp:10.9.100.1:9009:tcp"; do - LABEL="${check%%:*}" - REST="${check#*:}" - HOST="${REST%%:*}" - REST="${REST#*:}" - PORT="${REST%%:*}" - PROTO="${REST#*:}" - - if [ "$PROTO" = "tcp" ]; then - if timeout 2 bash -c "echo >/dev/tcp/$HOST/$PORT" 2>/dev/null; then - echo " [UP] $LABEL (port $PORT)" - else - echo " [DOWN] $LABEL (port $PORT)" - fi - else - # UDP — just check if host is reachable - if ping -c1 -W1 "$HOST" &>/dev/null; then - echo " [PING] $LABEL (host reachable)" - else - echo " [DOWN] $LABEL (host unreachable)" - fi - fi - done - - # Clean up the temporary IP (only if we added it) - if [ "$ADDED_IP" = true ]; then - sudo -n ip addr del 10.9.100.254/24 dev virbr-pxe 2>/dev/null || true - fi - else - echo " (Cannot reach server — bridge not available)" - echo " Use 'sudo virsh console $SERVER_NAME' to check manually" - fi - - echo "" - echo "Commands:" - echo " sudo virsh console $SERVER_NAME # Server serial console" - echo " virt-viewer $CLIENT_NAME # Client VNC display" - exit 0 -fi - -# ===================================================================== -# --client: Launch PXE client VM -# ===================================================================== -if [ "${1:-}" = "--client" ]; then - check_virsh - ensure_network - - # Check if server VM exists and is running - if ! virsh domstate "$SERVER_NAME" 2>/dev/null | grep -q "running"; then - echo "WARNING: Server VM ($SERVER_NAME) is not running." - echo " The PXE client needs the server for DHCP and boot files." - read -rp " Continue anyway? (y/N): " PROCEED - if [[ ! "$PROCEED" =~ ^[Yy]$ ]]; then - exit 1 - fi - fi - - # Remove existing client if present - if virsh dominfo "$CLIENT_NAME" &>/dev/null; then - echo "Removing existing client VM..." - virsh destroy "$CLIENT_NAME" 2>/dev/null || true - virsh undefine "$CLIENT_NAME" --nvram 2>/dev/null || true - virsh vol-delete "${CLIENT_NAME}.qcow2" --pool default 2>/dev/null || true - fi - - echo "Launching PXE client ($CLIENT_NAME)..." - echo " UEFI PXE boot on network: $NET_NAME" - echo "" - - virsh vol-create-as default "${CLIENT_NAME}.qcow2" "${VM_DISK_SIZE}G" --format qcow2 >/dev/null - - virt-install \ - --name "$CLIENT_NAME" \ - --memory "$VM_RAM" \ - --vcpus "$VM_CPUS" \ - --disk "vol=default/${CLIENT_NAME}.qcow2" \ - --network network="$NET_NAME",model=virtio \ - --os-variant ubuntu24.04 \ - --boot uefi,network \ - --graphics vnc,listen=0.0.0.0 \ - --noautoconsole - - # Get VNC port - VNC_PORT=$(virsh vncdisplay "$CLIENT_NAME" 2>/dev/null | sed 's/://' || echo "?") - VNC_PORT=$((5900 + VNC_PORT)) - - echo "" - echo "============================================" - echo "PXE client launched!" - echo "============================================" - echo "" - echo "Watch the PXE boot:" - echo " virt-viewer $CLIENT_NAME" - echo " (or VNC to localhost:$VNC_PORT)" - echo "" - echo "Expected boot sequence:" - echo " 1. UEFI firmware -> PXE boot" - echo " 2. DHCP from server (10.9.100.x)" - echo " 3. TFTP download ipxe.efi" - echo " 4. iPXE loads boot menu from port 4433" - echo " 5. GE Aerospace PXE Boot Menu appears" - echo "" - echo "Manage:" - echo " sudo virsh reboot $CLIENT_NAME # Retry PXE boot" - echo " sudo virsh destroy $CLIENT_NAME # Stop client" - echo " $0 --destroy # Remove everything" - exit 0 -fi - -# ===================================================================== -# Default: Launch PXE server VM -# ===================================================================== -check_virsh - -UBUNTU_ISO="${1:-}" -if [ -z "$UBUNTU_ISO" ] || [ ! -f "$UBUNTU_ISO" ]; then - echo "Usage: sudo $0 /path/to/ubuntu-24.04-live-server-amd64.iso" - echo "" - echo "Commands:" - echo " $0 /path/to/ubuntu.iso Launch PXE server VM" - echo " $0 --client Launch PXE client VM" - echo " $0 --status Check server readiness" - echo " $0 --destroy Remove everything" - exit 1 -fi - -# Check if Proxmox ISO exists, build if not -if [ ! -f "$PROXMOX_ISO" ]; then - echo "Proxmox ISO not found. Building it first..." - echo "" - "$SCRIPT_DIR/build-proxmox-iso.sh" "$UBUNTU_ISO" - echo "" -fi - -# Check server doesn't already exist -if virsh dominfo "$SERVER_NAME" &>/dev/null; then - echo "ERROR: Server VM already exists. Destroy first with: sudo $0 --destroy" - exit 1 -fi - -echo "============================================" -echo "PXE Lab Environment Setup" -echo "============================================" -echo "" - -# --- Step 1: Create isolated network --- -echo "[1/3] Setting up isolated network ($NET_NAME)..." -ensure_network -echo " Bridge: virbr-pxe (isolated, no host DHCP)" - -# --- Step 2: Extract kernel/initrd for direct boot --- -echo "[2/3] Extracting kernel and initrd from ISO..." -KERNEL="/tmp/${SERVER_NAME}-vmlinuz" -INITRD="/tmp/${SERVER_NAME}-initrd" -7z e -o/tmp -y "$PROXMOX_ISO" casper/vmlinuz casper/initrd >/dev/null 2>&1 -mv /tmp/vmlinuz "$KERNEL" -mv /tmp/initrd "$INITRD" -echo " Extracted vmlinuz and initrd" - -# --- Step 3: Launch server VM --- -echo "[3/3] Launching PXE server ($SERVER_NAME)..." - -virsh vol-create-as default "${SERVER_NAME}.qcow2" "${VM_DISK_SIZE}G" --format qcow2 >/dev/null - -virt-install \ - --name "$SERVER_NAME" \ - --memory "$VM_RAM" \ - --vcpus "$VM_CPUS" \ - --disk "vol=default/${SERVER_NAME}.qcow2" \ - --disk path="$PROXMOX_ISO",device=cdrom,readonly=on \ - --network network="$NET_NAME" \ - --os-variant ubuntu24.04 \ - --graphics none \ - --console pty,target_type=serial \ - --install kernel="$KERNEL",initrd="$INITRD",kernel_args="console=ttyS0,115200n8 autoinstall ds=nocloud\;s=/cdrom/server/" \ - --noautoconsole - -echo "" -echo "============================================" -echo "PXE server VM launched!" -echo "============================================" -echo "" -echo "The autoinstall + first-boot will take ~15 minutes." -echo "" -echo "Step 1 — Monitor the server install:" -echo " virsh console $SERVER_NAME" -echo " (Ctrl+] to detach)" -echo "" -echo "Step 2 — Check when services are ready:" -echo " $0 --status" -echo "" -echo "Step 3 — Launch a PXE client to test booting:" -echo " $0 --client" -echo "" -echo "Cleanup:" -echo " $0 --destroy" -echo "" diff --git a/test-vm.sh b/test-vm.sh deleted file mode 100755 index 04a94a1..0000000 --- a/test-vm.sh +++ /dev/null @@ -1,187 +0,0 @@ -#!/bin/bash -# -# test-vm.sh — Create a test VM to validate the PXE server setup -# -# This script: -# 1. Builds a CIDATA ISO with autoinstall config, packages, playbook, and webapp -# 2. Launches an Ubuntu 24.04 Server VM on the default libvirt network -# 3. The VM auto-installs, then runs the Ansible playbook on first boot -# -# Usage: -# ./test-vm.sh /path/to/ubuntu-24.04-live-server-amd64.iso -# -# After install completes (~10-15 min), access via: -# virsh console pxe-test (serial console, always works) -# ssh pxe@ (check: virsh domifaddr pxe-test) -# -# To clean up: -# ./test-vm.sh --destroy - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -VM_NAME="pxe-test" -VM_DISK="/var/lib/libvirt/images/${VM_NAME}.qcow2" -CIDATA_ISO="${SCRIPT_DIR}/.${VM_NAME}-cidata.iso" -VM_RAM=4096 -VM_CPUS=2 -VM_DISK_SIZE=40 # GB - -# --- Handle --destroy flag --- -if [ "${1:-}" = "--destroy" ]; then - echo "Destroying test environment..." - virsh destroy "$VM_NAME" 2>/dev/null || true - virsh undefine "$VM_NAME" 2>/dev/null || true - virsh vol-delete "${VM_NAME}.qcow2" --pool default 2>/dev/null || true - rm -f "$CIDATA_ISO" - rm -f "/tmp/${VM_NAME}-vmlinuz" "/tmp/${VM_NAME}-initrd" - echo "Done." - exit 0 -fi - -# --- Validate Ubuntu ISO argument --- -UBUNTU_ISO="${1:-}" -if [ -z "$UBUNTU_ISO" ] || [ ! -f "$UBUNTU_ISO" ]; then - echo "Usage: $0 /path/to/ubuntu-24.04-live-server-amd64.iso" - echo "" - echo "Download from: https://ubuntu.com/download/server" - echo "" - echo "Other commands:" - echo " $0 --destroy Remove the test VM and network" - exit 1 -fi - -echo "============================================" -echo "PXE Server Test VM Setup" -echo "============================================" -echo "" - -# --- Step 1: Build CIDATA ISO --- -echo "[1/4] Building CIDATA ISO..." -CIDATA_DIR=$(mktemp -d) - -# Autoinstall config -cp "$SCRIPT_DIR/autoinstall/user-data" "$CIDATA_DIR/user-data" -touch "$CIDATA_DIR/meta-data" - -# Offline .deb packages -if [ -d "$SCRIPT_DIR/offline-packages" ]; then - mkdir -p "$CIDATA_DIR/packages" - cp "$SCRIPT_DIR/offline-packages/"*.deb "$CIDATA_DIR/packages/" 2>/dev/null || true - echo " Copied $(ls -1 "$CIDATA_DIR/packages/"*.deb 2>/dev/null | wc -l) .deb packages" -else - echo " WARNING: No offline-packages/ directory. Run download-packages.sh first." -fi - -# Ansible playbook -mkdir -p "$CIDATA_DIR/playbook" -cp "$SCRIPT_DIR/playbook/"* "$CIDATA_DIR/playbook/" 2>/dev/null || true -echo " Copied playbook/" - -# Webapp -if [ -d "$SCRIPT_DIR/webapp" ]; then - mkdir -p "$CIDATA_DIR/webapp" - cp "$SCRIPT_DIR/webapp/app.py" "$SCRIPT_DIR/webapp/requirements.txt" "$CIDATA_DIR/webapp/" - cp -r "$SCRIPT_DIR/webapp/templates" "$SCRIPT_DIR/webapp/static" "$CIDATA_DIR/webapp/" - echo " Copied webapp/" -fi - -# Pip wheels -if [ -d "$SCRIPT_DIR/pip-wheels" ]; then - cp -r "$SCRIPT_DIR/pip-wheels" "$CIDATA_DIR/pip-wheels" - echo " Copied pip-wheels/" -elif [ -d "$SCRIPT_DIR/offline-packages/pip-wheels" ]; then - cp -r "$SCRIPT_DIR/offline-packages/pip-wheels" "$CIDATA_DIR/pip-wheels" - echo " Copied pip-wheels/ (from offline-packages/)" -fi - -# WinPE boot files (wimboot, boot.wim, BCD, ipxe.efi, etc.) -if [ -d "$SCRIPT_DIR/boot-files" ]; then - for bf in "$SCRIPT_DIR/boot-files"/*; do - [ -f "$bf" ] && cp "$bf" "$CIDATA_DIR/" - done - echo " Copied boot-files/ (wimboot, boot.wim, ipxe.efi, etc.)" -fi - -# Boot tools -if [ -d "$SCRIPT_DIR/boot-tools" ]; then - cp -r "$SCRIPT_DIR/boot-tools" "$CIDATA_DIR/boot-tools" - echo " Copied boot-tools/" -fi - -# Generate the CIDATA ISO -genisoimage -output "$CIDATA_ISO" -volid CIDATA -joliet -rock "$CIDATA_DIR" 2>/dev/null -CIDATA_SIZE=$(du -sh "$CIDATA_ISO" | cut -f1) -echo " CIDATA ISO: $CIDATA_ISO ($CIDATA_SIZE)" -rm -rf "$CIDATA_DIR" - -# --- Step 2: Create VM disk --- -echo "" -echo "[2/4] Creating VM disk (${VM_DISK_SIZE}GB)..." -if virsh vol-info "$VM_NAME.qcow2" --pool default &>/dev/null; then - echo " Disk already exists. Destroy first with: $0 --destroy" - exit 1 -fi -virsh vol-create-as default "${VM_NAME}.qcow2" "${VM_DISK_SIZE}G" --format qcow2 - -# --- Step 3: Extract kernel/initrd from ISO --- -echo "" -echo "[3/4] Extracting kernel and initrd from ISO..." -KERNEL="/tmp/${VM_NAME}-vmlinuz" -INITRD="/tmp/${VM_NAME}-initrd" -7z e -o/tmp -y "$UBUNTU_ISO" casper/vmlinuz casper/initrd 2>/dev/null -mv /tmp/vmlinuz "$KERNEL" -mv /tmp/initrd "$INITRD" -echo " Extracted vmlinuz and initrd from casper/" - -# --- Step 4: Launch VM --- -echo "" -echo "[4/4] Launching VM ($VM_NAME)..." - -# Use the default libvirt network (NAT, 192.168.122.0/24) for install access. -# If br-pxe bridge exists, add a second NIC for the isolated PXE switch. -# The Ansible playbook will configure 10.9.100.1/24 on the PXE interface. -PXE_BRIDGE_ARGS="" -if ip link show br-pxe &>/dev/null; then - PXE_BRIDGE_ARGS="--network bridge=br-pxe,model=virtio" - echo " Found br-pxe bridge, adding isolated switch NIC" -fi - -virt-install \ - --name "$VM_NAME" \ - --memory "$VM_RAM" \ - --vcpus "$VM_CPUS" \ - --disk path="$VM_DISK",format=qcow2 \ - --disk path="$UBUNTU_ISO",device=cdrom,readonly=on \ - --disk path="$CIDATA_ISO",device=cdrom \ - --network network=default \ - $PXE_BRIDGE_ARGS \ - --os-variant ubuntu24.04 \ - --graphics none \ - --console pty,target_type=serial \ - --install kernel="$KERNEL",initrd="$INITRD",kernel_args="console=ttyS0,115200n8 autoinstall" \ - --noautoconsole - -echo "" -echo "============================================" -echo "VM launched! The autoinstall will take ~10-15 minutes." -echo "============================================" -echo "" -echo "Watch progress:" -echo " sudo virsh console $VM_NAME" -echo " (Press Ctrl+] to detach)" -echo "" -echo "After install + first boot:" -echo " Console: sudo virsh console $VM_NAME" -echo " Find IP: sudo virsh domifaddr $VM_NAME" -echo " SSH: ssh pxe@" -echo "" -echo "NOTE: The Ansible playbook will change the VM's IP to 10.9.100.1." -echo " After that, use 'virsh console' to access the VM." -echo " On the VM, verify with: curl http://localhost:9009" -echo "" -echo "Manage:" -echo " sudo virsh start $VM_NAME" -echo " sudo virsh shutdown $VM_NAME" -echo " $0 --destroy (remove everything)" -echo ""