refactor(deploy-win): drop UUP dump, take ISO via --iso-url / --win-iso

On a Linux host the UUP converter only produces the RTM install.wim (build
26100.1), so Windows Update had to install the entire cumulative anyway —
the UUP download/convert machinery added a slow ~5 GB step and several host
tools (aria2/wimtools/cabextract/chntpw) for no benefit.

Replace it: deploy-win now takes the install ISO directly via `--iso-url`
(urllib download + cache by file name under <CI_ROOT>/iso/, optional
--iso-sha256) or a local `--win-iso` (exactly one required). Patch + deploy
flow is unchanged. Supply an already-updated ISO (e.g. a massgrave link) to
skip the WU step entirely.

- Remove winimage/uup.py and its tests.
- Trim setup-host-linux.sh deps to p7zip-full + xorriso + genisoimage.
- Update RUNBOOK §5 and the Windows-host port plan. Coverage 93.6%.
This commit is contained in:
2026-06-09 18:42:22 +02:00
parent cd88dec15d
commit c257c931c3
8 changed files with 108 additions and 445 deletions
+42 -24
View File
@@ -68,6 +68,7 @@ import sys
import tempfile
import threading
import time
import urllib.parse
import urllib.request
from datetime import UTC, datetime
from pathlib import Path
@@ -82,7 +83,6 @@ from ci_orchestrator.transport.errors import TransportError
from ci_orchestrator.transport.winrm import WinRmResult, WinRmTransport
from ci_orchestrator.winimage import autounattend as win_autounattend
from ci_orchestrator.winimage import iso_patch as win_iso_patch
from ci_orchestrator.winimage import uup as win_uup
@click.group()
@@ -2086,12 +2086,13 @@ def _vmrun_soft_stop(vmrun: str, vmx: str, *, timeout_s: float = 300.0) -> None:
@click.option("--vmx", "vmx_path_str", default=None, metavar="PATH",
help="VMX to create (default: <CI_TEMPLATES>/WinBuild2025/WinBuild2025.vmx).")
@click.option("--vm-name", default="WinBuild2025", show_default=True, help="VM display name.")
@click.option("--build", "uup_build", default=None,
help="Pin a UUP build number (e.g. 26100.32860). Default: newest WS2025.")
@click.option("--iso-url", "iso_url", default=None, metavar="URL",
help="Direct download URL of the Windows Server 2025 install ISO. "
"Cached under <CI_ROOT>/iso/. Mutually exclusive with --win-iso.")
@click.option("--iso-sha256", "iso_sha256", default="",
help="Expected SHA256 of the downloaded ISO (empty = skip verification).")
@click.option("--win-iso", "win_iso_str", default=None, metavar="PATH",
help="Use this Windows ISO instead of downloading from UUP dump.")
@click.option("--edition", default="SERVERSTANDARD", show_default=True, help="UUP edition.")
@click.option("--lang", default="en-us", show_default=True, help="UUP language pack.")
help="Use this local Windows ISO instead of --iso-url.")
@click.option("--image-index", default=2, show_default=True, help="install.wim image index.")
@click.option("--product-key", default="TVRH6-WHNXV-R9WG3-9XRFY-MY832", show_default=True)
@click.option("--admin-user", default="Administrator", show_default=True)
@@ -2119,10 +2120,9 @@ def _vmrun_soft_stop(vmrun: str, vmx: str, *, timeout_s: float = 300.0) -> None:
def template_deploy_win(
vmx_path_str: str | None,
vm_name: str,
uup_build: str | None,
iso_url: str | None,
iso_sha256: str,
win_iso_str: str | None,
edition: str,
lang: str,
image_index: int,
product_key: str,
admin_user: str,
@@ -2147,10 +2147,11 @@ def template_deploy_win(
) -> None:
"""Deploy the Windows Server 2025 build VM template (Linux VMware host).
Downloads the latest WS2025 ISO from UUP dump (deterministic, build-pinned),
patches it (inject autounattend.xml + post-install.ps1 + no-prompt boot),
runs the unattended install, swaps the NIC to vmxnet3 and snapshots the
template. Mirrors the legacy Deploy-WinBuild2025.ps1 ten-step flow.
Downloads the install ISO from ``--iso-url`` (cached under <CI_ROOT>/iso/)
or uses a local ``--win-iso``, patches it (inject autounattend.xml +
post-install.ps1 + no-prompt boot), runs the unattended install, swaps the
NIC to vmxnet3 and snapshots the template. Mirrors the legacy
Deploy-WinBuild2025.ps1 ten-step flow.
"""
config = load_config()
vmx_path = (
@@ -2197,13 +2198,11 @@ def template_deploy_win(
if not ok:
raise click.ClickException(f"[PreFlight] {label}")
click.echo(f" [OK] {label}")
if win_iso_str is None:
missing = win_uup.check_tools()
if missing:
raise click.ClickException(
"[PreFlight] missing UUP tools: " + ", ".join(missing)
)
click.echo(" [OK] UUP conversion tools present")
if bool(iso_url) == bool(win_iso_str):
raise click.ClickException(
"[PreFlight] provide exactly one of --iso-url or --win-iso."
)
click.echo(" [OK] ISO source: " + (f"url {iso_url}" if iso_url else f"file {win_iso_str}"))
if vm_dir.exists():
if not force:
raise click.ClickException(
@@ -2222,7 +2221,7 @@ def template_deploy_win(
if start_from_step <= 2:
click.echo(" [OK] answer files rendered")
# ── Step 3: acquire base ISO (UUP dump) ────────────────────────────────────
# ── Step 3: acquire base ISO (download from URL, or local file) ────────────
_deploy_step("Step 3: acquire Windows Server 2025 ISO", 3, start_from_step)
if win_iso_str is not None:
base_iso = Path(win_iso_str)
@@ -2230,9 +2229,28 @@ def template_deploy_win(
raise click.ClickException(f"--win-iso not found: {base_iso}")
click.echo(f" [OK] using supplied ISO: {base_iso}")
else:
build = win_uup.resolve_build(build=uup_build)
click.echo(f" resolved build: {build.build} ({build.uuid})")
base_iso = win_uup.build_iso(build, dest_dir=iso_dir, lang=lang, edition=edition)
assert iso_url is not None # guaranteed by Step 1 source validation
iso_name = Path(urllib.parse.urlparse(iso_url).path).name or "windows-install.iso"
base_iso = iso_dir / iso_name
if start_from_step <= 3:
if base_iso.is_file():
click.echo(f" [OK] ISO cached, reusing: {base_iso}")
else:
iso_dir.mkdir(parents=True, exist_ok=True)
try:
_download_vmdk(iso_url, base_iso)
except Exception as exc:
raise click.ClickException(f"[Deploy] ISO download failed: {exc}") from exc
click.echo(f" [OK] downloaded: {base_iso}")
if iso_sha256:
click.echo(" Verifying ISO SHA256 ...")
actual = _sha256_file(base_iso)
if actual != iso_sha256.upper():
raise click.ClickException(
f"[Deploy] ISO SHA256 mismatch!\n Expected: {iso_sha256.upper()}\n"
f" Actual: {actual}"
)
click.echo(f" [OK] ISO SHA256: {actual}")
# ── Step 4: patch ISO (inject answer files + no-prompt boot) ───────────────
_deploy_step("Step 4: patch ISO (inject autounattend + no-prompt boot)", 4, start_from_step)