feat(template): check-win-iso (massgrave resolver) + patch-win-iso
Add `ci template check-win-iso`: parse the massgrave windows-server-links markdown source on GitHub for the newest Windows Server 2025 build, file name and link, and report whether it is already in <CI_ROOT>/iso/. The download itself stays manual — the massgrave link is behind a Cloudflare Turnstile file host, not scriptable. Add `ci template patch-win-iso --in ISO [--out ISO]`: standalone equivalent of deploy-win Step 4 — inject autounattend.xml + post-install.ps1 and a no-prompt boot image into a manually downloaded ISO, producing a ready-to-deploy `.patched.iso`. winimage/massgrave.py does the parse (no auto-download). RUNBOOK §5 documents the check → download → patch → deploy workflow. Coverage 93.6%.
This commit is contained in:
@@ -83,6 +83,7 @@ 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 massgrave as win_massgrave
|
||||
|
||||
|
||||
@click.group()
|
||||
@@ -2358,3 +2359,102 @@ def template_deploy_win(
|
||||
click.echo(f" VMX: {vmx_path}")
|
||||
click.echo(f" Snapshot: {snapshot_name}")
|
||||
click.echo(f" Next: ci template prepare-win --vmx {vmx_path}")
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
# template check-win-iso — resolve the latest WS2025 ISO from massgrave
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
@template.command("check-win-iso")
|
||||
@click.option("--lang", default="English", show_default=True, help="massgrave language column.")
|
||||
@click.option("--arch", default="x64", show_default=True, help="Architecture column.")
|
||||
def template_check_win_iso(lang: str, arch: str) -> None:
|
||||
"""Report the newest Windows Server 2025 ISO from the massgrave source.
|
||||
|
||||
The download itself is not automatable (the massgrave link is behind a
|
||||
Cloudflare-Turnstile file host), so this only resolves the latest build,
|
||||
file name and URL and tells you whether it is already in <CI_ROOT>/iso/.
|
||||
Download it once in a browser, then run ``template patch-win-iso`` /
|
||||
``deploy-win --win-iso``.
|
||||
"""
|
||||
config = load_config()
|
||||
iso_dir = config.paths.root / "iso"
|
||||
info = win_massgrave.latest_ws2025(lang=lang, arch=arch)
|
||||
cached = (iso_dir / info.filename).is_file()
|
||||
|
||||
click.echo(f"Latest WS2025 ({lang}/{arch}):")
|
||||
click.echo(f" build: {info.build}")
|
||||
click.echo(f" filename: {info.filename}")
|
||||
click.echo(f" url: {info.url}")
|
||||
click.echo(f" cached: {'yes — ' + str(iso_dir / info.filename) if cached else 'no'}")
|
||||
if not cached:
|
||||
click.echo(
|
||||
"\nDownload it in a browser (the link is Cloudflare-gated), save it to\n"
|
||||
f" {iso_dir / info.filename}\n"
|
||||
"then: ci template patch-win-iso --in "
|
||||
f"{iso_dir / info.filename}"
|
||||
)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
# template patch-win-iso — standalone no-prompt + autounattend ISO patch
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
@template.command("patch-win-iso")
|
||||
@click.option("--in", "in_iso_str", required=True, metavar="PATH", help="Windows install ISO to patch.")
|
||||
@click.option("--out", "out_iso_str", default=None, metavar="PATH",
|
||||
help="Output ISO (default: <in>.patched.iso).")
|
||||
@click.option("--image-index", default=2, show_default=True)
|
||||
@click.option("--product-key", default="TVRH6-WHNXV-R9WG3-9XRFY-MY832", show_default=True)
|
||||
@click.option("--admin-user", default="Administrator", show_default=True)
|
||||
@click.option("--admin-password", default="WinBuild!", show_default=True)
|
||||
@click.option("--computer-name", default="WINBUILD-2025", show_default=True)
|
||||
@click.option("--locale-os", default="en-US", show_default=True)
|
||||
@click.option("--locale-user", default="it-IT", show_default=True)
|
||||
@click.option("--keyboard", default="0410:00000410", show_default=True)
|
||||
@click.option("--timezone", default="W. Europe Standard Time", show_default=True)
|
||||
def template_patch_win_iso(
|
||||
in_iso_str: str,
|
||||
out_iso_str: str | None,
|
||||
image_index: int,
|
||||
product_key: str,
|
||||
admin_user: str,
|
||||
admin_password: str,
|
||||
computer_name: str,
|
||||
locale_os: str,
|
||||
locale_user: str,
|
||||
keyboard: str,
|
||||
timezone: str,
|
||||
) -> None:
|
||||
"""Patch a Windows ISO: inject autounattend.xml + post-install.ps1 + no-prompt boot.
|
||||
|
||||
Standalone equivalent of deploy-win Step 4 — produce a ready-to-deploy ISO
|
||||
from a manually downloaded one, then ``deploy-win --win-iso <out>``.
|
||||
"""
|
||||
in_iso = Path(in_iso_str)
|
||||
if not in_iso.is_file():
|
||||
raise click.ClickException(f"input ISO not found: {in_iso}")
|
||||
out_iso = (
|
||||
Path(out_iso_str)
|
||||
if out_iso_str
|
||||
else in_iso.with_suffix(in_iso.suffix + ".patched.iso")
|
||||
)
|
||||
try:
|
||||
win_autounattend.validate_computer_name(computer_name)
|
||||
except ValueError as exc:
|
||||
raise click.ClickException(str(exc)) from exc
|
||||
|
||||
params = win_autounattend.WinUnattendParams(
|
||||
admin_user=admin_user, admin_password=admin_password,
|
||||
computer_name=computer_name, product_key=product_key,
|
||||
image_index=image_index, locale_os=locale_os, locale_user=locale_user,
|
||||
keyboard=keyboard, timezone=timezone,
|
||||
)
|
||||
win_iso_patch.patch_iso(
|
||||
in_iso,
|
||||
autounattend_xml=win_autounattend.render_autounattend(params),
|
||||
post_install_ps1=win_autounattend.render_post_install(params),
|
||||
out_iso=out_iso,
|
||||
)
|
||||
click.echo(f"\nPatched ISO: {out_iso}")
|
||||
click.echo(f"Deploy with: ci template deploy-win --win-iso {out_iso}")
|
||||
|
||||
Reference in New Issue
Block a user