mirror of
https://github.com/dockur/windows.git
synced 2026-08-03 20:47:12 +01:00
feat: Improve Windows image detection and unattended setup (#2037)
This commit is contained in:
+901
-485
File diff suppressed because it is too large
Load Diff
+144
-196
@@ -47,116 +47,79 @@ parseVersion() {
|
||||
|
||||
case "${VERSION,,}" in
|
||||
"11" | "11p" | "win11" | "pro11" | "win11p" | "windows11" | "windows 11" )
|
||||
VERSION="win11x64"
|
||||
;;
|
||||
VERSION="win11x64" ;;
|
||||
"11e" | "win11e" | "windows11e" | "windows 11e" )
|
||||
VERSION="win11x64-enterprise-eval"
|
||||
;;
|
||||
VERSION="win11x64-enterprise-eval" ;;
|
||||
"11l" | "11ltsc" | "ltsc11" | "win11l" | "win11-ltsc" | "win11x64-ltsc" )
|
||||
VERSION="win11x64-enterprise-ltsc-eval"
|
||||
;;
|
||||
VERSION="win11x64-enterprise-ltsc-eval" ;;
|
||||
"11i" | "11iot" | "iot11" | "win11i" | "win11-iot" | "win11x64-iot" )
|
||||
VERSION="win11x64-enterprise-iot-eval"
|
||||
;;
|
||||
VERSION="win11x64-enterprise-iot-eval" ;;
|
||||
"10" | "10p" | "win10" | "pro10" | "win10p" | "windows10" | "windows 10" )
|
||||
VERSION="win10x64"
|
||||
;;
|
||||
VERSION="win10x64" ;;
|
||||
"10e" | "win10e" | "windows10e" | "windows 10e" )
|
||||
VERSION="win10x64-enterprise-eval"
|
||||
;;
|
||||
VERSION="win10x64-enterprise-eval" ;;
|
||||
"10l" | "10ltsc" | "ltsc10" | "win10l" | "win10-ltsc" | "win10x64-ltsc" )
|
||||
VERSION="win10x64-enterprise-ltsc-eval"
|
||||
;;
|
||||
VERSION="win10x64-enterprise-ltsc-eval" ;;
|
||||
"10i" | "10iot" | "iot10" | "win10i" | "win10-iot" | "win10x64-iot" )
|
||||
VERSION="win10x64-enterprise-iot-eval"
|
||||
;;
|
||||
VERSION="win10x64-enterprise-iot-eval" ;;
|
||||
"8" | "8p" | "81" | "81p" | "pro8" | "8.1" | "win8" | "win8p" | "win81" | "win81p" | "windows 8" )
|
||||
VERSION="win81x64"
|
||||
;;
|
||||
VERSION="win81x64" ;;
|
||||
"8e" | "81e" | "8.1e" | "win8e" | "win81e" | "windows 8e" )
|
||||
VERSION="win81x64-enterprise-eval"
|
||||
;;
|
||||
VERSION="win81x64-enterprise-eval" ;;
|
||||
"7" | "win7" | "windows7" | "windows 7" )
|
||||
VERSION="win7x64"
|
||||
;;
|
||||
VERSION="win7x64" ;;
|
||||
"7u" | "win7u" | "windows7u" | "windows 7u" )
|
||||
VERSION="win7x64-ultimate"
|
||||
;;
|
||||
VERSION="win7x64-ultimate" ;;
|
||||
"7e" | "win7e" | "windows7e" | "windows 7e" )
|
||||
VERSION="win7x64-enterprise"
|
||||
;;
|
||||
VERSION="win7x64-enterprise" ;;
|
||||
"7x86" | "win7x86" | "win732" | "windows7x86" )
|
||||
VERSION="win7x86"
|
||||
;;
|
||||
VERSION="win7x86" ;;
|
||||
"7ux86" | "7u32" | "win7x86-ultimate" )
|
||||
VERSION="win7x86-ultimate"
|
||||
;;
|
||||
VERSION="win7x86-ultimate" ;;
|
||||
"7ex86" | "7e32" | "win7x86-enterprise" )
|
||||
VERSION="win7x86-enterprise"
|
||||
;;
|
||||
VERSION="win7x86-enterprise" ;;
|
||||
"vista" | "vs" | "6" | "winvista" | "windowsvista" | "windows vista" )
|
||||
VERSION="winvistax64"
|
||||
;;
|
||||
VERSION="winvistax64" ;;
|
||||
"vistu" | "vu" | "6u" | "winvistu" )
|
||||
VERSION="winvistax64-ultimate"
|
||||
;;
|
||||
VERSION="winvistax64-ultimate" ;;
|
||||
"viste" | "ve" | "6e" | "winviste" )
|
||||
VERSION="winvistax64-enterprise"
|
||||
;;
|
||||
VERSION="winvistax64-enterprise" ;;
|
||||
"vistax86" | "vista32" | "6x86" | "winvistax86" | "windowsvistax86" )
|
||||
VERSION="winvistax86"
|
||||
;;
|
||||
VERSION="winvistax86" ;;
|
||||
"vux86" | "vu32" | "winvistax86-ultimate" )
|
||||
VERSION="winvistax86-ultimate"
|
||||
;;
|
||||
VERSION="winvistax86-ultimate" ;;
|
||||
"vex86" | "ve32" | "winvistax86-enterprise" )
|
||||
VERSION="winvistax86-enterprise"
|
||||
;;
|
||||
VERSION="winvistax86-enterprise" ;;
|
||||
"xp" | "xp32" | "xpx86" | "5" | "5x86" | "winxp" | "winxp86" | "windowsxp" | "windows xp" )
|
||||
VERSION="winxpx86"
|
||||
;;
|
||||
VERSION="winxpx86" ;;
|
||||
"xp64" | "xpx64" | "5x64" | "winxp64" | "winxpx64" | "windowsxp64" | "windowsxpx64" )
|
||||
VERSION="winxpx64"
|
||||
;;
|
||||
VERSION="winxpx64" ;;
|
||||
"2k" | "2000" | "win2k" | "win2000" | "windows2k" | "windows2000" )
|
||||
VERSION="win2kx86"
|
||||
;;
|
||||
VERSION="win2kx86" ;;
|
||||
"25" | "2025" | "win25" | "win2025" | "windows2025" | "windows 2025" )
|
||||
VERSION="win2025-eval"
|
||||
;;
|
||||
VERSION="win2025-eval" ;;
|
||||
"22" | "2022" | "win22" | "win2022" | "windows2022" | "windows 2022" )
|
||||
VERSION="win2022-eval"
|
||||
;;
|
||||
VERSION="win2022-eval" ;;
|
||||
"19" | "2019" | "win19" | "win2019" | "windows2019" | "windows 2019" )
|
||||
VERSION="win2019-eval"
|
||||
;;
|
||||
VERSION="win2019-eval" ;;
|
||||
"16" | "2016" | "win16" | "win2016" | "windows2016" | "windows 2016" )
|
||||
VERSION="win2016-eval"
|
||||
;;
|
||||
VERSION="win2016-eval" ;;
|
||||
"hv" | "hyperv" | "hyper v" | "hyper-v" | "19hv" | "2019hv" | "win2019hv" )
|
||||
VERSION="win2019-hv"
|
||||
;;
|
||||
VERSION="win2019-hv" ;;
|
||||
"2012" | "2012r2" | "win2012" | "win2012r2" | "windows2012" | "windows 2012" )
|
||||
VERSION="win2012r2-eval"
|
||||
;;
|
||||
VERSION="win2012r2-eval" ;;
|
||||
"2008" | "2008r2" | "win2008" | "win2008r2" | "windows2008" | "windows 2008" )
|
||||
VERSION="win2008r2"
|
||||
;;
|
||||
VERSION="win2008r2" ;;
|
||||
"2003" | "2003r2" | "win2003" | "win2003r2" | "windows2003" | "windows 2003" )
|
||||
VERSION="win2003r2"
|
||||
;;
|
||||
VERSION="win2003r2" ;;
|
||||
"core11" | "core 11" )
|
||||
VERSION="core11"
|
||||
;;
|
||||
VERSION="core11" ;;
|
||||
"tiny11" | "tiny 11" )
|
||||
VERSION="tiny11"
|
||||
;;
|
||||
VERSION="tiny11" ;;
|
||||
"tiny10" | "tiny 10" )
|
||||
VERSION="tiny10"
|
||||
;;
|
||||
VERSION="tiny10" ;;
|
||||
"reactos" | "react os" )
|
||||
VERSION="reactos"
|
||||
;;
|
||||
VERSION="reactos" ;;
|
||||
esac
|
||||
|
||||
SUGGEST=$(getSuggestedVersion "$VERSION")
|
||||
@@ -172,29 +135,21 @@ getSuggestedVersion() {
|
||||
|
||||
case "$id" in
|
||||
"win10x64" | "win11x64" )
|
||||
echo "$id"
|
||||
;;
|
||||
echo "$id" ;;
|
||||
"win7x64" | "win7x86" | "winvistax64" | "winvistax86" )
|
||||
echo "$id-ultimate"
|
||||
;;
|
||||
echo "$id-ultimate" ;;
|
||||
"tiny10" )
|
||||
echo "win10x64-ltsc"
|
||||
;;
|
||||
echo "win10x64-ltsc" ;;
|
||||
*"-enterprise-ltsc-eval" )
|
||||
echo "${id%-enterprise-ltsc-eval}-ltsc"
|
||||
;;
|
||||
echo "${id%-enterprise-ltsc-eval}-ltsc" ;;
|
||||
*"-enterprise-iot-eval" )
|
||||
echo "${id%-enterprise-iot-eval}-iot"
|
||||
;;
|
||||
echo "${id%-enterprise-iot-eval}-iot" ;;
|
||||
*"-enterprise-ltsc" )
|
||||
echo "${id%-enterprise-ltsc}-ltsc"
|
||||
;;
|
||||
echo "${id%-enterprise-ltsc}-ltsc" ;;
|
||||
*"-enterprise-iot" )
|
||||
echo "${id%-enterprise-iot}-iot"
|
||||
;;
|
||||
echo "${id%-enterprise-iot}-iot" ;;
|
||||
*"-eval" )
|
||||
echo "${id%-eval}"
|
||||
;;
|
||||
echo "${id%-eval}" ;;
|
||||
esac
|
||||
|
||||
return 0
|
||||
@@ -205,11 +160,8 @@ getLanguage() {
|
||||
local source="$1"
|
||||
local input="${1,,}"
|
||||
local ret="$2"
|
||||
local id="$source"
|
||||
local lang=""
|
||||
local desc=""
|
||||
local short=""
|
||||
local culture=""
|
||||
|
||||
local id="$source" lang="" desc="" short="" culture=""
|
||||
|
||||
case "$input" in
|
||||
"ar" | "ar-"* | "arabic" | "arab" )
|
||||
@@ -496,14 +448,11 @@ printVariant() {
|
||||
|
||||
case "${id,,}" in
|
||||
*"-iot" | *"-iot-eval" )
|
||||
desc+=" IoT"
|
||||
;;
|
||||
desc+=" IoT" ;;
|
||||
*"-ltsc" | *"-ltsc-eval" )
|
||||
desc+=" LTSC"
|
||||
;;
|
||||
desc+=" LTSC" ;;
|
||||
*"-enterprise" | *"-enterprise-eval" )
|
||||
desc+=" Enterprise"
|
||||
;;
|
||||
desc+=" Enterprise" ;;
|
||||
esac
|
||||
|
||||
if enabled "$show_eval" && [[ "${id,,}" == *"-eval" ]]; then
|
||||
@@ -540,6 +489,7 @@ printEdition() {
|
||||
local id="$1"
|
||||
local desc="$2"
|
||||
local show_eval="${3:-N}"
|
||||
|
||||
local normalized="${id,,}"
|
||||
local result edition="" suffix=""
|
||||
|
||||
@@ -561,20 +511,15 @@ printEdition() {
|
||||
esac
|
||||
;;
|
||||
"home" )
|
||||
edition="Home"
|
||||
;;
|
||||
edition="Home" ;;
|
||||
"starter" )
|
||||
edition="Starter"
|
||||
;;
|
||||
edition="Starter" ;;
|
||||
"ultimate" )
|
||||
edition="Ultimate"
|
||||
;;
|
||||
edition="Ultimate" ;;
|
||||
"enterprise" )
|
||||
edition="Enterprise"
|
||||
;;
|
||||
edition="Enterprise" ;;
|
||||
"education" )
|
||||
edition="Education"
|
||||
;;
|
||||
edition="Education" ;;
|
||||
"n" )
|
||||
case "$normalized" in
|
||||
"win7"* ) edition="Professional N" ;;
|
||||
@@ -582,22 +527,18 @@ printEdition() {
|
||||
esac
|
||||
;;
|
||||
"iot" | "enterprise-iot" )
|
||||
edition="IoT Enterprise LTSC"
|
||||
;;
|
||||
edition="IoT Enterprise LTSC" ;;
|
||||
"ltsc" | "enterprise-ltsc" )
|
||||
edition="Enterprise LTSC"
|
||||
;;
|
||||
edition="Enterprise LTSC" ;;
|
||||
* )
|
||||
edition=$(formatEdition "$suffix")
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
"winxp"* )
|
||||
edition="Professional"
|
||||
;;
|
||||
edition="Professional" ;;
|
||||
"win2019-hv"* )
|
||||
edition="2019"
|
||||
;;
|
||||
edition="2019" ;;
|
||||
"win20"* )
|
||||
[[ "$normalized" == *"-"* ]] && suffix="${normalized#*-}"
|
||||
|
||||
@@ -625,10 +566,10 @@ printEdition() {
|
||||
|
||||
fromFile() {
|
||||
|
||||
local id=""
|
||||
local desc="$1"
|
||||
local file="${1,,}"
|
||||
local arch="${PLATFORM,,}"
|
||||
|
||||
local id="" arch="${PLATFORM,,}"
|
||||
|
||||
file="${file//-/_}"
|
||||
file="${file// /_}"
|
||||
@@ -694,10 +635,11 @@ fromFile() {
|
||||
|
||||
fromName() {
|
||||
|
||||
local id=""
|
||||
local name="$1"
|
||||
local arch="$2"
|
||||
|
||||
local id=""
|
||||
|
||||
local add=""
|
||||
[[ "$arch" != "x64" ]] && add="$arch"
|
||||
|
||||
@@ -723,9 +665,23 @@ fromName() {
|
||||
return 0
|
||||
}
|
||||
|
||||
isClientEdition() {
|
||||
|
||||
case "${1,,}" in
|
||||
"pro" | "professional" | "business" | \
|
||||
"enterprise" | "ultimate" | "education" | \
|
||||
"home" | "homepremium" | "home-premium" | \
|
||||
"homebasic" | "home-basic" | "starter" | "core" )
|
||||
return 0 ;;
|
||||
esac
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
normalizeEdition() {
|
||||
|
||||
local source="${1,,}"
|
||||
|
||||
local edition
|
||||
|
||||
source="${source//evaluation/}"
|
||||
@@ -745,7 +701,7 @@ normalizeEdition() {
|
||||
|
||||
normalizeEditionID() {
|
||||
|
||||
local edition
|
||||
local edition base
|
||||
local id="$2"
|
||||
|
||||
edition=$(normalizeEdition "$1")
|
||||
@@ -753,8 +709,21 @@ normalizeEditionID() {
|
||||
case "$edition" in
|
||||
"pro" | "professional" | "business" )
|
||||
edition="" ;;
|
||||
"pro-n" | "pron" | "professional-n" | "professionaln" )
|
||||
"pro-n" | "pron" | "professional-n" | "professionaln" | "business-n" | "businessn" )
|
||||
edition="n" ;;
|
||||
* )
|
||||
if ! isClientEdition "$edition"; then
|
||||
case "$edition" in
|
||||
*"-n" ) base="${edition%-n}" ;;
|
||||
*"n" ) base="${edition%n}" ;;
|
||||
* ) base="" ;;
|
||||
esac
|
||||
|
||||
if [ -n "$base" ] && isClientEdition "$base"; then
|
||||
edition="$base-n"
|
||||
fi
|
||||
|
||||
fi ;;
|
||||
esac
|
||||
|
||||
case "${id,,}" in
|
||||
@@ -778,6 +747,7 @@ getEditionID() {
|
||||
|
||||
local name="${1,,}"
|
||||
local id="${2,,}"
|
||||
|
||||
local edition
|
||||
|
||||
case "$id" in
|
||||
@@ -813,26 +783,19 @@ normalizeServerEdition() {
|
||||
|
||||
case "$edition" in
|
||||
"core" | "core-installation" | "server-core-installation" )
|
||||
edition="standard-core"
|
||||
;;
|
||||
edition="standard-core" ;;
|
||||
"desktop-experience" | "server-with-a-gui" | "full-installation" )
|
||||
edition="standard"
|
||||
;;
|
||||
edition="standard" ;;
|
||||
*"-server-core-installation" )
|
||||
edition="${edition%-server-core-installation}-core"
|
||||
;;
|
||||
edition="${edition%-server-core-installation}-core" ;;
|
||||
*"-core-installation" )
|
||||
edition="${edition%-core-installation}-core"
|
||||
;;
|
||||
edition="${edition%-core-installation}-core" ;;
|
||||
*"-desktop-experience" )
|
||||
edition="${edition%-desktop-experience}"
|
||||
;;
|
||||
edition="${edition%-desktop-experience}" ;;
|
||||
*"-server-with-a-gui" )
|
||||
edition="${edition%-server-with-a-gui}"
|
||||
;;
|
||||
edition="${edition%-server-with-a-gui}" ;;
|
||||
*"-full-installation" )
|
||||
edition="${edition%-full-installation}"
|
||||
;;
|
||||
edition="${edition%-full-installation}" ;;
|
||||
esac
|
||||
|
||||
edition="${edition#server-}"
|
||||
@@ -879,6 +842,7 @@ getServerEditionID() {
|
||||
|
||||
local name="${1,,}"
|
||||
local id="${2,,}"
|
||||
|
||||
local edition
|
||||
|
||||
case "$id" in
|
||||
@@ -901,42 +865,36 @@ getServerEditionID() {
|
||||
getEditionOrder() {
|
||||
|
||||
local id="${1,,}"
|
||||
local result_name="$2"
|
||||
local -n result="$result_name"
|
||||
|
||||
result=()
|
||||
|
||||
case "$id" in
|
||||
"win20"* )
|
||||
result=(
|
||||
"|default|@default"
|
||||
"-datacenter|datacenter|datacenter datacenter-*"
|
||||
"-datacenter-azure|datacenter|datacenter-azure"
|
||||
"-enterprise|enterprise|enterprise enterprise-*"
|
||||
"-web|web|web web-*"
|
||||
"-foundation|foundation|foundation foundation-*"
|
||||
"-essentials|essentials|essentials essentials-*"
|
||||
"-standard-core|standard-core|standard-core standard-core-*"
|
||||
"-datacenter-core|datacenter-core|datacenter-core datacenter-core-*"
|
||||
"-datacenter-azure-core|datacenter-core|datacenter-azure-core"
|
||||
"-enterprise-core|enterprise-core|enterprise-core enterprise-core-*"
|
||||
"-web-core|web-core|web-core web-core-*"
|
||||
printf '%s\n' \
|
||||
"|default|@default" \
|
||||
"-datacenter|datacenter|datacenter datacenter-*" \
|
||||
"-datacenter-azure|datacenter|datacenter-azure" \
|
||||
"-enterprise|enterprise|enterprise enterprise-*" \
|
||||
"-web|web|web web-*" \
|
||||
"-foundation|foundation|foundation foundation-*" \
|
||||
"-essentials|essentials|essentials essentials-*" \
|
||||
"-standard-core|standard-core|standard-core standard-core-*" \
|
||||
"-datacenter-core|datacenter-core|datacenter-core datacenter-core-*" \
|
||||
"-datacenter-azure-core|datacenter-core|datacenter-azure-core" \
|
||||
"-enterprise-core|enterprise-core|enterprise-core enterprise-core-*" \
|
||||
"-web-core|web-core|web-core web-core-*" \
|
||||
"-hv|hv|hv hv-*"
|
||||
)
|
||||
;;
|
||||
* )
|
||||
result=(
|
||||
"-enterprise|enterprise|enterprise enterprise-*"
|
||||
"-ultimate|ultimate|ultimate ultimate-*"
|
||||
"|default|@default n pro pro-* professional professional-* business business-*"
|
||||
"-iot|iot|iot iot-* enterprise-iot enterprise-iot-*"
|
||||
"-ltsc|ltsc|ltsc ltsc-* enterprise-ltsc enterprise-ltsc-*"
|
||||
"-education|education|education education-* pro-education pro-education-*"
|
||||
"-home|home|home home-*"
|
||||
"-home-premium|home|home-premium home-premium-*"
|
||||
"-home-basic|home|home-basic home-basic-*"
|
||||
printf '%s\n' \
|
||||
"-enterprise|enterprise|enterprise enterprise-*" \
|
||||
"-ultimate|ultimate|ultimate ultimate-*" \
|
||||
"|default|@default n pro pro-* professional professional-* business business-*" \
|
||||
"-iot|iot|iot iot-* enterprise-iot enterprise-iot-*" \
|
||||
"-ltsc|ltsc|ltsc ltsc-* enterprise-ltsc enterprise-ltsc-*" \
|
||||
"-education|education|education education-* pro-education pro-education-*" \
|
||||
"-home|home|home home-*" \
|
||||
"-home-premium|home|home-premium home-premium-*" \
|
||||
"-home-basic|home|home-basic home-basic-*" \
|
||||
"-starter|starter|starter starter-*"
|
||||
)
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -945,9 +903,10 @@ getEditionOrder() {
|
||||
|
||||
getVersion() {
|
||||
|
||||
local id edition
|
||||
local name="$1"
|
||||
local arch="$2"
|
||||
|
||||
local id edition
|
||||
local evaluation=""
|
||||
|
||||
id=$(fromName "$name" "$arch")
|
||||
@@ -1002,16 +961,11 @@ isLegacy() {
|
||||
|
||||
switchEdition() {
|
||||
|
||||
local -n id="$1"
|
||||
local version="$1"
|
||||
|
||||
[[ "${id,,}" == *"-eval" ]] || return 1
|
||||
|
||||
id="${id::-5}"
|
||||
|
||||
if ! enabled "${DETECTED_ORG:-}"; then
|
||||
DETECTED="${SUGGEST:-$id}"
|
||||
fi
|
||||
[[ "${version,,}" == *"-eval" ]] || return 1
|
||||
|
||||
echo "${version::-5}"
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -1020,9 +974,8 @@ getMido() {
|
||||
local id="$1"
|
||||
local lang="$2"
|
||||
local ret="$3"
|
||||
local url=""
|
||||
local sum=""
|
||||
local size=""
|
||||
|
||||
local url="" sum="" size=""
|
||||
|
||||
[[ "${lang,,}" != "en" && "${lang,,}" != "en-us" ]] && return 0
|
||||
|
||||
@@ -1119,9 +1072,8 @@ getLink1() {
|
||||
local id="$1"
|
||||
local lang="$2"
|
||||
local ret="$3"
|
||||
local url=""
|
||||
local sum=""
|
||||
local size=""
|
||||
|
||||
local url="" sum="" size=""
|
||||
local host="https://dl.bobpony.com/windows"
|
||||
|
||||
[[ "${lang,,}" != "en" && "${lang,,}" != "en-us" ]] && return 0
|
||||
@@ -1258,9 +1210,8 @@ getLink2() {
|
||||
local id="$1"
|
||||
local lang="$2"
|
||||
local ret="$3"
|
||||
local url=""
|
||||
local sum=""
|
||||
local size=""
|
||||
|
||||
local url="" sum="" size=""
|
||||
local host="https://files.dog/MSDN"
|
||||
|
||||
[[ "${lang,,}" != "en" && "${lang,,}" != "en-us" ]] && return 0
|
||||
@@ -1352,9 +1303,8 @@ getLink3() {
|
||||
local id="$1"
|
||||
local lang="$2"
|
||||
local ret="$3"
|
||||
local url=""
|
||||
local sum=""
|
||||
local size=""
|
||||
|
||||
local url="" sum="" size=""
|
||||
local host="https://iso.reactos.org"
|
||||
|
||||
[[ "${lang,,}" != "en" && "${lang,,}" != "en-us" ]] && return 0
|
||||
@@ -1381,9 +1331,8 @@ getLink4() {
|
||||
local id="$1"
|
||||
local lang="$2"
|
||||
local ret="$3"
|
||||
local url=""
|
||||
local sum=""
|
||||
local size=""
|
||||
|
||||
local url="" sum="" size=""
|
||||
local host="https://archive.org/download"
|
||||
|
||||
[[ "${lang,,}" != "en" && "${lang,,}" != "en-us" ]] && return 0
|
||||
@@ -1552,12 +1501,13 @@ getLink4() {
|
||||
|
||||
getValue() {
|
||||
|
||||
local val=""
|
||||
local id="$2"
|
||||
local lang="$3"
|
||||
local type="$4"
|
||||
local func="getLink$1"
|
||||
|
||||
local val=""
|
||||
|
||||
if [ "$1" -gt 0 ] && [ "$1" -le "$MIRRORS" ]; then
|
||||
val=$($func "$id" "$lang" "$type")
|
||||
fi
|
||||
@@ -1585,6 +1535,7 @@ isMido() {
|
||||
|
||||
local id="$1"
|
||||
local lang="$2"
|
||||
|
||||
local sum
|
||||
|
||||
disabled "${MIDO:-}" && return 1
|
||||
@@ -1607,8 +1558,7 @@ isESD() {
|
||||
"win10${PLATFORM,,}" | \
|
||||
"win11${PLATFORM,,}-enterprise" | \
|
||||
"win10${PLATFORM,,}-enterprise" )
|
||||
return 0
|
||||
;;
|
||||
return 0 ;;
|
||||
esac
|
||||
|
||||
return 1
|
||||
@@ -1627,10 +1577,8 @@ validVersion() {
|
||||
isESD "$id" "$lang" && return 0
|
||||
|
||||
for ((i=1;i<=MIRRORS;i++)); do
|
||||
|
||||
url=$(getLink "$i" "$id" "$lang")
|
||||
[ -n "$url" ] && return 0
|
||||
|
||||
done
|
||||
|
||||
return 1
|
||||
|
||||
+365
-408
File diff suppressed because it is too large
Load Diff
+234
-120
@@ -14,83 +14,138 @@ startWindows() {
|
||||
|
||||
if ! hasImage "$ISO"; then
|
||||
if ! downloadImage "$ISO" "$VERSION" "$LANGUAGE"; then
|
||||
removeIso "$ISO" && return 68
|
||||
removeIso "$ISO" || :
|
||||
return 68
|
||||
fi
|
||||
fi
|
||||
|
||||
local extracted=0
|
||||
local boot="$BOOT"
|
||||
local dir="$TMP/unpack"
|
||||
local handled=0 extracted=0
|
||||
|
||||
selectWindowsImage "$ISO" "$dir" "$boot" || return $?
|
||||
(( handled )) && return 0
|
||||
|
||||
configureMachine "$ISO" "$dir" "$boot" || return $?
|
||||
(( handled )) && return 0
|
||||
|
||||
prepareWindowsImage "$ISO" "$dir" "$boot" || return $?
|
||||
(( handled )) && return 0
|
||||
|
||||
finishInstall "$BOOT" "N" "$boot" || return 100
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
selectWindowsImage() {
|
||||
|
||||
local iso="$1"
|
||||
local dir="$2"
|
||||
local boot="$3"
|
||||
|
||||
local detect_rc=0
|
||||
|
||||
# Known versions already provide the required image metadata.
|
||||
if resolveImage "$VERSION"; then
|
||||
|
||||
if ! setImage; then
|
||||
abortInstall "$dir" "$ISO" "$boot" || return 70
|
||||
abortInstall "$dir" "$iso" "$boot" || return 70
|
||||
handled=1
|
||||
return 0
|
||||
fi
|
||||
|
||||
if needsExtraction "$DETECTED" "$ISO"; then
|
||||
if ! extractImage "$ISO" "$dir" "$VERSION"; then
|
||||
removeIso "$ISO" && return 72
|
||||
fi
|
||||
|
||||
extracted=1
|
||||
if ! needsExtraction "$DETECTED" "$iso"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
else
|
||||
if ! extractImage "$iso" "$dir" "$VERSION"; then
|
||||
removeIso "$iso" || :
|
||||
return 72
|
||||
fi
|
||||
|
||||
local detect_rc=0
|
||||
detectIsoImage "$ISO" || detect_rc=$?
|
||||
|
||||
case "$detect_rc" in
|
||||
|
||||
0 ) ;;
|
||||
1 )
|
||||
|
||||
if ! extractImage "$ISO" "$dir" "$VERSION"; then
|
||||
removeIso "$ISO" && return 74
|
||||
fi
|
||||
|
||||
extracted=1
|
||||
|
||||
if ! detectImage "$dir"; then
|
||||
abortInstall "$dir" "$ISO" "$boot" || return 76
|
||||
return 0
|
||||
fi ;;
|
||||
|
||||
* )
|
||||
abortInstall "$dir" "$ISO" "$boot" || return 76
|
||||
return 0 ;;
|
||||
|
||||
esac
|
||||
extracted=1
|
||||
return 0
|
||||
|
||||
fi
|
||||
|
||||
local desc
|
||||
if ! desc=$(printVariant "$DETECTED" "$DETECTED"); then
|
||||
abortInstall "$dir" "$ISO" "$boot" || return 78
|
||||
# Inspect unknown media directly before falling back to extraction.
|
||||
detectIsoImage "$iso" || detect_rc=$?
|
||||
|
||||
if (( detect_rc == 0 )); then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! setMachine "$DETECTED" "$ISO" "$dir" "$desc"; then
|
||||
abortInstall "$dir" "$ISO" "$boot" || return 80
|
||||
# Only code 1 indicates that extraction may recover detection.
|
||||
if (( detect_rc != 1 )); then
|
||||
abortInstall "$dir" "$iso" "$boot" || return 76
|
||||
handled=1
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! extractImage "$iso" "$dir" "$VERSION"; then
|
||||
removeIso "$iso" || :
|
||||
return 74
|
||||
fi
|
||||
|
||||
extracted=1
|
||||
|
||||
if detectImage "$dir"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
abortInstall "$dir" "$iso" "$boot" || return 76
|
||||
handled=1
|
||||
return 0
|
||||
}
|
||||
|
||||
configureMachine() {
|
||||
|
||||
local iso="$1"
|
||||
local dir="$2"
|
||||
local boot="$3"
|
||||
|
||||
local desc
|
||||
|
||||
if ! desc=$(printVariant "$DETECTED" "$DETECTED"); then
|
||||
abortInstall "$dir" "$iso" "$boot" || return 78
|
||||
handled=1
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! setMachine "$DETECTED" "$iso" "$dir" "$desc"; then
|
||||
abortInstall "$dir" "$iso" "$boot" || return 80
|
||||
handled=1
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! restoreMachineState; then
|
||||
abortInstall "$dir" "$ISO" "$boot" || return 82
|
||||
abortInstall "$dir" "$iso" "$boot" || return 82
|
||||
handled=1
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Direct-boot media skips all unattended installation preparation.
|
||||
if bootDirect "$DETECTED"; then
|
||||
abortInstall "$dir" "$ISO" "$boot" || return 83
|
||||
abortInstall "$dir" "$iso" "$boot" || return 83
|
||||
handled=1
|
||||
return 0
|
||||
fi
|
||||
|
||||
if canUseSetupImage "$DETECTED" "$ISO"; then
|
||||
return 0
|
||||
}
|
||||
|
||||
prepareWindowsImage() {
|
||||
|
||||
local iso="$1"
|
||||
local dir="$2"
|
||||
local boot="$3"
|
||||
|
||||
# Prefer the original ISO with a small setup image whenever possible.
|
||||
if canUseSetupImage "$DETECTED" "$iso"; then
|
||||
|
||||
if ! stageSetup "$XML" "$LANGUAGE" "$TMP/setup"; then
|
||||
abortInstall "$dir" "$ISO" "$boot" || return 84
|
||||
abortInstall "$dir" "$iso" "$boot" || return 84
|
||||
handled=1
|
||||
return 0
|
||||
fi
|
||||
|
||||
@@ -98,32 +153,33 @@ startWindows() {
|
||||
exit 86
|
||||
fi
|
||||
|
||||
useOriginalImage "$ISO" || return 88
|
||||
|
||||
else
|
||||
|
||||
if (( ! extracted )); then
|
||||
if ! extractImage "$ISO" "$dir" "$VERSION"; then
|
||||
removeIso "$ISO" && return 90
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! prepareImage "$ISO" "$dir"; then
|
||||
abortInstall "$dir" "$ISO" "$boot" || return 92
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! updateImage "$dir" "$XML" "$LANGUAGE"; then
|
||||
abortInstall "$dir" "$ISO" "$boot" || return 94
|
||||
return 0
|
||||
fi
|
||||
|
||||
removeImage "$ISO" || return 96
|
||||
buildImage "$dir" || return 98
|
||||
useOriginalImage "$iso" || return 88
|
||||
return 0
|
||||
|
||||
fi
|
||||
|
||||
finishInstall "$BOOT" "N" "$boot" || return 100
|
||||
# Legacy or modifiable media must be extracted, updated, and rebuilt.
|
||||
if (( ! extracted )); then
|
||||
if ! extractImage "$iso" "$dir" "$VERSION"; then
|
||||
removeIso "$iso" || :
|
||||
return 90
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! prepareImage "$iso" "$dir"; then
|
||||
abortInstall "$dir" "$iso" "$boot" || return 92
|
||||
handled=1
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! updateImage "$dir" "$XML" "$LANGUAGE"; then
|
||||
abortInstall "$dir" "$iso" "$boot" || return 94
|
||||
handled=1
|
||||
return 0
|
||||
fi
|
||||
|
||||
removeImage "$iso" || return 96
|
||||
buildImage "$dir" || return 98
|
||||
|
||||
return 0
|
||||
}
|
||||
@@ -218,6 +274,8 @@ startInstall() {
|
||||
ISO=$(basename "$BOOT")
|
||||
ISO="$TMP/$ISO"
|
||||
|
||||
# Work from the temporary directory so the persistent source path can
|
||||
# later contain either the preserved ISO or the rebuilt installation image.
|
||||
if [ -f "$BOOT" ] && [ -s "$BOOT" ]; then
|
||||
if ! mv -f -- "$BOOT" "$ISO"; then
|
||||
error "Failed to move ISO file from \"$BOOT\" to \"$ISO\" !"
|
||||
@@ -255,11 +313,16 @@ abortInstall() {
|
||||
local dir="$1"
|
||||
local iso="$2"
|
||||
local boot="$3"
|
||||
|
||||
local efi efi32 efi64
|
||||
|
||||
# Standalone ESD files and nested archives are not directly bootable media,
|
||||
# so they cannot use the manual-install fallback.
|
||||
[[ "${iso,,}" == *".esd" ]] && exit 60
|
||||
enabled "${UNPACK:-}" && exit 60
|
||||
|
||||
# When automatic preparation fails, inspect extracted media to determine
|
||||
# whether it can still be booted manually using legacy firmware.
|
||||
if [[ "${PLATFORM,,}" == "x64" ]] && [ -d "$dir" ]; then
|
||||
|
||||
efi=$(find "$dir" -maxdepth 1 -type d -iname efi -print -quit)
|
||||
@@ -278,6 +341,8 @@ abortInstall() {
|
||||
|
||||
fi
|
||||
|
||||
# Preserve custom media in place. Downloaded or reused media must be moved
|
||||
# back to persistent storage before the manual fallback is started.
|
||||
if [ -n "$CUSTOM" ]; then
|
||||
BOOT="$iso"
|
||||
REMOVE="N"
|
||||
@@ -317,9 +382,12 @@ skipInstall() {
|
||||
|
||||
local iso="$1"
|
||||
local previousBase="$2"
|
||||
|
||||
local boot="$STORAGE/windows.boot"
|
||||
|
||||
if [ -n "$previousBase" ]; then
|
||||
# A changed source invalidates an unfinished installation. Back up an
|
||||
# existing installation, but discard stale media when no disk exists yet.
|
||||
if [[ "${STORAGE,,}/${previousBase,,}" != "${iso,,}" ]]; then
|
||||
|
||||
if ! hasDisk; then
|
||||
@@ -372,6 +440,7 @@ finishInstall() {
|
||||
local iso="$1"
|
||||
local aborted="$2"
|
||||
local boot="$3"
|
||||
|
||||
local base
|
||||
|
||||
if [ ! -s "$iso" ] || [ ! -f "$iso" ]; then
|
||||
@@ -428,8 +497,9 @@ finishInstall() {
|
||||
|
||||
findFile() {
|
||||
|
||||
local dir file base
|
||||
local fname="$1"
|
||||
|
||||
local dir file base
|
||||
local boot="$STORAGE/windows.boot"
|
||||
|
||||
dir=$(find / -maxdepth 1 -type d -iname "$fname" -print -quit)
|
||||
@@ -462,6 +532,8 @@ findFile() {
|
||||
|
||||
ISO="$file"
|
||||
CUSTOM="$file"
|
||||
# Include the custom ISO size in its persistent name so replacing a
|
||||
# bind-mounted ISO is detected as a different installation source.
|
||||
BOOT="$STORAGE/windows.$size.iso"
|
||||
|
||||
return 0
|
||||
@@ -501,6 +573,8 @@ needsExtraction() {
|
||||
local id="$1"
|
||||
local iso="$2"
|
||||
|
||||
# Direct-boot media does not need rebuilding. Legacy/skipped versions,
|
||||
# standalone ESD downloads, and nested archives require full extraction.
|
||||
bootDirect "$id" && return 1
|
||||
|
||||
skipVersion "$id" ||
|
||||
@@ -512,6 +586,7 @@ checkFreeSpace() {
|
||||
|
||||
local dir="$1"
|
||||
local size="$2"
|
||||
|
||||
local size_gb space space_gb
|
||||
|
||||
size_gb=$(formatBytes "$size")
|
||||
@@ -526,16 +601,6 @@ checkFreeSpace() {
|
||||
return 0
|
||||
}
|
||||
|
||||
getEsdField() {
|
||||
|
||||
local list="$1"
|
||||
local index="$2"
|
||||
|
||||
sed -n "${index}p" <<< "$list" | tr -cd '0-9'
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
extractESD() {
|
||||
|
||||
local iso="$1"
|
||||
@@ -543,11 +608,11 @@ extractESD() {
|
||||
local version="$3"
|
||||
local desc="$4"
|
||||
|
||||
local info count totals links
|
||||
local bootTotal bootLinks
|
||||
local wimTotal wimLinks
|
||||
local installSize size
|
||||
local edition imgEdition
|
||||
local bootTotal bootLinks wimTotal wimLinks
|
||||
local installSize size edition imgEdition
|
||||
local bootWim installWim bootSize wimSize
|
||||
local index line ret xml metadata count
|
||||
local -a fields
|
||||
|
||||
local minSize=100000000
|
||||
local freeSpace=9606127360
|
||||
@@ -579,12 +644,30 @@ extractESD() {
|
||||
|
||||
checkFreeSpace "$dir" "$freeSpace" || return 1
|
||||
|
||||
info=$(wimlib-imagex info "$iso") || {
|
||||
if ! xml=$(wimlib-imagex info "$iso" --xml 2>/dev/null |
|
||||
iconv -f UTF-16LE -t UTF-8 2>/dev/null); then
|
||||
error "Cannot read ESD file information!"
|
||||
return 1
|
||||
}
|
||||
fi
|
||||
|
||||
count=$(awk '/Image Count:/ {print $3}' <<< "$info")
|
||||
# Microsoft download ESDs use images 1-3 for setup media, WinPE, and Windows
|
||||
# Setup; images 4 and higher contain installable editions. Read all metadata
|
||||
# once because repeatedly inspecting a solid-compressed ESD is expensive.
|
||||
if ! metadata=$(xmlstarlet sel -t \
|
||||
-v 'count(/WIM/IMAGE)' -n \
|
||||
-v 'normalize-space(/WIM/IMAGE[@INDEX="1"]/TOTALBYTES)' -n \
|
||||
-v 'normalize-space(/WIM/IMAGE[@INDEX="1"]/HARDLINKBYTES)' -n \
|
||||
-v 'normalize-space(/WIM/IMAGE[@INDEX="3"]/TOTALBYTES)' -n \
|
||||
-v 'normalize-space(/WIM/IMAGE[@INDEX="3"]/HARDLINKBYTES)' -n \
|
||||
-m '/WIM/IMAGE[number(@INDEX) >= 4]' -v '@INDEX' -o $'\t' -v 'DESCRIPTION' -n \
|
||||
<<< "$xml" 2>/dev/null); then
|
||||
error "Cannot read ESD file information!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
mapfile -t fields <<< "$metadata"
|
||||
|
||||
count="${fields[0]:-}"
|
||||
if [[ ! "$count" =~ ^[0-9]+$ ]]; then
|
||||
error "Cannot read the image count in ESD file!"
|
||||
return 1
|
||||
@@ -595,34 +678,33 @@ extractESD() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
totals=$(grep "Total Bytes:" <<< "$info" || true)
|
||||
links=$(grep "Hard Link Bytes:" <<< "$info" || true)
|
||||
bootTotal="${fields[1]:-}"
|
||||
bootLinks="${fields[2]:-}"
|
||||
|
||||
bootTotal=$(getEsdField "$totals" 1)
|
||||
bootLinks=$(getEsdField "$links" 1)
|
||||
|
||||
if [[ ! "$bootTotal" =~ ^[0-9]+$ ]] || [[ ! "$bootLinks" =~ ^[0-9]+$ ]]; then
|
||||
if [[ ! "$bootTotal" =~ ^[0-9]+$ ]] ||
|
||||
[[ ! "$bootLinks" =~ ^[0-9]+$ ]]; then
|
||||
error "Cannot read bootdisk size from ESD file!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local bootSize=$(( bootTotal - bootLinks ))
|
||||
bootSize=$(( bootTotal - bootLinks ))
|
||||
|
||||
wimTotal=$(getEsdField "$totals" 3)
|
||||
wimLinks=$(getEsdField "$links" 3)
|
||||
wimTotal="${fields[3]:-}"
|
||||
wimLinks="${fields[4]:-}"
|
||||
|
||||
if [[ ! "$wimTotal" =~ ^[0-9]+$ ]] || [[ ! "$wimLinks" =~ ^[0-9]+$ ]]; then
|
||||
if [[ ! "$wimTotal" =~ ^[0-9]+$ ]] ||
|
||||
[[ ! "$wimLinks" =~ ^[0-9]+$ ]]; then
|
||||
error "Cannot read boot.wim size from ESD file!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local wimSize=$(( wimTotal - wimLinks + bootPad ))
|
||||
wimSize=$(( wimTotal - wimLinks + bootPad ))
|
||||
|
||||
/run/progress.sh "$dir" "$bootSize" "$msg ([P])..." &
|
||||
|
||||
local index="1"
|
||||
index="1"
|
||||
wimlib-imagex apply "$iso" "$index" "$dir" --quiet 2>/dev/null || {
|
||||
local ret=$?
|
||||
ret=$?
|
||||
fKill "progress.sh"
|
||||
error "Extracting $desc bootdisk failed ($ret)"
|
||||
return 1
|
||||
@@ -630,8 +712,8 @@ extractESD() {
|
||||
|
||||
fKill "progress.sh"
|
||||
|
||||
local bootWim="$dir/sources/boot.wim"
|
||||
local installWim="$dir/sources/install.wim"
|
||||
bootWim="$dir/sources/boot.wim"
|
||||
installWim="$dir/sources/install.wim"
|
||||
|
||||
msg="Extracting $desc environment"
|
||||
info "$msg..." && html "$msg..."
|
||||
@@ -639,8 +721,9 @@ extractESD() {
|
||||
index="2"
|
||||
/run/progress.sh "$bootWim" "$wimSize" "$msg ([P])..." &
|
||||
|
||||
wimlib-imagex export "$iso" "$index" "$bootWim" --compress=none --quiet || {
|
||||
local ret=$?
|
||||
wimlib-imagex export "$iso" "$index" "$bootWim" \
|
||||
--compress=none --quiet || {
|
||||
ret=$?
|
||||
fKill "progress.sh"
|
||||
error "Adding WinPE failed ($ret)"
|
||||
return 1
|
||||
@@ -654,8 +737,9 @@ extractESD() {
|
||||
index="3"
|
||||
/run/progress.sh "$bootWim" "$wimSize" "$msg ([P])..." &
|
||||
|
||||
wimlib-imagex export "$iso" "$index" "$bootWim" --compress=none --boot --quiet || {
|
||||
local ret=$?
|
||||
wimlib-imagex export "$iso" "$index" "$bootWim" \
|
||||
--compress=none --boot --quiet || {
|
||||
ret=$?
|
||||
fKill "progress.sh"
|
||||
error "Adding Windows Setup failed ($ret)"
|
||||
return 1
|
||||
@@ -678,18 +762,19 @@ extractESD() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
for (( index=4; index<=count; index++ )); do
|
||||
for line in "${fields[@]:5}"; do
|
||||
|
||||
imgEdition=$(wimlib-imagex info "$iso" "$index" | grep '^Description:' | sed 's/Description:[ \t]*//')
|
||||
IFS=$'\t' read -r index imgEdition <<< "$line"
|
||||
|
||||
[[ ! "$index" =~ ^[0-9]+$ ]] && continue
|
||||
[[ "${imgEdition,,}" != "${edition,,}" ]] && continue
|
||||
|
||||
installSize=$(stat -c%s "$iso")
|
||||
installSize=$(( installSize + installPad ))
|
||||
installSize=$(( size + installPad ))
|
||||
|
||||
/run/progress.sh "$installWim" "$installSize" "$msg ([P])..." &
|
||||
|
||||
wimlib-imagex export "$iso" "$index" "$installWim" --quiet || {
|
||||
local ret=$?
|
||||
ret=$?
|
||||
fKill "progress.sh"
|
||||
error "Addition of $index to the $desc image failed ($ret)"
|
||||
return 1
|
||||
@@ -710,8 +795,9 @@ extractImage() {
|
||||
local iso="$1"
|
||||
local dir="$2"
|
||||
local version="$3"
|
||||
local desc="local ISO"
|
||||
|
||||
local file size
|
||||
local desc="local ISO"
|
||||
|
||||
if [ -z "$CUSTOM" ]; then
|
||||
desc="downloaded ISO"
|
||||
@@ -766,6 +852,8 @@ extractImage() {
|
||||
|
||||
else
|
||||
|
||||
# UNPACK archives contain another ISO. Extract the nested ISO, then
|
||||
# preserve it as the actual source media for subsequent processing.
|
||||
file=$(find "$dir" -maxdepth 1 -type f -iname "*.iso" -print -quit)
|
||||
|
||||
if [ -z "$file" ]; then
|
||||
@@ -799,7 +887,11 @@ setMachine() {
|
||||
local dir="$3"
|
||||
local desc="$4"
|
||||
|
||||
ETFS="boot/etfsboot.com"
|
||||
if [[ "${id,,}" != "win9"* ]]; then
|
||||
ETFS="boot/etfsboot.com"
|
||||
else
|
||||
ETFS="[BOOT]/Boot-1.44M.img"
|
||||
fi
|
||||
|
||||
local version=""
|
||||
case "${id,,}" in
|
||||
@@ -903,12 +995,13 @@ prepareImage() {
|
||||
|
||||
local iso="$1"
|
||||
local dir="$2"
|
||||
|
||||
local desc missing
|
||||
|
||||
desc=$(printVariant "$DETECTED" "$DETECTED")
|
||||
|
||||
if [[ "${BOOT_MODE,,}" == "windows_legacy" &&
|
||||
"${DETECTED,,}" != "win9"* ]]; then
|
||||
# Legacy rebuilt media must retain the source ISO's El Torito boot-load size.
|
||||
if [[ "${BOOT_MODE,,}" == "windows_legacy" && "${DETECTED,,}" != "win9"* ]]; then
|
||||
getBootLoadSize "$iso" "$dir" "$desc" || return 1
|
||||
fi
|
||||
|
||||
@@ -917,13 +1010,14 @@ prepareImage() {
|
||||
if [[ "${BOOT_MODE,,}" == "windows_legacy" ]]; then
|
||||
|
||||
extractBootImage "$iso" "$dir" "$desc" && return 0
|
||||
|
||||
error "Failed to extract boot image from ISO image \"${iso}\"!"
|
||||
|
||||
return 1
|
||||
fi
|
||||
|
||||
EFISYS="efi/microsoft/boot/efisys_noprompt.bin"
|
||||
|
||||
# A modern rebuilt ISO requires both its BIOS and no-prompt UEFI boot images.
|
||||
[ -f "$dir/$ETFS" ] && [ -s "$dir/$ETFS" ] &&
|
||||
[ -f "$dir/$EFISYS" ] && [ -s "$dir/$EFISYS" ] && return 0
|
||||
|
||||
@@ -954,7 +1048,8 @@ addFolder() {
|
||||
local target="${2:-image}"
|
||||
local log="${3:-Y}"
|
||||
local mode="${4:-copy}"
|
||||
local folder file="" source=""
|
||||
|
||||
local file="" source="" folder
|
||||
local dest="$src/\$OEM\$/\$1/OEM"
|
||||
local install="$src/.overlay-install.bat"
|
||||
|
||||
@@ -967,6 +1062,8 @@ addFolder() {
|
||||
info "$msg" && html "$msg"
|
||||
fi
|
||||
|
||||
# Setup-image mode cannot modify the original ISO, so create a temporary
|
||||
# writable copy of install.bat for the overlay image.
|
||||
if [ "$mode" = "overlay" ]; then
|
||||
|
||||
rm -f -- "$install" || return 1
|
||||
@@ -1037,6 +1134,7 @@ addDriver() {
|
||||
local path="$2"
|
||||
local target="$3"
|
||||
local driver="$4"
|
||||
|
||||
local folder="" desc
|
||||
|
||||
if [ -z "$id" ]; then
|
||||
@@ -1074,8 +1172,7 @@ addDriver() {
|
||||
|
||||
case "${id,,}" in
|
||||
"winvista"* )
|
||||
[[ "${driver,,}" == "viorng" ]] && return 0
|
||||
;;
|
||||
[[ "${driver,,}" == "viorng" ]] && return 0 ;;
|
||||
esac
|
||||
|
||||
local dest="$path/$target/$driver"
|
||||
@@ -1093,6 +1190,7 @@ addDrivers() {
|
||||
local file="${4:-}"
|
||||
local index="${5:-}"
|
||||
local log="${6:-Y}"
|
||||
|
||||
local drivers="$tmp/drivers"
|
||||
|
||||
rm -rf "$drivers"
|
||||
@@ -1114,6 +1212,7 @@ addDrivers() {
|
||||
|
||||
local target="\$WinPEDriver\$"
|
||||
local dest="$drivers/$target"
|
||||
|
||||
mkdir -p "$dest" || return 1
|
||||
|
||||
if [ -n "$file" ]; then
|
||||
@@ -1221,12 +1320,13 @@ updateImage() {
|
||||
local dir="$1"
|
||||
local asset="$2"
|
||||
local language="$3"
|
||||
|
||||
local script=""
|
||||
local tmp="/tmp/install"
|
||||
local xml="autounattend.xml"
|
||||
local bak="${xml//.xml/.org}"
|
||||
local dat="${xml//.xml/.dat}"
|
||||
local desc path src wim name info
|
||||
local script=""
|
||||
|
||||
skipVersion "${DETECTED,,}" && return 0
|
||||
|
||||
@@ -1255,6 +1355,7 @@ updateImage() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Windows Setup normally resides in boot image 2; single-image media uses 1.
|
||||
local idx="1"
|
||||
|
||||
if ! info=$(wimlib-imagex info -xml "$wim" | iconv -f UTF-16LE -t UTF-8); then
|
||||
@@ -1275,6 +1376,8 @@ updateImage() {
|
||||
error "Failed to add OEM folder to image!"
|
||||
fi
|
||||
|
||||
# Preserve an original answer file only once. The .dat marker identifies an
|
||||
# image where our generated answer file has already been installed.
|
||||
if wimlib-imagex extract "$wim" "$idx" "/$xml" "--dest-dir=$tmp" >/dev/null 2>&1; then
|
||||
if ! wimlib-imagex extract "$wim" "$idx" "/$dat" "--dest-dir=$tmp" >/dev/null 2>&1; then
|
||||
if ! wimlib-imagex extract "$wim" "$idx" "/$bak" "--dest-dir=$tmp" >/dev/null 2>&1; then
|
||||
@@ -1314,7 +1417,7 @@ updateImage() {
|
||||
validateGeneratedXML "$answer" || return 1
|
||||
|
||||
if [ -z "${CUSTOM_XML:-}" ]; then
|
||||
prepareSetupScript "$asset" "$tmp/setup" script || exit 84
|
||||
script=$(prepareSetupScript "$asset" "$tmp/setup") || exit 84
|
||||
fi
|
||||
|
||||
if ! wimlib-imagex update "$wim" "$idx" --command "add $answer /$xml" > /dev/null; then
|
||||
@@ -1328,6 +1431,8 @@ updateImage() {
|
||||
|
||||
fi
|
||||
|
||||
# Manual mode removes generated automation and restores the original answer
|
||||
# file when one was backed up earlier.
|
||||
if enabled "$MANUAL"; then
|
||||
|
||||
removeGeneratedXML "$asset" || return 1
|
||||
@@ -1342,6 +1447,8 @@ updateImage() {
|
||||
|
||||
fi
|
||||
|
||||
# Prevent a root-level answer file from overriding the selected automatic or
|
||||
# manual behavior when Windows Setup first boots.
|
||||
name="$xml"
|
||||
enabled "$MANUAL" && name="$bak"
|
||||
path=$(find "$dir" -maxdepth 1 -type f -iname "$name" -print -quit) || return 1
|
||||
@@ -1392,10 +1499,11 @@ reserveSambaPorts() {
|
||||
backup () {
|
||||
|
||||
local iso="$1"
|
||||
|
||||
local count=1
|
||||
local name="unknown"
|
||||
local root="$STORAGE/backups"
|
||||
local file previous failed=""
|
||||
local failed="" file previous
|
||||
|
||||
previous=$(readState "base") || return 1
|
||||
[ -n "$previous" ] && name="${previous%.*}"
|
||||
@@ -1437,6 +1545,8 @@ backup () {
|
||||
-not -iname '*.iso' -print0
|
||||
)
|
||||
|
||||
# Wait for the process-substitution find command so enumeration failures are
|
||||
# detected rather than being mistaken for a successful backup.
|
||||
local find_pid=$!
|
||||
|
||||
if ! wait "$find_pid"; then
|
||||
@@ -1461,6 +1571,8 @@ restoreBootMode() {
|
||||
|
||||
[ -n "$mode" ] || return 0
|
||||
|
||||
# A saved legacy mode always wins. A saved modern mode only replaces the
|
||||
# default mode and never an explicit user-selected boot configuration.
|
||||
if [[ "${mode,,}" == "windows_legacy" ]]; then
|
||||
BOOT_MODE="$mode"
|
||||
return 0
|
||||
@@ -1476,6 +1588,8 @@ restoreBootMode() {
|
||||
|
||||
restoreMachine() {
|
||||
|
||||
# Restore the saved machine only when q35 is still the default; an explicit
|
||||
# user-selected machine must remain untouched.
|
||||
[[ "${PLATFORM,,}" != "x64" ]] && return 0
|
||||
[[ "${MACHINE,,}" != "q35" ]] && return 0
|
||||
|
||||
|
||||
+214
-153
@@ -6,6 +6,7 @@ handleCurlError() {
|
||||
local code="$1"
|
||||
local server="$2"
|
||||
local reason="${3:-}"
|
||||
|
||||
local signal
|
||||
|
||||
if [ -n "$reason" ] && (( code <= 125 )); then
|
||||
@@ -29,8 +30,7 @@ handleCurlError() {
|
||||
SEGV | ABRT) error "Curl crashed with signal $signal." ;;
|
||||
"") error "Curl terminated with exit status $code." ;;
|
||||
*) error "Curl terminated due to signal $signal." ;;
|
||||
esac
|
||||
;;
|
||||
esac ;;
|
||||
esac
|
||||
|
||||
return 1
|
||||
@@ -38,10 +38,9 @@ handleCurlError() {
|
||||
|
||||
curlRequest() {
|
||||
|
||||
local output="$1"
|
||||
local server="$2"
|
||||
local agent="$3"
|
||||
shift 3
|
||||
local server="$1"
|
||||
local agent="$2"
|
||||
shift 2
|
||||
|
||||
local log reason response
|
||||
|
||||
@@ -50,6 +49,8 @@ curlRequest() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Preserve curl's status under errexit so its stderr can be translated
|
||||
# into a useful error instead of terminating the script immediately.
|
||||
{
|
||||
response=$(LC_ALL=C curl \
|
||||
--silent \
|
||||
@@ -76,10 +77,7 @@ curlRequest() {
|
||||
|
||||
rm -f "$log"
|
||||
|
||||
if [ -n "$output" ]; then
|
||||
printf -v "$output" '%s' "$response"
|
||||
fi
|
||||
|
||||
printf '%s' "$response"
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -93,13 +91,13 @@ downloadWindowsLink() {
|
||||
local desc="$6"
|
||||
local type="$7"
|
||||
|
||||
local skuId skuJson
|
||||
local linkJson link
|
||||
local ovData ovTime session
|
||||
local ovToken="" ovTicks=""
|
||||
local profile="606624d44113"
|
||||
local skuId skuJson linkJson
|
||||
local link ovData ovTime session
|
||||
|
||||
# uuidgen: For MacOS (installed by default) and other systems (e.g. with no /proc) that don't have a kernel interface for generating random UUIDs
|
||||
# Prefer the Linux kernel UUID source, with uuidgen as a portable fallback
|
||||
# for macOS and systems without /proc.
|
||||
if ! session=$(cat /proc/sys/kernel/random/uuid 2> /dev/null || uuidgen --random); then
|
||||
error "Failed to generate session ID!"
|
||||
return 1
|
||||
@@ -112,32 +110,32 @@ downloadWindowsLink() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Microsoft download "protection" requires the sessionId to be whitelisted through vlscppe.microsoft.com/tags
|
||||
# Register the session with Microsoft's anti-abuse endpoint before
|
||||
# requesting SKU or download links.
|
||||
|
||||
local orgId="y6jn8c31"
|
||||
local vlsUrl="https://vlscppe.microsoft.com/tags?org_id=$orgId&session_id=$session"
|
||||
|
||||
enabled "$DEBUG" && echo "Getting Session ID: $session"
|
||||
|
||||
# Permit Session ID
|
||||
curlRequest "" "Microsoft" "$agent" \
|
||||
curlRequest "Microsoft" "$agent" \
|
||||
--output /dev/null \
|
||||
--header "Accept:" \
|
||||
--max-filesize 100K \
|
||||
-- "$vlsUrl" || return 1
|
||||
|
||||
# Microsoft download "protection" also requires an ov-df.microsoft.com request/reply
|
||||
# 1) Request mdt.js to get w and rticks. InstanceId is (currently) constant.
|
||||
# Complete Microsoft's ov-df challenge by retrieving a token and timing
|
||||
# value, then returning both with the current timestamp.
|
||||
|
||||
local instance="560dc9f3-1aa5-4a2f-b63c-9e18f8d0e175"
|
||||
local ovUrl="https://ov-df.microsoft.com/mdt.js?instanceId=$instance&PageId=si&session_id=$session"
|
||||
|
||||
enabled "$DEBUG" && echo -n "Getting OV data: "
|
||||
|
||||
curlRequest ovData "Microsoft" "$agent" \
|
||||
ovData=$(curlRequest "Microsoft" "$agent" \
|
||||
--header "Accept:" \
|
||||
--max-filesize 1M \
|
||||
-- "$ovUrl" || return 1
|
||||
-- "$ovUrl") || return 1
|
||||
|
||||
if [[ $ovData =~ [\?\&]w=([A-Fa-f0-9]+) ]]; then
|
||||
ovToken="${BASH_REMATCH[1]}"
|
||||
@@ -156,14 +154,12 @@ downloadWindowsLink() {
|
||||
|
||||
sleep 0.2
|
||||
|
||||
# 2) Send a reply with session ID, current epoch and previously retrieved w and rticks
|
||||
|
||||
ovTime=$(date +%s%3N)
|
||||
ovUrl="https://ov-df.microsoft.com/?session_id=$session&CustomerId=$instance&PageId=si&w=$ovToken&mdt=$ovTime&rticks=$ovTicks"
|
||||
|
||||
enabled "$DEBUG" && echo "Sending OV reply: $instance"
|
||||
|
||||
curlRequest "" "Microsoft" "$agent" \
|
||||
curlRequest "Microsoft" "$agent" \
|
||||
--output /dev/null \
|
||||
--header "Accept:" \
|
||||
--max-filesize 100K \
|
||||
@@ -173,12 +169,14 @@ downloadWindowsLink() {
|
||||
|
||||
local skuUrl="https://www.microsoft.com/software-download-connector/api/getskuinformationbyproductedition?profile=$profile&ProductEditionId=$productId&SKU=undefined&friendlyFileName=undefined&Locale=en-US&sessionID=$session"
|
||||
|
||||
curlRequest skuJson "Microsoft" "$agent" \
|
||||
skuJson=$(curlRequest "Microsoft" "$agent" \
|
||||
--referer "$url" \
|
||||
--header "Accept:" \
|
||||
--max-filesize 100K \
|
||||
-- "$skuUrl" || return 1
|
||||
-- "$skuUrl") || return 1
|
||||
|
||||
# Guard jq under errexit so malformed API data can be handled as a normal
|
||||
# missing-result error. The same pattern is reused for the link response.
|
||||
{ skuId=$(printf '%s\n' "$skuJson" | jq --arg LANG "$language" -r 'first(.Skus[]? | select(.Language == $LANG) | .Id) // empty') 2>/dev/null; local rc=$?; } || :
|
||||
|
||||
if [ -z "$skuId" ] || [[ "${skuId,,}" == "null" ]] || (( rc != 0 )); then
|
||||
@@ -190,19 +188,18 @@ downloadWindowsLink() {
|
||||
enabled "$DEBUG" && echo "$skuId"
|
||||
enabled "$DEBUG" && echo "Getting ISO download link..."
|
||||
|
||||
# Get ISO download link
|
||||
# If any request is going to be blocked by Microsoft it's always this last one (the previous requests always seem to succeed)
|
||||
# Microsoft normally applies request or IP blocking on this final connector
|
||||
# call rather than during the preceding session setup.
|
||||
|
||||
local linkUrl="https://www.microsoft.com/software-download-connector/api/GetProductDownloadLinksBySku?profile=$profile&ProductEditionId=undefined&SKU=$skuId&friendlyFileName=undefined&Locale=en-US&sessionID=$session"
|
||||
|
||||
curlRequest linkJson "Microsoft" "$agent" \
|
||||
linkJson=$(curlRequest "Microsoft" "$agent" \
|
||||
--referer "$url" \
|
||||
--header "Accept:" \
|
||||
--max-filesize 100K \
|
||||
-- "$linkUrl" || return 1
|
||||
-- "$linkUrl") || return 1
|
||||
|
||||
if ! [ "$linkJson" ]; then
|
||||
# This should only happen if there's been some change to how this API works
|
||||
error "Microsoft servers gave us an empty response to our request for an automated download."
|
||||
return 1
|
||||
fi
|
||||
@@ -235,9 +232,8 @@ downloadWindows() {
|
||||
local lang="$2"
|
||||
local desc="$3"
|
||||
|
||||
local agent language
|
||||
local page productId
|
||||
local type winVer
|
||||
local agent language page
|
||||
local productId type winVer
|
||||
|
||||
agent=$(getAgent)
|
||||
language=$(getLanguage "$lang" "name")
|
||||
@@ -246,22 +242,18 @@ downloadWindows() {
|
||||
"win10x64" )
|
||||
productId="2618"
|
||||
winVer="10"
|
||||
type="1"
|
||||
;;
|
||||
type="1" ;;
|
||||
"win11x64" )
|
||||
productId="3321"
|
||||
winVer="11"
|
||||
type="1"
|
||||
;;
|
||||
type="1" ;;
|
||||
"win11arm64" )
|
||||
productId="3324"
|
||||
winVer="11arm64"
|
||||
type="2"
|
||||
;;
|
||||
type="2" ;;
|
||||
* )
|
||||
error "Invalid VERSION specified, value \"$id\" is not recognized!"
|
||||
return 1
|
||||
;;
|
||||
return 1 ;;
|
||||
esac
|
||||
|
||||
local url="https://www.microsoft.com/en-us/software-download/windows$winVer"
|
||||
@@ -275,14 +267,16 @@ downloadWindows() {
|
||||
|
||||
sleep 1
|
||||
|
||||
# Product edition IDs can change. If the configured ID fails, recover the
|
||||
# current value from Microsoft's public download page and retry once.
|
||||
local msg="retrying using a different method..."
|
||||
info "Microsoft download request failed, $msg"
|
||||
enabled "$DEBUG" && echo "Parsing download page: ${url}"
|
||||
|
||||
curlRequest page "Microsoft" "$agent" \
|
||||
page=$(curlRequest "Microsoft" "$agent" \
|
||||
--header "Accept:" \
|
||||
--max-filesize 1M \
|
||||
-- "$url" || return 1
|
||||
-- "$url") || return 1
|
||||
|
||||
enabled "$DEBUG" && echo -n "Getting Product edition ID: "
|
||||
productId=$(printf '%s' "$page" |
|
||||
@@ -310,6 +304,7 @@ downloadWindowsEval() {
|
||||
local id="$1"
|
||||
local lang="$2"
|
||||
local desc="$3"
|
||||
|
||||
local culture compare type
|
||||
local agent language winVer
|
||||
|
||||
@@ -354,13 +349,12 @@ downloadWindowsEval() {
|
||||
|
||||
enabled "$DEBUG" && echo "Parsing download page: ${url}"
|
||||
|
||||
curlRequest page "Microsoft" "$agent" \
|
||||
page=$(curlRequest "Microsoft" "$agent" \
|
||||
--location \
|
||||
--max-filesize 1M \
|
||||
-- "$url" || return 1
|
||||
-- "$url") || return 1
|
||||
|
||||
if ! [ "$page" ]; then
|
||||
# This should only happen if there's been some change to where this download page is located
|
||||
error "Windows server download page gave us an empty response"
|
||||
return 1
|
||||
fi
|
||||
@@ -375,7 +369,8 @@ downloadWindowsEval() {
|
||||
grep -Eio "https://go\.microsoft\.com/fwlink(/p)?/\?[^\"'<>[:space:]]+" |
|
||||
grep -Ei '(^|[?&])culture='"${culture,,}"'(&|$)' |
|
||||
grep -Ei '(^|[?&])country='"${country,,}"'(&|$)') || {
|
||||
# This should only happen if there's been some change to the download endpoint web address
|
||||
# Distinguish a changed or missing English page from an unavailable
|
||||
# translation for an otherwise supported product.
|
||||
if [[ "${lang,,}" == "en" || "${lang,,}" == "en-"* ]]; then
|
||||
error "Windows server download page gave us no download link!"
|
||||
else
|
||||
@@ -385,6 +380,8 @@ downloadWindowsEval() {
|
||||
return 1
|
||||
}
|
||||
|
||||
# Evaluation pages currently expose several matching fwlinks in a known
|
||||
# product/platform order, so select the entry for the requested variant.
|
||||
case "$type" in
|
||||
"iot" )
|
||||
case "${PLATFORM,,}" in
|
||||
@@ -420,19 +417,21 @@ downloadWindowsEval() {
|
||||
|
||||
[ -z "$link" ] && error "Could not parse download link from page!" && return 1
|
||||
|
||||
# Follow redirect so proceeding log message is useful
|
||||
# This is a request we make that Fido doesn't
|
||||
# Resolve the fwlink now so later logging and platform validation use the
|
||||
# actual ISO URL rather than Microsoft's generic redirect.
|
||||
|
||||
curlRequest link "Microsoft" "$agent" \
|
||||
link=$(curlRequest "Microsoft" "$agent" \
|
||||
--location \
|
||||
--output /dev/null \
|
||||
--write-out "%{url_effective}" \
|
||||
--head \
|
||||
-- "$link" || return 1
|
||||
-- "$link") || return 1
|
||||
|
||||
local lower="${link,,}"
|
||||
local separator='(^|[[:space:]_./-])'
|
||||
|
||||
# Guard against page-order changes resolving to the wrong architecture
|
||||
# before downloading a multi-gigabyte image.
|
||||
case "${PLATFORM,,}" in
|
||||
"x64" )
|
||||
if [[ "$lower" =~ ${separator}(arm64|a64) ]]; then
|
||||
@@ -450,6 +449,8 @@ downloadWindowsEval() {
|
||||
fi ;;
|
||||
esac
|
||||
|
||||
# During debug verification, compare the resolved filename with the static
|
||||
# catalog entry to expose unexpected changes on Microsoft's page.
|
||||
if enabled "$DEBUG" && enabled "$VERIFY" && [[ "${lang,,}" == "en"* ]]; then
|
||||
|
||||
compare=$(getMido "$id" "$lang" "")
|
||||
@@ -494,14 +495,11 @@ getMidoDetected() {
|
||||
# Derive the normal answer-file identity from the requested download route.
|
||||
case "$default" in
|
||||
*"-enterprise-ltsc-eval" )
|
||||
default="${default%-enterprise-ltsc-eval}-ltsc"
|
||||
;;
|
||||
default="${default%-enterprise-ltsc-eval}-ltsc" ;;
|
||||
*"-enterprise-iot-eval" )
|
||||
default="${default%-enterprise-iot-eval}-iot"
|
||||
;;
|
||||
default="${default%-enterprise-iot-eval}-iot" ;;
|
||||
*"-eval" )
|
||||
default="${default%-eval}"
|
||||
;;
|
||||
default="${default%-eval}" ;;
|
||||
esac
|
||||
|
||||
# Preserve a genuinely different DETECTED override.
|
||||
@@ -513,17 +511,13 @@ getMidoDetected() {
|
||||
# Select the answer-file identity for the source that actually succeeded.
|
||||
case "$source" in
|
||||
*"-enterprise-ltsc-eval" )
|
||||
detected="${source%-enterprise-ltsc-eval}-ltsc-eval"
|
||||
;;
|
||||
detected="${source%-enterprise-ltsc-eval}-ltsc-eval" ;;
|
||||
*"-enterprise-iot-eval" )
|
||||
detected="${source%-enterprise-iot-eval}-iot-eval"
|
||||
;;
|
||||
detected="${source%-enterprise-iot-eval}-iot-eval" ;;
|
||||
*"-eval" )
|
||||
detected="$source"
|
||||
;;
|
||||
detected="$source" ;;
|
||||
* )
|
||||
detected="${current:-$default}"
|
||||
;;
|
||||
detected="${current:-$default}" ;;
|
||||
esac
|
||||
|
||||
echo "$detected"
|
||||
@@ -535,21 +529,21 @@ downloadWindowsLtsc() {
|
||||
local id="$1"
|
||||
local lang="$2"
|
||||
local desc="$3"
|
||||
|
||||
local alternate alternate_desc
|
||||
|
||||
case "${id,,}" in
|
||||
"win11${PLATFORM,,}-enterprise-iot-eval" )
|
||||
alternate="win11${PLATFORM,,}-enterprise-ltsc-eval"
|
||||
;;
|
||||
alternate="win11${PLATFORM,,}-enterprise-ltsc-eval" ;;
|
||||
"win11${PLATFORM,,}-enterprise-ltsc-eval" )
|
||||
alternate="win11${PLATFORM,,}-enterprise-iot-eval"
|
||||
;;
|
||||
alternate="win11${PLATFORM,,}-enterprise-iot-eval" ;;
|
||||
* )
|
||||
error "Invalid VERSION specified, value \"$id\" is not recognized!"
|
||||
return 1
|
||||
;;
|
||||
return 1 ;;
|
||||
esac
|
||||
|
||||
# IoT and LTSC share related evaluation sources and may become unavailable
|
||||
# independently, so use the sibling edition as a compatibility fallback.
|
||||
if downloadWindowsEval "$id" "$lang" "$desc" > /dev/null 2>&1; then
|
||||
MIDO_SOURCE="$id"
|
||||
return 0
|
||||
@@ -574,6 +568,7 @@ getWindows() {
|
||||
local lang="$2"
|
||||
local desc="$3"
|
||||
local web_desc="$4"
|
||||
|
||||
local language edition
|
||||
|
||||
MIDO_SOURCE=""
|
||||
@@ -586,6 +581,8 @@ getWindows() {
|
||||
local web_msg="Requesting $web_desc from the Microsoft servers..."
|
||||
info "$msg" && html "$web_msg"
|
||||
|
||||
# These sources are only published in English, so avoid trying download
|
||||
# routes that cannot satisfy the requested language.
|
||||
case "${version,,}" in
|
||||
"win2008r2"* | \
|
||||
"win81${PLATFORM,,}"* | \
|
||||
@@ -598,6 +595,8 @@ getWindows() {
|
||||
fi ;;
|
||||
esac
|
||||
|
||||
# ARM64 downloads exist only for the explicitly supported Windows 11
|
||||
# routes; all other catalog entries remain x64-only.
|
||||
case "${version,,}" in
|
||||
"win10x64" ) ;;
|
||||
"win11${PLATFORM,,}" ) ;;
|
||||
@@ -610,6 +609,8 @@ getWindows() {
|
||||
fi ;;
|
||||
esac
|
||||
|
||||
# Prefer live Microsoft download routes. Unsupported or failed live routes
|
||||
# fall through to the configured static catalog below.
|
||||
case "${version,,}" in
|
||||
"win10x64" | "win11${PLATFORM,,}" )
|
||||
|
||||
@@ -621,8 +622,7 @@ getWindows() {
|
||||
"win11${PLATFORM,,}-enterprise-iot-eval" | \
|
||||
"win11${PLATFORM,,}-enterprise-ltsc-eval" )
|
||||
|
||||
downloadWindowsLtsc "$version" "$lang" "$edition" && return 0
|
||||
;;
|
||||
downloadWindowsLtsc "$version" "$lang" "$edition" && return 0 ;;
|
||||
|
||||
"win11${PLATFORM,,}-enterprise"* )
|
||||
|
||||
@@ -643,10 +643,11 @@ getWindows() {
|
||||
|
||||
* )
|
||||
error "Invalid VERSION specified, value \"$version\" is not recognized!"
|
||||
return 1
|
||||
;;
|
||||
return 1 ;;
|
||||
esac
|
||||
|
||||
# Static catalog URLs are the last resort after live Microsoft methods are
|
||||
# unavailable or have failed.
|
||||
MIDO_URL=$(getMido "$version" "$lang" "")
|
||||
[ -z "$MIDO_URL" ] && return 1
|
||||
|
||||
@@ -665,11 +666,10 @@ getBuild() {
|
||||
|
||||
local id="$1"
|
||||
local ret="$2"
|
||||
local url=""
|
||||
local name=""
|
||||
local build="$3"
|
||||
local edition=""
|
||||
|
||||
local file="catalog.xml"
|
||||
local url="" name="" edition=""
|
||||
|
||||
case "${id,,}" in
|
||||
"win11${PLATFORM,,}" )
|
||||
@@ -695,10 +695,9 @@ getCatalog() {
|
||||
|
||||
local id="$1"
|
||||
local ret="$2"
|
||||
local url=""
|
||||
local name=""
|
||||
local edition=""
|
||||
|
||||
local file="catalog.cab"
|
||||
local url="" name="" edition=""
|
||||
|
||||
if [[ "${id,,}" == "win11"* ]] && ! isCompatible; then
|
||||
# ARMv8.0 cannot run Windows 11 builds 24H2 and up.
|
||||
@@ -728,19 +727,108 @@ getCatalog() {
|
||||
"url" ) echo "$url" ;;
|
||||
"file" ) echo "$file" ;;
|
||||
"name" ) echo "$name" ;;
|
||||
"edition" ) echo '[Edition="'"${edition}"'"]' ;;
|
||||
"edition" ) echo "$edition" ;;
|
||||
*) echo "";;
|
||||
esac
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
getXmlTag() {
|
||||
parseESD() {
|
||||
|
||||
local tag="$1"
|
||||
local file="$2"
|
||||
local xml="$1"
|
||||
local version="$2"
|
||||
local lang="$3"
|
||||
local desc="$4"
|
||||
local edition="$5"
|
||||
local culture="$6"
|
||||
|
||||
xmllint --nonet --xpath "//$tag" "$file" 2>/dev/null | sed -E -e "s/<[\/]?$tag>//g" || true
|
||||
local xmlFile="${xml##*/}"
|
||||
local file_path file_sum file_size file_edition
|
||||
local file_culture file_match=0 language_match=0
|
||||
local records architecture language separator=$'\x1f'
|
||||
|
||||
ESD=""
|
||||
ESD_SUM=""
|
||||
ESD_SIZE=""
|
||||
|
||||
# Microsoft catalogs have used different XML namespaces. Match elements by
|
||||
# local name and flatten the catalog once so selection needs no temporary XML.
|
||||
if ! records=$(xmlstarlet sel \
|
||||
-T -t \
|
||||
-m "//*[local-name()='File']" \
|
||||
-v "normalize-space(*[local-name()='Architecture'])" \
|
||||
-o "$separator" \
|
||||
-v "normalize-space(*[local-name()='Edition'])" \
|
||||
-o "$separator" \
|
||||
-v "normalize-space(*[local-name()='LanguageCode'])" \
|
||||
-o "$separator" \
|
||||
-v "normalize-space(*[local-name()='FilePath'])" \
|
||||
-o "$separator" \
|
||||
-v "normalize-space(*[local-name()='Sha1'])" \
|
||||
-o "$separator" \
|
||||
-v "normalize-space(*[local-name()='Size'])" \
|
||||
-n \
|
||||
"$xml" 2>/dev/null); then
|
||||
|
||||
error "Failed to parse $xmlFile!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Track product/platform and language matches separately so failures can
|
||||
# distinguish an unavailable edition from an unavailable translation.
|
||||
while IFS="$separator" read -r \
|
||||
architecture file_edition file_culture \
|
||||
file_path file_sum file_size; do
|
||||
|
||||
[ -n "$architecture$file_path$file_sum$file_size$file_culture$file_edition" ] || continue
|
||||
|
||||
[ "${architecture,,}" = "${PLATFORM,,}" ] || continue
|
||||
|
||||
if [ -n "$edition" ] &&
|
||||
[ "${file_edition,,}" != "${edition,,}" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
file_match=1
|
||||
|
||||
[ "${file_culture,,}" = "${culture,,}" ] || continue
|
||||
|
||||
language_match=1
|
||||
ESD="$file_path"
|
||||
ESD_SUM="$file_sum"
|
||||
ESD_SIZE="$file_size"
|
||||
break
|
||||
|
||||
done <<< "$records"
|
||||
|
||||
if (( ! file_match )); then
|
||||
desc=$(printEdition "$version" "$desc" "Y")
|
||||
error "No download link available for $desc!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if (( ! language_match )); then
|
||||
desc=$(printEdition "$version" "$desc" "Y")
|
||||
language=$(getLanguage "$lang" "desc")
|
||||
error "No download in the $language language available for $desc!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ -z "$ESD" ]; then
|
||||
error "Failed to find ESD URL in $xmlFile!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ -z "$ESD_SUM" ]; then
|
||||
error "Failed to find ESD checksum in $xmlFile!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ -z "$ESD_SIZE" ]; then
|
||||
error "Failed to find ESD filesize in $xmlFile!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
@@ -751,12 +839,10 @@ getESD() {
|
||||
local version="$2"
|
||||
local lang="$3"
|
||||
local desc="$4"
|
||||
local file result culture
|
||||
local language edition catalog
|
||||
|
||||
local file culture log
|
||||
local edition catalog rc=0
|
||||
local xmlFile="products.xml"
|
||||
local esdFile="esd_edition.xml"
|
||||
local filterFile="products_filter.xml"
|
||||
local log
|
||||
|
||||
file=$(getCatalog "$version" "file")
|
||||
catalog=$(getCatalog "$version" "url")
|
||||
@@ -783,10 +869,12 @@ getESD() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Preserve wget's status under errexit so its log can provide the actual
|
||||
# server or filesystem failure reason.
|
||||
{
|
||||
LC_ALL=C wget "$catalog" -O "$dir/$file" --no-verbose --timeout=30 \
|
||||
--no-http-keep-alive --output-file="$log"
|
||||
local rc=$?
|
||||
rc=$?
|
||||
} || :
|
||||
|
||||
if (( rc != 0 )); then
|
||||
@@ -813,6 +901,8 @@ getESD() {
|
||||
|
||||
rm -f "$log"
|
||||
|
||||
# Normal catalogs arrive as CAB archives, while pinned build catalogs are
|
||||
# already XML and only need the common filename.
|
||||
if [[ "$file" == *".xml" ]]; then
|
||||
|
||||
if ! mv -f "$dir/$file" "$dir/$xmlFile"; then
|
||||
@@ -832,61 +922,13 @@ getESD() {
|
||||
|
||||
fi
|
||||
|
||||
if [ ! -f "$dir/$xmlFile" ] || [ ! -s "$dir/$xmlFile" ]; then
|
||||
if [ ! -s "$dir/$xmlFile" ]; then
|
||||
error "Failed to find $xmlFile in $file!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local query='//File[Architecture="'${PLATFORM,,}'"]'"${edition}"''
|
||||
result=$(xmllint --nonet --xpath "${query}" "$dir/$xmlFile" 2>/dev/null || true)
|
||||
|
||||
if [ -z "$result" ]; then
|
||||
|
||||
query='//File[Architecture="'${PLATFORM^^}'"]'"${edition}"''
|
||||
result=$(xmllint --nonet --xpath "${query}" "$dir/$xmlFile" 2>/dev/null || true)
|
||||
|
||||
if [ -z "$result" ]; then
|
||||
desc=$(printEdition "$version" "$desc" "Y")
|
||||
language=$(getLanguage "$lang" "desc")
|
||||
error "No download link available for $desc!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
echo -e '<Catalog>' > "$dir/$filterFile"
|
||||
echo "$result" >> "$dir/$filterFile"
|
||||
echo -e '</Catalog>'>> "$dir/$filterFile"
|
||||
|
||||
result=$(xmllint --nonet --xpath "//File[LanguageCode=\"${culture,,}\"]" "$dir/$filterFile" 2>/dev/null || true)
|
||||
|
||||
if [ -z "$result" ]; then
|
||||
desc=$(printEdition "$version" "$desc" "Y")
|
||||
language=$(getLanguage "$lang" "desc")
|
||||
error "No download in the $language language available for $desc!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "$result" > "$dir/$esdFile"
|
||||
|
||||
ESD=$(getXmlTag "FilePath" "$dir/$esdFile")
|
||||
|
||||
if [ -z "$ESD" ]; then
|
||||
error "Failed to find ESD URL in $esdFile!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
ESD_SUM=$(getXmlTag "Sha1" "$dir/$esdFile")
|
||||
|
||||
if [ -z "$ESD_SUM" ]; then
|
||||
error "Failed to find ESD checksum in $esdFile!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
ESD_SIZE=$(getXmlTag "Size" "$dir/$esdFile")
|
||||
|
||||
if [ -z "$ESD_SIZE" ]; then
|
||||
error "Failed to find ESD filesize in $esdFile!"
|
||||
if ! parseESD \
|
||||
"$dir/$xmlFile" "$version" "$lang" "$desc" "$edition" "$culture"; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
@@ -898,11 +940,12 @@ isCompressed() {
|
||||
|
||||
local url="${1%%\?*}"
|
||||
|
||||
# The ReactOS latest-build endpoint returns an archive without a filename
|
||||
# extension, so recognize its path explicitly.
|
||||
case "${url,,}" in
|
||||
*.7z | *.zip | *.rar | *.tar | *.cab | *.cpio | \
|
||||
*.lzh | *.lha | *.xar | */latest-x86-gcc-lin-rel )
|
||||
return 0
|
||||
;;
|
||||
return 0 ;;
|
||||
esac
|
||||
|
||||
return 1
|
||||
@@ -926,6 +969,9 @@ verifyFile() {
|
||||
|
||||
[ -z "$check" ] && return 0
|
||||
enabled "$VERIFY" || return 0
|
||||
|
||||
# Microsoft ESD catalogs publish SHA1, while current mirror metadata normally
|
||||
# uses SHA256; the digest length identifies which algorithm is required.
|
||||
[[ "${#check}" == "40" ]] && algo="SHA1"
|
||||
|
||||
local msg="Verifying downloaded ISO..."
|
||||
@@ -975,10 +1021,13 @@ downloadFile() {
|
||||
local desc="$4"
|
||||
local web_desc="$5"
|
||||
local connections="${6:-1}"
|
||||
|
||||
local domain dots
|
||||
local msg="Downloading $web_desc"
|
||||
local console_msg="Downloading $desc"
|
||||
local domain dots
|
||||
|
||||
# Keep mirror messages concise by reducing subdomains to the final two
|
||||
# labels, while Microsoft downloads retain the generic description.
|
||||
domain=$(echo "$url" | awk -F/ '{print $3}')
|
||||
dots=$(echo "$domain" | tr -cd '.' | wc -c)
|
||||
(( dots > 1 )) && domain=$(expr "$domain" : '.*\.\(.*\..*\)')
|
||||
@@ -1007,8 +1056,11 @@ tryDownload() {
|
||||
local desc="$6"
|
||||
local seconds="$7"
|
||||
local web_desc="$8"
|
||||
|
||||
local total minimum="104857600"
|
||||
|
||||
# Compressed archives can legitimately be much smaller than the ISO they
|
||||
# contain, so use a lower sanity threshold until extraction.
|
||||
if isCompressed "$url"; then
|
||||
minimum="10485760"
|
||||
fi
|
||||
@@ -1059,8 +1111,8 @@ fallbackEnglish() {
|
||||
local lang="$3"
|
||||
local desc="$4"
|
||||
local web_desc="$5"
|
||||
local culture web_msg
|
||||
|
||||
local culture web_msg
|
||||
local msg="No working download method was found for $desc, falling back to English..."
|
||||
info "$msg"
|
||||
|
||||
@@ -1086,12 +1138,11 @@ downloadImage() {
|
||||
local iso="$1"
|
||||
local version="$2"
|
||||
local lang="$3"
|
||||
|
||||
local requested="$version"
|
||||
local tried="n"
|
||||
local success="n"
|
||||
local seconds="5"
|
||||
local detected="$DETECTED"
|
||||
local url sum size base desc web_desc language i
|
||||
local tried="n" success="n" seconds="5"
|
||||
local i url sum size base language desc web_desc
|
||||
|
||||
if [[ "${version,,}" == "http"* ]]; then
|
||||
|
||||
@@ -1127,6 +1178,8 @@ downloadImage() {
|
||||
desc+=" in $language"
|
||||
fi
|
||||
|
||||
# Prefer a live Microsoft URL and retry link generation once before moving
|
||||
# on to ESD catalogs or mirrors.
|
||||
if isMido "$version" "$lang"; then
|
||||
|
||||
tried="y"
|
||||
@@ -1167,7 +1220,13 @@ downloadImage() {
|
||||
fi
|
||||
fi
|
||||
|
||||
if switchEdition version; then
|
||||
# Some editions share another download route. Update the effective version
|
||||
# before looking up ESD catalogs and mirrors.
|
||||
if version=$(switchEdition "$version"); then
|
||||
|
||||
if ! enabled "${DETECTED_ORG:-}"; then
|
||||
DETECTED="${SUGGEST:-$version}"
|
||||
fi
|
||||
|
||||
desc=$(printVariant "$DETECTED" "" "Y")
|
||||
web_desc=$(printVariant "$DETECTED" "")
|
||||
@@ -1196,6 +1255,8 @@ downloadImage() {
|
||||
|
||||
if [[ "$success" == "y" ]]; then
|
||||
|
||||
# Standalone ESD media requires a different extraction path, so expose
|
||||
# its real extension through the active ISO variable.
|
||||
ISO="${ISO%.*}.esd"
|
||||
|
||||
if tryDownload "$ISO" "$ESD" "$ESD_SUM" "$ESD_SIZE" "$lang" "$desc" "$seconds" "$web_desc"; then
|
||||
|
||||
+43
-1
@@ -23,6 +23,8 @@ bootStatus() {
|
||||
if [[ "${BOOT_MODE,,}" == "windows_legacy" ]]; then
|
||||
local line last recent
|
||||
|
||||
# Only inspect output produced after the most recent BIOS boot attempt so
|
||||
# stale failures from an earlier device do not affect the current state.
|
||||
line=$(grep -nE '^Booting from (Hard Disk|DVD/CD)' "$QEMU_PTY" | tail -1)
|
||||
[ -z "$line" ] && return 1
|
||||
|
||||
@@ -38,6 +40,9 @@ bootStatus() {
|
||||
return 2
|
||||
fi
|
||||
|
||||
# These BIOS messages only describe the failed device attempt. QEMU may
|
||||
# immediately continue with another boot target, so clear pending success
|
||||
# instead of treating them as a terminal failure.
|
||||
if grep -Fq \
|
||||
-e "Boot failed: not a bootable disk" \
|
||||
-e "Boot failed: Could not read from CDROM" \
|
||||
@@ -59,6 +64,8 @@ bootStatus() {
|
||||
|
||||
local line last recent
|
||||
|
||||
# OVMF logs every boot option it tries. Track the newest attempt and only
|
||||
# evaluate messages emitted from that point onward.
|
||||
line=$(grep -nE \
|
||||
'BdsDxe: starting Boot[[:xdigit:]]{4} ' \
|
||||
"$QEMU_PTY" | tail -1)
|
||||
@@ -105,6 +112,8 @@ waitForBoot() {
|
||||
|
||||
while isAlive "$pid"; do
|
||||
|
||||
# Send the boot key once, either immediately after the prompt appears or
|
||||
# shortly after firmware starts the DVD when the prompt is not logged.
|
||||
if (( ! keySent )) && needsBootKey; then
|
||||
|
||||
if keyDelay=$(bootKeyDelay); then
|
||||
@@ -151,6 +160,9 @@ waitForBoot() {
|
||||
|
||||
marker=$(getBootMarker)
|
||||
|
||||
# A firmware boot line alone is not proof that the guest started. Wait
|
||||
# briefly for a more definitive success or failure message, restarting
|
||||
# the grace period whenever firmware begins a different boot attempt.
|
||||
if [[ "$marker" != "$pendingLine" ]] || (( status != pendingType )); then
|
||||
pendingLine="$marker"
|
||||
pendingType=$status
|
||||
@@ -173,6 +185,8 @@ waitForBoot() {
|
||||
|
||||
5)
|
||||
|
||||
# A failed device attempt is transitional because firmware may continue
|
||||
# with another target. Discard any pending success decision.
|
||||
pendingType=0
|
||||
pendingLine=""
|
||||
pendingDeadline=0
|
||||
@@ -213,6 +227,8 @@ legacyBootReady() {
|
||||
last="${line#*:}"
|
||||
recent=$(tail -n +"${line%%:*}" "$QEMU_PTY")
|
||||
|
||||
# ACPI shutdown is safe once BIOS has handed control to the hard disk, unless
|
||||
# the same attempt already produced a known boot failure.
|
||||
[[ "${last,,}" != "${hard,,}"* ]] && return 1
|
||||
|
||||
grep -Fq "Loading FreeLoader..." <<< "$recent" && return 0
|
||||
@@ -226,6 +242,8 @@ legacyBootReady() {
|
||||
|
||||
ready() {
|
||||
|
||||
# The marker means installation completed previously, so shutdown no longer
|
||||
# needs to infer guest readiness from firmware output.
|
||||
[ -f "$STORAGE/windows.boot" ] && return 0
|
||||
[ ! -s "$QEMU_PTY" ] && return 1
|
||||
|
||||
@@ -239,6 +257,8 @@ ready() {
|
||||
'BdsDxe: starting Boot[[:xdigit:]]{4} ' \
|
||||
"$QEMU_PTY" | tail -1)
|
||||
|
||||
# Only a Windows Boot Manager entry loaded from a hard disk proves that setup
|
||||
# has progressed far enough for an ACPI shutdown request to be appropriate.
|
||||
grep -Eq \
|
||||
'BdsDxe: starting Boot[[:xdigit:]]{4} "Windows Boot Manager" from .*HD\(' \
|
||||
<<< "$last" && return 0
|
||||
@@ -258,6 +278,8 @@ sendKey() {
|
||||
[ ! -S "$ACPI_SOCKET" ] && return 1
|
||||
[[ "$delay" != "0" ]] && sleep "$delay"
|
||||
|
||||
# Send all repeats through one monitor connection so timing remains stable
|
||||
# and QEMU receives the sequence as one operation.
|
||||
if ! output=$(
|
||||
{
|
||||
for ((i = 1; i <= repeat; i++)); do
|
||||
@@ -272,6 +294,8 @@ sendKey() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
# The human monitor may return success at the transport level while reporting
|
||||
# a command error in its text response, so inspect that output explicitly.
|
||||
if grep -Eqi \
|
||||
-e 'unknown command' \
|
||||
-e 'unknown key' \
|
||||
@@ -312,11 +336,14 @@ bootKeyDelay() {
|
||||
|
||||
[ ! -s "$QEMU_PTY" ] && return 1
|
||||
|
||||
# A visible prompt is the safest trigger and should be answered immediately.
|
||||
if grep -Fq "Press any key to" "$QEMU_PTY"; then
|
||||
echo 0
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Some firmware or Windows versions do not log the prompt. In that case wait
|
||||
# briefly after the DVD boot attempt and send several short key presses.
|
||||
if [[ "${BOOT_MODE,,}" == "windows_legacy" ]]; then
|
||||
grep -Fq "Booting from DVD/CD" "$QEMU_PTY" || return 1
|
||||
else
|
||||
@@ -351,7 +378,8 @@ markWindowsBooted() {
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Remove CD-ROM ISO after install
|
||||
# Do not remove installation media until firmware output confirms Windows is
|
||||
# now booting from the installed disk rather than from setup media.
|
||||
ready || return 0
|
||||
|
||||
if ! touch "$file"; then
|
||||
@@ -376,6 +404,8 @@ finish() {
|
||||
|
||||
local reason=$1 failed=0
|
||||
|
||||
# QEMU_END distinguishes an expected shutdown path from an unexpected QEMU
|
||||
# exit carrying the same process status.
|
||||
if [ ! -f "$QEMU_END" ] && (( reason != 0 )); then
|
||||
failed=1
|
||||
fi
|
||||
@@ -412,6 +442,8 @@ abortDuringSetup() {
|
||||
|
||||
local code="$1"
|
||||
|
||||
# Before Windows boots from disk, ACPI may be ignored or interpreted by setup
|
||||
# itself. Terminate QEMU directly instead of waiting for a graceful shutdown.
|
||||
if [[ "${DETECTED,,}" != "reactos" ]] || [ -n "${CUSTOM:-}" ]; then
|
||||
info "Cannot send ACPI signal during $(app) setup, aborting..."
|
||||
else
|
||||
@@ -432,12 +464,16 @@ gracefulShutdown() {
|
||||
local sig="$1"
|
||||
local pid code
|
||||
|
||||
# Traps can run in subshells created by pipelines or command substitutions;
|
||||
# only the original shell may coordinate QEMU shutdown.
|
||||
[[ $BASHPID != "$TRAP_PID" ]] && return
|
||||
|
||||
code=$(signalCode "$sig")
|
||||
|
||||
if [ -f "$QEMU_END" ]; then
|
||||
|
||||
# A second Ctrl-C during an active shutdown skips the remaining grace period
|
||||
# and lets the shutdown loop force QEMU down immediately.
|
||||
if (( code == 130 && SHUTDOWN_SIGNAL == code )); then
|
||||
SHUTDOWN_SKIP=1
|
||||
echo && info "Received SIGINT again, forcing shutdown..."
|
||||
@@ -448,12 +484,16 @@ gracefulShutdown() {
|
||||
return
|
||||
fi
|
||||
|
||||
# Signal handlers must complete their own error handling and cleanup without
|
||||
# errexit terminating the shell partway through the shutdown sequence.
|
||||
set +e
|
||||
SHUTDOWN_SIGNAL=$code
|
||||
|
||||
touch "$QEMU_END"
|
||||
echo && info "Received $sig signal, sending ACPI shutdown signal..."
|
||||
|
||||
# Interactive startup may receive a signal before the PID file appears, so
|
||||
# briefly wait for it there; non-interactive operation fails immediately.
|
||||
if ! readQemuPid pid; then
|
||||
if ! interactive || ! waitQemuPid pid; then
|
||||
warn "QEMU PID file does not exist?"
|
||||
@@ -479,6 +519,8 @@ gracefulShutdown() {
|
||||
enabled "$SHUTDOWN" || return 0
|
||||
[ -n "${QEMU_TIMEOUT:-}" ] && TIMEOUT="$QEMU_TIMEOUT"
|
||||
|
||||
# Keep Ctrl-C available to interactive users without installing an unnecessary
|
||||
# SIGINT handler for background/container execution.
|
||||
if interactive; then
|
||||
_trap gracefulShutdown SIGINT
|
||||
fi
|
||||
|
||||
+53
-24
@@ -22,29 +22,32 @@ configureNetwork() {
|
||||
|
||||
if enabled "$DHCP"; then
|
||||
|
||||
hostname="$UPLINK"
|
||||
interfaces="$DEV"
|
||||
SAMBA_HOSTNAME="$UPLINK"
|
||||
SAMBA_INTERFACES="$DEV"
|
||||
|
||||
else
|
||||
|
||||
hostname="host.lan"
|
||||
SAMBA_HOSTNAME="host.lan"
|
||||
|
||||
# User-mode networking has no host bridge to bind to, so expose Samba only
|
||||
# through loopback and let QEMU's forwarding provide guest access.
|
||||
if isUserMode; then
|
||||
interfaces="lo"
|
||||
SAMBA_INTERFACES="lo"
|
||||
else
|
||||
interfaces="$BRIDGE"
|
||||
SAMBA_INTERFACES="$BRIDGE"
|
||||
fi
|
||||
|
||||
if [ -n "${SAMBA_INTERFACE:-}" ]; then
|
||||
interfaces+=",$SAMBA_INTERFACE"
|
||||
SAMBA_INTERFACES+=",$SAMBA_INTERFACE"
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
netbios="${hostname%%.*}"
|
||||
netbios="${netbios:0:15}"
|
||||
# NetBIOS names are limited to 15 visible characters.
|
||||
SAMBA_NETBIOS="${SAMBA_HOSTNAME%%.*}"
|
||||
SAMBA_NETBIOS="${SAMBA_NETBIOS:0:15}"
|
||||
|
||||
[ -z "$netbios" ] && netbios="host"
|
||||
[ -z "$SAMBA_NETBIOS" ] && SAMBA_NETBIOS="host"
|
||||
|
||||
return 0
|
||||
}
|
||||
@@ -110,6 +113,8 @@ addShare() {
|
||||
empty="Y"
|
||||
fi
|
||||
|
||||
# The generated fallback share contains only instructions and must never be
|
||||
# writable from the guest.
|
||||
if [[ "$dir" == "$tmp" ]]; then
|
||||
|
||||
readonly="Y"
|
||||
@@ -118,6 +123,8 @@ addShare() {
|
||||
|
||||
readonly="Y"
|
||||
|
||||
# Test actual write access instead of relying on mount flags or mode bits,
|
||||
# which may not reflect bind-mount and host filesystem restrictions.
|
||||
elif probe=$(mktemp "$dir/.samba-write-test.XXXXXX" 2>/dev/null); then
|
||||
|
||||
writable="Y"
|
||||
@@ -127,6 +134,8 @@ addShare() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Empty bind mounts are safe to initialize with shared-directory permissions.
|
||||
# Retry the write probe afterward because the original mode may have blocked it.
|
||||
elif [[ "$empty" == "Y" ]] && chmod 2777 "$dir" 2>/dev/null; then
|
||||
|
||||
if probe=$(mktemp "$dir/.samba-write-test.XXXXXX" 2>/dev/null); then
|
||||
@@ -146,6 +155,7 @@ addShare() {
|
||||
|
||||
if [[ "$empty" == "Y" ]]; then
|
||||
|
||||
# Keep newly created content in the shared group through the setgid bit.
|
||||
if ! chmod 2777 "$dir"; then
|
||||
error "Failed to set permissions for directory $dir" && return 1
|
||||
fi
|
||||
@@ -155,6 +165,8 @@ addShare() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Docker commonly creates a missing bind source as root. Transfer an empty
|
||||
# directory to the default non-root owner used for shared content.
|
||||
if [[ "$owner" == "0" ]]; then
|
||||
if ! chown "1000:1000" "$dir"; then
|
||||
error "Failed to set ownership for directory $dir" && return 1
|
||||
@@ -165,6 +177,7 @@ addShare() {
|
||||
|
||||
elif [[ "$readonly" != "Y" ]]; then
|
||||
|
||||
# Preserve access to non-writable mounts by exporting them read-only.
|
||||
readonly="Y"
|
||||
|
||||
fi
|
||||
@@ -200,20 +213,27 @@ writeConfig() {
|
||||
if ! {
|
||||
echo "[global]"
|
||||
echo " server string = Dockur"
|
||||
echo " netbios name = $netbios"
|
||||
echo " netbios name = $SAMBA_NETBIOS"
|
||||
echo " workgroup = WORKGROUP"
|
||||
echo " interfaces = $interfaces"
|
||||
echo " interfaces = $SAMBA_INTERFACES"
|
||||
echo " bind interfaces only = yes"
|
||||
echo " security = user"
|
||||
echo " guest account = nobody"
|
||||
echo " map to guest = Bad User"
|
||||
|
||||
# Retain SMB1 negotiation for legacy Windows guests.
|
||||
echo " server min protocol = NT1"
|
||||
|
||||
# Allow bind-mounted shares to follow symlinks outside their share root.
|
||||
echo " follow symlinks = yes"
|
||||
echo " wide links = yes"
|
||||
echo " unix extensions = no"
|
||||
echo " inherit owner = yes"
|
||||
echo " create mask = 0666"
|
||||
echo " directory mask = 02777"
|
||||
|
||||
# Perform guest filesystem access as root so bind mounts with differing host
|
||||
# ownership remain usable; share-level read-only checks still apply.
|
||||
echo " force user = root"
|
||||
echo " force group = root"
|
||||
echo " force create mode = 0666"
|
||||
@@ -241,11 +261,13 @@ selectPrimaryShare() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
share="/shared"
|
||||
[ ! -d "$share" ] && [ -d "$STORAGE/shared" ] && share="$STORAGE/shared"
|
||||
[ ! -d "$share" ] && [ -d "/data" ] && share="/data"
|
||||
[ ! -d "$share" ] && [ -d "$STORAGE/data" ] && share="$STORAGE/data"
|
||||
[ ! -d "$share" ] && share="$tmp"
|
||||
# Prefer explicit root-level bind mounts, then storage-local compatibility
|
||||
# paths. When none exist, publish an instructional read-only share.
|
||||
SAMBA_SHARE="/shared"
|
||||
[ ! -d "$SAMBA_SHARE" ] && [ -d "$STORAGE/shared" ] && SAMBA_SHARE="$STORAGE/shared"
|
||||
[ ! -d "$SAMBA_SHARE" ] && [ -d "/data" ] && SAMBA_SHARE="/data"
|
||||
[ ! -d "$SAMBA_SHARE" ] && [ -d "$STORAGE/data" ] && SAMBA_SHARE="$STORAGE/data"
|
||||
[ ! -d "$SAMBA_SHARE" ] && SAMBA_SHARE="$tmp"
|
||||
|
||||
return 0
|
||||
}
|
||||
@@ -256,6 +278,8 @@ addOptionalShare() {
|
||||
local ref="/shared$index"
|
||||
local name="Data$index"
|
||||
|
||||
# Optional shares are best-effort and must not prevent the primary share or
|
||||
# Samba service from starting.
|
||||
if [ -d "$ref" ]; then
|
||||
addShare "$ref" "$ref" "$name" "Shared" "$SAMBA_CONFIG" || :
|
||||
elif [ -d "/data$index" ]; then
|
||||
@@ -267,13 +291,13 @@ addOptionalShare() {
|
||||
|
||||
prepareSambaDirs() {
|
||||
|
||||
# Create directories if missing
|
||||
mkdir -p \
|
||||
/var/lib/samba/sysvol \
|
||||
/var/lib/samba/private \
|
||||
/var/lib/samba/bind-dns || return 1
|
||||
|
||||
# Try to repair Samba permissions
|
||||
# Runtime directories may retain restrictive modes from earlier daemon runs
|
||||
# or package defaults, so repair only the known Samba lock and core paths.
|
||||
[ -d /run/samba/msg.lock ] && chmod -R 0755 /run/samba/msg.lock 2>/dev/null || :
|
||||
[ -d /var/log/samba/cores ] && chmod -R 0700 /var/log/samba/cores 2>/dev/null || :
|
||||
[ -d /var/cache/samba/msg.lock ] && chmod -R 0755 /var/cache/samba/msg.lock 2>/dev/null || :
|
||||
@@ -300,6 +324,8 @@ startDaemon() {
|
||||
|
||||
rm -f "$log" || :
|
||||
|
||||
# Keep initialization alive after a daemon startup failure so its log can be
|
||||
# streamed and the actual Samba error remains visible to the user.
|
||||
if ! "$@"; then
|
||||
SAMBA_DEBUG="Y"
|
||||
error "Failed to start $name daemon!"
|
||||
@@ -319,7 +345,6 @@ startSamba() {
|
||||
|
||||
startNetbios() {
|
||||
|
||||
# Enable NetBIOS on Windows 7 and lower
|
||||
enabled "$DEBUG" && echo "Starting NetBIOS daemon..."
|
||||
|
||||
startDaemon "NetBIOS" "/var/log/samba/log.nmbd" \
|
||||
@@ -330,11 +355,12 @@ startNetbios() {
|
||||
|
||||
startWsddn() {
|
||||
|
||||
# Enable Web Service Discovery on Vista and up
|
||||
enabled "$DEBUG" && echo "Starting wsddn daemon..."
|
||||
|
||||
# wsddn accepts one interface, while Samba may bind to an additional
|
||||
# user-supplied interface as well.
|
||||
startDaemon "wsddn" "/var/log/wsddn.log" \
|
||||
wsddn -i "${interfaces%%,*}" -H "$hostname" \
|
||||
wsddn -i "${SAMBA_INTERFACES%%,*}" -H "$SAMBA_HOSTNAME" \
|
||||
--unixd --log-file=/var/log/wsddn.log --pid-file="$DDN_PID"
|
||||
|
||||
return 0
|
||||
@@ -346,19 +372,22 @@ html "Initializing shared folder..."
|
||||
enabled "$DEBUG" && echo "Starting Samba daemon..."
|
||||
|
||||
writeConfig || return 0
|
||||
|
||||
# Add shared folders
|
||||
selectPrimaryShare || return 0
|
||||
|
||||
addShare "$share" "/shared" "Data" "Shared" "$SAMBA_CONFIG" || return 0
|
||||
addShare "$SAMBA_SHARE" "/shared" "Data" "Shared" "$SAMBA_CONFIG" || return 0
|
||||
addOptionalShare "2" || :
|
||||
addOptionalShare "3" || :
|
||||
|
||||
prepareSambaDirs || return 0
|
||||
|
||||
startSamba || return 0
|
||||
|
||||
# User-mode networking does not expose a LAN interface where discovery
|
||||
# broadcasts would be useful.
|
||||
isUserMode && return 0
|
||||
|
||||
# Older Windows versions discover shares through NetBIOS, while modern Windows
|
||||
# uses Web Services Discovery.
|
||||
if [[ "${BOOT_MODE:-}" == "windows_legacy" ]]; then
|
||||
startNetbios || :
|
||||
else
|
||||
|
||||
Reference in New Issue
Block a user