mirror of
https://github.com/dockur/windows.git
synced 2026-07-27 21:42:36 +07:00
893 lines
26 KiB
YAML
893 lines
26 KiB
YAML
name: Installation
|
|
|
|
on:
|
|
workflow_call:
|
|
inputs:
|
|
name:
|
|
description: Display name for the Windows version
|
|
required: true
|
|
type: string
|
|
|
|
version:
|
|
description: VERSION value passed to the container
|
|
required: true
|
|
type: string
|
|
|
|
runner:
|
|
description: GitHub Actions runner
|
|
required: false
|
|
default: ubuntu-24.04
|
|
type: string
|
|
|
|
branch:
|
|
description: Repository branch to build
|
|
required: false
|
|
default: master
|
|
type: string
|
|
|
|
callback:
|
|
description: Guest script type
|
|
required: false
|
|
default: powershell
|
|
type: string
|
|
|
|
cpu:
|
|
description: CPU model exposed to Windows
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
|
|
disk_type:
|
|
description: Disk controller type exposed to Windows
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
|
|
kill_on_failure:
|
|
description: Stop the workflow when an installation failure is detected
|
|
required: false
|
|
default: false
|
|
type: boolean
|
|
|
|
expected_caption:
|
|
description: Text expected in the Windows caption
|
|
required: true
|
|
type: string
|
|
|
|
expected_edition:
|
|
description: Expected Windows EditionID
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
|
|
minimum_build:
|
|
description: Minimum acceptable Windows build number
|
|
required: true
|
|
type: number
|
|
|
|
platform:
|
|
description: Expected Windows platform
|
|
required: true
|
|
type: string
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
install:
|
|
name: ${{ inputs.name }}
|
|
runs-on: ${{ inputs.runner }}
|
|
timeout-minutes: 180
|
|
|
|
env:
|
|
CONTAINER: windows-test
|
|
IMAGE: windows-validation:local
|
|
TUNNEL: windows-test-tunnel
|
|
|
|
steps:
|
|
- name: Checkout branch
|
|
uses: actions/checkout@v7
|
|
with:
|
|
ref: ${{ inputs.branch }}
|
|
|
|
- name: Check KVM
|
|
shell: bash
|
|
run: |
|
|
set -Eeuo pipefail
|
|
|
|
test -c /dev/kvm
|
|
|
|
- name: Free disk space
|
|
shell: bash
|
|
run: |
|
|
set -Eeuo pipefail
|
|
|
|
sudo rm -rf \
|
|
/usr/local/.ghcup \
|
|
/usr/local/lib/android \
|
|
/usr/local/lib/node_modules \
|
|
/usr/local/share/boost \
|
|
/usr/local/share/chromium \
|
|
/usr/local/share/powershell \
|
|
/usr/share/dotnet \
|
|
/usr/share/swift \
|
|
/opt/az \
|
|
/opt/ghc \
|
|
/opt/google \
|
|
/opt/hostedtoolcache \
|
|
/opt/microsoft \
|
|
/opt/pipx
|
|
|
|
sudo apt-get clean
|
|
sudo rm -rf \
|
|
/var/cache/apt/* \
|
|
/var/lib/apt/lists/*
|
|
|
|
docker system prune \
|
|
--all \
|
|
--force \
|
|
--volumes > /dev/null || true
|
|
|
|
available_kb="$(df --output=avail / | tail -1)"
|
|
available_gb="$((available_kb / 1024 / 1024))"
|
|
|
|
echo "Available disk space: ${available_gb} GB"
|
|
|
|
if (( available_gb < 40 )); then
|
|
echo "At least 40 GB of free space is required."
|
|
exit 1
|
|
fi
|
|
|
|
- name: Prepare installation test
|
|
id: test
|
|
shell: bash
|
|
env:
|
|
CALLBACK: ${{ inputs.callback }}
|
|
run: |
|
|
set -Eeuo pipefail
|
|
|
|
case "$CALLBACK" in
|
|
powershell | legacy)
|
|
;;
|
|
*)
|
|
echo "Unsupported guest script type: $CALLBACK"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
token="$(cat /proc/sys/kernel/random/uuid)"
|
|
|
|
mkdir -p \
|
|
"$RUNNER_TEMP/data" \
|
|
"$RUNNER_TEMP/oem" \
|
|
"$RUNNER_TEMP/storage"
|
|
|
|
printf '%s\n' "$token" > "$RUNNER_TEMP/data/readme.txt"
|
|
|
|
cat > "$RUNNER_TEMP/oem/sync-log.bat" <<'BATCH'
|
|
@echo off
|
|
setlocal
|
|
|
|
:sync
|
|
if exist C:\OEM\install.log (
|
|
copy /Y C:\OEM\install.log \\host.lan\Data\install.tmp >nul 2>&1
|
|
|
|
if not errorlevel 1 (
|
|
move /Y \\host.lan\Data\install.tmp \\host.lan\Data\install.log >nul 2>&1
|
|
)
|
|
)
|
|
|
|
if /I "%~1"=="once" exit /B
|
|
if exist C:\OEM\install.done exit /B
|
|
|
|
ping 127.0.0.1 -n 6 >nul
|
|
goto sync
|
|
BATCH
|
|
|
|
case "$CALLBACK" in
|
|
powershell)
|
|
cat > "$RUNNER_TEMP/oem/ready.ps1" <<'POWERSHELL'
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$Token
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
while ($true) {
|
|
try {
|
|
$windows = Get-CimInstance Win32_OperatingSystem
|
|
$registry = Get-ItemProperty `
|
|
"HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion"
|
|
|
|
$platform = switch (
|
|
$env:PROCESSOR_ARCHITECTURE.ToUpperInvariant()
|
|
) {
|
|
"AMD64" {
|
|
"x64"
|
|
}
|
|
"ARM64" {
|
|
"arm64"
|
|
}
|
|
"X86" {
|
|
"x86"
|
|
}
|
|
default {
|
|
$env:PROCESSOR_ARCHITECTURE.ToLowerInvariant()
|
|
}
|
|
}
|
|
|
|
$share = (
|
|
Get-Content `
|
|
-LiteralPath "\\host.lan\Data\readme.txt" `
|
|
-Raw
|
|
).Trim()
|
|
|
|
$result = @{
|
|
token = $Token
|
|
caption = [string]$windows.Caption
|
|
edition = [string]$registry.EditionID
|
|
version = [string]$windows.Version
|
|
build = [string]$windows.BuildNumber
|
|
platform = $platform
|
|
share = $share
|
|
}
|
|
|
|
$json = $result | ConvertTo-Json -Compress
|
|
$temporary = "\\host.lan\Data\windows.tmp"
|
|
$destination = "\\host.lan\Data\windows.json"
|
|
$encoding = New-Object System.Text.UTF8Encoding($false)
|
|
|
|
[System.IO.File]::WriteAllText(
|
|
$temporary,
|
|
$json,
|
|
$encoding
|
|
)
|
|
|
|
Move-Item `
|
|
-LiteralPath $temporary `
|
|
-Destination $destination `
|
|
-Force
|
|
|
|
break
|
|
}
|
|
catch {
|
|
Write-Output "$(Get-Date -Format s) - Callback retry: $($_.Exception.Message)"
|
|
Start-Sleep -Seconds 10
|
|
}
|
|
}
|
|
POWERSHELL
|
|
|
|
cat > "$RUNNER_TEMP/oem/install.bat" <<EOF
|
|
@echo off
|
|
del /Q C:\OEM\install.done 2>nul
|
|
start "" /B cmd.exe /C C:\OEM\sync-log.bat
|
|
|
|
powershell.exe -NoProfile -ExecutionPolicy Bypass ^
|
|
-File C:\OEM\ready.ps1 ^
|
|
-Token "$token"
|
|
|
|
set "result=%errorlevel%"
|
|
type nul > C:\OEM\install.done
|
|
call C:\OEM\sync-log.bat once
|
|
exit /B %result%
|
|
EOF
|
|
;;
|
|
|
|
legacy)
|
|
cat > "$RUNNER_TEMP/oem/ready.vbs" <<'VBSCRIPT'
|
|
Option Explicit
|
|
|
|
Const ForReading = 1
|
|
Const ForWriting = 2
|
|
|
|
Dim arguments
|
|
Dim token
|
|
|
|
Set arguments = WScript.Arguments
|
|
|
|
If arguments.Count < 1 Then
|
|
WScript.Quit 1
|
|
End If
|
|
|
|
token = arguments(0)
|
|
|
|
Function EscapeJson(value)
|
|
Dim result
|
|
|
|
result = CStr(value)
|
|
result = Replace(result, "\", "\\")
|
|
result = Replace(result, Chr(34), "\" & Chr(34))
|
|
result = Replace(result, vbCr, "\r")
|
|
result = Replace(result, vbLf, "\n")
|
|
result = Replace(result, vbTab, "\t")
|
|
|
|
EscapeJson = result
|
|
End Function
|
|
|
|
Function ReadRegistry(shell, path)
|
|
Dim value
|
|
|
|
value = ""
|
|
|
|
On Error Resume Next
|
|
value = shell.RegRead(path)
|
|
|
|
If Err.Number <> 0 Then
|
|
Err.Clear
|
|
value = ""
|
|
End If
|
|
|
|
On Error GoTo 0
|
|
|
|
ReadRegistry = CStr(value)
|
|
End Function
|
|
|
|
Function ReadTextFile(filesystem, path)
|
|
Dim file
|
|
Dim value
|
|
|
|
value = ""
|
|
|
|
Set file = filesystem.OpenTextFile(path, ForReading, False)
|
|
value = file.ReadAll
|
|
file.Close
|
|
|
|
value = Replace(value, vbCr, "")
|
|
value = Replace(value, vbLf, "")
|
|
|
|
ReadTextFile = value
|
|
End Function
|
|
|
|
Function GetPlatform(shell)
|
|
Dim architecture
|
|
|
|
architecture = UCase(shell.ExpandEnvironmentStrings("%PROCESSOR_ARCHITECTURE%"))
|
|
|
|
Select Case architecture
|
|
Case "AMD64"
|
|
GetPlatform = "x64"
|
|
Case "ARM64"
|
|
GetPlatform = "arm64"
|
|
Case "X86"
|
|
GetPlatform = "x86"
|
|
Case Else
|
|
GetPlatform = LCase(architecture)
|
|
End Select
|
|
End Function
|
|
|
|
Dim filesystem
|
|
Dim shell
|
|
Dim locator
|
|
Dim service
|
|
Dim systems
|
|
Dim system
|
|
|
|
Dim caption
|
|
Dim edition
|
|
Dim version
|
|
Dim build
|
|
Dim platform
|
|
Dim share
|
|
|
|
Dim json
|
|
Dim temporary
|
|
Dim destination
|
|
Dim file
|
|
Dim success
|
|
|
|
temporary = "\\host.lan\Data\windows.tmp"
|
|
destination = "\\host.lan\Data\windows.json"
|
|
|
|
Do
|
|
success = False
|
|
|
|
On Error Resume Next
|
|
Err.Clear
|
|
|
|
Set filesystem = CreateObject("Scripting.FileSystemObject")
|
|
Set shell = CreateObject("WScript.Shell")
|
|
Set locator = CreateObject("WbemScripting.SWbemLocator")
|
|
Set service = locator.ConnectServer(".", "root\cimv2")
|
|
Set systems = service.ExecQuery("SELECT Caption, Version, BuildNumber FROM Win32_OperatingSystem")
|
|
|
|
caption = ""
|
|
version = ""
|
|
build = ""
|
|
|
|
If Err.Number = 0 Then
|
|
For Each system In systems
|
|
caption = CStr(system.Caption)
|
|
version = CStr(system.Version)
|
|
build = CStr(system.BuildNumber)
|
|
Exit For
|
|
Next
|
|
End If
|
|
|
|
If Err.Number = 0 Then
|
|
edition = ReadRegistry(shell, "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\EditionID")
|
|
platform = GetPlatform(shell)
|
|
share = ReadTextFile(filesystem, "\\host.lan\Data\readme.txt")
|
|
End If
|
|
|
|
If Err.Number = 0 Then
|
|
json = _
|
|
"{" & _
|
|
"""token"":""" & EscapeJson(token) & """," & _
|
|
"""caption"":""" & EscapeJson(caption) & """," & _
|
|
"""edition"":""" & EscapeJson(edition) & """," & _
|
|
"""version"":""" & EscapeJson(version) & """," & _
|
|
"""build"":""" & EscapeJson(build) & """," & _
|
|
"""platform"":""" & EscapeJson(platform) & """," & _
|
|
"""share"":""" & EscapeJson(share) & """" & _
|
|
"}"
|
|
|
|
Set file = filesystem.OpenTextFile(temporary, ForWriting, True)
|
|
file.Write json
|
|
file.Close
|
|
End If
|
|
|
|
If Err.Number = 0 Then
|
|
If filesystem.FileExists(destination) Then
|
|
filesystem.DeleteFile destination, True
|
|
End If
|
|
|
|
filesystem.MoveFile temporary, destination
|
|
End If
|
|
|
|
If Err.Number = 0 Then
|
|
success = True
|
|
Else
|
|
WScript.Echo _
|
|
Now & _
|
|
" - Callback retry: 0x" & _
|
|
Hex(Err.Number) & _
|
|
" - " & _
|
|
Err.Description
|
|
End If
|
|
|
|
Err.Clear
|
|
On Error GoTo 0
|
|
|
|
If success Then
|
|
Exit Do
|
|
End If
|
|
|
|
WScript.Sleep 10000
|
|
Loop
|
|
VBSCRIPT
|
|
|
|
cat > "$RUNNER_TEMP/oem/install.bat" <<EOF
|
|
@echo off
|
|
del /Q C:\OEM\install.done 2>nul
|
|
start "" /B cmd.exe /C C:\OEM\sync-log.bat
|
|
|
|
cscript.exe //B //NoLogo C:\OEM\ready.vbs "$token"
|
|
|
|
set "result=%errorlevel%"
|
|
type nul > C:\OEM\install.done
|
|
call C:\OEM\sync-log.bat once
|
|
exit /B %result%
|
|
EOF
|
|
;;
|
|
esac
|
|
|
|
echo "token=$token" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Build image
|
|
shell: bash
|
|
run: |
|
|
set -Eeuo pipefail
|
|
|
|
echo "Building branch: ${{ inputs.branch }}"
|
|
echo "Commit: $(git rev-parse HEAD)"
|
|
|
|
for attempt in 1 2 3; do
|
|
if docker build \
|
|
--tag "$IMAGE" \
|
|
.; then
|
|
break
|
|
fi
|
|
|
|
if (( attempt == 3 )); then
|
|
echo "Image build failed after $attempt attempts."
|
|
exit 1
|
|
fi
|
|
|
|
delay=$((attempt * 15))
|
|
echo "Image build failed, retrying in $delay seconds..."
|
|
sleep "$delay"
|
|
done
|
|
|
|
docker image inspect "$IMAGE" \
|
|
--format 'Image ID: {{.Id}}'
|
|
|
|
- name: Start Windows container
|
|
shell: bash
|
|
env:
|
|
CPU: ${{ inputs.cpu }}
|
|
DISK_TYPE: ${{ inputs.disk_type }}
|
|
VERSION: ${{ inputs.version }}
|
|
run: |
|
|
set -Eeuo pipefail
|
|
|
|
docker run --detach \
|
|
--name "$CONTAINER" \
|
|
--device /dev/kvm \
|
|
--device /dev/net/tun \
|
|
--cap-add NET_ADMIN \
|
|
--stop-timeout 120 \
|
|
--env "MACHINE=pc" \
|
|
--env "VERSION=$VERSION" \
|
|
--env "RAM_SIZE=half" \
|
|
--env "CPU_CORES=half" \
|
|
--env "DISK_SIZE=64G" \
|
|
--env "CPU_MODEL=$CPU" \
|
|
--env "DISK_TYPE=$DISK_TYPE" \
|
|
--env "LOG=Y" \
|
|
--env "DEBUG=Y" \
|
|
--volume "$RUNNER_TEMP/data:/shared" \
|
|
--volume "$RUNNER_TEMP/oem:/oem:ro" \
|
|
--volume "$RUNNER_TEMP/storage:/storage" \
|
|
"$IMAGE"
|
|
|
|
- name: Start noVNC tunnel
|
|
id: tunnel
|
|
shell: bash
|
|
run: |
|
|
set -Eeuo pipefail
|
|
|
|
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 "url=$novnc_url" >> "$GITHUB_OUTPUT"
|
|
|
|
{
|
|
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
|
|
|
|
- name: Install and validate Windows
|
|
shell: bash
|
|
env:
|
|
KILL_ON_FAILURE: ${{ inputs.kill_on_failure }}
|
|
EXPECTED_TOKEN: ${{ steps.test.outputs.token }}
|
|
EXPECTED_CAPTION: ${{ inputs.expected_caption }}
|
|
EXPECTED_EDITION: ${{ inputs.expected_edition }}
|
|
EXPECTED_PLATFORM: ${{ inputs.platform }}
|
|
MINIMUM_BUILD: ${{ inputs.minimum_build }}
|
|
DISPLAY_NAME: ${{ inputs.name }}
|
|
run: |
|
|
set -Eeuo pipefail
|
|
|
|
echo
|
|
echo "Container log:"
|
|
echo "------------------------------------------------------------"
|
|
|
|
docker logs \
|
|
--follow \
|
|
--timestamps \
|
|
"$CONTAINER" &
|
|
|
|
logs_pid="$!"
|
|
guest_log="$RUNNER_TEMP/data/install.log"
|
|
|
|
(
|
|
offset=0
|
|
snapshot="$RUNNER_TEMP/install-log.snapshot"
|
|
|
|
while true; do
|
|
if cp "$guest_log" "$snapshot" 2>/dev/null; then
|
|
size="$(stat -c %s "$snapshot" 2>/dev/null || echo 0)"
|
|
|
|
if (( size < offset )); then
|
|
offset=0
|
|
fi
|
|
|
|
if (( size > offset )); then
|
|
tail -c "+$((offset + 1))" "$snapshot" 2>/dev/null |
|
|
sed -u 's/^/[install.bat] /' || true
|
|
offset="$size"
|
|
fi
|
|
fi
|
|
|
|
sleep 2
|
|
done
|
|
) &
|
|
|
|
guest_logs_pid="$!"
|
|
|
|
stop_logs() {
|
|
kill "$logs_pid" "$guest_logs_pid" 2>/dev/null || true
|
|
wait "$logs_pid" "$guest_logs_pid" 2>/dev/null || true
|
|
}
|
|
|
|
trap stop_logs EXIT
|
|
|
|
failure_checks_disabled=0
|
|
|
|
handle_failure() {
|
|
local message="$1"
|
|
|
|
if [[ "$KILL_ON_FAILURE" == "true" ]]; then
|
|
return 1
|
|
fi
|
|
|
|
echo
|
|
echo "Automatic termination is disabled by kill_on_failure."
|
|
echo "The container and noVNC tunnel will remain available."
|
|
echo "::warning title=Failure detected::$message"
|
|
failure_checks_disabled=1
|
|
return 0
|
|
}
|
|
|
|
deadline=$((SECONDS + 9000))
|
|
reboot_timeout=1800
|
|
minimum_reboots=1
|
|
boot_loop_limit=10
|
|
first_bios_start=-1
|
|
|
|
while (( SECONDS < deadline )); do
|
|
state="$(
|
|
docker inspect \
|
|
--format '{{.State.Status}}' \
|
|
"$CONTAINER" 2>/dev/null || true
|
|
)"
|
|
|
|
if [[ "$state" != "running" ]]; then
|
|
echo
|
|
echo "------------------------------------------------------------"
|
|
echo "Container stopped before Windows became ready."
|
|
echo "Container state: ${state:-missing}"
|
|
exit 1
|
|
fi
|
|
|
|
container_log="$(docker logs "$CONTAINER" 2>&1 || true)"
|
|
|
|
if (( failure_checks_disabled == 0 )) &&
|
|
grep -Eqi \
|
|
'KVM internal error|KVM: entry failed|hardware error 0x[0-9a-f]+|Triple fault' \
|
|
<<< "$container_log"; then
|
|
echo
|
|
echo "------------------------------------------------------------"
|
|
echo "Detected a fatal QEMU or KVM error."
|
|
|
|
if ! handle_failure "Detected a fatal QEMU or KVM error."; then
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
if (( failure_checks_disabled == 0 )) &&
|
|
grep -Eqi \
|
|
'CDBOOT: Cannot boot from CD.*Code: 5' \
|
|
<<< "$container_log"; then
|
|
echo
|
|
echo "------------------------------------------------------------"
|
|
echo "The installation media could not be booted."
|
|
|
|
if ! handle_failure "The installation media could not be booted."; then
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
bios_starts="$(
|
|
grep -Fci 'SeaBIOS (version ' <<< "$container_log" || true
|
|
)"
|
|
|
|
hard_disk_boots="$(
|
|
grep -Fci 'Booting from Hard Disk' <<< "$container_log" || true
|
|
)"
|
|
|
|
dvd_boots="$(
|
|
grep -Fci 'Booting from DVD/CD' <<< "$container_log" || true
|
|
)"
|
|
|
|
not_bootable_disk="$(
|
|
grep -Fci \
|
|
'Boot failed: not a bootable disk' \
|
|
<<< "$container_log" || true
|
|
)"
|
|
|
|
unreadable_boot_disk="$(
|
|
grep -Fci \
|
|
'Boot failed: could not read the boot disk' \
|
|
<<< "$container_log" || true
|
|
)"
|
|
|
|
no_bootable_device="$(
|
|
grep -Fci 'No bootable device.' <<< "$container_log" || true
|
|
)"
|
|
|
|
bootmgr_missing="$(
|
|
grep -Fci 'BOOTMGR is missing' <<< "$container_log" || true
|
|
)"
|
|
|
|
reboots=$((bios_starts > 0 ? bios_starts - 1 : 0))
|
|
|
|
if (( first_bios_start < 0 && bios_starts > 0 )); then
|
|
first_bios_start=$SECONDS
|
|
fi
|
|
|
|
if (( failure_checks_disabled == 0 &&
|
|
first_bios_start >= 0 &&
|
|
SECONDS - first_bios_start >= reboot_timeout &&
|
|
reboots < minimum_reboots )); then
|
|
echo
|
|
echo "------------------------------------------------------------"
|
|
echo "The installation did not reboot within" \
|
|
"$((reboot_timeout / 60)) minutes after the first BIOS start."
|
|
echo "Observed reboots: $reboots"
|
|
echo "Required reboots: $minimum_reboots"
|
|
|
|
if ! handle_failure "The installation did not reboot in time."; then
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
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 ||
|
|
unreadable_boot_disk >= boot_loop_limit ||
|
|
no_bootable_device >= boot_loop_limit ||
|
|
bootmgr_missing >= boot_loop_limit )); then
|
|
echo
|
|
echo "------------------------------------------------------------"
|
|
echo "Detected a repeated BIOS boot loop."
|
|
echo "SeaBIOS starts: $bios_starts"
|
|
echo "Hard disk boots: $hard_disk_boots"
|
|
echo "DVD boots: $dvd_boots"
|
|
echo "Not-bootable disk failures: $not_bootable_disk"
|
|
echo "Unreadable boot-disk failures: $unreadable_boot_disk"
|
|
echo "No-bootable-device failures: $no_bootable_device"
|
|
echo "BOOTMGR failures: $bootmgr_missing"
|
|
|
|
if ! handle_failure "Detected a repeated BIOS boot loop."; then
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
response=""
|
|
|
|
if [[ -s "$RUNNER_TEMP/data/windows.json" ]]; then
|
|
response="$(cat "$RUNNER_TEMP/data/windows.json")"
|
|
fi
|
|
|
|
if [[ -n "$response" ]]; then
|
|
stop_logs
|
|
trap - EXIT
|
|
|
|
echo
|
|
echo "------------------------------------------------------------"
|
|
echo "Received response:"
|
|
|
|
if ! jq . <<< "$response"; then
|
|
echo "$response"
|
|
echo "The guest returned invalid JSON."
|
|
exit 1
|
|
fi
|
|
|
|
token="$(jq -r '.token // empty' <<< "$response")"
|
|
caption="$(jq -r '.caption // empty' <<< "$response")"
|
|
edition="$(jq -r '.edition // empty' <<< "$response")"
|
|
version="$(jq -r '.version // empty' <<< "$response")"
|
|
build="$(jq -r '.build // empty' <<< "$response")"
|
|
platform="$(jq -r '.platform // empty' <<< "$response")"
|
|
share="$(jq -r '.share // empty' <<< "$response")"
|
|
|
|
if [[ "$token" != "$EXPECTED_TOKEN" ]]; then
|
|
echo "The response token does not match."
|
|
exit 1
|
|
fi
|
|
|
|
if [[ "$share" != "$EXPECTED_TOKEN" ]]; then
|
|
echo "Failed to read \\\\host.lan\\Data\\readme.txt."
|
|
echo "Expected contents:"
|
|
echo " $EXPECTED_TOKEN"
|
|
echo "Received:"
|
|
echo " ${share:-empty}"
|
|
exit 1
|
|
fi
|
|
|
|
normalized_caption="${caption//\(R\)/}"
|
|
normalized_expected_caption="${EXPECTED_CAPTION//\(R\)/}"
|
|
|
|
if [[ "$normalized_caption" != *"$normalized_expected_caption"* ]]; then
|
|
echo "Expected caption containing:"
|
|
echo " $EXPECTED_CAPTION"
|
|
echo "Received:"
|
|
echo " $caption"
|
|
exit 1
|
|
fi
|
|
|
|
normalized_edition="${edition%Eval}"
|
|
normalized_expected_edition="${EXPECTED_EDITION%Eval}"
|
|
|
|
if [[ -n "$EXPECTED_EDITION" &&
|
|
"$normalized_edition" != "$normalized_expected_edition" ]]; then
|
|
echo "Expected edition: $EXPECTED_EDITION"
|
|
echo "Received edition: $edition"
|
|
exit 1
|
|
fi
|
|
|
|
if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
echo "Unexpected Windows version: $version"
|
|
exit 1
|
|
fi
|
|
|
|
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 [[ "$platform" != "$EXPECTED_PLATFORM" ]]; then
|
|
echo "Expected platform:"
|
|
echo " $EXPECTED_PLATFORM"
|
|
echo "Received platform:"
|
|
echo " $platform"
|
|
exit 1
|
|
fi
|
|
|
|
echo
|
|
echo "$DISPLAY_NAME installed successfully."
|
|
echo "Version: $version"
|
|
echo "Build: $build"
|
|
echo "Platform: $platform"
|
|
echo "Shared file: accessible"
|
|
exit 0
|
|
fi
|
|
|
|
sleep 10
|
|
done
|
|
|
|
echo
|
|
echo "------------------------------------------------------------"
|
|
echo "Timed out waiting for Windows installation."
|
|
exit 1
|
|
|
|
- name: Cleanup
|
|
if: always()
|
|
shell: bash
|
|
run: |
|
|
docker rm --force "$TUNNEL" 2>/dev/null || true
|
|
docker rm --force "$CONTAINER" 2>/dev/null || true
|