Skip to content

Commit

Permalink
feat: Improved download method (#228)
Browse files Browse the repository at this point in the history
  • Loading branch information
kroese authored Nov 15, 2024
1 parent 0435a59 commit 24a9b1b
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 57 deletions.
7 changes: 4 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
FROM scratch
COPY --from=qemux/qemu-arm:2.20 / /
COPY --from=qemux/qemu-arm:2.21 / /

ARG VERSION_ARG="4.00"
ARG VERSION_ARG="0.00"
ARG DEBCONF_NOWARNINGS="yes"
ARG DEBIAN_FRONTEND="noninteractive"
ARG DEBCONF_NONINTERACTIVE_SEEN="true"
Expand All @@ -10,6 +10,7 @@ RUN set -eu && \
apt-get update && \
apt-get --no-install-recommends -y install \
bc \
jq \
curl \
7zip \
wsdd \
Expand All @@ -31,8 +32,8 @@ COPY --chmod=755 ./assets /run/assets
ADD --chmod=755 https://raw.githubusercontent.com/christgau/wsdd/v0.8/src/wsdd.py /usr/sbin/wsdd
ADD --chmod=664 https://github.com/qemus/virtiso-arm/releases/download/v0.1.262-2/virtio-win-0.1.262.tar.xz /drivers.txz

EXPOSE 8006 3389
VOLUME /storage
EXPOSE 8006 3389

ENV VERSION="11"
ENV RAM_SIZE="4G"
Expand Down
4 changes: 2 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,8 @@ kubectl apply -f https://raw.githubusercontent.com/dockur/windows-arm/refs/heads
| **Value** | **Version** | **Platform** | **Size** |
|---|---|---|---|
| `11` | Windows 11 Pro | ARM64 | 4.9 GB |
| `11l` | Windows 11 LTSC | ARM64 | 4.8 GB |
| `11` | Windows 11 Pro | ARM64 | 5.0 GB |
| `11l` | Windows 11 LTSC | ARM64 | 3.9 GB |
| `11e` | Windows 11 Enterprise | ARM64 | 4.8 GB |
|||||
| `10` | Windows 10 Pro | ARM64 | 3.5 GB |
Expand Down
16 changes: 10 additions & 6 deletions src/define.sh
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#!/usr/bin/env bash
set -Eeuo pipefail

: "${XRES:=""}"
: "${YRES:=""}"
: "${WIDTH:=""}"
: "${HEIGHT:=""}"
: "${VERIFY:=""}"
: "${REGION:=""}"
: "${MANUAL:=""}"
Expand Down Expand Up @@ -147,7 +147,7 @@ getLanguage() {
desc="English"
culture="en-GB" ;;
"en" | "en-"* )
lang="English (United States)"
lang="English"
desc="English"
culture="en-US" ;;
"mx" | "es-mx" )
Expand Down Expand Up @@ -263,15 +263,15 @@ getLanguage() {
desc="$lang"
culture="uk-UA" ;;
"hk" | "zh-hk" | "cn-hk" )
lang="Chinese Traditional"
lang="Chinese (Traditional)"
desc="Chinese HK"
culture="zh-TW" ;;
"tw" | "zh-tw" | "cn-tw" )
lang="Chinese Traditional"
lang="Chinese (Traditional)"
desc="Chinese TW"
culture="zh-TW" ;;
"zh" | "zh-"* | "cn" | "cn-"* )
lang="Chinese Simplified"
lang="Chinese (Simplified)"
desc="Chinese"
culture="zh-CN" ;;
esac
Expand Down Expand Up @@ -493,6 +493,10 @@ getMido() {
[[ "${lang,,}" != "en" ]] && [[ "${lang,,}" != "en-us" ]] && return 0

case "${id,,}" in
"win11arm64" )
size=5460387840
sum="57d1dfb2c6690a99fe99226540333c6c97d3fd2b557a50dfe3d68c3f675ef2b0"
;;
"win11arm64-enterprise-ltsc-eval" )
size=4252764160
sum="ccec358a760c3c581249f091ed42d04f37b2b99c347b7a58257c3cc272d7982c"
Expand Down
19 changes: 15 additions & 4 deletions src/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ finishInstall() {

rm -f "$STORAGE/windows.old"
rm -f "$STORAGE/windows.vga"
rm -f "$STORAGE/windows.args"
rm -f "$STORAGE/windows.base"
rm -f "$STORAGE/windows.boot"
rm -f "$STORAGE/windows.mode"
Expand Down Expand Up @@ -156,6 +157,11 @@ finishInstall() {
fi
fi

if [ -n "${ARGS:-}" ]; then
ARGUMENTS="$ARGS ${ARGUMENTS:-}"
echo "$ARGS" > "$STORAGE/windows.args"
fi

if [ -n "${DISK_TYPE:-}" ] && [[ "${DISK_TYPE:-}" != "scsi" ]]; then
echo "$DISK_TYPE" > "$STORAGE/windows.type"
fi
Expand Down Expand Up @@ -614,11 +620,11 @@ updateXML() {
local language="$2"
local culture region user admin pass keyboard

[ -z "$YRES" ] && YRES="720"
[ -z "$XRES" ] && XRES="1280"
[ -z "$HEIGHT" ] && HEIGHT="720"
[ -z "$WIDTH" ] && WIDTH="1280"

sed -i "s/<VerticalResolution>1080<\/VerticalResolution>/<VerticalResolution>$YRES<\/VerticalResolution>/g" "$asset"
sed -i "s/<HorizontalResolution>1920<\/HorizontalResolution>/<HorizontalResolution>$XRES<\/HorizontalResolution>/g" "$asset"
sed -i "s/<VerticalResolution>1080<\/VerticalResolution>/<VerticalResolution>$HEIGHT<\/VerticalResolution>/g" "$asset"
sed -i "s/<HorizontalResolution>1920<\/HorizontalResolution>/<HorizontalResolution>$WIDTH<\/HorizontalResolution>/g" "$asset"

culture=$(getLanguage "$language" "culture")

Expand Down Expand Up @@ -965,6 +971,11 @@ bootWindows() {

rm -rf "$TMP"

if [ -f "$STORAGE/windows.args" ]; then
ARGS=$(<"$STORAGE/windows.args")
ARGUMENTS="$ARGS ${ARGUMENTS:-}"
fi

if [ -s "$STORAGE/windows.type" ] && [ -f "$STORAGE/windows.type" ]; then
[ -z "${DISK_TYPE:-}" ] && DISK_TYPE=$(<"$STORAGE/windows.type")
fi
Expand Down
85 changes: 43 additions & 42 deletions src/mido.sh
Original file line number Diff line number Diff line change
Expand Up @@ -63,30 +63,32 @@ download_windows() {
local lang="$2"
local desc="$3"
local sku_id=""
local sku_url=""
local iso_url=""
local iso_json=""
local language=""
local session_id=""
local user_agent=""
local download_type=""
local windows_version=""
local iso_download_link=""
local download_page_html=""
local product_edition_id=""
local iso_download_link_html=""
local iso_download_page_html=""
local language_skuid_table_html=""
local language_skuid_json=""
local profile="606624d44113"

user_agent=$(get_agent)
language=$(getLanguage "$lang" "name")

case "${id,,}" in
"win11x64" ) windows_version="11" ;;
"win10x64" ) windows_version="10" ;;
"win81x64" ) windows_version="8" ;;
"win11x64" ) windows_version="11" && download_type="1" ;;
"win10x64" ) windows_version="10" && download_type="1" ;;
"win11arm64" ) windows_version="11arm64" && download_type="2" ;;
* ) error "Invalid VERSION specified, value \"$id\" is not recognized!" && return 1 ;;
esac

user_agent=$(get_agent)
language=$(getLanguage "$lang" "name")

local url="https://www.microsoft.com/en-us/software-download/windows$windows_version"
case "$windows_version" in
8 | 10) url+="ISO";;
esac
[[ "${id,,}" == "win10"* ]] && url+="ISO"

# 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
session_id=$(cat /proc/sys/kernel/random/uuid 2> /dev/null || uuidgen --random)
Expand All @@ -96,44 +98,39 @@ download_windows() {
# 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
# 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
[[ "$DEBUG" == [Yy1]* ]] && echo " - Parsing download page: ${url}"
iso_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") || {
[[ "$DEBUG" == [Yy1]* ]] && echo "Parsing download page: ${url}"
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") || {
handle_curl_error $?
return $?
}

[[ "$DEBUG" == [Yy1]* ]] && echo -n "Getting Product edition ID: "
# tr: Filter for only numerics to prevent HTTP parameter injection
# head -c was recently added to POSIX: https://austingroupbugs.net/view.php?id=407
product_edition_id=$(echo "$iso_download_page_html" | grep -Eo '<option value="[0-9]+">Windows' | cut -d '"' -f 2 | head -n 1 | tr -cd '0-9' | head -c 16)
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)
[[ "$DEBUG" == [Yy1]* ]] && echo "$product_edition_id"

if [ -z "$product_edition_id" ]; then
error "Product edition ID not found!"
return 1
fi

[[ "$DEBUG" == [Yy1]* ]] && echo "Permit Session ID: $session_id"
# Permit Session ID
# "org_id" is always the same value
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" || {
# This should only happen if there's been some change to how this API works
handle_curl_error $?
return $?
}

# Extract everything after the last slash
local url_segment_parameter="${url##*/}"

[[ "$DEBUG" == [Yy1]* ]] && echo -n "Getting language SKU ID: "
# Get language -> skuID association table
# SKU ID: This specifies the language of the ISO. We always use "English (United States)", however, the SKU for this changes with each Windows release
# We must make this request so our next one will be allowed
# --data "" is required otherwise no "Content-Length" header will be sent causing HTTP response "411 Length Required"
language_skuid_table_html=$(curl --silent --max-time 30 --request POST --user-agent "$user_agent" --data "" --header "Accept:" --max-filesize 10K --fail --proto =https --tlsv1.2 --http1.1 -- "https://www.microsoft.com/en-US/api/controls/contentinclude/html?pageId=a8f8f489-4c7f-463a-9ca6-5cff94d8d041&host=www.microsoft.com&segments=software-download,$url_segment_parameter&query=&action=getskuinformationbyproductedition&sessionId=$session_id&productEditionId=$product_edition_id&sdVersion=2") || {
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"
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") || {
handle_curl_error $?
return $?
}

# tr: Filter for only alphanumerics or "-" to prevent HTTP parameter injection
sku_id=$(echo "$language_skuid_table_html" | grep -m 1 ">${language}<" | sed 's/&quot;//g' | cut -d ',' -f 1 | cut -d ':' -f 2 | tr -cd '[:alnum:]-' | head -c 16)
{ sku_id=$(echo "$language_skuid_json" | jq --arg LANG "$language" -r '.Skus[] | select(.Language==$LANG).Id') 2>/dev/null; rc=$?; } || :

if [ -z "$sku_id" ]; then
if [ -z "$sku_id" ] || [[ "${sku_id,,}" == "null" ]] || (( rc != 0 )); then
language=$(getLanguage "$lang" "desc")
error "No download in the $language language available for $desc!"
return 1
Expand All @@ -144,28 +141,31 @@ download_windows() {

# Get ISO download link
# If any request is going to be blocked by Microsoft it's always this last one (the previous requests always seem to succeed)
# --referer: Required by Microsoft servers to allow request
iso_download_link_html=$(curl --silent --max-time 30 --request POST --user-agent "$user_agent" --data "" --referer "$url" --header "Accept:" --max-filesize 100K --fail --proto =https --tlsv1.2 --http1.1 -- "https://www.microsoft.com/en-US/api/controls/contentinclude/html?pageId=6e2a1789-ef16-4f27-a296-74ef7ef5d96b&host=www.microsoft.com&segments=software-download,$url_segment_parameter&query=&action=GetProductDownloadLinksBySku&sessionId=$session_id&skuId=$sku_id&language=English&sdVersion=2")

if ! [ "$iso_download_link_html" ]; then
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"
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")

if ! [ "$iso_json" ]; then
# This should only happen if there's been some change to how this API works
error "Microsoft servers gave us an empty response to our request for an automated download."
return 1
fi

if echo "$iso_download_link_html" | grep -q "We are unable to complete your request at this time."; then
if echo "$iso_json" | grep -q "Sentinel marked this request as rejected."; then
error "Microsoft blocked the automated download request based on your IP address."
return 1
fi

if echo "$iso_json" | grep -q "We are unable to complete your request at this time."; then
error "Microsoft blocked the automated download request based on your IP address."
return 1
fi

# Filter for 64-bit ISO download URL
# sed: HTML decode "&" character
# tr: Filter for only alphanumerics or punctuation
iso_download_link=$(echo "$iso_download_link_html" | grep -o "https://software.download.prss.microsoft.com.*IsoX64" | cut -d '"' -f 1 | sed 's/&amp;/\&/g' | tr -cd '[:alnum:][:punct:]')
{ iso_download_link=$(echo "$iso_json" | jq --argjson TYPE "$download_type" -r '.ProductDownloadOptions[] | select(.DownloadType==$TYPE).Uri') 2>/dev/null; rc=$?; } || :

if ! [ "$iso_download_link" ]; then
# This should only happen if there's been some change to the download endpoint web address
if [ -z "$iso_download_link" ] || [[ "${iso_download_link,,}" == "null" ]] || (( rc != 0 )); then
error "Microsoft servers gave us no download link to our request for an automated download!"
info "Response: $iso_json"
return 1
fi

Expand Down Expand Up @@ -283,8 +283,8 @@ download_windows_eval() {
[[ "$DEBUG" == [Yy1]* ]] && echo "Found download link: $iso_download_link"

# Follow redirect so proceeding log message is useful
# This is a request we make this Fido doesn't
# We don't need to set "--max-filesize" here because this is a HEAD request and the output is to /dev/null anyway
# This is a request we make that Fido doesn't

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") || {
# This should only happen if the Microsoft servers are down
handle_curl_error $?
Expand Down Expand Up @@ -317,6 +317,7 @@ getWindows() {
esac

case "${version,,}" in
"win11${PLATFORM,,}" ) ;;
"win11${PLATFORM,,}-enterprise-iot"* ) ;;
"win11${PLATFORM,,}-enterprise-ltsc"* ) ;;
* )
Expand All @@ -327,7 +328,7 @@ getWindows() {
esac

case "${version,,}" in
"win81${PLATFORM,,}" | "win10${PLATFORM,,}" | "win11${PLATFORM,,}" )
"win10${PLATFORM,,}" | "win11${PLATFORM,,}" )
download_windows "$version" "$lang" "$edition" && return 0
;;
"win11${PLATFORM,,}-enterprise"* | "win10${PLATFORM,,}-enterprise"* )
Expand Down

0 comments on commit 24a9b1b

Please sign in to comment.