Compare commits

..

27 Commits
v4.03 ... v4.16

Author SHA1 Message Date
Kroese
e16af78828 feat: Check path to custom .iso (#1069) 2025-02-28 04:07:57 +01:00
Kroese
5ae6ecbe85 feat: Update download links (#1066) 2025-02-26 23:15:13 +01:00
Kroese
c9482fe3f0 build: Update qemu-docker to v6.18 (#1065) 2025-02-26 22:55:28 +01:00
Kroese
63a9d10a27 feat: Make app name configurable (#1058) 2025-02-25 15:15:48 +01:00
Kroese
009c2c7deb build: Update qemu-docker to v6.17 (#1056) 2025-02-25 06:05:42 +01:00
renovate[bot]
06434c02f4 chore(deps): update qemux/qemu-docker docker tag to v6.16 (#1054)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-24 04:16:07 +01:00
Kroese
32b92cc03a fix: Update download links (#1053) 2025-02-24 04:15:49 +01:00
Kroese
d799079040 docs: Readme (#1052) 2025-02-22 10:02:48 +01:00
Kroese
b57d34e11e fix: Update download links (#1051) 2025-02-22 09:53:17 +01:00
Kroese
61d7e0d3be feat: Fallback to Windows 11 if version unknown (#1042) 2025-02-17 18:59:40 +01:00
krzysiuu1998
e6d7495bac fix: Update compose.yml (#1040) 2025-02-17 09:07:25 +01:00
Kroese
faa7c58366 fix: Download links (#1035) 2025-02-15 04:49:24 +01:00
Kroese
c0f5cca574 docs: Add restart policy (#1028) 2025-02-10 00:26:49 +01:00
renovate[bot]
d86731dc24 chore(deps): update qemux/qemu-docker docker tag to v6.14 (#1024) 2025-02-06 02:12:20 +01:00
Kroese
bca8cb6817 build: Update qemu-docker to v6.13 (#1010) 2025-01-15 23:45:30 +01:00
ncheng89
d9c7983bb5 feat: Support more shared directories (#987)
* Support more shared directories

Control the sharing of more directories through environment variables instead of hard-coding more shared directories
2025-01-08 15:55:17 +01:00
James Nguyen
28f6e9c76b feat: Allow symlinks in shared folder (#1001) 2025-01-08 15:38:51 +01:00
Kroese
1081855571 feat: Update download links (#996) 2025-01-06 19:26:45 +01:00
Kroese
57193b0f59 fix: Follow download redirects (#981) 2024-12-20 18:32:26 +01:00
Kroese
6825b6a45a fix: Update download links (#980) 2024-12-20 15:43:37 +01:00
Kroese
c82725ec61 docs: Readme (#958) 2024-12-06 11:19:42 +01:00
Kroese
1f0cdc9bd1 build: Update qemu-docker to v6.11 (#946) 2024-12-03 12:12:47 +01:00
Kilian von Pflugk
9654a945fb docs: Add TUN device (#940) 2024-12-01 17:07:23 +01:00
renovate[bot]
a4fdfbdf91 chore(deps): update qemux/qemu-docker docker tag to v6.10 (#929) 2024-11-26 20:16:47 +01:00
Kroese
b84a2b60a9 feat: Additional download mirrors (#923) 2024-11-24 22:00:24 +01:00
Kroese
a5b4d7760d docs: Readme (#920) 2024-11-20 13:27:15 +01:00
Kroese
2c4094b0f7 feat: Improved download method (#903) 2024-11-15 05:16:48 +01:00
9 changed files with 254 additions and 1078 deletions

View File

@@ -1,7 +1,7 @@
ARG VERSION_ARG="4.00" ARG VERSION_ARG="latest"
FROM scratch AS build-amd64 FROM scratch AS build-amd64
COPY --from=qemux/qemu-docker:6.07 / /
COPY --from=qemux/qemu:6.18 / /
ARG DEBCONF_NOWARNINGS="yes" ARG DEBCONF_NOWARNINGS="yes"
ARG DEBIAN_FRONTEND="noninteractive" ARG DEBIAN_FRONTEND="noninteractive"
@@ -11,6 +11,7 @@ RUN set -eu && \
apt-get update && \ apt-get update && \
apt-get --no-install-recommends -y install \ apt-get --no-install-recommends -y install \
bc \ bc \
jq \
curl \ curl \
7zip \ 7zip \
wsdd \ wsdd \
@@ -28,13 +29,12 @@ RUN set -eu && \
COPY --chmod=755 ./src /run/ COPY --chmod=755 ./src /run/
COPY --chmod=755 ./assets /run/assets 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-whql/releases/download/v1.9.44-0/virtio-win-1.9.44.tar.xz /drivers.txz
ADD --chmod=664 https://github.com/qemus/virtiso-whql/releases/download/v1.9.43-0/virtio-win-1.9.43.tar.xz /drivers.txz
FROM dockurr/windows-arm:${VERSION_ARG} AS build-arm64 FROM dockurr/windows-arm:${VERSION_ARG} AS build-arm64
FROM build-${TARGETARCH} FROM build-${TARGETARCH}
ARG VERSION_ARG="4.00" ARG VERSION_ARG="0.00"
RUN echo "$VERSION_ARG" > /run/version RUN echo "$VERSION_ARG" > /run/version
VOLUME /storage VOLUME /storage

View File

@@ -6,10 +6,12 @@ services:
VERSION: "11" VERSION: "11"
devices: devices:
- /dev/kvm - /dev/kvm
- /dev/net/tun
cap_add: cap_add:
- NET_ADMIN - NET_ADMIN
ports: ports:
- 8006:8006 - 8006:8006
- 3389:3389/tcp - 3389:3389/tcp
- 3389:3389/udp - 3389:3389/udp
restart: always
stop_grace_period: 2m stop_grace_period: 2m

View File

@@ -1,10 +1,11 @@
---
apiVersion: v1 apiVersion: v1
kind: PersistentVolumeClaim kind: PersistentVolumeClaim
metadata: metadata:
name: windows-pvc name: windows-pvc
spec: spec:
accessModes: accessModes:
- ReadWriteOnce - ReadWriteOnce
resources: resources:
requests: requests:
storage: 64Gi storage: 64Gi
@@ -16,59 +17,61 @@ metadata:
labels: labels:
name: windows name: windows
spec: spec:
terminationGracePeriodSeconds: 120 # the Kubernetes default is 30 seconds and it may be not enough
containers: containers:
- name: windows - name: windows
image: dockurr/windows image: dockurr/windows
ports: env:
- containerPort: 8006 - name: VERSION
protocol: TCP value: "11"
- containerPort: 3389 - name: RAM_SIZE
protocol: TCP value: "4G"
- containerPort: 3389 - name: CPU_CORES
protocol: UDP value: "2"
securityContext: - name: DISK_SIZE
privileged: true value: "64G"
env: ports:
- name: VERSION - containerPort: 8006
value: "11" - containerPort: 3389
- name: RAM_SIZE - containerPort: 3389
value: "4G" protocol: UDP
- name: CPU_CORES securityContext:
value: "2" capabilities:
- name: DISK_SIZE add:
value: "64G" - NET_ADMIN
volumeMounts: privileged: true
- mountPath: /storage volumeMounts:
name: storage - mountPath: /storage
- mountPath: /dev/kvm name: storage
name: dev-kvm - mountPath: /dev/kvm
name: dev-kvm
- mountPath: /dev/net/tun
name: dev-tun
terminationGracePeriodSeconds: 120
volumes: volumes:
- name: storage - name: storage
persistentVolumeClaim: persistentVolumeClaim:
claimName: windows-pvc claimName: windows-pvc
- name: dev-kvm - hostPath:
hostPath: path: /dev/kvm
path: /dev/kvm name: dev-kvm
- hostPath:
path: /dev/net/tun
type: CharDevice
name: dev-tun
--- ---
apiVersion: v1 apiVersion: v1
kind: Service kind: Service
metadata: metadata:
name: windows name: windows
spec: spec:
type: NodePort ports:
- name: tcp-8006
port: 8006
- name: tcp-3389
port: 3389
- name: udp-3389
port: 3389
protocol: UDP
selector: selector:
name: windows name: windows
ports: type: NodePort
- name: tcp-8006
protocol: TCP
port: 8006
targetPort: 8006
- name: tcp-3389
protocol: TCP
port: 3389
targetPort: 3389
- name: udp-3389
protocol: UDP
port: 3389
targetPort: 3389

View File

@@ -37,19 +37,21 @@ services:
VERSION: "11" VERSION: "11"
devices: devices:
- /dev/kvm - /dev/kvm
- /dev/net/tun
cap_add: cap_add:
- NET_ADMIN - NET_ADMIN
ports: ports:
- 8006:8006 - 8006:8006
- 3389:3389/tcp - 3389:3389/tcp
- 3389:3389/udp - 3389:3389/udp
restart: always
stop_grace_period: 2m stop_grace_period: 2m
``` ```
Via Docker CLI: Via Docker CLI:
```bash ```bash
docker run -it --rm -p 8006:8006 --device=/dev/kvm --cap-add NET_ADMIN --stop-timeout 120 dockurr/windows docker run -it --rm -p 8006:8006 --device=/dev/kvm --device=/dev/net/tun --cap-add NET_ADMIN --stop-timeout 120 dockurr/windows
``` ```
Via Kubernetes: Via Kubernetes:
@@ -84,7 +86,7 @@ kubectl apply -f https://raw.githubusercontent.com/dockur/windows/refs/heads/mas
### How do I select the Windows version? ### How do I select the Windows version?
By default, Windows 11 will be installed. But you can add the `VERSION` environment variable to your compose file, in order to specify an alternative Windows version to be downloaded: By default, Windows 11 Pro will be installed. But you can add the `VERSION` environment variable to your compose file, in order to specify an alternative Windows version to be downloaded:
```yaml ```yaml
environment: environment:
@@ -96,14 +98,13 @@ kubectl apply -f https://raw.githubusercontent.com/dockur/windows/refs/heads/mas
| **Value** | **Version** | **Size** | | **Value** | **Version** | **Size** |
|---|---|---| |---|---|---|
| `11` | Windows 11 Pro | 5.4 GB | | `11` | Windows 11 Pro | 5.4 GB |
| `11l` | Windows 11 LTSC | 4.2 GB | | `11l` | Windows 11 LTSC | 4.7 GB |
| `11e` | Windows 11 Enterprise | 5.8 GB | | `11e` | Windows 11 Enterprise | 4.0 GB |
|||| ||||
| `10` | Windows 10 Pro | 5.7 GB | | `10` | Windows 10 Pro | 5.7 GB |
| `10l` | Windows 10 LTSC | 4.6 GB | | `10l` | Windows 10 LTSC | 4.6 GB |
| `10e` | Windows 10 Enterprise | 5.2 GB | | `10e` | Windows 10 Enterprise | 5.2 GB |
|||| ||||
| `8` | Windows 8.1 Pro | 4.0 GB |
| `8e` | Windows 8.1 Enterprise | 3.7 GB | | `8e` | Windows 8.1 Enterprise | 3.7 GB |
| `7e` | Windows 7 Enterprise | 3.0 GB | | `7e` | Windows 7 Enterprise | 3.0 GB |
| `ve` | Windows Vista Enterprise | 3.0 GB | | `ve` | Windows Vista Enterprise | 3.0 GB |
@@ -177,7 +178,9 @@ kubectl apply -f https://raw.githubusercontent.com/dockur/windows/refs/heads/mas
### How do I run a script after installation? ### How do I run a script after installation?
To run your own script after installation, you can create a file called `install.bat` and place it in a folder together with any additional files it needs (software to be installed for example). Then bind that folder in your compose file like this: To run your own script after installation, you can create a file called `install.bat` and place it in a folder together with any additional files it needs (software to be installed for example).
Then bind that folder in your compose file like this:
```yaml ```yaml
volumes: volumes:

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,9 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -Eeuo pipefail set -Eeuo pipefail
: "${APP:="Windows"}"
: "${BOOT_MODE:="windows"}" : "${BOOT_MODE:="windows"}"
: "${SUPPORT:="https://github.com/dockur/windows"}"
APP="Windows"
SUPPORT="https://github.com/dockur/windows"
cd /run cd /run

View File

@@ -76,8 +76,6 @@ startInstall() {
BOOT="$STORAGE/$file" BOOT="$STORAGE/$file"
! migrateFiles "$BOOT" "$VERSION" && error "Migration failed!" && exit 57
fi fi
skipInstall "$BOOT" && return 1 skipInstall "$BOOT" && return 1
@@ -202,10 +200,16 @@ abortInstall() {
detectCustom() { detectCustom() {
local file base local file base
local fname="custom.iso"
CUSTOM="" CUSTOM=""
file=$(find / -maxdepth 1 -type f -iname custom.iso | head -n 1) if [ -d "/$fname" ]; then
[ ! -s "$file" ] && file=$(find "$STORAGE" -maxdepth 1 -type f -iname custom.iso | head -n 1) error "The file /$fname has an invalid path!" && return 1
fi
file=$(find / -maxdepth 1 -type f -iname "$fname" | head -n 1)
[ ! -s "$file" ] && file=$(find "$STORAGE" -maxdepth 1 -type f -iname "$fname" | head -n 1)
if [ ! -s "$file" ] && [[ "${VERSION,,}" != "http"* ]]; then if [ ! -s "$file" ] && [[ "${VERSION,,}" != "http"* ]]; then
base=$(basename "$VERSION") base=$(basename "$VERSION")
@@ -489,6 +493,10 @@ setXML() {
local file="/custom.xml" local file="/custom.xml"
if [ -d "$file" ]; then
warn "The file $file has an invalid path!"
fi
[ ! -f "$file" ] || [ ! -s "$file" ] && file="$STORAGE/custom.xml" [ ! -f "$file" ] || [ ! -s "$file" ] && file="$STORAGE/custom.xml"
[ ! -f "$file" ] || [ ! -s "$file" ] && file="/run/assets/custom.xml" [ ! -f "$file" ] || [ ! -s "$file" ] && file="/run/assets/custom.xml"
[ ! -f "$file" ] || [ ! -s "$file" ] && file="$1" [ ! -f "$file" ] || [ ! -s "$file" ] && file="$1"
@@ -620,11 +628,11 @@ updateXML() {
local language="$2" local language="$2"
local culture region user admin pass keyboard local culture region user admin pass keyboard
[ -z "$YRES" ] && YRES="720" [ -z "$HEIGHT" ] && HEIGHT="720"
[ -z "$XRES" ] && XRES="1280" [ -z "$WIDTH" ] && WIDTH="1280"
sed -i "s/<VerticalResolution>1080<\/VerticalResolution>/<VerticalResolution>$YRES<\/VerticalResolution>/g" "$asset" sed -i "s/<VerticalResolution>1080<\/VerticalResolution>/<VerticalResolution>$HEIGHT<\/VerticalResolution>/g" "$asset"
sed -i "s/<HorizontalResolution>1920<\/HorizontalResolution>/<HorizontalResolution>$XRES<\/HorizontalResolution>/g" "$asset" sed -i "s/<HorizontalResolution>1920<\/HorizontalResolution>/<HorizontalResolution>$WIDTH<\/HorizontalResolution>/g" "$asset"
culture=$(getLanguage "$language" "culture") culture=$(getLanguage "$language" "culture")
@@ -668,6 +676,11 @@ updateXML() {
sed -z "s/<AdministratorPassword>...............<Value \/>/<AdministratorPassword>\n <Value>$admin<\/Value>/g" -i "$asset" sed -z "s/<AdministratorPassword>...............<Value \/>/<AdministratorPassword>\n <Value>$admin<\/Value>/g" -i "$asset"
fi fi
if [ -n "$EDITION" ]; then
[[ "${EDITION^^}" == "CORE" ]] && EDITION="STANDARDCORE"
sed -i "s/SERVERSTANDARD<\/Value>/SERVER${EDITION^^}<\/Value>/g" "$asset"
fi
return 0 return 0
} }
@@ -677,8 +690,13 @@ addDriver() {
local path="$2" local path="$2"
local target="$3" local target="$3"
local driver="$4" local driver="$4"
local desc=""
local folder="" local folder=""
if [ -z "$id" ]; then
warn "no Windows version specified for \"$driver\" driver!" && return 0
fi
case "${id,,}" in case "${id,,}" in
"win7x86"* ) folder="w7/x86" ;; "win7x86"* ) folder="w7/x86" ;;
"win7x64"* ) folder="w7/amd64" ;; "win7x64"* ) folder="w7/amd64" ;;
@@ -698,7 +716,8 @@ addDriver() {
esac esac
if [ -z "$folder" ]; then if [ -z "$folder" ]; then
warn "no \"$driver\" driver found for \"$DETECTED\" !" && return 0 desc=$(printVersion "$id" "$id")
warn "no \"$driver\" driver available for \"$desc\" !" && return 0
fi fi
[ ! -d "$path/$driver/$folder" ] && return 0 [ ! -d "$path/$driver/$folder" ] && return 0
@@ -731,6 +750,11 @@ addDrivers() {
local msg="Adding drivers to image..." local msg="Adding drivers to image..."
info "$msg" && html "$msg" info "$msg" && html "$msg"
if [ -z "$version" ]; then
version="win11x64"
warn "Windows version unknown, falling back to Windows 11 drivers..."
fi
if ! bsdtar -xf /drivers.txz -C "$drivers"; then if ! bsdtar -xf /drivers.txz -C "$drivers"; then
error "Failed to extract drivers from archive!" && return 1 error "Failed to extract drivers from archive!" && return 1
fi fi

View File

@@ -4,25 +4,26 @@ set -Eeuo pipefail
handle_curl_error() { handle_curl_error() {
local error_code="$1" local error_code="$1"
local server_name="$2"
case "$error_code" in case "$error_code" in
1) error "Unsupported protocol!" ;; 1) error "Unsupported protocol!" ;;
2) error "Failed to initialize curl!" ;; 2) error "Failed to initialize curl!" ;;
3) error "The URL format is malformed!" ;; 3) error "The URL format is malformed!" ;;
5) error "Failed to resolve address of proxy host!" ;; 5) error "Failed to resolve address of proxy host!" ;;
6) error "Failed to resolve Microsoft servers! Is there an Internet connection?" ;; 6) error "Failed to resolve $server_name servers! Is there an Internet connection?" ;;
7) error "Failed to contact Microsoft servers! Is there an Internet connection or is the server down?" ;; 7) error "Failed to contact $server_name servers! Is there an Internet connection or is the server down?" ;;
8) error "Microsoft servers returned a malformed HTTP response!" ;; 8) error "$server_name servers returned a malformed HTTP response!" ;;
16) error "A problem was detected in the HTTP2 framing layer!" ;; 16) error "A problem was detected in the HTTP2 framing layer!" ;;
22) error "Microsoft servers returned a failing HTTP status code!" ;; 22) error "$server_name servers returned a failing HTTP status code!" ;;
23) error "Failed at writing Windows media to disk! Out of disk space or permission error?" ;; 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!" ;; 26) error "Failed to read Windows media from disk!" ;;
27) error "Ran out of memory during download!" ;; 27) error "Ran out of memory during download!" ;;
28) error "Connection timed out to Microsoft server!" ;; 28) error "Connection timed out to $server_name server!" ;;
35) error "SSL connection error from Microsoft server!" ;; 35) error "SSL connection error from $server_name server!" ;;
36) error "Failed to continue earlier download!" ;; 36) error "Failed to continue earlier download!" ;;
52) error "Received no data from the Microsoft server!" ;; 52) error "Received no data from the $server_name server!" ;;
63) error "Microsoft servers returned an unexpectedly large response!" ;; 63) error "$server_name servers returned an unexpectedly large response!" ;;
# POSIX defines exit statuses 1-125 as usable by us # POSIX defines exit statuses 1-125 as usable by us
# https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_08_02 # https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_08_02
$((error_code <= 125))) $((error_code <= 125)))
@@ -63,31 +64,33 @@ download_windows() {
local lang="$2" local lang="$2"
local desc="$3" local desc="$3"
local sku_id="" local sku_id=""
local sku_url=""
local iso_url=""
local iso_json=""
local language="" local language=""
local session_id="" local session_id=""
local user_agent="" local user_agent=""
local download_type=""
local windows_version="" local windows_version=""
local iso_download_link="" local iso_download_link=""
local download_page_html=""
local product_edition_id="" local product_edition_id=""
local iso_download_link_html="" local language_skuid_json=""
local iso_download_page_html="" local profile="606624d44113"
local language_skuid_table_html=""
case "${id,,}" in
"win11x64" ) windows_version="11" ;;
"win10x64" ) windows_version="10" ;;
"win81x64" ) windows_version="8" ;;
* ) error "Invalid VERSION specified, value \"$id\" is not recognized!" && return 1 ;;
esac
user_agent=$(get_agent) user_agent=$(get_agent)
language=$(getLanguage "$lang" "name") language=$(getLanguage "$lang" "name")
local url="https://www.microsoft.com/en-us/software-download/windows$windows_version" case "${id,,}" in
case "$windows_version" in "win11x64" ) windows_version="11" && download_type="1" ;;
8 | 10) url+="ISO";; "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 esac
local url="https://www.microsoft.com/en-us/software-download/windows$windows_version"
[[ "${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 # 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) session_id=$(cat /proc/sys/kernel/random/uuid 2> /dev/null || uuidgen --random)
@@ -96,44 +99,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 # 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 # 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 # Remove "Accept" header that curl sends by default
[[ "$DEBUG" == [Yy1]* ]] && echo " - Parsing download page: ${url}" [[ "$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") || { 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 $? handle_curl_error "$?" "Microsoft"
return $? return $?
} }
[[ "$DEBUG" == [Yy1]* ]] && echo -n "Getting Product edition ID: " [[ "$DEBUG" == [Yy1]* ]] && echo -n "Getting Product edition ID: "
# tr: Filter for only numerics to prevent HTTP parameter injection 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)
# 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)
[[ "$DEBUG" == [Yy1]* ]] && echo "$product_edition_id" [[ "$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" [[ "$DEBUG" == [Yy1]* ]] && echo "Permit Session ID: $session_id"
# Permit 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" || { 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 # This should only happen if there's been some change to how this API works
handle_curl_error $? handle_curl_error "$?" "Microsoft"
return $? return $?
} }
# Extract everything after the last slash
local url_segment_parameter="${url##*/}"
[[ "$DEBUG" == [Yy1]* ]] && echo -n "Getting language SKU ID: " [[ "$DEBUG" == [Yy1]* ]] && echo -n "Getting language SKU ID: "
# Get language -> skuID association table 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"
# 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 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") || {
# We must make this request so our next one will be allowed handle_curl_error "$?" "Microsoft"
# --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") || {
handle_curl_error $?
return $? return $?
} }
# tr: Filter for only alphanumerics or "-" to prevent HTTP parameter injection { sku_id=$(echo "$language_skuid_json" | jq --arg LANG "$language" -r '.Skus[] | select(.Language==$LANG).Id') 2>/dev/null; rc=$?; } || :
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)
if [ -z "$sku_id" ]; then if [ -z "$sku_id" ] || [[ "${sku_id,,}" == "null" ]] || (( rc != 0 )); then
language=$(getLanguage "$lang" "desc") language=$(getLanguage "$lang" "desc")
error "No download in the $language language available for $desc!" error "No download in the $language language available for $desc!"
return 1 return 1
@@ -144,28 +142,31 @@ download_windows() {
# Get ISO download link # 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) # 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 # 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." error "Microsoft servers gave us an empty response to our request for an automated download."
return 1 return 1
fi 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." error "Microsoft blocked the automated download request based on your IP address."
return 1 return 1
fi fi
# Filter for 64-bit ISO download URL if echo "$iso_json" | grep -q "We are unable to complete your request at this time."; then
# sed: HTML decode "&" character error "Microsoft blocked the automated download request based on your IP address."
# tr: Filter for only alphanumerics or punctuation return 1
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:]') fi
if ! [ "$iso_download_link" ]; then { iso_download_link=$(echo "$iso_json" | jq --argjson TYPE "$download_type" -r '.ProductDownloadOptions[] | select(.DownloadType==$TYPE).Uri') 2>/dev/null; rc=$?; } || :
# 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!" error "Microsoft servers gave us no download link to our request for an automated download!"
info "Response: $iso_json"
return 1 return 1
fi fi
@@ -229,7 +230,7 @@ download_windows_eval() {
[[ "$DEBUG" == [Yy1]* ]] && echo "Parsing download page: ${url}" [[ "$DEBUG" == [Yy1]* ]] && echo "Parsing download page: ${url}"
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") || { 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") || {
handle_curl_error $? handle_curl_error "$?" "Microsoft"
return $? return $?
} }
@@ -241,10 +242,10 @@ download_windows_eval() {
[[ "$DEBUG" == [Yy1]* ]] && echo "Getting download link.." [[ "$DEBUG" == [Yy1]* ]] && echo "Getting download link.."
if [[ "$enterprise_type" == "iot" ]]; then filter="https://go.microsoft.com/fwlink/?linkid=[0-9]\+&clcid=0x[0-9a-z]\+&culture=${culture,,}&country=${country,,}"
filter="https://go.microsoft.com/fwlink/?linkid=[0-9]\+&clcid=0x[0-9a-z]\+&culture=${culture,,}&country=${country^^}"
else if ! echo "$iso_download_page_html" | grep -io "$filter" > /dev/null; then
filter="https://go.microsoft.com/fwlink/p/?LinkID=[0-9]\+&clcid=0x[0-9a-z]\+&culture=${culture,,}&country=${country^^}" filter="https://go.microsoft.com/fwlink/p/?linkid=[0-9]\+&clcid=0x[0-9a-z]\+&culture=${culture,,}&country=${country,,}"
fi fi
iso_download_links=$(echo "$iso_download_page_html" | grep -io "$filter") || { iso_download_links=$(echo "$iso_download_page_html" | grep -io "$filter") || {
@@ -283,11 +284,11 @@ download_windows_eval() {
[[ "$DEBUG" == [Yy1]* ]] && echo "Found download link: $iso_download_link" [[ "$DEBUG" == [Yy1]* ]] && echo "Found download link: $iso_download_link"
# Follow redirect so proceeding log message is useful # Follow redirect so proceeding log message is useful
# This is a request we make this Fido doesn't # This is a request we make that 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
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") || { 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 # This should only happen if the Microsoft servers are down
handle_curl_error $? handle_curl_error "$?" "Microsoft"
return $? return $?
} }
@@ -317,6 +318,7 @@ getWindows() {
esac esac
case "${version,,}" in case "${version,,}" in
"win11${PLATFORM,,}" ) ;;
"win11${PLATFORM,,}-enterprise-iot"* ) ;; "win11${PLATFORM,,}-enterprise-iot"* ) ;;
"win11${PLATFORM,,}-enterprise-ltsc"* ) ;; "win11${PLATFORM,,}-enterprise-ltsc"* ) ;;
* ) * )
@@ -327,7 +329,7 @@ getWindows() {
esac esac
case "${version,,}" in case "${version,,}" in
"win81${PLATFORM,,}" | "win10${PLATFORM,,}" | "win11${PLATFORM,,}" ) "win10${PLATFORM,,}" | "win11${PLATFORM,,}" )
download_windows "$version" "$lang" "$edition" && return 0 download_windows "$version" "$lang" "$edition" && return 0
;; ;;
"win11${PLATFORM,,}-enterprise"* | "win10${PLATFORM,,}-enterprise"* ) "win11${PLATFORM,,}-enterprise"* | "win10${PLATFORM,,}-enterprise"* )

View File

@@ -71,6 +71,9 @@ addShare() {
echo " guest account = nobody" echo " guest account = nobody"
echo " map to guest = Bad User" echo " map to guest = Bad User"
echo " server min protocol = NT1" echo " server min protocol = NT1"
echo " follow symlinks = yes"
echo " wide links = yes"
echo " unix extensions = no"
echo "" echo ""
echo " # disable printing services" echo " # disable printing services"
echo " load printers = no" echo " load printers = no"
@@ -89,6 +92,13 @@ addShare "$share" "Data" "Shared" || error "Failed to create shared folder!"
[ -d "/data2" ] && addShare "/data2" "Data2" "Shared" [ -d "/data2" ] && addShare "/data2" "Data2" "Shared"
[ -d "/data3" ] && addShare "/data3" "Data3" "Shared" [ -d "/data3" ] && addShare "/data3" "Data3" "Shared"
IFS=',' read -r -a dirs <<< "${SHARES:-}"
for dir in "${dirs[@]}"; do
[ ! -d "$dir" ] && continue
dir_name=$(basename "$dir")
addShare "$dir" "$dir_name" "Shared $dir_name" || error "Failed to create shared folder for $dir!"
done
if ! smbd; then if ! smbd; then
error "Samba daemon failed to start!" error "Samba daemon failed to start!"
smbd -i --debug-stdout || true smbd -i --debug-stdout || true