Image import keeps the shared BPRT link and seeds BPRT on a fresh box
An MCL import merged Deploy/Applications with rsync -a, which replaced each image's BPRT symlink with a plain copy of the vendor folder. That copy has no site overlay, so standard/engineer enrollment lost Register-BulkEnrollTask-Standard.ps1 (specialize Order 39). Now Deploy/Applications is imported first and without BPRT: - where _shared/Applications/BPRT exists it is left alone (the BPRT page keeps it current) and the image link is put back; - on a box with none yet, the imported BPRT seeds it: vendor Select-PCConfig.ps1 kept as -vendor, our overlay added, BOM fix applied; - the import refuses before anything moves when that folder would not work (vendor scripts missing, or no overlay copy to add). The two Register scripts join Select-PCConfig.ps1 in the repo. The playbook already syncs shopfloor-setup to the enrollment share, so config.BPRT_OVERLAY_DIR finds all three there; overlay_sources now falls back to it for every overlay file, not only the shim.
This commit is contained in:
@@ -50,6 +50,7 @@ import zipfile
|
||||
from collections import Counter
|
||||
from datetime import datetime, timezone
|
||||
|
||||
import config
|
||||
from services import mcl_ppkg
|
||||
from services import updates_common as uc
|
||||
|
||||
@@ -65,12 +66,14 @@ UNIT = "pxe-bprt-sync"
|
||||
# Select-PCConfig-vendor.ps1 (VENDOR_RENAMED);
|
||||
# Register-BulkEnrollTask-Standard.ps1 registers the orchestrator; specialize Order 39
|
||||
# of playbook/FlatUnattendW10.xml and
|
||||
# FlatUnattendW10-engineer.xml runs it. It exists
|
||||
# only on the share (no repo copy);
|
||||
# FlatUnattendW10-engineer.xml runs it;
|
||||
# Register-BulkEnrollTask.ps1 the shopfloor -ManualFallback variant. Nothing
|
||||
# calls it since the shopfloor unattend dropped
|
||||
# its Order 16 (2026-08-06); kept as it was.
|
||||
OVERLAY = ("Select-PCConfig.ps1", "Register-BulkEnrollTask-Standard.ps1",
|
||||
# All three are in the repo, playbook/shopfloor-setup/BPRT/; the playbook
|
||||
# syncs them to config.BPRT_OVERLAY_DIR. A copy in the live BPRT wins; those
|
||||
# copies fill in what it lacks (overlay_sources).
|
||||
OVERLAY =("Select-PCConfig.ps1", "Register-BulkEnrollTask-Standard.ps1",
|
||||
"Register-BulkEnrollTask.ps1")
|
||||
# Vendor files the overlay displaces, and the name the vendor's copy keeps.
|
||||
VENDOR_RENAMED = {"Select-PCConfig.ps1": "Select-PCConfig-vendor.ps1"}
|
||||
@@ -332,22 +335,98 @@ def vendor_view(root, files):
|
||||
return out
|
||||
|
||||
|
||||
def overlay_fallbacks():
|
||||
"""Folders holding our overlay outside BPRT: the copy the playbook syncs
|
||||
to the enrollment share, then the repo when the webapp runs from a
|
||||
checkout."""
|
||||
here = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
return [config.BPRT_OVERLAY_DIR,
|
||||
os.path.abspath(os.path.join(here, "..", "playbook", "shopfloor-setup", "BPRT"))]
|
||||
|
||||
|
||||
def overlay_sources(root, files):
|
||||
"""{overlay name: absolute source path} found in a BPRT folder; our shim
|
||||
falls back to the repo copy when the webapp runs from a checkout."""
|
||||
"""{overlay name: absolute source path}: the copy in this BPRT folder,
|
||||
else the first one in overlay_fallbacks(). A Select-PCConfig.ps1 counts
|
||||
only when it is our shim."""
|
||||
out = {}
|
||||
for name in OVERLAY:
|
||||
path = os.path.join(root, name)
|
||||
if name in files and (name not in VENDOR_RENAMED or is_shim(path)):
|
||||
out[name] = path
|
||||
if "Select-PCConfig.ps1" not in out:
|
||||
here = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
repo = os.path.join(here, "..", "playbook", "shopfloor-setup", "BPRT", "Select-PCConfig.ps1")
|
||||
if is_shim(repo):
|
||||
out["Select-PCConfig.ps1"] = os.path.abspath(repo)
|
||||
continue
|
||||
for folder in overlay_fallbacks():
|
||||
path = os.path.join(folder, name)
|
||||
if os.path.isfile(path) and (name not in VENDOR_RENAMED or is_shim(path)):
|
||||
out[name] = path
|
||||
break
|
||||
return out
|
||||
|
||||
|
||||
def seed_from_import(src):
|
||||
"""An image import brought a BPRT folder (src). On a box that has no
|
||||
_shared/Applications/BPRT yet (a fresh install) it becomes that folder,
|
||||
with the vendor's Select-PCConfig.ps1 kept as Select-PCConfig-vendor.ps1
|
||||
and our overlay added, the way activate() builds one. Where BPRT exists
|
||||
it is left alone: the BPRT page keeps it current. Returns "seeded" or
|
||||
"kept"; raises before anything changed when the folder would not work."""
|
||||
with _Lock():
|
||||
_recover()
|
||||
live = bprt_dir()
|
||||
if os.path.isdir(live) and not os.path.islink(live):
|
||||
return "kept"
|
||||
if os.path.lexists(live):
|
||||
raise RuntimeError(f"{live} exists but is not a folder; fix it on the server first")
|
||||
files, _ = walk(src)
|
||||
names = _lc(files)
|
||||
missing = [r for r in REQUIRED if r.lower() not in names]
|
||||
if missing:
|
||||
raise RuntimeError("the imported BPRT folder lacks " + ", ".join(missing)
|
||||
+ ": the bulk-enroll flow calls them by path")
|
||||
srcs = overlay_sources(src, files)
|
||||
lack = [n for n in ("Select-PCConfig.ps1",) + OVERLAY_REQUIRED if n not in srcs]
|
||||
if lack:
|
||||
raise RuntimeError(", ".join(lack) + " not found in " + config.BPRT_OVERLAY_DIR
|
||||
+ ": run the playbook (it copies shopfloor-setup there), then import again")
|
||||
new = new_dir()
|
||||
if os.path.lexists(new):
|
||||
shutil.rmtree(new) # left by a failed run; never live
|
||||
os.makedirs(os.path.dirname(new), exist_ok=True)
|
||||
try:
|
||||
shutil.copytree(src, new, symlinks=False)
|
||||
top = os.listdir(new)
|
||||
for vendor, kept in VENDOR_RENAMED.items():
|
||||
for n in top:
|
||||
p = os.path.join(new, n)
|
||||
if n.lower() == vendor.lower() and not is_shim(p):
|
||||
os.replace(p, os.path.join(new, kept))
|
||||
top = os.listdir(new)
|
||||
for oname, s in srcs.items():
|
||||
for n in top:
|
||||
if n.lower() == oname.lower():
|
||||
os.remove(os.path.join(new, n)) # the vendor shipped one (any case); ours wins
|
||||
shutil.copy2(s, os.path.join(new, oname))
|
||||
bom = []
|
||||
for rel in walk(new)[0]:
|
||||
path = os.path.join(new, *rel.split("/"))
|
||||
if _is_script(rel) and _bom_less_non_ascii(path):
|
||||
with open(path, "rb") as f:
|
||||
data = f.read()
|
||||
with open(path, "wb") as f:
|
||||
f.write(BOM + data)
|
||||
bom.append(rel)
|
||||
for dirpath, dirs, fnames in os.walk(new):
|
||||
_match_owner(dirpath, 0o755)
|
||||
for n in fnames:
|
||||
_match_owner(os.path.join(dirpath, n), _DEFAULT_FILE_MODE)
|
||||
os.rename(new, live)
|
||||
except BaseException:
|
||||
shutil.rmtree(new, ignore_errors=True)
|
||||
raise
|
||||
_audit("BPRT_SEED", f"from image import {src}: {len(files)} files, overlay "
|
||||
f"{', '.join(sorted(srcs))}, BOM added to {len(bom)}")
|
||||
return "seeded"
|
||||
|
||||
|
||||
def manifest(build=False, progress=None):
|
||||
"""What manifest.json serves: every vendor file of the live BPRT with size
|
||||
and sha256 (and sha256_nobom where we added a BOM). Returns None when a
|
||||
|
||||
Reference in New Issue
Block a user