Initial project setup: automated PXE server provisioning

Reorganized from OneDrive export into a clean project structure:
- autoinstall/: cloud-init user-data and meta-data for Ubuntu 24.04 autoinstall
- playbook/: Ansible playbook for PXE server config (dnsmasq, Apache, Samba, iPXE)
- unattend/: Windows unattend.xml sample for image deployment
- build-usb.sh: builds a bootable USB with Ubuntu installer + CIDATA partition
- download-packages.sh: downloads all offline .deb dependencies via Docker

Key improvements over original:
- Fully air-gapped: all packages bundled offline, no WiFi needed
- Hardware-agnostic network config (wildcard NIC matching)
- Removed plaintext WiFi credentials
- Single USB build process (was 15+ manual steps)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
cproudlock
2026-02-06 15:47:36 -05:00
commit 5791bd1b49
9 changed files with 1040 additions and 0 deletions

30
.gitignore vendored Normal file
View File

@@ -0,0 +1,30 @@
# Large binary files — download/build these, don't commit them
*.deb
*.zip
*.wim
*.iso
*.efi
*.sdi
# OneDrive download artifacts
OneDrive_*/
# Error folders from OneDrive download
__*/
___*.txt
# Original OneDrive folder structure (reorganized into autoinstall/ and playbook/)
WestJeff*/
# Duplicate at root (canonical copy in unattend/)
/FlatUnattendW10.xml
# Offline packages (built by download-packages.sh)
offline-packages/
# OS files
.DS_Store
Thumbs.db
# Secrets
secrets.md

0
autoinstall/meta-data Normal file
View File

106
autoinstall/user-data Normal file
View File

@@ -0,0 +1,106 @@
#cloud-config
autoinstall:
version: 1
# Locale, keyboard, timezone
locale: en_US.UTF-8
keyboard:
layout: us
variant: ""
timezone: America/New_York
# Network configuration
# Uses a broad match so any wired NIC gets the static PXE address.
# No WiFi needed — all packages are on the CIDATA partition.
network:
version: 2
ethernets:
any-eth:
match:
name: "en*"
addresses:
- 10.9.100.1/24
dhcp4: false
dhcp6: false
optional: true
# Storage configuration
storage:
layout:
name: lvm
match:
size: largest
swap:
size: 0
# User identity
identity:
hostname: pxeserver
username: pxe
password: "$6$rounds=656000$TpsuBw0N85085mpx$KtKsCwFlowg4NY41gUqx5ljef8cJ8uPFfgg43MyCPWByfXkhM5XushcdtkNps6lKeQFQZtli/QU.s52AUc7XC."
# Installer-stage late commands
late-commands:
# Install deb packages from CIDATA USB
- |
curtin in-target --target=/target -- bash -c '
mkdir -p /mnt/cidata
CIDATA_DEV=$(blkid -L CIDATA)
if [ -n "$CIDATA_DEV" ]; then
mount "$CIDATA_DEV" /mnt/cidata
if compgen -G "/mnt/cidata/packages/*.deb" > /dev/null; then
cp /mnt/cidata/packages/*.deb /tmp/
dpkg -i /tmp/*.deb 2>/dev/null || true
dpkg -i /tmp/*.deb 2>/dev/null || true
if command -v nmcli >/dev/null; then
systemctl enable NetworkManager
fi
fi
umount /mnt/cidata
fi
'
# Create first-boot.sh
- |
curtin in-target --target=/target -- bash -c '
cat <<"EOF" > /opt/first-boot.sh
#!/bin/bash
CIDATA_DEV=$(blkid -L CIDATA)
if [ -n "$CIDATA_DEV" ]; then
mkdir -p /mnt/usb
mount "$CIDATA_DEV" /mnt/usb
# Install all offline .deb packages (ansible, dnsmasq, apache2, samba, etc.)
if compgen -G "/mnt/usb/packages/*.deb" > /dev/null; then
dpkg -i /mnt/usb/packages/*.deb 2>/dev/null || true
dpkg -i /mnt/usb/packages/*.deb 2>/dev/null || true
fi
# Run the Ansible playbook
if [ -f /mnt/usb/playbook/pxe_server_setup.yml ]; then
cd /mnt/usb/playbook
ansible-playbook -i localhost, -c local pxe_server_setup.yml
fi
umount /mnt/usb
fi
# Disable rc.local to prevent rerunning
sed -i "s|^/opt/first-boot.sh.*|# &|" /etc/rc.local
lvextend -r -l +100%FREE /dev/mapper/ubuntu--vg-ubuntu--lv || true
EOF
'
- curtin in-target --target=/target -- chmod +x /opt/first-boot.sh
# Create rc.local without unintended indentation
- |
curtin in-target --target=/target -- bash -c '
cat <<"EOF" > /etc/rc.local
#!/bin/bash
/opt/first-boot.sh > /var/log/first-boot.log 2>&1 &
exit 0
EOF
'
- curtin in-target --target=/target -- chmod +x /etc/rc.local
user-data:
disable_root: false
refresh-installer:
update: yes

196
build-usb.sh Executable file
View File

@@ -0,0 +1,196 @@
#!/bin/bash
#
# build-usb.sh — Build a bootable PXE-server installer USB
#
# Creates a two-partition USB:
# Partition 1: Ubuntu Server 24.04 installer (ISO contents)
# Partition 2: CIDATA volume (autoinstall config, .debs, playbook)
#
# The target machine boots from this USB, Ubuntu auto-installs with
# cloud-init (user-data/meta-data from CIDATA), installs offline .debs,
# and on first boot runs the Ansible playbook to configure PXE services.
#
# Usage:
# sudo ./build-usb.sh /dev/sdX /path/to/ubuntu-24.04-live-server-amd64.iso
#
# WARNING: This will ERASE the target USB device.
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"
# --- Validate arguments ---
if [ $# -ne 2 ]; then
echo "Usage: sudo $0 /dev/sdX /path/to/ubuntu-24.04.iso"
echo ""
echo "Available removable devices:"
lsblk -d -o NAME,SIZE,TRAN,RM | grep -E '^\S+\s+\S+\s+(usb)\s+1'
exit 1
fi
USB_DEV="$1"
ISO_PATH="$2"
# Safety checks
if [ "$(id -u)" -ne 0 ]; then
echo "ERROR: Must run as root (sudo)."
exit 1
fi
if [ ! -b "$USB_DEV" ]; then
echo "ERROR: $USB_DEV is not a block device."
exit 1
fi
if [ ! -f "$ISO_PATH" ]; then
echo "ERROR: ISO not found at $ISO_PATH"
exit 1
fi
# Verify it's a removable device (safety against wiping system disks)
REMOVABLE=$(lsblk -nd -o RM "$USB_DEV" 2>/dev/null || echo "0")
if [ "$REMOVABLE" != "1" ]; then
echo "WARNING: $USB_DEV does not appear to be a removable device."
read -rp "Are you SURE you want to erase $USB_DEV? (type YES): " CONFIRM
if [ "$CONFIRM" != "YES" ]; then
echo "Aborted."
exit 1
fi
fi
# Verify required source files exist
if [ ! -f "$AUTOINSTALL_DIR/user-data" ]; then
echo "ERROR: user-data not found at $AUTOINSTALL_DIR/user-data"
exit 1
fi
if [ ! -f "$AUTOINSTALL_DIR/meta-data" ]; then
echo "ERROR: meta-data not found at $AUTOINSTALL_DIR/meta-data"
exit 1
fi
if [ ! -f "$PLAYBOOK_DIR/pxe_server_setup.yml" ]; then
echo "ERROR: pxe_server_setup.yml not found at $PLAYBOOK_DIR/"
exit 1
fi
echo "============================================"
echo "PXE Server USB Builder"
echo "============================================"
echo "USB Device : $USB_DEV"
echo "ISO : $ISO_PATH"
echo "Source Dir : $SCRIPT_DIR"
echo ""
echo "This will ERASE all data on $USB_DEV."
read -rp "Continue? (y/N): " PROCEED
if [[ ! "$PROCEED" =~ ^[Yy]$ ]]; then
echo "Aborted."
exit 1
fi
# --- Unmount any existing partitions ---
echo ""
echo "[1/6] Unmounting existing partitions on $USB_DEV..."
for part in "${USB_DEV}"*; do
umount "$part" 2>/dev/null || true
done
# --- Write ISO to USB ---
echo "[2/6] Writing Ubuntu ISO to $USB_DEV (this may take several minutes)..."
dd if="$ISO_PATH" of="$USB_DEV" bs=4M status=progress oflag=sync
sync
# --- Find the end of the ISO to create CIDATA partition ---
echo "[3/6] Creating CIDATA partition after ISO data..."
# Get ISO size in bytes and calculate the start sector for the new partition
ISO_SIZE=$(stat -c%s "$ISO_PATH")
SECTOR_SIZE=512
# Start the CIDATA partition 1MB after the ISO ends (alignment)
START_SECTOR=$(( (ISO_SIZE / SECTOR_SIZE) + 2048 ))
# Use sfdisk to append a new partition
echo " ISO size: $((ISO_SIZE / 1024 / 1024)) MB"
echo " CIDATA partition starts at sector $START_SECTOR"
# Add a new partition using sfdisk --append
echo "${START_SECTOR},+,L" | sfdisk --append "$USB_DEV" --no-reread 2>/dev/null || true
partprobe "$USB_DEV"
sleep 2
# Determine the new partition name (could be sdX3, sdX4, etc.)
CIDATA_PART=""
for part in "${USB_DEV}"[0-9]*; do
# Find the partition that starts at or after our start sector
PART_START=$(sfdisk -d "$USB_DEV" 2>/dev/null | grep "$part" | grep -o 'start=[[:space:]]*[0-9]*' | grep -o '[0-9]*')
if [ -n "$PART_START" ] && [ "$PART_START" -ge "$START_SECTOR" ]; then
CIDATA_PART="$part"
break
fi
done
# Fallback: use the last partition
if [ -z "$CIDATA_PART" ]; then
CIDATA_PART=$(lsblk -ln -o NAME "$USB_DEV" | tail -1)
CIDATA_PART="/dev/$CIDATA_PART"
fi
echo " CIDATA partition: $CIDATA_PART"
# --- Format CIDATA partition ---
echo "[4/6] Formatting $CIDATA_PART as FAT32 (label: CIDATA)..."
mkfs.vfat -F 32 -n CIDATA "$CIDATA_PART"
# --- Mount and copy files ---
echo "[5/6] Copying autoinstall config, packages, and playbook to CIDATA..."
MOUNT_POINT=$(mktemp -d)
mount "$CIDATA_PART" "$MOUNT_POINT"
# Copy cloud-init files
cp "$AUTOINSTALL_DIR/user-data" "$MOUNT_POINT/"
cp "$AUTOINSTALL_DIR/meta-data" "$MOUNT_POINT/"
# Copy offline .deb packages into packages/ subdirectory
mkdir -p "$MOUNT_POINT/packages"
DEB_COUNT=0
if [ -d "$OFFLINE_PKG_DIR" ]; then
for deb in "$OFFLINE_PKG_DIR"/*.deb; do
if [ -f "$deb" ]; then
cp "$deb" "$MOUNT_POINT/packages/"
DEB_COUNT=$((DEB_COUNT + 1))
fi
done
fi
echo " Copied $DEB_COUNT .deb packages to packages/"
# Copy playbook directory
cp -r "$PLAYBOOK_DIR" "$MOUNT_POINT/playbook"
echo " Copied playbook/"
# List what's on CIDATA
echo ""
echo " CIDATA contents:"
ls -lh "$MOUNT_POINT/" | sed 's/^/ /'
# --- Cleanup ---
echo ""
echo "[6/6] Syncing and unmounting..."
sync
umount "$MOUNT_POINT"
rmdir "$MOUNT_POINT"
echo ""
echo "============================================"
echo "USB build complete!"
echo "============================================"
echo ""
echo "Next steps:"
echo " 1. Insert USB into target machine"
echo " 2. Boot from USB (F12 / boot menu)"
echo " 3. Ubuntu will auto-install and configure the PXE server"
echo " 4. After reboot, move the NIC to the isolated PXE network"
echo ""

83
download-packages.sh Executable file
View File

@@ -0,0 +1,83 @@
#!/bin/bash
#
# download-packages.sh — Download all .deb packages needed for offline PXE server setup
#
# Run this on a machine with internet access running Ubuntu 24.04 (Noble).
# It downloads every .deb needed by the Ansible playbook into a local directory,
# which then gets bundled onto the installer USB.
#
# Usage:
# ./download-packages.sh [output_directory]
#
# Default output: ./offline-packages/
set -euo pipefail
OUT_DIR="${1:-./offline-packages}"
mkdir -p "$OUT_DIR"
# Packages installed by the Ansible playbook (pxe_server_setup.yml)
PLAYBOOK_PACKAGES=(
ansible
dnsmasq
apache2
samba
unzip
ufw
cron
)
# Packages installed during autoinstall late-commands (NetworkManager, WiFi, etc.)
# These are already in your ubuntu_playbook/*.deb files, but we can refresh them here too.
AUTOINSTALL_PACKAGES=(
network-manager
wpasupplicant
wireless-tools
linux-firmware
firmware-sof-signed
)
ALL_PACKAGES=("${PLAYBOOK_PACKAGES[@]}" "${AUTOINSTALL_PACKAGES[@]}")
echo "============================================"
echo "Offline Package Downloader"
echo "============================================"
echo "Output directory: $OUT_DIR"
echo ""
echo "Packages to resolve:"
printf ' - %s\n' "${ALL_PACKAGES[@]}"
echo ""
# Update package cache
echo "[1/3] Updating package cache..."
sudo apt-get update -qq
# Simulate install to find all dependencies
echo "[2/3] Resolving dependencies..."
DEPS=$(apt-get install --simulate "${ALL_PACKAGES[@]}" 2>&1 \
| grep "^Inst " \
| awk '{print $2}' \
| sort -u)
DEP_COUNT=$(echo "$DEPS" | wc -l)
echo " Found $DEP_COUNT packages (including dependencies)"
# Download all packages
echo "[3/3] Downloading packages to $OUT_DIR..."
cd "$OUT_DIR"
apt-get download $DEPS 2>&1 | tail -5
DEB_COUNT=$(ls -1 *.deb 2>/dev/null | wc -l)
TOTAL_SIZE=$(du -sh . | cut -f1)
echo ""
echo "============================================"
echo "Download complete!"
echo "============================================"
echo " Packages: $DEB_COUNT"
echo " Total size: $TOTAL_SIZE"
echo " Location: $OUT_DIR/"
echo ""
echo "Next: copy these into your ubuntu_playbook/ directory"
echo " cp $OUT_DIR/*.deb /path/to/ubuntu_playbook/"
echo ""

2
playbook/inventory.ini Normal file
View File

@@ -0,0 +1,2 @@
[pxe_servers]
localhost ansible_connection=local

View File

@@ -0,0 +1,272 @@
---
- 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
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" # where your USB is mounted
image_types:
- geastandardpbr
- geaengineerpbr
- geashopfloorpbr
- gestandardlegacy
- geengineerlegacy
- geshopfloorlegacy
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: "Determine PXE interface"
set_fact:
pxe_iface: >-
{{ (ansible_interfaces
| select('match','^e(th|n)')
| reject('equalto','lo')
| reject('equalto', ansible_default_ipv4.interface)
| list
)
| first
| default(ansible_default_ipv4.interface) }}
- name: "Debug: final pxe_iface choice"
debug:
msg: "Using {{ pxe_iface }} for DHCP/TFTP"
- 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
dhcp-option=3,10.9.100.1
dhcp-option=6,8.8.8.8
enable-tftp
tftp-root={{ tftp_dir }}
dhcp-boot=ipxe.efi
- 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 GetPxeScript.aspx"
copy:
dest: "{{ web_root }}/Altiris/iPXE/GetPxeScript.aspx"
backup: yes
content: |
#!ipxe
set server 10.9.100.1
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
- 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: "Configure Samba share"
blockinfile:
path: /etc/samba/smb.conf
backup: yes
block: |
[winpeapps]
path = {{ samba_share }}
browseable = yes
read only = no
guest ok = yes
- 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: "Copy WinPE & boot files from USB"
copy:
src: "{{ usb_mount }}/{{ 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" }
- name: "Copy iPXE binaries from USB"
copy:
src: "{{ usb_mount }}/{{ item }}"
dest: "{{ tftp_dir }}/{{ item }}"
mode: '0755'
loop:
- ipxe.efi
- 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:
- 67
- 69
- 80
- 4433
- 445
- name: "Enable UFW firewall"
ufw:
state: enabled
policy: allow
- name: "Schedule dnsmasq restart 15s after reboot"
cron:
name: "Restart dnsmasq after reboot"
user: root
special_time: "reboot"
job: "/bin/sleep 15 && /usr/bin/systemctl restart dnsmasq.service"
- 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

129
setup-guide-original.txt Normal file
View File

@@ -0,0 +1,129 @@
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 vendors one-time boot key) and choose the USB device.
2.3 Follow Ubuntus 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.

View File

@@ -0,0 +1,222 @@
<?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend"
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<!-- 1. windowsPE is intentionally empty -->
<settings pass="windowsPE" />
<!-- 2. Offline servicing (drivers) -->
<settings pass="offlineServicing">
<component name="Microsoft-Windows-PnpCustomizationsNonWinPE"
processorArchitecture="amd64"
publicKeyToken="31bf3856ad364e35"
language="neutral"
versionScope="nonSxS">
<DriverPaths>
<PathAndCredentials wcm:action="add" wcm:keyValue="1">
<Path>W:\Drivers</Path>
</PathAndCredentials>
<PathAndCredentials wcm:action="add" wcm:keyValue="2">
<Path>W:\Deploy\Applications\extra\printdrivers\BROTHER\UNIV-PS-01181\PS\64</Path>
</PathAndCredentials>
<PathAndCredentials wcm:action="add" wcm:keyValue="3">
<Path>W:\Deploy\Applications\extra\printdrivers\HP</Path>
</PathAndCredentials>
<PathAndCredentials wcm:action="add" wcm:keyValue="4">
<Path>W:\Deploy\Applications\extra\printdrivers\XEROX\UNIV_5.1035.2.0_PS_x64_Driver</Path>
</PathAndCredentials>
</DriverPaths>
</component>
</settings>
<!-- 3. specialize: computer naming + RunSynchronous all in ONE component -->
<settings pass="specialize">
<!-- 3a. Shell-Setup for naming/owner/org -->
<component name="Microsoft-Windows-Shell-Setup"
processorArchitecture="amd64"
publicKeyToken="31bf3856ad364e35"
language="neutral"
versionScope="nonSxS">
<ComputerName>H%serialnumber%</ComputerName>
<RegisteredOrganization>GE Aerospace</RegisteredOrganization>
<RegisteredOwner>GE</RegisteredOwner>
<TimeZone>Eastern Standard Time</TimeZone>
</component>
<!-- 3b. RunSynchronous for all of your installers, copies, etc. -->
<component name="Microsoft-Windows-Deployment"
processorArchitecture="amd64"
publicKeyToken="31bf3856ad364e35"
language="neutral"
versionScope="nonSxS">
<RunSynchronous>
<!-- EAP-PEAP MSI -->
<RunSynchronousCommand wcm:action="add">
<Order>1</Order>
<Path>msiexec /i "C:\Deploy\Applications\extra\wireless\EAP-PEAP.msi" /quiet /norestart</Path>
<Description>Install EAP-PEAP</Description>
</RunSynchronousCommand>
<!-- Wi-Fi profiles -->
<RunSynchronousCommand wcm:action="add">
<Order>2</Order>
<Path>cmd /c netsh wlan add profile filename="C:\Deploy\Applications\extra\wireless\BLUESSO.xml" user=all</Path>
<Description>Add BLUESSO WiFi profile</Description>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Order>3</Order>
<Path>cmd /c netsh wlan add profile filename="C:\Deploy\Applications\extra\wireless\WiFi-Profile.xml" user=all</Path>
<Description>Add generic WiFi profile</Description>
</RunSynchronousCommand>
<!-- Certificates -->
<RunSynchronousCommand wcm:action="add">
<Order>4</Order>
<Path>cmd /c certutil -addstore Root "C:\Deploy\Applications\GE_External_Root_CA_2_1.cer"</Path>
<Description>Install External Root Certificate</Description>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Order>5</Order>
<Path>cmd /c certutil -addstore CA "C:\Deploy\Applications\GE_External_Intermediate_CA_2_1.cer"</Path>
<Description>Install External Intermediate Certificate</Description>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Order>6</Order>
<Path>cmd /c certutil -addstore Root "C:\Deploy\Applications\GE_Enterprise_Root_CA_2_1.cer"</Path>
<Description>Install Enterprise Root Certificate</Description>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Order>7</Order>
<Path>cmd /c certutil -addstore CA "C:\Deploy\Applications\GE_Enterprise_Device_Issuing_CA_2_1.cer"</Path>
<Description>Install Enterprise Device Issuing Certificate</Description>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Order>8</Order>
<Path>cmd /c certutil -addstore CA "C:\Deploy\Applications\GE_Enterprise_Server_Issuing_CA_2_1.cer"</Path>
<Description>Install Enterprise Server Issuing Certificate</Description>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Order>9</Order>
<Path>cmd /c certutil -addstore CA "C:\Deploy\Applications\GE_Enterprise_Smart_Card_Issuing_CA_2_1.cer"</Path>
<Description>Install SmartCard Issuing Certificate</Description>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Order>10</Order>
<Path>cmd /c certutil -addstore CA "C:\Deploy\Applications\GE_Enterprise_User_Issuing_CA_2_1.cer"</Path>
<Description>Install User Issuing Certificate</Description>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Order>11</Order>
<Path>cmd /c certutil -addstore Root "C:\Deploy\Applications\GE_Aerospace_Enterprise_Root_CA_1.cer"</Path>
<Description>Install Aerospace Enterprise Root CA</Description>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Order>12</Order>
<Path>cmd /c certutil -addstore Root "C:\Deploy\Applications\ZscalerCommercialCertificate-2048-SHA256.crt"</Path>
<Description>Install Zscaler Certificate</Description>
</RunSynchronousCommand>
<!-- Fonts -->
<RunSynchronousCommand wcm:action="add">
<Order>13</Order>
<Path>cmd /c copy "C:\Deploy\Applications\extra\fonts\3OF9.TTF" "%WINDIR%\Fonts\" /Y</Path>
<Description>Copy 3OF9 Font</Description>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Order>14</Order>
<Path>cmd /c reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts" /v "3 of 9 Barcode" /t REG_SZ /d "3OF9.TTF" /f</Path>
<Description>Register 3OF9 Barcode Font</Description>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Order>15</Order>
<Path>cmd /c copy "C:\Deploy\Applications\extra\fonts\Code39AzaleaNarrow3.ttf" "%WINDIR%\Fonts\" /Y</Path>
<Description>Copy Code39 Azalea Narrow Font</Description>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Order>16</Order>
<Path>cmd /c reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts" /v "Code39AzaleaNarrow3" /t REG_SZ /d "Code39AzaleaNarrow3.ttf" /f</Path>
<Description>Register Code39 Azalea Narrow Font</Description>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Order>17</Order>
<Path>cmd /c copy "C:\Deploy\Applications\extra\fonts\Code39Azalea.ttf" "%WINDIR%\Fonts\" /Y</Path>
<Description>Copy Code39 Azalea Font</Description>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Order>18</Order>
<Path>cmd /c reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts" /v "Code39Azalea" /t REG_SZ /d "Code39Azalea.ttf" /f</Path>
<Description>Register Code39 Azalea Font</Description>
</RunSynchronousCommand>
<!-- OpenText installers -->
<RunSynchronousCommand wcm:action="add">
<Order>19</Order>
<Path>C:\Deploy\Applications\extra\opentext\opentext_hostexplorer_sp1_15.0_v01.exe /quiet /norestart</Path>
<Description>Install OpenText HostExplorer SP1</Description>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Order>20</Order>
<Path>C:\Deploy\Applications\extra\opentext\J2SE_Runtime_Environment_1.6.0_22_Static_Config_V2_Co-Exist.EXE /silent /norestart</Path>
<Description>Install J2SE Runtime Environment 1.6.0_22</Description>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Order>21</Order>
<Path>C:\Deploy\Applications\extra\opentext\unattended.bat</Path>
<Description>Install J2SE Runtime Environment 1.6.0_22</Description>
</RunSynchronousCommand>
<!-- Adobe -->
<RunSynchronousCommand wcm:action="add">
<Order>22</Order>
<Path>msiexec /i "C:\Deploy\Applications\extra\adobe\AcroRead.msi" TRANSFORMS="C:\Deploy\Applications\extra\adobe\AcroRead.mst" /quiet /norestart</Path>
<Description>Install Adobe</Description>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Order>23</Order>
<Path>msiexec /p "C:\Deploy\Applications\extra\adobe\AcroRdrDCUpd2500120531.msp" /quiet /norestart</Path>
<Description>Apply Adobe Reader Update</Description>
</RunSynchronousCommand>
</RunSynchronous>
</component>
</settings>
<!-- 4. oobeSystem: hide OEM/EULA screens -->
<settings pass="oobeSystem">
<component name="Microsoft-Windows-Shell-Setup"
processorArchitecture="amd64"
publicKeyToken="31bf3856ad364e35"
language="neutral"
versionScope="nonSxS">
<OOBE>
<HideEULAPage>true</HideEULAPage>
<HideOEMRegistrationScreen>true</HideOEMRegistrationScreen>
<HideOnlineAccountScreens>false</HideOnlineAccountScreens>
<HideWirelessSetupInOOBE>false</HideWirelessSetupInOOBE>
<HideLocalAccountScreen>true</HideLocalAccountScreen>
<NetworkLocation>Work</NetworkLocation>
<ProtectYourPC>3</ProtectYourPC>
<SkipUserOOBE>false</SkipUserOOBE>
<SkipMachineOOBE>false</SkipMachineOOBE>
</OOBE>
<FirstLogonCommands>
<SynchronousCommand wcm:action="add">
<Order>1</Order>
<CommandLine>C:\Deploy\Applications\extra\zscaler\zscaler.bat</CommandLine>
<Description>Install Zscaler Client Connector</Description>
</SynchronousCommand>
<SynchronousCommand wcm:action="add">
<Order>2</Order>
<CommandLine>shutdown -a</CommandLine>
<Description>Cancel any scheduled shutdown from Office installation</Description>
</SynchronousCommand>
<SynchronousCommand wcm:action="add">
<Order>3</Order>
<CommandLine>cmd /c cd C:\Deploy\Applications\extra\office &amp;&amp; install.bat</CommandLine>
<Description>Install Office</Description>
</SynchronousCommand>
</FirstLogonCommands>
</component>
</settings>
</unattend>