Compare commits

...

53 Commits

Author SHA1 Message Date
Kroese efe47da76d build: Improve installation tests (#2179) 2026-08-22 01:05:26 +02:00
Kroese 2fe4645685 fix: Hide long mode from 32-bit Windows guests (#2178) 2026-08-22 00:07:19 +02:00
Kroese afa55b476c feat: Disable menu animations on NT5 and Win9x (#2177) 2026-08-21 19:30:23 +02:00
Kroese 1e5d3c75a8 feat: Add additional download mirrors (#2176) 2026-08-21 18:39:01 +02:00
Kroese 4c74370da0 feat: Shuffle download mirror order (#2175) 2026-08-21 18:33:02 +02:00
Kroese ae9fa8716b feat: Enable Hyper-V for Windows Vista and 7 (#2174) 2026-08-21 18:01:30 +02:00
Kroese 0d84fd35f5 fix: Disable generic display driver for Server 2003 (#2173) 2026-08-21 17:34:05 +02:00
Kroese d68a489295 feat: Add additional download mirrors (#2172) 2026-08-21 17:10:44 +02:00
Kroese c86067cb6e feat: Disable Shutdown Tracker for Server 2003 (#2171) 2026-08-21 15:43:49 +02:00
Kroese 37f4908f22 feat: Disable Shutdown Tracker on Windows Server (#2170) 2026-08-21 15:38:29 +02:00
Kroese 09627c4e51 feat: Import driver certificate on Windows 2003 (#2169) 2026-08-21 01:09:36 +02:00
Kroese f6e6a15b42 feat: Defer mapped Z drive until first access (#2168) 2026-08-21 00:31:09 +02:00
Kroese eeaf64bdd9 feat: Install faster graphics driver for 2000/XP/2003 (#2167) 2026-08-20 22:43:01 +02:00
Kroese eb1befd90e feat: Implement Windows 95 support (#2165) 2026-08-20 04:50:57 +02:00
Kroese afb28c9234 build: Update wsddn package to v1.27 (#2164) 2026-08-20 03:06:20 +02:00
Kroese 2a116bf655 build: Update QEMU base image to v7.48 (#2163) 2026-08-19 20:11:24 +02:00
Kroese 9af2409510 feat: Implement Windows ME support (#2161) 2026-08-18 18:04:20 +02:00
Kroese 6bfe3b1adc build: Automatically open a tunnel for stalled tests (#2159) 2026-08-18 16:18:21 +02:00
Kroese 2904a8d932 build: Update VirtIO drivers to v1.9.60 (#2155) 2026-08-18 07:21:50 +02:00
Kroese e490d142ef build: Update QEMU base image to v7.47 (#2154) 2026-08-18 05:15:55 +02:00
Kroese 2bf6ae55ab feat: Hide first-logon console during answer generation (#2152) 2026-08-18 02:15:37 +02:00
Kroese a0a4520be6 docs: Environment variables (#2153) 2026-08-18 02:13:02 +02:00
Kroese 003d7da5db fix: Change socket path (#2150) 2026-08-17 14:20:01 +02:00
Kroese 1c2efa65df docs: Environment variables (#2148) 2026-08-16 16:10:40 +02:00
Kroese 00fe722d4e build: Update QEMU base image to v7.46 (#2147) 2026-08-16 03:01:20 +02:00
Kroese 603bd079fc fix: Improve failed legacy boot detection (#2146) 2026-08-15 16:46:35 +02:00
Kroese be032648e8 fix: Handle system image shutdown (#2145) 2026-08-15 16:33:41 +02:00
Kroese 1d98f065dc fix: Find nested ISO files recursively (#2144) 2026-08-15 16:10:56 +02:00
Kroese a0822d00a7 feat: Implement Windows 98 support (#2143) 2026-08-15 16:02:41 +02:00
Kroese 8713fa50e1 fix: Error handling while generating answer file (#2142) 2026-08-15 12:54:17 +02:00
Kroese e8028db237 feat: Refactor Windows installation and startup flow (#2141) 2026-08-15 12:42:13 +02:00
Kroese e4b910fba3 fix: Decrease shutdown timeout during setup (#2140) 2026-08-15 01:21:50 +02:00
Kroese 1066895aca feat: Centralize platform and image state checks (#2139) 2026-08-14 11:46:00 +02:00
Kroese 39862b25b5 feat: Centralize boot marker checks (#2138) 2026-08-14 11:19:03 +02:00
Kroese a3461db9a8 feat: Centralize image and boot state checks (#2137) 2026-08-14 11:07:46 +02:00
Kroese 019c70dbaa feat: Add qxldod driver (#2136) 2026-08-13 23:29:47 +02:00
Kroese ffec37e758 fix: Check for ACPI support before shutdown (#2135) 2026-08-13 23:15:11 +02:00
Kroese e832e705f9 feat: Support pre-installed system images (#2134) 2026-08-13 23:10:56 +02:00
Kroese db6b085ae2 fix: Improve extracted ISO size estimation (#2133) 2026-08-13 23:07:45 +02:00
Kroese cf93c6d7f3 build: Update entrypoint (#2132) 2026-08-13 23:03:28 +02:00
Kroese afce7af300 build: Update VirtIO drivers to v1.9.59 (#2131) 2026-08-13 23:00:54 +02:00
Kroese cfac963278 feat: Install the VirtIO Balloon service on 32-bit Windows (#2130) 2026-08-13 12:36:42 +02:00
Kroese 0d7e26adad feat: Use removeState helper from base (#2127) 2026-08-10 11:36:13 +02:00
Kroese 9ef6f3c4ca fix: Migrate Win9x machine type (#2126) 2026-08-10 07:10:13 +02:00
Kroese 5b9a376111 fix: Stop console after QEMU exits (#2125) 2026-08-10 03:37:05 +02:00
Kroese a4ae486d83 build: Refactor Windows installation code (#2124) 2026-08-10 02:05:45 +02:00
Kroese c9b12ba97d build: Update QEMU base image to v7.45 (#2123) 2026-08-10 00:51:53 +02:00
Kroese 7e3e24780d feat: Enforce a minimal disk size per Windows version (#2122) 2026-08-09 22:16:20 +02:00
Kroese 433c83cab0 docs: Add QMP configuration variable (#2121) 2026-08-09 11:25:25 +02:00
Kroese 19264a42da feat: Improve error handling (#2120) 2026-08-09 02:46:04 +02:00
Kroese 33ae12677e fix: Skip ISO inspection for non-ISO downloads (#2119) 2026-08-08 17:53:15 +02:00
Kroese 133c3d3bc3 feat: Move image edition selection policy (#2118) 2026-08-08 17:39:43 +02:00
Kroese 43be9024e9 fix: Do not fail if marker cannot be created (#2117) 2026-08-08 16:33:27 +02:00
37 changed files with 8270 additions and 2791 deletions
@@ -1,8 +1,8 @@
{
"name": "Tiny11 Nano",
"name": "Windows ME",
"service": "windows",
"containerEnv": {
"VERSION": "nano11"
"VERSION": "me"
},
"forwardPorts": [8006],
"portsAttributes": {
@@ -0,0 +1,19 @@
{
"name": "Windows 98",
"service": "windows",
"containerEnv": {
"VERSION": "98"
},
"forwardPorts": [8006],
"portsAttributes": {
"8006": {
"label": "Web",
"onAutoForward": "notify"
}
},
"otherPortsAttributes": {
"onAutoForward": "ignore"
},
"dockerComposeFile": "../codespaces.yml",
"initializeCommand": "docker system prune --all --force"
}
@@ -0,0 +1,19 @@
{
"name": "Windows 95",
"service": "windows",
"containerEnv": {
"VERSION": "95"
},
"forwardPorts": [8006],
"portsAttributes": {
"8006": {
"label": "Web",
"onAutoForward": "notify"
}
},
"otherPortsAttributes": {
"onAutoForward": "ignore"
},
"dockerComposeFile": "../codespaces.yml",
"initializeCommand": "docker system prune --all --force"
}
@@ -0,0 +1,19 @@
{
"name": "Reactos",
"service": "windows",
"containerEnv": {
"VERSION": "reactos"
},
"forwardPorts": [8006],
"portsAttributes": {
"8006": {
"label": "Web",
"onAutoForward": "notify"
}
},
"otherPortsAttributes": {
"onAutoForward": "ignore"
},
"dockerComposeFile": "../codespaces.yml",
"initializeCommand": "docker system prune --all --force"
}
+320 -5
View File
@@ -73,6 +73,12 @@ on:
default: false
type: boolean
tunnel_delay:
description: Automatically open the noVNC tunnel after this many seconds
required: false
default: 0
type: number
kill_on_failure:
description: Stop the workflow when an installation failure is detected
required: false
@@ -227,11 +233,12 @@ jobs:
shell: bash
env:
CALLBACK: ${{ inputs.callback }}
VERSION: ${{ inputs.version }}
run: |
set -Eeuo pipefail
case "$CALLBACK" in
powershell | legacy)
powershell | legacy | basic)
;;
*)
echo "Unsupported guest script type: $CALLBACK"
@@ -249,6 +256,7 @@ jobs:
printf '%s\n' "$token" > "$RUNNER_TEMP/data/validation.token"
printf '%s\n' "$token" > "$RUNNER_TEMP/oem/validation.token"
if [[ "$CALLBACK" != "basic" ]]; then
cat > "$RUNNER_TEMP/oem/sync-log.bat" <<'BATCH'
@echo off
setlocal
@@ -327,6 +335,7 @@ jobs:
shell.Run "cmd.exe /C echo VALIDATION_CALLBACK_FAILED^>COM1", 0, True
VBSCRIPT
fi
case "$CALLBACK" in
powershell)
@@ -361,6 +370,12 @@ jobs:
}
}
if (-not (Test-Path -LiteralPath "Z:\" -PathType Container)) {
throw "Mapped Z: drive is not available."
}
$driveZ = "ok"
$share = (
Get-Content `
-LiteralPath "\\host.lan\Data\validation.token" `
@@ -423,6 +438,7 @@ jobs:
version = [string]$windows.Version
build = [string]$windows.BuildNumber
platform = $platform
drive_z = $driveZ
oem_file = $oemFile
share = $share
share_write = "ok"
@@ -582,6 +598,15 @@ jobs:
TestSharedFolder = "ok"
End Function
Function TestMappedDrive(filesystem)
If Not filesystem.DriveExists("Z:") Then
Err.Raise vbObjectError + 4, "TestMappedDrive", _
"Mapped Z: drive is not available."
End If
TestMappedDrive = "ok"
End Function
Function TestInternet()
Dim request
Dim response
@@ -639,6 +664,7 @@ jobs:
Dim oemFile
Dim share
Dim shareWrite
Dim driveZ
Dim internet
Dim json
@@ -677,6 +703,7 @@ jobs:
oemFile = ReadTextFile(filesystem, "C:\OEM\validation.token")
share = ReadTextFile(filesystem, "\\host.lan\Data\validation.token")
shareWrite = TestSharedFolder(filesystem, token)
driveZ = TestMappedDrive(filesystem)
internet = TestInternet()
End If
@@ -692,6 +719,7 @@ jobs:
"""oem_file"":""" & EscapeJson(oemFile) & """," & _
"""share"":""" & EscapeJson(share) & """," & _
"""share_write"":""" & EscapeJson(shareWrite) & """," & _
"""drive_z"":""" & EscapeJson(driveZ) & """," & _
"""internet"":""" & EscapeJson(internet) & """" & _
"}"
@@ -764,6 +792,42 @@ jobs:
exit /B %result%
EOF
;;
basic)
case "${VERSION,,}" in
95) share_host="Host" ;;
*) share_host="host.lan" ;;
esac
sed "s|@@SHARE_HOST@@|$share_host|g" > "$RUNNER_TEMP/oem/install.bat" <<'BATCH'
@echo off
:retry
if not exist \\@@SHARE_HOST@@\Data\validation.token goto wait
if not exist Z:\validation.token goto wait
echo TOKEN> C:\OEM\validation.result
type C:\OEM\validation.token >> C:\OEM\validation.result
echo SHARE>> C:\OEM\validation.result
type \\@@SHARE_HOST@@\Data\validation.token >> C:\OEM\validation.result
echo DRIVE_Z>> C:\OEM\validation.result
echo ok>> C:\OEM\validation.result
echo VERSION>> C:\OEM\validation.result
ver >> C:\OEM\validation.result
if exist \\@@SHARE_HOST@@\Data\validation.result del \\@@SHARE_HOST@@\Data\validation.result >nul
copy C:\OEM\validation.result \\@@SHARE_HOST@@\Data\validation.result >nul
if errorlevel 1 goto wait
goto end
:wait
ping 127.0.0.1 -n 6 >nul
goto retry
:end
BATCH
;;
esac
echo "token=$token" >> "$GITHUB_OUTPUT"
@@ -890,6 +954,9 @@ jobs:
- name: Install and validate Windows
shell: bash
env:
CALLBACK: ${{ inputs.callback }}
ENABLE_TUNNEL: ${{ inputs.enable_tunnel }}
TUNNEL_DELAY: ${{ inputs.tunnel_delay }}
KILL_ON_FAILURE: ${{ inputs.kill_on_failure }}
INSTALL_TIMEOUT: ${{ inputs.install_timeout }}
REBOOT_TIMEOUT: ${{ inputs.reboot_timeout }}
@@ -901,6 +968,7 @@ jobs:
EXPECTED_PLATFORM: ${{ inputs.platform }}
MINIMUM_BUILD: ${{ inputs.minimum_build }}
DISPLAY_NAME: ${{ inputs.name }}
VERSION: ${{ inputs.version }}
run: |
set -Eeuo pipefail
@@ -954,8 +1022,82 @@ jobs:
) &
guest_logs_pid="$!"
tunnel_delay_pid=""
start_delayed_tunnel() {
local state
local novnc_url
sleep "$TUNNEL_DELAY"
state="$(
docker inspect \
--format '{{.State.Status}}' \
"$CONTAINER" 2>/dev/null || true
)"
if [[ "$state" != "running" ]]; then
return 0
fi
echo
echo "------------------------------------------------------------"
echo "Installation is still running after $((TUNNEL_DELAY / 60)) minutes."
echo "Starting temporary noVNC tunnel..."
if docker run --detach \
--name "$TUNNEL" \
--network "container:$CONTAINER" \
cloudflare/cloudflared:latest \
tunnel \
--no-autoupdate \
--url http://127.0.0.1:8006 > /dev/null 2>/dev/null; then
novnc_url=""
for _ in {1..30}; do
novnc_url="$(
docker logs "$TUNNEL" 2>&1 |
grep -Eo 'https://[-a-z0-9]+\.trycloudflare\.com' |
tail -n 1 || true
)"
[ -n "$novnc_url" ] && break
sleep 2
done
if [ -n "$novnc_url" ]; then
echo
echo "::notice title=noVNC viewer::$novnc_url"
echo "noVNC viewer: $novnc_url"
echo "Warning: this temporary URL is publicly accessible."
{
echo "### noVNC viewer"
echo
echo "[$novnc_url]($novnc_url)"
echo
echo "> This temporary URL is publicly accessible while the job is running."
} >> "$GITHUB_STEP_SUMMARY"
else
echo "::warning::Failed to obtain a noVNC tunnel URL."
docker logs "$TUNNEL" 2>&1 || true
fi
else
echo "::warning::Failed to start the noVNC tunnel."
fi
}
if [[ "$ENABLE_TUNNEL" != "true" ]] && (( TUNNEL_DELAY > 0 )); then
start_delayed_tunnel &
tunnel_delay_pid="$!"
fi
stop_logs() {
if [[ -n "$tunnel_delay_pid" ]]; then
kill "$tunnel_delay_pid" 2>/dev/null || true
wait "$tunnel_delay_pid" 2>/dev/null || true
fi
kill "$logs_pid" "$guest_logs_pid" 2>/dev/null || true
wait "$logs_pid" "$guest_logs_pid" 2>/dev/null || true
print_guest_log
@@ -975,18 +1117,68 @@ jobs:
echo
echo "Automatic termination is disabled by kill_on_failure."
echo "The container and noVNC tunnel will remain available."
echo "The container will remain available for debugging."
echo "::warning title=Failure detected::$message"
failure_checks_disabled=1
return 0
}
check_rdp_port() {
local container_ip
local attempt
container_ip="$(
docker inspect \
--format '{{range .NetworkSettings.Networks}}{{println .IPAddress}}{{end}}' \
"$CONTAINER" 2>/dev/null |
head -n 1 || true
)"
if [[ -z "$container_ip" ]]; then
echo "Could not determine the Windows container IP address."
return 1
fi
echo
echo "Checking TCP port 3389 at $container_ip..."
for attempt in {1..30}; do
if timeout 2 bash -c \
"exec 3<>/dev/tcp/$container_ip/3389; exec 3>&-; exec 3<&-" \
2>/dev/null; then
echo "TCP port 3389 is open."
return 0
fi
sleep 2
done
case "${VERSION,,}" in
95 | 98 | me)
echo "::warning::TCP port 3389 is not open for $DISPLAY_NAME; this is allowed."
return 0
;;
esac
echo "TCP port 3389 is not open for $DISPLAY_NAME."
return 1
}
deadline=$((SECONDS + INSTALL_TIMEOUT))
reboot_timeout="$REBOOT_TIMEOUT"
minimum_reboots="$MINIMUM_REBOOTS"
boot_loop_limit="$BOOT_LOOP_LIMIT"
first_bios_start=-1
version_lower="${VERSION,,}"
case "$version_lower" in
95 | 98 | me)
reboot_timeout=$((REBOOT_TIMEOUT * 2))
echo "Extended first reboot timeout to $((reboot_timeout / 60)) minutes for $DISPLAY_NAME."
;;
esac
while (( SECONDS < deadline )); do
state="$(
docker inspect \
@@ -1023,7 +1215,7 @@ jobs:
fi
echo "Automatic termination is disabled by kill_on_failure."
echo "The container and noVNC tunnel will remain available."
echo "The container will remain available for debugging."
echo "::warning title=Failure detected::The guest validation callback failed."
failure_checks_disabled=1
fi
@@ -1108,7 +1300,8 @@ jobs:
fi
fi
if (( bios_starts >= boot_loop_limit )) &&
if (( failure_checks_disabled == 0 &&
bios_starts >= boot_loop_limit )) &&
(( hard_disk_boots >= boot_loop_limit ||
dvd_boots >= boot_loop_limit ||
not_bootable_disk >= boot_loop_limit ||
@@ -1127,7 +1320,118 @@ jobs:
echo "No-bootable-device failures: $no_bootable_device"
echo "BOOTMGR failures: $bootmgr_missing"
exit 1
if ! handle_failure "Detected a repeated BIOS boot loop."; then
exit 1
fi
fi
basic_result="$RUNNER_TEMP/data/validation.result"
if [[ "$CALLBACK" == "basic" && -s "$basic_result" ]]; then
basic_response="$(tr -d '\r' < "$basic_result")"
oem_file="$(
awk '$0 == "TOKEN" { getline; print; exit }' <<< "$basic_response"
)"
share="$(
awk '$0 == "SHARE" { getline; print; exit }' <<< "$basic_response"
)"
drive_z="$(
awk '$0 == "DRIVE_Z" { getline; print; exit }' <<< "$basic_response"
)"
version="$(
awk '
$0 == "VERSION" { found = 1; next }
found && NF { print; exit }
' <<< "$basic_response"
)"
if [[ -z "$oem_file" || -z "$share" || -z "$drive_z" || -z "$version" ]] ||
! grep -Eq '[0-9]+\.[0-9]+\.[0-9]+' <<< "$version"; then
sleep 1
continue
fi
stop_logs
trap - EXIT
echo
echo "------------------------------------------------------------"
echo "Received response:"
printf '%s\n' "$basic_response"
if [[ "$oem_file" != "$EXPECTED_TOKEN" ]]; then
echo "Failed to read C:\\OEM\\validation.token."
echo "Expected contents:"
echo " $EXPECTED_TOKEN"
echo "Received:"
echo " ${oem_file:-empty}"
exit 1
fi
if [[ "$share" != "$EXPECTED_TOKEN" ]]; then
echo "Failed to read \\\\host.lan\\Data\\validation.token."
echo "Expected contents:"
echo " $EXPECTED_TOKEN"
echo "Received:"
echo " ${share:-empty}"
exit 1
fi
if [[ "$drive_z" != "ok" ]]; then
echo "The mapped Z: drive test did not succeed."
exit 1
fi
normalized_caption="${version//\(R\)/}"
normalized_expected_caption="${EXPECTED_CAPTION//\(R\)/}"
if [[ "$normalized_caption" != *"$normalized_expected_caption"* ]]; then
echo "Expected caption containing:"
echo " $EXPECTED_CAPTION"
echo "Received:"
echo " $version"
exit 1
fi
version_number="$(
grep -Eo '[0-9]+\.[0-9]+\.[0-9]+' <<< "$version" |
head -n 1 || true
)"
if [[ ! "$version_number" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Unexpected Windows version: $version"
exit 1
fi
build="${version_number##*.}"
if [[ ! "$build" =~ ^[0-9]+$ ]]; then
echo "Invalid Windows build number: $build"
exit 1
fi
if (( build < MINIMUM_BUILD )); then
echo "Expected build $MINIMUM_BUILD or newer."
echo "Received build: $build"
exit 1
fi
if ! check_rdp_port; then
exit 1
fi
echo
echo "$DISPLAY_NAME installed successfully."
echo "Version: $version_number"
echo "Build: $build"
echo "OEM files: copied successfully"
echo "Shared folder: readable and writable"
echo "Mapped Z: drive: available"
exit 0
fi
response="$(
@@ -1160,6 +1464,7 @@ jobs:
oem_file="$(jq -r '.oem_file // empty' <<< "$response")"
share="$(jq -r '.share // empty' <<< "$response")"
share_write="$(jq -r '.share_write // empty' <<< "$response")"
drive_z="$(jq -r '.drive_z // empty' <<< "$response")"
internet="$(jq -r '.internet // empty' <<< "$response")"
if [[ "$token" != "$EXPECTED_TOKEN" ]]; then
@@ -1190,6 +1495,11 @@ jobs:
exit 1
fi
if [[ "$drive_z" != "ok" ]]; then
echo "The mapped Z: drive test did not succeed."
exit 1
fi
if [[ "$internet" != "ok" ]]; then
echo "The guest internet connection test did not succeed."
exit 1
@@ -1240,6 +1550,10 @@ jobs:
exit 1
fi
if ! check_rdp_port; then
exit 1
fi
echo
echo "$DISPLAY_NAME installed successfully."
echo "Version: $version"
@@ -1247,6 +1561,7 @@ jobs:
echo "Platform: $platform"
echo "OEM files: copied successfully"
echo "Shared folder: readable and writable"
echo "Mapped Z: drive: available"
echo "Internet connection: accessible"
exit 0
fi
+2 -1
View File
@@ -52,9 +52,10 @@ jobs:
version: ${{ inputs.version }}
callback: ${{ inputs.callback }}
enable_tunnel: false
tunnel_delay: 1800
kill_on_failure: true
install_timeout: 9000
reboot_timeout: 1800
reboot_timeout: 18000
minimum_reboots: 1
boot_loop_limit: 10
expected_caption: ${{ inputs.expected_caption }}
+33
View File
@@ -206,6 +206,39 @@ jobs:
minimum_build: ${{ matrix.minimum_build }}
platform: ${{ matrix.platform }}
win9x:
strategy:
fail-fast: false
matrix:
include:
- name: Windows 95
version: "95"
expected_caption: Windows 95
minimum_build: 950
platform: x86
- name: Windows 98
version: "98"
expected_caption: Windows 98
minimum_build: 2222
platform: x86
- name: Windows ME
version: "me"
expected_caption: Windows Millennium
minimum_build: 3000
platform: x86
name: ${{ matrix.name }}
uses: ./.github/workflows/settings.yml
with:
name: ${{ matrix.name }}
version: ${{ matrix.version }}
callback: basic
expected_caption: ${{ matrix.expected_caption }}
minimum_build: ${{ matrix.minimum_build }}
platform: ${{ matrix.platform }}
tiny:
strategy:
fail-fast: false
+5 -7
View File
@@ -3,13 +3,13 @@
ARG VERSION_ARG="latest"
FROM scratch AS build-amd64
COPY --from=qemux/qemu:7.44 / /
COPY --from=qemux/qemu:7.48 / /
ARG TARGETARCH
ARG VERSION_UDF="1.2.0"
ARG VERSION_WSDD="1.26"
ARG VERSION_VIRTIO="1.9.58"
ARG VERSION_WSDD="1.27"
ARG VERSION_VIRTIO="1.9.60"
ARG VERSION_BLINTER="1.0.112"
ARG DEBCONF_NOWARNINGS="yes"
@@ -21,6 +21,7 @@ RUN <<EOF
apt-get update
apt-get --no-install-recommends -y install \
gcab \
samba \
mtools \
wimtools \
@@ -32,10 +33,7 @@ RUN <<EOF
libarchive-tools
# Install Blinter
python3 -m pip install \
--break-system-packages \
--no-cache-dir \
"Blinter==${VERSION_BLINTER}"
python3 -m pip install --break-system-packages --root-user-action=ignore --no-cache-dir "Blinter==${VERSION_BLINTER}"
# Install wsddn package
wget "https://github.com/gershnik/wsdd-native/releases/download/v${VERSION_WSDD}/wsddn_${VERSION_WSDD}_${TARGETARCH}.deb" -O /tmp/wsddn.deb -q --timeout=10
+3
View File
@@ -15,6 +15,9 @@ if exist "%SETUP_COMPLETE%" exit /b 0
type nul > "%SETUP_STARTED%"
rem Disable Shutdown Event Tracker.
reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\Reliability" /v "ShutdownReasonOn" /t REG_DWORD /d 0 /f
rem Allow guest access to network shares.
reg.exe add "HKLM\SYSTEM\CurrentControlSet\Services\LanmanWorkstation\Parameters" /v "AllowInsecureGuestAuth" /t REG_DWORD /d 1 /f
+3
View File
@@ -15,6 +15,9 @@ if exist "%SETUP_COMPLETE%" exit /b 0
type nul > "%SETUP_STARTED%"
rem Disable Shutdown Event Tracker.
reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\Reliability" /v "ShutdownReasonOn" /t REG_DWORD /d 0 /f
rem Allow guest access to network shares.
reg.exe add "HKLM\SYSTEM\CurrentControlSet\Services\LanmanWorkstation\Parameters" /v "AllowInsecureGuestAuth" /t REG_DWORD /d 1 /f
+3
View File
@@ -15,6 +15,9 @@ if exist "%SETUP_COMPLETE%" exit /b 0
type nul > "%SETUP_STARTED%"
rem Disable Shutdown Event Tracker.
reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\Reliability" /v "ShutdownReasonOn" /t REG_DWORD /d 0 /f
rem Allow guest access to network shares.
reg.exe add "HKLM\SYSTEM\CurrentControlSet\Services\LanmanWorkstation\Parameters" /v "AllowInsecureGuestAuth" /t REG_DWORD /d 1 /f
+3
View File
@@ -15,6 +15,9 @@ if exist "%SETUP_COMPLETE%" exit /b 0
type nul > "%SETUP_STARTED%"
rem Disable Shutdown Event Tracker.
reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\Reliability" /v "ShutdownReasonOn" /t REG_DWORD /d 0 /f
rem Allow guest access to network shares.
reg.exe add "HKLM\SYSTEM\CurrentControlSet\Services\LanmanWorkstation\Parameters" /v "AllowInsecureGuestAuth" /t REG_DWORD /d 1 /f
+3
View File
@@ -15,6 +15,9 @@ if exist "%SETUP_COMPLETE%" exit /b 0
type nul > "%SETUP_STARTED%"
rem Disable Shutdown Event Tracker.
reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\Reliability" /v "ShutdownReasonOn" /t REG_DWORD /d 0 /f
rem Allow guest access to network shares.
reg.exe add "HKLM\SYSTEM\CurrentControlSet\Services\LanmanWorkstation\Parameters" /v "AllowInsecureGuestAuth" /t REG_DWORD /d 1 /f
+3
View File
@@ -15,6 +15,9 @@ if exist "%SETUP_COMPLETE%" exit /b 0
type nul > "%SETUP_STARTED%"
rem Disable Shutdown Event Tracker.
reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\Reliability" /v "ShutdownReasonOn" /t REG_DWORD /d 0 /f
rem Allow guest access to network shares.
reg.exe add "HKLM\SYSTEM\CurrentControlSet\Services\LanmanWorkstation\Parameters" /v "AllowInsecureGuestAuth" /t REG_DWORD /d 1 /f
+3
View File
@@ -15,6 +15,9 @@ if exist "%SETUP_COMPLETE%" exit /b 0
type nul > "%SETUP_STARTED%"
rem Disable Shutdown Event Tracker.
reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\Reliability" /v "ShutdownReasonOn" /t REG_DWORD /d 0 /f
rem Allow guest access to network shares.
reg.exe add "HKLM\SYSTEM\CurrentControlSet\Services\LanmanWorkstation\Parameters" /v "AllowInsecureGuestAuth" /t REG_DWORD /d 1 /f
+4
View File
@@ -59,6 +59,10 @@ netsh advfirewall firewall set rule group="@FirewallAPI.dll,-32752" new enable=Y
rem Enable File Sharing.
netsh advfirewall firewall set rule group="@FirewallAPI.dll,-28502" new enable=Yes
rem Install the VirtIO Balloon service once.
sc.exe query BalloonService >nul 2>&1
if errorlevel 1 "%SystemRoot%\Drivers\Balloon\blnsvr.exe" -i
rem BEGIN PRODUCT_KEY
rem Install the product key without activating Windows immediately.
cscript.exe //B //Nologo "%SystemRoot%\System32\slmgr.vbs" /ipk "XXX"
+4
View File
@@ -59,6 +59,10 @@ netsh advfirewall firewall set rule group="@FirewallAPI.dll,-32752" new enable=Y
rem Enable File Sharing.
netsh advfirewall firewall set rule group="@FirewallAPI.dll,-28502" new enable=Yes
rem Install the VirtIO Balloon service once.
sc.exe query BalloonService >nul 2>&1
if errorlevel 1 "%SystemRoot%\Drivers\Balloon\blnsvr.exe" -i
rem BEGIN PRODUCT_KEY
rem Install the product key without activating Windows immediately.
cscript.exe //B //Nologo "%SystemRoot%\System32\slmgr.vbs" /ipk "XXX"
+4
View File
@@ -59,6 +59,10 @@ netsh advfirewall firewall set rule group="@FirewallAPI.dll,-32752" new enable=Y
rem Enable File Sharing.
netsh advfirewall firewall set rule group="@FirewallAPI.dll,-28502" new enable=Yes
rem Install the VirtIO Balloon service once.
sc.exe query BalloonService >nul 2>&1
if errorlevel 1 "%SystemRoot%\Drivers\Balloon\blnsvr.exe" -i
rem BEGIN PRODUCT_KEY
rem Install the product key without activating Windows immediately.
cscript.exe //B //Nologo "%SystemRoot%\System32\slmgr.vbs" /ipk "XXX"
+4
View File
@@ -71,6 +71,10 @@ netsh advfirewall firewall set rule group="@FirewallAPI.dll,-32752" new enable=Y
rem Enable File Sharing.
netsh advfirewall firewall set rule group="@FirewallAPI.dll,-28502" new enable=Yes
rem Install the VirtIO Balloon service once.
sc.exe query BalloonService >nul 2>&1
if errorlevel 1 "%SystemRoot%\Drivers\Balloon\blnsvr.exe" -i
rem BEGIN PRODUCT_KEY
rem Install the product key without activating Windows immediately.
cscript.exe //B //Nologo "%SystemRoot%\System32\slmgr.vbs" /ipk "XXX"
+4
View File
@@ -71,6 +71,10 @@ netsh advfirewall firewall set rule group="@FirewallAPI.dll,-32752" new enable=Y
rem Enable File Sharing.
netsh advfirewall firewall set rule group="@FirewallAPI.dll,-28502" new enable=Yes
rem Install the VirtIO Balloon service once.
sc.exe query BalloonService >nul 2>&1
if errorlevel 1 "%SystemRoot%\Drivers\Balloon\blnsvr.exe" -i
rem BEGIN PRODUCT_KEY
rem Install the product key without activating Windows immediately.
cscript.exe //B //Nologo "%SystemRoot%\System32\slmgr.vbs" /ipk "XXX"
+4
View File
@@ -71,6 +71,10 @@ netsh advfirewall firewall set rule group="@FirewallAPI.dll,-32752" new enable=Y
rem Enable File Sharing.
netsh advfirewall firewall set rule group="@FirewallAPI.dll,-28502" new enable=Yes
rem Install the VirtIO Balloon service once.
sc.exe query BalloonService >nul 2>&1
if errorlevel 1 "%SystemRoot%\Drivers\Balloon\blnsvr.exe" -i
rem BEGIN PRODUCT_KEY
rem Install the product key without activating Windows immediately.
cscript.exe //B //Nologo "%SystemRoot%\System32\slmgr.vbs" /ipk "XXX"
+9 -6
View File
@@ -41,7 +41,7 @@ An empty default means the variable is unset and its value is determined automat
|---|---|---|
| `DISK_SIZE` | `64G` | Size of the primary disk. |
| `DISK_FMT` | `raw` | Disk image format: `raw` or `qcow2`. |
| `DISK_TYPE` | `scsi` | Disk device type, such as `sata`, `scsi`, `nvme`, or `blk`. |
| `DISK_TYPE` | `scsi` | Disk device type, such as `ide`, `scsi`, or `blk`. |
| `DISK_CACHE` | `none` | Disk cache mode, such as `none` or `writeback`. |
| `DISK_IO` | `native` | Disk I/O mode, such as `native`, `threads`, or `io_uring`. |
| `DISK_DISCARD` | `unmap` | Discard/TRIM mode for the primary disk. |
@@ -79,11 +79,10 @@ An empty default means the variable is unset and its value is determined automat
| Variable | Default | Description |
|---|---|---|
| `DISPLAY` | `web` | Display backend, such as `web`, `vnc`, `disabled`, or `none`. |
| `LOSSY` | `N` | Enables lossy VNC compression to reduce bandwidth usage. |
| `VGA` | `virtio` | QEMU video adapter model. |
| `WIDTH` | `1280` | Display width configured in Windows. |
| `HEIGHT` | `720` | Display height configured in Windows. |
| `GPU` | `N` | Enables experimental Intel iGPU acceleration. |
| `GPU` | `N` | Enables experimental GPU acceleration. |
| `RENDERNODE` | `/dev/dri/renderD128` | Render node used for GPU acceleration. |
## 🌍 Web UI
@@ -93,8 +92,8 @@ An empty default means the variable is unset and its value is determined automat
| `WEB` | `Y` | Enables the web interface. |
| `WEB_PORT` | `8006` | Port for the web interface. |
| `VNC_PORT` | `5900` | Port for the VNC server. |
| `LOSSY` | `N` | Enables lossy VNC compression to reduce bandwidth usage. |
| `AUDIO` | `N` | Streams guest audio to the web viewer. |
| `SOUND` | `intel-hda` | QEMU audio device used by the web viewer. |
| `PROTECT` | `N` | Enables password protection for the web interface. |
## 📁 File Sharing
@@ -112,9 +111,12 @@ An empty default means the variable is unset and its value is determined automat
|---|---|---|
| `MACHINE` | `q35` | QEMU machine type. |
| `PCI_BUS` | `pcie.0` | Overrides the PCI bus used for attached devices. |
| `RNG` | `Y` | Adds the Virtio RNG device to the machine. |
| `UUID` | | UUID assigned to Windows. |
| `HPET` | `off` | QEMU HPET timer setting. |
| `VMPORT` | `off` | QEMU VMware port setting. |
| `HPET` | `off` | HPET timer setting. |
| `VMPORT` | `off` | VMware port setting. |
| `MOUSE` | `usb-tablet` | Pointing device used by the machine. |
| `SOUND` | `intel-hda` | Audio device used when `AUDIO=Y`. |
| `SM_BIOS` | | Additional arguments passed to QEMUs `-smbios` option. |
| `ARGUMENTS` | | Additional raw arguments appended to the QEMU command line. |
@@ -177,3 +179,4 @@ Also see [Dynamic memory allocation](https://github.com/qemus/qemu/blob/master/d
| `LOG` | `N` | Saves all output from `install.bat` to `C:\OEM\install.log` for troubleshooting. |
| `SERIAL` | `mon:stdio` | QEMU serial device configuration. |
| `MONITOR` | | QEMU monitor configuration. |
| `QMP` | | QEMU Machine Protocol configuration. |
+3
View File
@@ -125,6 +125,9 @@ For a complete graphical desktop experience, see [WinBoat](https://winboat.app),
| `vu` | Windows Vista Ultimate | 3.0 GB |
| `xp` | Windows XP Professional | 0.6 GB |
| `2k` | Windows 2000 Professional | 0.4 GB |
| `me` | Windows ME | 0.5 GB |
| `98` | Windows 98 | 0.7 GB |
| `95` | Windows 95 | 0.6 GB |
||||
| `2025` | Windows Server 2025 | 7.6 GB |
| `2022` | Windows Server 2022 | 6.0 GB |
+229 -891
View File
File diff suppressed because it is too large Load Diff
+3259
View File
File diff suppressed because it is too large Load Diff
+954 -284
View File
File diff suppressed because it is too large Load Diff
+5 -1
View File
@@ -13,9 +13,13 @@ cd /run
. init.sh # Initialize system
. memory.sh # Check memory
. server.sh # Start webserver
. download.sh # Load functions
. define.sh # Define versions
. mido.sh # Download Windows
. answer.sh # Setup answer files
. answer.sh # Modern unattended
. batch.sh # Win9x unattended
. sif.sh # NT5 unattended
. legacy.sh # Legacy installs
. image.sh # Detect image files
. install.sh # Run installation
. disk.sh # Initialize disks
+308 -431
View File
File diff suppressed because it is too large Load Diff
+1333 -1067
View File
File diff suppressed because it is too large Load Diff
+369
View File
@@ -0,0 +1,369 @@
#!/usr/bin/env bash
set -Eeuo pipefail
setMachine() {
local id="$1"
local iso="$2"
local dir="$3"
local desc="$4"
if isLegacy "$id"; then
if ! legacyInstall "$id" "$iso" "$dir" "$desc"; then
error "Failed to prepare $desc ISO!"
return 1
fi
writeState "mode" "windows_legacy" || return 1
case "${id,,}" in
"win9"* | "winnt4" | "win2k"* | "reactos" )
writeState "old" "pc" || return 1
writeState "type" "auto" || return 1 ;;
esac
case "${id,,}" in
"winnt4" )
writeState "vga" "cirrus" || return 1 ;;
*) writeState "vga" "std" || return 1 ;;
esac
case "${id,,}" in
"win9"* | "winnt4" )
writeState "hv" "N" || return 1
writeState "usb" "N" || return 1
writeState "port" "on" || return 1
writeState "net" "pcnet" || return 1
writeState "sound" "AC97" || return 1 ;;
"win2k"* )
writeState "hv" "N" || return 1
writeState "net" "rtl8139" || return 1
writeState "usb" "pci-ohci" || return 1
writeState "sound" "usb-audio" || return 1 ;;
"winxpx"* | "win2003"* )
writeState "hv" "N" || return 1
writeState "type" "blk" || return 1
writeState "net" "rtl8139" || return 1
writeState "sound" "usb-audio" || return 1 ;;
"reactos" )
writeState "sound" "AC97" || return 1
writeState "net" "rtl8139" || return 1
writeState "usb" "pci-ohci" || return 1
if isReactOSLiveCD "$iso"; then
SYSTEM="$iso"
createMarker "kill" || return 1
fi ;;
esac
fi
restoreMachine || return 1
restoreBootMode || return 1
case "${id,,}" in
"win95" | "winnt4" )
# Windows 95 does not support ACPI so disable graceful shutdown
createMarker "kill" || return 1 ;;
esac
case "${id,,}" in
"win9"* | "winnt4" | "win2k"* | *"x86"* )
# Legacy 32-bit Windows may enter an incompatible PAE/DEP path when the
# NX flag is exposed, causing installation failures or repeated resets.
#
# Long mode is unusable by 32-bit guests and may cause the firmware to
# allocate PCI resources above 4 GB, which older systems may not handle.
writeState "flag" "nx=off,lm=off" || return 1 ;;
esac
case "${id,,}" in
"win9"* | "winnt4" | "win2k"* | "winxp"* | "win2003"* | \
"winvistax86"* | "win7x86"* | "reactos" )
if isQ35 "$MACHINE"; then
# pc-q35-2.11 began advertising a synthetic 64-bit PCI MMIO aperture.
# Older Windows ACPI implementations may reject that resource layout,
# so retain the pre-2.11 behavior for these guests to prevent a
# blue screen on XP and others if the 64 bit PCI hole size is >2G.
writeState "args" "-global q35-pcihost.x-pci-hole64-fix=false" || return 1
fi ;;
esac
return 0
}
restoreMachine() {
isPlatform "x64" || return 0
# Restore the saved machine only when q35 is still the default;
# an explicit user-selected machine must remain untouched.
[[ "${MACHINE,,}" != "q35" ]] && return 0
MACHINE=""
restoreState "MACHINE" "old" || return 1
# Migrate existing Win9x installs to QEMU 11
if [[ "${MACHINE,,}" == "pc-i440fx-2.4" ]]; then
MACHINE="pc"
writeState "old" "$MACHINE" || return 1
fi
[ -z "$MACHINE" ] && MACHINE="q35"
return 0
}
restoreMachineState() {
restoreState "HV" "hv" || return 1
restoreState "VGA" "vga" || return 1
restoreState "USB" "usb" || return 1
restoreState "SOUND" "sound" || return 1
restoreState "ADAPTER" "net" || return 1
restoreState "VMPORT" "port" || return 1
restoreState "CPU_MODEL" "cpu" || return 1
restoreState "DISK_TYPE" "type" || return 1
if [ -z "${BIOS:-}" ] && [ -s "$(stateFile "bios")" ]; then
BIOS="$(stateFile "bios")"
fi
mergeState "CPU_FLAGS" "flag" "," || return 1
mergeState "ARGUMENTS" "args" " " || return 1
return 0
}
restoreBootMode() {
local current="${BOOT_MODE:-}"
local mode
mode=$(readState "mode") || return 1
[ -n "$mode" ] || return 0
# A saved legacy mode always wins. A saved modern mode only replaces the
# default mode and never an explicit user-selected boot configuration.
if [[ "${mode,,}" == "windows_legacy" ]]; then
BOOT_MODE="$mode"
return 0
fi
case "${current,,}" in
"" | "windows" | "windows_plain" )
BOOT_MODE="$mode" ;;
esac
return 0
}
extractDrivers() {
local drivers="$1"
rm -rf "$drivers" || return 1
mkdir -p "$drivers" || return 1
if ! bsdtar -xf /var/drivers.txz -C "$drivers"; then
error "Failed to extract drivers!"
return 1
fi
return 0
}
legacyInstall() {
local id="$1"
local iso="$2"
local dir="$3"
local desc="$4"
case "${id,,}" in
"win9"* )
Win9xInstall "$id" "$iso" "$dir" "$desc" || return 1 ;;
"win2k"* )
SIFInstall "$iso" "$dir" "$desc" "2k" || return 1 ;;
"winxp"* )
SIFInstall "$iso" "$dir" "$desc" "xp" || return 1 ;;
"win2003"* )
SIFInstall "$iso" "$dir" "$desc" "2k3" || return 1 ;;
esac
return 0
}
isReactOSLiveCD() {
local iso="$1"
local files
files=$(bsdtar -tf "$iso" 2>/dev/null) || return 1
! grep -Ei '(^|/)reactos/txtsetup\.sif(;1)?$' <<< "$files" >/dev/null
}
detectReactOS() {
local dir="$1"
local marker
marker=$(find "$dir" -maxdepth 2 -type f \
\( -ipath '*/reactos/reactos.inf' -o -ipath '*/reactos/unattend.inf' \) -print -quit) || return 2
[ -n "$marker" ] || return 1
DETECTED="reactos"
return 0
}
detectLegacy() {
local dir="$1"
local marker
isPlatform "x64" || return 1
# Legacy media is identified from setup marker files rather than WIM
# metadata. The order is intentional because several releases share markers.
marker=$(find "$dir" -maxdepth 1 -type d -iname 'ia64' -print -quit) || return 2
if [ -n "$marker" ]; then
error "Windows IA-64 (Itanium) images are not supported by this container!"
return 2
fi
marker=$(find "$dir" -maxdepth 1 -type d -iname WIN95 -print -quit) || return 2
if [ -n "$marker" ]; then
DETECTED="win95"
return 0
fi
marker=$(find "$dir" -maxdepth 1 -type d -iname WIN98 -print -quit) || return 2
if [ -n "$marker" ]; then
DETECTED="win98"
return 0
fi
marker=$(find "$dir" -maxdepth 1 -type d -iname WIN9X -print -quit) || return 2
if [ -n "$marker" ]; then
DETECTED="win9x"
return 0
fi
marker=$(find "$dir" -maxdepth 1 -type f \
\( \
-iname CDROM_W.40 -o \
-iname CDROM_S.40 -o \
-iname CDROM_TS.40 \
\) \
-print -quit) || return 2
if [ -n "$marker" ]; then
DETECTED="winnt4"
return 0
fi
marker=$(find "$dir" -maxdepth 1 -type f -iname CDROM_NT.5 -print -quit) || return 2
if [ -n "$marker" ]; then
marker=$(find "$dir" -maxdepth 1 -type f \
\( \
-iname CDROM_IA.5 -o \
-iname CDROM_ID.5 -o \
-iname CDROM_IP.5 -o \
-iname CDROM_IS.5 \
\) \
-print -quit) || return 2
if [ -n "$marker" ]; then
DETECTED="win2k"
return 0
fi
fi
# WIN51 identifies the NT 5.1/5.2 media family; the companion marker then
# distinguishes XP x86, XP x64, and Server 2003.
marker=$(find "$dir" -maxdepth 1 -iname WIN51 -print -quit) || return 2
[ -n "$marker" ] || return 1
marker=$(find "$dir" -maxdepth 1 -type f -iname WIN51AP -print -quit) || return 2
if [ -n "$marker" ]; then
DETECTED="winxpx64"
return 0
fi
marker=$(find "$dir" -maxdepth 1 -type f \
\( \
-iname WIN51IC -o \
-iname WIN51IP -o \
-iname setupxp.htm \
\) \
-print -quit) || return 2
if [ -n "$marker" ]; then
DETECTED="winxpx86"
return 0
fi
marker=$(find "$dir" -maxdepth 1 -type f \
\( \
-iname WIN51IS -o \
-iname WIN51IA -o \
-iname WIN51IB -o \
-iname WIN51ID -o \
-iname WIN51IL -o \
-iname WIN51AA -o \
-iname WIN51AD -o \
-iname WIN51AS -o \
-iname WIN51MA -o \
-iname WIN51MD -o \
-iname WIN51MP \
\) \
-print -quit) || return 2
if [ -n "$marker" ]; then
DETECTED="win2003r2"
return 0
fi
return 1
}
return 0
+42 -29
View File
@@ -7,8 +7,6 @@ handleCurlError() {
local server="$2"
local reason="${3:-}"
local signal
if [ -n "$reason" ] && (( code <= 125 )); then
error "Request to $server servers failed: ${reason%.}."
return 1
@@ -23,6 +21,7 @@ handleCurlError() {
return 1
fi
local signal
signal=$(kill -l "$((code - 128))" 2>/dev/null || true)
case "$signal" in
@@ -72,10 +71,7 @@ curlRequest() {
rm -f "$log"
handleCurlError "$rc" "$server" "$reason" || :
if (( rc >= 129 )); then
return "$rc"
fi
(( rc >= 129 )) && return "$rc"
return 1
fi
@@ -179,13 +175,16 @@ downloadWindowsLink() {
--max-filesize 100K \
-- "$skuUrl") || return
# Let jq parsing failures propagate so malformed API data is not mistaken
# for a normal missing-result response. The same applies to the link data.
skuId=$(printf '%s\n' "$skuJson" | jq --arg LANG "$language" -r 'first(.Skus[]? | select(.Language == $LANG) | .Id) // empty') 2>/dev/null || return
skuId=$(printf '%s\n' "$skuJson" | jq --arg LANG "$language" -r 'first(.Skus[]? | select(.Language == $LANG) | .Id) // empty') 2>/dev/null || skuId=""
if [ -z "$skuId" ] || [[ "${skuId,,}" == "null" ]]; then
language=$(getLanguage "$lang" "desc")
error "No download in the $language language available for $desc!"
if [[ "${lang,,}" != "en" && "${lang,,}" != "en-"* ]]; then
language=$(getLanguage "$lang" "desc")
error "No download in the $language language available for $desc!"
else
error "Microsoft server provided us no SKU ID in response to our request!"
info "Response: $skuJson"
fi
return 1
fi
@@ -218,10 +217,10 @@ downloadWindowsLink() {
return 1
fi
link=$(printf '%s\n' "$linkJson" | jq --argjson TYPE "$type" -r 'first(.ProductDownloadOptions[]? | select(.DownloadType == $TYPE) | .Uri) // empty') 2>/dev/null || return
link=$(printf '%s\n' "$linkJson" | jq --argjson TYPE "$type" -r 'first(.ProductDownloadOptions[]? | select(.DownloadType == $TYPE) | .Uri) // empty') 2>/dev/null || link=""
if [ -z "$link" ] || [[ "${link,,}" == "null" ]]; then
error "Microsoft server gave us no download link to our request for an automated download!"
error "Microsoft server provided us no download link to our request for an automated download!"
info "Response: $linkJson"
return 1
fi
@@ -291,7 +290,7 @@ downloadWindows() {
grep -Eio "<option[^>]*value=[\"'][0-9]+[\"'][^>]*>[[:space:]]*Windows[^<]*" |
sed -nE "s/.*value=[\"']([0-9]+)[\"'].*/\1/p" |
sed -n '1p' |
cut -c 1-16) || return
cut -c 1-16) || productId=""
enabled "$DEBUG" && echo "$productId"
if [ -z "$productId" ]; then
@@ -321,7 +320,7 @@ downloadWindowsEval() {
type="iot"
winVer="windows-11-iot-enterprise-ltsc-eval" ;;
"win11${PLATFORM,,}-enterprise-ltsc-eval" )
if [[ "${PLATFORM,,}" == "arm64" ]]; then
if isPlatform "arm64"; then
type="iot"
winVer="windows-11-iot-enterprise-ltsc-eval"
else
@@ -579,11 +578,13 @@ getWindows() {
"win81${PLATFORM,,}"* | \
"win10${PLATFORM,,}-enterprise-ltsc-eval" | \
"win11${PLATFORM,,}-enterprise-iot-eval" )
if [[ "${lang,,}" != "en" && "${lang,,}" != "en-"* ]]; then
error "No download in the $language language available for $edition!"
MIDO_URL=""
return 1
fi ;;
esac
# ARM64 downloads exist only for the explicitly supported Windows 11
@@ -593,24 +594,28 @@ getWindows() {
"win11${PLATFORM,,}" ) ;;
"win11${PLATFORM,,}-enterprise"* ) ;;
* )
if [[ "${PLATFORM,,}" != "x64" ]]; then
if ! isPlatform "x64"; then
error "No download for the ${PLATFORM^^} platform available for $edition!"
MIDO_URL=""
return 1
fi ;;
esac
# Prefer live Microsoft download routes. Unsupported or failed live routes
# fall through to the configured static catalog below.
case "${version,,}" in
"win10x64" | "win11${PLATFORM,,}" )
if downloadWindows "$version" "$lang" "$edition"; then
return 0
else
rc=$?
(( rc == 1 )) || return "$rc"
fi ;;
fi
(( rc == 1 )) || return "$rc" ;;
"win11${PLATFORM,,}-enterprise"* )
@@ -618,8 +623,9 @@ getWindows() {
return 0
else
rc=$?
(( rc == 1 )) || return "$rc"
fi ;;
fi
(( rc == 1 )) || return "$rc" ;;
"win2025-eval" | "win2022-eval" | "win2019-eval" | \
"win2019-hv" | "win2016-eval" | "win2012r2-eval" )
@@ -628,14 +634,16 @@ getWindows() {
return 0
else
rc=$?
(( rc == 1 )) || return "$rc"
fi ;;
fi
(( rc == 1 )) || return "$rc" ;;
"win2008r2"*| "win81${PLATFORM,,}"* | "win10${PLATFORM,,}-enterprise"* ) ;;
* )
error "Invalid VERSION specified, value \"$version\" is not recognized!"
return 1 ;;
esac
# Static catalog URLs are the last resort after live Microsoft methods are
@@ -995,10 +1003,7 @@ getESD() {
rm -f "$log"
if (( rc >= 129 )); then
return "$rc"
fi
(( rc >= 129 )) && return "$rc"
return 1
fi
@@ -1117,7 +1122,7 @@ verifyFile() {
info "Successfully verified $type!" && return 0
fi
error "The downloaded file has an unknown $algo checksum: $hash , as the expected value was: $check. Please report this at $SUPPORT/issues"
warn "the downloaded file has an unknown $algo checksum: $hash , as the expected value was: $check. Please report this at $SUPPORT/issues"
return 1
}
@@ -1209,6 +1214,7 @@ tryDownload() {
if ! rm -f -- "$iso" "$iso.aria2"; then
warn "failed to remove invalid download \"$iso\"!"
fi
return 2
}
@@ -1271,13 +1277,18 @@ downloadImage() {
local tried="n" success="n" seconds="5"
local i url sum size base language desc web_desc rc
if [[ "${version,,}" == "http"* ]]; then
if isURL "$version"; then
base=$(basename "$iso")
desc=$(fromFile "$base")
web_desc="$desc"
tryDownload "$iso" "$version" "" "" "$desc" "$seconds" "$web_desc" || return
tryDownload "$iso" "$version" "" "" "$desc" "$seconds" "$web_desc" || {
rc=$?
error "Failed to download the Windows image from the specified URL!"
return "$rc"
}
return 0
fi
@@ -1446,6 +1457,8 @@ downloadImage() {
if [[ "$tried" == "n" ]]; then
error "No download method is available for $desc!"
else
error "All download methods failed for $desc!"
fi
return 1
+43 -66
View File
@@ -11,7 +11,7 @@ SHUTDOWN_SIGNAL=0
QEMU_PTY="$QEMU_DIR/qemu.pty"
QEMU_END="$QEMU_DIR/qemu.end"
ACPI_SOCKET="$QEMU_DIR/acpi.sock"
ACPI_SOCKET="$QEMU_DIR/monitor.sock"
CONSOLE_PID="$QEMU_DIR/console.pid"
CONSOLE_SOCKET="$QEMU_DIR/console.sock"
QEMU_START_PID="$QEMU_DIR/qemu.start.pid"
@@ -28,7 +28,7 @@ bootStatus() {
[ ! -s "$QEMU_PTY" ] && return 1
if [[ "${BOOT_MODE,,}" == "windows_legacy" ]]; then
if isLegacyBoot; then
local line last recent
# Only inspect output produced after the most recent BIOS boot attempt so
@@ -44,6 +44,9 @@ bootStatus() {
if grep -Fq \
-e "No bootable device." \
-e "BOOTMGR is missing" \
-e "Replace the disk, and then press any key" \
-e "The following file is missing or corrupted:" \
-e "Type the name of the Windows loader" \
<<< "$recent"; then
return 2
fi
@@ -130,7 +133,7 @@ waitForBoot() {
elif bootKeyReady; then
(( keyWait += 1 ))
if [[ "${BOOT_MODE,,}" == "windows_legacy" ]]; then
if isLegacyBoot; then
# Keep the legacy fallback at about one second after the DVD marker.
if (( keyWait >= 5 )); then
if sendKey ret 0 100 6 0.25; then
@@ -251,20 +254,24 @@ legacyBootReady() {
grep -Fq "Loading FreeLoader..." <<< "$recent" && return 0
grep -Fq "No bootable device." <<< "$recent" && return 1
grep -Fq "BOOTMGR is missing" <<< "$recent" && return 1
grep -Fq "Replace the disk, and then press any key" <<< "$recent" && return 1
grep -Fq "Boot failed: not a bootable disk" <<< "$recent" && return 1
grep -Fq "Boot failed: could not read the boot disk" <<< "$recent" && return 1
grep -Fq "The following file is missing or corrupted:" <<< "$recent" && return 1
grep -Fq "Type the name of the Windows loader" <<< "$recent" && return 1
return 0
}
ready() {
# The marker means installation completed previously, so shutdown no longer
# needs to infer guest readiness from firmware output.
[ -f "$STORAGE/windows.boot" ] && return 0
# The marker means installation completed previously, so shutdown
# no longer needs to infer guest readiness from firmware output.
hasBootMarker && return 0
[ ! -s "$QEMU_PTY" ] && return 1
if [[ "${BOOT_MODE,,}" == "windows_legacy" ]]; then
if isLegacyBoot; then
legacyBootReady && return 0
return 1
fi
@@ -343,32 +350,26 @@ sendKey() {
return 0
}
supportsBootKey() {
local id="$1"
[[ "${id,,}" == "win"* ]]
}
needsBootKey() {
[ ! -s "$BOOT" ] && return 1
[[ "${BOOT,,}" != *".iso" ]] && return 1
[ -f "$STORAGE/windows.boot" ] && return 1
hasImage "$BOOT" || return 1
supportsBootKey "$DETECTED"
hasCompletedInstall && return 1
supportsBootKey "$DETECTED" || return 1
return 0
}
bootKeyReady() {
[ ! -s "$QEMU_PTY" ] && return 1
if [[ "${BOOT_MODE,,}" == "windows_legacy" ]]; then
if isLegacyBoot; then
grep -Fq "Booting from DVD/CD" "$QEMU_PTY"
return $?
fi
if [[ "${PLATFORM,,}" == "arm64" ]]; then
if isPlatform "arm64"; then
grep -Eq "$UEFI_USB_BOOT_PATTERN" "$QEMU_PTY"
else
grep -Eq "$UEFI_DVD_BOOT_PATTERN" "$QEMU_PTY"
@@ -377,7 +378,7 @@ bootKeyReady() {
getBootMarker() {
if [[ "${BOOT_MODE,,}" == "windows_legacy" ]]; then
if isLegacyBoot; then
grep -nE "$LEGACY_BOOT_PATTERN" "$QEMU_PTY" | tail -1
return 0
fi
@@ -389,9 +390,7 @@ getBootMarker() {
markWindowsBooted() {
local file="$STORAGE/windows.boot"
if [ -f "$file" ] || [ ! -f "$BOOT" ]; then
if hasBootMarker || [ ! -f "$BOOT" ]; then
return 0
fi
@@ -399,19 +398,13 @@ markWindowsBooted() {
# now booting from the installed disk rather than from setup media.
ready || return 0
if ! touch "$file"; then
warn "failed to create Windows installation marker!"
return 0
fi
if ! setOwner "$file"; then
rm -f "$file"
warn "failed to set the owner for \"$file\" !"
return 0
fi
createMarker "boot" || return 0
if ! disabled "$REMOVE"; then
removeImage "$BOOT" || :
case "${BOOT,,}" in
*.img | *.raw | *.qcow2 ) ;;
* ) removeImage "$BOOT" || : ;;
esac
fi
rm -f "$STORAGE/setup.img" 2>/dev/null || :
@@ -423,29 +416,26 @@ finish() {
local reason=$1 failed=0
# QEMU_END distinguishes an expected shutdown path from an unexpected QEMU
# exit carrying the same process status.
# A nonzero exit is unexpected only when QEMU_END is missing.
if [ ! -f "$QEMU_END" ] && (( reason != 0 )); then
failed=1
fi
touch "$QEMU_END"
touch "$QEMU_END" || :
forceKillQemu "$reason"
if [ ! -f "$STORAGE/windows.boot" ]; then
if ! hasBootMarker; then
markWindowsBooted
fi
cleanupHelpers \
"${SMB_PID:-}" \
"${NMB_PID:-}" \
"${DDN_PID:-}"
cleanupHelpers "${SMB_PID:-}" "${NMB_PID:-}" "${DDN_PID:-}"
if ! waitQemuExit 10; then
warn "Timed out while waiting for $(app) to exit!"
fi
stopConsole
echo
if (( failed == 0 )); then
@@ -457,27 +447,6 @@ finish() {
exit "$reason"
}
abortDuringSetup() {
local code="$1"
# Before Windows boots from disk, ACPI may be ignored or interpreted by setup
# itself. Terminate QEMU directly instead of waiting for a graceful shutdown.
if [[ "${DETECTED,,}" != "reactos" ]] || [ -n "${CUSTOM:-}" ]; then
info "Cannot send ACPI signal during $(app) setup, terminating..."
else
info "ReactOS LiveCD does not support ACPI shutdown, terminating..."
fi
terminateQemu
if ! waitQemuExit 10; then
warn "Timed out while waiting for $(app) to exit!"
fi
finish "$code"
}
gracefulShutdown() {
local sig="$1"
@@ -526,8 +495,16 @@ gracefulShutdown() {
finish "$code"
fi
if ! ready; then
abortDuringSetup "$code"
if hasMarker "kill"; then
info "This $(app) version does not support ACPI shutdown, decreasing timeout to 1 second..."
TIMEOUT=7
elif ! ready || { hasSystemImage && ! hasBootMarker; }; then
info "$(app) will ignore ACPI signals during setup, decreasing timeout to 10 seconds..."
TIMEOUT=13
fi
normalizeTimeout 105
+1 -1
View File
@@ -388,7 +388,7 @@ isUserMode && return 0
# Older Windows versions discover shares through NetBIOS, while modern Windows
# uses Web Services Discovery.
if [[ "${BOOT_MODE:-}" == "windows_legacy" ]]; then
if isLegacyBoot; then
startNetbios || :
else
startWsddn || :
+1251
View File
File diff suppressed because it is too large Load Diff