Commit Graph

30 Commits

Author SHA1 Message Date
cproudlock
5f7954ac88 Share folder for other sites: no WJ Blancco release or Wi-Fi password
stage-usb-share.sh --no-blancco-release leaves out boot-tools/blancco's
vendor release (config.img carries this site's licensing Wi-Fi password and
location) and the unused blancco-uki.efi. The KB's Part 4 now has the site
load its own Blancco 7.20 ISO on Updates > Blancco releases. The Wi-Fi
password is gone from SITE-CUSTOMIZATION.md.
2026-10-17 11:51:46 -04:00
cproudlock
b9a4d0230d Engineer image: upload the Engineering Layer from the web app
New Engineering Layer tab on the engineer image page. A site uploads
Engineering_Install_v<ver>.exe (keeps prereqs) or a .zip of the whole
extra_engineering payload. The upload is streamed to disk, checked (MZ
header, no unsafe zip paths) and swapped in; the replaced payload is
archived (two kept) with a Restore button. Install-EngineeringLayer.ps1
stays repo-owned and now installs the newest Engineering_Install_v*.exe.

KB: Part 4 becomes Site Content (enrollment package, Engineering Layer,
Blancco site settings, Shop Floor apps) plus a new Adding Shop Floor
Apps section for preinstall.json/postppkg.json. WJ examples removed.
2026-10-16 20:48:25 -04:00
cproudlock
04db6d6605 Generic build for other GE sites: no WJ data, one shopfloor type, Windows USB builder
Everything West Jefferson specific moved out to a separate repo
(pxe-site-wj): NTLARS/UDC/heat-treat backups keyed by WJ machine numbers,
CMM and Wax/Trace bay lists, WJ OpenText profiles, site-config.json, the
WJ preinstall list, and the WJ shopfloor PC types (Collections,
NoCollections, Common, Keyence, CMM, Genspect, HeatTreat, Wax/Trace,
Display, PartMarker) with GE-Enforce, machine numbers and the desktop
layout. The part-marker bench tools moved to their own repo. Dead
scripts removed: build-proxmox-iso, deploy-bios, sync_hardware_models,
mirror-from-gold, pull-bios, startnet-template.cmd.

Shopfloor is one generic type, gea-shopfloor-standard, with today's
timing:
- PPKG at first logon (unattend Order 10, unchanged).
- Install-AppList.ps1 runs C:\PreInstall\preinstall.json during setup.
- A new SYSTEM task, GE Shopfloor Post-PPKG, runs postppkg.json once
  after the post-PPKG reboot. It also clears AutoLogonCount after the
  ShopFloor handover (the Users-level monitor can be denied that HKLM
  write) and removes the Intune Sync task when done.
- Both lists ship empty. A site adds entries and installers.
- WinPE choice 3 goes straight to the PPKG menu. Staging, fetch and heal
  carry only the generic payload. The webapp's shopfloor sub-menu editor
  is gone, and the imaging tiles name the new stages.
- The playbook deploys the empty lists only if missing. It now deploys
  the PowerShell 7 MSI the shopfloor unattend installs, which it never
  did before.

Blancco site settings move into the webapp (Updates > Blancco releases):
business location, licensing Wi-Fi SSID and password. They are rendered
into the served preferences.xml and applied to every staged release, and
are seeded from site-vars (site_name, blancco_wifi_ssid,
blancco_wifi_password) only while none exist. The repo XML carries no
site values; the plaintext Wi-Fi password is gone from it.

New site_name and site_timezone settings: report and evidence-pack site
name, server clock and webapp time zone. No more West Jefferson defaults
in webapp/config.py or the dashboard title. Sync-BPRT.ps1 gets the
server address filled in at download instead of WJ's GE address.

USB on Windows:
- scripts/Build-PxeUsb.ps1 runs the same checks as build-usb.sh. It
  writes one FAT32 CIDATA partition (ISO files plus patched grub.cfg plus
  payload) with diskpart and reads every file back by SHA-256.
- Tested with Windows PowerShell 5.1 in the win11 VM, and the layout
  installs and first-boots in the pxetest VM.
- scripts/stage-usb-share.sh stages the build folder on a share.
- site-content/ is optional for both builders.

KB docs/kb/How_To_Deploy_PXE_Server_New_Site.html: new-site deployment,
Windows only, with a site-vars.yml guide.

Checks: webapp tests pass (5 suites); the unattend lint and playbook
syntax checks pass; every .ps1 parses; there are no WJ hosts, IPs, names
or PC types in playbook/, webapp/, scripts/ or autoinstall/.
2026-10-16 20:37:22 -04:00
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
cproudlock
821a927365 Engineering Layer: survive the C:\Deploy cleanup; the employee gets the icon
Engineer builds left the employee without "Configure my Engineering
Layer". First logon ran C:\Deploy\Applications\extra_engineering\
Install-EngineeringLayer.ps1, and the script itself lived in C:\Deploy:
std/eng have no autologon, so a PC that rebooted before the employee
signed in had lost C:\Deploy to the orchestrator's DeleteDeployDir task,
and nothing installed, silently.

- engineer unattend, specialize Order 37 (free slot): run the script with
  -StageOnly, copying the payload and the script to C:\PreInstall\engineering
  while C:\Deploy exists (Path 119 chars)
- first logon Order 11: run it from C:\PreInstall\engineering, falling back
  to C:\Deploy if staging did not happen
- the script (now in the repo, playbook/engineering/; it lived only on the
  server): after the install, restore the Public desktop icon if it is
  missing and the PC is not configured yet (no C:\TMP\DUU_Complete.log), and
  register Active Setup so each later user gets the icon on their own
  desktop when the Public one is gone. v3.1 puts the icon on the Public
  desktop; clicking it (configure, from GE's V: share) deletes it for all
- playbook deploys the script to gea-engineer extra_engineering; the 650 MB
  payload stays site content (documented in SITE-CUSTOMIZATION.md)

Tested in the win11 VM with the exact unattend strings and a stand-in
installer that skips the icon: specialize staged payload and script;
C:\Deploy deleted; first logon installed from the staged copy, restored the
icon and registered Active Setup. The per-user step as a new user: icon on
their desktop when the PC is unconfigured and the Public icon is gone; none
when configured or when the Public icon is there. Lint clean. Not deployed:
unattends go out only through scripts/deploy-unattend.sh.
2026-10-16 12:37:50 -04:00
cproudlock
9d56a5369e Clonezilla back up / restore by machine number
Boot menu: "Clonezilla: back up this PC" and "restore this PC" ask for the
machine number and boot Clonezilla (64-bit with Debian shim, or 32-bit
i686 by cpuid) with ocs_repository=smb3://clonezilla:<pw>@server/clonezilla/,
ocs_prerun fetching clonezilla-scripts/pxe-cz.sh and ocs_live_run running
it. The script validates the machine number, saves
<machine>-<YYYY-MM-DD-HHMM> (server time), writes pxe-meta.txt, runs
ocs-chkimg, and restores only that machine's images with -k1 -r after a
typed YES. The old entry is "Clonezilla: manual".

Playbook: clonezilla Samba user for the [clonezilla] share only, password
from clonezilla_smb_password (validated, filled into the menu
placeholder), scripts installed to the web root.

Webapp: Clonezilla backups page lists image folders and zips grouped by
machine number with serial/model/version/check, serial mismatch flag,
delete, move to another machine, streamed tar download, disk space bar
and an explicit keep-last-N retention with preview. Tests and a VM test
plan added.
2026-10-15 15:26:30 -04:00
cproudlock
dde1aacccb Clonezilla for XP-era PCs: 32-bit build picked by CPU
Current Clonezilla is amd64-only. The menu now checks the CPU with iPXE's
cpuid --ext 29 (long mode): 64-bit CPUs get the current release (through
Debian's shim under Secure Boot), 32-bit-only CPUs (Pentium 4 without
EM64T, Pentium M, early Atom) get Clonezilla 3.2.0-5 i686, the last
32-bit release, from clonezilla-i686/. Without a cpuid command the 32-bit
build is used, which also runs on 64-bit CPUs; UEFI always gets the
64-bit build. A 'Clonezilla Live 32-bit' item forces it.
prepare-boot-tools.sh downloads it, the playbook deploys it.
2026-10-15 14:50:40 -04:00
cproudlock
54015566a7 Menu: Clonezilla boots with Secure Boot on (Debian shim); Memtest86+ removed
Clonezilla Live runs a Debian-signed kernel whose modules (usb-storage,
uas, exfat) are signed by the same build key, so the menu now starts it
through Debian's Microsoft-signed shim with the iPXE shim command, the
same way Blancco uses Ubuntu's. prepare-boot-tools.sh extracts that shim
from the Clonezilla release (EFI/boot/bootx64.efi -> clonezilla/
shimx64.efi) and warns when the shim is not Microsoft-signed or the
kernel not Debian-signed.

Memtest86+ is unsigned and needed Secure Boot off; memory tests now use
the Dell built-in diagnostics (F12 at power-on, Diagnostics), noted in
the menu. The playbook removes the old memtest web dir. Nothing on the
menu needs Secure Boot off any more; the 'disable Secure Boot' screen
remains only as the fallback when a signed path fails.
2026-10-15 14:47:25 -04:00
cproudlock
da6e62c084 Merge branch 'fix-build' into pxe-webapp-redesign 2026-10-15 14:38:48 -04:00
cproudlock
0b1625ed87 Build/playbook re-run safety: corp access, netplan rollback, first-boot retry
H1  corp_admin_cidrs (UFW 22/9009 on the corp NIC from these only; refuses
    when it would lock out the current SSH client) and corp_ssh_password_auth
    (sshd drop-in, passwords only from the imaging subnet). Defaults keep the
    current behaviour and print a loud warning.
H2  pxe-netplan-apply.sh: pre-flight refusal (pxe-MAC NIC holds the default
    route, a DHCP lease or the route to an SSH client), run from the USB
    before anything changes; post-apply checks (names, imaging IPv4, corp
    IPv4 back within 30 s, SSH route) and a 120 s systemd-run auto-revert.
H3  restore() puts the previous NIC names back (two-phase rename via
    temporary names, so swaps work) before generate + apply.
H4  a failed rename is fatal (restore + exit 1); the play drops the old
    any-interface UFW rules only after verifying both NICs and their
    per-interface SSH rules.
H5  first-boot checks the playbook exit code, retries at next boot (max 5),
    writes /var/lib/pxe-firstboot.failed and /etc/issue + /etc/motd banners.
M1  build-usb.sh verifies iPXE files against the playbook pins, every wheel
    against its own package's hashes, drops the unpinned pip fallback,
    requires --site-vars or --no-site-vars.
M2  webapp restarts when the code, wheels or unit changed.
M3  ignore-carrier on the imaging NIC; ip_nonlocal_bind kept for the
    apache/networkd boot race.
M4  menu suffix must match blancco_kver; missing kernel with no earlier
    build is fatal; a live menu with the staged Blancco test entry is not
    overwritten.
M5  build-proxmox-iso.sh is unsupported (hard exit); use build-usb --image.
M6  deploy-unattend.sh: password via SSHPASS env and sudo stdin only,
    %q-quoted remote args, sha256 check of the live file; lint-unattend.py
    reads PXE_SERVER/PXE_USER/PXE_PASS and uses sshpass -e.
M7  the playbook lints the unattend files before the force: yes copies
    (lint-unattend.py ships on CIDATA as scripts/); pre-commit ACMR.
M8  Blancco initramfs build: no ignore_errors, pipefail, trap cleanup.
Also: Send-ImagingStage.ps1 deploy, carrier hook install tasks
(pxe-nic-carrier, networkd-dispatcher), L1 imaging-active hold
(allow_disruptive) and no iPXE downgrade below an Updates-page install,
L2 iso-volid, L3 kver pin read from the playbook, L4 cp -rL, L5 refuse
>= 4 GiB WinPE files, L6 PIP_DIR wipe guard, L7 per-package wheel hashes,
no online fallback, L8 check-updates.py (apt_pkg/cached compare, all
components, exit 2 on no debs / bad answers).
2026-10-15 14:32:23 -04:00
cproudlock
21ff95af78 NIC guard: ignore client EAPOL, re-check on carrier change, SMB-only lease cleanup
- pxe-nic-check: count 802.1X only from an authenticator (EAPOL type 0
  with EAP Request/Success/Failure). iPXE sends EAPOL-Start on every NIC
  open and Windows sends Start/Response, so a PXE-booting bay made the
  verdict 'ge' and the watch stopped dnsmasq for the floor.
- pxe-nic-check: join the STP/PAE/LLDP/CDP/PVST+ multicast groups on
  every capture socket, so CDP-only ports are heard on a plain NIC.
- pxe-nic-check --watch: if dnsmasq was (re)started inside the window,
  re-check (~6 s) before stopping it; skip when already stopped.
  New --state-cmd and --print-imaging.
- pxe-nic-carrier (+ pxe-nic-carrier.service): networkd-dispatcher hook
  for the imaging NIC only. Carrier loss stops dnsmasq; carrier gain
  restarts it through its guard if it was running or the hook stopped it.
- pxe-dhcp-hook.sh: clear only SMB (tcp 445/139) conntrack entries and
  sockets, not all traffic for the client, which raced iPXE's first
  HTTP fetch.
- GetPxeScript.aspx: every boot line has a failure branch (iPXE 2.0
  accepts an unsigned EFI kernel and fails at boot under Secure Boot,
  which aborted the script).
- Tests: client EAPOL, packet_mreq packing, watch race, shell helpers on
  stub PATH, lab scenarios eapol-client, br-eapol-client, race, carrier.
2026-10-15 14:26:39 -04:00
cproudlock
ead3314f58 Two-NIC standard: pin imaging and GE NICs by MAC everywhere
Every server has an imaging NIC (static, the only one that serves
DHCP/TFTP/HTTP boot/SMB) and a GE corporate NIC (DHCP client, SSH and the
webapp on 9009). The playbook guessed the PXE NIC and the installer put
172.16.9.1/24 on every en* NIC, so a two-NIC box could answer on the GE
network.

- New per-site vars pxe_nic_mac / corp_nic_mac (required, from
  playbook/site-vars.yml or -e; template site-vars.example.yml), stable
  names pxe_nic_name / corp_nic_name (default pxe0 / corp0, overridable),
  pxe_server_ip, pxe_subnet, DHCP range. pxe_imaging_mac / pxe_ge_mac are
  derived from them. The play stops before touching anything if a MAC is
  unset, malformed, duplicated or not on the box (lists the box's NICs).
- Heuristic NIC detection and the single-NIC netplan task replaced by a
  two-NIC netplan template (match macaddress + set-name), installed by
  pxe-netplan-apply.sh: validate in a scratch root, back up, generate,
  rename, apply, restore the previous config on any failure. cloud-init
  network config disabled so it cannot rewrite it.
- dnsmasq: /etc/dnsmasq.d/pxe-nic-only.conf from the vars (interface=,
  except-interface=, no-dhcp-interface=); dnsmasq.conf IP/range from vars.
- UFW per interface: imaging NIC 22, 67/udp, 69/udp, 80, 4433, 445, 139,
  137/udp, 138/udp, 9009; GE NIC 22 and 9009 only. Old any-interface rules
  and the stale 9011 rule removed after the new ones are in.
- sysctl drop-in: IPv4/IPv6 forwarding off; ip_nonlocal_bind so Apache
  can bind the imaging IP before the address is up.
- Samba [global]: interfaces = 127.0.0.0/8 <imaging NIC>, bind interfaces
  only. Apache 80 and 4433 on the imaging IP only; 9009 stays on all (TODO:
  the webapp has no auth).
- autoinstall: DHCP only on en*, no static 172.16.9.1; apt geoip off and
  offline-install fallback now that the corp NIC may have a lease.
- Blancco UEFI/Secure Boot: blancco_kver (7.0.0-34-generic) drives the
  kernel, initramfs and modules builds instead of the running kernel;
  outputs vmlinuz-ubuntu-70, kexec-initrd-70.img, kmod-<kver>.tar.gz as the
  menu and blancco-init.sh expect; creates: guards keyed on the full kver.
  Ubuntu's shimx64.efi.signed published as blancco/shimx64.efi.
  download-packages.sh adds shim-signed, linux-generic-hwe-24.04 and the
  pinned linux-image/modules/modules-extra for BLANCCO_KVER.

Rendered with the live MACs and names, netplan generate output is
identical to the live config; pxe-nic-only.conf and the Samba binding
match live directive for directive.
2026-10-15 08:53:41 -04:00
cproudlock
bc422649e9 NIC guard: dnsmasq never serves DHCP/PXE on the GE network
pxe-nic-check classifies each NIC from what it hears: LLDP/CDP/802.1X
from managed switches, foreign DHCP offers and their domain (one
DHCPDISCOVER on the imaging NIC; the GE NIC's networkd lease is read
instead), STP and ARP hosts for information. A kernel BPF filter keeps
bulk SMB traffic out of the capture loop; bridge ports are listened on
too, since LLDP and EAPOL stop at the port.

- dnsmasq drop-in runs --guard as ExecStartPre: exit 1 on a ge or
  foreign-dhcp imaging verdict, fail open (loudly) on bugs or no link
- pxe-nic-check.timer runs --watch every 5 min: stops dnsmasq on a bad
  imaging verdict, never starts it
- /run/pxe-nic-status.json (0644, atomic) for the webapp
- playbook installs it all and writes /etc/pxe-server/nic.conf from
  pxe_imaging_mac, pxe_ge_mac, pxe_corp_domain_suffixes,
  pxe_nic_allowed_switches
- unit tests plus a no-root network-namespace lab (veth pairs, a raw
  socket DHCP/LLDP peer and real dnsmasq); docs/pxe-nic-guard.md has the
  operator notes and the two floor tests
2026-10-14 17:18:53 -04:00
cproudlock
26d14b0684 Key the MTConnect runbook on machine numbers, not hostnames
The first draft identified every bay by PC name, taken from a 12 June map and
the machine list's Shop Floor PC Name column. Both are hand-built snapshots: a
PC that has since been replaced or re-imaged answers to a different name while
the machine it sits at has not moved. Following those names would eventually
mean renaming the wrong bay.

Machine number leads every table now, hostnames are marked as "where the fault
was last seen", and the reader is pointed at what the bay itself knows - the
eDNC registry first, then the enrollment file, then UDC - with ShopDB as the
live PC-to-machine record since the collector maintains it.

Also notes that either CSV should be generated from ShopDB rather than
hand-maintained, for the same reason.
2026-08-11 14:46:52 -04:00
cproudlock
cc20501e2e Runbook for the MTConnect follow-up work
The rename fix only changes what future renumbers do. Nine bays are already in
the state it prevents, the 3.25.3 adapter is staged but not on the SFLD share,
and two questions - whether DMG Mori needs a bundle at all, and whether the v2
manifest ever received the MTConnect entries - are unanswered.

All of it needs a machine the dev box cannot reach, so it is written as steps
to follow rather than something to run here: exact hashes to check before
copying, per-bay numbers from the hostname map, verification after each change,
and rollback for the share copy.

Notes the two things not proven on real hardware: that stopping the Makino
Adapter Manager keeps it from rewriting config on shutdown, and that the four
Fanuc bays with no agent device name are a different fault that a rename would
not fix.
2026-08-11 14:44:08 -04:00
cproudlock
6d5fee786c Proposal: fold in what a day of fixing the current pipeline taught
New section 9a - five design rules, each from a bug fixed on 2026-08-06 and each
cheap to honour in a new design but expensive to retrofit:

  Absent and empty are different. Four scripts tested config as
  "not null AND Count > 0", so an explicitly empty list fell through to a
  hardcoded default - a kiosk configured with "desktopApps": [] was given the
  full machine-tool set. Configuring "none" produced "everything".

  Deny by default. preinstall.json had 16 of 21 entries at PCTypes ['*'] and the
  SMB manifest 18 of 25 unfiltered. Both filters worked perfectly; they just were
  not applied.

  Gate at the point of action. Call-site gates on PC type were bypassed by the
  finalization phase, silently.

  The image carries configuration. WJ Shopfloor.lnk is inside the WIM, so no
  pipeline change removes it.

  The ppkg is a hard boundary. Chrome, RealVNC, Tanium, CyberArk and the rest come
  from the package - thinning the image does not thin them.

Also corrects a premise in section 4.2: a first-boot selection UI cannot assume
the API is reachable, because ShopDB needs the AESFMA wifi SSID and the imaging
LAN has no route to it. Points at Install-ShopdbKiosk-WhenOnline.ps1 as the
reusable shape - arm a task at imaging, do nothing until the API answers, act
once, verify, self-remove.

Plus two risks, the diagnostics collector in the reference map, and a gotchas
section: HardwareDriver.json defeats ConvertFrom-Json on every PowerShell
version, Compress-Archive caps at 2 GB against an 8 GB package, and PowerShell
Core normalises backslashes on Linux so bay scripts can be tested here.
2026-08-06 17:36:17 -04:00
cproudlock
68f2ce29e4 Proposal: full reference map, and correct the LTSC autostart assumption
REFERENCES (section 13, was six lines, now a map)
Where every referenced thing lives, so the document is usable by someone without
today's context: this repo, the PXE server share, shopdb-flask, the on-bay log
locations, and the evidence bundles each claim came from. Plus re-derivation
recipes - pulling startnet out of boot.wim, extracting customizations.xml from an
8 GB .ppkg without unpacking it, reading the provisioning evtx, and the two
validation commands - and the 2026-08-06 commit list.

LTSC AUTOSTART - CORRECTED
Section 7.1 previously argued PESetup might already auto-start: AutoStartCountDown
is 31, disableauto.json is absent, and the operator on 579C144 clicked Next at 11
seconds, so nobody had waited long enough to find out.

Per the team's operational experience that is wrong for our media. LTSC builds
cannot autostart - the SELECT OS confirmation is mandatory regardless of the
countdown, and every image we produce is Windows 11 LTSC 24H2.

So the countdown is not a route to zero-touch. That promotes re-implementing the
imaging step from a contingency to the actual decision, and the risk table and
open questions now frame it as "re-implement, or accept one click per build" -
zero-touch being the only thing that requires it. The hands-off test is kept, but
only to confirm and record the behaviour first-hand.
2026-08-06 15:05:22 -04:00
cproudlock
f442ff2f65 Propose zero-touch, ShopDB-driven imaging
Design proposal, nothing built. Two ideas in one architecture: the image becomes
thin (partition, apply, join Entra, install the enforce client - everything else
is data pulled from the API and enforced by GE-Enforce), and imaging becomes a
job rather than a menu (open a machine in ShopDB, click Create this PC, pick the
zeroed box waiting in WinPE, and it images start to finish reporting progress
onto that machine's record).

Every claim about current behaviour is sourced from 2026-08-06 evidence -
decompiled binaries, live logs off bay 579C144, and the live share - and where
something is inferred it says so.

Covers: why the current early-decision chain fails silently (five worked examples
from one day); what already exists to build on; per-machine configuration records
replacing 147 .reg files and two CSVs, referencing payloads rather than embedding
them; the dual-NIC design with the firewall work that gates it; whether PESetup
needs replacing at all; the BPRT token shelf-life constraint on pre-imaged stock;
an ownership table; six delivery phases each useful alone; risks; and seven open
questions.

Two findings in here are worth acting on before any of it:

  disableauto.json is ABSENT from the media and AutoStartCountDown is 31, while
  the operator on 579C144 clicked Next at 11 seconds. PESetup may already
  auto-start and nobody has waited long enough to find out. One bay, hands off
  the keyboard, count to 40.

  dnsmasq is already safely bound (listen-address + bind-dynamic) so DHCP will
  not leak onto a GE-side NIC - but Samba binds to all interfaces and every ufw
  rule is ALLOW IN Anywhere, including SSH on a pxe/pxe account. That work gates
  dual-homing.
2026-08-06 15:02:08 -04:00
cproudlock
68df59e117 Record who owns what, and report repo-vs-share drift
OWNERSHIP.md
Every expensive bug in this pipeline has had one shape: two systems setting the
same thing, last writer winning silently. Four happened on 2026-08-06 alone -
computer name (package vs run-enrollment), drive letters (PESetup vs a volume
finder), enrollment (package vs an at-logon -ManualFallback task that syspreped
finished machines), kiosk URLs (GE-Enforce vs site-config). Each is written down
with the evidence so the next person deletes a writer instead of adding one.

share-drift.py
The share is production and the repo is meant to describe it, but drift runs both
ways: live hand-edits nobody committed, and repo fixes never deployed. The
unattend outage lived only on the share while the repo copy was fine, and nothing
compared them.

Each mapped pair is classified. git-owned means the repo wins and the pair must
match - those fail the run. unreconciled means the two have genuinely diverged
and nobody has decided; reported, not failed. The unattends are unreconciled on
purpose: live is ~17 KB against ~12 KB in the repo, so a blind push would regress
production. Reads over SSH via base64 so BOM and CRLF survive the hop.

First run: 8 git-owned pairs all match, 4 known-unreconciled.

Run-ShopfloorSetup.ps1
Corrects a comment that was actively misleading. It claimed shopfloor PCs are
"vanilla by design" and that the orchestrator runs -ManualFallback to skip BPRT
injection and the package entirely. Shopfloor bays DO enrol - the SFLD package
joins Entra with its BPRT token and a human assigns the device category in
Intune. -ManualFallback runs sysprep /oobe /reboot, which is why wiring it to an
at-logon task destroyed the deployment chain.

The absent Entra wait is still correct, for a different reason: at that point the
bay is on the isolated PXE LAN with no route to Entra (579C144 held 172.16.9.81
and 172.24.19.142, neither in the production ranges). sync_intune retries until
the tech re-cables. "Entra ID Joined: false" right after imaging is normal.
2026-08-06 14:28:16 -04:00
cproudlock
d04865da0f docs: record the PESetup version actually in production; match LogonCount to the live unattend
PESETUP-INTERNALS.md was written from a decompiled 4.0.0.17. The media in
production reports 4.0.0.20 in its own log. Rather than restate the document as
4.0.0.20, which would claim a re-derivation that has not happened, it now names
both: line-level claims are 4.0.0.17, and the behaviour re-observed on bay
579C144 on 2026-08-06 is listed so a reader knows which parts are confirmed
current - media drive Z:, W: created by PrepareDisk and used for every copy
destination, the fallback Deploy\FlatUnattendW10.xml being the unattend that
loads, and driver selection by model.

FlatUnattendW10-shopfloor.xml carried LogonCount 7 while the live shopfloor
unattend has 12, and Run-ShopfloorSetup.ps1's comment about topping up the
autologon budget already said 12. The live value is the real one, so the repo
follows it.

Both files still lint clean under scripts/lint-unattend.py.
2026-08-06 12:25:30 -04:00
cproudlock
936902dc4d Document how PESetup.exe actually works
Written from the decompiled assembly rather than from observation. Three
long-standing beliefs about this tool are wrong, and each has cost real
debugging time:

W: is not a guess. PESetup hardcodes it in nine places - every copy
destination, the DISM offline sessions, bcdboot, reagentc - and creates it
during its own disk preparation. startnet.cmd's volume finder, diskpart
reassignment and W: wait loop are machinery built around a problem that does
not exist.

The copy steps do not filter. CopyPackages copies the whole of
Deploy\Applications recursively to W:\Deploy\Applications - no manifest, no
unattend parsing, no extension or size rules. Anything dropped there reaches the
target, which is the basis for staging our own payload without the enrollment
share. It is also fail-fast: one unreadable file fails the whole step.

A missing driver match is a WARNING, not an error. GetDriverByModel does a
substring test of comma-separated tokens with first-match-wins, the family
filter knows only Latitude, OptiPlex and Precision, and a single 'virtual
platform' entry in the catalogue fails every physical machine. A miss lets
imaging finish with no drivers, so no NIC, so DNS failures at first boot -
symptoms far from the cause, and easily misattributed to the drive letter.

Includes the bundle-extraction recipe so the next person can re-derive all of
this instead of trusting this document.
2026-08-06 08:04:48 -04:00
cproudlock
57fae57d3b CMM/DODA: fix DODA paths + CyberArk EPM policy doc
- 09-Setup-CMM.ps1: Step 2.5 ACL list targeted C:\Program Files\DODA (a path
  that never exists), so the BUILTIN\Users write grant on DODA was silently
  skipped. Corrected to C:\Apps\DODA, where Install-DODA.ps1 actually extracts.
- Install-DODA.ps1: create C:\Apps\DODA\PreProcess after extract. The DODA
  zip unpacks flat without it; MergeFiles.exe expects it and crashed with
  DirectoryNotFoundException (MergeFiles.GetDoDAFolder) when absent.
- docs/cyberark-cmm-doda-policy.md: EPM admin reference for elevating the CMM
  report toolchain. CyberArk EPM elevation is per-process and not inherited, so
  the external tools PC-DMIS spawns (MergeFiles/PCDToIGES/RotateProbeVector/
  DovetailAnalysis) run un-elevated and fail. Doc gives the Application Group
  (by SHA-256), the Elevate policy, scope, verify steps, and the
  CREATE_PDF_FROM_RTF.BAS rework that drops Word/Reader from the elevation set.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 12:38:34 -04:00
cproudlock
02499cf74b docs: post-deploy checklist + COM 2/4 PCIe serial-port flowchart update
New post-deploy-checklist.md (+ live.html + static.html siblings):
- Four-section terse coach checklist run after a freshly imaged PC
  reaches the login screen. Pairs with the existing post-deploy debug
  flowchart for failure paths.
- Sections: Common Shop Floor opens + connects, controller comms
  (ping 192.168.1.1 / NTLARS General+FMS), UDC COM port matches
  physical socket + no machine-comm error dialog + Tools > Retry
  Connection succeeds, printers ready (Genspect-specific note).

post-deploy-debug-flowchart.md:
- COM port mapping: PCIe add-in card now listed as 'COM 2 or COM 4'
  (Windows enumeration varies by hardware) instead of fixed COM 2.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 19:39:15 -04:00
cproudlock
ce604adcda Renumber PXE LAN from 10.9.100.0/24 to 172.16.9.0/24
Single-site bay-stuck issue at WJ: GE Intune Report IP script filters
Get-NetIPAddress on StartsWith("10.") and posts everything matching
to the GE Tines webhook. Bays at WJ get the PXE LAN 10.9.100.x IP
captured and reported -> GE backend tags bays as on a non-corp 10.x
subnet -> dynamic group eligibility for SFLD policy never matches.
Other GE sites work because their PXE LANs aren't on 10.x at all.

Renumber PXE LAN to RFC1918 172.16.9.0/24 so the GE filter naturally
skips wired PXE addresses without any disable-NIC dance.

Server-side already in flight (netplan dual-bound, dnsmasq scope +
boot URL repointed, blancco preferences + grub.cfg + iPXE GetPxeScript
all sed'd to 172.16.9.1). This commit is the playbook / scripts /
docs side: 109 hits across 35 files sed'd in one shot.

After this lands + boot.wim is rebuilt + bays renumber off DHCP,
the 10.9.100.1 binding will be dropped from netplan as the final
cleanup step.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 16:30:32 -04:00
cproudlock
c8a0f98be1 docs: add post-deploy debug flowchart for UDC + DNC issues
Step-by-step runbook for techs at the imaged PC. Two symptoms covered:
UDC not collecting data (admin-unlock + COM port walkthrough) and DNC
not pushing to controller (NTLARS reg restore + FMS Host FQDN + Realtek
PCIe GbE static IP). Mermaid overview links to each section. Live HTML
uses CDN; static HTML pre-renders SVG for offline / printable use.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 15:13:12 -04:00
cproudlock
99802ebbc9 BIOS: add OptiPlex 7080 (1.37.0)
Models.txt entry maps "7080" substring (matches WMI csproduct name
"OptiPlex 7080") to OptiPlex_7080_1.37.0.exe. BIOS .exe already
deployed to /srv/samba/winpeapps/_shared/BIOS/ on the live PXE
server via download-drivers.py.

Also adds docs/geastandardpbr-overrides.md tracking the local
geastandardpbr/ edits (user_selections.json + HardwareDriver.json
get a 7080 entry under "D11 OptiPlex Family") that the gitignore
prevents from being tracked directly. Includes a Python snippet
to idempotently re-apply after a fresh USB import.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 17:23:28 -04:00
cproudlock
ce3fbf5a28 sweep: pre-existing drift + matrix UDC entry + ignore 142MB EXE
Bundles drift left uncommitted from prior sessions and the UDC matrix
verify entry added today.

Drift items (all per session-progress.md, completed in earlier sessions
but never staged):

- playbook/check-bios.cmd (deleted, moved to BIOS/check-bios.cmd)
- playbook/migrate-to-wifi.ps1 (made no-op 2026-04-24 after the dnsmasq
  no-gateway fix removed the wired-NIC race that motivated it)
- playbook/preinstall/oracle/Install-Oracle11r2.cmd (post-OUI .ora copy
  added 2026-04-24)
- playbook/preinstall/oracle/tnsnames.ora (live tnsnames, 469 KB,
  deployed alongside the wrapper 2026-04-24)
- playbook/pxe_server_setup.yml (dnsmasq dhcp-option=3,6 commented,
  Oracle .ora deploy task added 2026-04-24)
- playbook/shopfloor-setup/BIOS/{check-bios.cmd, models.txt} (BIOS
  detection refinements)
- playbook/shopfloor-setup/Shopfloor/Force-Lockdown.bat
- playbook/shopfloor-setup/Shopfloor/Monitor-IntuneProgress.ps1
- playbook/shopfloor-setup/Shopfloor/SetShopfloorAutoLogon.bat (new)
- playbook/shopfloor-setup/Shopfloor/09-Install-PrinterInstallerMap.ps1
  (new, places PrinterInstallerMap.exe + Public Desktop shortcut at
  imaging time; manifest entry self-heals on tamper)
- playbook/shopfloor-setup/Shopfloor/lib/Show-IntuneDeviceQR.ps1 (new,
  standalone QR rendering for site that wanted just that piece)
- playbook/shopfloor-setup/gea-shopfloor-collections/{Install-eMxInfo.cmd.template,
  Restore-UDCData.ps1} (these were uncommitted in pre-rename Standard/;
  git mv didn't catch them because they were untracked at the time)
- docs/shopfloor-machine-imaging-guide.md (operator-facing how-to)

Matrix:
- common.test/matrix.json: add UDC verify entry to gea-shopfloor-collections
  row. Surfaces UDC silent-install issue (item H pending) instead of
  letting it pass silently.

.gitignore:
- PrinterInstallerMap.exe (142 MB) excluded. Track via LFS or stage on
  PXE server only - too big for regular git history. Untouched on disk
  so existing local copy still works.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 08:49:43 -04:00
cproudlock
80e9c32fae Add GE-Enforce v2 architecture doc
Captures the full picture of how the manifest engine works, why scripts
don't need self-heal entries (run from share), credential context
(SYSTEM = computer account, requires Mount-SFLDShare for file-level
reads), C:\Enrollment vs SFLD share copy distinction, and update
workflows. Written in response to a session that wasted time adding
redundant manifest entries because this wasn't documented.

Companion to scripts/diagnostics/Capture-LockdownState.ps1 and the
auditing script in pxe-images/Audit-SFLDShare.ps1.
2026-05-01 12:15:31 -04:00
cproudlock
adef507084 Add ARCHITECTURE.md, SITE-CUSTOMIZATION.md, mirror-from-gold.sh
Two new docs cover what the system is (boot chain, services, shares,
enrollment layout, data flow) and what to change per site (every
hardcoded value, where it lives, secrets handling).

scripts/mirror-from-gold.sh replicates content from an existing PXE
server (Operating Systems, drivers, packages, custom installers, BIOS,
PCDMIS, Blancco custom image, site-config) onto a freshly-installed PXE
server. Translates the legacy flat enrollment layout on the source into
the reorganized taxonomy (ppkgs/, pre-install/installers/, installers-
post/cmm/, blancco/, config/) on the destination. Tolerates rsync
exit 23 (permission-denied subdirs like the OpenText W10shortcuts dir
that is pxe-upload-group-only on legacy servers).
2026-04-14 17:56:46 -04:00
cproudlock
76165495ff Shopfloor PC type system, webapp enhancements, slim Blancco GRUB
- Shopfloor PC type menu (CMM, WaxAndTrace, Keyence, Genspect, Display, Standard)
- Baseline scripts: OpenText CSF, Start Menu shortcuts, network/WinRM, power/display
- Standard type: eDNC + MarkZebra with 64-bit path mirroring
- CMM type: Hexagon CLM Tools, PC-DMIS 2016/2019 R2
- Display sub-type: Lobby vs Dashboard
- Webapp: enrollment management, image config editor, UI refresh
- Upload-Image.ps1: robocopy MCL cache to PXE server
- Download-Drivers.ps1: Dell driver download pipeline
- Slim Blancco GRUB EFI (10MB -> 660KB) for old hardware compat
- Shopfloor display imaging guide docs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:25:07 -04:00