fix(deploy-win): extract ISO with 7z so no-prompt boot patch works
xorriso -extract reads the ISO9660 name tree, which on a Windows ISO has
no Rock Ridge — names come back upper-cased/truncated, so the
`efi/microsoft/boot/efisys_noprompt.bin` lookup failed ("missing from
ISO") and a repack from that tree would corrupt Windows' long file names.
Extract with 7z instead (reads the UDF/Joliet tree, preserving the real
lower-case paths), excluding 7z's synthetic `[BOOT]` dump. Verified
end-to-end on the cached 26100.32860 ISO: 7z extract → xorriso repack
yields a 5 GB ISO with autounattend.xml + post-install.ps1 at the root and
efisys_noprompt.bin as the UEFI El Torito image (no boot prompt).
Also: cap the Joliet volume id at 16 chars (silence xorriso warning), add
a 7z preflight check, and add p7zip-full to setup-host-linux.sh.
This commit is contained in:
+2
-1
@@ -188,7 +188,8 @@ fi
|
||||
# xorriso/genisoimage — repack ISO bootable / build seed ISO (deploy-linux)
|
||||
# cabextract — estrazione pacchetti .cab durante la conversione UUP
|
||||
# chntpw — richiesto dal converter UUP (uup_download_linux.sh)
|
||||
for pkg in aria2 wimtools xorriso genisoimage cabextract chntpw; do
|
||||
# p7zip-full — 7z: estrae l'ISO Windows (UDF/Joliet) per il repack patch
|
||||
for pkg in aria2 wimtools xorriso genisoimage cabextract chntpw p7zip-full; do
|
||||
if ! dpkg -s "$pkg" &>/dev/null; then
|
||||
echo " Installazione pacchetto $pkg..."
|
||||
apt-get install -y "$pkg" -qq
|
||||
|
||||
@@ -2174,6 +2174,7 @@ def template_deploy_win(
|
||||
(f"vmrun found: {vmrun}", bool(shutil.which(vmrun) or Path(vmrun).is_file())),
|
||||
(f"vmware-vdiskmanager found: {vdisk_mgr}", Path(vdisk_mgr).is_file()),
|
||||
("xorriso in PATH", shutil.which("xorriso") is not None),
|
||||
("7z in PATH", any(shutil.which(n) for n in ("7z", "7za", "7zr"))),
|
||||
(f"VMware Tools ISO: {tools_iso}", tools_iso.is_file()),
|
||||
]
|
||||
for label, ok in checks:
|
||||
|
||||
@@ -30,6 +30,14 @@ def _require_xorriso() -> str:
|
||||
return exe
|
||||
|
||||
|
||||
def _require_7z() -> str:
|
||||
for name in ("7z", "7za", "7zr"):
|
||||
exe = shutil.which(name)
|
||||
if exe is not None:
|
||||
return exe
|
||||
raise click.ClickException("7z not found. Install p7zip-full.")
|
||||
|
||||
|
||||
def _read_volid(base_iso: Path) -> str:
|
||||
"""Read the ISO volume id (label); fall back to a sane default."""
|
||||
label = ""
|
||||
@@ -42,7 +50,8 @@ def _read_volid(base_iso: Path) -> str:
|
||||
check=False,
|
||||
)
|
||||
label = proc.stdout.strip()
|
||||
return (label or "WINSERVER")[:32]
|
||||
# Joliet volume ids are capped at 16 chars; longer ids make xorriso warn.
|
||||
return (label or "WINSERVER")[:16]
|
||||
|
||||
|
||||
def _check_free_space(base_iso: Path, work_parent: Path) -> None:
|
||||
@@ -66,6 +75,7 @@ def patch_iso(
|
||||
) -> Path:
|
||||
"""Inject answer files + no-prompt boot into ``base_iso`` → ``out_iso``."""
|
||||
xorriso = _require_xorriso()
|
||||
sevenz = _require_7z()
|
||||
if not base_iso.is_file():
|
||||
raise click.ClickException(f"base ISO not found: {base_iso}")
|
||||
|
||||
@@ -79,12 +89,19 @@ def patch_iso(
|
||||
tree.mkdir(parents=True)
|
||||
|
||||
try:
|
||||
# Extract with 7z (reads the UDF/Joliet tree) so the long, lower-case
|
||||
# Windows paths survive. xorriso's -extract uses the ISO9660 name tree
|
||||
# (upper-case / truncated, no Rock Ridge on Windows ISOs), which both
|
||||
# breaks the efisys_noprompt.bin lookup and would corrupt Windows file
|
||||
# names in the repacked image. ``[BOOT]`` is 7z's synthetic El Torito
|
||||
# dump — excluded so it does not pollute the tree.
|
||||
click.echo(f" [iso] extracting base ISO: {base_iso.name}")
|
||||
subprocess.run(
|
||||
[xorriso, "-osirrox", "on", "-indev", str(base_iso), "-extract", "/", str(tree)],
|
||||
[sevenz, "x", str(base_iso), f"-o{tree}", "-y", "-bd", "-x![BOOT]"],
|
||||
check=True,
|
||||
stdout=subprocess.DEVNULL,
|
||||
)
|
||||
# Extracted files are read-only; make the tree writable so we can add files.
|
||||
# Extracted files may be read-only; make the tree writable so we can add files.
|
||||
subprocess.run(["chmod", "-R", "u+w", str(tree)], check=True)
|
||||
|
||||
efi_boot = tree / _EFI_BOOT_NOPROMPT
|
||||
|
||||
@@ -199,13 +199,15 @@ def test_patch_iso_happy(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> Non
|
||||
base.write_bytes(b"x" * 1000)
|
||||
out = tmp_path / "out.iso"
|
||||
monkeypatch.setattr(ip, "_require_xorriso", lambda: "xorriso")
|
||||
monkeypatch.setattr(ip, "_require_7z", lambda: "7z")
|
||||
monkeypatch.setattr(ip, "_read_volid", lambda _p: "WINSERVER")
|
||||
monkeypatch.setattr(ip, "_check_free_space", lambda *_a: None)
|
||||
|
||||
def fake_run(argv: list[str], **_kw: Any) -> subprocess.CompletedProcess[str]:
|
||||
# simulate xorriso -extract creating the efi boot file in the tree
|
||||
if "-extract" in argv:
|
||||
tree = Path(argv[-1])
|
||||
# simulate 7z extraction creating the efi boot file in the tree
|
||||
if argv[0] == "7z" and "x" in argv:
|
||||
outflag = next(a for a in argv if a.startswith("-o"))
|
||||
tree = Path(outflag[2:])
|
||||
boot = tree / ip._EFI_BOOT_NOPROMPT
|
||||
boot.parent.mkdir(parents=True, exist_ok=True)
|
||||
boot.write_bytes(b"boot")
|
||||
@@ -225,6 +227,7 @@ def test_patch_iso_missing_efi_boot(monkeypatch: pytest.MonkeyPatch, tmp_path: P
|
||||
base = tmp_path / "base.iso"
|
||||
base.write_bytes(b"x" * 1000)
|
||||
monkeypatch.setattr(ip, "_require_xorriso", lambda: "xorriso")
|
||||
monkeypatch.setattr(ip, "_require_7z", lambda: "7z")
|
||||
monkeypatch.setattr(ip, "_check_free_space", lambda *_a: None)
|
||||
monkeypatch.setattr(
|
||||
ip.subprocess, "run",
|
||||
|
||||
Reference in New Issue
Block a user