The inline one-liner in startnet.cmd called Get-NetAdapter, which is
not available in WinPE's stripped PowerShell (no NetTCPIP module).
Errors silently swallowed by the surrounding try/catch - POST never
fired, dashboard never showed bays during the WIM-apply phase.
Externalize to a standalone .ps1 on the enrollment share:
* Uses wmic (always present in WinPE 10+) for both serial AND mac
instead of Get-CimInstance / Get-NetAdapter.
* Logs every step to X:\Windows\Temp\winpe-status-push.log so a
future "POST didn't fire" debug is one file read away.
* startnet.cmd now just runs powershell -File Y:\scripts\winpe-status-
push.ps1. Future edits to the push logic do NOT require a boot.wim
rebuild; just edit the .ps1 on the share.
Mirror the existing pattern for run-enrollment.ps1 / wait-for-internet.ps1
/ migrate-to-wifi.ps1 (all already at /srv/samba/enrollment/scripts/).
Add the new file to the playbook's enrollment-scripts copy loop.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1132 lines
42 KiB
YAML
1132 lines
42 KiB
YAML
---
|
|
- name: PXE Server Setup (Ubuntu with dnsmasq)
|
|
hosts: localhost
|
|
connection: local
|
|
become: yes
|
|
gather_facts: yes
|
|
|
|
pre_tasks:
|
|
- name: "Verify required packages are installed (pre-installed from offline .debs)"
|
|
command: dpkg -s {{ item }}
|
|
loop:
|
|
- dnsmasq
|
|
- apache2
|
|
- samba
|
|
- unzip
|
|
- ufw
|
|
- cron
|
|
- ansible
|
|
- wimtools
|
|
- conntrack
|
|
register: pkg_check
|
|
failed_when: false
|
|
changed_when: false
|
|
|
|
- name: "Warn about missing packages"
|
|
debug:
|
|
msg: "WARNING: {{ item.item }} is not installed! Install offline .debs first."
|
|
loop: "{{ pkg_check.results }}"
|
|
when: item.rc != 0
|
|
|
|
vars:
|
|
tftp_dir: "/srv/tftp"
|
|
web_root: "/var/www/html"
|
|
samba_share: "/srv/samba/winpeapps"
|
|
usb_mount: "/mnt/usb/playbook" # playbook location on USB
|
|
usb_root: "/mnt/usb" # CIDATA partition root
|
|
image_types:
|
|
- gea-standard
|
|
- gea-engineer
|
|
- gea-shopfloor
|
|
- ge-standard
|
|
- ge-engineer
|
|
- ge-shopfloor-lockdown
|
|
- ge-shopfloor-mce
|
|
shopfloor_types:
|
|
- gea-shopfloor
|
|
# Image variants that share the generic Win10/Win11 unattend
|
|
# (gea-standard is now Win11, gea-engineer is Win10 — same unattend file
|
|
# drives both because the RunSynchronous entries are OS-version-agnostic).
|
|
standard_types:
|
|
- gea-standard
|
|
- gea-engineer
|
|
- ge-standard
|
|
- ge-engineer
|
|
deploy_subdirs:
|
|
- Applications
|
|
- Control
|
|
- "Operating Systems"
|
|
- "Out-of-box Drivers"
|
|
- Packages
|
|
- Tools
|
|
|
|
tasks:
|
|
- name: "Gather minimal network facts"
|
|
ansible.builtin.setup:
|
|
filter:
|
|
- ansible_interfaces
|
|
- ansible_default_ipv4
|
|
|
|
- name: "Bring up all ethernet-like interfaces"
|
|
command: ip link set dev {{ item }} up
|
|
loop: "{{ ansible_interfaces | select('match','^e(th|n)') | list }}"
|
|
ignore_errors: yes
|
|
|
|
- name: "Find interface with 10.9.100.1 already configured"
|
|
set_fact:
|
|
preconfigured_iface: >-
|
|
{{ ansible_interfaces
|
|
| select('match','^e(th|n)')
|
|
| map('regex_replace','^(.*)$','ansible_\1')
|
|
| map('extract', hostvars[inventory_hostname])
|
|
| selectattr('ipv4','defined')
|
|
| selectattr('ipv4.address','equalto','10.9.100.1')
|
|
| map(attribute='device')
|
|
| list
|
|
| first
|
|
| default('') }}
|
|
ignore_errors: yes
|
|
|
|
- name: "Determine PXE interface"
|
|
set_fact:
|
|
pxe_iface: >-
|
|
{{ preconfigured_iface | default('',true)
|
|
or (ansible_interfaces
|
|
| select('match','^e(th|n)')
|
|
| reject('equalto','lo')
|
|
| reject('equalto', ansible_default_ipv4.interface | default(''))
|
|
| list
|
|
)
|
|
| first
|
|
| default(ansible_default_ipv4.interface | default(
|
|
ansible_interfaces | select('match','^e(th|n)') | first | default('eth0')
|
|
)) }}
|
|
|
|
- name: "Debug: final pxe_iface choice"
|
|
debug:
|
|
msg: "Using {{ pxe_iface }} for DHCP/TFTP"
|
|
|
|
- name: "Expand root partition and filesystem to use full disk"
|
|
args:
|
|
executable: /bin/bash
|
|
shell: |
|
|
# Find the root LV device
|
|
ROOT_DEV=$(findmnt -n -o SOURCE /)
|
|
ROOT_DISK=$(lsblk -n -o PKNAME $(readlink -f "$ROOT_DEV") | tail -1)
|
|
if [ -z "$ROOT_DISK" ]; then
|
|
echo "Could not determine root disk, skipping"
|
|
exit 0
|
|
fi
|
|
# Find the partition number for the LVM PV
|
|
PV_PART=$(pvs --noheadings -o pv_name | tr -d ' ' | head -1)
|
|
if [ -z "$PV_PART" ]; then
|
|
echo "No LVM PV found, skipping"
|
|
exit 0
|
|
fi
|
|
PART_NUM=$(echo "$PV_PART" | grep -o '[0-9]*$')
|
|
echo "Expanding /dev/${ROOT_DISK} partition ${PART_NUM} (${PV_PART})..."
|
|
growpart "/dev/${ROOT_DISK}" "${PART_NUM}" 2>&1 || true
|
|
pvresize "$PV_PART" 2>&1
|
|
lvextend -l +100%FREE "$ROOT_DEV" 2>&1 || true
|
|
resize2fs "$ROOT_DEV" 2>&1
|
|
echo "Disk: $(df -h / | tail -1)"
|
|
register: disk_expand
|
|
changed_when: "'CHANGED' in disk_expand.stdout or 'resized' in disk_expand.stdout"
|
|
|
|
- name: "Deploy dnsmasq dhcp-script for per-lease state cleanup"
|
|
copy:
|
|
src: "{{ usb_mount }}/pxe-server-helpers/pxe-dhcp-hook.sh"
|
|
dest: /usr/local/sbin/pxe-dhcp-hook.sh
|
|
mode: '0755'
|
|
|
|
- name: "Configure dnsmasq for DHCP and TFTP"
|
|
copy:
|
|
dest: /etc/dnsmasq.conf
|
|
backup: yes
|
|
content: |
|
|
port=0
|
|
interface={{ pxe_iface }}
|
|
bind-interfaces
|
|
dhcp-range=10.9.100.10,10.9.100.100,12h
|
|
# No default gateway (option 3) and no DNS (option 6) handed out:
|
|
# the PXE network is isolated and the PXE server does not forward
|
|
# internet traffic. Previously we set both, which made imaged PCs
|
|
# add a default route via 10.9.100.1 and prefer it over WiFi (lower
|
|
# interface metric). PPKG / Intune enrollment then black-holed
|
|
# internet-bound traffic. The fix used to be migrate-to-wifi.ps1
|
|
# disabling the wired NIC during first-logon, which created an
|
|
# eDNC race (10022 socket errors until the SYSTEM task re-enabled
|
|
# the wired NIC much later). Removing these options entirely lets
|
|
# Windows route internet via WiFi and same-subnet PXE/SMB traffic
|
|
# via wired, no migration script needed.
|
|
#
|
|
# Important: dnsmasq DEFAULTS to sending its own listening address as
|
|
# both router and DNS when these options are unset. Commenting them
|
|
# out is NOT the same as disabling - imaged PCs (and Blancco PXE
|
|
# clients) end up with 10.9.100.1 as gateway. The empty-value form
|
|
# below explicitly suppresses both options.
|
|
dhcp-option=3
|
|
dhcp-option=6
|
|
enable-tftp
|
|
tftp-root={{ tftp_dir }}
|
|
# Arch-aware NBP: legacy BIOS PXE ROMs (client-arch=0) cannot run
|
|
# the EFI iPXE binary and report "NBP is too big to fit in free
|
|
# base memory" because ipxe.efi (~675KB) exceeds the BIOS PXE
|
|
# NBP cap. Serve undionly.kpxe (~70KB, BIOS-mode iPXE) to them
|
|
# instead. Everything else (UEFI x86_64 = arch 7 or 9, plus any
|
|
# future arches) keeps getting ipxe.efi - default-safe.
|
|
dhcp-match=set:bios,option:client-arch,0
|
|
dhcp-boot=tag:bios,undionly.kpxe
|
|
dhcp-boot=tag:!bios,ipxe.efi
|
|
log-dhcp
|
|
# Per-lease state cleanup: flush conntrack + port-445 sockets for
|
|
# the client IP on add/del. Prevents "System error 53" when a PXE
|
|
# client re-images the same machine and hits a stale SMB session.
|
|
# Script runs as root by default (dnsmasq --dhcp-scriptuser default).
|
|
dhcp-script=/usr/local/sbin/pxe-dhcp-hook.sh
|
|
|
|
- name: "Create TFTP directory"
|
|
file:
|
|
path: "{{ tftp_dir }}"
|
|
state: directory
|
|
mode: '0755'
|
|
owner: nobody
|
|
group: nogroup
|
|
|
|
- name: "Create Win11 directory structure"
|
|
file:
|
|
path: "{{ web_root }}/win11/{{ item }}"
|
|
state: directory
|
|
mode: '0755'
|
|
loop:
|
|
- "EFI/Boot"
|
|
- "EFI/Microsoft/Boot"
|
|
- "Boot"
|
|
- "sources"
|
|
|
|
- name: "Create Altiris iPXE directory"
|
|
file:
|
|
path: "{{ web_root }}/Altiris/iPXE"
|
|
state: directory
|
|
mode: '0755'
|
|
|
|
- name: "Create boot tool directories"
|
|
file:
|
|
path: "{{ web_root }}/{{ item }}"
|
|
state: directory
|
|
mode: '0755'
|
|
loop:
|
|
- clonezilla
|
|
- blancco
|
|
- memtest
|
|
|
|
- name: "Create GetPxeScript.aspx (iPXE boot menu)"
|
|
copy:
|
|
dest: "{{ web_root }}/Altiris/iPXE/GetPxeScript.aspx"
|
|
backup: yes
|
|
content: |
|
|
#!ipxe
|
|
|
|
set server 10.9.100.1
|
|
|
|
:menu
|
|
menu GE Aerospace PXE Boot Menu
|
|
item --gap -- ---- Windows Deployment ----
|
|
item winpe Windows PE (Image Deployment)
|
|
item --gap -- ---- Utilities (Secure Boot must be DISABLED) ----
|
|
item blancco Blancco Drive Eraser
|
|
item clonezilla Clonezilla Live (Disk Imaging)
|
|
item memtest Memtest86+ (Memory Diagnostics)
|
|
item --gap -- ----
|
|
item reboot Reboot
|
|
item exit Exit to BIOS
|
|
choose --default winpe --timeout 30000 target && goto ${target}
|
|
|
|
:winpe
|
|
echo
|
|
echo Windows deployment requires Secure Boot to be ENABLED.
|
|
echo If you disabled it for Blancco/Clonezilla, re-enable it now.
|
|
echo
|
|
prompt --timeout 5000 Press any key to continue (auto-boot in 5s)... && goto winpe_boot || goto winpe_boot
|
|
|
|
:winpe_boot
|
|
kernel http://${server}/win11/wimboot gui
|
|
initrd http://${server}/win11/EFI/Microsoft/Boot/boot.stl EFI/Microsoft/Boot/Boot.stl
|
|
initrd http://${server}/win11/EFI/Microsoft/Boot/BCD EFI/Microsoft/Boot/BCD
|
|
initrd http://${server}/win11/EFI/Boot/bootx64.efi EFI/Boot/bootx64.efi
|
|
initrd http://${server}/win11/Boot/boot.sdi Boot/boot.sdi
|
|
initrd http://${server}/win11/sources/boot.wim sources/boot.wim
|
|
boot
|
|
|
|
:clonezilla
|
|
set base http://${server}/clonezilla
|
|
kernel ${base}/vmlinuz boot=live username=user union=overlay config components noswap edd=on nomodeset nodmraid locales= keyboard-layouts= ocs_live_run="ocs-live-general" ocs_live_extra_param="" ocs_live_batch=no net.ifnames=0 nosplash noprompt fetch=${base}/filesystem.squashfs || goto secureboot_warn
|
|
initrd ${base}/initrd.img
|
|
boot
|
|
|
|
:blancco
|
|
chain http://${server}/blancco/grubx64.efi || goto secureboot_warn
|
|
|
|
:memtest
|
|
kernel http://${server}/memtest/memtest.efi || goto secureboot_warn
|
|
boot
|
|
|
|
:secureboot_warn
|
|
echo
|
|
echo ======================================================
|
|
echo This option requires Secure Boot to be DISABLED.
|
|
echo
|
|
echo 1. Reboot this machine
|
|
echo 2. Press F2 / Del to enter BIOS Setup
|
|
echo 3. Disable Secure Boot
|
|
echo 4. Save and exit BIOS
|
|
echo 5. PXE boot again and select this option
|
|
echo
|
|
echo Re-enable Secure Boot after completing the task.
|
|
echo ======================================================
|
|
echo
|
|
prompt Press any key to return to menu...
|
|
goto menu
|
|
|
|
:reboot
|
|
reboot
|
|
|
|
:exit
|
|
exit
|
|
|
|
- name: "Ensure Apache listens on port 4433"
|
|
lineinfile:
|
|
path: /etc/apache2/ports.conf
|
|
line: "Listen 4433"
|
|
backup: yes
|
|
state: present
|
|
|
|
- name: "Create VirtualHost for Altiris iPXE on 4433"
|
|
copy:
|
|
dest: /etc/apache2/sites-available/altiris-ipxe.conf
|
|
backup: yes
|
|
content: |
|
|
<VirtualHost *:4433>
|
|
DocumentRoot {{ web_root }}
|
|
<Directory "{{ web_root }}/Altiris/iPXE">
|
|
Options Indexes FollowSymLinks
|
|
AllowOverride None
|
|
Require all granted
|
|
AddType text/plain .aspx
|
|
</Directory>
|
|
</VirtualHost>
|
|
|
|
- name: "Enable Altiris iPXE site"
|
|
command: a2ensite altiris-ipxe.conf
|
|
args:
|
|
creates: /etc/apache2/sites-enabled/altiris-ipxe.conf
|
|
|
|
- name: "Reload Apache to apply changes"
|
|
systemd:
|
|
name: apache2
|
|
state: reloaded
|
|
|
|
- name: "Create Samba share root"
|
|
file:
|
|
path: "{{ samba_share }}"
|
|
state: directory
|
|
mode: '0777'
|
|
|
|
- name: "Create Clonezilla backup share directory"
|
|
file:
|
|
path: /srv/samba/clonezilla
|
|
state: directory
|
|
mode: '0777'
|
|
|
|
- name: "Create Blancco reports share directory"
|
|
file:
|
|
path: /srv/samba/blancco-reports
|
|
state: directory
|
|
mode: '0777'
|
|
|
|
- name: "Create enrollment share with internal taxonomy"
|
|
file:
|
|
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 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/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/ppkgs/$name"
|
|
done
|
|
ls -lh /srv/samba/enrollment/ppkgs/*.ppkg 2>/dev/null
|
|
ignore_errors: yes
|
|
|
|
- name: "Deploy enrollment scripts to scripts/"
|
|
copy:
|
|
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" }
|
|
- { src: "{{ usb_mount }}/winpe-status-push.ps1", dest: "winpe-status-push.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
|
|
|
|
- name: "Deploy Dell driver packs to shared Out-of-box Drivers"
|
|
args:
|
|
executable: /bin/bash
|
|
shell: |
|
|
set +e
|
|
SRC="{{ usb_root }}/drivers"
|
|
DEST="/srv/samba/winpeapps/_shared/Out-of-box Drivers/Dell_11"
|
|
if [ ! -d "$SRC" ]; then
|
|
echo "No drivers/ on USB - skipping"
|
|
exit 0
|
|
fi
|
|
mkdir -p "$DEST"
|
|
# Copy everything except split chunks
|
|
rsync -a --exclude='*.part.*' "$SRC/" "$DEST/"
|
|
# Reassemble any split driver files
|
|
while IFS= read -r first; do
|
|
base="${first%.part.00}"
|
|
rel="${base#$SRC/}"
|
|
out="$DEST/$rel"
|
|
mkdir -p "$(dirname "$out")"
|
|
echo "Reassembling $rel from chunks..."
|
|
cat "${base}.part."* > "$out"
|
|
done < <(find "$SRC" -name '*.part.00')
|
|
echo "Deployed Dell drivers from USB"
|
|
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
|
|
|
|
- name: "Deploy preinstall.json to pre-install/"
|
|
copy:
|
|
src: "{{ usb_mount }}/preinstall/preinstall.json"
|
|
dest: /srv/samba/enrollment/pre-install/preinstall.json
|
|
mode: '0644'
|
|
ignore_errors: yes
|
|
|
|
# Oracle Client 11.2 is the first app the preinstall runner executes, so it
|
|
# has to be present on-disk before the first client images. The 686 MB
|
|
# Oracle_OracleDatabase_11r2_V03.zip doesn't belong in git; it rides on the
|
|
# USB under oracle/ alongside the BIOS exes. Install-Oracle11r2.cmd is
|
|
# tracked at playbook/preinstall/oracle/ and arrives via usb_mount.
|
|
- name: "Create pre-install/installers/oracle/ staging dir"
|
|
file:
|
|
path: /srv/samba/enrollment/pre-install/installers/oracle
|
|
state: directory
|
|
mode: '0755'
|
|
ignore_errors: yes
|
|
|
|
- name: "Deploy Install-Oracle11r2.cmd to pre-install/installers/oracle/"
|
|
copy:
|
|
src: "{{ usb_mount }}/preinstall/oracle/Install-Oracle11r2.cmd"
|
|
dest: /srv/samba/enrollment/pre-install/installers/oracle/Install-Oracle11r2.cmd
|
|
mode: '0755'
|
|
ignore_errors: yes
|
|
|
|
# Per-file overrides for the .ora config dropped by the wrapper post-install.
|
|
# The zip's client/ora/ contains dated defaults; shipping an updated
|
|
# tnsnames.ora alongside the wrapper lets us refresh the DB catalog without
|
|
# repackaging the 686 MB zip. sqlnet.ora and ldap.ora can be added here the
|
|
# same way if they ever need to diverge from the zip's bundled copies.
|
|
- name: "Deploy updated tnsnames.ora override to pre-install/installers/oracle/"
|
|
copy:
|
|
src: "{{ usb_mount }}/preinstall/oracle/tnsnames.ora"
|
|
dest: /srv/samba/enrollment/pre-install/installers/oracle/tnsnames.ora
|
|
mode: '0644'
|
|
ignore_errors: yes
|
|
|
|
- name: "Deploy Oracle 11.2 zip (686 MB) from USB to pre-install/installers/oracle/"
|
|
shell: >
|
|
if [ -f "{{ usb_root }}/oracle/Oracle_OracleDatabase_11r2_V03.zip" ]; then
|
|
cp -f "{{ usb_root }}/oracle/Oracle_OracleDatabase_11r2_V03.zip" \
|
|
/srv/samba/enrollment/pre-install/installers/oracle/
|
|
echo "Deployed Oracle 11.2 zip ($(stat -c %s /srv/samba/enrollment/pre-install/installers/oracle/Oracle_OracleDatabase_11r2_V03.zip) bytes)"
|
|
else
|
|
echo "WARNING: {{ usb_root }}/oracle/Oracle_OracleDatabase_11r2_V03.zip not on USB - preinstall runner will fail on Oracle step until this file is staged at /srv/samba/enrollment/pre-install/installers/oracle/"
|
|
fi
|
|
ignore_errors: yes
|
|
|
|
- name: "Ensure winpeapps/_shared/BIOS directory exists"
|
|
file:
|
|
path: /srv/samba/winpeapps/_shared/BIOS
|
|
state: directory
|
|
mode: '0755'
|
|
|
|
- name: "Deploy BIOS check script + manifest to winpeapps/_shared/BIOS/"
|
|
# Path matches what startnet.cmd reads at WinPE boot:
|
|
# net use B: \\10.9.100.1\winpeapps\_shared
|
|
# if exist B:\BIOS\check-bios.cmd ...
|
|
# Earlier deploy targeted enrollment/pre-install/bios/ (different share)
|
|
# which startnet.cmd never read, so BIOS_STATUS perma-stuck on
|
|
# "No BIOS check (share unavailable)". Corrected 2026-04-28.
|
|
copy:
|
|
src: "{{ usb_mount }}/shopfloor-setup/BIOS/{{ item }}"
|
|
dest: "/srv/samba/winpeapps/_shared/BIOS/{{ item }}"
|
|
mode: '0644'
|
|
loop:
|
|
- check-bios.cmd
|
|
- models.txt
|
|
ignore_errors: yes
|
|
|
|
- name: "Deploy Dell Flash64W.exe to winpeapps/_shared/BIOS/"
|
|
copy:
|
|
src: "{{ usb_root }}/bios/Flash64W.exe"
|
|
dest: /srv/samba/winpeapps/_shared/BIOS/Flash64W.exe
|
|
mode: '0644'
|
|
ignore_errors: yes
|
|
|
|
- name: "Deploy BIOS update binaries from USB to winpeapps/_shared/BIOS/"
|
|
shell: >
|
|
if [ -d "{{ usb_root }}/bios" ]; then
|
|
cp -f {{ usb_root }}/bios/*.exe /srv/samba/winpeapps/_shared/BIOS/ 2>/dev/null || true
|
|
count=$(find /srv/samba/winpeapps/_shared/BIOS -name '*.exe' | wc -l)
|
|
echo "Deployed $count BIOS binaries"
|
|
else
|
|
echo "No bios/ on USB - skipping"
|
|
fi
|
|
ignore_errors: yes
|
|
|
|
- name: "Create image upload staging directory"
|
|
file:
|
|
path: /home/pxe/image-upload
|
|
state: directory
|
|
mode: '0777'
|
|
owner: pxe
|
|
group: pxe
|
|
|
|
- name: "Enable Samba symlink following (shared image dirs)"
|
|
blockinfile:
|
|
path: /etc/samba/smb.conf
|
|
backup: yes
|
|
marker: "# {mark} MANAGED - GLOBAL SYMLINKS"
|
|
insertafter: '\[global\]'
|
|
block: |
|
|
follow symlinks = yes
|
|
wide links = yes
|
|
unix extensions = no
|
|
|
|
- name: "Samba SMB session handling for WinPE re-image robustness"
|
|
blockinfile:
|
|
path: /etc/samba/smb.conf
|
|
backup: yes
|
|
marker: "# {mark} MANAGED - PXE REIMAGE FIX"
|
|
insertafter: "# END MANAGED - GLOBAL SYMLINKS"
|
|
block: |
|
|
# Reduce the chance a WinPE client rebooting mid-imaging leaves a
|
|
# stale session on the server that blocks its next connection
|
|
# attempt with "System error 53 network path not found". Combined
|
|
# with /etc/sysctl.d/99-pxe-conntrack.conf (shorter nf_conntrack
|
|
# TCP timeouts) this keeps the conntrack + smbd state in sync with
|
|
# the short-lived flows that PXE imaging produces.
|
|
socket options = TCP_NODELAY SO_KEEPALIVE IPTOS_LOWDELAY
|
|
keepalive = 30
|
|
deadtime = 5
|
|
|
|
- name: "Configure Samba shares"
|
|
blockinfile:
|
|
path: /etc/samba/smb.conf
|
|
backup: yes
|
|
block: |
|
|
[winpeapps]
|
|
path = {{ samba_share }}
|
|
browseable = yes
|
|
read only = no
|
|
guest ok = no
|
|
valid users = pxe-upload
|
|
force user = root
|
|
|
|
[clonezilla]
|
|
path = /srv/samba/clonezilla
|
|
browseable = yes
|
|
read only = no
|
|
guest ok = no
|
|
valid users = pxe-upload
|
|
force user = root
|
|
comment = Clonezilla backup images
|
|
|
|
[blancco-reports]
|
|
path = /srv/samba/blancco-reports
|
|
browseable = yes
|
|
read only = no
|
|
guest ok = no
|
|
valid users = pxe-upload blancco
|
|
force user = root
|
|
comment = Blancco Drive Eraser reports
|
|
|
|
[enrollment]
|
|
path = /srv/samba/enrollment
|
|
browseable = yes
|
|
read only = no
|
|
guest ok = no
|
|
valid users = pxe-upload
|
|
force user = root
|
|
comment = GCCH bulk enrollment packages
|
|
|
|
[image-upload]
|
|
path = /home/pxe/image-upload
|
|
browseable = yes
|
|
read only = no
|
|
guest ok = no
|
|
valid users = pxe-upload
|
|
force user = pxe
|
|
force group = pxe
|
|
comment = PXE image upload staging area
|
|
oplocks = no
|
|
level2 oplocks = no
|
|
strict sync = yes
|
|
|
|
- name: "Create Samba users (pxe-upload and blancco)"
|
|
shell: |
|
|
id pxe-upload >/dev/null 2>&1 || useradd -M -s /usr/sbin/nologin pxe-upload
|
|
echo -e 'pxe\npxe' | smbpasswd -a pxe-upload -s
|
|
id blancco >/dev/null 2>&1 || useradd -M -s /usr/sbin/nologin blancco
|
|
echo -e 'blancco\nblancco' | smbpasswd -a blancco -s
|
|
args:
|
|
executable: /bin/bash
|
|
changed_when: false
|
|
|
|
- name: "Deploy nf_conntrack TCP timeout sysctl for PXE workload"
|
|
copy:
|
|
src: "{{ usb_mount }}/pxe-server-helpers/99-pxe-conntrack.conf"
|
|
dest: /etc/sysctl.d/99-pxe-conntrack.conf
|
|
mode: '0644'
|
|
notify: reload sysctl
|
|
|
|
- name: "Deploy SMB diagnostic + soft-reset helper scripts"
|
|
copy:
|
|
src: "{{ usb_mount }}/pxe-server-helpers/{{ item }}"
|
|
dest: "/usr/local/sbin/{{ item }}"
|
|
mode: '0755'
|
|
loop:
|
|
- smb-diag.sh
|
|
- smb-soft-reset.sh
|
|
|
|
- name: "Create image-type top-level directories"
|
|
file:
|
|
path: "{{ samba_share }}/{{ item }}"
|
|
state: directory
|
|
mode: '0777'
|
|
loop: "{{ image_types }}"
|
|
|
|
- name: "Create Deploy subdirectories for each image type"
|
|
file:
|
|
path: "{{ samba_share }}/{{ item.0 }}/Deploy/{{ item.1 }}"
|
|
state: directory
|
|
mode: '0777'
|
|
with_nested:
|
|
- "{{ image_types }}"
|
|
- "{{ deploy_subdirs }}"
|
|
|
|
- name: "Create Media.tag for FlatSetupLoader.exe drive detection"
|
|
copy:
|
|
content: ""
|
|
dest: "{{ samba_share }}/{{ item }}/Deploy/Control/Media.tag"
|
|
mode: '0644'
|
|
force: no
|
|
loop: "{{ image_types }}"
|
|
|
|
- name: "Deploy shopfloor unattend.xml template"
|
|
copy:
|
|
src: "{{ usb_mount }}/FlatUnattendW10-shopfloor.xml"
|
|
dest: "{{ samba_share }}/{{ item }}/Deploy/FlatUnattendW10.xml"
|
|
mode: '0644'
|
|
force: no
|
|
loop: "{{ shopfloor_types }}"
|
|
ignore_errors: yes
|
|
|
|
# gea-standard (Win11) + gea-engineer (Win10) + ge-standard/engineer all use
|
|
# the same unattend. force: yes because drift between this file and the
|
|
# shared copies is what caused the Win10/Win11 search-cleanup regression
|
|
# earlier this session (d49f516) — prefer repo as source of truth.
|
|
- name: "Deploy standard/engineer unattend.xml"
|
|
copy:
|
|
src: "{{ usb_mount }}/FlatUnattendW10.xml"
|
|
dest: "{{ samba_share }}/{{ item }}/Deploy/FlatUnattendW10.xml"
|
|
mode: '0644'
|
|
force: yes
|
|
loop: "{{ standard_types }}"
|
|
ignore_errors: yes
|
|
|
|
- name: "Daily cron to create/refresh Media.tag for all images"
|
|
copy:
|
|
content: |
|
|
# Create Media.tag in any image with Deploy/Control/ and refresh existing ones
|
|
@reboot root for d in {{ samba_share }}/*/Deploy/Control; do [ -d "$d" ] && touch "$d/Media.tag"; done
|
|
0 0 * * * root for d in {{ samba_share }}/*/Deploy/Control; do [ -d "$d" ] && touch "$d/Media.tag"; done
|
|
dest: /etc/cron.d/media-tag-refresh
|
|
mode: '0644'
|
|
|
|
- name: "Copy WinPE & boot files from USB (skipped if not present)"
|
|
copy:
|
|
src: "{{ usb_root }}/{{ item.src }}"
|
|
dest: "{{ web_root }}/win11/{{ item.dest }}"
|
|
mode: '0644'
|
|
loop:
|
|
- { src: "wimboot", dest: "wimboot" }
|
|
- { src: "boot.stl", dest: "EFI/Microsoft/Boot/boot.stl" }
|
|
- { src: "BCD", dest: "EFI/Microsoft/Boot/BCD" }
|
|
- { src: "bootx64.efi", dest: "EFI/Boot/bootx64.efi" }
|
|
- { src: "boot.sdi", dest: "Boot/boot.sdi" }
|
|
- { src: "boot.wim", dest: "sources/boot.wim" }
|
|
ignore_errors: yes
|
|
|
|
- name: "Inject startnet.cmd into boot.wim (virtual BOOT/MEDIA volumes)"
|
|
shell: |
|
|
WIM="{{ web_root }}/win11/sources/boot.wim"
|
|
STARTNET="{{ usb_mount }}/startnet.cmd"
|
|
if [ -f "$WIM" ] && [ -f "$STARTNET" ]; then
|
|
echo "add $STARTNET /Windows/System32/startnet.cmd" | wimupdate "$WIM" 1
|
|
echo "Updated startnet.cmd in boot.wim"
|
|
else
|
|
echo "Skipped: boot.wim or startnet.cmd not found"
|
|
fi
|
|
args:
|
|
executable: /bin/bash
|
|
ignore_errors: yes
|
|
|
|
- name: "Copy iPXE binaries from USB (skipped if not present)"
|
|
copy:
|
|
src: "{{ usb_root }}/{{ item }}"
|
|
dest: "{{ tftp_dir }}/{{ item }}"
|
|
mode: '0755'
|
|
loop:
|
|
- ipxe.efi
|
|
- undionly.kpxe
|
|
ignore_errors: yes
|
|
|
|
- name: "Copy boot tool files from USB (Clonezilla, Blancco, Memtest)"
|
|
shell: >
|
|
cp -r "{{ usb_root }}/boot-tools/{{ item }}/"* "{{ web_root }}/{{ item }}/" 2>/dev/null || true
|
|
loop:
|
|
- clonezilla
|
|
- blancco
|
|
- memtest
|
|
|
|
# --- Blancco PXE boot via Ubuntu kernel + switch_root ---
|
|
# Blancco's own kernel freezes on Dell Precision towers. Workaround:
|
|
# Boot Ubuntu kernel, download Blancco rootfs, overlay mount, switch_root.
|
|
|
|
- name: "Build Blancco PXE initramfs"
|
|
# The narrow hand-picked NIC driver list used before 2026-04-22 produced
|
|
# a 2 MB initramfs that hung on "waiting for network interface" for any
|
|
# hardware outside e1000e/igb/tg3/bnx2/bnxt_en/b44. This rewrite sweeps
|
|
# the full drivers/net/ tree (ethernet + phy + mdio + usb + fddi + wan)
|
|
# plus overlay / squashfs / loop / ptp / libphy / mii deps, runs depmod
|
|
# so blancco-init.sh can use modprobe with proper dependency resolution,
|
|
# and produces a ~20 MB initramfs. Size isn't a concern - HTTP loads it
|
|
# in under two seconds at gigabit, versus the many minutes of hangtime
|
|
# the narrow build cost us when a NIC was unsupported.
|
|
args:
|
|
executable: /bin/bash
|
|
creates: "{{ web_root }}/blancco/kexec-initrd.img"
|
|
shell: |
|
|
set -e
|
|
WORK=$(mktemp -d)
|
|
KVER=$(uname -r)
|
|
mkdir -p "$WORK"/{bin,lib/modules/$KVER/kernel,lib64,sbin,usr/share/udhcpc,etc,run,proc,sys,dev}
|
|
|
|
# Busybox (static) - bundled on USB at playbook/busybox-static
|
|
if [ -f /bin/busybox ]; then
|
|
cp /bin/busybox "$WORK/bin/busybox"
|
|
elif [ -f "{{ usb_root }}/playbook/busybox-static" ]; then
|
|
cp "{{ usb_root }}/playbook/busybox-static" "$WORK/bin/busybox"
|
|
else
|
|
echo "ERROR: No busybox available (not at /bin/busybox or on USB)"
|
|
exit 1
|
|
fi
|
|
chmod +x "$WORK/bin/busybox"
|
|
# All applets blancco-init.sh uses: modprobe, insmod, dmesg, find, env
|
|
# and export added vs the old narrow list.
|
|
for cmd in sh ash awk cat chmod cp dd echo grep gunzip ifconfig ip insmod ln losetup ls mkdir mknod modprobe mount mv reboot rm rmdir route sed sleep switch_root tar udhcpc umount wget cpio dmesg env export find; do
|
|
ln -sf busybox "$WORK/bin/$cmd"
|
|
done
|
|
|
|
# Full drivers/net/ tree - ethernet + phy + mdio + usb + fddi + wan.
|
|
# Preserve path under /lib/modules/$KVER so depmod can resolve deps.
|
|
NETDIR=/lib/modules/$KVER/kernel/drivers/net
|
|
for sub in ethernet mdio phy usb fddi wan; do
|
|
if [ -d "$NETDIR/$sub" ]; then
|
|
mkdir -p "$WORK/lib/modules/$KVER/kernel/drivers/net/$sub"
|
|
cp -r "$NETDIR/$sub/"* "$WORK/lib/modules/$KVER/kernel/drivers/net/$sub/" 2>/dev/null || true
|
|
fi
|
|
done
|
|
# Overlay + squashfs + loop + usb + hid + ptp + mii + net/core deps
|
|
for modpath in fs/overlayfs fs/squashfs drivers/block drivers/usb/core drivers/usb/host drivers/hid drivers/ptp drivers/net/mii.ko net/core; do
|
|
if [ -e "/lib/modules/$KVER/kernel/$modpath" ]; then
|
|
mkdir -p "$WORK/lib/modules/$KVER/kernel/$(dirname $modpath)"
|
|
cp -r "/lib/modules/$KVER/kernel/$modpath" "$WORK/lib/modules/$KVER/kernel/$modpath" 2>/dev/null || true
|
|
fi
|
|
done
|
|
|
|
# Decompress zstd modules in-place (busybox insmod can't handle .zst)
|
|
find "$WORK/lib/modules" -name '*.ko.zst' -print0 | \
|
|
xargs -0 -I {} sh -c 'zstd -d --rm -o "${1%.zst}" "$1" 2>/dev/null' _ {} || true
|
|
|
|
# Preserve modules.builtin / modules.order, regenerate modules.dep so
|
|
# modprobe can resolve dependencies inside the initramfs.
|
|
cp /lib/modules/$KVER/modules.builtin "$WORK/lib/modules/$KVER/" 2>/dev/null || true
|
|
cp /lib/modules/$KVER/modules.order "$WORK/lib/modules/$KVER/" 2>/dev/null || true
|
|
(cd "$WORK" && depmod -b . $KVER) 2>/dev/null || echo "depmod warning (non-fatal)"
|
|
|
|
# Init script
|
|
cp "{{ usb_root }}/playbook/blancco-init.sh" "$WORK/init"
|
|
chmod +x "$WORK/init"
|
|
|
|
# Build CPIO
|
|
cd "$WORK"
|
|
find . | cpio -o -H newc --quiet | gzip -1 > "{{ web_root }}/blancco/kexec-initrd.img"
|
|
cd /
|
|
rm -rf "$WORK"
|
|
echo "Built kexec-initrd.img: $(stat -c %s '{{ web_root }}/blancco/kexec-initrd.img') bytes"
|
|
ignore_errors: yes
|
|
|
|
- name: "Copy Ubuntu kernel for Blancco PXE boot"
|
|
copy:
|
|
src: "/boot/vmlinuz-{{ ansible_kernel }}"
|
|
dest: "{{ web_root }}/blancco/vmlinuz-ubuntu"
|
|
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
|
|
KVER=$(uname -r)
|
|
tar czf "{{ web_root }}/blancco/kmod.tar.gz" -C / "lib/modules/$KVER"
|
|
echo "Built kmod.tar.gz: $(du -h '{{ web_root }}/blancco/kmod.tar.gz' | cut -f1)"
|
|
args:
|
|
creates: "{{ web_root }}/blancco/kmod.tar.gz"
|
|
|
|
- name: "Extract config-clean.xml from config.img (strip null bytes)"
|
|
shell: |
|
|
if [ -f "{{ web_root }}/blancco/config.img" ]; then
|
|
WORK=$(mktemp -d)
|
|
cd "$WORK"
|
|
cpio -id < "{{ web_root }}/blancco/config.img" 2>/dev/null
|
|
tr -d '\000' < config.xml > "{{ web_root }}/blancco/config-clean.xml"
|
|
rm -rf "$WORK"
|
|
fi
|
|
args:
|
|
creates: "{{ web_root }}/blancco/config-clean.xml"
|
|
|
|
# Idempotent file copy (was bundled into the previous shell task with a
|
|
# 'creates:' gate, so playbook re-runs never picked up edits to the
|
|
# source XML - stale prefs survived deploys).
|
|
- name: "Deploy Blancco preferences.xml from playbook source"
|
|
copy:
|
|
src: "{{ usb_root }}/playbook/blancco-preferences.xml"
|
|
dest: "{{ web_root }}/blancco/preferences.xml"
|
|
mode: '0644'
|
|
|
|
# Hard gate: blancco-init.sh in initramfs drops to shell if the served
|
|
# preferences.xml is malformed or missing the SMB target. Fail the
|
|
# playbook here too so the problem surfaces at deploy time, not at the
|
|
# next erasure attempt.
|
|
- name: "Validate deployed preferences.xml is well-formed + targets the right SMB share"
|
|
shell: |
|
|
set -e
|
|
python3 -c 'import xml.etree.ElementTree as ET; ET.parse("{{ web_root }}/blancco/preferences.xml")'
|
|
grep -q '<hostname>10.9.100.1</hostname>' "{{ web_root }}/blancco/preferences.xml"
|
|
grep -q '<path>blancco-reports</path>' "{{ web_root }}/blancco/preferences.xml"
|
|
changed_when: false
|
|
|
|
- name: "Ensure Samba user for Blancco reports exists (idempotent)"
|
|
shell: |
|
|
id blancco >/dev/null 2>&1 || useradd -r -s /usr/sbin/nologin blancco
|
|
pdbedit -L 2>/dev/null | grep -q '^blancco:' || (echo -e "blancco\nblancco" | smbpasswd -a -s blancco)
|
|
changed_when: false
|
|
|
|
- name: "Check for WinPE deployment content on USB"
|
|
stat:
|
|
path: "{{ usb_root }}/images"
|
|
register: usb_images_dir
|
|
|
|
- name: "Import WinPE deployment content from USB (if present)"
|
|
shell: >
|
|
cp -rn "{{ usb_root }}/images/{{ item }}/"* "{{ samba_share }}/{{ item }}/" 2>/dev/null || true
|
|
loop: "{{ image_types }}"
|
|
when: usb_images_dir.stat.exists
|
|
|
|
- name: "Restart and enable services"
|
|
systemd:
|
|
name: "{{ item }}"
|
|
state: restarted
|
|
enabled: yes
|
|
loop:
|
|
- dnsmasq
|
|
- apache2
|
|
- smbd
|
|
|
|
- name: "Allow necessary firewall ports (UFW)"
|
|
ufw:
|
|
rule: allow
|
|
port: "{{ item }}"
|
|
proto: "{{ 'udp' if item in ['67','69'] else 'tcp' }}"
|
|
loop:
|
|
- "22"
|
|
- "67"
|
|
- "69"
|
|
- "80"
|
|
- "4433"
|
|
- "445"
|
|
- "9009"
|
|
|
|
- name: "Enable UFW firewall"
|
|
ufw:
|
|
state: enabled
|
|
policy: deny
|
|
|
|
- name: "Schedule dnsmasq restart 15s after reboot"
|
|
copy:
|
|
dest: /etc/cron.d/dnsmasq-restart
|
|
mode: '0644'
|
|
content: |
|
|
@reboot root /bin/sleep 15 && /usr/bin/systemctl restart dnsmasq.service
|
|
|
|
# --- Web Management App (Flask) ---
|
|
- name: "Create webapp directory"
|
|
file:
|
|
path: /opt/pxe-webapp
|
|
state: directory
|
|
mode: '0755'
|
|
|
|
- name: "Copy webapp from USB"
|
|
shell: >
|
|
cp -r "{{ usb_root }}/webapp/"* /opt/pxe-webapp/ 2>/dev/null || true
|
|
args:
|
|
creates: /opt/pxe-webapp/app.py
|
|
|
|
- name: "Install webapp Python dependencies (offline wheels)"
|
|
args:
|
|
executable: /bin/bash
|
|
shell: |
|
|
# Find the pip-wheels directory on the CIDATA mount
|
|
export WHEEL_DIR=""
|
|
for d in "{{ usb_root }}/pip-wheels" "{{ usb_mount }}/pip-wheels"; do
|
|
if [ -d "$d" ] && compgen -G "$d/*.whl" > /dev/null; then
|
|
export WHEEL_DIR="$(cd "$d" && pwd)"
|
|
break
|
|
fi
|
|
done
|
|
if [ -n "$WHEEL_DIR" ]; then
|
|
# Install wheels directly using Python zipfile (bypasses pip entirely)
|
|
# This avoids the pip3/distutils incompatibility between Ubuntu 22.04 debs and Python 3.12
|
|
python3 << 'PYEOF'
|
|
import zipfile, sysconfig, glob, os
|
|
site = sysconfig.get_path('platlib')
|
|
wheel_dir = os.environ['WHEEL_DIR']
|
|
for whl in sorted(glob.glob(os.path.join(wheel_dir, '*.whl'))):
|
|
name = os.path.basename(whl)
|
|
print(f' Installing {name}')
|
|
with zipfile.ZipFile(whl) as z:
|
|
z.extractall(site)
|
|
print('All wheels installed to ' + site)
|
|
PYEOF
|
|
else
|
|
# Fallback: try system pip (works if system has internet and compatible pip)
|
|
python3 -m pip install --break-system-packages flask lxml 2>/dev/null ||
|
|
pip3 install --break-system-packages flask lxml
|
|
fi
|
|
|
|
- name: "Create systemd service for PXE webapp"
|
|
copy:
|
|
dest: /etc/systemd/system/pxe-webapp.service
|
|
content: |
|
|
[Unit]
|
|
Description=PXE Server Web Management
|
|
After=network.target apache2.service
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=root
|
|
WorkingDirectory=/opt/pxe-webapp
|
|
Environment=SAMBA_SHARE={{ samba_share }}
|
|
Environment=CLONEZILLA_SHARE=/srv/samba/clonezilla
|
|
Environment=WEB_ROOT={{ web_root }}
|
|
Environment=BLANCCO_REPORTS=/srv/samba/blancco-reports
|
|
Environment=ENROLLMENT_SHARE=/srv/samba/enrollment
|
|
Environment=AUDIT_LOG=/var/log/pxe-webapp-audit.log
|
|
# Lock TZ so report/backup mtimes and audit-log timestamps render
|
|
# in Eastern time regardless of how the host's /etc/localtime ends
|
|
# up. Without this, a Python process started before timedatectl
|
|
# finishes can cache UTC for its lifetime.
|
|
Environment=TZ=America/New_York
|
|
ExecStart=/usr/bin/python3 app.py
|
|
Restart=always
|
|
RestartSec=5
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
|
|
- name: "Enable and start PXE webapp service"
|
|
systemd:
|
|
name: pxe-webapp
|
|
state: started
|
|
enabled: yes
|
|
daemon_reload: yes
|
|
|
|
- name: "Configure unified Apache site (static files + webapp proxy)"
|
|
copy:
|
|
dest: /etc/apache2/sites-available/pxe-server.conf
|
|
content: |
|
|
Listen 9009
|
|
<VirtualHost *:80>
|
|
DocumentRoot {{ web_root }}
|
|
<Directory "{{ web_root }}">
|
|
Options Indexes FollowSymLinks
|
|
AllowOverride None
|
|
Require all granted
|
|
</Directory>
|
|
ProxyPreserveHost On
|
|
ProxyPass /manage http://127.0.0.1:9010/
|
|
ProxyPassReverse /manage http://127.0.0.1:9010/
|
|
</VirtualHost>
|
|
<VirtualHost *:9009>
|
|
Alias /static /opt/pxe-webapp/static
|
|
<Directory "/opt/pxe-webapp/static">
|
|
Require all granted
|
|
Options -Indexes
|
|
</Directory>
|
|
ProxyPreserveHost On
|
|
ProxyPass /static !
|
|
ProxyPass / http://127.0.0.1:9010/
|
|
ProxyPassReverse / http://127.0.0.1:9010/
|
|
</VirtualHost>
|
|
|
|
- name: "Enable Apache proxy modules"
|
|
command: a2enmod proxy proxy_http
|
|
args:
|
|
creates: /etc/apache2/mods-enabled/proxy.load
|
|
|
|
- name: "Disable default Apache site"
|
|
command: a2dissite 000-default.conf
|
|
args:
|
|
removes: /etc/apache2/sites-enabled/000-default.conf
|
|
|
|
- name: "Enable unified PXE server site"
|
|
command: a2ensite pxe-server.conf
|
|
args:
|
|
creates: /etc/apache2/sites-enabled/pxe-server.conf
|
|
|
|
- name: "Reload Apache after site changes"
|
|
systemd:
|
|
name: apache2
|
|
state: reloaded
|
|
|
|
# Single-NIC fresh-deploy default. Boxes that need higher throughput
|
|
# (e.g. WJF prod uses a USB-C 5 Gbps NIC) override this with a bridge
|
|
# config bonding the USB NIC + onboard NIC into br-pxe. Live override
|
|
# currently deployed on 10.9.100.1 (do NOT re-run this task there
|
|
# without first reviewing /etc/netplan/50-cloud-init.yaml.pre-gold-swap):
|
|
#
|
|
# network:
|
|
# version: 2
|
|
# renderer: networkd
|
|
# ethernets:
|
|
# enp128s31f6: { dhcp4: no }
|
|
# enx34c8d6b11010: { dhcp4: no }
|
|
# bridges:
|
|
# br-pxe:
|
|
# interfaces: [enp128s31f6, enx34c8d6b11010]
|
|
# addresses: [10.9.100.1/24]
|
|
# parameters:
|
|
# stp: false
|
|
#
|
|
# Re-running THIS task on a box already using the bridge config will
|
|
# replace the bridge with a single-iface flat config and likely break
|
|
# the PXE LAN. Skip this task or restore the .pre-gold-swap backup
|
|
# before applying.
|
|
- name: "Configure static IP for PXE interface"
|
|
copy:
|
|
dest: /etc/netplan/50-cloud-init.yaml
|
|
backup: yes
|
|
content: |
|
|
network:
|
|
version: 2
|
|
renderer: networkd
|
|
ethernets:
|
|
{{ pxe_iface }}:
|
|
dhcp4: no
|
|
addresses: [10.9.100.1/24]
|
|
notify: "Apply netplan"
|
|
|
|
handlers:
|
|
- name: "Apply netplan"
|
|
command: netplan apply
|
|
|
|
- name: "reload sysctl"
|
|
command: sysctl --system
|