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/.
This commit is contained in:
cproudlock
2026-10-16 20:37:22 -04:00
parent b11e678297
commit 04db6d6605
710 changed files with 3113 additions and 82414 deletions

View File

@@ -1648,8 +1648,6 @@ def startnet_editor():
boot_menu=boot_menu,
lint=lint,
snapshots=snapshots,
shopfloor_items=read_shopfloor_menu(),
shopfloor_available=available_pctypes(),
image_types=config.IMAGE_TYPES,
friendly_names=config.FRIENDLY_NAMES,
)
@@ -1813,77 +1811,6 @@ def startnet_diff():
return jsonify({"error": "provide snapshot_id or content"}), 400
# ---------------------------------------------------------------------------
# Routes - Shopfloor boot-menu (data-driven menu.json)
# ---------------------------------------------------------------------------
SHOPFLOOR_SETUP_DIR = os.path.join(config.ENROLLMENT_SHARE, "shopfloor-setup")
SHOPFLOOR_MENU_JSON = os.path.join(SHOPFLOOR_SETUP_DIR, "menu.json")
def read_shopfloor_menu():
"""The shopfloor PC-type menu items from menu.json (list; [] if absent)."""
try:
with open(SHOPFLOOR_MENU_JSON) as fh:
return json.load(fh).get("shopfloor", [])
except (OSError, json.JSONDecodeError):
return []
def available_pctypes():
"""gea-shopfloor-* handler dirs on the share - the only valid menu keys."""
try:
return sorted(
d for d in os.listdir(SHOPFLOOR_SETUP_DIR)
if d.startswith("gea-shopfloor-")
and os.path.isdir(os.path.join(SHOPFLOOR_SETUP_DIR, d))
)
except OSError:
return []
@app.route("/shopfloor-menu", methods=["GET", "POST"])
def shopfloor_menu():
if request.method == "POST":
try:
items = json.loads(request.form.get("items", "[]"))
except json.JSONDecodeError:
flash("Invalid menu payload.", "danger")
return redirect(url_for("startnet_editor") + "#tab-shopfloor")
valid = set(available_pctypes())
clean = []
for it in items:
if not isinstance(it, dict):
continue
key = (it.get("key") or "").strip()
label = (it.get("label") or "").strip()
if not key or not label:
continue
if key not in valid:
flash(f"Skipped '{label}': {key} has no shopfloor-setup handler.", "warning")
continue
clean.append({
"key": key,
"label": label,
"hint": (it.get("hint") or "").strip(),
"enabled": bool(it.get("enabled", True)),
})
if not clean:
flash("Refused to save an empty menu.", "danger")
return redirect(url_for("startnet_editor") + "#tab-shopfloor")
try:
os.makedirs(SHOPFLOOR_SETUP_DIR, exist_ok=True)
with open(SHOPFLOOR_MENU_JSON, "w") as fh:
json.dump({"shopfloor": clean}, fh, indent=2)
audit("SHOPFLOOR_MENU_SAVE", f"{len(clean)} items")
flash("Shopfloor boot menu saved - WinPE reads it at next boot.", "success")
except OSError as exc:
flash(f"Could not write menu.json: {exc}", "danger")
return redirect(url_for("startnet_editor") + "#tab-shopfloor")
return redirect(url_for("startnet_editor") + "#tab-shopfloor")
# ---------------------------------------------------------------------------
# Routes - Audit Log
# ---------------------------------------------------------------------------
@@ -2064,6 +1991,7 @@ app.jinja_env.globals["blancco_failure_reason"] = blancco_report.failure_reason
@app.context_processor
def inject_globals():
return {
"site_name": config.BLANCCO_SITE,
"all_image_types": config.IMAGE_TYPES,
"all_friendly_names": config.FRIENDLY_NAMES,
# image pages: tab table, header data, tab URLs (see _image_header.html)