Add wimtools and startnet.cmd editor for boot.wim modification

- Added wimtools to offline packages and playbook verification
- Webapp startnet.cmd editor: extract, view, edit, save back to boot.wim
- Uses wimextract/wimupdate for in-place WIM modification
- Dark-themed code editor with tab support and common command reference

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
cproudlock
2026-02-06 16:23:22 -05:00
parent e7313c2ca3
commit 89b58347d9
5 changed files with 250 additions and 0 deletions

View File

@@ -29,6 +29,8 @@ app.config["MAX_CONTENT_LENGTH"] = 16 * 1024 * 1024 * 1024 # 16 GB max upload
# ---------------------------------------------------------------------------
SAMBA_SHARE = os.environ.get("SAMBA_SHARE", "/srv/samba/winpeapps")
CLONEZILLA_SHARE = os.environ.get("CLONEZILLA_SHARE", "/srv/samba/clonezilla")
WEB_ROOT = os.environ.get("WEB_ROOT", "/var/www/html")
BOOT_WIM = os.path.join(WEB_ROOT, "win11", "sources", "boot.wim")
IMAGE_TYPES = [
"gea-standard",
@@ -638,6 +640,118 @@ def clonezilla_delete(filename):
return redirect(url_for("clonezilla_backups"))
# ---------------------------------------------------------------------------
# Routes — startnet.cmd Editor (WIM)
# ---------------------------------------------------------------------------
def _wim_extract_startnet(wim_path):
"""Extract startnet.cmd from a WIM file using wimextract."""
import tempfile
tmpdir = tempfile.mkdtemp()
try:
result = subprocess.run(
["wimextract", wim_path, "1",
"/Windows/System32/startnet.cmd",
"--dest-dir", tmpdir],
capture_output=True, text=True, timeout=30,
)
startnet_path = os.path.join(tmpdir, "startnet.cmd")
if result.returncode == 0 and os.path.isfile(startnet_path):
content = open(startnet_path, "r", encoding="utf-8", errors="replace").read()
return content
return None
except Exception:
return None
finally:
shutil.rmtree(tmpdir, ignore_errors=True)
def _wim_update_startnet(wim_path, content):
"""Update startnet.cmd inside a WIM file using wimupdate."""
import tempfile
tmpdir = tempfile.mkdtemp()
try:
startnet_path = os.path.join(tmpdir, "startnet.cmd")
with open(startnet_path, "w", encoding="utf-8", newline="\r\n") as fh:
fh.write(content)
# wimupdate reads commands from stdin
update_cmd = f"add {startnet_path} /Windows/System32/startnet.cmd\n"
result = subprocess.run(
["wimupdate", wim_path, "1"],
input=update_cmd,
capture_output=True, text=True, timeout=60,
)
if result.returncode != 0:
return False, result.stderr.strip()
return True, ""
except Exception as exc:
return False, str(exc)
finally:
shutil.rmtree(tmpdir, ignore_errors=True)
def _wim_list_files(wim_path, path="/"):
"""List files inside a WIM at the given path."""
try:
result = subprocess.run(
["wimdir", wim_path, "1", path],
capture_output=True, text=True, timeout=30,
)
if result.returncode == 0:
return [l.strip() for l in result.stdout.splitlines() if l.strip()]
return []
except Exception:
return []
@app.route("/startnet")
def startnet_editor():
wim_exists = os.path.isfile(BOOT_WIM)
content = ""
wim_info = {}
if wim_exists:
content = _wim_extract_startnet(BOOT_WIM) or ""
# Get WIM info
try:
result = subprocess.run(
["wiminfo", BOOT_WIM],
capture_output=True, text=True, timeout=15,
)
if result.returncode == 0:
for line in result.stdout.splitlines():
if ":" in line:
key, _, val = line.partition(":")
wim_info[key.strip()] = val.strip()
except Exception:
pass
return render_template(
"startnet_editor.html",
wim_exists=wim_exists,
wim_path=BOOT_WIM,
content=content,
wim_info=wim_info,
image_types=IMAGE_TYPES,
friendly_names=FRIENDLY_NAMES,
)
@app.route("/startnet/save", methods=["POST"])
def startnet_save():
if not os.path.isfile(BOOT_WIM):
flash("boot.wim not found.", "danger")
return redirect(url_for("startnet_editor"))
content = request.form.get("content", "")
ok, err = _wim_update_startnet(BOOT_WIM, content)
if ok:
flash("startnet.cmd updated successfully in boot.wim.", "success")
else:
flash(f"Failed to update boot.wim: {err}", "danger")
return redirect(url_for("startnet_editor"))
# ---------------------------------------------------------------------------
# Routes — API
# ---------------------------------------------------------------------------