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>
273 lines
7.6 KiB
YAML
273 lines
7.6 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
|
|
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
|