Files
pxe-server/docs/SITE-CUSTOMIZATION.md
cproudlock b11e678297 NICs: pick the imaging port at install, GE can go into any other port
Sites no longer need either MAC in site-vars.

pxe_nic_mac empty: before anything changes, the playbook runs
pxe-pick-nic --auto. It takes the one wired port with a link, accepts it
only if pxe-nic-check calls it isolated (no LLDP/CDP/802.1X, no other
DHCP server, at most 10 hosts heard in 65 s), and pins it. The choice is
saved in /etc/pxe-server/nic-pick.conf and never re-guessed at boot.
When it cannot tell (no link, two links, not isolated) nothing changes,
and the console banner says why and to run sudo pxe-pick-nic: pull the
imaging cable and plug it back in, and the port that blinked is the
imaging one. Both helpers are installed before the pick, so that works
after a refused first boot. The site's corp domains and allowed
switches are passed to the check.

corp_nic_mac empty: every other wired port (en*, eth*) is a catch-all
DHCP client, so GE can be plugged in at install or later.
- dnsmasq stays pinned with interface=pxe0. A new check refuses any
  dhcp-range outside the imaging subnet, and proxy ranges.
- The catch-all ports ignore offers from 172.16.9.1.
- UFW allows SSH and 9009 by source on any interface, and keeps those
  rules when the old any-interface rules are removed.
- pxe_nic_name must not look like en*/eth* in this mode.

pxe-netplan-apply.sh takes 3 or 5 args. Without a pinned GE NIC it:
- renames stray names (the old pxe0 after a re-pick, a leftover corp0)
  to enx<mac>, so the catch-all takes them, and restores them on revert;
- checks that every NIC that had a DHCP address before has one after,
  tracked by MAC.

With both MACs set (the WJ server), netplan, the dnsmasq pin and UFW
render byte-for-byte as before.

Tested in the pxetest VMs:
- Fresh install with no MACs and the GE link down: the imaging port was
  picked and pinned as pxe0 with 172.16.9.1, and dnsmasq bound DHCP to
  pxe0 only. GE plugged in afterwards got a lease, SSH worked, and the
  guard reported pxe0 isolated.
- --auto refuses with two links.
- The interactive pick works by pulling and replugging the link.
- A 3-arg re-apply is UNCHANGED.

Unit tests (fake sysfs) cover the re-pick and pinned-to-catch-all renames.
2026-10-16 15:56:47 -04:00

14 KiB

Site Customization

The PXE server in this repo was built for the GE Aerospace West Jefferson site. To stand it up at another site, several values need to change. This document lists every site-specific value, where it lives today, and the recommended forward state.

Status

The repo has not yet been parameterized. As of 2026-04-14 the site-specific values below are hardcoded in multiple files. The config/ directory at the repo root is a placeholder for the future state where one YAML file drives substitution at build time. Until that exists, customizing for a new site means editing the source files directly.

If you are bringing up the first non-WJ site, expect to do this manually and contribute a config/sites/<sitename>.yaml template back to the repo.

Values to change

Network

The imaging NIC (isolated imaging LAN, static 172.16.9.1, the only NIC that serves DHCP/TFTP/HTTP boot/SMB) is pinned by MAC. The GE corporate side (DHCP client; SSH and the webapp on 9009 only) is optional. Both MACs in playbook/site-vars.yml (copy playbook/site-vars.example.yml) may be left empty:

  • pxe_nic_mac empty: the first boot picks the imaging NIC itself (pxe-pick-nic --auto). It takes the one wired port with a link, and only if pxe-nic-check calls it isolated, then pins it; the choice is in /etc/pxe-server/nic-pick.conf and is never re-guessed at boot. So during the install plug in ONLY the imaging switch. When it cannot tell (no link, two links, or that port sees GE) it changes nothing and the console says to run sudo pxe-pick-nic: pull the imaging cable and plug it back in, and the port that blinked is the imaging one. Run it again after swapping a dead USB NIC, then re-run the playbook.
  • corp_nic_mac empty: every other wired port (en*, eth*) is a DHCP client, so the GE cable can go into any of them, at install or later. dnsmasq is pinned to the imaging NIC (interface=) and serves only the imaging subnet, and those ports ignore DHCP offers from 172.16.9.1. UFW allows SSH and 9009 by source, not by interface.
  • corp_nic_mac set: that port alone is the GE NIC, named corp0 (as on the WJ server).

The first-boot playbook pins the result in netplan (names pxe0 / corp0, overridable), dnsmasq, UFW and Samba.

Value Default Where it lives
PXE server IP 172.16.9.1 playbook/pxe_server_setup.yml (dnsmasq config, iPXE script, samba conf, webapp env), playbook/startnet.cmd (mount paths), boot-tools/blancco/grub-blancco.cfg (TFTP/HTTP URLs)
PXE subnet 172.16.9.0/24 Same as above, plus playbook/pxe_server_setup.yml (UFW rules)
DHCP range 172.16.9.10-100 playbook/site-vars.yml (pxe_dhcp_start, pxe_dhcp_end)
NIC MACs / names (picked at install) / pxe0, corp0 playbook/site-vars.yml (pxe_nic_mac, corp_nic_mac, pxe_nic_name, corp_nic_name)
Hostname pxeserver autoinstall/user-data (identity.hostname)

Access from the GE corporate network

SSH (password auth, the shared pxe password) and the webapp on 9009 (no login, runs as root) are reachable on the corp NIC. By default from ALL of GE, as before, so a re-run never locks anyone out; the play prints a loud warning on every run while that is so. Set these in playbook/site-vars.yml:

Var Default Effect
corp_admin_cidrs [] List of admin subnets/hosts, e.g. ["10.134.48.0/24", "3.12.34.56/32"]. When non-empty, UFW allows 22 and 9009 from these only (on corp_nic_name when corp_nic_mac is set, else on any interface) and drops the any-source rules. The play refuses (nothing changed) if an entry is invalid or your own SSH session is not inside one of them. The imaging NIC keeps its rules.
corp_ssh_password_auth true false adds /etc/ssh/sshd_config.d/10-pxe-corp-auth.conf: PasswordAuthentication no, re-allowed by a Match Address block for pxe_subnet and localhost. Keys only from GE. The play refuses while pxe has no ~/.ssh/authorized_keys, and removes the drop-in again if sshd -t rejects it.
allow_disruptive false Re-runs while imaging is active (SMB sessions on 445, or DHCP leases granted in the last pxe_busy_window_min = 30 min) do not bounce the NICs or restart dnsmasq/Apache/Samba; a needed network change stops the play. -e allow_disruptive=true overrides.

Recommended for a production box: corp_admin_cidrs set to the admin desks' subnet, then (after ssh-copy-id) corp_ssh_password_auth: false.

Re-running the playbook over SSH (from the GE side)

The network step refuses before changing anything when the NIC that pxe_nic_mac names holds the default route, a DHCP lease, or the route to your SSH client (swapped MACs or cables). When it does apply, it arms a 120 s auto-revert timer (pxe-netplan-revert-*), checks the names, the imaging address and that the corp NIC (or, without one pinned, every other port that had an address) gets its IPv4 back within 30 s, and restores the previous netplan config AND NIC names on any failure. Backups: /var/backups/netplan-<timestamp>/.

Identity and credentials

Value Default Where it lives
Linux user pxe / pxe autoinstall/user-data (identity.username, password hash)
Samba upload user pxe-upload / pxe playbook/pxe_server_setup.yml (Samba user creation)
Samba blancco user blancco / blancco playbook/pxe_server_setup.yml
Sudo password pxe autoinstall password hash; also referenced in scripts that ssh + sudo

WiFi and BMC (Blancco licensing)

The Blancco custom config.img carries WiFi credentials and BMC cloud credentials in plaintext. These are GE-Aerospace specific.

Value Default Where it lives
WiFi SSID INTERNETACCESS boot-tools/blancco/config.img (inside the cpio archive: preferences.xml)
WiFi password Acc3ssGr@nted22 Same
BMC endpoint classic.eu-west-1.blancco.cloud:443 Same; encrypted credentials inside config.img

To rebuild config.img for a different site, see BLANCCO.md ("Rebuilding config.img").

Image types

The repo defines seven image types (see playbook/pxe_server_setup.yml, image_types). Most sites likely only need a subset. The current set:

  • gea-standard
  • gea-engineer
  • gea-shopfloor
  • ge-standard
  • ge-engineer
  • ge-shopfloor-lockdown
  • ge-shopfloor-mce

Trim or extend this list and the matching shopfloor_types filter as needed.

Engineering Layer (gea-engineer)

Handled by the repo; nothing to work out per site except the payload.

  • playbook/engineering/Install-EngineeringLayer.ps1 is deployed by the playbook to gea-engineer/Deploy/Applications/extra_engineering/.
  • FlatUnattendW10-engineer.xml runs it twice:
    • specialize Order 37 (-StageOnly) copies the payload and the script to C:\PreInstall\engineering while C:\Deploy exists;
    • first logon Order 11 installs from that copy. The bulk-enrollment DeleteDeployDir task removes C:\Deploy on the next boot, so a PC that reboots before the employee signs in would otherwise install nothing and show no icon.
  • After the install it makes sure the "Configure my Engineering Layer" icon is on the Public desktop (until the PC is configured), and registers Active Setup so a later user gets the icon if someone else already used it. The employee clicks it once, on the GE network: it maps V: and runs install_local_new.cmd from GE's share, which removes the icon.
  • Log on the PC: C:\Logs\Engineering\install-engineering-layer.log.

The site supplies the payload beside the script, in gea-engineer/Deploy/Applications/extra_engineering/: engineering_layer/Engineering_Install_v3.1.exe and prereqs/ (.NET 3.5 sxs cabs, VC++ redists), about 650 MB.

PC types (shopfloor sub-types)

playbook/shopfloor-setup/ contains per-PC-type install logic. The current types reflect WJ shopfloor needs:

  • CMM (PC-DMIS)
  • WaxAndTrace
  • Keyence
  • Genspect
  • Display (Lobby and Dashboard variants)
  • Standard (Timeclock and Machine variants)
  • Lab

A site without CMMs can drop the CMM type and its installers entirely.

GCCH bulk-enrollment PPKGs

PPKG filenames are baked into playbook/startnet.cmd and startnet-template.cmd. They are tied to the GE Aerospace tenant. Other sites/tenants will have their own PPKGs with different filenames.

PPKG enrollment menu choice Default filename
1 NoOffice GCCH_Prod_SFLD_NoOffice_US_Exp_20260430_v4.10.ppkg
2 StdOffice x86 GCCH_Prod_SFLD_StdOffice-x86_US_Exp_20260430_v4.10.ppkg
3 StdOffice x64 GCCH_Prod_SFLD_StdOffice-x64_US_Exp_20260430_v4.10.ppkg
4 ProPlusOffice x86 GCCH_Prod_SFLD_ProPlusOffice-x86_US_Exp_20260430_v4.10.ppkg
5 ProPlusOffice x64 GCCH_Prod_SFLD_ProPlusOffice-x64_US_Exp_20260430_v4.10.ppkg

When you bump PPKG versions, update the filenames in startnet.cmd, drop the new PPKGs in enrollment/ on the workstation, and rebuild the USB.

Site-specific shopfloor config

playbook/shopfloor-setup/site-config.json has site-specific values like default machine number ranges, share paths for software, and more. Inspect that file for site name strings, share UNC paths, and machine number ranges.

Branding

The Flask webapp uses GE Aerospace branding:

  • Logo: webapp/static/ge-aerospace-logo.svg
  • Favicon: webapp/static/favicon.ico
  • Page titles and templates reference "GE Aerospace" in webapp/templates/*.html
  • iPXE menu title: "GE Aerospace PXE Boot Menu" in playbook/pxe_server_setup.yml

Drivers (Dell)

Driver content is hardware-specific, not strictly site-specific, but the set of driver families to bundle changes by what hardware the site deploys:

  • Dell_11/Pro and Dell_11/ProMax: Dell Pro Micro PCs (most common shopfloor)
  • Dell_11/Optiplex: traditional desktops
  • Dell_11/Precision: workstations
  • Dell_11/Latitude: laptops (large, often excluded if site has none)

The scripts/download-drivers.py and scripts/Download-Drivers.ps1 scripts populate drivers-staging/ from upstream sources. Edit these to target the relevant hardware lists.

BIOS update manifest

playbook/shopfloor-setup/BIOS/models.txt maps Dell model strings to the BIOS .exe to flash. Update this whenever new hardware arrives or Dell publishes a new firmware release for a model already in the fleet.

Intune DSC categories

The post-enrollment Intune DSC delivery (per intune-dsc.md memory) pulls per-category YAML from Azure Blob:

  • main
  • cmm
  • displaypcs
  • waxtrace

Different sites/tenants will need their own categories and their own Azure Blob storage account.

Image-upload paths on Windows

scripts/Upload-Image.ps1 defaults to:

  • \\172.16.9.1\image-upload as the destination
  • C:\ProgramData\GEAerospace\MediaCreator\Cache\ as the source

Update both for a different site (-Server, -CachePath; -WebPort if the webapp is not on 9009). The script checks every file on the server through the webapp before the Image Import page will accept the upload.

A site config file should drive substitution at build time. Proposed schema:

# config/sites/<sitename>.yaml
site:
  name: westjeff
  pxe_server_ip: 172.16.9.1
  pxe_subnet: 172.16.9.0/24
  dhcp_range_start: 172.16.9.10
  dhcp_range_end: 172.16.9.100
  hostname: pxeserver

credentials:
  pxe_user: pxe
  pxe_password: pxe
  pxe_upload_password: pxe
  blancco_password: blancco

blancco:
  wifi_ssid: INTERNETACCESS
  wifi_password: Acc3ssGr@nted22
  bmc_endpoint: classic.eu-west-1.blancco.cloud:443

ppkg_versions:
  current: v4.10
  expiry: 20260430

intune_dsc_categories: [main, cmm, displaypcs, waxtrace]

image_types: [gea-standard, gea-engineer, gea-shopfloor]
shopfloor_types: [gea-shopfloor]

scripts/build-usb.sh and the playbook would consume this and substitute into the templated config files. Implementing this is a separate piece of work; until then, edit source files directly.

Secrets handling

Some values above are sensitive (WiFi passwords, BMC credentials, Samba passwords). Today these sit in plain text in the repo. For a multi-site deployment, treat them as per-site secrets:

  • Keep config/sites/<sitename>.secrets.yaml out of git (gitignore).
  • Distribute via secure channel (1Password, Vault, etc.) when standing up a new site.
  • The eMxInfo.txt precedent (see feedback_emxinfo_secret.md) is a good template: never commit, document where the canonical copy lives.