diff --git a/scripts/download-drivers.py b/scripts/download-drivers.py index 5b97978..999bb01 100755 --- a/scripts/download-drivers.py +++ b/scripts/download-drivers.py @@ -68,6 +68,53 @@ def ssh_cmd(host, cmd): +# Remote path resolver that is case-insensitive about EXISTING directories. +# +# Dell/GE manifests spell the same folder inconsistently ("OptiPlex" vs +# "Optiplex"). Windows does not care; the Samba share is backed by a +# case-sensitive Linux filesystem, so a blind "mkdir -p" creates a SECOND tree +# and the drivers split between them. That is exactly how the OptiPlex Micro +# 7020 package went missing: the manifest asked for OptiPlex/D13MLK while the +# 3.2 GB zip sat in Optiplex/D13MLK, so imaging installed no drivers at all - +# no NIC, no WiFi, and bulk enrollment could not reach the CDN. +# +# Walk the path one component at a time and reuse whatever is already there, +# whatever its case. Only genuinely new components get created. +CI_RESOLVE_SH = r""" +set -e +p="$1"; shift +for c in "$@"; do + [ -z "$c" ] && continue + if [ -d "$p/$c" ]; then + p="$p/$c" + else + m=$(ls -1 "$p" 2>/dev/null | awk -v c="$c" 'tolower($0)==tolower(c){print; exit}') + if [ -n "$m" ]; then p="$p/$m"; else mkdir -p "$p/$c"; p="$p/$c"; fi + fi +done +printf '%s' "$p" +""" + + +def mkdir_ci(host, target_dir): + """mkdir -p that reuses existing dirs differing only in case. + + Returns the path that actually exists on the server, which may differ in + case from target_dir. Callers must use the RETURNED path.""" + parts = [c for c in target_dir.split("/") if c] + if not parts: + return target_dir + base = "/" if target_dir.startswith("/") else "." + r = ssh_cmd(host, "bash -s -- '%s' %s <<'CIEOF'\n%s\nCIEOF" % ( + base, " ".join("'%s'" % c.replace("'", "'\''") for c in parts), CI_RESOLVE_SH)) + out = (r.stdout or "").strip() + if r.returncode != 0 or not out: + # Fall back to the old behaviour rather than skipping the download. + ssh_cmd(host, "mkdir -p '%s'" % target_dir) + return target_dir + return out + + def verify_sha256(filepath, expected): sha = hashlib.sha256() with open(filepath, "rb") as f: @@ -324,7 +371,9 @@ def process_download(args, url, filename, sha256, size, target_dir, label, tmpdi # Push zip to PXE server print(f" [{label}] Pushing to {target_dir}/{zip_name}...") - ssh_cmd(args.server, f"mkdir -p '{target_dir}'") + # Reuse an existing directory that differs only in case, or the drivers + # split across two trees and imaging silently installs none of them. + target_dir = mkdir_ci(args.server, target_dir) r = subprocess.run([ "rsync", "-a", "-e", f"sshpass -p {PXE_PASS} ssh -o StrictHostKeyChecking=no -o LogLevel=ERROR",