mido.sh 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681
  1. #!/usr/bin/env bash
  2. set -Eeuo pipefail
  3. handle_curl_error() {
  4. local error_code="$1"
  5. local server_name="$2"
  6. case "$error_code" in
  7. 1) error "Unsupported protocol!" ;;
  8. 2) error "Failed to initialize curl!" ;;
  9. 3) error "The URL format is malformed!" ;;
  10. 5) error "Failed to resolve address of proxy host!" ;;
  11. 6) error "Failed to resolve $server_name servers! Is there an Internet connection?" ;;
  12. 7) error "Failed to contact $server_name servers! Is there an Internet connection or is the server down?" ;;
  13. 8) error "$server_name servers returned a malformed HTTP response!" ;;
  14. 16) error "A problem was detected in the HTTP2 framing layer!" ;;
  15. 22) error "$server_name servers returned a failing HTTP status code!" ;;
  16. 23) error "Failed at writing Windows media to disk! Out of disk space or permission error?" ;;
  17. 26) error "Failed to read Windows media from disk!" ;;
  18. 27) error "Ran out of memory during download!" ;;
  19. 28) error "Connection timed out to $server_name server!" ;;
  20. 35) error "SSL connection error from $server_name server!" ;;
  21. 36) error "Failed to continue earlier download!" ;;
  22. 52) error "Received no data from the $server_name server!" ;;
  23. 63) error "$server_name servers returned an unexpectedly large response!" ;;
  24. # POSIX defines exit statuses 1-125 as usable by us
  25. # https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_08_02
  26. $((error_code <= 125)))
  27. # Must be some other server or network error (possibly with this specific request/file)
  28. # 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
  29. error "Miscellaneous server or network error, reason: $error_code"
  30. ;;
  31. 126 | 127 ) error "Curl command not found!" ;;
  32. # Exit statuses are undefined by POSIX beyond this point
  33. *)
  34. case "$(kill -l "$error_code")" in
  35. # Signals defined to exist by POSIX:
  36. # https://pubs.opengroup.org/onlinepubs/009695399/basedefs/signal.h.html
  37. INT) error "Curl was interrupted!" ;;
  38. # There could be other signals but these are most common
  39. SEGV | ABRT ) error "Curl crashed! Please report any core dumps to curl developers." ;;
  40. *) error "Curl terminated due to fatal signal $error_code !" ;;
  41. esac
  42. esac
  43. return 1
  44. }
  45. get_agent() {
  46. local user_agent
  47. # Determine approximate latest Firefox release
  48. browser_version="$((124 + ($(date +%s) - 1710892800) / 2419200))"
  49. echo "Mozilla/5.0 (X11; Linux x86_64; rv:${browser_version}.0) Gecko/20100101 Firefox/${browser_version}.0"
  50. return 0
  51. }
  52. download_windows() {
  53. local id="$1"
  54. local lang="$2"
  55. local desc="$3"
  56. local sku_id=""
  57. local sku_url=""
  58. local iso_url=""
  59. local iso_json=""
  60. local language=""
  61. local session_id=""
  62. local user_agent=""
  63. local download_type=""
  64. local windows_version=""
  65. local iso_download_link=""
  66. local download_page_html=""
  67. local product_edition_id=""
  68. local language_skuid_json=""
  69. local profile="606624d44113"
  70. user_agent=$(get_agent)
  71. language=$(getLanguage "$lang" "name")
  72. case "${id,,}" in
  73. "win11x64" ) windows_version="11" && download_type="1" ;;
  74. "win10x64" ) windows_version="10" && download_type="1" ;;
  75. "win11arm64" ) windows_version="11arm64" && download_type="2" ;;
  76. * ) error "Invalid VERSION specified, value \"$id\" is not recognized!" && return 1 ;;
  77. esac
  78. local url="https://www.microsoft.com/en-us/software-download/windows$windows_version"
  79. [[ "${id,,}" == "win10"* ]] && url+="ISO"
  80. # 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
  81. session_id=$(cat /proc/sys/kernel/random/uuid 2> /dev/null || uuidgen --random)
  82. # Get product edition ID for latest release of given Windows version
  83. # Product edition ID: This specifies both the Windows release (e.g. 22H2) and edition ("multi-edition" is default, either Home/Pro/Edu/etc., we select "Pro" in the answer files) in one number
  84. # This is the *only* request we make that Fido doesn't. Fido manually maintains a list of all the Windows release/edition product edition IDs in its script (see: $WindowsVersions array). This is helpful for downloading older releases (e.g. Windows 10 1909, 21H1, etc.) but we always want to get the newest release which is why we get this value dynamically
  85. # Also, keeping a "$WindowsVersions" array like Fido does would be way too much of a maintenance burden
  86. # Remove "Accept" header that curl sends by default
  87. [[ "$DEBUG" == [Yy1]* ]] && echo "Parsing download page: ${url}"
  88. download_page_html=$(curl --silent --max-time 30 --user-agent "$user_agent" --header "Accept:" --max-filesize 1M --fail --proto =https --tlsv1.2 --http1.1 -- "$url") || {
  89. handle_curl_error "$?" "Microsoft"
  90. return $?
  91. }
  92. [[ "$DEBUG" == [Yy1]* ]] && echo -n "Getting Product edition ID: "
  93. product_edition_id=$(echo "$download_page_html" | grep -Eo '<option value="[0-9]+">Windows' | cut -d '"' -f 2 | head -n 1 | tr -cd '0-9' | head -c 16)
  94. [[ "$DEBUG" == [Yy1]* ]] && echo "$product_edition_id"
  95. if [ -z "$product_edition_id" ]; then
  96. error "Product edition ID not found!"
  97. return 1
  98. fi
  99. [[ "$DEBUG" == [Yy1]* ]] && echo "Permit Session ID: $session_id"
  100. # Permit Session ID
  101. curl --silent --max-time 30 --output /dev/null --user-agent "$user_agent" --header "Accept:" --max-filesize 100K --fail --proto =https --tlsv1.2 --http1.1 -- "https://vlscppe.microsoft.com/tags?org_id=y6jn8c31&session_id=$session_id" || {
  102. # This should only happen if there's been some change to how this API works
  103. handle_curl_error "$?" "Microsoft"
  104. return $?
  105. }
  106. [[ "$DEBUG" == [Yy1]* ]] && echo -n "Getting language SKU ID: "
  107. sku_url="https://www.microsoft.com/software-download-connector/api/getskuinformationbyproductedition?profile=$profile&ProductEditionId=$product_edition_id&SKU=undefined&friendlyFileName=undefined&Locale=en-US&sessionID=$session_id"
  108. language_skuid_json=$(curl --silent --max-time 30 --request GET --user-agent "$user_agent" --referer "$url" --header "Accept:" --max-filesize 100K --fail --proto =https --tlsv1.2 --http1.1 -- "$sku_url") || {
  109. handle_curl_error "$?" "Microsoft"
  110. return $?
  111. }
  112. { sku_id=$(echo "$language_skuid_json" | jq --arg LANG "$language" -r '.Skus[] | select(.Language==$LANG).Id') 2>/dev/null; rc=$?; } || :
  113. if [ -z "$sku_id" ] || [[ "${sku_id,,}" == "null" ]] || (( rc != 0 )); then
  114. language=$(getLanguage "$lang" "desc")
  115. error "No download in the $language language available for $desc!"
  116. return 1
  117. fi
  118. [[ "$DEBUG" == [Yy1]* ]] && echo "$sku_id"
  119. [[ "$DEBUG" == [Yy1]* ]] && echo "Getting ISO download link..."
  120. # Get ISO download link
  121. # If any request is going to be blocked by Microsoft it's always this last one (the previous requests always seem to succeed)
  122. iso_url="https://www.microsoft.com/software-download-connector/api/GetProductDownloadLinksBySku?profile=$profile&ProductEditionId=undefined&SKU=$sku_id&friendlyFileName=undefined&Locale=en-US&sessionID=$session_id"
  123. iso_json=$(curl --silent --max-time 30 --request GET --user-agent "$user_agent" --referer "$url" --header "Accept:" --max-filesize 100K --fail --proto =https --tlsv1.2 --http1.1 -- "$iso_url")
  124. if ! [ "$iso_json" ]; then
  125. # This should only happen if there's been some change to how this API works
  126. error "Microsoft servers gave us an empty response to our request for an automated download."
  127. return 1
  128. fi
  129. if echo "$iso_json" | grep -q "Sentinel marked this request as rejected."; then
  130. error "Microsoft blocked the automated download request based on your IP address."
  131. return 1
  132. fi
  133. if echo "$iso_json" | grep -q "We are unable to complete your request at this time."; then
  134. error "Microsoft blocked the automated download request based on your IP address."
  135. return 1
  136. fi
  137. { iso_download_link=$(echo "$iso_json" | jq --argjson TYPE "$download_type" -r '.ProductDownloadOptions[] | select(.DownloadType==$TYPE).Uri') 2>/dev/null; rc=$?; } || :
  138. if [ -z "$iso_download_link" ] || [[ "${iso_download_link,,}" == "null" ]] || (( rc != 0 )); then
  139. error "Microsoft servers gave us no download link to our request for an automated download!"
  140. info "Response: $iso_json"
  141. return 1
  142. fi
  143. MIDO_URL="$iso_download_link"
  144. return 0
  145. }
  146. download_windows_eval() {
  147. local id="$1"
  148. local lang="$2"
  149. local desc="$3"
  150. local filter=""
  151. local culture=""
  152. local language=""
  153. local user_agent=""
  154. local enterprise_type=""
  155. local windows_version=""
  156. case "${id,,}" in
  157. "win11${PLATFORM,,}-enterprise-eval" )
  158. enterprise_type="enterprise"
  159. windows_version="windows-11-enterprise" ;;
  160. "win11${PLATFORM,,}-enterprise-iot-eval" )
  161. enterprise_type="iot"
  162. windows_version="windows-11-iot-enterprise-ltsc-eval" ;;
  163. "win11${PLATFORM,,}-enterprise-ltsc-eval" )
  164. enterprise_type="iot"
  165. windows_version="windows-11-iot-enterprise-ltsc-eval" ;;
  166. "win10${PLATFORM,,}-enterprise-eval" )
  167. enterprise_type="enterprise"
  168. windows_version="windows-10-enterprise" ;;
  169. "win10${PLATFORM,,}-enterprise-ltsc-eval" )
  170. enterprise_type="ltsc"
  171. windows_version="windows-10-enterprise" ;;
  172. "win2025-eval" )
  173. enterprise_type="server"
  174. windows_version="windows-server-2025" ;;
  175. "win2022-eval" )
  176. enterprise_type="server"
  177. windows_version="windows-server-2022" ;;
  178. "win2019-hv" )
  179. enterprise_type="server"
  180. windows_version="hyper-v-server-2019" ;;
  181. "win2019-eval" )
  182. enterprise_type="server"
  183. windows_version="windows-server-2019" ;;
  184. "win2016-eval" )
  185. enterprise_type="server"
  186. windows_version="windows-server-2016" ;;
  187. "win2012r2-eval" )
  188. enterprise_type="server"
  189. windows_version="windows-server-2012-r2" ;;
  190. * )
  191. error "Invalid VERSION specified, value \"$id\" is not recognized!" && return 1 ;;
  192. esac
  193. user_agent=$(get_agent)
  194. culture=$(getLanguage "$lang" "culture")
  195. local country="${culture#*-}"
  196. local iso_download_page_html=""
  197. local url="https://www.microsoft.com/en-us/evalcenter/download-$windows_version"
  198. [[ "$DEBUG" == [Yy1]* ]] && echo "Parsing download page: ${url}"
  199. iso_download_page_html=$(curl --silent --max-time 30 --user-agent "$user_agent" --location --max-filesize 1M --fail --proto =https --tlsv1.2 --http1.1 -- "$url") || {
  200. handle_curl_error "$?" "Microsoft"
  201. return $?
  202. }
  203. if ! [ "$iso_download_page_html" ]; then
  204. # This should only happen if there's been some change to where this download page is located
  205. error "Windows server download page gave us an empty response"
  206. return 1
  207. fi
  208. [[ "$DEBUG" == [Yy1]* ]] && echo "Getting download link.."
  209. filter="https://go.microsoft.com/fwlink/?linkid=[0-9]\+&clcid=0x[0-9a-z]\+&culture=${culture,,}&country=${country,,}"
  210. if ! echo "$iso_download_page_html" | grep -io "$filter" > /dev/null; then
  211. filter="https://go.microsoft.com/fwlink/p/?linkid=[0-9]\+&clcid=0x[0-9a-z]\+&culture=${culture,,}&country=${country,,}"
  212. fi
  213. iso_download_links=$(echo "$iso_download_page_html" | grep -io "$filter") || {
  214. # This should only happen if there's been some change to the download endpoint web address
  215. if [[ "${lang,,}" == "en" ]] || [[ "${lang,,}" == "en-"* ]]; then
  216. error "Windows server download page gave us no download link!"
  217. else
  218. language=$(getLanguage "$lang" "desc")
  219. error "No download in the $language language available for $desc!"
  220. fi
  221. return 1
  222. }
  223. case "$enterprise_type" in
  224. "enterprise" )
  225. iso_download_link=$(echo "$iso_download_links" | head -n 2 | tail -n 1)
  226. ;;
  227. "iot" )
  228. if [[ "${PLATFORM,,}" == "x64" ]]; then
  229. iso_download_link=$(echo "$iso_download_links" | head -n 1)
  230. fi
  231. if [[ "${PLATFORM,,}" == "arm64" ]]; then
  232. iso_download_link=$(echo "$iso_download_links" | head -n 2 | tail -n 1)
  233. fi
  234. ;;
  235. "ltsc" )
  236. iso_download_link=$(echo "$iso_download_links" | head -n 4 | tail -n 1)
  237. ;;
  238. "server" )
  239. iso_download_link=$(echo "$iso_download_links" | head -n 1)
  240. ;;
  241. * )
  242. error "Invalid type specified, value \"$enterprise_type\" is not recognized!" && return 1 ;;
  243. esac
  244. [[ "$DEBUG" == [Yy1]* ]] && echo "Found download link: $iso_download_link"
  245. # Follow redirect so proceeding log message is useful
  246. # This is a request we make that Fido doesn't
  247. iso_download_link=$(curl --silent --max-time 30 --user-agent "$user_agent" --location --output /dev/null --silent --write-out "%{url_effective}" --head --fail --proto =https --tlsv1.2 --http1.1 -- "$iso_download_link") || {
  248. # This should only happen if the Microsoft servers are down
  249. handle_curl_error "$?" "Microsoft"
  250. return $?
  251. }
  252. MIDO_URL="$iso_download_link"
  253. return 0
  254. }
  255. getWindows() {
  256. local version="$1"
  257. local lang="$2"
  258. local desc="$3"
  259. local language edition
  260. language=$(getLanguage "$lang" "desc")
  261. edition=$(printEdition "$version" "$desc")
  262. local msg="Requesting $desc from the Microsoft servers..."
  263. info "$msg" && html "$msg"
  264. case "${version,,}" in
  265. "win2008r2" | "win7${PLATFORM,,}"* | "win81${PLATFORM,,}"* | "win11${PLATFORM,,}-enterprise-iot"* | "win11${PLATFORM,,}-enterprise-ltsc"* )
  266. if [[ "${lang,,}" != "en" ]] && [[ "${lang,,}" != "en-"* ]]; then
  267. error "No download in the $language language available for $edition!"
  268. MIDO_URL="" && return 1
  269. fi ;;
  270. esac
  271. case "${version,,}" in
  272. "win11${PLATFORM,,}" ) ;;
  273. "win11${PLATFORM,,}-enterprise-iot"* ) ;;
  274. "win11${PLATFORM,,}-enterprise-ltsc"* ) ;;
  275. * )
  276. if [[ "${PLATFORM,,}" != "x64" ]]; then
  277. error "No download for the ${PLATFORM^^} platform available for $edition!"
  278. MIDO_URL="" && return 1
  279. fi ;;
  280. esac
  281. case "${version,,}" in
  282. "win10${PLATFORM,,}" | "win11${PLATFORM,,}" )
  283. download_windows "$version" "$lang" "$edition" && return 0
  284. ;;
  285. "win11${PLATFORM,,}-enterprise"* | "win10${PLATFORM,,}-enterprise"* )
  286. download_windows_eval "$version" "$lang" "$edition" && return 0
  287. ;;
  288. "win2025-eval" | "win2022-eval" | "win2019-eval" | "win2019-hv" | "win2016-eval" | "win2012r2-eval" )
  289. download_windows_eval "$version" "$lang" "$edition" && return 0
  290. ;;
  291. "win7${PLATFORM,,}"* | "win81${PLATFORM,,}-enterprise"* | "win2008r2" )
  292. ;;
  293. * ) error "Invalid VERSION specified, value \"$version\" is not recognized!" ;;
  294. esac
  295. MIDO_URL=$(getMido "$version" "$lang" "")
  296. [ -z "$MIDO_URL" ] && return 1
  297. return 0
  298. }
  299. getCatalog() {
  300. local id="$1"
  301. local ret="$2"
  302. local url=""
  303. local name=""
  304. local edition=""
  305. case "${id,,}" in
  306. "win11${PLATFORM,,}" )
  307. edition="Professional"
  308. name="Windows 11 Pro"
  309. url="https://go.microsoft.com/fwlink?linkid=2156292" ;;
  310. "win10${PLATFORM,,}" )
  311. edition="Professional"
  312. name="Windows 10 Pro"
  313. url="https://go.microsoft.com/fwlink/?LinkId=841361" ;;
  314. "win11${PLATFORM,,}-enterprise" | "win11${PLATFORM,,}-enterprise-eval")
  315. edition="Enterprise"
  316. name="Windows 11 Enterprise"
  317. url="https://go.microsoft.com/fwlink?linkid=2156292" ;;
  318. "win10${PLATFORM,,}-enterprise" | "win10${PLATFORM,,}-enterprise-eval" )
  319. edition="Enterprise"
  320. name="Windows 10 Enterprise"
  321. url="https://go.microsoft.com/fwlink/?LinkId=841361" ;;
  322. esac
  323. case "${ret,,}" in
  324. "url" ) echo "$url" ;;
  325. "name" ) echo "$name" ;;
  326. "edition" ) echo "$edition" ;;
  327. *) echo "";;
  328. esac
  329. return 0
  330. }
  331. getESD() {
  332. local dir="$1"
  333. local version="$2"
  334. local lang="$3"
  335. local desc="$4"
  336. local culture
  337. local language
  338. local editionName
  339. local winCatalog size
  340. culture=$(getLanguage "$lang" "culture")
  341. winCatalog=$(getCatalog "$version" "url")
  342. editionName=$(getCatalog "$version" "edition")
  343. if [ -z "$winCatalog" ] || [ -z "$editionName" ]; then
  344. error "Invalid VERSION specified, value \"$version\" is not recognized!" && return 1
  345. fi
  346. local msg="Downloading product information from Microsoft server..."
  347. info "$msg" && html "$msg"
  348. rm -rf "$dir"
  349. mkdir -p "$dir"
  350. local wFile="catalog.cab"
  351. local xFile="products.xml"
  352. local eFile="esd_edition.xml"
  353. local fFile="products_filter.xml"
  354. { wget "$winCatalog" -O "$dir/$wFile" -q --timeout=30 --no-http-keep-alive; rc=$?; } || :
  355. msg="Failed to download $winCatalog"
  356. (( rc == 3 )) && error "$msg , cannot write file (disk full?)" && return 1
  357. (( rc == 4 )) && error "$msg , network failure!" && return 1
  358. (( rc == 8 )) && error "$msg , server issued an error response!" && return 1
  359. (( rc != 0 )) && error "$msg , reason: $rc" && return 1
  360. cd "$dir"
  361. if ! cabextract "$wFile" > /dev/null; then
  362. cd /run
  363. error "Failed to extract $wFile!" && return 1
  364. fi
  365. cd /run
  366. if [ ! -s "$dir/$xFile" ]; then
  367. error "Failed to find $xFile in $wFile!" && return 1
  368. fi
  369. local edQuery='//File[Architecture="'${PLATFORM}'"][Edition="'${editionName}'"]'
  370. echo -e '<Catalog>' > "$dir/$fFile"
  371. xmllint --nonet --xpath "${edQuery}" "$dir/$xFile" >> "$dir/$fFile" 2>/dev/null
  372. echo -e '</Catalog>'>> "$dir/$fFile"
  373. xmllint --nonet --xpath "//File[LanguageCode=\"${culture,,}\"]" "$dir/$fFile" >"$dir/$eFile"
  374. size=$(stat -c%s "$dir/$eFile")
  375. if ((size<20)); then
  376. desc=$(printEdition "$version" "$desc")
  377. language=$(getLanguage "$lang" "desc")
  378. error "No download in the $language language available for $desc!" && return 1
  379. fi
  380. local tag="FilePath"
  381. ESD=$(xmllint --nonet --xpath "//$tag" "$dir/$eFile" | sed -E -e "s/<[\/]?$tag>//g")
  382. if [ -z "$ESD" ]; then
  383. error "Failed to find ESD URL in $eFile!" && return 1
  384. fi
  385. tag="Sha1"
  386. ESD_SUM=$(xmllint --nonet --xpath "//$tag" "$dir/$eFile" | sed -E -e "s/<[\/]?$tag>//g")
  387. tag="Size"
  388. ESD_SIZE=$(xmllint --nonet --xpath "//$tag" "$dir/$eFile" | sed -E -e "s/<[\/]?$tag>//g")
  389. rm -rf "$dir"
  390. return 0
  391. }
  392. verifyFile() {
  393. local iso="$1"
  394. local size="$2"
  395. local total="$3"
  396. local check="$4"
  397. if [ -n "$size" ] && [[ "$total" != "$size" ]] && [[ "$size" != "0" ]]; then
  398. if [[ "$VERIFY" == [Yy1]* ]] || [[ "$DEBUG" == [Yy1]* ]]; then
  399. warn "The downloaded file has a different size ( $total bytes) than expected ( $size bytes). Please report this at $SUPPORT/issues"
  400. fi
  401. fi
  402. local hash=""
  403. local algo="SHA256"
  404. [ -z "$check" ] && return 0
  405. [[ "$VERIFY" != [Yy1]* ]] && return 0
  406. [[ "${#check}" == "40" ]] && algo="SHA1"
  407. local msg="Verifying downloaded ISO..."
  408. info "$msg" && html "$msg"
  409. if [[ "${algo,,}" != "sha256" ]]; then
  410. hash=$(sha1sum "$iso" | cut -f1 -d' ')
  411. else
  412. hash=$(sha256sum "$iso" | cut -f1 -d' ')
  413. fi
  414. if [[ "$hash" == "$check" ]]; then
  415. info "Succesfully verified ISO!" && return 0
  416. fi
  417. error "The downloaded file has an unknown $algo checksum: $hash , as the expected value was: $check. Please report this at $SUPPORT/issues"
  418. return 1
  419. }
  420. downloadFile() {
  421. local iso="$1"
  422. local url="$2"
  423. local sum="$3"
  424. local size="$4"
  425. local lang="$5"
  426. local desc="$6"
  427. local rc total progress domain dots space folder
  428. rm -f "$iso"
  429. if [ -n "$size" ] && [[ "$size" != "0" ]]; then
  430. folder=$(dirname -- "$iso")
  431. space=$(df --output=avail -B 1 "$folder" | tail -n 1)
  432. (( size > space )) && error "Not enough free space left to download file!" && return 1
  433. fi
  434. # Check if running with interactive TTY or redirected to docker log
  435. if [ -t 1 ]; then
  436. progress="--progress=bar:noscroll"
  437. else
  438. progress="--progress=dot:giga"
  439. fi
  440. local msg="Downloading $desc"
  441. html "$msg..."
  442. domain=$(echo "$url" | awk -F/ '{print $3}')
  443. dots=$(echo "$domain" | tr -cd '.' | wc -c)
  444. (( dots > 1 )) && domain=$(expr "$domain" : '.*\.\(.*\..*\)')
  445. if [ -n "$domain" ] && [[ "${domain,,}" != *"microsoft.com" ]]; then
  446. msg="Downloading $desc from $domain"
  447. fi
  448. info "$msg..."
  449. /run/progress.sh "$iso" "$size" "$msg ([P])..." &
  450. { wget "$url" -O "$iso" -q --timeout=30 --no-http-keep-alive --show-progress "$progress"; rc=$?; } || :
  451. fKill "progress.sh"
  452. if (( rc == 0 )) && [ -f "$iso" ]; then
  453. total=$(stat -c%s "$iso")
  454. if [ "$total" -lt 100000000 ]; then
  455. error "Invalid download link: $url (is only $total bytes?). Please report this at $SUPPORT/issues." && return 1
  456. fi
  457. verifyFile "$iso" "$size" "$total" "$sum" || return 1
  458. html "Download finished successfully..." && return 0
  459. fi
  460. msg="Failed to download $url"
  461. (( rc == 3 )) && error "$msg , cannot write file (disk full?)" && return 1
  462. (( rc == 4 )) && error "$msg , network failure!" && return 1
  463. (( rc == 8 )) && error "$msg , server issued an error response! Please report this at $SUPPORT/issues." && return 1
  464. error "$msg , reason: $rc"
  465. return 1
  466. }
  467. downloadImage() {
  468. local iso="$1"
  469. local version="$2"
  470. local lang="$3"
  471. local delay=5
  472. local tried="n"
  473. local success="n"
  474. local url sum size base desc language
  475. local msg="Will retry after $delay seconds..."
  476. if [[ "${version,,}" == "http"* ]]; then
  477. base=$(basename "$iso")
  478. desc=$(fromFile "$base")
  479. downloadFile "$iso" "$version" "" "" "" "$desc" && return 0
  480. info "$msg" && html "$msg" && sleep "$delay"
  481. downloadFile "$iso" "$version" "" "" "" "$desc" && return 0
  482. rm -f "$iso"
  483. return 1
  484. fi
  485. if ! validVersion "$version" "en"; then
  486. error "Invalid VERSION specified, value \"$version\" is not recognized!" && return 1
  487. fi
  488. desc=$(printVersion "$version" "")
  489. if [[ "${lang,,}" != "en" ]] && [[ "${lang,,}" != "en-"* ]]; then
  490. language=$(getLanguage "$lang" "desc")
  491. if ! validVersion "$version" "$lang"; then
  492. desc=$(printEdition "$version" "$desc")
  493. error "The $language language version of $desc is not available, please switch to English." && return 1
  494. fi
  495. desc+=" in $language"
  496. fi
  497. if isMido "$version" "$lang"; then
  498. tried="y"
  499. success="n"
  500. if getWindows "$version" "$lang" "$desc"; then
  501. success="y"
  502. else
  503. info "$msg" && html "$msg" && sleep "$delay"
  504. getWindows "$version" "$lang" "$desc" && success="y"
  505. fi
  506. if [[ "$success" == "y" ]]; then
  507. size=$(getMido "$version" "$lang" "size" )
  508. sum=$(getMido "$version" "$lang" "sum")
  509. downloadFile "$iso" "$MIDO_URL" "$sum" "$size" "$lang" "$desc" && return 0
  510. info "$msg" && html "$msg" && sleep "$delay"
  511. downloadFile "$iso" "$MIDO_URL" "$sum" "$size" "$lang" "$desc" && return 0
  512. rm -f "$iso"
  513. fi
  514. fi
  515. switchEdition "$version"
  516. if isESD "$version" "$lang"; then
  517. if [[ "$tried" != "n" ]]; then
  518. info "Failed to download $desc, will try a diferent method now..."
  519. fi
  520. tried="y"
  521. success="n"
  522. if getESD "$TMP/esd" "$version" "$lang" "$desc"; then
  523. success="y"
  524. else
  525. info "$msg" && html "$msg" && sleep "$delay"
  526. getESD "$TMP/esd" "$version" "$lang" "$desc" && success="y"
  527. fi
  528. if [[ "$success" == "y" ]]; then
  529. ISO="${ISO%.*}.esd"
  530. downloadFile "$ISO" "$ESD" "$ESD_SUM" "$ESD_SIZE" "$lang" "$desc" && return 0
  531. info "$msg" && html "$msg" && sleep "$delay"
  532. downloadFile "$ISO" "$ESD" "$ESD_SUM" "$ESD_SIZE" "$lang" "$desc" && return 0
  533. rm -f "$ISO"
  534. ISO="$iso"
  535. fi
  536. fi
  537. for ((i=1;i<=MIRRORS;i++)); do
  538. url=$(getLink "$i" "$version" "$lang")
  539. if [ -n "$url" ]; then
  540. if [[ "$tried" != "n" ]]; then
  541. info "Failed to download $desc, will try another mirror now..."
  542. fi
  543. tried="y"
  544. size=$(getSize "$i" "$version" "$lang")
  545. sum=$(getHash "$i" "$version" "$lang")
  546. downloadFile "$iso" "$url" "$sum" "$size" "$lang" "$desc" && return 0
  547. info "$msg" && html "$msg" && sleep "$delay"
  548. downloadFile "$iso" "$url" "$sum" "$size" "$lang" "$desc" && return 0
  549. rm -f "$iso"
  550. fi
  551. done
  552. return 1
  553. }
  554. return 0