Compare commits

...

7 Commits

Author SHA1 Message Date
Kroese f0b3967927 fix: Unbound USB variable (#1860) 2026-07-15 19:39:32 +02:00
Kroese 3b2a26e303 fix: Backwards compatibility for USB state (#1859) 2026-07-15 18:40:29 +02:00
Kroese 463935baf2 fix: Make changing ownership non-fatal (#1858) 2026-07-15 18:27:08 +02:00
Kroese 50d5f4fc09 fix: Remove deprecated Samba socket address (#1857) 2026-07-15 17:03:33 +02:00
Kroese 9421d31b91 fix: Preserve installation ISO when marker creation fails (#1856) 2026-07-15 16:25:48 +02:00
Kroese 7642a17b57 feat: Improve curl error reporting (#1855) 2026-07-15 11:34:26 +02:00
Kroese b1ff5da11a feat: Improve wget error reporting (#1854) 2026-07-15 10:29:15 +02:00
4 changed files with 286 additions and 178 deletions
+3 -6
View File
@@ -204,8 +204,7 @@ finishInstall() {
if [[ "$iso" == "$STORAGE/"* ]]; then
if ! setOwner "$iso"; then
error "Failed to set the owner for \"$iso\" !"
return 1
warn "failed to set the owner for \"$iso\" !"
fi
fi
@@ -221,8 +220,7 @@ finishInstall() {
cp -f /etc/version "$file" || return 1
if ! setOwner "$file"; then
error "Failed to set the owner for \"$file\" !"
return 1
warn "Failed to set the owner for \"$file\" !"
fi
if [[ "$iso" == "$STORAGE/"* ]]; then
@@ -1261,8 +1259,7 @@ buildImage() {
mv -f "$out" "$BOOT" || return 1
if ! setOwner "$BOOT"; then
error "Failed to set the owner for \"$BOOT\" !"
return 1
warn "Failed to set the owner for \"$BOOT\" !"
fi
return 0
+178 -90
View File
@@ -5,46 +5,85 @@ handleCurlError() {
local code="$1"
local server="$2"
local reason="${3:-}"
local signal=""
if [ -n "$reason" ] && (( code <= 125 )); then
error "Request to $server servers failed: ${reason%.}."
return 1
fi
case "$code" in
1) error "Unsupported protocol!" ;;
2) error "Failed to initialize curl!" ;;
3) error "The URL format is malformed!" ;;
5) error "Failed to resolve address of proxy host!" ;;
6) error "Failed to resolve $server servers! Is there an Internet connection?" ;;
7) error "Failed to contact $server servers! Is there an Internet connection or is the server down?" ;;
8) error "$server servers returned a malformed HTTP response!" ;;
16) error "A problem was detected in the HTTP2 framing layer!" ;;
22) error "$server servers returned a failing HTTP status code!" ;;
23) error "Failed at writing Windows media to disk! Out of disk space or permission error?" ;;
26) error "Failed to read Windows media from disk!" ;;
27) error "Ran out of memory during download!" ;;
28) error "Connection timed out to $server server!" ;;
35) error "SSL connection error from $server server!" ;;
36) error "Failed to continue earlier download!" ;;
52) error "Received no data from the $server server!" ;;
63) error "$server servers returned an unexpectedly large response!" ;;
126) error "Curl command cannot be executed!" ;;
127) error "Curl command not found!" ;;
126) error "The curl command could not be executed." ;;
127) error "The curl command was not found." ;;
*)
if (( code <= 125 )); then
# Must be some other server or network error (possibly with this specific request/file)
# This is when accounting for all possible errors in the curl manual assuming a correctly formed
# curl command and an HTTP(S) request, using only the curl features we're using, and a sane build.
error "Miscellaneous server or network error, reason: $code"
else
case "$(kill -l "$code" 2>/dev/null || true)" in
INT) error "Curl was interrupted!" ;;
SEGV | ABRT) error "Curl crashed! Please report any core dumps to curl developers." ;;
*) error "Curl terminated due to fatal signal $code !" ;;
esac
if (( code < 129 )); then
error "Request to $server servers failed with curl exit status $code."
return 1
fi
signal=$(kill -l "$((code - 128))" 2>/dev/null || true)
case "$signal" in
INT) error "Curl was interrupted." ;;
SEGV | ABRT) error "Curl crashed with signal $signal." ;;
"") error "Curl terminated with exit status $code." ;;
*) error "Curl terminated due to signal $signal." ;;
esac
;;
esac
return 1
}
curlRequest() {
local output="$1"
local server="$2"
local agent="$3"
shift 3
local log reason
local rc=0 response=""
if ! log=$(mktemp); then
error "Failed to create a temporary curl log."
return 1
fi
{
response=$(LC_ALL=C curl \
--silent \
--show-error \
--max-time 30 \
--user-agent "$agent" \
--fail \
--proto =https \
--tlsv1.2 \
--http1.1 \
"$@" 2>"$log")
rc=$?
} || :
if (( rc != 0 )); then
reason=$(sed -nE 's/^curl: \([0-9]+\) //p' "$log" | tail -n 1)
rm -f "$log"
handleCurlError "$rc" "$server" "$reason"
return 1
fi
rm -f "$log"
if [ -n "$output" ]; then
printf -v "$output" '%s' "$response"
fi
return 0
}
getAgent() {
local browser_version
@@ -62,15 +101,14 @@ downloadWindows() {
local lang="$2"
local desc="$3"
local rc=0
local ovToken="" ovTicks="" ovTime=""
local skuId="" skuUrl="" skuJson=""
local linkUrl="" linkJson="" link=""
local language="" orgId=""
local instance="" vlsUrl="" ovUrl="" ovData=""
local language="" orgId="" ovData=""
local instance="" vlsUrl="" ovUrl=""
local session="" agent="" type=""
local winVer="" page="" productId=""
local profile="606624d44113"
local rc=0 profile="606624d44113"
agent=$(getAgent)
language=$(getLanguage "$lang" "name")
@@ -102,10 +140,11 @@ downloadWindows() {
# Also, keeping a "$WindowsVersions" array like Fido does would be way too much of a maintenance burden
# Remove "Accept" header that curl sends by default
enabled "$DEBUG" && echo "Parsing download page: ${url}"
page=$(curl --silent --max-time 30 --user-agent "$agent" --header "Accept:" --max-filesize 1M --fail --proto =https --tlsv1.2 --http1.1 -- "$url") || {
handleCurlError "$?" "Microsoft"
return $?
}
curlRequest page "Microsoft" "$agent" \
--header "Accept:" \
--max-filesize 1M \
-- "$url" || return 1
enabled "$DEBUG" && echo -n "Getting Product edition ID: "
productId=$(echo "$page" | grep -Eo '<option value="[0-9]+">Windows' | cut -d '"' -f 2 | head -n 1 | tr -cd '0-9' | head -c 16)
@@ -124,11 +163,11 @@ downloadWindows() {
enabled "$DEBUG" && echo "Getting Session ID: $session"
# Permit Session ID
curl --silent --max-time 30 --output /dev/null --user-agent "$agent" --header "Accept:" --max-filesize 100K --fail --proto =https --tlsv1.2 --http1.1 -- "$vlsUrl" || {
# This should only happen if there's been some change to how this API works
handleCurlError "$?" "Microsoft"
return $?
}
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.
@@ -138,10 +177,10 @@ downloadWindows() {
enabled "$DEBUG" && echo -n "Getting OV data: "
ovData=$(curl --silent --max-time 30 --user-agent "$agent" --header "Accept:" --max-filesize 1M --fail --proto =https --tlsv1.2 --http1.1 -- "$ovUrl") || {
handleCurlError "$?" "Microsoft"
return $?
}
curlRequest ovData "Microsoft" "$agent" \
--header "Accept:" \
--max-filesize 1M \
-- "$ovUrl" || return 1
if [[ $ovData =~ [\?\&]w=([A-Fa-f0-9]+) ]]; then
ovToken="${BASH_REMATCH[1]}"
@@ -167,19 +206,21 @@ downloadWindows() {
enabled "$DEBUG" && echo "Sending OV reply: $instance"
curl --silent --max-time 30 --output /dev/null --user-agent "$agent" --header "Accept:" --max-filesize 100K --fail --proto =https --tlsv1.2 --http1.1 -- "$ovUrl" || {
# This should only happen if there's been some change to how this API works
handleCurlError "$?" "Microsoft"
return $?
}
curlRequest "" "Microsoft" "$agent" \
--output /dev/null \
--header "Accept:" \
--max-filesize 100K \
-- "$ovUrl" || return 1
enabled "$DEBUG" && echo -n "Getting language SKU ID: "
skuUrl="https://www.microsoft.com/software-download-connector/api/getskuinformationbyproductedition?profile=$profile&ProductEditionId=$productId&SKU=undefined&friendlyFileName=undefined&Locale=en-US&sessionID=$session"
skuJson=$(curl --silent --max-time 30 --request GET --user-agent "$agent" --referer "$url" --header "Accept:" --max-filesize 100K --fail --proto =https --tlsv1.2 --http1.1 -- "$skuUrl") || {
handleCurlError "$?" "Microsoft"
return $?
}
curlRequest skuJson "Microsoft" "$agent" \
--referer "$url" \
--header "Accept:" \
--max-filesize 100K \
-- "$skuUrl" || return 1
{ skuId=$(echo "$skuJson" | jq --arg LANG "$language" -r '.Skus[] | select(.Language==$LANG).Id') 2>/dev/null; rc=$?; } || :
@@ -196,10 +237,12 @@ downloadWindows() {
# If any request is going to be blocked by Microsoft it's always this last one (the previous requests always seem to succeed)
linkUrl="https://www.microsoft.com/software-download-connector/api/GetProductDownloadLinksBySku?profile=$profile&ProductEditionId=undefined&SKU=$skuId&friendlyFileName=undefined&Locale=en-US&sessionID=$session"
linkJson=$(curl --silent --max-time 30 --request GET --user-agent "$agent" --referer "$url" --header "Accept:" --max-filesize 100K --fail --proto =https --tlsv1.2 --http1.1 -- "$linkUrl") || {
handleCurlError "$?" "Microsoft"
return $?
}
curlRequest linkJson "Microsoft" "$agent" \
--referer "$url" \
--header "Accept:" \
--max-filesize 100K \
-- "$linkUrl" || return 1
if ! [ "$linkJson" ]; then
# This should only happen if there's been some change to how this API works
@@ -279,10 +322,11 @@ downloadWindowsEval() {
local url="https://www.microsoft.com/en-us/evalcenter/download-$winVer"
enabled "$DEBUG" && echo "Parsing download page: ${url}"
page=$(curl --silent --max-time 30 --user-agent "$agent" --location --max-filesize 1M --fail --proto =https --tlsv1.2 --http1.1 -- "$url") || {
handleCurlError "$?" "Microsoft"
return $?
}
curlRequest page "Microsoft" "$agent" \
--location \
--max-filesize 1M \
-- "$url" || return 1
if ! [ "$page" ]; then
# This should only happen if there's been some change to where this download page is located
@@ -346,11 +390,12 @@ downloadWindowsEval() {
# Follow redirect so proceeding log message is useful
# This is a request we make that Fido doesn't
link=$(curl --silent --max-time 30 --user-agent "$agent" --location --output /dev/null --silent --write-out "%{url_effective}" --head --fail --proto =https --tlsv1.2 --http1.1 -- "$link") || {
# This should only happen if the Microsoft servers are down
handleCurlError "$?" "Microsoft"
return $?
}
curlRequest link "Microsoft" "$agent" \
--location \
--output /dev/null \
--write-out "%{url_effective}" \
--head \
-- "$link" || return 1
case "${PLATFORM,,}" in
"x64" )
@@ -523,13 +568,12 @@ getESD() {
local version="$2"
local lang="$3"
local desc="$4"
local rc=0
local file result culture
local language edition catalog
local xmlFile="products.xml"
local esdFile="esd_edition.xml"
local filterFile="products_filter.xml"
local query
local log query rc=0 reason=""
file=$(getCatalog "$version" "file")
catalog=$(getCatalog "$version" "url")
@@ -551,13 +595,36 @@ getESD() {
return 1
fi
{ wget "$catalog" -O "$dir/$file" -q --timeout=30 --no-http-keep-alive; rc=$?; } || :
log=$(mktemp)
msg="Failed to download $catalog"
(( rc == 3 )) && error "$msg , cannot write file (disk full?)" && return 1
(( rc == 4 )) && error "$msg , network failure!" && return 1
(( rc == 8 )) && error "$msg , server issued an error response!" && return 1
(( rc != 0 )) && error "$msg , reason: $rc" && return 1
{
LC_ALL=C wget "$catalog" -O "$dir/$file" --no-verbose --timeout=30 \
--no-http-keep-alive --output-file="$log"
rc=$?
} || :
if (( rc != 0 )); then
reason=$(sed -n \
-e 's/^wget: //p' \
-e 's/^[0-9-]\{10\} [0-9:]\{8\} ERROR //p' \
"$log" | tail -n 1)
msg="Failed to download $catalog"
if (( rc == 3 )); then
error "$msg because the file could not be written (disk full?)."
elif [ -n "$reason" ]; then
error "$msg: ${reason%.}."
else
error "$msg with exit status $rc."
fi
rm -f "$log"
return 1
fi
rm -f "$log"
if [[ "$file" == *".xml" ]]; then
@@ -703,20 +770,22 @@ downloadFile() {
local size="$4"
local lang="$5"
local desc="$6"
local reason=""
local msg="Downloading $desc"
local rc total total_gb progress domain dots agent space folder
local rc total total_gb progress log
local domain dots agent space folder
agent=$(getAgent)
if [ -n "$size" ] && [[ "$size" != "0" ]]; then
folder=$(dirname -- "$iso")
if ! space=$(df --output=avail -B 1 "$folder" | tail -n 1); then
error "Failed to check free space in $folder!"
return 1
fi
total_gb=$(formatBytes "$space")
(( size > space )) && error "Not enough free space to download file, only $total_gb left!" && return 1
fi
@@ -740,12 +809,27 @@ downloadFile() {
fi
info "$msg..."
log=$(mktemp)
enabled "$DEBUG" && echo "Downloading: $url"
{ wget "$url" -O "$iso" --continue -q --timeout=30 --no-http-keep-alive --user-agent "$agent" --show-progress "$progress"; rc=$?; } || :
{
LC_ALL=C wget "$url" -O "$iso" --continue --no-verbose --timeout=30 \
--no-http-keep-alive --user-agent "$agent" --show-progress "$progress" \
--output-file="$log"
rc=$?
} || :
fKill "progress.sh"
if (( rc != 0 )); then
reason=$(sed -n \
-e 's/^wget: //p' \
-e 's/^[0-9-]\{10\} [0-9:]\{8\} ERROR //p' \
"$log" | tail -n 1)
fi
rm -f "$log"
if (( rc == 0 )) && [ -f "$iso" ]; then
if ! total=$(stat -c%s "$iso"); then
@@ -754,26 +838,30 @@ downloadFile() {
fi
total_gb=$(formatBytes "$total")
if [ "$total" -lt 100000000 ]; then
error "Invalid download link: $url (is only $total_gb ?). Please report this at $SUPPORT/issues"
return 1
fi
verifyFile "$iso" "$size" "$total" "$sum" || return 1
isCompressed "$url" && UNPACK="Y"
html "Download finished successfully..."
return 0
fi
msg="Failed to download $url"
(( rc == 3 )) && error "$msg , cannot write file (disk full?)" && return 1
(( rc == 4 )) && error "$msg , network failure!" && return 1
(( rc == 8 )) && error "$msg , server issued an error response! Please report this at $SUPPORT/issues" && return 1
error "$msg , reason: $rc"
if (( rc == 3 )); then
error "$msg because the file could not be written (disk full?)."
elif [ -n "$reason" ]; then
error "$msg: ${reason%.}."
else
error "$msg with exit status $rc."
fi
return 1
}
@@ -875,7 +963,7 @@ downloadImage() {
fi
tryDownload "$iso" "$MIDO_URL" "$sum" "$size" "$lang" "$desc" "$seconds" && return 0
fi
fi
+12 -4
View File
@@ -151,10 +151,18 @@ markWindowsBooted() {
# Remove CD-ROM ISO after install
! ready && return 0
touch "$file"
! setOwner "$file" && error "Failed to set the owner for \"$file\" !"
if ! touch "$file"; then
warn "failed to create Windows installation marker!"
return 0
fi
if ! setOwner "$file"; then
rm -f "$file"
warn "failed to set the owner for \"$file\" !"
return 0
fi
if ! disabled "$REMOVE"; then
rm -f "$BOOT" 2>/dev/null || true
fi
+93 -78
View File
@@ -3,15 +3,16 @@ set -Eeuo pipefail
: "${SAMBA:="Y"}" # Enable Samba
: "${SAMBA_DEBUG:="N"}" # Disable debug
tmp="/tmp/smb"
rm -rf "$tmp"
: "${SAMBA_CONFIG:="/etc/samba/smb.conf"}"
DDN_PID="/var/run/wsdd.pid"
NMB_PID="/var/run/samba/nmbd.pid"
SMB_PID="/var/run/samba/smbd.pid"
rm -f "$SMB_PID" "$NMB_PID" "$DDN_PID"
if ! rm -f "$SMB_PID" "$NMB_PID" "$DDN_PID"; then
error "Failed to clean Samba PID files!"
return 0
fi
disabled "$SAMBA" && return 0
disabled "$NETWORK" && return 0
@@ -19,23 +20,23 @@ disabled "$NETWORK" && return 0
configureNetwork() {
if enabled "$DHCP"; then
socket="$UPLINK"
hostname="$UPLINK"
interfaces="$DEV"
return 0
fi
hostname="host.lan"
if isUserMode; then
interfaces="lo"
else
hostname="host.lan"
interfaces="$BRIDGE"
fi
if isUserMode; then
interfaces="lo"
socket="127.0.0.1"
else
socket="$IP"
interfaces="$BRIDGE"
fi
if [ -n "${SAMBA_INTERFACE:-}" ]; then
interfaces+=",$SAMBA_INTERFACE"
fi
if [ -n "${SAMBA_INTERFACE:-}" ]; then
interfaces+=",$SAMBA_INTERFACE"
fi
return 0
@@ -46,25 +47,29 @@ writeReadme() {
local dir="$1"
local ref="$2"
{ echo "--------------------------------------------------------"
echo " $APP for $ENGINE v$(</etc/version)..."
echo " For support visit $SUPPORT"
echo "--------------------------------------------------------"
echo ""
echo "Using this folder you can exchange files with the host machine."
echo ""
echo "To select a folder on the host for this purpose, include the following bind mount in your compose file:"
echo ""
echo " volumes:"
echo " - \"./example:${ref}\""
echo ""
echo "Or in your run command:"
echo ""
echo " -v \"\${PWD:-.}/example:${ref}\""
echo ""
echo "Replace the example path ./example with your desired shared folder, which then will become visible here."
echo ""
} | unix2dos > "$dir/readme.txt"
if ! {
echo "--------------------------------------------------------"
echo " $APP for $ENGINE v$(</etc/version)..."
echo " For support visit $SUPPORT"
echo "--------------------------------------------------------"
echo ""
echo "Using this folder you can exchange files with the host machine."
echo ""
echo "To select a folder on the host for this purpose, include the following bind mount in your compose file:"
echo ""
echo " volumes:"
echo " - \"./example:${ref}\""
echo ""
echo "Or in your run command:"
echo ""
echo " -v \"\${PWD:-.}/example:${ref}\""
echo ""
echo "Replace the example path ./example with your desired shared folder, which then will become visible here."
echo ""
} | unix2dos > "$dir/readme.txt"; then
error "Failed to write shared folder readme!"
return 1
fi
return 0
}
@@ -77,6 +82,7 @@ addShare() {
local comment="$4"
local cfg="$5"
local owner=""
local tmp="/tmp/smb"
if [ ! -d "$dir" ]; then
if ! mkdir -p "$dir"; then
@@ -85,13 +91,13 @@ addShare() {
fi
if ! ls -A "$dir" >/dev/null 2>&1; then
msg="No permission to access shared folder ($dir)."
local msg="No permission to access shared folder ($dir)."
msg+=" If SELinux is active, you need to add the \":Z\" flag to the bind mount."
error "$msg" && return 1
fi
if [ ! -w "$dir" ]; then
msg="shared folder ($dir) is not writeable!"
local msg="shared folder ($dir) is not writeable!"
warn "$msg"
fi
@@ -136,40 +142,50 @@ addShare() {
writeConfig() {
{ echo "[global]"
echo " server string = Dockur"
echo " netbios name = $hostname"
echo " workgroup = WORKGROUP"
echo " interfaces = $interfaces"
echo " bind interfaces only = yes"
echo " socket address = $socket"
echo " security = user"
echo " guest account = nobody"
echo " map to guest = Bad User"
echo " server min protocol = NT1"
echo " follow symlinks = yes"
echo " wide links = yes"
echo " unix extensions = no"
echo " inherit owner = yes"
echo " create mask = 0666"
echo " directory mask = 02777"
echo " force user = root"
echo " force group = root"
echo " force create mode = 0666"
echo " force directory mode = 02777"
echo ""
echo " # Disable printing services"
echo " load printers = no"
echo " printing = bsd"
echo " printcap name = /dev/null"
echo " disable spoolss = yes"
} > "$SAMBA_CONFIG"
if ! {
echo "[global]"
echo " server string = Dockur"
echo " netbios name = $hostname"
echo " workgroup = WORKGROUP"
echo " interfaces = $interfaces"
echo " bind interfaces only = yes"
echo " security = user"
echo " guest account = nobody"
echo " map to guest = Bad User"
echo " server min protocol = NT1"
echo " follow symlinks = yes"
echo " wide links = yes"
echo " unix extensions = no"
echo " inherit owner = yes"
echo " create mask = 0666"
echo " directory mask = 02777"
echo " force user = root"
echo " force group = root"
echo " force create mode = 0666"
echo " force directory mode = 02777"
echo ""
echo " # Disable printing services"
echo " load printers = no"
echo " printing = bsd"
echo " printcap name = /dev/null"
echo " disable spoolss = yes"
} > "$SAMBA_CONFIG"; then
error "Failed to write Samba config \"$SAMBA_CONFIG\" !"
return 1
fi
return 0
}
selectPrimaryShare() {
local tmp="/tmp/smb"
if ! rm -rf "$tmp"; then
error "Failed to clean temporary Samba folder!"
return 1
fi
share="/shared"
[ ! -d "$share" ] && [ -d "$STORAGE/shared" ] && share="$STORAGE/shared"
[ ! -d "$share" ] && [ -d "/data" ] && share="/data"
@@ -227,7 +243,7 @@ startDaemon() {
local log="$2"
shift 2
rm -f "$log"
rm -f "$log" || :
if ! "$@"; then
SAMBA_DEBUG="Y"
@@ -269,30 +285,29 @@ startWsddn() {
return 0
}
configureNetwork
configureNetwork || return 0
html "Initializing shared folder..."
SAMBA_CONFIG="/etc/samba/smb.conf"
enabled "$DEBUG" && echo "Starting Samba daemon..."
writeConfig || return 1
writeConfig || return 0
# Add shared folders
selectPrimaryShare
! addShare "$share" "/shared" "Data" "Shared" "$SAMBA_CONFIG" && return 0
selectPrimaryShare || return 0
addOptionalShare "2"
addOptionalShare "3"
addShare "$share" "/shared" "Data" "Shared" "$SAMBA_CONFIG" || return 0
addOptionalShare "2" || :
addOptionalShare "3" || :
prepareSambaDirs || return 1
startSamba
prepareSambaDirs || return 0
startSamba || return 0
isUserMode && return 0
if [[ "${BOOT_MODE:-}" == "windows_legacy" ]]; then
startNetbios
startNetbios || :
else
startWsddn
startWsddn || :
fi
return 0