mirror of
https://github.com/dockur/windows.git
synced 2026-08-24 15:48:59 +01:00
feat: Improve error handling (#2120)
This commit is contained in:
+107
-39
@@ -30,10 +30,21 @@ updateXML() {
|
|||||||
[ -z "${HEIGHT:-}" ] && HEIGHT="720"
|
[ -z "${HEIGHT:-}" ] && HEIGHT="720"
|
||||||
|
|
||||||
validateXMLSettings || return 1
|
validateXMLSettings || return 1
|
||||||
ensureXMLDefaultNamespace "$asset" || return 1
|
|
||||||
|
|
||||||
updateUserXML "$asset" || return 1
|
if ! ensureXMLDefaultNamespace "$asset"; then
|
||||||
updateLocaleXML "$asset" "$language" || return 1
|
error "Failed to prepare the answer file XML namespace!"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! updateUserXML "$asset"; then
|
||||||
|
error "Failed to update user and display settings in answer file!"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! updateLocaleXML "$asset" "$language"; then
|
||||||
|
error "Failed to update regional settings in answer file!"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -n "$domain" ]; then
|
if [ -n "$domain" ]; then
|
||||||
|
|
||||||
@@ -46,13 +57,24 @@ updateXML() {
|
|||||||
|
|
||||||
else
|
else
|
||||||
|
|
||||||
updateLocalAccount "$asset" || return 1
|
if ! updateLocalAccount "$asset"; then
|
||||||
|
error "Failed to update local account settings in answer file!"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
updateMembership "$asset" "$domain" "$workgroup" "$account" "$auth" || return 1
|
updateMembership "$asset" "$domain" "$workgroup" "$account" "$auth" || return 1
|
||||||
updateAutologinXML "$asset" || return 1
|
|
||||||
updateEditionXML "$asset" || return 1
|
if ! updateAutologinXML "$asset"; then
|
||||||
|
error "Failed to update automatic logon settings in answer file!"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! updateEditionXML "$asset"; then
|
||||||
|
error "Failed to update edition settings in answer file!"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
validateGeneratedXML "$asset" || return 1
|
validateGeneratedXML "$asset" || return 1
|
||||||
|
|
||||||
@@ -73,7 +95,7 @@ setXML() {
|
|||||||
|
|
||||||
if [ -d "${custom_files[0]}" ]; then
|
if [ -d "${custom_files[0]}" ]; then
|
||||||
error "The bind ${custom_files[0]} maps to a file that does not exist!"
|
error "The bind ${custom_files[0]} maps to a file that does not exist!"
|
||||||
exit 67
|
return 2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# A custom answer file always takes precedence over bundled or generated
|
# A custom answer file always takes precedence over bundled or generated
|
||||||
@@ -182,7 +204,7 @@ addAnswerFile() {
|
|||||||
|
|
||||||
if ! updateDiskID "$answer" "${DISK_TYPE:-}"; then
|
if ! updateDiskID "$answer" "${DISK_TYPE:-}"; then
|
||||||
error "Failed to adjust the Windows installation disk!"
|
error "Failed to adjust the Windows installation disk!"
|
||||||
exit 85
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! setConfigurationXML "$answer"; then
|
if ! setConfigurationXML "$answer"; then
|
||||||
@@ -194,7 +216,10 @@ addAnswerFile() {
|
|||||||
|
|
||||||
if [ -z "${CUSTOM_XML:-}" ]; then
|
if [ -z "${CUSTOM_XML:-}" ]; then
|
||||||
|
|
||||||
prepareSetupScript "$asset" "$stage" || exit 84
|
if ! prepareSetupScript "$asset" "$stage"; then
|
||||||
|
error "Failed to prepare the Windows setup script!"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -1080,7 +1105,13 @@ updateDiskID() {
|
|||||||
|
|
||||||
done <<< "$values"
|
done <<< "$values"
|
||||||
|
|
||||||
mapfile -t ids < <(printf '%s\n' "${ids[@]}" | sort -u)
|
local unique
|
||||||
|
unique=$(printf '%s\n' "${ids[@]}" | sort -u) || {
|
||||||
|
error "Failed to normalize DiskID values from answer file: $asset"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
mapfile -t ids <<< "$unique"
|
||||||
|
|
||||||
# Leave explicit multi-disk configurations untouched.
|
# Leave explicit multi-disk configurations untouched.
|
||||||
(( ${#ids[@]} == 1 )) || return 0
|
(( ${#ids[@]} == 1 )) || return 0
|
||||||
@@ -1946,10 +1977,16 @@ markGeneratedXML() {
|
|||||||
|
|
||||||
local file="$1"
|
local file="$1"
|
||||||
local marker='<!-- generated-answer-file: do not reuse as a template -->'
|
local marker='<!-- generated-answer-file: do not reuse as a template -->'
|
||||||
|
local first
|
||||||
|
|
||||||
[ -s "$file" ] || return 1
|
[ -s "$file" ] || return 1
|
||||||
|
|
||||||
if head -n 1 "$file" | grep -q '^<?xml'; then
|
if ! first=$(head -n 1 "$file"); then
|
||||||
|
error "Failed to inspect generated answer file: $file"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$first" == "<?xml"* ]]; then
|
||||||
sed -i "1a$marker" "$file" || return 1
|
sed -i "1a$marker" "$file" || return 1
|
||||||
else
|
else
|
||||||
sed -i "1i$marker" "$file" || return 1
|
sed -i "1i$marker" "$file" || return 1
|
||||||
@@ -1961,12 +1998,17 @@ markGeneratedXML() {
|
|||||||
removeGeneratedXML() {
|
removeGeneratedXML() {
|
||||||
|
|
||||||
local file="$1"
|
local file="$1"
|
||||||
|
local header
|
||||||
|
|
||||||
[ -n "$file" ] || return 0
|
[ -n "$file" ] || return 0
|
||||||
[ -f "$file" ] || return 0
|
[ -f "$file" ] || return 0
|
||||||
|
|
||||||
head -n 5 "$file" |
|
if ! header=$(head -n 5 "$file"); then
|
||||||
grep -Fqi 'generated-answer-file' || return 0
|
error "Failed to inspect answer file: $file"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
grep -Fqi 'generated-answer-file' <<< "$header" || return 0
|
||||||
|
|
||||||
if ! rm -f "$file"; then
|
if ! rm -f "$file"; then
|
||||||
error "Failed to remove generated answer file: $file"
|
error "Failed to remove generated answer file: $file"
|
||||||
@@ -2323,10 +2365,18 @@ addSIFEntry() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if grep -Fqx "$entry" "$file" || grep -Fqx "$entry"$'\r' "$file"; then
|
local rc=0
|
||||||
|
grep -Fqx -e "$entry" -e "$entry"$'\r' "$file" || rc=$?
|
||||||
|
|
||||||
|
if (( rc == 0 )); then
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if (( rc != 1 )); then
|
||||||
|
error "Failed to inspect section \"$header\" in \"$file\" !"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ "$ending" == "crlf" ]]; then
|
if [[ "$ending" == "crlf" ]]; then
|
||||||
printf '%s\r\n' "$entry"
|
printf '%s\r\n' "$entry"
|
||||||
else
|
else
|
||||||
@@ -2412,7 +2462,9 @@ addLegacyDrivers() {
|
|||||||
patchStorageDriver "$file" "$arch" || return 1
|
patchStorageDriver "$file" "$arch" || return 1
|
||||||
addSataDriver "$dir" "$target" "$arch" "$drivers" "$file" || return 1
|
addSataDriver "$dir" "$target" "$arch" "$drivers" "$file" || return 1
|
||||||
|
|
||||||
rm -rf "$drivers" || return 1
|
if ! rm -rf "$drivers"; then
|
||||||
|
warn "failed to clean temporary driver files!"
|
||||||
|
fi
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@@ -2424,7 +2476,7 @@ setLegacyKey() {
|
|||||||
local arch="$3"
|
local arch="$3"
|
||||||
local desc="$4"
|
local desc="$4"
|
||||||
|
|
||||||
local setup pid key file
|
local setup pid file block
|
||||||
setup=$(find "$target" -maxdepth 1 -type f -iname setupp.ini -print -quit) || return 1
|
setup=$(find "$target" -maxdepth 1 -type f -iname setupp.ini -print -quit) || return 1
|
||||||
|
|
||||||
[[ -n "$setup" ]] || return 0
|
[[ -n "$setup" ]] || return 0
|
||||||
@@ -2434,7 +2486,8 @@ setLegacyKey() {
|
|||||||
pid="${pid%$'\r'}"
|
pid="${pid%$'\r'}"
|
||||||
|
|
||||||
if [[ "$driver" == "2k" ]]; then
|
if [[ "$driver" == "2k" ]]; then
|
||||||
echo "${pid::-3}270" > "$setup" || return 1
|
[ "${#pid}" -ge 3 ] || return 0
|
||||||
|
echo "${pid::-3}270" > "$setup" || :
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -2447,28 +2500,44 @@ setLegacyKey() {
|
|||||||
|
|
||||||
if [[ -n "$file" ]]; then
|
if [[ -n "$file" ]]; then
|
||||||
|
|
||||||
|
local key=""
|
||||||
|
|
||||||
# Prefer a staging or OEM key already shipped on the media before falling
|
# Prefer a staging or OEM key already shipped on the media before falling
|
||||||
# back to Microsoft's documented generic installation keys.
|
# back to Microsoft's documented generic installation keys.
|
||||||
if [[ "$driver" == "2k3" ]]; then
|
if [[ "$driver" == "2k3" ]]; then
|
||||||
|
|
||||||
key=$(grep -i -A 2 "StagingKey" "$file" | tail -n 2 | head -n 1) || key=""
|
block=$(grep -i -A 2 "StagingKey" "$file") || block=""
|
||||||
|
|
||||||
|
if [ -n "$block" ]; then
|
||||||
|
key=$(printf '%s\n' "$block" | tail -n 2 | head -n 1) || key=""
|
||||||
|
fi
|
||||||
|
|
||||||
else
|
else
|
||||||
|
|
||||||
key="${pid: -8:5}"
|
key="${pid: -8:5}"
|
||||||
|
|
||||||
if [[ "${pid^^}" == *"OEM" ]]; then
|
if [[ "${pid^^}" == *"OEM" ]]; then
|
||||||
key=$(grep -i -A 2 "$key" "$file" | tail -n 2 | head -n 1) || key=""
|
|
||||||
|
block=$(grep -i -A 2 "$key" "$file") || block=""
|
||||||
|
|
||||||
else
|
else
|
||||||
key=$(grep -i -m 1 -A 2 "$key" "$file" | tail -n 2 | head -n 1) || key=""
|
|
||||||
|
block=$(grep -i -m 1 -A 2 "$key" "$file") || block=""
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$block" ]; then
|
||||||
|
key=$(printf '%s\n' "$block" | tail -n 2 | head -n 1) || key=""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
key="${key#*= }"
|
key="${key#*= }"
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
key="${key%$'\r'}"
|
if [ -n "$key" ]; then
|
||||||
[[ "${#key}" == "29" ]] && KEY="$key"
|
key="${key%$'\r'}"
|
||||||
|
[[ "${#key}" == "29" ]] && KEY="$key"
|
||||||
|
fi
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -2499,7 +2568,9 @@ setLegacyKey() {
|
|||||||
|
|
||||||
esac
|
esac
|
||||||
|
|
||||||
echo "${pid::-3}000" > "$setup" || return 1
|
if [ "${#pid}" -ge 3 ]; then
|
||||||
|
echo "${pid::-3}000" > "$setup" || :
|
||||||
|
fi
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@@ -2768,12 +2839,10 @@ disableAutoReboot() {
|
|||||||
|
|
||||||
local target="$1"
|
local target="$1"
|
||||||
|
|
||||||
local file
|
local file rc=0
|
||||||
local pattern='^[[:space:]]*HKLM[[:space:]]*,[[:space:]]*"SYSTEM\\CurrentControlSet\\Control\\CrashControl"[[:space:]]*,[[:space:]]*"AutoReboot"[[:space:]]*,[[:space:]]*[^,]*,'
|
local pattern='^[[:space:]]*HKLM[[:space:]]*,[[:space:]]*"SYSTEM\\CurrentControlSet\\Control\\CrashControl"[[:space:]]*,[[:space:]]*"AutoReboot"[[:space:]]*,[[:space:]]*[^,]*,'
|
||||||
|
|
||||||
file=$(find \
|
file=$(find "$target" -maxdepth 1 -type f -iname HIVESYS.INF -print -quit) || return 1
|
||||||
"$target" -maxdepth 1 -type f -iname HIVESYS.INF -print -quit
|
|
||||||
) || return 1
|
|
||||||
|
|
||||||
if [ -z "$file" ]; then
|
if [ -z "$file" ]; then
|
||||||
error "The file HIVESYS.INF could not be found!"
|
error "The file HIVESYS.INF could not be found!"
|
||||||
@@ -2782,19 +2851,18 @@ disableAutoReboot() {
|
|||||||
|
|
||||||
# Keep setup crashes visible instead of immediately rebooting into an
|
# Keep setup crashes visible instead of immediately rebooting into an
|
||||||
# opaque installation loop.
|
# opaque installation loop.
|
||||||
if grep -Eqi "${pattern}[[:space:]]*[^,;[:space:]]+" "$file"; then
|
grep -Eqi "${pattern}[[:space:]]*[^,;[:space:]]+" "$file" || rc=$?
|
||||||
|
|
||||||
sed -i -E \
|
case "$rc" in
|
||||||
"s|(${pattern})[[:space:]]*[^,;[:space:]]+|\\1 0|I" \
|
0 )
|
||||||
"$file" || return 1
|
sed -i -E "s|(${pattern})[[:space:]]*[^,;[:space:]]+|\\1 0|I" "$file" || :
|
||||||
|
;;
|
||||||
else
|
1 )
|
||||||
|
printf '%s\n' \
|
||||||
printf '%s\n' \
|
'HKLM,"SYSTEM\CurrentControlSet\Control\CrashControl","AutoReboot",0x00010001,0' |
|
||||||
'HKLM,"SYSTEM\CurrentControlSet\Control\CrashControl","AutoReboot",0x00010001,0' |
|
unix2dos >> "$file" || :
|
||||||
unix2dos >> "$file" || return 1
|
;;
|
||||||
|
esac
|
||||||
fi
|
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|||||||
+41
-23
@@ -647,23 +647,28 @@ getVersion() {
|
|||||||
local evaluation=""
|
local evaluation=""
|
||||||
|
|
||||||
id=$(fromName "$name" "$arch")
|
id=$(fromName "$name" "$arch")
|
||||||
|
[ -z "$id" ] && return 0
|
||||||
|
|
||||||
[[ "${name,,}" == *"evaluation"* ]] && evaluation="-eval"
|
[[ "${name,,}" == *"evaluation"* ]] && evaluation="-eval"
|
||||||
|
|
||||||
case "${id,,}" in
|
case "${id,,}" in
|
||||||
"winvista"* | "win7"* | "win8"* | "win10"* | "win11"* )
|
|
||||||
if edition=$(getEditionID "$name" "$id"); then
|
|
||||||
[ -n "$edition" ] && id+="-$edition"
|
|
||||||
[ -n "$evaluation" ] && id+="$evaluation"
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
"win20"* )
|
"win20"* )
|
||||||
|
|
||||||
if [[ "${name,,}" == *"hyper-v server"* ]]; then
|
if [[ "${name,,}" == *"hyper-v server"* ]]; then
|
||||||
id+="-hv"
|
id+="-hv"
|
||||||
elif edition=$(getServerEditionID "$name" "$id"); then
|
elif edition=$(getServerEditionID "$name" "$id"); then
|
||||||
[ -n "$edition" ] && id+="-$edition"
|
[ -n "$edition" ] && id+="-$edition"
|
||||||
[ -n "$evaluation" ] && id+="$evaluation"
|
[ -n "$evaluation" ] && id+="$evaluation"
|
||||||
fi
|
fi ;;
|
||||||
;;
|
|
||||||
|
* )
|
||||||
|
|
||||||
|
if edition=$(getEditionID "$name" "$id"); then
|
||||||
|
[ -n "$edition" ] && id+="-$edition"
|
||||||
|
[ -n "$evaluation" ] && id+="$evaluation"
|
||||||
|
fi ;;
|
||||||
|
|
||||||
esac
|
esac
|
||||||
|
|
||||||
echo "$id"
|
echo "$id"
|
||||||
@@ -695,6 +700,7 @@ getEditionRank() {
|
|||||||
|
|
||||||
case "$base" in
|
case "$base" in
|
||||||
"win20"* )
|
"win20"* )
|
||||||
|
|
||||||
case "$edition" in
|
case "$edition" in
|
||||||
"" ) echo 0 ;;
|
"" ) echo 0 ;;
|
||||||
"datacenter-azure-core" | "datacenter-azure-core-"* | \
|
"datacenter-azure-core" | "datacenter-azure-core-"* | \
|
||||||
@@ -709,9 +715,10 @@ getEditionRank() {
|
|||||||
"essentials" | "essentials-"* ) echo 5 ;;
|
"essentials" | "essentials-"* ) echo 5 ;;
|
||||||
"hv" | "hv-"* ) echo 10 ;;
|
"hv" | "hv-"* ) echo 10 ;;
|
||||||
* ) echo 99 ;;
|
* ) echo 99 ;;
|
||||||
esac
|
esac ;;
|
||||||
;;
|
|
||||||
* )
|
* )
|
||||||
|
|
||||||
case "$edition" in
|
case "$edition" in
|
||||||
"enterprise-iot" | "enterprise-iot-"* | "iot" | "iot-"* ) echo 3 ;;
|
"enterprise-iot" | "enterprise-iot-"* | "iot" | "iot-"* ) echo 3 ;;
|
||||||
"enterprise-ltsc" | "enterprise-ltsc-"* | "ltsc" | "ltsc-"* ) echo 4 ;;
|
"enterprise-ltsc" | "enterprise-ltsc-"* | "ltsc" | "ltsc-"* ) echo 4 ;;
|
||||||
@@ -723,8 +730,8 @@ getEditionRank() {
|
|||||||
"home" | "home-"* ) echo 6 ;;
|
"home" | "home-"* ) echo 6 ;;
|
||||||
"starter" | "starter-"* ) echo 7 ;;
|
"starter" | "starter-"* ) echo 7 ;;
|
||||||
* ) echo 99 ;;
|
* ) echo 99 ;;
|
||||||
esac
|
esac ;;
|
||||||
;;
|
|
||||||
esac
|
esac
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
@@ -735,6 +742,7 @@ getEditionPolicy() {
|
|||||||
local base="${1,,}"
|
local base="${1,,}"
|
||||||
|
|
||||||
case "$base" in
|
case "$base" in
|
||||||
|
|
||||||
"win20"* )
|
"win20"* )
|
||||||
printf '%s\n' \
|
printf '%s\n' \
|
||||||
"normalizeServerEditionID" \
|
"normalizeServerEditionID" \
|
||||||
@@ -750,9 +758,10 @@ getEditionPolicy() {
|
|||||||
"-datacenter-azure-core" \
|
"-datacenter-azure-core" \
|
||||||
"-enterprise-core" \
|
"-enterprise-core" \
|
||||||
"-web-core" \
|
"-web-core" \
|
||||||
"-hv"
|
"-hv" ;;
|
||||||
;;
|
|
||||||
* )
|
* )
|
||||||
|
|
||||||
printf '%s\n' \
|
printf '%s\n' \
|
||||||
"normalizeEditionID" \
|
"normalizeEditionID" \
|
||||||
"-enterprise" \
|
"-enterprise" \
|
||||||
@@ -764,8 +773,8 @@ getEditionPolicy() {
|
|||||||
"-home" \
|
"-home" \
|
||||||
"-home-premium" \
|
"-home-premium" \
|
||||||
"-home-basic" \
|
"-home-basic" \
|
||||||
"-starter"
|
"-starter" ;;
|
||||||
;;
|
|
||||||
esac
|
esac
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
@@ -774,18 +783,20 @@ getEditionPolicy() {
|
|||||||
normalizeEdition() {
|
normalizeEdition() {
|
||||||
|
|
||||||
local source="${1,,}"
|
local source="${1,,}"
|
||||||
|
local edition transliterated
|
||||||
local edition
|
|
||||||
|
|
||||||
source="${source//evaluation/}"
|
source="${source//evaluation/}"
|
||||||
source=$(printf '%s' "$source" |
|
|
||||||
uconv -x 'Any-Latin; Latin-ASCII' 2>/dev/null) || return 1
|
if transliterated=$(printf '%s' "$source" |
|
||||||
|
uconv -x 'Any-Latin; Latin-ASCII' 2>/dev/null); then
|
||||||
|
source="$transliterated"
|
||||||
|
fi
|
||||||
|
|
||||||
edition=$(sed -E \
|
edition=$(sed -E \
|
||||||
-e 's/[^a-z0-9]+/-/g' \
|
-e 's/[^a-z0-9]+/-/g' \
|
||||||
-e 's/^-+//' \
|
-e 's/^-+//' \
|
||||||
-e 's/-+$//' \
|
-e 's/-+$//' \
|
||||||
<<< "$source")
|
<<< "$source") || edition=""
|
||||||
|
|
||||||
echo "$edition"
|
echo "$edition"
|
||||||
return 0
|
return 0
|
||||||
@@ -799,11 +810,15 @@ normalizeEditionID() {
|
|||||||
edition=$(normalizeEdition "$1") || return 1
|
edition=$(normalizeEdition "$1") || return 1
|
||||||
|
|
||||||
case "$edition" in
|
case "$edition" in
|
||||||
|
|
||||||
"pro" | "professional" | "business" )
|
"pro" | "professional" | "business" )
|
||||||
edition="" ;;
|
edition="" ;;
|
||||||
|
|
||||||
"pro-n" | "pron" | "professional-n" | "professionaln" | "business-n" | "businessn" )
|
"pro-n" | "pron" | "professional-n" | "professionaln" | "business-n" | "businessn" )
|
||||||
edition="n" ;;
|
edition="n" ;;
|
||||||
|
|
||||||
* )
|
* )
|
||||||
|
|
||||||
if ! isClientEdition "$edition"; then
|
if ! isClientEdition "$edition"; then
|
||||||
case "$edition" in
|
case "$edition" in
|
||||||
*"-n" ) base="${edition%-n}" ;;
|
*"-n" ) base="${edition%-n}" ;;
|
||||||
@@ -816,10 +831,13 @@ normalizeEditionID() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
fi ;;
|
fi ;;
|
||||||
|
|
||||||
esac
|
esac
|
||||||
|
|
||||||
case "${id,,}" in
|
case "${id,,}" in
|
||||||
|
|
||||||
"win10"* | "win11"* )
|
"win10"* | "win11"* )
|
||||||
|
|
||||||
case "$edition" in
|
case "$edition" in
|
||||||
"iot-enterprise-ltsc" | \
|
"iot-enterprise-ltsc" | \
|
||||||
"iot-enterprise-ltsc-"[0-9][0-9][0-9][0-9] )
|
"iot-enterprise-ltsc-"[0-9][0-9][0-9][0-9] )
|
||||||
@@ -827,8 +845,8 @@ normalizeEditionID() {
|
|||||||
"enterprise-ltsc" | \
|
"enterprise-ltsc" | \
|
||||||
"enterprise-ltsc-"[0-9][0-9][0-9][0-9] )
|
"enterprise-ltsc-"[0-9][0-9][0-9][0-9] )
|
||||||
edition="ltsc" ;;
|
edition="ltsc" ;;
|
||||||
esac
|
esac ;;
|
||||||
;;
|
|
||||||
esac
|
esac
|
||||||
|
|
||||||
echo "$edition"
|
echo "$edition"
|
||||||
|
|||||||
+86
-25
@@ -273,13 +273,17 @@ detectLanguage() {
|
|||||||
|
|
||||||
for path in "${paths[@]}"; do
|
for path in "${paths[@]}"; do
|
||||||
|
|
||||||
lang=$(xmlstarlet sel -T -t -v "normalize-space(string(($path)[1]))" - 2>/dev/null <<< "$xml") || lang=""
|
if ! lang=$(xmlstarlet sel -T -t -v "normalize-space(string(($path)[1]))" - 2>/dev/null <<< "$xml"); then
|
||||||
|
warn "failed to read language metadata from Windows image!"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
[ -n "$lang" ] && break
|
[ -n "$lang" ] && break
|
||||||
|
|
||||||
done
|
done
|
||||||
|
|
||||||
if [ -z "$lang" ]; then
|
if [ -z "$lang" ]; then
|
||||||
warn "Language could not be detected from ISO!"
|
warn "language could not be detected from ISO!"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -290,7 +294,7 @@ detectLanguage() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
warn "Invalid language detected: \"$lang\""
|
warn "invalid language detected: \"$lang\""
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -617,7 +621,7 @@ detectLegacy() {
|
|||||||
|
|
||||||
if [ -n "$marker" ]; then
|
if [ -n "$marker" ]; then
|
||||||
error "Windows IA-64 (Itanium) images are not supported by this container!"
|
error "Windows IA-64 (Itanium) images are not supported by this container!"
|
||||||
exit 67
|
return 2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
marker=$(find "$dir" -maxdepth 1 -type d -iname WIN95 -print -quit) || return 2
|
marker=$(find "$dir" -maxdepth 1 -type d -iname WIN95 -print -quit) || return 2
|
||||||
@@ -786,7 +790,6 @@ readWimHeader() {
|
|||||||
|
|
||||||
if ! rm -f -- "$header"; then
|
if ! rm -f -- "$header"; then
|
||||||
enabled "$DEBUG" && echo "Failed to remove the previous temporary WIM header: $header" >&2
|
enabled "$DEBUG" && echo "Failed to remove the previous temporary WIM header: $header" >&2
|
||||||
error "Failed to prepare Windows image header!"
|
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -794,35 +797,30 @@ readWimHeader() {
|
|||||||
# extracting install.wim or install.esd from the ISO.
|
# extracting install.wim or install.esd from the ISO.
|
||||||
if ! udfread range --ignore-case -o "$header" "$iso" "$image" 0 208 >/dev/null 2>&1; then
|
if ! udfread range --ignore-case -o "$header" "$iso" "$image" 0 208 >/dev/null 2>&1; then
|
||||||
enabled "$DEBUG" && echo "udfread failed to read the first 208 bytes of $image from $iso." >&2
|
enabled "$DEBUG" && echo "udfread failed to read the first 208 bytes of $image from $iso." >&2
|
||||||
error "Failed to read Windows image header!"
|
|
||||||
rm -f -- "$header"
|
rm -f -- "$header"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! size=$(stat -c%s -- "$header"); then
|
if ! size=$(stat -c%s -- "$header"); then
|
||||||
enabled "$DEBUG" && echo "Failed to determine the size of the temporary WIM header: $header" >&2
|
enabled "$DEBUG" && echo "Failed to determine the size of the temporary WIM header: $header" >&2
|
||||||
error "Failed to read Windows image header!"
|
|
||||||
rm -f -- "$header"
|
rm -f -- "$header"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if (( size != 208 )); then
|
if (( size != 208 )); then
|
||||||
enabled "$DEBUG" && echo "The WIM header is $size bytes instead of the expected 208 bytes." >&2
|
enabled "$DEBUG" && echo "The WIM header is $size bytes instead of the expected 208 bytes." >&2
|
||||||
error "Failed to read Windows image header!"
|
|
||||||
rm -f -- "$header"
|
rm -f -- "$header"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! signature=$(od -An -N8 -tx1 "$header" | tr -d ' \n'); then
|
if ! signature=$(od -An -N8 -tx1 "$header" | tr -d ' \n'); then
|
||||||
enabled "$DEBUG" && echo "Failed to read the WIM header signature from $header." >&2
|
enabled "$DEBUG" && echo "Failed to read the WIM header signature from $header." >&2
|
||||||
error "Failed to read Windows image header!"
|
|
||||||
rm -f -- "$header"
|
rm -f -- "$header"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$signature" != "4d5357494d000000" ]]; then
|
if [[ "$signature" != "4d5357494d000000" ]]; then
|
||||||
enabled "$DEBUG" && echo "The WIM header has an invalid signature: ${signature:-empty}." >&2
|
enabled "$DEBUG" && echo "The WIM header has an invalid signature: ${signature:-empty}." >&2
|
||||||
error "Failed to read Windows image header!"
|
|
||||||
rm -f -- "$header"
|
rm -f -- "$header"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
@@ -1241,7 +1239,7 @@ configureImage() {
|
|||||||
|
|
||||||
if [[ "$DETECTED" == "win81x86"* || "$DETECTED" == "win10x86"* ]]; then
|
if [[ "$DETECTED" == "win81x86"* || "$DETECTED" == "win10x86"* ]]; then
|
||||||
error "The 32-bit version of $desc is not supported!"
|
error "The 32-bit version of $desc is not supported!"
|
||||||
exit 67
|
return 2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local msg="the answer file for $desc was not found ($DETECTED.xml)"
|
local msg="the answer file for $desc was not found ($DETECTED.xml)"
|
||||||
@@ -1269,18 +1267,14 @@ configureImage() {
|
|||||||
detectImageInfo() {
|
detectImageInfo() {
|
||||||
|
|
||||||
local image_info="$1"
|
local image_info="$1"
|
||||||
|
|
||||||
local desc index rc
|
local desc index rc
|
||||||
|
|
||||||
checkPlatform "$image_info" || {
|
checkPlatform "$image_info" || return 2
|
||||||
enabled "$DEBUG" && echo "Platform validation failed for the Windows image metadata." >&2
|
|
||||||
exit 67
|
|
||||||
}
|
|
||||||
|
|
||||||
local output
|
local output
|
||||||
output=$(detectVersion "$image_info") || {
|
output=$(detectVersion "$image_info") || {
|
||||||
enabled "$DEBUG" && echo "Version detection failed while parsing the Windows image metadata." >&2
|
enabled "$DEBUG" && echo "Version detection failed while parsing the Windows image metadata." >&2
|
||||||
error "Failed to detect Windows version from image metadata!"
|
error "Failed to detect the Windows version from image metadata!"
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1292,16 +1286,18 @@ detectImageInfo() {
|
|||||||
|
|
||||||
validateEdition || {
|
validateEdition || {
|
||||||
enabled "$DEBUG" && echo "Edition validation failed for detected image: ${DETECTED:-empty}, index: ${index:-empty}." >&2
|
enabled "$DEBUG" && echo "Edition validation failed for detected image: ${DETECTED:-empty}, index: ${index:-empty}." >&2
|
||||||
error "Failed to validate Windows edition from image metadata!"
|
error "Failed to validate the Windows edition from image metadata!"
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if [ -z "$DETECTED" ]; then
|
if [ -z "$DETECTED" ]; then
|
||||||
|
|
||||||
unknownImage || {
|
unknownImage || {
|
||||||
rc=$?
|
rc=$?
|
||||||
enabled "$DEBUG" && echo "Unknown-image handling failed after no Windows version could be detected (status $rc)." >&2
|
enabled "$DEBUG" && echo "Unknown-image handling failed after no Windows version could be detected (status $rc)." >&2
|
||||||
return "$rc"
|
return "$rc"
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -1337,25 +1333,84 @@ detectIsoImage() {
|
|||||||
# the caller may extract the media. Metadata parsing/configuration errors use 2.
|
# the caller may extract the media. Metadata parsing/configuration errors use 2.
|
||||||
image=$(findIsoImage "$iso") || {
|
image=$(findIsoImage "$iso") || {
|
||||||
rc=$?
|
rc=$?
|
||||||
enabled "$DEBUG" && echo "Direct ISO image lookup stopped with status $rc." >&2
|
enabled "$DEBUG" && echo "ISO image lookup failed (status $rc)." >&2
|
||||||
return "$rc"
|
return "$rc"
|
||||||
}
|
}
|
||||||
|
|
||||||
header=$(readWimHeader "$iso" "$image") || {
|
header=$(readWimHeader "$iso" "$image") || {
|
||||||
enabled "$DEBUG" && echo "Reading the WIM header failed for $image." >&2
|
error "Failed to read the Windows image header!"
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
|
|
||||||
image_info=$(readIsoImageInfo "$iso" "$image" "$header") || {
|
image_info=$(readIsoImageInfo "$iso" "$image" "$header") || {
|
||||||
enabled "$DEBUG" && echo "Reading the WIM XML metadata failed for $image." >&2
|
error "Failed to read the Windows image metadata!"
|
||||||
error "Failed to read Windows image metadata!"
|
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
|
|
||||||
info "Detecting version from ISO image..."
|
info "Detecting version from ISO image..."
|
||||||
|
|
||||||
detectImageInfo "$image_info" || {
|
detectImageInfo "$image_info" || {
|
||||||
enabled "$DEBUG" && echo "Processing the Windows image metadata failed." >&2
|
error "Failed to process the Windows image metadata!"
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
detectESDImage() {
|
||||||
|
|
||||||
|
local iso="$1"
|
||||||
|
|
||||||
|
local image_info install_info output index rc
|
||||||
|
local -a detected=()
|
||||||
|
|
||||||
|
image_info=$(wimlib-imagex info "$iso" --xml 2>/dev/null |
|
||||||
|
iconv -f UTF-16LE -t UTF-8 2>/dev/null) || {
|
||||||
|
rc=$?
|
||||||
|
error "Cannot read ESD file information (status $rc)."
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
# Microsoft download ESDs use images 1-3 for setup media, WinPE, and Windows
|
||||||
|
# Setup; images 4 and higher contain installable editions.
|
||||||
|
if ! install_info=$(xmlstarlet ed \
|
||||||
|
-d '/WIM/IMAGE[number(@INDEX) < 4]' \
|
||||||
|
<<< "$image_info" 2>/dev/null); then
|
||||||
|
error "Cannot read installable images from ESD file!"
|
||||||
|
return 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
checkPlatform "$install_info" || return 2
|
||||||
|
|
||||||
|
local output
|
||||||
|
output=$(detectVersion "$install_info") || {
|
||||||
|
error "Failed to detect Windows version from the ESD metadata!"
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
mapfile -t detected <<< "$output"
|
||||||
|
index="${detected[1]:-}"
|
||||||
|
|
||||||
|
if [ -z "$index" ]; then
|
||||||
|
error "Failed to select an installation image based on the ESD metadata!"
|
||||||
|
return 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# extractESD removes every other image, leaving the selected edition at
|
||||||
|
# index 1. Detect against that final layout so the generated answer file
|
||||||
|
# already references the index that will exist after extraction.
|
||||||
|
if ! image_info=$(xmlstarlet ed \
|
||||||
|
-d "/WIM/IMAGE[number(@INDEX) != $index]" \
|
||||||
|
-u "/WIM/IMAGE[@INDEX='$index']/@INDEX" -v '1' \
|
||||||
|
<<< "$install_info" 2>/dev/null); then
|
||||||
|
error "Cannot prepare ESD image information!"
|
||||||
|
return 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
info "Detecting version from ESD image..."
|
||||||
|
|
||||||
|
detectImageInfo "$image_info" || {
|
||||||
|
error "Failed to process the ESD image metadata!"
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1364,6 +1419,8 @@ detectIsoImage() {
|
|||||||
|
|
||||||
baseDir() {
|
baseDir() {
|
||||||
|
|
||||||
|
# TODO: Can be removed with base image 7.45+
|
||||||
|
|
||||||
local path="${1%/}"
|
local path="${1%/}"
|
||||||
|
|
||||||
[[ -z "$path" || "$path" == "/" ]] && {
|
[[ -z "$path" || "$path" == "/" ]] && {
|
||||||
@@ -1584,7 +1641,7 @@ extractESD() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
checkPlatform "$xml" || return
|
checkPlatform "$installXml" || return
|
||||||
|
|
||||||
output=$(detectVersion "$installXml") || return
|
output=$(detectVersion "$installXml") || return
|
||||||
mapfile -t detected <<< "$output"
|
mapfile -t detected <<< "$output"
|
||||||
@@ -1990,7 +2047,11 @@ buildImage() {
|
|||||||
|
|
||||||
base=$(basename "$BOOT")
|
base=$(basename "$BOOT")
|
||||||
local out="$TMP/${base%.*}.tmp"
|
local out="$TMP/${base%.*}.tmp"
|
||||||
rm -f "$out"
|
|
||||||
|
if ! rm -f "$out"; then
|
||||||
|
error "Failed to remove temporary ISO image: $out"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
desc=$(printVariant "$DETECTED" "ISO")
|
desc=$(printVariant "$DETECTED" "ISO")
|
||||||
|
|
||||||
|
|||||||
+171
-48
@@ -3,20 +3,43 @@ set -Eeuo pipefail
|
|||||||
|
|
||||||
startWindows() {
|
startWindows() {
|
||||||
|
|
||||||
parseVersion || exit 58
|
parseVersion || {
|
||||||
parseLanguage || exit 62
|
error "Failed to parse the Windows version!"
|
||||||
detectCustom || exit 64
|
exit 58
|
||||||
|
}
|
||||||
|
|
||||||
|
parseLanguage || {
|
||||||
|
error "Failed to parse the Windows language!"
|
||||||
|
exit 62
|
||||||
|
}
|
||||||
|
|
||||||
|
detectCustom || {
|
||||||
|
error "Failed to scan for custom installation media!"
|
||||||
|
exit 64
|
||||||
|
}
|
||||||
|
|
||||||
|
local rc=0
|
||||||
|
startInstall || rc=$?
|
||||||
|
(( rc > 1 )) && exit "$rc"
|
||||||
|
|
||||||
|
if (( rc )); then
|
||||||
|
|
||||||
|
bootWindows || {
|
||||||
|
error "Failed to boot Windows!"
|
||||||
|
exit 66
|
||||||
|
}
|
||||||
|
|
||||||
if ! startInstall; then
|
|
||||||
bootWindows || exit 66
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! hasImage "$ISO"; then
|
if ! hasImage "$ISO"; then
|
||||||
|
|
||||||
if ! downloadImage "$ISO" "$VERSION" "$LANGUAGE"; then
|
if ! downloadImage "$ISO" "$VERSION" "$LANGUAGE"; then
|
||||||
removeImage "$ISO" || :
|
removeImage "$ISO" || :
|
||||||
exit 68
|
exit 68
|
||||||
fi
|
fi
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local boot="$BOOT"
|
local boot="$BOOT"
|
||||||
@@ -48,11 +71,12 @@ selectWindowsImage() {
|
|||||||
XML=""
|
XML=""
|
||||||
FB="falling back to manual installation!"
|
FB="falling back to manual installation!"
|
||||||
|
|
||||||
normalizeDetected || return 70
|
normalizeDetected || :
|
||||||
|
|
||||||
if [ -n "$DETECTED" ]; then
|
if [ -n "$DETECTED" ]; then
|
||||||
|
|
||||||
if ! setImage; then
|
if ! setImage; then
|
||||||
|
error "Failed to configure the detected Windows image!"
|
||||||
return 70
|
return 70
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -61,6 +85,7 @@ selectWindowsImage() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if ! extractImage "$iso" "$dir" "$VERSION"; then
|
if ! extractImage "$iso" "$dir" "$VERSION"; then
|
||||||
|
error "Failed to extract the Windows installation image!"
|
||||||
removeImage "$iso" || :
|
removeImage "$iso" || :
|
||||||
return 72
|
return 72
|
||||||
fi
|
fi
|
||||||
@@ -76,11 +101,21 @@ selectWindowsImage() {
|
|||||||
detectIsoImage "$iso" && return 0
|
detectIsoImage "$iso" && return 0
|
||||||
|
|
||||||
rc=$?
|
rc=$?
|
||||||
(( rc == 1 )) || return 76
|
if (( rc != 1 )); then
|
||||||
|
error "Failed to inspect the Windows installation ISO!"
|
||||||
|
return 76
|
||||||
|
fi
|
||||||
|
|
||||||
|
elif [[ "${iso,,}" == *.esd ]]; then
|
||||||
|
|
||||||
|
detectESDImage "$iso" && return 0
|
||||||
|
error "Failed to inspect the Windows installation ESD!"
|
||||||
|
return 76
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! extractImage "$iso" "$dir" "$VERSION"; then
|
if ! extractImage "$iso" "$dir" "$VERSION"; then
|
||||||
|
error "Failed to extract the Windows installation image!"
|
||||||
removeImage "$iso" || :
|
removeImage "$iso" || :
|
||||||
return 74
|
return 74
|
||||||
fi
|
fi
|
||||||
@@ -90,9 +125,15 @@ selectWindowsImage() {
|
|||||||
detectImage "$dir" && return 0
|
detectImage "$dir" && return 0
|
||||||
|
|
||||||
rc=$?
|
rc=$?
|
||||||
(( rc == 1 )) || return 76
|
if (( rc != 1 )); then
|
||||||
|
error "Failed to detect the extracted Windows installation image!"
|
||||||
|
return 76
|
||||||
|
fi
|
||||||
|
|
||||||
skipUnattended "$dir" "$iso" "$boot" || return 76
|
skipUnattended "$dir" "$iso" "$boot" || {
|
||||||
|
error "Failed to fall back to manual installation!"
|
||||||
|
return 76
|
||||||
|
}
|
||||||
|
|
||||||
handled=1
|
handled=1
|
||||||
return 0
|
return 0
|
||||||
@@ -108,24 +149,37 @@ configureMachine() {
|
|||||||
desc=$(printVariant "$DETECTED" "$DETECTED") || return 78
|
desc=$(printVariant "$DETECTED" "$DETECTED") || return 78
|
||||||
|
|
||||||
if ! checkMemory "$DETECTED"; then
|
if ! checkMemory "$DETECTED"; then
|
||||||
|
|
||||||
if [ -z "$CUSTOM" ]; then
|
if [ -z "$CUSTOM" ]; then
|
||||||
useOriginalImage "$iso" || return 79
|
useOriginalImage "$iso" || {
|
||||||
|
error "Failed to preserve the original installation image!"
|
||||||
|
return 79
|
||||||
|
}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
return 79
|
return 79
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! setMachine "$DETECTED" "$iso" "$dir" "$desc"; then
|
if ! setMachine "$DETECTED" "$iso" "$dir" "$desc"; then
|
||||||
|
error "Failed to configure the virtual machine for $desc!"
|
||||||
return 80
|
return 80
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! restoreMachineState; then
|
if ! restoreMachineState; then
|
||||||
|
error "Failed to restore the saved machine state!"
|
||||||
return 82
|
return 82
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! supportsUnattended "$DETECTED"; then
|
if ! supportsUnattended "$DETECTED"; then
|
||||||
skipUnattended "$dir" "$iso" "$boot" "N" || return 83
|
|
||||||
|
skipUnattended "$dir" "$iso" "$boot" "N" || {
|
||||||
|
error "Failed to fall back to manual installation!"
|
||||||
|
return 83
|
||||||
|
}
|
||||||
|
|
||||||
handled=1
|
handled=1
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
@@ -141,16 +195,23 @@ prepareWindowsImage() {
|
|||||||
if supportsXML "$DETECTED"; then
|
if supportsXML "$DETECTED"; then
|
||||||
|
|
||||||
if ! createOverlay "$XML" "$LANGUAGE" "$TMP/setup"; then
|
if ! createOverlay "$XML" "$LANGUAGE" "$TMP/setup"; then
|
||||||
|
error "Failed to create the Windows setup overlay!"
|
||||||
return 84
|
return 84
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! createSetupImage "$TMP/setup" "$STORAGE/setup.img"; then
|
if ! createSetupImage "$TMP/setup" "$STORAGE/setup.img"; then
|
||||||
|
error "Failed to create the Windows setup image!"
|
||||||
return 86
|
return 86
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Bootable ISOs can be reused unchanged with the generated setup image.
|
# Bootable ISOs can be reused unchanged with the generated setup image.
|
||||||
if (( ! extracted )); then
|
if (( ! extracted )) && isDirectImage "$iso"; then
|
||||||
useOriginalImage "$iso" || return 88
|
|
||||||
|
useOriginalImage "$iso" || {
|
||||||
|
error "Failed to preserve the original installation image!"
|
||||||
|
return 88
|
||||||
|
}
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -158,28 +219,54 @@ prepareWindowsImage() {
|
|||||||
|
|
||||||
# Extracted modern sources and SIF-based legacy media require a clean rebuild.
|
# Extracted modern sources and SIF-based legacy media require a clean rebuild.
|
||||||
if (( ! extracted )); then
|
if (( ! extracted )); then
|
||||||
|
|
||||||
if ! extractImage "$iso" "$dir" "$VERSION"; then
|
if ! extractImage "$iso" "$dir" "$VERSION"; then
|
||||||
|
error "Failed to extract the Windows installation image!"
|
||||||
removeImage "$iso" || :
|
removeImage "$iso" || :
|
||||||
return 90
|
return 90
|
||||||
fi
|
fi
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! prepareImage "$iso" "$dir"; then
|
if ! prepareImage "$iso" "$dir"; then
|
||||||
|
error "Failed to prepare the Windows installation image!"
|
||||||
return 92
|
return 92
|
||||||
fi
|
fi
|
||||||
|
|
||||||
removeImage "$iso" || return 96
|
removeImage "$iso" || {
|
||||||
buildImage "$dir" || return 98
|
error "Failed to remove the source installation image!"
|
||||||
|
return 96
|
||||||
|
}
|
||||||
|
|
||||||
|
buildImage "$dir" || {
|
||||||
|
error "Failed to build the Windows installation image!"
|
||||||
|
return 98
|
||||||
|
}
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
bootWindows() {
|
bootWindows() {
|
||||||
|
|
||||||
restoreMachineState || return
|
if ! restoreMachineState; then
|
||||||
restoreBootMode || return
|
error "Failed to restore the saved machine state!"
|
||||||
restoreMachine || return
|
return 1
|
||||||
reserveSambaPorts || return
|
fi
|
||||||
|
|
||||||
|
if ! restoreBootMode; then
|
||||||
|
error "Failed to restore the saved boot mode!"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! restoreMachine; then
|
||||||
|
error "Failed to restore the saved machine type!"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! reserveSambaPorts; then
|
||||||
|
error "Failed to reserve Samba ports!"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@@ -211,13 +298,17 @@ startInstall() {
|
|||||||
case "${boot,,}" in
|
case "${boot,,}" in
|
||||||
"windows."* )
|
"windows."* )
|
||||||
error "The download filename \"$file\" uses the reserved \"windows.*\" namespace!"
|
error "The download filename \"$file\" uses the reserved \"windows.*\" namespace!"
|
||||||
exit 58 ;;
|
return 58 ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
else
|
else
|
||||||
|
|
||||||
local language
|
local language
|
||||||
language=$(getLanguage "$LANGUAGE" "culture")
|
if ! language=$(getLanguage "$LANGUAGE" "culture"); then
|
||||||
|
error "Failed to determine the Windows language!"
|
||||||
|
return 62
|
||||||
|
fi
|
||||||
|
|
||||||
language="${language%%-*}"
|
language="${language%%-*}"
|
||||||
|
|
||||||
if [ -n "$language" ] && [[ "${language,,}" != "en" ]]; then
|
if [ -n "$language" ] && [[ "${language,,}" != "en" ]]; then
|
||||||
@@ -235,28 +326,32 @@ startInstall() {
|
|||||||
|
|
||||||
if ! rm -rf -- "$TMP"; then
|
if ! rm -rf -- "$TMP"; then
|
||||||
error "Failed to remove directory \"$TMP\" !"
|
error "Failed to remove directory \"$TMP\" !"
|
||||||
exit 50
|
return 50
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local setup="$STORAGE/setup.img"
|
local setup="$STORAGE/setup.img"
|
||||||
|
|
||||||
if ! rm -f -- "$setup" "${setup}.tmp"; then
|
if ! rm -f -- "$setup" "${setup}.tmp"; then
|
||||||
error "Failed to remove setup image \"$setup\" !"
|
error "Failed to remove setup image \"$setup\" !"
|
||||||
exit 50
|
return 50
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local previousBase
|
local previousBase
|
||||||
if ! previousBase=$(readState "base"); then
|
if ! previousBase=$(readState "base"); then
|
||||||
error "Failed to read the previous installation state!"
|
error "Failed to read the previous installation state!"
|
||||||
exit 50
|
return 50
|
||||||
fi
|
fi
|
||||||
|
|
||||||
skipInstall "$BOOT" "$previousBase" && return 1
|
local rc=0
|
||||||
|
skipInstall "$BOOT" "$previousBase" || rc=$?
|
||||||
|
|
||||||
|
(( rc > 1 )) && return "$rc"
|
||||||
|
(( rc )) || return 1
|
||||||
|
|
||||||
if [ -z "$previousBase" ] && hasData; then
|
if [ -z "$previousBase" ] && hasData; then
|
||||||
|
|
||||||
if enabled "$SHUTDOWN" && [ ! -f "$STORAGE/windows.boot" ]; then
|
if enabled "$SHUTDOWN" && [ ! -f "$STORAGE/windows.boot" ]; then
|
||||||
discardPrevious "" || exit 50
|
discardPrevious "" || return 50
|
||||||
else
|
else
|
||||||
if ! backupPrevious ""; then
|
if ! backupPrevious ""; then
|
||||||
warn "the backup was incomplete, continuing with installation..."
|
warn "the backup was incomplete, continuing with installation..."
|
||||||
@@ -267,7 +362,7 @@ startInstall() {
|
|||||||
|
|
||||||
if ! makeDir "$TMP"; then
|
if ! makeDir "$TMP"; then
|
||||||
error "Failed to create directory \"$TMP\" !"
|
error "Failed to create directory \"$TMP\" !"
|
||||||
exit 50
|
return 50
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -z "$CUSTOM" ]; then
|
if [ -z "$CUSTOM" ]; then
|
||||||
@@ -285,27 +380,27 @@ startInstall() {
|
|||||||
if [ -n "$CUSTOM" ] || [ ! -s "$BOOT" ]; then
|
if [ -n "$CUSTOM" ] || [ ! -s "$BOOT" ]; then
|
||||||
if ! rm -f -- "$BOOT"; then
|
if ! rm -f -- "$BOOT"; then
|
||||||
error "Failed to remove obsolete ISO file \"$BOOT\" !"
|
error "Failed to remove obsolete ISO file \"$BOOT\" !"
|
||||||
exit 50
|
return 50
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! find "$STORAGE" -maxdepth 1 -type f -iname 'data.*' -not -iname '*.iso' -delete; then
|
if ! find "$STORAGE" -maxdepth 1 -type f -iname 'data.*' -not -iname '*.iso' -delete; then
|
||||||
error "Failed to remove obsolete disk files from \"$STORAGE\" !"
|
error "Failed to remove obsolete disk files from \"$STORAGE\" !"
|
||||||
exit 50
|
return 50
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! find "$STORAGE" -maxdepth 1 -type f -iname 'windows.*' -not -iname '*.iso' -delete; then
|
if ! find "$STORAGE" -maxdepth 1 -type f -iname 'windows.*' -not -iname '*.iso' -delete; then
|
||||||
error "Failed to remove obsolete Windows files from \"$STORAGE\" !"
|
error "Failed to remove obsolete Windows files from \"$STORAGE\" !"
|
||||||
exit 50
|
return 50
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! find "$STORAGE" -maxdepth 1 -type f \( -iname '*.rom' -or -iname '*.vars' \) -delete; then
|
if ! find "$STORAGE" -maxdepth 1 -type f \( -iname '*.rom' -or -iname '*.vars' \) -delete; then
|
||||||
error "Failed to remove obsolete firmware files from \"$STORAGE\" !"
|
error "Failed to remove obsolete firmware files from \"$STORAGE\" !"
|
||||||
exit 50
|
return 50
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -z "$CUSTOM" ] && [[ "${VERSION,,}" != "http"* ]]; then
|
if [ -z "$CUSTOM" ] && [[ "${VERSION,,}" != "http"* ]]; then
|
||||||
checkMemory "$VERSION" || exit 67
|
checkMemory "$VERSION" || return 67
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Work from the temporary directory so the persistent source path can
|
# Work from the temporary directory so the persistent source path can
|
||||||
@@ -313,7 +408,7 @@ startInstall() {
|
|||||||
if [ -z "$CUSTOM" ] && [ -f "$BOOT" ] && [ -s "$BOOT" ]; then
|
if [ -z "$CUSTOM" ] && [ -f "$BOOT" ] && [ -s "$BOOT" ]; then
|
||||||
if ! mv -f -- "$BOOT" "$ISO"; then
|
if ! mv -f -- "$BOOT" "$ISO"; then
|
||||||
error "Failed to move ISO file from \"$BOOT\" to \"$ISO\" !"
|
error "Failed to move ISO file from \"$BOOT\" to \"$ISO\" !"
|
||||||
exit 50
|
return 50
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -333,7 +428,7 @@ skipUnattended() {
|
|||||||
# so they cannot use the manual-install fallback.
|
# so they cannot use the manual-install fallback.
|
||||||
if ! isDirectImage "$iso"; then
|
if ! isDirectImage "$iso"; then
|
||||||
error "Failed to boot \"$iso\" because it is not a directly bootable ISO image!"
|
error "Failed to boot \"$iso\" because it is not a directly bootable ISO image!"
|
||||||
exit 60
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# When automatic preparation fails, inspect extracted media to determine
|
# When automatic preparation fails, inspect extracted media to determine
|
||||||
@@ -382,7 +477,7 @@ skipInstall() {
|
|||||||
|
|
||||||
if ! writeState "base" "$previousBase"; then
|
if ! writeState "base" "$previousBase"; then
|
||||||
error "Failed to migrate the previous installation state!"
|
error "Failed to migrate the previous installation state!"
|
||||||
exit 50
|
return 50
|
||||||
fi
|
fi
|
||||||
|
|
||||||
fi
|
fi
|
||||||
@@ -390,10 +485,12 @@ skipInstall() {
|
|||||||
# Older releases may have left a rebuilt custom ISO at its synthetic source
|
# Older releases may have left a rebuilt custom ISO at its synthetic source
|
||||||
# identity. A completed installation no longer needs that installation media.
|
# identity. A completed installation no longer needs that installation media.
|
||||||
if [[ "${previousBase,,}" == "windows."* ]] && hasData && [ -f "$marker" ]; then
|
if [[ "${previousBase,,}" == "windows."* ]] && hasData && [ -f "$marker" ]; then
|
||||||
|
|
||||||
if ! rm -f -- "$STORAGE/$previousBase"; then
|
if ! rm -f -- "$STORAGE/$previousBase"; then
|
||||||
error "Failed to remove obsolete ISO file \"$STORAGE/$previousBase\" !"
|
error "Failed to remove obsolete ISO file \"$STORAGE/$previousBase\" !"
|
||||||
exit 50
|
return 50
|
||||||
fi
|
fi
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# A changed source invalidates an unfinished installation. Back up an
|
# A changed source invalidates an unfinished installation. Back up an
|
||||||
@@ -404,7 +501,7 @@ skipInstall() {
|
|||||||
|
|
||||||
if ! rm -f -- "$STORAGE/$previousBase"; then
|
if ! rm -f -- "$STORAGE/$previousBase"; then
|
||||||
error "Failed to remove ISO file \"$STORAGE/$previousBase\" !"
|
error "Failed to remove ISO file \"$STORAGE/$previousBase\" !"
|
||||||
exit 50
|
return 50
|
||||||
fi
|
fi
|
||||||
|
|
||||||
return 1
|
return 1
|
||||||
@@ -430,7 +527,7 @@ skipInstall() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if enabled "$SHUTDOWN" && [ ! -f "$marker" ]; then
|
if enabled "$SHUTDOWN" && [ ! -f "$marker" ]; then
|
||||||
discardPrevious "$STORAGE/$previousBase" || exit 50
|
discardPrevious "$STORAGE/$previousBase" || return 50
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -469,7 +566,10 @@ finishInstall() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
local file="$STORAGE/windows.ver"
|
local file="$STORAGE/windows.ver"
|
||||||
cp -f /etc/version "$file" || return 1
|
cp -f /etc/version "$file" || {
|
||||||
|
error "Failed to save the Windows installation version!"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
if ! setOwner "$file"; then
|
if ! setOwner "$file"; then
|
||||||
warn "Failed to set the owner for \"$file\" !"
|
warn "Failed to set the owner for \"$file\" !"
|
||||||
@@ -477,15 +577,23 @@ finishInstall() {
|
|||||||
|
|
||||||
if [[ "$boot" == "$STORAGE/"* ]]; then
|
if [[ "$boot" == "$STORAGE/"* ]]; then
|
||||||
if [[ "$aborted" != [Yy1]* ]] || [ -z "$CUSTOM" ]; then
|
if [[ "$aborted" != [Yy1]* ]] || [ -z "$CUSTOM" ]; then
|
||||||
|
|
||||||
base=$(basename "$boot")
|
base=$(basename "$boot")
|
||||||
writeState "base" "$base" || return 1
|
writeState "base" "$base" || {
|
||||||
|
error "Failed to save the Windows installation source!"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "${PLATFORM,,}" == "x64" ]]; then
|
if [[ "${PLATFORM,,}" == "x64" ]]; then
|
||||||
if [[ "${BOOT_MODE,,}" == "windows_legacy" ]]; then
|
if [[ "${BOOT_MODE,,}" == "windows_legacy" ]]; then
|
||||||
|
|
||||||
writeState "mode" "$BOOT_MODE" || return 1
|
writeState "mode" "$BOOT_MODE" || {
|
||||||
|
error "Failed to save the Windows boot mode!"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
else
|
else
|
||||||
|
|
||||||
@@ -496,14 +604,22 @@ finishInstall() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if (( secure )); then
|
if (( secure )); then
|
||||||
|
|
||||||
BOOT_MODE="windows_secure"
|
BOOT_MODE="windows_secure"
|
||||||
writeState "mode" "$BOOT_MODE" || return 1
|
writeState "mode" "$BOOT_MODE" || {
|
||||||
|
error "Failed to save the Windows boot mode!"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
reserveSambaPorts || return 1
|
reserveSambaPorts || {
|
||||||
|
error "Failed to reserve Samba ports!"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
if ! rm -rf -- "$TMP"; then
|
if ! rm -rf -- "$TMP"; then
|
||||||
error "Failed to remove directory \"$TMP\" !"
|
error "Failed to remove directory \"$TMP\" !"
|
||||||
@@ -516,7 +632,6 @@ finishInstall() {
|
|||||||
findFile() {
|
findFile() {
|
||||||
|
|
||||||
local fname="$1"
|
local fname="$1"
|
||||||
|
|
||||||
local dir file base
|
local dir file base
|
||||||
local boot="$STORAGE/windows.boot"
|
local boot="$STORAGE/windows.boot"
|
||||||
|
|
||||||
@@ -896,7 +1011,12 @@ detectImage() {
|
|||||||
local image_info
|
local image_info
|
||||||
image_info=$(readImageInfo "$wim") || return $?
|
image_info=$(readImageInfo "$wim") || return $?
|
||||||
|
|
||||||
detectImageInfo "$image_info" || return 2
|
detectImageInfo "$image_info" || {
|
||||||
|
error "Failed to process the Windows image metadata!"
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareImage() {
|
prepareImage() {
|
||||||
@@ -1212,7 +1332,10 @@ createOverlay() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
addAnswerFile "$asset" "$language" "$stage" || return 1
|
addAnswerFile "$asset" "$language" "$stage" || {
|
||||||
|
error "Failed to include the Windows answer file!"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@@ -1309,8 +1432,8 @@ backupPrevious () {
|
|||||||
failed="Y"
|
failed="Y"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
[ -z "$(ls -A "$dir")" ] && rm -rf "$dir"
|
rmdir "$dir" 2>/dev/null || :
|
||||||
[ -z "$(ls -A "$root")" ] && rm -rf "$root"
|
rmdir "$root" 2>/dev/null || :
|
||||||
|
|
||||||
[ -n "$failed" ] && return 1
|
[ -n "$failed" ] && return 1
|
||||||
|
|
||||||
|
|||||||
+19
-10
@@ -175,13 +175,16 @@ downloadWindowsLink() {
|
|||||||
--max-filesize 100K \
|
--max-filesize 100K \
|
||||||
-- "$skuUrl") || return
|
-- "$skuUrl") || return
|
||||||
|
|
||||||
# Let jq parsing failures propagate so malformed API data is not mistaken
|
skuId=$(printf '%s\n' "$skuJson" | jq --arg LANG "$language" -r 'first(.Skus[]? | select(.Language == $LANG) | .Id) // empty') 2>/dev/null || skuId=""
|
||||||
# for a normal missing-result response. The same applies to the link data.
|
|
||||||
skuId=$(printf '%s\n' "$skuJson" | jq --arg LANG "$language" -r 'first(.Skus[]? | select(.Language == $LANG) | .Id) // empty') 2>/dev/null || return
|
|
||||||
|
|
||||||
if [ -z "$skuId" ] || [[ "${skuId,,}" == "null" ]]; then
|
if [ -z "$skuId" ] || [[ "${skuId,,}" == "null" ]]; then
|
||||||
language=$(getLanguage "$lang" "desc")
|
if [[ "${lang,,}" != "en" && "${lang,,}" != "en-"* ]]; then
|
||||||
error "No download in the $language language available for $desc!"
|
language=$(getLanguage "$lang" "desc")
|
||||||
|
error "No download in the $language language available for $desc!"
|
||||||
|
else
|
||||||
|
error "Microsoft server provided us no SKU ID in response to our request!"
|
||||||
|
info "Response: $skuJson"
|
||||||
|
fi
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -214,10 +217,10 @@ downloadWindowsLink() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
link=$(printf '%s\n' "$linkJson" | jq --argjson TYPE "$type" -r 'first(.ProductDownloadOptions[]? | select(.DownloadType == $TYPE) | .Uri) // empty') 2>/dev/null || return
|
link=$(printf '%s\n' "$linkJson" | jq --argjson TYPE "$type" -r 'first(.ProductDownloadOptions[]? | select(.DownloadType == $TYPE) | .Uri) // empty') 2>/dev/null || link=""
|
||||||
|
|
||||||
if [ -z "$link" ] || [[ "${link,,}" == "null" ]]; then
|
if [ -z "$link" ] || [[ "${link,,}" == "null" ]]; then
|
||||||
error "Microsoft server gave us no download link to our request for an automated download!"
|
error "Microsoft server provided us no download link to our request for an automated download!"
|
||||||
info "Response: $linkJson"
|
info "Response: $linkJson"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
@@ -287,7 +290,7 @@ downloadWindows() {
|
|||||||
grep -Eio "<option[^>]*value=[\"'][0-9]+[\"'][^>]*>[[:space:]]*Windows[^<]*" |
|
grep -Eio "<option[^>]*value=[\"'][0-9]+[\"'][^>]*>[[:space:]]*Windows[^<]*" |
|
||||||
sed -nE "s/.*value=[\"']([0-9]+)[\"'].*/\1/p" |
|
sed -nE "s/.*value=[\"']([0-9]+)[\"'].*/\1/p" |
|
||||||
sed -n '1p' |
|
sed -n '1p' |
|
||||||
cut -c 1-16) || return
|
cut -c 1-16) || productId=""
|
||||||
enabled "$DEBUG" && echo "$productId"
|
enabled "$DEBUG" && echo "$productId"
|
||||||
|
|
||||||
if [ -z "$productId" ]; then
|
if [ -z "$productId" ]; then
|
||||||
@@ -1119,7 +1122,7 @@ verifyFile() {
|
|||||||
info "Successfully verified $type!" && return 0
|
info "Successfully verified $type!" && return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
error "The downloaded file has an unknown $algo checksum: $hash , as the expected value was: $check. Please report this at $SUPPORT/issues"
|
warn "the downloaded file has an unknown $algo checksum: $hash , as the expected value was: $check. Please report this at $SUPPORT/issues"
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1280,7 +1283,11 @@ downloadImage() {
|
|||||||
desc=$(fromFile "$base")
|
desc=$(fromFile "$base")
|
||||||
web_desc="$desc"
|
web_desc="$desc"
|
||||||
|
|
||||||
tryDownload "$iso" "$version" "" "" "$desc" "$seconds" "$web_desc" || return
|
tryDownload "$iso" "$version" "" "" "$desc" "$seconds" "$web_desc" || {
|
||||||
|
rc=$?
|
||||||
|
error "Failed to download the Windows image from the specified URL!"
|
||||||
|
return "$rc"
|
||||||
|
}
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
@@ -1450,6 +1457,8 @@ downloadImage() {
|
|||||||
|
|
||||||
if [[ "$tried" == "n" ]]; then
|
if [[ "$tried" == "n" ]]; then
|
||||||
error "No download method is available for $desc!"
|
error "No download method is available for $desc!"
|
||||||
|
else
|
||||||
|
error "All download methods failed for $desc!"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
return 1
|
return 1
|
||||||
|
|||||||
Reference in New Issue
Block a user