mirror of
https://github.com/dockur/windows.git
synced 2026-07-27 21:42:36 +07:00
Compare commits
20 Commits
fdad5260cf
...
2ac9d71b32
| Author | SHA1 | Date | |
|---|---|---|---|
| 2ac9d71b32 | |||
| e7e637531e | |||
| b90ded928b | |||
| 0d0dd7be87 | |||
| 5b080ac6c8 | |||
| 6df58b5dc7 | |||
| 8929f7cdcd | |||
| aa6240ff99 | |||
| 3d3bab023d | |||
| 36d127d8cf | |||
| 2b7d7bf902 | |||
| 30d9858c7d | |||
| 09941ba0f6 | |||
| 828bdb1b29 | |||
| c64cf2313b | |||
| f45575c13f | |||
| 11b300149f | |||
| 605f31af72 | |||
| 4e35ead612 | |||
| 0a3bff5a7d |
@@ -1,4 +1,5 @@
|
||||
version: 2
|
||||
|
||||
updates:
|
||||
- package-ecosystem: docker
|
||||
directory: /
|
||||
@@ -6,9 +7,15 @@ updates:
|
||||
interval: weekly
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
cooldown:
|
||||
default-days: 7
|
||||
ignore:
|
||||
- dependency-name: "*"
|
||||
update-types:
|
||||
- version-update:semver-minor
|
||||
- version-update:semver-patch
|
||||
|
||||
@@ -0,0 +1,680 @@
|
||||
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
|
||||
|
||||
image:
|
||||
description: Container image to test
|
||||
required: false
|
||||
default: ghcr.io/dockur/windows:latest
|
||||
type: string
|
||||
|
||||
callback:
|
||||
description: Guest script type
|
||||
required: false
|
||||
default: powershell
|
||||
type: string
|
||||
|
||||
cpu:
|
||||
description: CPU model exposed to Windows
|
||||
required: false
|
||||
default: host
|
||||
type: string
|
||||
|
||||
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: ${{ inputs.image }}
|
||||
|
||||
steps:
|
||||
- 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"
|
||||
|
||||
case "$CALLBACK" in
|
||||
powershell)
|
||||
cat > "$RUNNER_TEMP/oem/ready.ps1" <<'POWERSHELL'
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Token
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$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()
|
||||
}
|
||||
}
|
||||
|
||||
while ($true) {
|
||||
try {
|
||||
$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 {
|
||||
Start-Sleep -Seconds 10
|
||||
}
|
||||
}
|
||||
POWERSHELL
|
||||
|
||||
cat > "$RUNNER_TEMP/oem/install.bat" <<EOF
|
||||
@echo off
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass ^
|
||||
-File C:\OEM\ready.ps1 ^
|
||||
-Token "$token"
|
||||
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
|
||||
|
||||
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 = ""
|
||||
|
||||
For Each system In systems
|
||||
caption = CStr(system.Caption)
|
||||
version = CStr(system.Version)
|
||||
build = CStr(system.BuildNumber)
|
||||
Exit For
|
||||
Next
|
||||
|
||||
edition = ReadRegistry(shell, "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\EditionID")
|
||||
platform = GetPlatform(shell)
|
||||
|
||||
temporary = "\\host.lan\Data\windows.tmp"
|
||||
destination = "\\host.lan\Data\windows.json"
|
||||
|
||||
Do
|
||||
On Error Resume Next
|
||||
|
||||
share = ReadTextFile(filesystem, "\\host.lan\Data\readme.txt")
|
||||
|
||||
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
|
||||
|
||||
If filesystem.FileExists(destination) Then
|
||||
filesystem.DeleteFile destination, True
|
||||
End If
|
||||
|
||||
filesystem.MoveFile temporary, destination
|
||||
End If
|
||||
|
||||
If Err.Number = 0 Then
|
||||
On Error GoTo 0
|
||||
Exit Do
|
||||
End If
|
||||
|
||||
Err.Clear
|
||||
On Error GoTo 0
|
||||
|
||||
WScript.Sleep 10000
|
||||
Loop
|
||||
VBSCRIPT
|
||||
|
||||
cat > "$RUNNER_TEMP/oem/install.bat" <<EOF
|
||||
@echo off
|
||||
cscript.exe //B //NoLogo C:\OEM\ready.vbs "$token"
|
||||
EOF
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "token=$token" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Pull image
|
||||
shell: bash
|
||||
run: |
|
||||
set -Eeuo pipefail
|
||||
|
||||
docker pull "$IMAGE"
|
||||
|
||||
docker image inspect "$IMAGE" \
|
||||
--format 'Digest: {{index .RepoDigests 0}}'
|
||||
|
||||
- name: Install and validate Windows
|
||||
shell: bash
|
||||
env:
|
||||
CPU: ${{ inputs.cpu }}
|
||||
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 }}
|
||||
VERSION: ${{ inputs.version }}
|
||||
DISPLAY_NAME: ${{ inputs.name }}
|
||||
run: |
|
||||
set -Eeuo pipefail
|
||||
|
||||
docker run --detach \
|
||||
--name "$CONTAINER" \
|
||||
--device /dev/kvm \
|
||||
--device /dev/net/tun \
|
||||
--cap-add NET_ADMIN \
|
||||
--stop-timeout 120 \
|
||||
--env "VERSION=$VERSION" \
|
||||
--env "RAM_SIZE=half" \
|
||||
--env "CPU_CORES=half" \
|
||||
--env "CPU_MODEL=$CPU" \
|
||||
--env "DISK_SIZE=64G" \
|
||||
--volume "$RUNNER_TEMP/data:/shared" \
|
||||
--volume "$RUNNER_TEMP/oem:/oem:ro" \
|
||||
--volume "$RUNNER_TEMP/storage:/storage" \
|
||||
"$IMAGE"
|
||||
|
||||
echo
|
||||
echo "Container log:"
|
||||
echo "------------------------------------------------------------"
|
||||
|
||||
docker logs \
|
||||
--follow \
|
||||
--timestamps \
|
||||
"$CONTAINER" &
|
||||
|
||||
logs_pid="$!"
|
||||
|
||||
stop_logs() {
|
||||
kill "$logs_pid" 2>/dev/null || true
|
||||
wait "$logs_pid" 2>/dev/null || true
|
||||
}
|
||||
|
||||
trap stop_logs EXIT
|
||||
|
||||
deadline=$((SECONDS + 9000))
|
||||
reboot_timeout=1800
|
||||
minimum_reboots=1
|
||||
boot_loop_limit=5
|
||||
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 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."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if grep -Eqi \
|
||||
'CDBOOT: Cannot boot from CD.*Code: 5' \
|
||||
<<< "$container_log"; then
|
||||
echo
|
||||
echo "------------------------------------------------------------"
|
||||
echo "The installation media could not be booted."
|
||||
exit 1
|
||||
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 (( 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"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if (( 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"
|
||||
exit 1
|
||||
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 "$CONTAINER" 2>/dev/null || true
|
||||
+107
-5
@@ -2,6 +2,8 @@ name: Links
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
|
||||
concurrency:
|
||||
group: links
|
||||
@@ -22,29 +24,63 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
-
|
||||
name: Validate Links
|
||||
name: Validate all download mirrors
|
||||
id: validate
|
||||
run: |
|
||||
errors=0
|
||||
count=0
|
||||
skipped=0
|
||||
tested=0
|
||||
host=""
|
||||
failed_links=""
|
||||
failed_links_html=""
|
||||
declare -A seen
|
||||
|
||||
ignored_domains=(
|
||||
files.dog
|
||||
bobpony.com
|
||||
)
|
||||
|
||||
wget https://github.com/lwthiker/curl-impersonate/releases/download/v0.6.1/curl-impersonate-v0.6.1.x86_64-linux-gnu.tar.gz
|
||||
tar -xzf curl-impersonate-v0.6.1.x86_64-linux-gnu.tar.gz
|
||||
sudo cp curl-impersonate-ff /usr/local/bin/
|
||||
|
||||
check() {
|
||||
local url="$1" http
|
||||
|
||||
http=$(curl-impersonate-ff -sSL -o /dev/null -w "%{http_code}" --max-time 10 -I -- "$url" 2>&1) || http="000"
|
||||
[[ "$http" == 2* ]] && return 0
|
||||
|
||||
http=$(curl-impersonate-ff -sSL -o /dev/null -w "%{http_code}" --max-time 10 -r "0-0" -- "$url" 2>&1) || http="000"
|
||||
[[ "$http" == 2* ]]
|
||||
}
|
||||
|
||||
isIgnoredDomain() {
|
||||
local url="$1"
|
||||
local domain ignored
|
||||
|
||||
domain="${url#*://}"
|
||||
domain="${domain%%/*}"
|
||||
domain="${domain%%:*}"
|
||||
domain="${domain,,}"
|
||||
|
||||
for ignored in "${ignored_domains[@]}"; do
|
||||
if [[ "$domain" == "$ignored" || "$domain" == *."$ignored" ]]; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
while IFS= read -r line; do
|
||||
if [[ "$line" =~ ^[[:space:]]*local[[:space:]]+host=\"(https://[^\"]+)\" ]]; then
|
||||
host="${BASH_REMATCH[1]%/}"
|
||||
continue
|
||||
fi
|
||||
|
||||
[[ "$line" =~ ^[[:space:]]*url=\"(.+)\" ]] || continue
|
||||
val="${BASH_REMATCH[1]#/}"
|
||||
|
||||
if [[ "$val" == https://* ]]; then
|
||||
url="$val"
|
||||
elif [[ -n "$host" ]]; then
|
||||
@@ -52,16 +88,82 @@ jobs:
|
||||
else
|
||||
continue
|
||||
fi
|
||||
|
||||
[[ -v seen[$url] ]] && continue
|
||||
seen[$url]=1
|
||||
count=$((count + 1))
|
||||
|
||||
if isIgnoredDomain "$url"; then
|
||||
echo "SKIP: $url (ignored domain)"
|
||||
skipped=$((skipped + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
tested=$((tested + 1))
|
||||
|
||||
if check "$url"; then
|
||||
echo " OK: $url"
|
||||
else
|
||||
echo "FAIL: $url"
|
||||
errors=$((errors + 1))
|
||||
failed_links+="$url"$'\n'
|
||||
failed_links_html+="<li><a href=\"$url\">$url</a></li>"$'\n'
|
||||
fi
|
||||
done < "src/define.sh"
|
||||
|
||||
echo ""
|
||||
printf '%d/%d links valid\n' "$(( count - errors ))" "$count"
|
||||
(( errors == 0 ))
|
||||
printf '%d/%d tested links valid\n' "$((tested - errors))" "$tested"
|
||||
|
||||
if (( skipped > 0 )); then
|
||||
printf '%d link(s) skipped for ignored domains\n' "$skipped"
|
||||
fi
|
||||
|
||||
if (( errors > 0 )); then
|
||||
printf '%d blocking failure(s)\n' "$errors"
|
||||
|
||||
{
|
||||
echo "failed=true"
|
||||
echo "links<<EOF"
|
||||
printf '%s' "$failed_links"
|
||||
echo "EOF"
|
||||
echo "html_links<<EOF"
|
||||
printf '%s' "$failed_links_html"
|
||||
echo "EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "failed=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
-
|
||||
name: Send mail
|
||||
if: steps.validate.outputs.failed == 'true'
|
||||
uses: action-pack/send-mail@v1
|
||||
with:
|
||||
to: ${{secrets.MAILTO}}
|
||||
from: Github Actions <${{secrets.MAILTO}}>
|
||||
connection_url: ${{secrets.MAIL_CONNECTION}}
|
||||
subject: Mirror verification failed!
|
||||
body: |
|
||||
Mirror verification failed!
|
||||
|
||||
The following links failed:
|
||||
|
||||
${{ steps.validate.outputs.links }}
|
||||
|
||||
See https://github.com/${{ github.repository }}/actions for more information.
|
||||
html_body: |
|
||||
<p>Mirror verification failed!</p>
|
||||
|
||||
<p>The following links failed:</p>
|
||||
|
||||
<ul>
|
||||
${{ steps.validate.outputs.html_links }}
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
See
|
||||
<a href="https://github.com/${{ github.repository }}/actions">GitHub Actions</a>
|
||||
for more information.
|
||||
</p>
|
||||
-
|
||||
name: Fail workflow
|
||||
if: always() && steps.validate.outputs.failed == 'true'
|
||||
run: exit 1
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
name: Validation
|
||||
|
||||
on:
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: validation-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
desktop:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- name: Windows 11 Pro
|
||||
version: "11"
|
||||
expected_caption: Windows 11
|
||||
expected_edition: Professional
|
||||
minimum_build: 22000
|
||||
platform: x64
|
||||
|
||||
- name: Windows 11 LTSC
|
||||
version: "11l"
|
||||
expected_caption: Windows 11
|
||||
expected_edition: EnterpriseS
|
||||
minimum_build: 26100
|
||||
platform: x64
|
||||
|
||||
- name: Windows 11 Enterprise
|
||||
version: "11e"
|
||||
expected_caption: Windows 11
|
||||
expected_edition: Enterprise
|
||||
minimum_build: 22000
|
||||
platform: x64
|
||||
|
||||
- name: Windows 11 IoT Enterprise
|
||||
version: "11i"
|
||||
expected_caption: Windows 11
|
||||
expected_edition: IoTEnterpriseS
|
||||
minimum_build: 26100
|
||||
platform: x64
|
||||
|
||||
- name: Windows 10 Pro
|
||||
version: "10"
|
||||
expected_caption: Windows 10
|
||||
expected_edition: Professional
|
||||
minimum_build: 10240
|
||||
platform: x64
|
||||
|
||||
- name: Windows 10 LTSC
|
||||
version: "10l"
|
||||
expected_caption: Windows 10
|
||||
expected_edition: EnterpriseS
|
||||
minimum_build: 17763
|
||||
platform: x64
|
||||
|
||||
- name: Windows 10 Enterprise
|
||||
version: "10e"
|
||||
expected_caption: Windows 10
|
||||
expected_edition: Enterprise
|
||||
minimum_build: 10240
|
||||
platform: x64
|
||||
|
||||
- name: Windows 10 IoT Enterprise
|
||||
version: "10i"
|
||||
expected_caption: Windows 10
|
||||
expected_edition: IoTEnterpriseS
|
||||
minimum_build: 19044
|
||||
platform: x64
|
||||
|
||||
- name: Windows 8.1 Enterprise
|
||||
version: "8e"
|
||||
expected_caption: Windows 8.1
|
||||
expected_edition: Enterprise
|
||||
minimum_build: 9600
|
||||
platform: x64
|
||||
|
||||
name: ${{ matrix.name }}
|
||||
uses: ./.github/workflows/install.yml
|
||||
with:
|
||||
name: ${{ matrix.name }}
|
||||
version: ${{ matrix.version }}
|
||||
expected_caption: ${{ matrix.expected_caption }}
|
||||
expected_edition: ${{ matrix.expected_edition }}
|
||||
minimum_build: ${{ matrix.minimum_build }}
|
||||
platform: ${{ matrix.platform }}
|
||||
|
||||
tiny:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- name: Core11
|
||||
version: "core11"
|
||||
expected_caption: Windows 11
|
||||
minimum_build: 22000
|
||||
platform: x64
|
||||
|
||||
- name: Tiny11
|
||||
version: "tiny11"
|
||||
expected_caption: Windows 11
|
||||
minimum_build: 22000
|
||||
platform: x64
|
||||
|
||||
- name: Tiny10
|
||||
version: "tiny10"
|
||||
expected_caption: Windows 10
|
||||
minimum_build: 19041
|
||||
platform: x64
|
||||
|
||||
name: ${{ matrix.name }}
|
||||
uses: ./.github/workflows/install.yml
|
||||
with:
|
||||
name: ${{ matrix.name }}
|
||||
version: ${{ matrix.version }}
|
||||
expected_caption: ${{ matrix.expected_caption }}
|
||||
minimum_build: ${{ matrix.minimum_build }}
|
||||
platform: ${{ matrix.platform }}
|
||||
|
||||
server:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- name: Windows Server 2025
|
||||
version: "2025"
|
||||
expected_caption: Windows Server 2025
|
||||
minimum_build: 26100
|
||||
platform: x64
|
||||
|
||||
- name: Windows Server 2022
|
||||
version: "2022"
|
||||
expected_caption: Windows Server 2022
|
||||
minimum_build: 20348
|
||||
platform: x64
|
||||
|
||||
- name: Windows Server 2019
|
||||
version: "2019"
|
||||
expected_caption: Windows Server 2019
|
||||
minimum_build: 17763
|
||||
platform: x64
|
||||
|
||||
- name: Windows Server 2016
|
||||
version: "2016"
|
||||
expected_caption: Windows Server 2016
|
||||
minimum_build: 14393
|
||||
platform: x64
|
||||
|
||||
- name: Windows Server 2012 R2
|
||||
version: "2012"
|
||||
expected_caption: Windows Server 2012 R2
|
||||
minimum_build: 9600
|
||||
platform: x64
|
||||
|
||||
name: ${{ matrix.name }}
|
||||
uses: ./.github/workflows/install.yml
|
||||
with:
|
||||
name: ${{ matrix.name }}
|
||||
version: ${{ matrix.version }}
|
||||
expected_caption: ${{ matrix.expected_caption }}
|
||||
minimum_build: ${{ matrix.minimum_build }}
|
||||
platform: ${{ matrix.platform }}
|
||||
|
||||
legacy:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- name: Windows 7 Ultimate
|
||||
version: "7u"
|
||||
cpu: host
|
||||
expected_caption: Windows 7
|
||||
minimum_build: 7601
|
||||
platform: x64
|
||||
|
||||
- name: Windows 7 Ultimate x86
|
||||
version: "7x86"
|
||||
cpu: host
|
||||
expected_caption: Windows 7
|
||||
minimum_build: 7601
|
||||
platform: x86
|
||||
|
||||
- name: Windows Vista Ultimate
|
||||
version: "vu"
|
||||
cpu: host
|
||||
expected_caption: Windows Vista
|
||||
minimum_build: 6002
|
||||
platform: x64
|
||||
|
||||
- name: Windows Vista Ultimate x86
|
||||
version: "vistax86"
|
||||
cpu: host
|
||||
expected_caption: Windows Vista
|
||||
minimum_build: 6002
|
||||
platform: x86
|
||||
|
||||
- name: Windows XP Professional
|
||||
version: "xp"
|
||||
cpu: host
|
||||
expected_caption: Windows XP
|
||||
minimum_build: 2600
|
||||
platform: x86
|
||||
|
||||
- name: Windows XP Professional x64
|
||||
version: "xp64"
|
||||
cpu: host
|
||||
expected_caption: Windows XP
|
||||
minimum_build: 3790
|
||||
platform: x64
|
||||
|
||||
- name: Windows 2000 Professional
|
||||
version: "2k"
|
||||
cpu: host
|
||||
expected_caption: Windows 2000
|
||||
minimum_build: 2195
|
||||
platform: x86
|
||||
|
||||
- name: Windows Server 2008 R2
|
||||
version: "2008"
|
||||
cpu: host
|
||||
expected_caption: Windows Server 2008 R2
|
||||
minimum_build: 7601
|
||||
platform: x64
|
||||
|
||||
- name: Windows Server 2003 R2
|
||||
version: "2003"
|
||||
cpu: host
|
||||
expected_caption: Windows Server 2003
|
||||
minimum_build: 3790
|
||||
platform: x64
|
||||
|
||||
name: ${{ matrix.name }}
|
||||
uses: ./.github/workflows/install.yml
|
||||
with:
|
||||
name: ${{ matrix.name }}
|
||||
version: ${{ matrix.version }}
|
||||
callback: legacy
|
||||
cpu: ${{ matrix.cpu }}
|
||||
expected_caption: ${{ matrix.expected_caption }}
|
||||
minimum_build: ${{ matrix.minimum_build }}
|
||||
platform: ${{ matrix.platform }}
|
||||
@@ -161,6 +161,7 @@ Also see [Dynamic memory allocation](https://github.com/qemus/qemu/blob/master/d
|
||||
| `VERIFY` | `N` | Verifies downloaded installation media against predefined checksums. |
|
||||
| `REMOVE` | `Y` | Deletes the downloaded Windows ISO after installation to save space. |
|
||||
| `MANUAL` | `N` | Enables manual installation instead of unattended installation. |
|
||||
| `REBUILD` | `Y` | Skips rebuilding the Windows ISO and uses the original image unchanged. |
|
||||
| `COMMAND` | | Command to be executed during the final step of automatic installation. |
|
||||
|
||||
## 🔌 Shutdown
|
||||
|
||||
+530
-366
File diff suppressed because it is too large
Load Diff
+2
-1
@@ -11,6 +11,7 @@ set -Eeuo pipefail
|
||||
: "${EDITION:=""}"
|
||||
: "${MANUAL:=""}"
|
||||
: "${REMOVE:=""}"
|
||||
: "${REBUILD:=""}"
|
||||
: "${VERSION:=""}"
|
||||
: "${COMMAND:=""}"
|
||||
: "${DETECTED:=""}"
|
||||
@@ -776,7 +777,7 @@ normalizeEditionID() {
|
||||
case "$edition" in
|
||||
"pro" | "professional" | "business" )
|
||||
edition="" ;;
|
||||
"pro-n" | "professional-n" )
|
||||
"pro-n" | "pron" | "professional-n" | "professionaln" )
|
||||
edition="n" ;;
|
||||
esac
|
||||
|
||||
|
||||
+535
-137
@@ -184,7 +184,7 @@ getVersions() {
|
||||
groups_ref=()
|
||||
indexes_ref=()
|
||||
|
||||
platform=$(getPlatform "$xml")
|
||||
platform=$(getPlatform "$xml") || return 1
|
||||
count=$(xmllint --nonet --xpath 'count(/WIM/IMAGE)' - 2>/dev/null <<< "$xml") || return 0
|
||||
|
||||
for ((i=1; i<=count; i++)); do
|
||||
@@ -220,6 +220,21 @@ getVersions() {
|
||||
continue
|
||||
fi
|
||||
|
||||
local evaluation=""
|
||||
|
||||
if [[ "${image,,}" == *"evaluation"* ||
|
||||
"${display,,}" == *"evaluation"* ||
|
||||
"${product,,}" == *"evaluation"* ||
|
||||
"${edition_id,,}" == *"eval"* ||
|
||||
"${flags,,}" == *"eval"* ]]; then
|
||||
evaluation="-eval"
|
||||
fi
|
||||
|
||||
if [ -n "$evaluation" ] &&
|
||||
[[ "${candidate_id,,}" != *"-eval" ]]; then
|
||||
candidate_id+="$evaluation"
|
||||
fi
|
||||
|
||||
local key="${candidate_id,,}"
|
||||
|
||||
# Some client media use the same friendly name-derived ID for distinct
|
||||
@@ -230,10 +245,10 @@ getVersions() {
|
||||
|
||||
case "${candidate_base,,}" in
|
||||
"winvista"* | "win7"* | "win8"* | "win10"* | "win11"* )
|
||||
structured=$(normalizeEditionID "${edition_id:-${flags:-}}" "$candidate_base")
|
||||
structured=$(normalizeEditionID "${edition_id:-${flags:-}}" "$candidate_base") || return 1
|
||||
;;
|
||||
"win20"* )
|
||||
structured=$(normalizeServerEditionID "${flags:-$edition_id}")
|
||||
structured=$(normalizeServerEditionID "${flags:-$edition_id}") || return 1
|
||||
|
||||
# Some media use the same EDITIONID for Core and Desktop images.
|
||||
# INSTALLATIONTYPE provides the structural distinction without
|
||||
@@ -246,7 +261,7 @@ getVersions() {
|
||||
esac
|
||||
|
||||
if [ -n "$structured" ]; then
|
||||
candidate_id="$candidate_base-$structured"
|
||||
candidate_id="$candidate_base-$structured$evaluation"
|
||||
key="${candidate_id,,}"
|
||||
fi
|
||||
fi
|
||||
@@ -327,7 +342,7 @@ selectEdition() {
|
||||
if [ -n "$EDITION" ]; then
|
||||
|
||||
for base in "${edition_bases[@]}"; do
|
||||
edition=$("$normalize_name" "$EDITION" "$base")
|
||||
edition=$("$normalize_name" "$EDITION" "$base") || return 1
|
||||
preferred+=("$base${edition:+-$edition}")
|
||||
done
|
||||
|
||||
@@ -429,7 +444,7 @@ detectVersion() {
|
||||
versions \
|
||||
bases \
|
||||
groups \
|
||||
image_indexes
|
||||
image_indexes || return 1
|
||||
|
||||
[ "${#versions[@]}" -eq 0 ] && return 0
|
||||
|
||||
@@ -464,7 +479,7 @@ detectLanguage() {
|
||||
|
||||
local xml="$1"
|
||||
local index="${2:-}"
|
||||
local xpath lang
|
||||
local xpath lang culture
|
||||
|
||||
if [[ "$index" =~ ^[0-9]+$ ]]; then
|
||||
xpath="string((/WIM/IMAGE[@INDEX='$index']/WINDOWS/LANGUAGES/DEFAULT | /WIM/IMAGE[@INDEX='$index']/WINDOWS/LANGUAGES/FALLBACK/DEFAULT)[1])"
|
||||
@@ -479,9 +494,12 @@ detectLanguage() {
|
||||
return 0
|
||||
fi
|
||||
|
||||
local culture
|
||||
culture=$(getLanguage "$lang" "culture")
|
||||
[ -n "$culture" ] && LANGUAGE="$lang" && return 0
|
||||
culture=$(getLanguage "$lang" "culture") || return 1
|
||||
|
||||
if [ -n "$culture" ]; then
|
||||
LANGUAGE="$lang"
|
||||
return 0
|
||||
fi
|
||||
|
||||
warn "Invalid language detected: \"$lang\""
|
||||
return 0
|
||||
@@ -502,60 +520,105 @@ skipVersion() {
|
||||
detectLegacy() {
|
||||
|
||||
local dir="$1"
|
||||
local find
|
||||
local marker
|
||||
|
||||
[[ "${PLATFORM,,}" != "x64" ]] && return 1
|
||||
[[ "${PLATFORM,,}" == "x64" ]] || return 1
|
||||
|
||||
find=$(find "$dir" -maxdepth 1 -type d -iname WIN95 -print -quit)
|
||||
[ -n "$find" ] && DETECTED="win95" && return 0
|
||||
marker=$(find "$dir" -maxdepth 1 -type d -iname WIN95 -print -quit) || return 1
|
||||
|
||||
find=$(find "$dir" -maxdepth 1 -type d -iname WIN98 -print -quit)
|
||||
[ -n "$find" ] && DETECTED="win98" && return 0
|
||||
if [ -n "$marker" ]; then
|
||||
DETECTED="win95"
|
||||
return 0
|
||||
fi
|
||||
|
||||
find=$(find "$dir" -maxdepth 1 -type d -iname WIN9X -print -quit)
|
||||
[ -n "$find" ] && DETECTED="win9x" && return 0
|
||||
marker=$(find "$dir" -maxdepth 1 -type d -iname WIN98 -print -quit) || return 1
|
||||
|
||||
find=$(find "$dir" -maxdepth 1 -type f -iname CDROM_W.40 -print -quit)
|
||||
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname CDROM_S.40 -print -quit)
|
||||
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname CDROM_TS.40 -print -quit)
|
||||
[ -n "$find" ] && DETECTED="winnt4" && return 0
|
||||
if [ -n "$marker" ]; then
|
||||
DETECTED="win98"
|
||||
return 0
|
||||
fi
|
||||
|
||||
find=$(find "$dir" -maxdepth 1 -type f -iname CDROM_NT.5 -print -quit)
|
||||
marker=$(find "$dir" -maxdepth 1 -type d -iname WIN9X -print -quit) || return 1
|
||||
|
||||
if [ -n "$find" ]; then
|
||||
if [ -n "$marker" ]; then
|
||||
DETECTED="win9x"
|
||||
return 0
|
||||
fi
|
||||
|
||||
find=$(find "$dir" -maxdepth 1 -type f -iname CDROM_IA.5 -print -quit)
|
||||
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname CDROM_ID.5 -print -quit)
|
||||
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname CDROM_IP.5 -print -quit)
|
||||
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname CDROM_IS.5 -print -quit)
|
||||
[ -n "$find" ] && DETECTED="win2k" && return 0
|
||||
marker=$(find "$dir" -maxdepth 1 -type f \
|
||||
\( \
|
||||
-iname CDROM_W.40 -o \
|
||||
-iname CDROM_S.40 -o \
|
||||
-iname CDROM_TS.40 \
|
||||
\) \
|
||||
-print -quit) || return 1
|
||||
|
||||
if [ -n "$marker" ]; then
|
||||
DETECTED="winnt4"
|
||||
return 0
|
||||
fi
|
||||
|
||||
marker=$(find "$dir" -maxdepth 1 -type f -iname CDROM_NT.5 -print -quit) || return 1
|
||||
|
||||
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 1
|
||||
|
||||
if [ -n "$marker" ]; then
|
||||
DETECTED="win2k"
|
||||
return 0
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
find=$(find "$dir" -maxdepth 1 -iname WIN51 -print -quit)
|
||||
marker=$(find "$dir" -maxdepth 1 -iname WIN51 -print -quit) || return 1
|
||||
[ -n "$marker" ] || return 1
|
||||
|
||||
if [ -n "$find" ]; then
|
||||
marker=$(find "$dir" -maxdepth 1 -type f -iname WIN51AP -print -quit) || return 1
|
||||
|
||||
find=$(find "$dir" -maxdepth 1 -type f -iname WIN51AP -print -quit)
|
||||
[ -n "$find" ] && DETECTED="winxpx64" && return 0
|
||||
if [ -n "$marker" ]; then
|
||||
DETECTED="winxpx64"
|
||||
return 0
|
||||
fi
|
||||
|
||||
find=$(find "$dir" -maxdepth 1 -type f -iname WIN51IC -print -quit)
|
||||
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51IP -print -quit)
|
||||
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname setupxp.htm -print -quit)
|
||||
[ -n "$find" ] && DETECTED="winxpx86" && return 0
|
||||
marker=$(find "$dir" -maxdepth 1 -type f \
|
||||
\( \
|
||||
-iname WIN51IC -o \
|
||||
-iname WIN51IP -o \
|
||||
-iname setupxp.htm \
|
||||
\) \
|
||||
-print -quit) || return 1
|
||||
|
||||
find=$(find "$dir" -maxdepth 1 -type f -iname WIN51IS -print -quit)
|
||||
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51IA -print -quit)
|
||||
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51IB -print -quit)
|
||||
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51ID -print -quit)
|
||||
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51IL -print -quit)
|
||||
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51AA -print -quit)
|
||||
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51AD -print -quit)
|
||||
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51AS -print -quit)
|
||||
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51MA -print -quit)
|
||||
[ -z "$find" ] && find=$(find "$dir" -maxdepth 1 -type f -iname WIN51MD -print -quit)
|
||||
[ -n "$find" ] && DETECTED="win2003r2" && return 0
|
||||
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 \
|
||||
\) \
|
||||
-print -quit) || return 1
|
||||
|
||||
if [ -n "$marker" ]; then
|
||||
DETECTED="win2003r2"
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
@@ -592,15 +655,15 @@ resolveImage() {
|
||||
setImage() {
|
||||
|
||||
skipVersion "${DETECTED,,}" && return 0
|
||||
setXML "" && return 0
|
||||
enabled "$MANUAL" && return 0
|
||||
|
||||
if ! setXML "" && ! enabled "$MANUAL"; then
|
||||
MANUAL="Y"
|
||||
MANUAL="Y"
|
||||
|
||||
local desc
|
||||
desc=$(printEdition "$DETECTED" "this version")
|
||||
warn "the answer file for $desc was not found ($DETECTED.xml), $FB."
|
||||
fi
|
||||
local desc
|
||||
desc=$(printEdition "$DETECTED" "this version") || return 1
|
||||
|
||||
warn "the answer file for $desc was not found ($DETECTED.xml), $FB."
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -662,20 +725,19 @@ getSuggestion() {
|
||||
validateEdition() {
|
||||
|
||||
[ -n "$EDITION" ] || return 0
|
||||
[[ "${DETECTED,,}" == "win20"* ]] || return 0
|
||||
|
||||
case "${DETECTED,,}" in
|
||||
"win20"* )
|
||||
local edition
|
||||
edition=$(normalizeServerEditionID "$EDITION")
|
||||
local edition
|
||||
edition=$(normalizeServerEditionID "$EDITION") || return 1
|
||||
|
||||
if [ -n "$edition" ] &&
|
||||
[[ "${DETECTED,,}" != *"-${edition,,}" &&
|
||||
"${DETECTED,,}" != *"-${edition,,}-eval" ]]; then
|
||||
EDITION=""
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
[ -n "$edition" ] || return 0
|
||||
|
||||
if [[ "${DETECTED,,}" == *"-${edition,,}" ||
|
||||
"${DETECTED,,}" == *"-${edition,,}-eval" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
EDITION=""
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -700,13 +762,12 @@ describeImage() {
|
||||
local result_name="$3"
|
||||
local result
|
||||
|
||||
result=$(printEdition "$DETECTED" "$DETECTED" "Y")
|
||||
|
||||
detectLanguage "$info_xml" "$index"
|
||||
result=$(printEdition "$DETECTED" "$DETECTED" "Y") || return 1
|
||||
detectLanguage "$info_xml" "$index" || return 1
|
||||
|
||||
if [[ "${LANGUAGE,,}" != "en" && "${LANGUAGE,,}" != "en-"* ]]; then
|
||||
local language
|
||||
language=$(getLanguage "$LANGUAGE" "desc")
|
||||
language=$(getLanguage "$LANGUAGE" "desc") || return 1
|
||||
result+=" ($language)"
|
||||
fi
|
||||
|
||||
@@ -730,13 +791,18 @@ configureImage() {
|
||||
local msg="the answer file for $desc was not found ($DETECTED.xml)"
|
||||
local fallback="/run/assets/${DETECTED%%-*}.xml"
|
||||
|
||||
if setXML "$fallback" "$index" || enabled "$MANUAL"; then
|
||||
! enabled "$MANUAL" && warn "${msg}."
|
||||
else
|
||||
MANUAL="Y"
|
||||
warn "${msg}, $FB."
|
||||
if setXML "$fallback" "$index"; then
|
||||
if ! enabled "$MANUAL"; then
|
||||
warn "${msg}."
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
|
||||
enabled "$MANUAL" && return 0
|
||||
|
||||
MANUAL="Y"
|
||||
warn "${msg}, $FB."
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -748,9 +814,7 @@ detectImage() {
|
||||
|
||||
XML=""
|
||||
|
||||
resolveImage "$version" || :
|
||||
|
||||
if [ -n "$DETECTED" ]; then
|
||||
if resolveImage "$version"; then
|
||||
setImage || return 1
|
||||
return 0
|
||||
fi
|
||||
@@ -758,7 +822,7 @@ detectImage() {
|
||||
info "Detecting version from ISO image..."
|
||||
|
||||
if detectLegacy "$dir"; then
|
||||
desc=$(printEdition "$DETECTED" "$DETECTED" "Y")
|
||||
desc=$(printEdition "$DETECTED" "$DETECTED" "Y") || return 1
|
||||
info "Detected: $desc"
|
||||
return 0
|
||||
fi
|
||||
@@ -791,6 +855,65 @@ detectImage() {
|
||||
return 0
|
||||
}
|
||||
|
||||
normalizeBatch() {
|
||||
|
||||
local file="$1"
|
||||
local bom tmp encoding
|
||||
|
||||
[ ! -s "$file" ] && return 0
|
||||
|
||||
bom=$(od -An -N2 -tx1 "$file" | tr -d ' \n') || return 1
|
||||
|
||||
case "$bom" in
|
||||
"fffe" ) encoding="UTF-16LE" ;;
|
||||
"feff" ) encoding="UTF-16BE" ;;
|
||||
* ) return 0 ;;
|
||||
esac
|
||||
|
||||
if ! tmp=$(mktemp "${file}.XXXXXX"); then
|
||||
error "Failed to create temporary batch file!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! tail -c +3 "$file" | iconv -f "$encoding" -t UTF-8 > "$tmp"; then
|
||||
rm -f "$tmp"
|
||||
error "Failed to convert $file from $encoding to UTF-8!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! chmod --reference="$file" "$tmp" || ! mv -f "$tmp" "$file"; then
|
||||
rm -f "$tmp"
|
||||
error "Failed to replace batch file: $file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
reportBatchMatches() {
|
||||
|
||||
local file="$1"
|
||||
local source="$2"
|
||||
local pattern="$3"
|
||||
local message="$4"
|
||||
local suggestion="$5"
|
||||
local matches line
|
||||
|
||||
matches=$(grep -Pin "$pattern" "$file" || true)
|
||||
|
||||
[ -n "$matches" ] || return 0
|
||||
|
||||
warn "$message in $source:"
|
||||
|
||||
while IFS= read -r line; do
|
||||
printf ' %s\n' "$line" >&2
|
||||
done <<< "$matches"
|
||||
|
||||
printf ' %s\n\n' "$suggestion" >&2
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
checkBatch() {
|
||||
|
||||
local file="$1"
|
||||
@@ -798,7 +921,7 @@ checkBatch() {
|
||||
local tmp output
|
||||
local matches line
|
||||
|
||||
[ ! -f "$file" ] && return 0
|
||||
[ -s "$file" ] || return 0
|
||||
|
||||
if ! tmp=$(mktemp -d /tmp/blinter.XXXXXX); then
|
||||
warn "failed to create temporary Blinter directory."
|
||||
@@ -809,14 +932,20 @@ checkBatch() {
|
||||
[ -n "${COMMAND:-}" ] && source="your COMMAND variable"
|
||||
|
||||
if enabled "$DEBUG"; then
|
||||
|
||||
report="Y"
|
||||
|
||||
if LC_ALL=C grep -Pq '[^\x09\x0D\x20-\x7E]' "$file"; then
|
||||
warn "non-ASCII characters were detected in $source and may not execute correctly in Windows Command Prompt."
|
||||
fi
|
||||
|
||||
else
|
||||
|
||||
# First pass: silently check only for Error-level findings.
|
||||
cat > "$tmp/blinter.ini" <<'EOF'
|
||||
cat > "$tmp/blinter.ini" <<'EOC'
|
||||
[general]
|
||||
min_severity = error
|
||||
EOF
|
||||
EOC
|
||||
|
||||
if ! (
|
||||
cd "$tmp"
|
||||
@@ -831,14 +960,14 @@ EOF
|
||||
|
||||
# Show useful diagnostic context, while excluding findings that are
|
||||
# irrelevant to unattended OEM scripts.
|
||||
cat > "$tmp/blinter.ini" <<'EOF'
|
||||
cat > "$tmp/blinter.ini" <<'EOC'
|
||||
[general]
|
||||
min_severity = warning
|
||||
show_summary = false
|
||||
|
||||
[rules]
|
||||
disabled_rules = W001,W028,W041,SEC002,SEC005
|
||||
EOF
|
||||
EOC
|
||||
|
||||
output=$(
|
||||
cd "$tmp"
|
||||
@@ -879,75 +1008,344 @@ EOF
|
||||
|
||||
fi
|
||||
|
||||
rm -rf "$tmp"
|
||||
rm -rf "$tmp" || true
|
||||
|
||||
matches=$(
|
||||
grep -Pin \
|
||||
'(?<!\\)\\host[.]lan[\\]' \
|
||||
"$file" || true
|
||||
)
|
||||
reportBatchMatches \
|
||||
"$file" \
|
||||
"$source" \
|
||||
'(?<!\\)\\host[.]lan[\\]' \
|
||||
"invalid single-backslash UNC path detected" \
|
||||
'Use "\\host.lan\Data\..." instead of "\host.lan\Data\...".'
|
||||
|
||||
if [ -n "$matches" ]; then
|
||||
warn "invalid single-backslash UNC path detected in $source:"
|
||||
reportBatchMatches \
|
||||
"$file" \
|
||||
"$source" \
|
||||
'(?<![\\[:alnum:]._-])host[.]lan[\\]' \
|
||||
"UNC path without leading backslashes detected" \
|
||||
'Use "\\host.lan\Data\..." instead of "host.lan\Data\...".'
|
||||
|
||||
while IFS= read -r line; do
|
||||
printf ' %s\n' "$line" >&2
|
||||
done <<< "$matches"
|
||||
reportBatchMatches \
|
||||
"$file" \
|
||||
"$source" \
|
||||
'//host[.]lan/' \
|
||||
"invalid forward-slash UNC path detected" \
|
||||
'Use "\\host.lan\Data\..." instead of "//host.lan/Data/...".'
|
||||
|
||||
printf '%s\n\n' \
|
||||
' Use "\\host.lan\Data\..." instead of "\host.lan\Data\...".' >&2
|
||||
reportBatchMatches \
|
||||
"$file" \
|
||||
"$source" \
|
||||
'\\\\host[.]lan\\shared(?:[\\/]|$)' \
|
||||
"invalid Samba share name detected" \
|
||||
'The "/shared" folder is exposed to Windows as "\\host.lan\Data".'
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
getBootLoadSize() {
|
||||
|
||||
local iso="$1"
|
||||
local desc="$2"
|
||||
local boot_info value
|
||||
|
||||
if ! boot_info=$(isoinfo -d -i "$iso"); then
|
||||
|
||||
case "${DETECTED,,}" in
|
||||
"win2k"* | "winxp"* | "win2003"* )
|
||||
BOOT_LOAD_SIZE=4
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
|
||||
error "Failed to read boot image information from $desc ISO!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
matches=$(
|
||||
grep -Pin \
|
||||
'(?<![\\[:alnum:]._-])host[.]lan[\\]' \
|
||||
"$file" || true
|
||||
)
|
||||
value=$(awk '/Nsect / { print $NF; exit }' <<< "$boot_info")
|
||||
|
||||
if [ -n "$matches" ]; then
|
||||
warn "UNC path without leading backslashes detected in $source:"
|
||||
if [ -z "$value" ]; then
|
||||
|
||||
while IFS= read -r line; do
|
||||
printf ' %s\n' "$line" >&2
|
||||
done <<< "$matches"
|
||||
case "${DETECTED,,}" in
|
||||
"win2k"* | "winxp"* | "win2003"* )
|
||||
BOOT_LOAD_SIZE=4
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
|
||||
printf '%s\n\n' \
|
||||
' Use "\\host.lan\Data\..." instead of "host.lan\Data\...".' >&2
|
||||
error "Failed to determine boot image load size from $desc ISO!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
matches=$(
|
||||
grep -Pin \
|
||||
'//host[.]lan/' \
|
||||
"$file" || true
|
||||
)
|
||||
|
||||
if [ -n "$matches" ]; then
|
||||
warn "invalid forward-slash UNC path detected in $source:"
|
||||
|
||||
while IFS= read -r line; do
|
||||
printf ' %s\n' "$line" >&2
|
||||
done <<< "$matches"
|
||||
|
||||
printf '%s\n\n' \
|
||||
' Use "\\host.lan\Data\..." instead of "//host.lan/Data/...".' >&2
|
||||
if [[ ! "$value" =~ ^[0-9]+$ ]] || [ "${#value}" -gt 5 ]; then
|
||||
error "Invalid boot image load size found in $desc ISO!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
matches=$(
|
||||
grep -Pin \
|
||||
'\\\\host[.]lan\\shared(?:[\\/]|$)' \
|
||||
"$file" || true
|
||||
)
|
||||
BOOT_LOAD_SIZE=$((10#$value))
|
||||
|
||||
if [ -n "$matches" ]; then
|
||||
warn "invalid Samba share name detected in $source:"
|
||||
|
||||
while IFS= read -r line; do
|
||||
printf ' %s\n' "$line" >&2
|
||||
done <<< "$matches"
|
||||
|
||||
printf '%s\n\n' \
|
||||
' The "/shared" folder is exposed to Windows as "\\host.lan\Data".' >&2
|
||||
if (( BOOT_LOAD_SIZE < 1 || BOOT_LOAD_SIZE > 65535 )); then
|
||||
error "Invalid boot image load size found in $desc ISO!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
extractBootImage() {
|
||||
|
||||
local iso="$1"
|
||||
local dir="$2"
|
||||
local desc="$3"
|
||||
|
||||
local tmp="$TMP/boot-images"
|
||||
local max_size=$((32 * 1024 * 1024))
|
||||
local rc size offset image=""
|
||||
local msg="using legacy extraction..."
|
||||
local boot_info
|
||||
local -a images=()
|
||||
|
||||
ETFS="boot.img"
|
||||
[ -s "$dir/$ETFS" ] && return 0
|
||||
|
||||
rm -f "$dir/$ETFS" || return 1
|
||||
rm -rf "$tmp" || return 1
|
||||
|
||||
if LC_ALL=C xorriso \
|
||||
-no_rc \
|
||||
-osirrox on \
|
||||
-indev "$iso" \
|
||||
-extract_boot_images "$tmp" >/dev/null 2>&1; then
|
||||
|
||||
mapfile -t images < <(
|
||||
find "$tmp" \
|
||||
-maxdepth 1 \
|
||||
-type f \
|
||||
-name 'eltorito_img*_bios.img' \
|
||||
-print
|
||||
)
|
||||
|
||||
if (( ${#images[@]} == 1 )); then
|
||||
image="${images[0]}"
|
||||
|
||||
if [ ! -s "$image" ]; then
|
||||
warn "The extracted BIOS boot image is empty, $msg"
|
||||
elif ! size=$(stat -c%s "$image"); then
|
||||
warn "Failed to determine the BIOS boot image size, $msg"
|
||||
elif (( size > max_size )); then
|
||||
warn "The extracted BIOS boot image exceeds 32 MB, $msg"
|
||||
else
|
||||
if ! mv -f "$image" "$dir/$ETFS"; then
|
||||
rm -rf "$tmp" || true
|
||||
error "Failed to save boot image from $desc ISO!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
rm -rf "$tmp" || return 1
|
||||
return 0
|
||||
fi
|
||||
|
||||
elif (( ${#images[@]} > 1 )); then
|
||||
warn "Multiple BIOS boot images were found, $msg"
|
||||
else
|
||||
warn "No BIOS boot image was found, $msg"
|
||||
fi
|
||||
|
||||
else
|
||||
rc=$?
|
||||
|
||||
if (( rc > 128 )); then
|
||||
rm -rf "$tmp" || true
|
||||
exit "$rc"
|
||||
fi
|
||||
|
||||
warn "Failed to extract the BIOS boot image, $msg"
|
||||
fi
|
||||
|
||||
rm -rf "$tmp" || true
|
||||
|
||||
local boot_info
|
||||
|
||||
if ! boot_info=$(isoinfo -d -i "$iso"); then
|
||||
error "Failed to read boot image information from $desc ISO!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
offset=$(awk '/Bootoff / { print $NF; exit }' <<< "$boot_info")
|
||||
|
||||
if [ -z "$offset" ]; then
|
||||
error "Failed to determine boot image offset from $desc ISO!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ ! "$offset" =~ ^[0-9]+$ ]]; then
|
||||
error "Invalid boot image location found in $desc ISO!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! dd \
|
||||
"if=$iso" \
|
||||
"of=$dir/$ETFS" \
|
||||
bs=2048 \
|
||||
"count=$BOOT_LOAD_SIZE" \
|
||||
"skip=$offset" \
|
||||
status=none; then
|
||||
rm -f "$dir/$ETFS" || true
|
||||
error "Failed to extract boot image from $desc ISO!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ ! -s "$dir/$ETFS" ]; then
|
||||
rm -f "$dir/$ETFS" || true
|
||||
error "Failed to locate file \"$ETFS\" in $desc ISO image!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
buildImage() {
|
||||
|
||||
local dir="$1"
|
||||
local failed=""
|
||||
local cat="BOOT.CAT"
|
||||
local log="/run/shm/iso.log"
|
||||
local base size desc
|
||||
|
||||
if [ -f "$BOOT" ]; then
|
||||
error "File $BOOT does already exist?!" && return 1
|
||||
fi
|
||||
|
||||
base=$(basename "$BOOT")
|
||||
local out="$TMP/${base%.*}.tmp"
|
||||
rm -f "$out"
|
||||
|
||||
desc=$(printVariant "$DETECTED" "ISO")
|
||||
|
||||
local msg="Building $desc image"
|
||||
info "$msg..." && html "$msg..."
|
||||
|
||||
[ -z "$LABEL" ] && LABEL="Windows"
|
||||
|
||||
if [ ! -f "$dir/$ETFS" ] || [ ! -s "$dir/$ETFS" ]; then
|
||||
error "Failed to locate file \"$ETFS\" in ISO image!" && return 1
|
||||
fi
|
||||
|
||||
if [[ "${BOOT_MODE,,}" == "windows_legacy" &&
|
||||
"${DETECTED,,}" != "win9"* &&
|
||||
-z "${BOOT_LOAD_SIZE:-}" ]]; then
|
||||
error "Failed to determine the boot image load size!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
size=$(du -b --max-depth=0 "$dir" | cut -f1)
|
||||
checkFreeSpace "$TMP" "$size" || return 1
|
||||
|
||||
/run/progress.sh "$out" "$size" "$msg ([P])..." &
|
||||
|
||||
if [[ "${BOOT_MODE,,}" != "windows_legacy" ]]; then
|
||||
|
||||
genisoimage \
|
||||
-o "$out" \
|
||||
-b "$ETFS" \
|
||||
-no-emul-boot \
|
||||
-c "$cat" \
|
||||
-iso-level 4 \
|
||||
-J \
|
||||
-l \
|
||||
-D \
|
||||
-N \
|
||||
-joliet-long \
|
||||
-relaxed-filenames \
|
||||
-V "${LABEL::30}" \
|
||||
-udf \
|
||||
-boot-info-table \
|
||||
-eltorito-alt-boot \
|
||||
-eltorito-boot "$EFISYS" \
|
||||
-no-emul-boot \
|
||||
-allow-limited-size \
|
||||
-quiet \
|
||||
"$dir" 2> "$log" || failed="y"
|
||||
|
||||
else
|
||||
|
||||
case "${DETECTED,,}" in
|
||||
"win2k"* | "winxp"* | "win2003"* )
|
||||
genisoimage \
|
||||
-o "$out" \
|
||||
-b "$ETFS" \
|
||||
-no-emul-boot \
|
||||
-boot-load-seg 1984 \
|
||||
-boot-load-size "$BOOT_LOAD_SIZE" \
|
||||
-c "$cat" \
|
||||
-iso-level 2 \
|
||||
-J \
|
||||
-l \
|
||||
-D \
|
||||
-N \
|
||||
-joliet-long \
|
||||
-relaxed-filenames \
|
||||
-V "${LABEL::30}" \
|
||||
-quiet \
|
||||
"$dir" 2> "$log" || failed="y"
|
||||
;;
|
||||
|
||||
"win9"* )
|
||||
genisoimage \
|
||||
-o "$out" \
|
||||
-b "$ETFS" \
|
||||
-J \
|
||||
-r \
|
||||
-V "${LABEL::30}" \
|
||||
-quiet \
|
||||
"$dir" 2> "$log" || failed="y"
|
||||
;;
|
||||
|
||||
* )
|
||||
genisoimage \
|
||||
-o "$out" \
|
||||
-b "$ETFS" \
|
||||
-no-emul-boot \
|
||||
-boot-load-size "$BOOT_LOAD_SIZE" \
|
||||
-c "$cat" \
|
||||
-iso-level 2 \
|
||||
-J \
|
||||
-l \
|
||||
-D \
|
||||
-N \
|
||||
-joliet-long \
|
||||
-relaxed-filenames \
|
||||
-V "${LABEL::30}" \
|
||||
-udf \
|
||||
-allow-limited-size \
|
||||
-quiet \
|
||||
"$dir" 2> "$log" || failed="y"
|
||||
;;
|
||||
esac
|
||||
|
||||
fi
|
||||
|
||||
fKill "progress.sh"
|
||||
|
||||
if [ -n "$failed" ]; then
|
||||
[ -s "$log" ] && echo "$(<"$log")"
|
||||
error "Failed to build image!" && return 1
|
||||
fi
|
||||
|
||||
local err=""
|
||||
local hide="Warning: creating filesystem that does not conform to ISO-9660."
|
||||
|
||||
[ -s "$log" ] && err="$(<"$log")"
|
||||
|
||||
if [ -n "$err" ] && [[ "$err" != "$hide" ]]; then
|
||||
echo "$err"
|
||||
fi
|
||||
|
||||
mv -f "$out" "$BOOT" || return 1
|
||||
|
||||
if ! setOwner "$BOOT"; then
|
||||
warn "Failed to set the owner for \"$BOOT\" !"
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
return 0
|
||||
|
||||
+59
-107
@@ -696,25 +696,27 @@ setMachine() {
|
||||
local iso="$2"
|
||||
local dir="$3"
|
||||
local desc="$4"
|
||||
local legacy=""
|
||||
|
||||
if ! disabled "$REBUILD"; then
|
||||
|
||||
case "${id,,}" in
|
||||
"win2k"* ) legacy="2k" ;;
|
||||
"winxp"* ) legacy="xp" ;;
|
||||
"win2003"* ) legacy="2k3" ;;
|
||||
esac
|
||||
|
||||
if [ -n "$legacy" ]; then
|
||||
if ! legacyInstall "$iso" "$dir" "$desc" "$legacy"; then
|
||||
error "Failed to prepare $desc ISO!"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
case "${id,,}" in
|
||||
"win9"* )
|
||||
ETFS="[BOOT]/Boot-1.44M.img" ;;
|
||||
"win2k"* )
|
||||
if ! legacyInstall "$iso" "$dir" "$desc" "2k"; then
|
||||
error "Failed to prepare $desc ISO!" && return 1
|
||||
fi ;;
|
||||
"winxp"* )
|
||||
if ! legacyInstall "$iso" "$dir" "$desc" "xp"; then
|
||||
error "Failed to prepare $desc ISO!" && return 1
|
||||
fi ;;
|
||||
"win2003"* )
|
||||
if ! legacyInstall "$iso" "$dir" "$desc" "2k3"; then
|
||||
error "Failed to prepare $desc ISO!" && return 1
|
||||
fi ;;
|
||||
esac
|
||||
|
||||
case "${id,,}" in
|
||||
"win9"* )
|
||||
USB="no"
|
||||
VGA="cirrus"
|
||||
@@ -722,6 +724,7 @@ setMachine() {
|
||||
MACHINE="pc-i440fx-2.4"
|
||||
BOOT_MODE="windows_legacy"
|
||||
[ -z "${ADAPTER:-}" ] && ADAPTER="pcnet" ;;
|
||||
|
||||
"win2k"* )
|
||||
VGA="cirrus"
|
||||
MACHINE="pc"
|
||||
@@ -729,6 +732,7 @@ setMachine() {
|
||||
DISK_TYPE="auto"
|
||||
BOOT_MODE="windows_legacy"
|
||||
[ -z "${ADAPTER:-}" ] && ADAPTER="rtl8139" ;;
|
||||
|
||||
"winxp"* )
|
||||
DISK_TYPE="blk"
|
||||
BOOT_MODE="windows_legacy"
|
||||
@@ -736,26 +740,30 @@ setMachine() {
|
||||
|
||||
if [ -z "${CPU_MODEL:-}" ] || [[ "${CPU_MODEL:-}" == "host" ]]; then
|
||||
# Workaround for boot loop on AMD EPYC processors
|
||||
if [[ "${CPU,,}" == *"amd epyc"* ]]; then
|
||||
if [[ "${id,,}" == "winxpx86"* && "${CPU,,}" == *"amd epyc"* ]]; then
|
||||
CPU_MODEL="qemu32"
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
fi ;;
|
||||
|
||||
"win2003"* )
|
||||
DISK_TYPE="blk"
|
||||
BOOT_MODE="windows_legacy"
|
||||
[ -z "${SOUND:-}" ] && SOUND="usb-audio"
|
||||
;;
|
||||
[ -z "${SOUND:-}" ] && SOUND="usb-audio" ;;
|
||||
|
||||
"winvista"* | "win7"* | "win2008"* )
|
||||
BOOT_MODE="windows_legacy" ;;
|
||||
|
||||
esac
|
||||
|
||||
case "${id,,}" in
|
||||
|
||||
"winxp"* | "win2003"* | "winvistax86"* | "win7x86"* | "win2008r2x86"* )
|
||||
|
||||
if isQ35 "${MACHINE:-q35}"; then
|
||||
# Prevent bluescreen if 64 bit PCI hole size is >2G.
|
||||
ARGS="-global q35-pcihost.x-pci-hole64-fix=false"
|
||||
fi ;;
|
||||
|
||||
esac
|
||||
|
||||
return 0
|
||||
@@ -769,26 +777,35 @@ prepareImage() {
|
||||
|
||||
desc=$(printVariant "$DETECTED" "$DETECTED")
|
||||
|
||||
# Adjust QEMU machine configuration for legacy versions
|
||||
setMachine "$DETECTED" "$iso" "$dir" "$desc" || return 1
|
||||
|
||||
disabled "$REBUILD" && return 0
|
||||
|
||||
if [[ "${BOOT_MODE,,}" == "windows_legacy" &&
|
||||
"${DETECTED,,}" != "win9"* ]]; then
|
||||
getBootLoadSize "$iso" "$desc" || return 1
|
||||
fi
|
||||
|
||||
skipVersion "$DETECTED" && return 0
|
||||
|
||||
if [[ "${BOOT_MODE,,}" != "windows_legacy" ]]; then
|
||||
if [[ "${BOOT_MODE,,}" == "windows_legacy" ]]; then
|
||||
|
||||
[ -f "$dir/$ETFS" ] && [ -s "$dir/$ETFS" ] &&
|
||||
[ -f "$dir/$EFISYS" ] && [ -s "$dir/$EFISYS" ] && return 0
|
||||
extractBootImage "$iso" "$dir" "$desc" && return 0
|
||||
|
||||
missing=$(basename "$dir/$EFISYS")
|
||||
if [ ! -f "$dir/$ETFS" ] || [ ! -s "$dir/$ETFS" ]; then
|
||||
missing=$(basename "$dir/$ETFS")
|
||||
fi
|
||||
|
||||
error "Failed to locate file \"${missing,,}\" in ISO image!"
|
||||
error "Failed to extract boot image from ISO image \"${iso}\"!"
|
||||
return 1
|
||||
fi
|
||||
|
||||
legacyPrepare "$iso" "$dir" "$desc" && return 0
|
||||
[ -f "$dir/$ETFS" ] && [ -s "$dir/$ETFS" ] &&
|
||||
[ -f "$dir/$EFISYS" ] && [ -s "$dir/$EFISYS" ] && return 0
|
||||
|
||||
error "Failed to extract boot image from ISO image!"
|
||||
missing=$(basename "$dir/$EFISYS")
|
||||
if [ ! -f "$dir/$ETFS" ] || [ ! -s "$dir/$ETFS" ]; then
|
||||
missing=$(basename "$dir/$ETFS")
|
||||
fi
|
||||
|
||||
error "Failed to locate file \"${missing,,}\" in ISO image!"
|
||||
return 1
|
||||
}
|
||||
|
||||
@@ -816,6 +833,10 @@ addFolder() {
|
||||
|
||||
file=$(find "$dest" -maxdepth 1 -type f -iname install.bat -print -quit) || return 1
|
||||
|
||||
if [ -s "$file" ]; then
|
||||
normalizeBatch "$file" || return 1
|
||||
fi
|
||||
|
||||
if [ -n "$COMMAND" ]; then
|
||||
|
||||
[ -z "$file" ] && file="$dest/install.bat"
|
||||
@@ -828,7 +849,7 @@ addFolder() {
|
||||
|
||||
fi
|
||||
|
||||
if [ -f "$file" ]; then
|
||||
if [ -s "$file" ]; then
|
||||
|
||||
if ! unix2dos -q "$file"; then
|
||||
error "Failed to convert $file to DOS format!"
|
||||
@@ -962,6 +983,11 @@ updateImage() {
|
||||
local dat="${xml//.xml/.dat}"
|
||||
local desc path src wim name info
|
||||
|
||||
if disabled "$REBUILD"; then
|
||||
info "Skipping modifications to the installation image..."
|
||||
return 1
|
||||
fi
|
||||
|
||||
skipVersion "${DETECTED,,}" && return 0
|
||||
|
||||
if [ ! -s "$asset" ] || [ ! -f "$asset" ]; then
|
||||
@@ -1106,80 +1132,6 @@ removeImage() {
|
||||
return 0
|
||||
}
|
||||
|
||||
buildImage() {
|
||||
|
||||
local dir="$1"
|
||||
local failed=""
|
||||
local cat="BOOT.CAT"
|
||||
local log="/run/shm/iso.log"
|
||||
local base size desc
|
||||
|
||||
if [ -f "$BOOT" ]; then
|
||||
error "File $BOOT does already exist?!" && return 1
|
||||
fi
|
||||
|
||||
base=$(basename "$BOOT")
|
||||
local out="$TMP/${base%.*}.tmp"
|
||||
rm -f "$out"
|
||||
|
||||
desc=$(printVariant "$DETECTED" "ISO")
|
||||
|
||||
local msg="Building $desc image"
|
||||
info "$msg..." && html "$msg..."
|
||||
|
||||
[ -z "$LABEL" ] && LABEL="Windows"
|
||||
|
||||
if [ ! -f "$dir/$ETFS" ] || [ ! -s "$dir/$ETFS" ]; then
|
||||
error "Failed to locate file \"$ETFS\" in ISO image!" && return 1
|
||||
fi
|
||||
|
||||
size=$(du -b --max-depth=0 "$dir" | cut -f1)
|
||||
checkFreeSpace "$TMP" "$size" || return 1
|
||||
|
||||
/run/progress.sh "$out" "$size" "$msg ([P])..." &
|
||||
|
||||
if [[ "${BOOT_MODE,,}" != "windows_legacy" ]]; then
|
||||
|
||||
genisoimage -o "$out" -b "$ETFS" -no-emul-boot -c "$cat" -iso-level 4 -J -l -D -N -joliet-long -relaxed-filenames -V "${LABEL::30}" \
|
||||
-udf -boot-info-table -eltorito-alt-boot -eltorito-boot "$EFISYS" -no-emul-boot -allow-limited-size -quiet "$dir" 2> "$log" || failed="y"
|
||||
|
||||
else
|
||||
|
||||
case "${DETECTED,,}" in
|
||||
"win2k"* | "winxp"* | "win2003"* )
|
||||
genisoimage -o "$out" -b "$ETFS" -no-emul-boot -boot-load-seg 1984 -boot-load-size 4 -c "$cat" -iso-level 2 -J -l -D -N -joliet-long \
|
||||
-relaxed-filenames -V "${LABEL::30}" -quiet "$dir" 2> "$log" || failed="y" ;;
|
||||
"win9"* )
|
||||
genisoimage -o "$out" -b "$ETFS" -J -r -V "${LABEL::30}" -quiet "$dir" 2> "$log" || failed="y" ;;
|
||||
* )
|
||||
genisoimage -o "$out" -b "$ETFS" -no-emul-boot -c "$cat" -iso-level 2 -J -l -D -N -joliet-long -relaxed-filenames -V "${LABEL::30}" \
|
||||
-udf -allow-limited-size -quiet "$dir" 2> "$log" || failed="y" ;;
|
||||
esac
|
||||
|
||||
fi
|
||||
|
||||
fKill "progress.sh"
|
||||
|
||||
if [ -n "$failed" ]; then
|
||||
[ -s "$log" ] && echo "$(<"$log")"
|
||||
error "Failed to build image!" && return 1
|
||||
fi
|
||||
|
||||
local err=""
|
||||
local hide="Warning: creating filesystem that does not conform to ISO-9660."
|
||||
|
||||
[ -s "$log" ] && err="$(<"$log")"
|
||||
[[ "$err" != "$hide" ]] && echo "$err"
|
||||
|
||||
mv -f "$out" "$BOOT" || return 1
|
||||
|
||||
if ! setOwner "$BOOT"; then
|
||||
warn "Failed to set the owner for \"$BOOT\" !"
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
bootWindows() {
|
||||
|
||||
ARGS=$(readState "$STORAGE/windows.args") || return 1
|
||||
|
||||
Reference in New Issue
Block a user