mido.sh 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700
  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. session_id="${session_id//[![:print:]]/}"
  83. # Get product edition ID for latest release of given Windows version
  84. # 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
  85. # 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
  86. # Also, keeping a "$WindowsVersions" array like Fido does would be way too much of a maintenance burden
  87. # Remove "Accept" header that curl sends by default
  88. [[ "$DEBUG" == [Yy1]* ]] && echo "Parsing download page: ${url}"
  89. 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") || {
  90. handle_curl_error "$?" "Microsoft"
  91. return $?
  92. }
  93. [[ "$DEBUG" == [Yy1]* ]] && echo -n "Getting Product edition ID: "
  94. 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)
  95. [[ "$DEBUG" == [Yy1]* ]] && echo "$product_edition_id"
  96. if [ -z "$product_edition_id" ]; then
  97. error "Product edition ID not found!"
  98. return 1
  99. fi
  100. [[ "$DEBUG" == [Yy1]* ]] && echo "Permit Session ID: $session_id"
  101. # Permit Session ID
  102. 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" || {
  103. # This should only happen if there's been some change to how this API works
  104. handle_curl_error "$?" "Microsoft"
  105. return $?
  106. }
  107. [[ "$DEBUG" == [Yy1]* ]] && echo -n "Getting language SKU ID: "
  108. 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"
  109. 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") || {
  110. handle_curl_error "$?" "Microsoft"
  111. return $?
  112. }
  113. { sku_id=$(echo "$language_skuid_json" | jq --arg LANG "$language" -r '.Skus[] | select(.Language==$LANG).Id') 2>/dev/null; rc=$?; } || :
  114. if [ -z "$sku_id" ] || [[ "${sku_id,,}" == "null" ]] || (( rc != 0 )); then
  115. language=$(getLanguage "$lang" "desc")
  116. error "No download in the $language language available for $desc!"
  117. return 1
  118. fi
  119. [[ "$DEBUG" == [Yy1]* ]] && echo "$sku_id"
  120. [[ "$DEBUG" == [Yy1]* ]] && echo "Getting ISO download link..."
  121. # Get ISO download link
  122. # If any request is going to be blocked by Microsoft it's always this last one (the previous requests always seem to succeed)
  123. 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"
  124. 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")
  125. if ! [ "$iso_json" ]; then
  126. # This should only happen if there's been some change to how this API works
  127. error "Microsoft servers gave us an empty response to our request for an automated download."
  128. return 1
  129. fi
  130. if echo "$iso_json" | grep -q "Sentinel marked this request as rejected."; then
  131. error "Microsoft blocked the automated download request based on your IP address."
  132. return 1
  133. fi
  134. if echo "$iso_json" | grep -q "We are unable to complete your request at this time."; then
  135. error "Microsoft blocked the automated download request based on your IP address."
  136. return 1
  137. fi
  138. { iso_download_link=$(echo "$iso_json" | jq --argjson TYPE "$download_type" -r '.ProductDownloadOptions[] | select(.DownloadType==$TYPE).Uri') 2>/dev/null; rc=$?; } || :
  139. if [ -z "$iso_download_link" ] || [[ "${iso_download_link,,}" == "null" ]] || (( rc != 0 )); then
  140. error "Microsoft servers gave us no download link to our request for an automated download!"
  141. info "Response: $iso_json"
  142. return 1
  143. fi
  144. MIDO_URL="$iso_download_link"
  145. return 0
  146. }
  147. download_windows_eval() {
  148. local id="$1"
  149. local lang="$2"
  150. local desc="$3"
  151. local filter=""
  152. local culture=""
  153. local language=""
  154. local user_agent=""
  155. local enterprise_type=""
  156. local windows_version=""
  157. case "${id,,}" in
  158. "win11${PLATFORM,,}-enterprise-eval" )
  159. enterprise_type="enterprise"
  160. windows_version="windows-11-enterprise" ;;
  161. "win11${PLATFORM,,}-enterprise-iot-eval" )
  162. enterprise_type="iot"
  163. windows_version="windows-11-iot-enterprise-ltsc-eval" ;;
  164. "win11${PLATFORM,,}-enterprise-ltsc-eval" )
  165. enterprise_type="iot"
  166. windows_version="windows-11-iot-enterprise-ltsc-eval" ;;
  167. "win10${PLATFORM,,}-enterprise-eval" )
  168. enterprise_type="enterprise"
  169. windows_version="windows-10-enterprise" ;;
  170. "win10${PLATFORM,,}-enterprise-ltsc-eval" )
  171. enterprise_type="ltsc"
  172. windows_version="windows-10-enterprise" ;;
  173. "win2025-eval" )
  174. enterprise_type="server"
  175. windows_version="windows-server-2025" ;;
  176. "win2022-eval" )
  177. enterprise_type="server"
  178. windows_version="windows-server-2022" ;;
  179. "win2019-hv" )
  180. enterprise_type="server"
  181. windows_version="hyper-v-server-2019" ;;
  182. "win2019-eval" )
  183. enterprise_type="server"
  184. windows_version="windows-server-2019" ;;
  185. "win2016-eval" )
  186. enterprise_type="server"
  187. windows_version="windows-server-2016" ;;
  188. "win2012r2-eval" )
  189. enterprise_type="server"
  190. windows_version="windows-server-2012-r2" ;;
  191. * )
  192. error "Invalid VERSION specified, value \"$id\" is not recognized!" && return 1 ;;
  193. esac
  194. user_agent=$(get_agent)
  195. culture=$(getLanguage "$lang" "culture")
  196. local country="${culture#*-}"
  197. local iso_download_page_html=""
  198. local url="https://www.microsoft.com/en-us/evalcenter/download-$windows_version"
  199. [[ "$DEBUG" == [Yy1]* ]] && echo "Parsing download page: ${url}"
  200. 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") || {
  201. handle_curl_error "$?" "Microsoft"
  202. return $?
  203. }
  204. if ! [ "$iso_download_page_html" ]; then
  205. # This should only happen if there's been some change to where this download page is located
  206. error "Windows server download page gave us an empty response"
  207. return 1
  208. fi
  209. [[ "$DEBUG" == [Yy1]* ]] && echo "Getting download link.."
  210. filter="https://go.microsoft.com/fwlink/?linkid=[0-9]\+&clcid=0x[0-9a-z]\+&culture=${culture,,}&country=${country,,}"
  211. if ! echo "$iso_download_page_html" | grep -io "$filter" > /dev/null; then
  212. filter="https://go.microsoft.com/fwlink/p/?linkid=[0-9]\+&clcid=0x[0-9a-z]\+&culture=${culture,,}&country=${country,,}"
  213. fi
  214. iso_download_links=$(echo "$iso_download_page_html" | grep -io "$filter") || {
  215. # This should only happen if there's been some change to the download endpoint web address
  216. if [[ "${lang,,}" == "en" ]] || [[ "${lang,,}" == "en-"* ]]; then
  217. error "Windows server download page gave us no download link!"
  218. else
  219. language=$(getLanguage "$lang" "desc")
  220. error "No download in the $language language available for $desc!"
  221. fi
  222. return 1
  223. }
  224. case "$enterprise_type" in
  225. "enterprise" )
  226. iso_download_link=$(echo "$iso_download_links" | head -n 2 | tail -n 1)
  227. ;;
  228. "iot" )
  229. if [[ "${PLATFORM,,}" == "x64" ]]; then
  230. iso_download_link=$(echo "$iso_download_links" | head -n 1)
  231. fi
  232. if [[ "${PLATFORM,,}" == "arm64" ]]; then
  233. iso_download_link=$(echo "$iso_download_links" | head -n 2 | tail -n 1)
  234. fi
  235. ;;
  236. "ltsc" )
  237. iso_download_link=$(echo "$iso_download_links" | head -n 4 | tail -n 1)
  238. ;;
  239. "server" )
  240. iso_download_link=$(echo "$iso_download_links" | head -n 1)
  241. ;;
  242. * )
  243. error "Invalid type specified, value \"$enterprise_type\" is not recognized!" && return 1 ;;
  244. esac
  245. [[ "$DEBUG" == [Yy1]* ]] && echo "Found download link: $iso_download_link"
  246. # Follow redirect so proceeding log message is useful
  247. # This is a request we make that Fido doesn't
  248. 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") || {
  249. # This should only happen if the Microsoft servers are down
  250. handle_curl_error "$?" "Microsoft"
  251. return $?
  252. }
  253. MIDO_URL="$iso_download_link"
  254. return 0
  255. }
  256. getWindows() {
  257. local version="$1"
  258. local lang="$2"
  259. local desc="$3"
  260. local language edition
  261. language=$(getLanguage "$lang" "desc")
  262. edition=$(printEdition "$version" "$desc")
  263. local msg="Requesting $desc from the Microsoft servers..."
  264. info "$msg" && html "$msg"
  265. case "${version,,}" in
  266. "win2008r2" | "win81${PLATFORM,,}"* | "win11${PLATFORM,,}-enterprise-iot"* | "win11${PLATFORM,,}-enterprise-ltsc"* )
  267. if [[ "${lang,,}" != "en" ]] && [[ "${lang,,}" != "en-"* ]]; then
  268. error "No download in the $language language available for $edition!"
  269. MIDO_URL="" && return 1
  270. fi ;;
  271. esac
  272. case "${version,,}" in
  273. "win11${PLATFORM,,}" ) ;;
  274. "win11${PLATFORM,,}-enterprise-iot"* ) ;;
  275. "win11${PLATFORM,,}-enterprise-ltsc"* ) ;;
  276. * )
  277. if [[ "${PLATFORM,,}" != "x64" ]]; then
  278. error "No download for the ${PLATFORM^^} platform available for $edition!"
  279. MIDO_URL="" && return 1
  280. fi ;;
  281. esac
  282. case "${version,,}" in
  283. "win10${PLATFORM,,}" | "win11${PLATFORM,,}" )
  284. download_windows "$version" "$lang" "$edition" && return 0
  285. ;;
  286. "win11${PLATFORM,,}-enterprise"* | "win10${PLATFORM,,}-enterprise"* )
  287. download_windows_eval "$version" "$lang" "$edition" && return 0
  288. ;;
  289. "win2025-eval" | "win2022-eval" | "win2019-eval" | "win2019-hv" | "win2016-eval" | "win2012r2-eval" )
  290. download_windows_eval "$version" "$lang" "$edition" && return 0
  291. ;;
  292. "win81${PLATFORM,,}-enterprise"* | "win2008r2" )
  293. ;;
  294. * ) error "Invalid VERSION specified, value \"$version\" is not recognized!" ;;
  295. esac
  296. MIDO_URL=$(getMido "$version" "$lang" "")
  297. [ -z "$MIDO_URL" ] && return 1
  298. return 0
  299. }
  300. getCatalog() {
  301. local id="$1"
  302. local ret="$2"
  303. local url=""
  304. local name=""
  305. local edition=""
  306. case "${id,,}" in
  307. "win11${PLATFORM,,}" )
  308. edition="Professional"
  309. name="Windows 11 Pro"
  310. url="https://go.microsoft.com/fwlink?linkid=2156292" ;;
  311. "win10${PLATFORM,,}" )
  312. edition="Professional"
  313. name="Windows 10 Pro"
  314. url="https://go.microsoft.com/fwlink/?LinkId=841361" ;;
  315. "win11${PLATFORM,,}-enterprise" | "win11${PLATFORM,,}-enterprise-eval")
  316. edition="Enterprise"
  317. name="Windows 11 Enterprise"
  318. url="https://go.microsoft.com/fwlink?linkid=2156292" ;;
  319. "win10${PLATFORM,,}-enterprise" | "win10${PLATFORM,,}-enterprise-eval" )
  320. edition="Enterprise"
  321. name="Windows 10 Enterprise"
  322. url="https://go.microsoft.com/fwlink/?LinkId=841361" ;;
  323. esac
  324. case "${ret,,}" in
  325. "url" ) echo "$url" ;;
  326. "name" ) echo "$name" ;;
  327. "edition" ) echo "$edition" ;;
  328. *) echo "";;
  329. esac
  330. return 0
  331. }
  332. getESD() {
  333. local dir="$1"
  334. local version="$2"
  335. local lang="$3"
  336. local desc="$4"
  337. local culture
  338. local language
  339. local editionName
  340. local winCatalog size
  341. culture=$(getLanguage "$lang" "culture")
  342. winCatalog=$(getCatalog "$version" "url")
  343. editionName=$(getCatalog "$version" "edition")
  344. if [ -z "$winCatalog" ] || [ -z "$editionName" ]; then
  345. error "Invalid VERSION specified, value \"$version\" is not recognized!" && return 1
  346. fi
  347. local msg="Downloading product information from Microsoft server..."
  348. info "$msg" && html "$msg"
  349. rm -rf "$dir"
  350. mkdir -p "$dir"
  351. local wFile="catalog.cab"
  352. local xFile="products.xml"
  353. local eFile="esd_edition.xml"
  354. local fFile="products_filter.xml"
  355. { wget "$winCatalog" -O "$dir/$wFile" -q --timeout=30 --no-http-keep-alive; rc=$?; } || :
  356. msg="Failed to download $winCatalog"
  357. (( rc == 3 )) && error "$msg , cannot write file (disk full?)" && return 1
  358. (( rc == 4 )) && error "$msg , network failure!" && return 1
  359. (( rc == 8 )) && error "$msg , server issued an error response!" && return 1
  360. (( rc != 0 )) && error "$msg , reason: $rc" && return 1
  361. cd "$dir"
  362. if ! cabextract "$wFile" > /dev/null; then
  363. cd /run
  364. error "Failed to extract $wFile!" && return 1
  365. fi
  366. cd /run
  367. if [ ! -s "$dir/$xFile" ]; then
  368. error "Failed to find $xFile in $wFile!" && return 1
  369. fi
  370. local edQuery='//File[Architecture="'${PLATFORM}'"][Edition="'${editionName}'"]'
  371. echo -e '<Catalog>' > "$dir/$fFile"
  372. xmllint --nonet --xpath "${edQuery}" "$dir/$xFile" >> "$dir/$fFile" 2>/dev/null
  373. echo -e '</Catalog>'>> "$dir/$fFile"
  374. xmllint --nonet --xpath "//File[LanguageCode=\"${culture,,}\"]" "$dir/$fFile" >"$dir/$eFile"
  375. size=$(stat -c%s "$dir/$eFile")
  376. if ((size<20)); then
  377. desc=$(printEdition "$version" "$desc")
  378. language=$(getLanguage "$lang" "desc")
  379. error "No download in the $language language available for $desc!" && return 1
  380. fi
  381. local tag="FilePath"
  382. ESD=$(xmllint --nonet --xpath "//$tag" "$dir/$eFile" | sed -E -e "s/<[\/]?$tag>//g")
  383. if [ -z "$ESD" ]; then
  384. error "Failed to find ESD URL in $eFile!" && return 1
  385. fi
  386. tag="Sha1"
  387. ESD_SUM=$(xmllint --nonet --xpath "//$tag" "$dir/$eFile" | sed -E -e "s/<[\/]?$tag>//g")
  388. tag="Size"
  389. ESD_SIZE=$(xmllint --nonet --xpath "//$tag" "$dir/$eFile" | sed -E -e "s/<[\/]?$tag>//g")
  390. rm -rf "$dir"
  391. return 0
  392. }
  393. isCompressed() {
  394. local file="$1"
  395. case "${file,,}" in
  396. *".7z" | *".zip" | *".rar" | *".lzma" | *".bz" | *".bz2" )
  397. return 0 ;;
  398. esac
  399. return 1
  400. }
  401. verifyFile() {
  402. local iso="$1"
  403. local size="$2"
  404. local total="$3"
  405. local check="$4"
  406. if [ -n "$size" ] && [[ "$total" != "$size" ]] && [[ "$size" != "0" ]]; then
  407. if [[ "$VERIFY" == [Yy1]* ]] || [[ "$DEBUG" == [Yy1]* ]]; then
  408. warn "The downloaded file has a different size ( $total bytes) than expected ( $size bytes). Please report this at $SUPPORT/issues"
  409. fi
  410. fi
  411. local hash=""
  412. local algo="SHA256"
  413. [ -z "$check" ] && return 0
  414. [[ "$VERIFY" != [Yy1]* ]] && return 0
  415. [[ "${#check}" == "40" ]] && algo="SHA1"
  416. local msg="Verifying downloaded ISO..."
  417. info "$msg" && html "$msg"
  418. if [[ "${algo,,}" != "sha256" ]]; then
  419. hash=$(sha1sum "$iso" | cut -f1 -d' ')
  420. else
  421. hash=$(sha256sum "$iso" | cut -f1 -d' ')
  422. fi
  423. if [[ "$hash" == "$check" ]]; then
  424. info "Successfully verified ISO!" && return 0
  425. fi
  426. error "The downloaded file has an unknown $algo checksum: $hash , as the expected value was: $check. Please report this at $SUPPORT/issues"
  427. return 1
  428. }
  429. downloadFile() {
  430. local iso="$1"
  431. local url="$2"
  432. local sum="$3"
  433. local size="$4"
  434. local lang="$5"
  435. local desc="$6"
  436. local msg="Downloading $desc"
  437. local rc total total_gb progress domain dots agent space folder
  438. rm -f "$iso"
  439. agent=$(get_agent)
  440. if [ -n "$size" ] && [[ "$size" != "0" ]]; then
  441. folder=$(dirname -- "$iso")
  442. space=$(df --output=avail -B 1 "$folder" | tail -n 1)
  443. total_gb=$(formatBytes "$space")
  444. (( size > space )) && error "Not enough free space to download file, only $total_gb left!" && return 1
  445. fi
  446. # Check if running with interactive TTY or redirected to docker log
  447. if [ -t 1 ]; then
  448. progress="--progress=bar:noscroll"
  449. else
  450. progress="--progress=dot:giga"
  451. fi
  452. html "$msg..."
  453. /run/progress.sh "$iso" "$size" "$msg ([P])..." &
  454. domain=$(echo "$url" | awk -F/ '{print $3}')
  455. dots=$(echo "$domain" | tr -cd '.' | wc -c)
  456. (( dots > 1 )) && domain=$(expr "$domain" : '.*\.\(.*\..*\)')
  457. if [ -n "$domain" ] && [[ "${domain,,}" != *"microsoft.com" ]]; then
  458. msg="Downloading $desc from $domain"
  459. fi
  460. info "$msg..."
  461. { wget "$url" -O "$iso" -q --timeout=30 --no-http-keep-alive --user-agent "$agent" --show-progress "$progress"; rc=$?; } || :
  462. fKill "progress.sh"
  463. if (( rc == 0 )) && [ -f "$iso" ]; then
  464. total=$(stat -c%s "$iso")
  465. total_gb=$(formatBytes "$total")
  466. if [ "$total" -lt 100000000 ]; then
  467. error "Invalid download link: $url (is only $total_gb ?). Please report this at $SUPPORT/issues" && return 1
  468. fi
  469. verifyFile "$iso" "$size" "$total" "$sum" || return 1
  470. isCompressed "$url" && UNPACK="Y"
  471. html "Download finished successfully..." && return 0
  472. fi
  473. msg="Failed to download $url"
  474. (( rc == 3 )) && error "$msg , cannot write file (disk full?)" && return 1
  475. (( rc == 4 )) && error "$msg , network failure!" && return 1
  476. (( rc == 8 )) && error "$msg , server issued an error response! Please report this at $SUPPORT/issues" && return 1
  477. error "$msg , reason: $rc"
  478. return 1
  479. }
  480. downloadImage() {
  481. local iso="$1"
  482. local version="$2"
  483. local lang="$3"
  484. local delay=5
  485. local tried="n"
  486. local success="n"
  487. local url sum size base desc language
  488. local msg="Will retry after $delay seconds..."
  489. if [[ "${version,,}" == "http"* ]]; then
  490. base=$(basename "$iso")
  491. desc=$(fromFile "$base")
  492. downloadFile "$iso" "$version" "" "" "" "$desc" && return 0
  493. info "$msg" && html "$msg" && sleep "$delay"
  494. downloadFile "$iso" "$version" "" "" "" "$desc" && return 0
  495. rm -f "$iso"
  496. return 1
  497. fi
  498. if ! validVersion "$version" "en"; then
  499. error "Invalid VERSION specified, value \"$version\" is not recognized!" && return 1
  500. fi
  501. desc=$(printVersion "$version" "")
  502. if [[ "${lang,,}" != "en" ]] && [[ "${lang,,}" != "en-"* ]]; then
  503. language=$(getLanguage "$lang" "desc")
  504. if ! validVersion "$version" "$lang"; then
  505. desc=$(printEdition "$version" "$desc")
  506. error "The $language language version of $desc is not available, please switch to English." && return 1
  507. fi
  508. desc+=" in $language"
  509. fi
  510. if isMido "$version" "$lang"; then
  511. tried="y"
  512. success="n"
  513. if getWindows "$version" "$lang" "$desc"; then
  514. success="y"
  515. else
  516. info "$msg" && html "$msg" && sleep "$delay"
  517. getWindows "$version" "$lang" "$desc" && success="y"
  518. fi
  519. if [[ "$success" == "y" ]]; then
  520. size=$(getMido "$version" "$lang" "size" )
  521. sum=$(getMido "$version" "$lang" "sum")
  522. downloadFile "$iso" "$MIDO_URL" "$sum" "$size" "$lang" "$desc" && return 0
  523. info "$msg" && html "$msg" && sleep "$delay"
  524. downloadFile "$iso" "$MIDO_URL" "$sum" "$size" "$lang" "$desc" && return 0
  525. rm -f "$iso"
  526. fi
  527. fi
  528. switchEdition "$version"
  529. if isESD "$version" "$lang"; then
  530. if [[ "$tried" != "n" ]]; then
  531. info "Failed to download $desc, will try a diferent method now..."
  532. fi
  533. tried="y"
  534. success="n"
  535. if getESD "$TMP/esd" "$version" "$lang" "$desc"; then
  536. success="y"
  537. else
  538. info "$msg" && html "$msg" && sleep "$delay"
  539. getESD "$TMP/esd" "$version" "$lang" "$desc" && success="y"
  540. fi
  541. if [[ "$success" == "y" ]]; then
  542. ISO="${ISO%.*}.esd"
  543. downloadFile "$ISO" "$ESD" "$ESD_SUM" "$ESD_SIZE" "$lang" "$desc" && return 0
  544. info "$msg" && html "$msg" && sleep "$delay"
  545. downloadFile "$ISO" "$ESD" "$ESD_SUM" "$ESD_SIZE" "$lang" "$desc" && return 0
  546. rm -f "$ISO"
  547. ISO="$iso"
  548. fi
  549. fi
  550. for ((i=1;i<=MIRRORS;i++)); do
  551. url=$(getLink "$i" "$version" "$lang")
  552. if [ -n "$url" ]; then
  553. if [[ "$tried" != "n" ]]; then
  554. info "Failed to download $desc, will try another mirror now..."
  555. fi
  556. tried="y"
  557. size=$(getSize "$i" "$version" "$lang")
  558. sum=$(getHash "$i" "$version" "$lang")
  559. downloadFile "$iso" "$url" "$sum" "$size" "$lang" "$desc" && return 0
  560. info "$msg" && html "$msg" && sleep "$delay"
  561. downloadFile "$iso" "$url" "$sum" "$size" "$lang" "$desc" && return 0
  562. rm -f "$iso"
  563. fi
  564. done
  565. return 1
  566. }
  567. return 0