build: Update workflow (#2012)

This commit is contained in:
Kroese
2026-07-28 00:30:42 +02:00
committed by GitHub
parent 69727e49dc
commit 45ba5317a7
2 changed files with 378 additions and 91 deletions
+286 -21
View File
@@ -19,10 +19,10 @@ on:
default: ubuntu-24.04 default: ubuntu-24.04
type: string type: string
image: branch:
description: Container image to test description: Repository branch to build
required: false required: false
default: ghcr.io/dockur/windows:latest default: dev
type: string type: string
callback: callback:
@@ -31,12 +31,48 @@ on:
default: powershell default: powershell
type: string type: string
machine:
description: QEMU machine type exposed to Windows
required: false
default: ""
type: string
cpu: cpu:
description: CPU model exposed to Windows description: CPU model exposed to Windows
required: false required: false
default: host default: ""
type: string type: string
cpu_flags:
description: Additional CPU flags exposed to Windows
required: false
default: ""
type: string
disk_type:
description: Disk controller type exposed to Windows
required: false
default: ""
type: string
network:
description: Network backend exposed to Windows
required: false
default: ""
type: string
adapter:
description: Network adapter exposed to Windows
required: false
default: ""
type: string
kill_on_failure:
description: Stop the workflow when an installation failure is detected
required: false
default: true
type: boolean
expected_caption: expected_caption:
description: Text expected in the Windows caption description: Text expected in the Windows caption
required: true required: true
@@ -69,9 +105,45 @@ jobs:
env: env:
CONTAINER: windows-test CONTAINER: windows-test
IMAGE: ${{ inputs.image }} IMAGE: windows-validation:local
TUNNEL: windows-test-tunnel
steps: steps:
- name: Resolve branch
id: branch
shell: bash
env:
REQUESTED_BRANCH: ${{ inputs.branch }}
GITHUB_TOKEN: ${{ github.token }}
run: |
set -Eeuo pipefail
authorization="$(
printf 'x-access-token:%s' "$GITHUB_TOKEN" |
base64 -w 0
)"
if git \
-c "http.extraheader=Authorization: basic $authorization" \
ls-remote \
--exit-code \
--heads \
"$GITHUB_SERVER_URL/$GITHUB_REPOSITORY.git" \
"refs/heads/$REQUESTED_BRANCH" > /dev/null 2>&1; then
branch="$REQUESTED_BRANCH"
else
branch="master"
echo "Branch '$REQUESTED_BRANCH' does not exist; using '$branch'."
fi
echo "branch=$branch" >> "$GITHUB_OUTPUT"
- name: Checkout branch
uses: actions/checkout@v7
with:
ref: ${{ steps.branch.outputs.branch }}
- name: Check KVM - name: Check KVM
shell: bash shell: bash
run: | run: |
@@ -166,6 +238,61 @@ jobs:
goto sync goto sync
BATCH BATCH
cat > "$RUNNER_TEMP/oem/shutdown.vbs" <<'VBSCRIPT'
On Error Resume Next
Dim service
Dim systems
Dim system
Set service = GetObject("winmgmts:{impersonationLevel=impersonate,(Shutdown)}!\\.\root\cimv2")
Set systems = service.ExecQuery("SELECT * FROM Win32_OperatingSystem")
For Each system In systems
system.Win32Shutdown 5
Next
VBSCRIPT
cat > "$RUNNER_TEMP/oem/watchdog.vbs" <<'VBSCRIPT'
Option Explicit
Const ForAppending = 8
Dim filesystem
Dim file
Dim shell
Dim service
Dim systems
Dim system
WScript.Sleep 180000
Set filesystem = CreateObject("Scripting.FileSystemObject")
If filesystem.FileExists("C:\OEM\install.done") Then
WScript.Quit 0
End If
On Error Resume Next
Set file = filesystem.OpenTextFile("C:\OEM\install.log", ForAppending, True)
file.WriteLine Now & " - Callback failed for 3 minutes."
file.Close
Set file = filesystem.CreateTextFile("C:\OEM\install.done", True)
file.Close
Set shell = CreateObject("WScript.Shell")
shell.Run "shutdown.exe -s -f -t 0", 0, False
Set service = GetObject("winmgmts:{impersonationLevel=impersonate,(Shutdown)}!\\.\root\cimv2")
Set systems = service.ExecQuery("SELECT * FROM Win32_OperatingSystem")
For Each system In systems
system.Win32Shutdown 5
Next
VBSCRIPT
case "$CALLBACK" in case "$CALLBACK" in
powershell) powershell)
cat > "$RUNNER_TEMP/oem/ready.ps1" <<'POWERSHELL' cat > "$RUNNER_TEMP/oem/ready.ps1" <<'POWERSHELL'
@@ -244,12 +371,22 @@ jobs:
@echo off @echo off
del /Q C:\OEM\install.done 2>nul del /Q C:\OEM\install.done 2>nul
start "" /B cmd.exe /C C:\OEM\sync-log.bat start "" /B cmd.exe /C C:\OEM\sync-log.bat
start "" /B cscript.exe //B //NoLogo C:\OEM\watchdog.vbs
powershell.exe -NoProfile -ExecutionPolicy Bypass ^ powershell.exe -NoProfile -ExecutionPolicy Bypass ^
-File C:\OEM\ready.ps1 ^ -File C:\OEM\ready.ps1 ^
-Token "$token" -Token "$token"
set "result=%errorlevel%" set "result=%errorlevel%"
if not "%result%"=="0" (
echo %date% %time% - Callback failed.>>C:\OEM\install.log
type nul > C:\OEM\install.done
shutdown.exe -s -f -t 0 >nul 2>&1
if errorlevel 1 cscript.exe //B //NoLogo C:\OEM\shutdown.vbs
exit /B %result%
)
type nul > C:\OEM\install.done type nul > C:\OEM\install.done
call C:\OEM\sync-log.bat once call C:\OEM\sync-log.bat once
exit /B %result% exit /B %result%
@@ -443,10 +580,20 @@ jobs:
@echo off @echo off
del /Q C:\OEM\install.done 2>nul del /Q C:\OEM\install.done 2>nul
start "" /B cmd.exe /C C:\OEM\sync-log.bat start "" /B cmd.exe /C C:\OEM\sync-log.bat
start "" /B cscript.exe //B //NoLogo C:\OEM\watchdog.vbs
cscript.exe //B //NoLogo C:\OEM\ready.vbs "$token" cscript.exe //B //NoLogo C:\OEM\ready.vbs "$token"
set "result=%errorlevel%" set "result=%errorlevel%"
if not "%result%"=="0" (
echo %date% %time% - Callback failed.>>C:\OEM\install.log
type nul > C:\OEM\install.done
shutdown.exe -s -f -t 0 >nul 2>&1
if errorlevel 1 cscript.exe //B //NoLogo C:\OEM\shutdown.vbs
exit /B %result%
)
type nul > C:\OEM\install.done type nul > C:\OEM\install.done
call C:\OEM\sync-log.bat once call C:\OEM\sync-log.bat once
exit /B %result% exit /B %result%
@@ -456,27 +603,44 @@ jobs:
echo "token=$token" >> "$GITHUB_OUTPUT" echo "token=$token" >> "$GITHUB_OUTPUT"
- name: Pull image - name: Build image
shell: bash shell: bash
run: | run: |
set -Eeuo pipefail set -Eeuo pipefail
docker pull "$IMAGE" echo "Building branch: ${{ steps.branch.outputs.branch }}"
echo "Commit: $(git rev-parse HEAD)"
for attempt in 1 2 3; do
if docker build \
--tag "$IMAGE" \
. >/dev/null 2>/dev/null; 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" \ docker image inspect "$IMAGE" \
--format 'Digest: {{index .RepoDigests 0}}' --format 'Image ID: {{.Id}}'
- name: Install and validate Windows - name: Start Windows container
shell: bash shell: bash
env: env:
ADAPTER: ${{ inputs.adapter }}
CPU: ${{ inputs.cpu }} CPU: ${{ inputs.cpu }}
EXPECTED_TOKEN: ${{ steps.test.outputs.token }} CPU_FLAGS: ${{ inputs.cpu_flags }}
EXPECTED_CAPTION: ${{ inputs.expected_caption }} DISK_TYPE: ${{ inputs.disk_type }}
EXPECTED_EDITION: ${{ inputs.expected_edition }} MACHINE: ${{ inputs.machine }}
EXPECTED_PLATFORM: ${{ inputs.platform }} NETWORK: ${{ inputs.network }}
MINIMUM_BUILD: ${{ inputs.minimum_build }}
VERSION: ${{ inputs.version }} VERSION: ${{ inputs.version }}
DISPLAY_NAME: ${{ inputs.name }}
run: | run: |
set -Eeuo pipefail set -Eeuo pipefail
@@ -486,17 +650,86 @@ jobs:
--device /dev/net/tun \ --device /dev/net/tun \
--cap-add NET_ADMIN \ --cap-add NET_ADMIN \
--stop-timeout 120 \ --stop-timeout 120 \
--env "MACHINE=$MACHINE" \
--env "VERSION=$VERSION" \ --env "VERSION=$VERSION" \
--env "RAM_SIZE=half" \ --env "RAM_SIZE=half" \
--env "CPU_CORES=half" \ --env "CPU_CORES=half" \
--env "CPU_MODEL=$CPU" \
--env "LOG=Y" \
--env "DISK_SIZE=64G" \ --env "DISK_SIZE=64G" \
--env "CPU_MODEL=$CPU" \
--env "CPU_FLAGS=$CPU_FLAGS" \
--env "DISK_TYPE=$DISK_TYPE" \
--env "NETWORK=$NETWORK" \
--env "ADAPTER=$ADAPTER" \
--env "LOG=Y" \
--env "DEBUG=Y" \
--env "LOSSY=Y" \
--volume "$RUNNER_TEMP/data:/shared" \ --volume "$RUNNER_TEMP/data:/shared" \
--volume "$RUNNER_TEMP/oem:/oem:ro" \ --volume "$RUNNER_TEMP/oem:/oem:ro" \
--volume "$RUNNER_TEMP/storage:/storage" \ --volume "$RUNNER_TEMP/storage:/storage" \
"$IMAGE" "$IMAGE"
- name: Start noVNC tunnel
if: ${{ !inputs.kill_on_failure }}
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
echo "Container log:" echo "Container log:"
echo "------------------------------------------------------------" echo "------------------------------------------------------------"
@@ -541,10 +774,27 @@ jobs:
trap stop_logs EXIT 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)) deadline=$((SECONDS + 9000))
reboot_timeout=1800 reboot_timeout=1800
minimum_reboots=1 minimum_reboots=1
boot_loop_limit=5 boot_loop_limit=10
first_bios_start=-1 first_bios_start=-1
while (( SECONDS < deadline )); do while (( SECONDS < deadline )); do
@@ -564,23 +814,31 @@ jobs:
container_log="$(docker logs "$CONTAINER" 2>&1 || true)" container_log="$(docker logs "$CONTAINER" 2>&1 || true)"
if grep -Eqi \ if (( failure_checks_disabled == 0 )) &&
grep -Eqi \
'KVM internal error|KVM: entry failed|hardware error 0x[0-9a-f]+|Triple fault' \ 'KVM internal error|KVM: entry failed|hardware error 0x[0-9a-f]+|Triple fault' \
<<< "$container_log"; then <<< "$container_log"; then
echo echo
echo "------------------------------------------------------------" echo "------------------------------------------------------------"
echo "Detected a fatal QEMU or KVM error." echo "Detected a fatal QEMU or KVM error."
if ! handle_failure "Detected a fatal QEMU or KVM error."; then
exit 1 exit 1
fi fi
fi
if grep -Eqi \ if (( failure_checks_disabled == 0 )) &&
grep -Eqi \
'CDBOOT: Cannot boot from CD.*Code: 5' \ 'CDBOOT: Cannot boot from CD.*Code: 5' \
<<< "$container_log"; then <<< "$container_log"; then
echo echo
echo "------------------------------------------------------------" echo "------------------------------------------------------------"
echo "The installation media could not be booted." echo "The installation media could not be booted."
if ! handle_failure "The installation media could not be booted."; then
exit 1 exit 1
fi fi
fi
bios_starts="$( bios_starts="$(
grep -Fci 'SeaBIOS (version ' <<< "$container_log" || true grep -Fci 'SeaBIOS (version ' <<< "$container_log" || true
@@ -620,7 +878,8 @@ jobs:
first_bios_start=$SECONDS first_bios_start=$SECONDS
fi fi
if (( first_bios_start >= 0 && if (( failure_checks_disabled == 0 &&
first_bios_start >= 0 &&
SECONDS - first_bios_start >= reboot_timeout && SECONDS - first_bios_start >= reboot_timeout &&
reboots < minimum_reboots )); then reboots < minimum_reboots )); then
echo echo
@@ -629,8 +888,11 @@ jobs:
"$((reboot_timeout / 60)) minutes after the first BIOS start." "$((reboot_timeout / 60)) minutes after the first BIOS start."
echo "Observed reboots: $reboots" echo "Observed reboots: $reboots"
echo "Required reboots: $minimum_reboots" echo "Required reboots: $minimum_reboots"
if ! handle_failure "The installation did not reboot in time."; then
exit 1 exit 1
fi fi
fi
if (( bios_starts >= boot_loop_limit )) && if (( bios_starts >= boot_loop_limit )) &&
(( hard_disk_boots >= boot_loop_limit || (( hard_disk_boots >= boot_loop_limit ||
@@ -642,6 +904,7 @@ jobs:
echo echo
echo "------------------------------------------------------------" echo "------------------------------------------------------------"
echo "Detected a repeated BIOS boot loop." echo "Detected a repeated BIOS boot loop."
echo
echo "SeaBIOS starts: $bios_starts" echo "SeaBIOS starts: $bios_starts"
echo "Hard disk boots: $hard_disk_boots" echo "Hard disk boots: $hard_disk_boots"
echo "DVD boots: $dvd_boots" echo "DVD boots: $dvd_boots"
@@ -649,6 +912,7 @@ jobs:
echo "Unreadable boot-disk failures: $unreadable_boot_disk" echo "Unreadable boot-disk failures: $unreadable_boot_disk"
echo "No-bootable-device failures: $no_bootable_device" echo "No-bootable-device failures: $no_bootable_device"
echo "BOOTMGR failures: $bootmgr_missing" echo "BOOTMGR failures: $bootmgr_missing"
exit 1 exit 1
fi fi
@@ -760,4 +1024,5 @@ jobs:
if: always() if: always()
shell: bash shell: bash
run: | run: |
docker rm --force "$TUNNEL" 2>/dev/null || true
docker rm --force "$CONTAINER" 2>/dev/null || true docker rm --force "$CONTAINER" 2>/dev/null || true
+24 -2
View File
@@ -48,11 +48,17 @@ jobs:
check() { check() {
local url="$1" local url="$1"
local agent="Mozilla/5.0 (X11; Linux x86_64; rv:154.0) Gecko/20100101 Firefox/154.0" local agent="Mozilla/5.0 (X11; Linux x86_64; rv:154.0) Gecko/20100101 Firefox/154.0"
local tmp output pid blocks bytes rc local tmp output pid blocks bytes rc status delay
local start=$SECONDS local attempt=1
local attempts=3
local start
tmp=$(mktemp -d) tmp=$(mktemp -d)
while (( attempt <= attempts )); do
output="$tmp/aria2.log" output="$tmp/aria2.log"
rm -f -- "$tmp/probe" "$tmp/probe.aria2" "$output"
start=$SECONDS
aria2c \ aria2c \
--no-conf=true \ --no-conf=true \
@@ -120,9 +126,23 @@ jobs:
return 0 return 0
fi fi
status=$(sed -n 's/.*status=\([0-9][0-9][0-9]\).*/\1/p' "$output" | tail -n 1)
if (( attempt < attempts )) && [[ "$status" == "429" || "$status" == 5?? ]]; then
delay=$((attempt * 15))
echo " HTTP ${status}, retrying in ${delay} seconds..." >&2
sleep "$delay"
attempt=$((attempt + 1))
continue
fi
sed 's/^/ /' "$output" >&2 sed 's/^/ /' "$output" >&2
echo " Downloaded bytes: $bytes" >&2 echo " Downloaded bytes: $bytes" >&2
echo " aria2c exit status: $rc" >&2 echo " aria2c exit status: $rc" >&2
rm -rf -- "$tmp"
return 1
done
rm -rf -- "$tmp" rm -rf -- "$tmp"
return 1 return 1
} }
@@ -180,6 +200,8 @@ jobs:
failed_links+="$url"$'\n' failed_links+="$url"$'\n'
failed_links_html+="<li><a href=\"$url\">$url</a></li>"$'\n' failed_links_html+="<li><a href=\"$url\">$url</a></li>"$'\n'
fi fi
sleep 2
done < "src/define.sh" done < "src/define.sh"
echo "" echo ""