From c739e41e7e3f469c3bef2f466a50c58dacf2e5a3 Mon Sep 17 00:00:00 2001 From: Kroese Date: Fri, 24 Jul 2026 16:52:14 +0200 Subject: [PATCH] feat: Improve Windows media detection and selection (#1946) --- Dockerfile | 1 + src/answer.sh | 142 +++++++++++- src/define.sh | 578 ++++++++++++++++++++++++++++++++++++------------- src/image.sh | 273 ++++++++++++++++------- src/install.sh | 22 +- src/mido.sh | 265 ++++++++++++++++++++++- 6 files changed, 1025 insertions(+), 256 deletions(-) diff --git a/Dockerfile b/Dockerfile index 0166fccf..c8311586 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,6 +22,7 @@ RUN <.*<\/InstallFrom>/d' \ + -e '/.*<\/ProductKey>/d' \ + -e '//,/<\/InstallFrom>/d' \ + -e '//,/<\/ProductKey>/d' \ + "$source" > "$tmp"; then + rm -f "$tmp" + error "Failed to generate fallback answer file from $source!" + return 1 + fi + + if [ -n "$index" ]; then + if ! sed -i \ + '0,//{ //i\ + \ + \ + /IMAGE/INDEX\ + '"$index"'\ + \ + + }' "$tmp"; then + rm -f "$tmp" + error "Failed to select fallback image index $index!" + return 1 + fi + fi + + if ! markGeneratedXML "$tmp" || + ! xmllint --nonet --noout "$tmp"; then + rm -f "$tmp" + error "Generated fallback answer file is invalid!" + return 1 + fi + + if ! chmod 644 "$tmp" || ! mv -f "$tmp" "$target"; then + rm -f "$tmp" + error "Failed to create fallback answer file: $target" + return 1 + fi + + return 0 +} + setXML() { local file local index="${2:-}" + local target="/run/assets/$DETECTED.xml" + local custom_files=( "/custom.xml" "$STORAGE/custom.xml" @@ -334,6 +442,8 @@ setXML() { CUSTOM_XML="" + removeGeneratedXML "$target" || return 1 + if [ -d "${custom_files[0]}" ]; then error "The bind ${custom_files[0]} maps to a file that does not exist!" exit 67 @@ -347,6 +457,22 @@ setXML() { fi done + # Known Server editions normally share one answer file. When the image + # contains only one uniquely detected Server edition and no explicit + # EDITION was requested, generate a keyless answer file using that index. + if [ -n "$index" ] && [ -z "${EDITION:-}" ] && + [[ "${DETECTED,,}" == win20* && "${DETECTED,,}" != *-* ]] && + [ -s "$target" ]; then + + file="/run/assets/$DETECTED-selected.xml" + + removeGeneratedXML "$file" || return 1 + generateFallbackXML "$DETECTED-selected" "$index" || return 1 + + XML="$file" + return 0 + fi + file="$1" if [[ "${DETECTED,,}" == *"-eval" ]]; then @@ -356,7 +482,10 @@ setXML() { fi if [ ! -f "$file" ] || [ ! -s "$file" ]; then - file="/run/assets/$DETECTED.xml" + file="$target" + elif [[ "$file" != "$target" ]]; then + generateFallbackXML "$DETECTED" "$index" || return 1 + file="$target" fi [ -f "$file" ] && [ -s "$file" ] || return 1 @@ -710,13 +839,14 @@ updateXML() { fi if [ -n "${EDITION:-}" ]; then - case "${EDITION,,}" in - "core" ) edition="STANDARDCORE" ;; - * ) edition="${EDITION^^}" ;; - esac + + edition=$(normalizeServerEdition "$EDITION") || return 1 + edition="${edition//-/}" + edition="${edition^^}" edition=$(escapeXMLSed "$edition") || return 1 sed -i "s|SERVERSTANDARD|SERVER$edition|g" "$asset" || return 1 + fi if [ -n "${KEY:-}" ]; then diff --git a/src/define.sh b/src/define.sh index e1894e87..1efb1e73 100644 --- a/src/define.sh +++ b/src/define.sh @@ -54,11 +54,9 @@ parseVersion() { ;; "11l" | "11ltsc" | "ltsc11" | "win11l" | "win11-ltsc" | "win11x64-ltsc" ) VERSION="win11x64-enterprise-ltsc-eval" - SUGGEST="win11x64-ltsc" ;; "11i" | "11iot" | "iot11" | "win11i" | "win11-iot" | "win11x64-iot" ) VERSION="win11x64-enterprise-iot-eval" - SUGGEST="win11x64-iot" ;; "10" | "10p" | "win10" | "pro10" | "win10p" | "windows10" | "windows 10" ) VERSION="win10x64" @@ -68,11 +66,9 @@ parseVersion() { ;; "10l" | "10ltsc" | "ltsc10" | "win10l" | "win10-ltsc" | "win10x64-ltsc" ) VERSION="win10x64-enterprise-ltsc-eval" - SUGGEST="win10x64-ltsc" ;; "10i" | "10iot" | "iot10" | "win10i" | "win10-iot" | "win10x64-iot" ) VERSION="win10x64-enterprise-iot-eval" - SUGGEST="win10x64-iot" ;; "8" | "8p" | "81" | "81p" | "pro8" | "8.1" | "win8" | "win8p" | "win81" | "win81p" | "windows 8" ) VERSION="win81x64" @@ -82,7 +78,6 @@ parseVersion() { ;; "7" | "win7" | "windows7" | "windows 7" ) VERSION="win7x64" - SUGGEST="win7x64-ultimate" ;; "7u" | "win7u" | "windows7u" | "windows 7u" ) VERSION="win7x64-ultimate" @@ -92,7 +87,6 @@ parseVersion() { ;; "7x86" | "win7x86" | "win732" | "windows7x86" ) VERSION="win7x86" - SUGGEST="win7x86-ultimate" ;; "7ux86" | "7u32" | "win7x86-ultimate" ) VERSION="win7x86-ultimate" @@ -102,7 +96,6 @@ parseVersion() { ;; "vista" | "vs" | "6" | "winvista" | "windowsvista" | "windows vista" ) VERSION="winvistax64" - SUGGEST="winvistax64-ultimate" ;; "vistu" | "vu" | "6u" | "winvistu" ) VERSION="winvistax64-ultimate" @@ -112,7 +105,6 @@ parseVersion() { ;; "vistax86" | "vista32" | "6x86" | "winvistax86" | "windowsvistax86" ) VERSION="winvistax86" - SUGGEST="winvistax86-ultimate" ;; "vux86" | "vu32" | "winvistax86-ultimate" ) VERSION="winvistax86-ultimate" @@ -164,20 +156,46 @@ parseVersion() { ;; "tiny10" | "tiny 10" ) VERSION="tiny10" - SUGGEST="win10x64-ltsc" ;; esac - if [ -z "$SUGGEST" ]; then - case "${VERSION,,}" in - *"-enterprise-ltsc-eval" ) - SUGGEST="${VERSION%-enterprise-ltsc-eval}-ltsc" ;; - *"-enterprise-iot-eval" ) - SUGGEST="${VERSION%-enterprise-iot-eval}-iot" ;; - *"-eval" ) - SUGGEST="${VERSION%-eval}" ;; - esac - fi + SUGGEST=$(getSuggestedVersion "$VERSION") + + return 0 +} + +getSuggestedVersion() { + + local id="${1,,}" + + [[ "$id" == http* ]] && return 0 + + case "$id" in + "win10x64" | "win11x64" ) + echo "$id" + ;; + "win7x64" | "win7x86" | "winvistax64" | "winvistax86" ) + echo "$id-ultimate" + ;; + "tiny10" ) + echo "win10x64-ltsc" + ;; + *"-enterprise-ltsc-eval" ) + echo "${id%-enterprise-ltsc-eval}-ltsc" + ;; + *"-enterprise-iot-eval" ) + echo "${id%-enterprise-iot-eval}-iot" + ;; + *"-enterprise-ltsc" ) + echo "${id%-enterprise-ltsc}-ltsc" + ;; + *"-enterprise-iot" ) + echo "${id%-enterprise-iot}-iot" + ;; + *"-eval" ) + echo "${id%-eval}" + ;; + esac return 0 } @@ -388,7 +406,8 @@ parseLanguage() { "danish" | "dk" | "danske" ) LANGUAGE="da" ;; "dutch" | "nederlands" ) LANGUAGE="nl" ;; "english" ) LANGUAGE="en" ;; - "british" | "gb" ) LANGUAGE="en-gb" ;; "estonian" | "eesti" ) LANGUAGE="et" ;; + "british" | "gb" ) LANGUAGE="en-gb" ;; + "estonian" | "eesti" ) LANGUAGE="et" ;; "finnish" | "suomi" ) LANGUAGE="fi" ;; "french" | "français" | "francais" ) LANGUAGE="fr" ;; "german" | "deutsch" ) LANGUAGE="de" ;; @@ -491,59 +510,103 @@ printVariant() { return 0 } +formatEdition() { + + local edition="${1//-/ }" + local result="" word + + for word in $edition; do + if [ "$word" == "for" ]; then + word="for" + elif [ "${#word}" -eq 1 ]; then + word="${word^^}" + else + word="${word^}" + fi + + result+="${result:+ }$word" + done + + echo "$result" + return 0 +} + printEdition() { local id="$1" local desc="$2" local show_eval="${3:-N}" - local result="" edition="" + local normalized="${id,,}" + local result="" edition="" suffix="" result=$(printVersion "$id" "x") [[ "$result" == "x" ]] && echo "$desc" && return 0 - case "${id,,}" in - *"-home" ) - edition="Home" - ;; - *"-starter" ) - edition="Starter" - ;; - *"-ultimate" ) - edition="Ultimate" - ;; - *"-enterprise" | *"-enterprise-eval" ) - edition="Enterprise" - ;; - *"-education" ) - edition="Education" - ;; - *"-hv" ) - edition="2019" - ;; - *"-iot" | *"-iot-eval" ) - edition="IoT Enterprise LTSC" - ;; - *"-ltsc" | *"-ltsc-eval" ) - edition="Enterprise LTSC" - ;; - "win7"* ) - edition="Professional" - ;; - "win8"* | "win10"* | "win11"* ) - edition="Pro" + normalized="${normalized%-eval}" + + case "$normalized" in + "winvista"* | "win7"* | "win8"* | "win10"* | "win11"* ) + [[ "$normalized" == *"-"* ]] && suffix="${normalized#*-}" + + case "$suffix" in + "" ) + case "$normalized" in + "winvista"* ) edition="Business" ;; + "win7"* ) edition="Professional" ;; + * ) edition="Pro" ;; + esac + ;; + "home" ) + edition="Home" + ;; + "starter" ) + edition="Starter" + ;; + "ultimate" ) + edition="Ultimate" + ;; + "enterprise" ) + edition="Enterprise" + ;; + "education" ) + edition="Education" + ;; + "n" ) + case "$normalized" in + "win7"* ) edition="Professional N" ;; + * ) edition="Pro N" ;; + esac + ;; + "iot" | "enterprise-iot" ) + edition="IoT Enterprise LTSC" + ;; + "ltsc" | "enterprise-ltsc" ) + edition="Enterprise LTSC" + ;; + * ) + edition=$(formatEdition "$suffix") + ;; + esac ;; "winxp"* ) edition="Professional" ;; - "winvista"* ) - edition="Business" + "win2019-hv"* ) + edition="2019" ;; - "win2025"* | "win2022"* | "win2019"* | "win2016"* | "win2012"* | "win2008"* | "win2003"* ) - case "${EDITION^^}" in - *"DATACENTER"* ) edition="Datacenter" ;; - "CORE" | "STANDARDCORE" ) edition="Core" ;; - * ) edition="Standard" ;; - esac + "win2025"* | "win2022"* | "win2019"* | "win2016"* | \ + "win2012"* | "win2008"* | "win2003"* ) + [[ "$normalized" == *"-"* ]] && suffix="${normalized#*-}" + + if [ -n "$suffix" ]; then + edition=$(formatEdition "$suffix") + else + case "${EDITION^^}" in + *"DATACENTER"* ) edition="Datacenter" ;; + "CORE" | "STANDARDCORE" ) edition="Core" ;; + * ) edition="Standard" ;; + esac + fi ;; esac @@ -678,9 +741,223 @@ fromName() { return 0 } +normalizeEdition() { + + local source="${1,,}" + local edition + + source="${source//evaluation/}" + + source=$(printf '%s' "$source" | + uconv -x 'Any-Latin; Latin-ASCII' 2>/dev/null) || return 1 + + edition=$(sed -E \ + -e 's/[^a-z0-9]+/-/g' \ + -e 's/^-+//' \ + -e 's/-+$//' \ + <<< "$source") + + echo "$edition" + return 0 +} + +normalizeEditionID() { + + local edition + local id="$2" + + edition=$(normalizeEdition "$1") + + case "$edition" in + "pro" | "professional" | "business" ) + edition="" + ;; + "pro-n" | "professional-n" ) + edition="n" + ;; + esac + + case "${id,,}" in + "winvista"* ) + case "$edition" in + "business" ) + edition="" + ;; + "home-basic" | "home-premium" ) + edition="home" + ;; + esac + ;; + "win7"* ) + case "$edition" in + "home-basic" | "home-premium" ) + edition="home" + ;; + esac + ;; + "win10"* | "win11"* ) + case "$edition" in + "iot-enterprise-ltsc" | \ + "iot-enterprise-ltsc-"[0-9][0-9][0-9][0-9] ) + edition="iot" + ;; + "enterprise-ltsc" | \ + "enterprise-ltsc-"[0-9][0-9][0-9][0-9] ) + edition="ltsc" + ;; + esac + ;; + esac + + echo "$edition" + return 0 +} + +getEditionID() { + + local name="${1,,}" + local id="${2,,}" + local edition="" + + case "$id" in + "winvista"* ) + edition="${name#*vista}" + ;; + "win7"* ) + edition="${name#*7}" + ;; + "win8"* ) + if [[ "$name" == *"8.1"* ]]; then + edition="${name#*8.1}" + else + edition="${name#*8}" + fi + ;; + "win10"* ) + edition="${name#*10}" + ;; + "win11"* ) + edition="${name#*11}" + ;; + * ) + return 1 + ;; + esac + + edition=$(normalizeEditionID "$edition" "$id") + + echo "$edition" + return 0 +} + +normalizeServerEdition() { + + local edition + + edition=$(normalizeEdition "$1") || return 1 + edition="${edition#r2-}" + + case "$edition" in + "core" | "core-installation" | "server-core-installation" ) + edition="standard-core" + ;; + "desktop-experience" | "server-with-a-gui" | "full-installation" ) + edition="standard" + ;; + *"-server-core-installation" ) + edition="${edition%-server-core-installation}-core" + ;; + *"-core-installation" ) + edition="${edition%-core-installation}-core" + ;; + *"-desktop-experience" ) + edition="${edition%-desktop-experience}" + ;; + *"-server-with-a-gui" ) + edition="${edition%-server-with-a-gui}" + ;; + *"-full-installation" ) + edition="${edition%-full-installation}" + ;; + esac + + edition="${edition#server-}" + edition="${edition#server}" + + [ "$edition" == "core" ] && edition="standard-core" + + echo "$edition" + return 0 +} + +normalizeServerEditionID() { + + local edition + + edition=$(normalizeServerEdition "$1") || return 1 + + case "$edition" in + "" | "core" | \ + "standard" | "standard-core" | "standardcore" | \ + "serverstandard" | "serverstandardcore" | \ + "datacenter" | "datacenter-core" | "datacentercore" | \ + "serverdatacenter" | "serverdatacentercore" | \ + "enterprise" | "enterprise-core" | "enterprisecore" | \ + "serverenterprise" | "serverenterprisecore" | \ + "web" | "web-core" | "webcore" | \ + "serverweb" | "serverwebcore" | \ + "foundation" | "serverfoundation" | \ + "essentials" | "serveressentials" ) + edition="" + ;; + esac + + echo "$edition" + return 0 +} + +getServerEditionID() { + + local name="${1,,}" + local id="${2,,}" + local edition="" + + case "$id" in + "win2025"* ) + edition="${name#*server 2025}" + ;; + "win2022"* ) + edition="${name#*server 2022}" + ;; + "win2019"* ) + edition="${name#*server 2019}" + ;; + "win2016"* ) + edition="${name#*server 2016}" + ;; + "win2012"* ) + edition="${name#*server 2012}" + ;; + "win2008"* ) + edition="${name#*server 2008}" + ;; + "win2003"* ) + edition="${name#*server 2003}" + ;; + * ) + return 1 + ;; + esac + + edition=$(normalizeServerEditionID "$edition") + + echo "$edition" + return 0 +} + getVersion() { - local id + local id edition local name="$1" local arch="$2" local evaluation="" @@ -689,34 +966,20 @@ getVersion() { [[ "${name,,}" == *"evaluation"* ]] && evaluation="-eval" case "${id,,}" in - "win7"* | "winvista"* ) - case "${name,,}" in - *" home"* ) id="$id-home" ;; - *" starter"* ) id="$id-starter" ;; - *" ultimate"* ) id="$id-ultimate" ;; - *" enterprise"* ) id="$id-enterprise$evaluation" ;; - esac - ;; - "win8"* ) - case "${name,,}" in - *" enterprise"* ) id="$id-enterprise$evaluation" ;; - esac - ;; - "win10"* | "win11"* ) - case "${name,,}" in - *" iot"* ) id="$id-iot$evaluation" ;; - *" ltsc"* ) id="$id-ltsc$evaluation" ;; - *" home"* ) id="$id-home" ;; - *" education"* ) id="$id-education" ;; - *" enterprise"* ) id="$id-enterprise$evaluation" ;; - esac + "winvista"* | "win7"* | "win8"* | "win10"* | "win11"* ) + if edition=$(getEditionID "$name" "$id"); then + [ -n "$edition" ] && id+="-$edition" + [ -n "$evaluation" ] && id+="$evaluation" + fi ;; "win2025"* | "win2022"* | "win2019"* | "win2016"* | \ "win2012"* | "win2008"* | "win2003"* ) - case "${name,,}" in - *"hyper-v server"* ) id="$id-hv" ;; - *"evaluation"* ) id="$id-eval" ;; - esac + if [[ "${name,,}" == *"hyper-v server"* ]]; then + id+="-hv" + elif edition=$(getServerEditionID "$name" "$id"); then + [ -n "$edition" ] && id+="-$edition" + [ -n "$evaluation" ] && id+="$evaluation" + fi ;; esac @@ -724,6 +987,21 @@ getVersion() { return 0 } +switchEdition() { + + local -n id="$1" + + [[ "${id,,}" == *"-eval" ]] || return 1 + + id="${id::-5}" + + if ! enabled "${DETECTED_ORG:-}"; then + DETECTED="${SUGGEST:-$id}" + fi + + return 0 +} + getMido() { local id="$1" @@ -832,72 +1110,72 @@ getLink1() { [[ "${lang,,}" != "en" && "${lang,,}" != "en-us" ]] && return 0 case "${id,,}" in - "win11x64" | "win11x64-enterprise" | "win11x64-enterprise-eval" ) + "win11x64" | "win11x64-enterprise" ) size=6927149056 sum="f5ffe9313eebc6299fba9e6eeb2971007264e6c6be013073a89b5ae9bd85bfb3" url="11/en-us_windows_11_25h2_x64.iso" ;; - "win11x64-ltsc" | "win11x64-enterprise-ltsc" | "win11x64-enterprise-ltsc-eval" ) + "win11x64-ltsc" | "win11x64-enterprise-ltsc" ) size=5144817664 sum="4f59662a96fc1da48c1b415d6c369d08af55ddd64e8f1c84e0166d9e50405d7a" url="11/X23-81951_26100.1742.240906-0331.ge_release_svc_refresh_CLIENT_ENTERPRISES_OEM_x64FRE_en-us.iso" ;; - "win11x64-iot" | "win11x64-enterprise-iot" | "win11x64-enterprise-iot-eval" ) + "win11x64-iot" | "win11x64-enterprise-iot" ) size=5144817664 sum="4f59662a96fc1da48c1b415d6c369d08af55ddd64e8f1c84e0166d9e50405d7a" url="11/X23-81951_26100.1742.240906-0331.ge_release_svc_refresh_CLIENT_ENTERPRISES_OEM_x64FRE_en-us.iso" ;; - "win10x64" | "win10x64-enterprise" | "win10x64-enterprise-eval" ) + "win10x64" | "win10x64-enterprise" ) size=5723299840 sum="316f718f21fc9b386d81dadd62dc60268a1cfd65b184ac6a052875a454c3431b" url="10/en-us_windows_10_22h2_x64.iso" ;; - "win10x64-ltsc" | "win10x64-enterprise-ltsc" | "win10x64-enterprise-ltsc-eval" ) + "win10x64-ltsc" | "win10x64-enterprise-ltsc" ) size=4899461120 sum="c90a6df8997bf49e56b9673982f3e80745058723a707aef8f22998ae6479597d" url="10/en-us_windows_10_enterprise_ltsc_2021_x64_dvd_d289cf96.iso" ;; - "win10x64-iot" | "win10x64-enterprise-iot" | "win10x64-enterprise-iot-eval" ) + "win10x64-iot" | "win10x64-enterprise-iot" ) size=4851668992 sum="a0334f31ea7a3e6932b9ad7206608248f0bd40698bfb8fc65f14fc5e4976c160" url="10/en-us_windows_10_iot_enterprise_ltsc_2021_x64_dvd_257ad90f.iso" - ;; + ;; "win81x64" ) size=4320526336 sum="d8333cf427eb3318ff6ab755eb1dd9d433f0e2ae43745312c1cd23e83ca1ce51" url="8.x/8.1/en_windows_8.1_with_update_x64_dvd_6051480.iso" ;; - "win81x64-enterprise" | "win81x64-enterprise-eval" ) + "win81x64-enterprise" ) size=4139163648 sum="c3c604c03677504e8905090a8ce5bb1dde76b6fd58e10f32e3a25bef21b2abe1" url="8.x/8.1/en_windows_8.1_enterprise_with_update_x64_dvd_6054382.iso" ;; - "win2025" | "win2025-eval" ) + "win2025" ) size=7571058688 sum="d273d0a85565ffbc06a3d46313f619103e2830a3373306ddbb9a08b8824f509d" url="server/2025/en-us_windows_server_2025_updated_oct_2025_x64_dvd_6c0c5aa8.iso" ;; - "win2022" | "win2022-eval" ) + "win2022" ) size=6023239680 sum="5d6d91efa972cbdd6701d78db1dcf6a34c7024ca931c1718e7cb3d0c6dd54e88" url="server/2022/en-us_windows_server_2022_updated_oct_2025_x64_dvd_26e9af36.iso" ;; - "win2019" | "win2019-eval" ) + "win2019" ) size=5575774208 sum="0067afe7fdc4e61f677bd8c35a209082aa917df9c117527fc4b2b52a447e89bb" url="server/2019/en-us_windows_server_2019_updated_aug_2021_x64_dvd_a6431a28.iso" ;; - "win2016" | "win2016-eval" ) + "win2016" ) size=6006587392 sum="af06e5483c786c023123e325cea4775050324d9e1366f46850b515ae43f764be" url="server/2016/en_windows_server_2016_updated_feb_2018_x64_dvd_11636692.iso" ;; - "win2012r2" | "win2012r2-eval" ) + "win2012r2" ) size=5397889024 sum="f351e89eb88a96af4626ceb3450248b8573e3ed5924a4e19ea891e6003b62e4e" url="server/2012r2/en_windows_server_2012_r2_with_update_x64_dvd_6052708-004.iso" ;; - "win2008r2" | "win2008r2-eval" ) + "win2008r2" ) size=3166584832 sum="dfd9890881b7e832a927c38310fb415b7ea62ac5a896671f2ce2a111998f0df8" url="server/2008r2/en_windows_server_2008_r2_with_sp1_x64_dvd_617601-018.iso" @@ -907,7 +1185,7 @@ getLink1() { sum="0b738b55a5ea388ad016535a5c8234daf2e5715a0638488ddd8a228a836055a1" url="7/en_windows_7_with_sp1_x64.iso" ;; - "win7x64-enterprise" | "win7x64-enterprise-eval" ) + "win7x64-enterprise" ) size=3182604288 sum="ee69f3e9b86ff973f632db8e01700c5724ef78420b175d25bae6ead90f6805a7" url="7/en_windows_7_enterprise_with_sp1_x64_dvd_u_677651.iso" @@ -917,7 +1195,7 @@ getLink1() { sum="99f3369c90160816be07093dbb0ac053e0a84e52d6ed1395c92ae208ccdf67e5" url="7/en_windows_7_with_sp1_x86.iso" ;; - "win7x86-enterprise" | "win7x86-enterprise-eval" ) + "win7x86-enterprise" ) size=2434502656 sum="8bdd46ff8cb8b8de9c4aba02706629c8983c45e87da110e64e13be17c8434dad" url="7/en_windows_7_enterprise_with_sp1_x86_dvd_u_677710.iso" @@ -976,17 +1254,17 @@ getLink2() { sum="d8333cf427eb3318ff6ab755eb1dd9d433f0e2ae43745312c1cd23e83ca1ce51" url="Windows%208.1%20with%20Update/en_windows_8.1_with_update_x64_dvd_6051480.iso" ;; - "win81x64-enterprise" | "win81x64-enterprise-eval" ) + "win81x64-enterprise" ) size=4139163648 sum="c3c604c03677504e8905090a8ce5bb1dde76b6fd58e10f32e3a25bef21b2abe1" url="Windows%208.1%20with%20Update/en_windows_8.1_enterprise_with_update_x64_dvd_6054382.iso" ;; - "win2012r2" | "win2012r2-eval" ) + "win2012r2" ) size=5397889024 sum="f351e89eb88a96af4626ceb3450248b8573e3ed5924a4e19ea891e6003b62e4e" url="Windows%20Server%202012%20R2%20with%20Update/en_windows_server_2012_r2_with_update_x64_dvd_6052708.iso" ;; - "win2008r2" | "win2008r2-eval" ) + "win2008r2" ) size=3166584832 sum="dfd9890881b7e832a927c38310fb415b7ea62ac5a896671f2ce2a111998f0df8" url="Windows%20Server%202008%20R2/en_windows_server_2008_r2_with_sp1_x64_dvd_617601.iso" @@ -996,7 +1274,7 @@ getLink2() { sum="36f4fa2416d0982697ab106e3a72d2e120dbcdb6cc54fd3906d06120d0653808" url="Windows%207/en_windows_7_ultimate_with_sp1_x64_dvd_u_677332.iso" ;; - "win7x64-enterprise" | "win7x64-enterprise-eval" ) + "win7x64-enterprise" ) size=3182604288 sum="ee69f3e9b86ff973f632db8e01700c5724ef78420b175d25bae6ead90f6805a7" url="Windows%207/en_windows_7_enterprise_with_sp1_x64_dvd_u_677651.iso" @@ -1006,7 +1284,7 @@ getLink2() { sum="e2c009a66d63a742941f5087acae1aa438dcbe87010bddd53884b1af6b22c940" url="Windows%207/en_windows_7_ultimate_with_sp1_x86_dvd_u_677460.iso" ;; - "win7x86-enterprise" | "win7x86-enterprise-eval" ) + "win7x86-enterprise" ) size=2434502656 sum="8bdd46ff8cb8b8de9c4aba02706629c8983c45e87da110e64e13be17c8434dad" url="Windows%207/en_windows_7_enterprise_with_sp1_x86_dvd_u_677710.iso" @@ -1065,57 +1343,37 @@ getLink3() { [[ "${lang,,}" != "en" && "${lang,,}" != "en-us" ]] && return 0 case "${id,,}" in - "nano11" ) - size=2463565824 - sum="a1e0614372768cbe2d24de74b78a4a97bc1017ea5080dfed1d2125e4a527eb1a" - url="nano11_25h2/nano11%2025h2.iso" - ;; - "core11" ) - size=3304132608 - sum="c0e0252b24144b8defb6c7ded2bc09f9297daf1fb8369b16c5b85382331eb47f" - url="tiny11_25H2/tiny11core_25H2_Nov25.iso" - ;; - "tiny11" ) - size=5730246656 - sum="7b24815845684add7250808b3b0027ba4e94cf52c62e9ef40d5b965dd304d6ca" - url="tiny11_25H2/tiny11_25H2_Nov25.iso" - ;; - "tiny10" ) - size=3839819776 - sum="a11116c0645d892d6a5a7c585ecc1fa13aa66f8c7cc6b03bf1f27bd16860cc35" - url="tiny-10-23-h2/tiny10%20x64%2023h2.iso" - ;; "win11x64" ) size=7736125440 sum="d141f6030fed50f75e2b03e1eb2e53646c4b21e5386047cb860af5223f102a32" url="W11x64_26200.6584/26200.6584.250915-1905.25h2_ge_release_svc_refresh_CLIENT_CONSUMER_x64FRE_en-us.iso" ;; - "win11x64-enterprise" | "win11x64-enterprise-eval" ) + "win11x64-enterprise" ) size=7620513792 sum="2b65df49334b64e9341dc404e9c527bf1b2a9a105e95314a347fd29ac9900581" url="massgrave.dev-windows-x64-and-x86-archive/en-us_windows_11_business_editions_version_25h2_x64_dvd_41c521e7.iso" ;; - "win11x64-ltsc" | "win11x64-enterprise-ltsc" | "win11x64-enterprise-ltsc-eval" ) + "win11x64-ltsc" | "win11x64-enterprise-ltsc" ) size=5144817664 sum="4f59662a96fc1da48c1b415d6c369d08af55ddd64e8f1c84e0166d9e50405d7a" url="Windows11LTSC/X23-81951_26100.1742.240906-0331.ge_release_svc_refresh_CLIENT_ENTERPRISES_OEM_x64FRE_en-us.iso" ;; - "win11x64-iot" | "win11x64-enterprise-iot" | "win11x64-enterprise-iot-eval" ) + "win11x64-iot" | "win11x64-enterprise-iot" ) size=5144817664 sum="4f59662a96fc1da48c1b415d6c369d08af55ddd64e8f1c84e0166d9e50405d7a" url="Windows11LTSC/X23-81951_26100.1742.240906-0331.ge_release_svc_refresh_CLIENT_ENTERPRISES_OEM_x64FRE_en-us.iso" ;; - "win10x64" | "win10x64-enterprise" | "win10x64-enterprise-eval" ) + "win10x64" | "win10x64-enterprise" ) size=6985445376 sum="2c23bc8b95a9314f15ebff881dcbea49651f52a96a0327d7aaf523aa66043765" - url="windows_10_version_2004/Windows%2010%2C%20version%2022H2/Updated%20October%202025%20%2819045.6456%29/en-us_windows_10_business_editions_version_22h2_updated_oct_2025_x64_dvd_d2eef4b0.iso" + url="windows-10-business-editions-version-22h2-updated-oct-2025-en-us/en-us_windows_10_business_editions_version_22h2_updated_oct_2025_x64_dvd_d2eef4b0.iso" ;; - "win10x64-ltsc" | "win10x64-enterprise-ltsc" | "win10x64-enterprise-ltsc-eval" ) + "win10x64-ltsc" | "win10x64-enterprise-ltsc" ) size=4899461120 sum="c90a6df8997bf49e56b9673982f3e80745058723a707aef8f22998ae6479597d" url="en-us_windows_10_enterprise_ltsc_2021_x64_dvd_d289cf96_202302/en-us_windows_10_enterprise_ltsc_2021_x64_dvd_d289cf96.iso" ;; - "win10x64-iot" | "win10x64-enterprise-iot" | "win10x64-enterprise-iot-eval" ) + "win10x64-iot" | "win10x64-enterprise-iot" ) size=4851668992 sum="a0334f31ea7a3e6932b9ad7206608248f0bd40698bfb8fc65f14fc5e4976c160" url="en-us_windows_10_iot_enterprise_ltsc_2021_x64_dvd_257ad90f_202411/en-us_windows_10_iot_enterprise_ltsc_2021_x64_dvd_257ad90f.iso" @@ -1125,37 +1383,37 @@ getLink3() { sum="d8333cf427eb3318ff6ab755eb1dd9d433f0e2ae43745312c1cd23e83ca1ce51" url="en_windows_8.1_with_update_x64_dvd_6051480/en_windows_8.1_with_update_x64_dvd_6051480.iso" ;; - "win81x64-enterprise" | "win81x64-enterprise-eval" ) + "win81x64-enterprise" ) size=4139163648 sum="c3c604c03677504e8905090a8ce5bb1dde76b6fd58e10f32e3a25bef21b2abe1" url="en_windows_8.1_enterprise_with_update_x64_dvd/en_windows_8.1_enterprise_with_update_x64_dvd_6054382.iso" ;; - "win2025" | "win2025-eval" ) + "win2025" ) size=8145395712 sum="f3e277e75acdb793e6f08f4880b514ae0046cedf618c22f727890e54367075e6" url="massgrave.dev-windows-x64-and-x86-archive/en-us_windows_server_2025_updated_dec_2025_x64_dvd_c54ab58b.iso" ;; - "win2022" | "win2022-eval" ) + "win2022" ) size=6023239680 sum="5d6d91efa972cbdd6701d78db1dcf6a34c7024ca931c1718e7cb3d0c6dd54e88" url="massgrave.dev-windows-x64-and-x86-archive/en-us_windows_server_2022_updated_oct_2025_x64_dvd_26e9af36.iso" ;; - "win2019" | "win2019-eval" ) + "win2019" ) size=5651695616 sum="ea247e5cf4df3e5829bfaaf45d899933a2a67b1c700a02ee8141287a8520261c" url="massgrave.dev-windows-x64-and-x86-archive/en-us_windows_server_2019_x64_dvd_f9475476.iso" ;; - "win2016" | "win2016-eval" ) + "win2016" ) size=6006587392 sum="af06e5483c786c023123e325cea4775050324d9e1366f46850b515ae43f764be" url="en_windows_server_2016_updated_feb_2018_x64_dvd_11636692/en_windows_server_2016_updated_feb_2018_x64_dvd_11636692.iso" ;; - "win2012r2" | "win2012r2-eval" ) + "win2012r2" ) size=5397889024 sum="f351e89eb88a96af4626ceb3450248b8573e3ed5924a4e19ea891e6003b62e4e" url="en_windows_server_2012_r2_with_update_x64_dvd_6052708_202006/en_windows_server_2012_r2_with_update_x64_dvd_6052708.iso" ;; - "win2008r2" | "win2008r2-eval" ) + "win2008r2" ) size=3166584832 sum="dfd9890881b7e832a927c38310fb415b7ea62ac5a896671f2ce2a111998f0df8" url="en_windows_server_2008_r2_with_sp1_x64_dvd_617601_202006/en_windows_server_2008_r2_with_sp1_x64_dvd_617601.iso" @@ -1165,7 +1423,7 @@ getLink3() { sum="36f4fa2416d0982697ab106e3a72d2e120dbcdb6cc54fd3906d06120d0653808" url="win7-ult-sp1-english/Win7_Ult_SP1_English_x64.iso" ;; - "win7x64-enterprise" | "win7x64-enterprise-eval" ) + "win7x64-enterprise" ) size=3182604288 sum="ee69f3e9b86ff973f632db8e01700c5724ef78420b175d25bae6ead90f6805a7" url="en_windows_7_enterprise_with_sp1_x64_dvd_u_677651_202006/en_windows_7_enterprise_with_sp1_x64_dvd_u_677651.iso" @@ -1175,7 +1433,7 @@ getLink3() { sum="e2c009a66d63a742941f5087acae1aa438dcbe87010bddd53884b1af6b22c940" url="win7-ult-sp1-english/Win7_Ult_SP1_English_x32.iso" ;; - "win7x86-enterprise" | "win7x86-enterprise-eval" ) + "win7x86-enterprise" ) size=2434502656 sum="8bdd46ff8cb8b8de9c4aba02706629c8983c45e87da110e64e13be17c8434dad" url="en_windows_7_enterprise_with_sp1_x86_dvd_u_677710_202006/en_windows_7_enterprise_with_sp1_x86_dvd_u_677710.iso" @@ -1220,6 +1478,26 @@ getLink3() { sum="e3816f6e80b66ff686ead03eeafffe9daf020a5e4717b8bd4736b7c51733ba22" url="MicrosoftWindows2000BuildCollection/5.00.2195.6717_x86fre_client-professional_retail_en-us-ZRMPFPP_EN.iso" ;; + "nano11" ) + size=2463565824 + sum="a1e0614372768cbe2d24de74b78a4a97bc1017ea5080dfed1d2125e4a527eb1a" + url="nano11_25h2/nano11%2025h2.iso" + ;; + "core11" ) + size=3304132608 + sum="c0e0252b24144b8defb6c7ded2bc09f9297daf1fb8369b16c5b85382331eb47f" + url="tiny11_25H2/tiny11core_25H2_Nov25.iso" + ;; + "tiny11" ) + size=5730246656 + sum="7b24815845684add7250808b3b0027ba4e94cf52c62e9ef40d5b965dd304d6ca" + url="tiny11_25H2/tiny11_25H2_Nov25.iso" + ;; + "tiny10" ) + size=3839819776 + sum="a11116c0645d892d6a5a7c585ecc1fa13aa66f8c7cc6b03bf1f27bd16860cc35" + url="tiny-10-23-h2/tiny10%20x64%2023h2.iso" + ;; esac case "${ret,,}" in @@ -1296,13 +1574,10 @@ isESD() { disabled "${ESD:-}" && return 1 case "${id,,}" in - "win11${PLATFORM,,}" | "win10${PLATFORM,,}" ) - return 0 - ;; - "win11${PLATFORM,,}-enterprise" | "win11${PLATFORM,,}-enterprise-eval") - return 0 - ;; - "win10${PLATFORM,,}-enterprise" | "win10${PLATFORM,,}-enterprise-eval" ) + "win11${PLATFORM,,}" | \ + "win10${PLATFORM,,}" | \ + "win11${PLATFORM,,}-enterprise" | \ + "win10${PLATFORM,,}-enterprise" ) return 0 ;; esac @@ -1316,9 +1591,12 @@ validVersion() { local lang="$2" local url i=0 - isESD "$id" "$lang" && return 0 isMido "$id" "$lang" && return 0 + [[ "${id,,}" == *"-eval" ]] && id="${id::-5}" + + isESD "$id" "$lang" && return 0 + for ((i=1;i<=MIRRORS;i++)); do url=$(getLink "$i" "$id" "$lang") diff --git a/src/image.sh b/src/image.sh index ea5b5d31..dfc116b4 100644 --- a/src/image.sh +++ b/src/image.sh @@ -8,13 +8,24 @@ getPlatform() { local platform="x64" local arch - arch=$(sed -n "/$tag/{s/.*<$tag>\(.*\)<\/$tag>.*/\1/;p}" <<< "$xml") + local -a arches=() - case "${arch,,}" in - "0" ) platform="x86" ;; - "9" ) platform="x64" ;; - "12" ) platform="arm64" ;; - esac + mapfile -t arches < <( + sed -n "/$tag/{s/.*<$tag>\(.*\)<\/$tag>.*/\1/;p}" <<< "$xml" | + sort -u + ) + + if [ "${#arches[@]}" -gt 1 ]; then + platform="mixed" + elif [ "${#arches[@]}" -eq 1 ]; then + arch="${arches[0]}" + + case "${arch,,}" in + "0" ) platform="x86" ;; + "9" ) platform="x64" ;; + "12" ) platform="arm64" ;; + esac + fi echo "$platform" return 0 @@ -31,6 +42,10 @@ checkPlatform() { "x86" ) compat="x64" ;; "x64" ) compat="$platform" ;; "arm64" ) compat="$platform" ;; + "mixed" ) + error "Windows images with mixed architectures are not supported!" + return 1 + ;; * ) compat="${PLATFORM,,}" ;; esac @@ -86,10 +101,11 @@ hasVersion() { fi fi - # Client editions without a dedicated template can use the generic - # template. updateImage() makes that copy edition-neutral. + # Editions without a dedicated template can use the generic template. case "${selected_id,,}" in - "win7"* | "win8"* | "win10"* | "win11"* | "winvista"* ) + "win7"* | "win8"* | "win10"* | "win11"* | "winvista"* | \ + "win2003"* | "win2008"* | "win2012"* | "win2016"* | \ + "win2019"* | "win2022"* | "win2025"* ) file="/run/assets/${selected_id%%-*}.xml" if [ -s "$file" ]; then @@ -104,19 +120,80 @@ hasVersion() { return 1 } -selectVersion() { +getVersionPriority() { - local tag="$1" - local xml="$2" - local platform="$3" - local suggested="${4:-}" + local id="${1%-eval}" + local base="$2" + local edition="${id#"$base"}" - local name id base prefer match suffix actual + edition="${edition#-}" + + case "$edition" in + "iot" | "iot-"* | "enterprise-iot" | "enterprise-iot-"* ) + echo "iot" + ;; + "ltsc" | "ltsc-"* | "enterprise-ltsc" | "enterprise-ltsc-"* ) + echo "ltsc" + ;; + "enterprise" | "enterprise-"* ) + echo "enterprise" + ;; + "ultimate" | "ultimate-"* ) + echo "ultimate" + ;; + "education" | "education-"* | "pro-education" | "pro-education-"* ) + echo "education" + ;; + "home" | "home-"* ) + echo "home" + ;; + "starter" | "starter-"* ) + echo "starter" + ;; + "hv" | "hv-"* ) + echo "hv" + ;; + "" | "n" | "pro" | "pro-"* | "professional" | "professional-"* | \ + "business" | "business-"* ) + echo "default" + ;; + * ) + echo "other" + ;; + esac + + return 0 +} + +detectVersion() { + + local xml="$1" + local suggested="${2:-}" + + local tag name id base prefer match platform + local priority actual edition suffix i local tried="" + local -a tags=( + "DISPLAYNAME" + "PRODUCTNAME" + "NAME" + ) local -a versions=() local -a bases=() + local -a groups=() local -a priorities=( + "enterprise" + "ultimate" + "default" + "iot" + "ltsc" + "education" + "home" + "starter" + "hv" + ) + local -a suffixes=( "-enterprise" "-ultimate" "" @@ -128,44 +205,55 @@ selectVersion() { "-hv" ) - while IFS= read -r name; do - [[ "$name" == *"Operating System"* ]] && continue - [ -z "$name" ] && continue + platform=$(getPlatform "$xml") - base=$(fromName "$name" "$platform") - id=$(getVersion "$name" "$platform") + for tag in "${tags[@]}"; do + while IFS= read -r name; do + [[ "$name" == *"Operating System"* ]] && continue + [ -z "$name" ] && continue - if [ -z "$base" ] || [ -z "$id" ]; then - warn "Unknown ${tag,,}: '$name'" - continue - fi + base=$(fromName "$name" "$platform") + id=$(getVersion "$name" "$platform") - versions+=("$id") - bases+=("$base") - done < <( - sed -n \ - "/$tag/{s/.*<$tag>\(.*\)<\/$tag>.*/\1/;p}" \ - <<< "$xml" - ) + if [ -z "$base" ] || [ -z "$id" ]; then + warn "Unknown ${tag,,}: '$name'" + continue + fi + + versions+=("$id") + bases+=("$base") + groups+=("$(getVersionPriority "$id" "$base")") + done < <( + sed -n \ + "/$tag/{s/.*<$tag>\(.*\)<\/$tag>.*/\1/;p}" \ + <<< "$xml" + ) + done [ "${#versions[@]}" -eq 0 ] && return 0 if [ -n "$EDITION" ]; then for base in "${bases[@]}"; do - [[ "${base,,}" == win20* ]] && continue - tried="Y" + case "${base,,}" in + "win2003"* | "win2008"* | "win2012"* | "win2016"* | \ + "win2019"* | "win2022"* | "win2025"* ) + edition=$(normalizeServerEditionID "$EDITION") - case "${EDITION,,}" in - "pro" | "professional" | "business" ) - prefer="$base" + # Known Server editions continue using the generic Server ID. + # updateXML() applies Standard, Datacenter or Core to that template. + [ -z "$edition" ] && continue ;; * ) - prefer="$base-${EDITION,,}" + edition=$(normalizeEditionID "$EDITION" "$base") ;; esac + tried="Y" + prefer="$base" + [ -n "$edition" ] && prefer+="-$edition" + if match=$(hasVersion "$prefer" "${versions[@]}"); then echo "$match" return 0 @@ -173,7 +261,7 @@ selectVersion() { done if [ -n "$tried" ]; then - warn "Edition '$EDITION' is not supported by this image, using automatic selection instead." + warn "edition '$EDITION' is not supported by this image, using automatic selection instead." fi fi @@ -187,60 +275,56 @@ selectVersion() { fi fi - # Preserve the existing preference for Enterprise, Ultimate, and the - # normal Pro/Professional/Business edition. The remaining entries provide - # deterministic selection when those editions are absent. - for suffix in "${priorities[@]}"; do + # Prefer the normal edition within each selection family. hasVersion() + # still allows its Evaluation counterpart when the normal variant is absent. + for suffix in "${suffixes[@]}"; do for base in "${bases[@]}"; do prefer="$base$suffix" - # Automatic selection must prefer an edition that is actually present. - for actual in "${versions[@]}"; do - [[ "${actual%-eval}" == "${prefer%-eval}" ]] || continue - - if match=$(hasVersion "$prefer" "${versions[@]}"); then - echo "$match" - return 0 - fi - done + if match=$(hasVersion "$prefer" "${versions[@]}"); then + echo "$match" + return 0 + fi done done - # Future or unusual edition that getVersion() recognizes but which is not - # included in the priority list: use the first recognized WIM image. + # When the normal edition is absent, select another compatible member of + # that family, such as N, Workstations, or a future dynamic variant. + for priority in "${priorities[@]}"; do + for (( i=0; i<${#versions[@]}; i++ )); do + [[ "${groups[$i]}" == "$priority" ]] || continue + + actual="${versions[$i]}" + + if match=$(hasVersion "$actual" "${versions[@]}"); then + echo "$match" + return 0 + fi + done + done + + # Future or unusual editions that do not belong to a known selection + # family use the first recognized WIM image. echo "${versions[0]}" return 0 } -detectVersion() { - - local xml="$1" - local suggested="${2:-}" - local id platform - - platform=$(getPlatform "$xml") - id=$(selectVersion "DISPLAYNAME" "$xml" "$platform" "$suggested") - [ -z "$id" ] && - id=$(selectVersion "PRODUCTNAME" "$xml" "$platform" "$suggested") - [ -z "$id" ] && - id=$(selectVersion "NAME" "$xml" "$platform" "$suggested") - - echo "$id" - return 0 -} - getImageIndex() { local xml="$1" local wanted="$2" local platform tag index name id + local -a matches=() + [ -z "$wanted" ] && return 1 platform=$(getPlatform "$xml") for tag in DISPLAYNAME PRODUCTNAME NAME; do + matches=() + while IFS=$'\t' read -r index name; do [ -n "$index" ] || continue [[ "$name" == *"Operating System"* ]] && continue @@ -249,8 +333,7 @@ getImageIndex() { id=$(getVersion "$name" "$platform") [[ "${id,,}" == "${wanted,,}" ]] || continue - echo "$index" - return 0 + matches+=("$index") done < <( awk -v tag="$tag" ' /= 129 )); then + exit "$rc" + fi + warn "failed to read Windows image information, $FB" return 1 - fi + } checkPlatform "$info" || exit 67 @@ -458,6 +562,21 @@ detectImage() { DETECTED=$(detectVersion "$info" "$suggested") + if [ -n "$EDITION" ]; then + case "${DETECTED,,}" in + "win2003"* | "win2008"* | "win2012"* | "win2016"* | \ + "win2019"* | "win2022"* | "win2025"* ) + edition=$(normalizeServerEditionID "$EDITION") + + if [ -n "$edition" ] && + [[ "${DETECTED,,}" != *"-${edition,,}" && + "${DETECTED,,}" != *"-${edition,,}-eval" ]]; then + EDITION="" + fi + ;; + esac + fi + if [ -z "$DETECTED" ]; then msg="Failed to determine Windows version from image" @@ -493,7 +612,7 @@ detectImage() { msg="the answer file for $desc was not found ($DETECTED.xml)" local fallback="/run/assets/${DETECTED%%-*}.xml" - if setXML "$fallback" || enabled "$MANUAL"; then + if setXML "$fallback" "$index" || enabled "$MANUAL"; then ! enabled "$MANUAL" && warn "${msg}." else MANUAL="Y" diff --git a/src/install.sh b/src/install.sh index 0561f367..bf0c5c64 100644 --- a/src/install.sh +++ b/src/install.sh @@ -1013,23 +1013,7 @@ updateImage() { return 1 fi - local fallback="/run/assets/${DETECTED%%-*}.xml" - - if [[ "$DETECTED" != win20* && - "$asset" == "$fallback" && - "$asset" != "/run/assets/$DETECTED.xml" ]]; then - - if ! sed -i \ - -e '/.*<\/InstallFrom>/d' \ - -e '/.*<\/ProductKey>/d' \ - -e '//,/<\/InstallFrom>/d' \ - -e '//,/<\/ProductKey>/d' \ - "$answer"; then - error "Failed to make answer file edition-neutral: $answer" - return 1 - fi - - fi + removeGeneratedXML "$asset" || return 1 if [ -n "${CUSTOM_XML:-}" ]; then @@ -1058,6 +1042,8 @@ updateImage() { if enabled "$MANUAL"; then + removeGeneratedXML "$asset" || return 1 + wimlib-imagex update "$wim" "$idx" --command "delete --force /$xml" > /dev/null || true if wimlib-imagex extract "$wim" "$idx" "/$bak" "--dest-dir=$tmp" >/dev/null 2>&1; then @@ -1204,6 +1190,8 @@ bootWindows() { ! parseLanguage && exit 56 ! detectCustom && exit 59 +testImages + if ! startInstall; then bootWindows && return 0 exit 68 diff --git a/src/mido.sh b/src/mido.sh index 5260f522..00c55ed0 100644 --- a/src/mido.sh +++ b/src/mido.sh @@ -853,8 +853,8 @@ verifyFile() { fi fi - local hash="" local algo="SHA256" + local hash="" rc=0 [ -z "$check" ] && return 0 ! enabled "$VERIFY" && return 0 @@ -864,15 +864,31 @@ verifyFile() { info "$msg" && html "$msg" if [[ "${algo,,}" != "sha256" ]]; then - if ! hash=$(sha1sum "$iso" | cut -f1 -d' '); then + + hash=$(sha1sum "$iso" | cut -f1 -d' ') || { + rc=$? + + if (( rc >= 129 )); then + exit "$rc" + fi + error "Failed to calculate SHA1 checksum for $iso!" return 1 - fi + } + else - if ! hash=$(sha256sum "$iso" | cut -f1 -d' '); then + + hash=$(sha256sum "$iso" | cut -f1 -d' ') || { + rc=$? + + if (( rc >= 129 )); then + exit "$rc" + fi + error "Failed to calculate SHA256 checksum for $iso!" return 1 - fi + } + fi if [[ "$hash" == "$check" ]]; then @@ -993,11 +1009,237 @@ fallbackEnglish() { downloadImage "$iso" "$version" "$LANGUAGE" } +testDownload() { + + local file="$1" + local url="$2" + local sum="$3" + local size="$4" + local desc="$5" + local web_desc="$6" + local total failed="" + + if ! rm -f -- "$file" "$file.aria2"; then + error "Failed to remove previous test download: $file" + return 1 + fi + + # Call downloadFile() directly so downloadRetry(), delays and retries + # from the normal installation path are not used. + if ! downloadFile \ + "$file" \ + "$url" \ + "$size" \ + "$desc" \ + "$web_desc" \ + "${CONNECTIONS:-1}"; then + failed="Y" + elif ! total=$(stat -c%s -- "$file" 2>/dev/null); then + error "Failed to determine downloaded file size: $file" + failed="Y" + else + + if [ -n "$size" ] && [[ "$size" != "0" && "$total" != "$size" ]]; then + error "The downloaded file has a different size ( $total bytes) than expected ( $size bytes)." + failed="Y" + fi + + # Size was checked strictly above. Passing an empty expected size avoids + # the normal warning-only size handling while still verifying the hash. + if ! verifyFile "$file" "" "$total" "$sum"; then + failed="Y" + fi + + fi + + if ! rm -f -- "$file" "$file.aria2"; then + error "Failed to remove test download: $file" + failed="Y" + fi + + [ -z "$failed" ] +} + +downloadTest() { + + local version="$1" + local lang="$2" + local normal="$version" + local dir="$STORAGE/tmp/download-test" + local file="$dir/test.iso" + local tested=0 failed=0 + local url sum size base desc web_desc language i + + # Dynamically scoped, so verifyFile() validates every available checksum. + local VERIFY="Y" + + if ! rm -rf -- "$dir" || ! makeDir "$dir"; then + error "Failed to create download test directory: $dir" + return 1 + fi + + if [[ "${version,,}" == "http"* ]]; then + + base=$(basename "${version%%\?*}") + desc=$(fromFile "$base") + web_desc="$desc" + tested=$((tested + 1)) + + info "Testing custom download..." + + if ! testDownload "$file" "$version" "" "" "$desc" "$web_desc"; then + failed=$((failed + 1)) + fi + + else + + if ! validVersion "$version" "en"; then + error "Invalid VERSION specified, value \"$version\" is not recognized!" + rm -rf -- "$dir" 2>/dev/null || true + return 1 + fi + + desc=$(printVariant "$version" "" "Y") + web_desc=$(printVariant "$version" "") + + if [[ "${lang,,}" != "en" && "${lang,,}" != "en-"* ]]; then + language=$(getLanguage "$lang" "desc") + desc+=" in $language" + web_desc+=" in $language" + fi + + if isMido "$version" "$lang"; then + + tested=$((tested + 1)) + MIDO_URL="" + + info "Testing MIDO download..." + + if ! getWindows "$version" "$lang" "$desc" "$web_desc" || + [ -z "$MIDO_URL" ]; then + failed=$((failed + 1)) + else + + url=$(getMido "$version" "$lang" "") + sum="" + size="" + + # Skip static verification if Microsoft returned a different URL. + if [[ "${MIDO_URL%%\?*}" == "${url%%\?*}" ]]; then + size=$(getMido "$version" "$lang" "size") + sum=$(getMido "$version" "$lang" "sum") + fi + + if ! testDownload \ + "$file" \ + "$MIDO_URL" \ + "$sum" \ + "$size" \ + "$desc" \ + "$web_desc"; then + failed=$((failed + 1)) + fi + + fi + + fi + + # Evaluation media only exists through MIDO. ESD and mirrors use the + # corresponding normal version. + [[ "${normal,,}" == *"-eval" ]] && normal="${normal::-5}" + + desc=$(printVariant "$normal" "" "Y") + web_desc=$(printVariant "$normal" "") + + if [[ "${lang,,}" != "en" && "${lang,,}" != "en-"* ]]; then + desc+=" in $language" + web_desc+=" in $language" + fi + + if isESD "$normal" "$lang"; then + + tested=$((tested + 1)) + ESD="" + + info "Testing ESD download..." + + if ! getESD "$dir/esd" "$normal" "$lang" "$desc"; then + failed=$((failed + 1)) + elif ! testDownload \ + "$dir/test.esd" \ + "$ESD" \ + "$ESD_SUM" \ + "$ESD_SIZE" \ + "$desc" \ + "$web_desc"; then + failed=$((failed + 1)) + fi + + fi + + for ((i=1;i<=MIRRORS;i++)); do + + url=$(getLink "$i" "$normal" "$lang") + [ -z "$url" ] && continue + + tested=$((tested + 1)) + size=$(getSize "$i" "$normal" "$lang") + sum=$(getHash "$i" "$normal" "$lang") + + info "Testing download mirror $i: $(getHost "$url")..." + + if ! testDownload \ + "$file" \ + "$url" \ + "$sum" \ + "$size" \ + "$desc" \ + "$web_desc"; then + failed=$((failed + 1)) + fi + + done + + fi + + if ! rm -rf -- "$dir"; then + error "Failed to remove download test directory: $dir" + failed=$((failed + 1)) + fi + + if (( tested == 0 )); then + error "No download methods are available for $version." + return 1 + fi + + if (( failed > 0 )); then + error "$failed of $tested download methods failed." + return 1 + fi + + info "All $tested download methods completed successfully." + return 0 +} + +testImages() { + + disabled "${TEST:-}" && return 0 + + if downloadTest "$VERSION" "$LANGUAGE"; then + info "Download test completed successfully." + exit 0 + fi + + error "Download test failed." + exit 61 +} + downloadImage() { local iso="$1" local version="$2" local lang="$3" + local requested="$version" local tried="n" local success="n" local seconds="5" @@ -1073,6 +1315,17 @@ downloadImage() { fi fi + if switchEdition version; then + + desc=$(printVariant "$DETECTED" "" "Y") + web_desc=$(printVariant "$DETECTED" "") + + if [[ "${lang,,}" != "en" && "${lang,,}" != "en-"* ]]; then + desc+=" in $language" + fi + + fi + if isESD "$version" "$lang"; then if [[ "$tried" != "n" ]]; then @@ -1122,7 +1375,7 @@ downloadImage() { done if [[ "${lang,,}" != "en" && "${lang,,}" != "en-"* ]]; then - if fallbackEnglish "$iso" "$version" "$lang" "$desc" "$web_desc"; then + if fallbackEnglish "$iso" "$requested" "$lang" "$desc" "$web_desc"; then return 0 fi fi