enrollment: auto-activate uploaded ppkg via ppkg.conf (no boot.wim edit)

startnet.cmd now sources SOURCE_PPKG/PPKG_VER/PPKG_EXP from \\<pxe>\enrollment\ppkg.conf (baked-in values kept as fallback if the file is missing). The webapp writes ppkg.conf on upload for standard GCCH_..._v<ver>.ppkg files: version parsed from the filename, optional expiry field (blank keeps current), so a new ppkg goes live at next boot with no startnet/boot.wim edit. Enrollment page shows the active ppkg. ppkg.conf seeded on the share (v4.16) and the ppkg.conf-aware startnet.cmd deployed into boot.wim.
This commit is contained in:
cproudlock
2026-07-23 11:02:43 -04:00
parent 3b63697176
commit 0ffdcc79ed
3 changed files with 82 additions and 5 deletions

View File

@@ -15,6 +15,7 @@ This file is the route surface; most logic lives in ``services/``:
import hashlib
import json
import os
import re
import shutil
import tempfile
import time
@@ -814,6 +815,37 @@ def imaging_set_laps(serial):
# Routes - Enrollment Packages
# ---------------------------------------------------------------------------
PPKG_CONF = os.path.join(config.ENROLLMENT_SHARE, "ppkg.conf")
def read_ppkg_conf():
"""Read the active-PPKG config (SOURCE_PPKG/PPKG_VER/PPKG_EXP) that
startnet.cmd sources at boot. Returns a dict (empty if unset)."""
data = {}
if os.path.isfile(PPKG_CONF):
try:
with open(PPKG_CONF, "r", errors="replace") as fh:
for line in fh:
if "=" in line:
k, _, v = line.strip().partition("=")
data[k.strip()] = v.strip()
except OSError:
pass
return data
def write_ppkg_conf(source_ppkg, ppkg_ver, ppkg_exp):
"""Write ppkg.conf (CRLF) so startnet.cmd picks up the active ppkg at
boot with no boot.wim edit."""
lines = [
f"SOURCE_PPKG={source_ppkg}",
f"PPKG_VER={ppkg_ver}",
f"PPKG_EXP={ppkg_exp}",
]
with open(PPKG_CONF, "w", newline="\r\n") as fh:
fh.write("\n".join(lines) + "\n")
@app.route("/enrollment")
def enrollment():
packages = []
@@ -830,6 +862,7 @@ def enrollment():
return render_template(
"enrollment.html",
packages=packages,
active_ppkg=read_ppkg_conf(),
image_types=config.IMAGE_TYPES,
friendly_names=config.FRIENDLY_NAMES,
)
@@ -856,6 +889,21 @@ def enrollment_upload():
f.save(dest)
audit("ENROLLMENT_UPLOAD", filename)
flash(f"Uploaded {filename} successfully.", "success")
# 2b: set this ppkg active for startnet.cmd by writing ppkg.conf on the
# share (no boot.wim edit). Only for the standard GCCH_..._v<ver>.ppkg name.
m = re.search(r"_(v\d+(?:\.\d+)*)\.ppkg$", filename, re.IGNORECASE)
if m:
ver = m.group(1)
expiry = (request.form.get("expiry") or "").strip()
if not expiry:
expiry = read_ppkg_conf().get("PPKG_EXP", "")
try:
write_ppkg_conf(filename, ver, expiry)
audit("PPKG_ACTIVATE", f"{filename} ver={ver} exp={expiry}")
flash(f"Set {filename} as the active PPKG - startnet.cmd will use it.", "success")
except OSError as exc:
flash(f"Uploaded, but could not update ppkg.conf: {exc}", "warning")
return redirect(url_for("enrollment"))

View File

@@ -11,6 +11,15 @@
</div>
</div>
{% if active_ppkg and active_ppkg.SOURCE_PPKG %}
<div class="alert alert-info">
<strong>Active PPKG:</strong> <span class="mono">{{ active_ppkg.SOURCE_PPKG }}</span>
&nbsp;<span class="badge badge-info">{{ active_ppkg.PPKG_VER }}</span>
{% if active_ppkg.PPKG_EXP %}<span class="badge badge-secondary">exp {{ active_ppkg.PPKG_EXP }}</span>{% endif %}
<span class="form-text">- startnet.cmd boots with this (from <span class="mono">ppkg.conf</span>).</span>
</div>
{% endif %}
<div class="card">
<div class="card-header">
<span><i class="bi bi-box-seam"></i> GCCH Provisioning Packages</span>
@@ -89,9 +98,16 @@
<input type="file" class="form-control" id="ppkgFile" name="ppkg_file"
accept=".ppkg" required>
<div class="form-text">
Use <span class="mono">with-office.ppkg</span> or <span class="mono">without-office.ppkg</span> to match the WinPE boot menu.
A standard <span class="mono">GCCH_Prod_SFLD_v&lt;ver&gt;.ppkg</span> upload is set as the active PPKG automatically
(written to <span class="mono">ppkg.conf</span> on the share; startnet.cmd reads it at boot - no boot.wim edit).
</div>
</div>
<div class="form-field">
<label for="ppkgExpiry" class="form-label">Expiry (optional)</label>
<input type="text" class="form-control mono" id="ppkgExpiry" name="expiry"
placeholder="YYYYMMDD" pattern="\d{8}">
<div class="form-text">Blank keeps the current expiry.</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>