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:
165
webapp/services/blancco_prefs.py
Normal file
165
webapp/services/blancco_prefs.py
Normal file
@@ -0,0 +1,165 @@
|
||||
"""Site settings in the Blancco preferences: set in the webapp, not in XML.
|
||||
|
||||
Three values differ per site and are filled into our preferences.xml:
|
||||
business_location printed on every erasure report (the site)
|
||||
wifi_ssid the Wi-Fi Blancco joins to license erasures in the GE
|
||||
wifi_password Blancco cloud (the imaging LAN has no internet)
|
||||
|
||||
They live in UPDATES_DIR/blancco/site-settings.json (0600, root). The repo's
|
||||
playbook/blancco-preferences.xml carries none of them; the playbook installs
|
||||
it as WEB_ROOT/blancco/preferences.repo.xml and this module renders the
|
||||
served WEB_ROOT/blancco/preferences.xml from it. The same values are applied
|
||||
whenever a new Blancco release is staged (blancco_update.apply_prefs), so an
|
||||
update keeps them.
|
||||
|
||||
The playbook seeds the settings from site-vars.yml (site_name,
|
||||
blancco_wifi_ssid, blancco_wifi_password) only while no settings file exists:
|
||||
after install the webapp page (Updates > Blancco releases) is the place to
|
||||
change them, and a playbook re-run never overwrites them.
|
||||
|
||||
python3 -m services.blancco_prefs render [--seed]
|
||||
--seed reads BLANCCO_SEED_LOCATION / _SSID / _PASSWORD from the
|
||||
environment (not argv: the password stays out of ps).
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from xml.sax.saxutils import escape
|
||||
|
||||
import config
|
||||
from services import updates_common as uc
|
||||
|
||||
FIELDS = ("business_location", "wifi_ssid", "wifi_password")
|
||||
DEFAULTS = {"business_location": "", "wifi_ssid": "INTERNETACCESS", "wifi_password": ""}
|
||||
|
||||
_LOCATION = re.compile(rb"(<business_location>)[^<]*(</business_location>)")
|
||||
_WIRELESS = re.compile(
|
||||
rb'(<wireless>\s*<enabled>[^<]*</enabled>\s*<ssid encrypted="false">)[^<]*'
|
||||
rb'(</ssid>\s*<password encrypted="false">)[^<]*(</password>)')
|
||||
|
||||
|
||||
def settings_path():
|
||||
return os.path.join(uc.state_dir("blancco"), "site-settings.json")
|
||||
|
||||
|
||||
def repo_prefs_path():
|
||||
return os.path.join(config.WEB_ROOT, "blancco", "preferences.repo.xml")
|
||||
|
||||
|
||||
def served_prefs_path():
|
||||
return os.path.join(config.WEB_ROOT, "blancco", "preferences.xml")
|
||||
|
||||
|
||||
def load():
|
||||
data = uc.read_json(settings_path(), None)
|
||||
out = dict(DEFAULTS)
|
||||
if isinstance(data, dict):
|
||||
for k in FIELDS:
|
||||
if isinstance(data.get(k), str):
|
||||
out[k] = data[k]
|
||||
return out
|
||||
|
||||
|
||||
def exists():
|
||||
return os.path.isfile(settings_path())
|
||||
|
||||
|
||||
def _clean(value, field):
|
||||
value = (value or "").strip()
|
||||
if len(value) > 128:
|
||||
raise ValueError(f"{field} is too long (128 characters at most)")
|
||||
if any(ord(c) < 32 for c in value):
|
||||
raise ValueError(f"{field} has control characters")
|
||||
return value
|
||||
|
||||
|
||||
def save(business_location, wifi_ssid, wifi_password=None):
|
||||
"""Store the settings. wifi_password None (or '') keeps the stored one."""
|
||||
cur = load()
|
||||
new = {
|
||||
"business_location": _clean(business_location, "Business location"),
|
||||
"wifi_ssid": _clean(wifi_ssid, "Wi-Fi network"),
|
||||
"wifi_password": cur["wifi_password"] if not wifi_password else _clean(wifi_password, "Wi-Fi password"),
|
||||
}
|
||||
if not new["wifi_ssid"]:
|
||||
raise ValueError("Wi-Fi network (SSID) cannot be empty")
|
||||
path = settings_path()
|
||||
tmp = path + ".tmp"
|
||||
fd = os.open(tmp, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
|
||||
with os.fdopen(fd, "w") as f:
|
||||
json.dump(new, f, indent=2)
|
||||
os.replace(tmp, path)
|
||||
return new
|
||||
|
||||
|
||||
def apply(xml, settings=None):
|
||||
"""Our preferences (bytes) with the site settings filled in. Values are
|
||||
XML-escaped; the replacement is a function, so nothing in them is read
|
||||
as a regex group reference."""
|
||||
s = settings or load()
|
||||
loc = escape(s["business_location"] or "Unnamed site").encode("utf-8")
|
||||
ssid = escape(s["wifi_ssid"]).encode("utf-8")
|
||||
pw = escape(s["wifi_password"]).encode("utf-8")
|
||||
out, n = _LOCATION.subn(lambda m: m.group(1) + loc + m.group(2), xml)
|
||||
if n != 1:
|
||||
raise ValueError("preferences have no single <business_location> element")
|
||||
out, n = _WIRELESS.subn(lambda m: m.group(1) + ssid + m.group(2) + pw + m.group(3), out)
|
||||
if n != 1:
|
||||
raise ValueError("preferences have no single <wireless> ssid/password block")
|
||||
return out
|
||||
|
||||
|
||||
def render():
|
||||
"""Write the served preferences.xml from the repo copy + settings, when it
|
||||
changes. Returns True if the file was written."""
|
||||
with open(repo_prefs_path(), "rb") as f:
|
||||
base = f.read()
|
||||
data = apply(base)
|
||||
# Same guard the playbook and the Blancco page apply: well-formed XML.
|
||||
import xml.etree.ElementTree as ET
|
||||
ET.fromstring(data)
|
||||
path = served_prefs_path()
|
||||
try:
|
||||
with open(path, "rb") as f:
|
||||
if f.read() == data:
|
||||
return False
|
||||
except OSError:
|
||||
pass
|
||||
tmp = path + ".tmp"
|
||||
with open(tmp, "wb") as f:
|
||||
f.write(data)
|
||||
os.chmod(tmp, 0o644)
|
||||
os.replace(tmp, path)
|
||||
return True
|
||||
|
||||
|
||||
def public():
|
||||
"""For the page: the settings without the password."""
|
||||
s = load()
|
||||
return {"business_location": s["business_location"], "wifi_ssid": s["wifi_ssid"],
|
||||
"wifi_password_set": bool(s["wifi_password"]), "configured": exists()}
|
||||
|
||||
|
||||
def main(argv):
|
||||
if not argv or argv[0] != "render":
|
||||
print(__doc__)
|
||||
return 2
|
||||
if "--seed" in argv[1:] and not exists():
|
||||
save(os.environ.get("BLANCCO_SEED_LOCATION", ""),
|
||||
os.environ.get("BLANCCO_SEED_SSID", "") or DEFAULTS["wifi_ssid"],
|
||||
os.environ.get("BLANCCO_SEED_PASSWORD", ""))
|
||||
print("seeded site settings from site-vars")
|
||||
s = load()
|
||||
changed = render()
|
||||
print("CHANGED" if changed else "UNCHANGED")
|
||||
if not s["business_location"]:
|
||||
print("WARNING: business_location is empty: reports say 'Unnamed site'")
|
||||
if not s["wifi_password"]:
|
||||
print(f"WARNING: no Wi-Fi password for '{s['wifi_ssid']}': Blancco cannot license erasures")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(sys.argv[1:]))
|
||||
@@ -955,6 +955,15 @@ def apply_prefs(version):
|
||||
ours = _read(src)
|
||||
if not ours:
|
||||
raise ValueError(f"no preferences at {src}")
|
||||
# The site's business location and Wi-Fi (Updates > Blancco, site
|
||||
# settings) go into every release, so an update keeps them.
|
||||
from services import blancco_prefs
|
||||
try:
|
||||
ours = blancco_prefs.apply(ours)
|
||||
except ValueError as e:
|
||||
meta.update(status="failed", error=f"our preferences ({src}): {e}", prefs_source=src)
|
||||
uc.write_json(meta_path, meta)
|
||||
raise
|
||||
try:
|
||||
cfg = patch_config_img(vendor_cfg, ours)
|
||||
except StageBlocked as e:
|
||||
|
||||
Reference in New Issue
Block a user