diff --git a/Config.psm1 b/Config.psm1 index 8317b51..74e1ae8 100644 --- a/Config.psm1 +++ b/Config.psm1 @@ -115,12 +115,22 @@ function Get-AvailableConfigOptions { @{"Name" = "install_qemu_ga"; "GroupName" = "custom";"DefaultValue" = "False"; "Description" = "Installs QEMU guest agent services from the Fedora VirtIO website. Defaults to 'False' (no installation will be performed). - If set to 'True', the following MSI installer will be downloaded and installed: + If set to 'True', by default, the following MSI installer will be downloaded and installed: * for x86: https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-qemu-ga/qemu-ga-win-100.0.0.0-3.el7ev/qemu-ga-x86.msi * for x64: https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-qemu-ga/qemu-ga-win-100.0.0.0-3.el7ev/qemu-ga-x64.msi The value can be changed to a custom URL, to allow other QEMU guest agent versions to be installed. Note: QEMU guest agent requires VirtIO drivers to be present on the image. "}, + @{"Name" = "source"; "GroupName" = "virtio_qemu_guest_agent"; "DefaultValue" = "web"; + "Description" = "Source for QEMU Guest Agent installation. Options: + 'iso' - Extract from VirtIO ISO (requires virtio_iso_path to be set), + 'web' - Download from Internet (default behavior), + 'auto' - Try ISO first, fallback to web if extraction fails. + Default: 'web'"}, + @{"Name" = "url"; "GroupName" = "virtio_qemu_guest_agent"; + "Description" = "Custom URL for QEMU Guest Agent MSI installer. Must be used together with 'checksum' parameter for SHA256 verification."}, + @{"Name" = "checksum"; "GroupName" = "virtio_qemu_guest_agent"; + "Description" = "SHA256 checksum of the QEMU Guest Agent MSI installer. Must be used together with 'url' parameter."}, @{"Name" = "drivers_path"; "GroupName" = "drivers"; "Description" = "The location where additional drivers that are needed for the image are located."}, @{"Name" = "install_updates"; "GroupName" = "updates"; "DefaultValue" = $false; "AsBoolean" = $true; diff --git a/Examples/windows-image-config-example.ini b/Examples/windows-image-config-example.ini index e0a7aa4..dd4f8a1 100644 --- a/Examples/windows-image-config-example.ini +++ b/Examples/windows-image-config-example.ini @@ -142,6 +142,25 @@ drivers_path="" # The value can be changed to a custom URL, to allow other QEMU guest agent versions to be installed. # Note: QEMU guest agent requires VirtIO drivers to be present on the image. install_qemu_ga=False + +[virtio_qemu_guest_agent] +# Source for QEMU Guest Agent installation. Options: +# - 'iso': Extract from VirtIO ISO (requires virtio_iso_path to be set) +# - 'web': Download from Internet (default behavior) +# - 'auto': Try ISO first, fallback to web if extraction fails +# Default: 'web' +source=web +# Custom URL for QEMU Guest Agent MSI installer. +# Must be used together with 'checksum' parameter for SHA256 verification. +# Example: url=https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-qemu-ga/qemu-ga-win-VERSION/qemu-ga-x64.msi +url= +# SHA256 checksum of the QEMU Guest Agent MSI installer. +# Must be used together with 'url' parameter. +# To get the checksum on Windows: Get-FileHash -Path "qemu-ga-x64.msi" -Algorithm SHA256 +# To get the checksum on Linux/macOS: sha256sum qemu-ga-x64.msi +checksum= + +[custom] # Set a custom timezone for the Windows image. time_zone="" # Set custom ntp servers(space separated) for the Windows image diff --git a/README.md b/README.md index 56dd7ea..82c781b 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,173 @@ the resulting VHDX is shrinked to a minimum size and converted to the required f You can find a PowerShell example to generate a raw OpenStack Ironic image that also works on KVM
in `Examples/create-windows-online-cloud-image.ps1` +## QEMU Guest Agent Configuration + +### Overview + +The QEMU Guest Agent installation supports multiple configuration modes: + +- **Source selection**: Install from VirtIO ISO, web download, or automatic fallback +- **Checksum verification**: Optional SHA256 verification for enhanced security +- **Full backward compatibility**: All existing configurations continue to work + +### Installation Source Options + +The `source` parameter in the `[virtio_qemu_guest_agent]` section controls where the QEMU Guest Agent is obtained from: + +| Value | Description | Requires VirtIO ISO | Behavior | +|-------|-------------|---------------------|----------| +| `web` | Download from internet (default) | No | Downloads from fedorapeople.org | +| `iso` | Extract from VirtIO ISO only | **Yes** | Fails if ISO not available or MSI not found | +| `auto` | Try ISO first, fallback to web | No | Intelligent: uses ISO if available, otherwise downloads | + +### Configuration Examples + +#### 1. Default Installation (Simple) + +```ini +[custom] +install_qemu_ga=True +``` + +Uses the default version from the VirtIO archive (web download). + +#### 2. Extract from VirtIO ISO (Offline Mode) + +```ini +[drivers] +virtio_iso_path=/path/to/virtio-win.iso + +[custom] +install_qemu_ga=True + +[virtio_qemu_guest_agent] +source=iso +``` + +Extracts the QEMU Guest Agent from the VirtIO ISO. Useful for offline environments. + +#### 3. Automatic Mode (Recommended) + +```ini +[drivers] +virtio_iso_path=/path/to/virtio-win.iso + +[custom] +install_qemu_ga=True + +[virtio_qemu_guest_agent] +source=auto +``` + +Tries ISO extraction first, automatically falls back to web download if needed. + +#### 4. Secure Installation with Checksum + +```ini +[custom] +install_qemu_ga=True + +[virtio_qemu_guest_agent] +source=web +url=https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-qemu-ga/qemu-ga-win-VERSION/qemu-ga-x64.msi +checksum= +``` + +Downloads from a custom URL with SHA256 checksum verification. Both `url` and `checksum` must be specified together. + +#### 5. Legacy Custom URL + +```ini +[custom] +install_qemu_ga=https://example.com/custom-qemu-ga.msi +``` + +Downloads from a custom URL without checksum verification (backward compatibility). + +### Priority Order + +The system follows this priority order: + +1. **Custom URL + Checksum** (if both provided) → Always used, `source` is ignored +2. **Source-based installation**: + - `source=iso` → Extract from ISO only (error if fails) + - `source=auto` → Try ISO, fallback to web + - `source=web` → Download from internet +3. **Legacy behavior** (backward compatibility): + - `install_qemu_ga=True` → Default URL + - `install_qemu_ga=` → Custom URL without checksum + +### VirtIO ISO Structure + +When using `source=iso` or `source=auto`, the system searches for the QEMU Guest Agent MSI in these locations: + +- `guest-agent/qemu-ga-x86_64.msi` (for 64-bit) ✓ **Most common** +- `guest-agent/qemu-ga-i386.msi` (for 32-bit) ✓ **Most common** +- `guest-agent/qemu-ga-x64.msi` (alternative naming) +- `guest-agent/qemu-ga-x86.msi` (alternative naming) +- Additional fallback paths and recursive search + +The system automatically detects and uses the correct MSI file based on the image architecture. + +### Getting the SHA256 Checksum + +**Windows (PowerShell)**: +```powershell +Get-FileHash -Path "qemu-ga-x64.msi" -Algorithm SHA256 +``` + +**Linux/macOS**: +```bash +sha256sum qemu-ga-x64.msi +``` + +### Benefits + +- **Offline environments**: Use `source=iso` for air-gapped systems +- **Faster builds**: Avoid network downloads with local ISO +- **Version consistency**: Match guest agent with VirtIO drivers version +- **Security**: SHA256 checksum verification prevents tampering +- **Flexibility**: `source=auto` works both online and offline +- **Bandwidth savings**: Reuse ISO for multiple image builds + +### Best Practices + +1. **Use `source=auto` for flexibility**: Works both online and offline +2. **Use `source=iso` for strict offline environments**: Ensures no internet access is attempted +3. **Use `source=web` with checksum**: For maximum security when downloading +4. **Match versions**: Keep guest agent version consistent with VirtIO drivers + +### Technical Details + +**ISO Mounting Process**: +1. Creates a temporary backup copy of the ISO +2. Mounts the ISO using Windows VirtualDisk API +3. Searches for the QEMU Guest Agent MSI +4. Copies the MSI to the resources directory +5. Safely dismounts and cleans up the temporary ISO + +**Architecture Mapping**: + +| Windows Architecture | ISO Filename | +|---------------------|--------------| +| AMD64 (64-bit) | `qemu-ga-x86_64.msi` | +| x86 (32-bit) | `qemu-ga-i386.msi` | + +### Troubleshooting + +**Q: The build fails with "QEMU Guest Agent MSI not found in VirtIO ISO"** + +A: Your VirtIO ISO might have a different structure. Use `source=auto` to fall back to web download, or `source=web` to skip ISO extraction entirely. + +**Q: I want to force internet download even though I have an ISO** + +A: Set `source=web` in the configuration. + +**Q: How do I ensure version consistency between VirtIO drivers and guest agent?** + +A: Use `source=iso` to extract the guest agent from the same VirtIO ISO used for drivers. + ## Frequently Asked Questions (FAQ) ### The image generation never stops diff --git a/WinImageBuilder.psm1 b/WinImageBuilder.psm1 index adb7cca..d780ddf 100755 --- a/WinImageBuilder.psm1 +++ b/WinImageBuilder.psm1 @@ -563,31 +563,219 @@ function Download-CloudbaseInit { } } -function Download-QemuGuestAgent { +function Copy-QemuGuestAgentFromISO { Param( [Parameter(Mandatory=$true)] - [string]$QemuGuestAgentConfig, + [string]$IsoPath, [Parameter(Mandatory=$true)] [string]$ResourcesDir, [Parameter(Mandatory=$true)] [string]$OsArch ) - $QemuGuestAgentUrl = $QemuGuestAgentConfig - if ($QemuGuestAgentConfig -eq 'True') { - $arch = "x86" - if ($OsArch -eq "AMD64") { - $arch = "x64" + Write-Log "Extracting QEMU Guest Agent from VirtIO ISO: $IsoPath..." + + # Map architecture to ISO file naming convention + $arch = "i386" + $archAlt = "x86" + if ($OsArch -eq "AMD64") { + $arch = "x86_64" + $archAlt = "x64" + } + + $isoPathBak = $IsoPath + (Get-Random) + ".iso" + Copy-Item $IsoPath $isoPathBak -Force + Write-Log "Using backed up ISO for safe dismount." + $IsoPath = $isoPathBak + + $v = [WIMInterop.VirtualDisk]::OpenVirtualDisk($IsoPath) + try { + if (Is-IsoFile $IsoPath) { + $v.AttachVirtualDisk() + Get-PSDrive | Out-Null + $devicePath = $v.GetVirtualDiskPhysicalPath() + $isoDriveLetter = Execute-Retry { + $res = (Get-DiskImage -DevicePath $devicePath | Get-Volume).DriveLetter + if (!$res) { + throw "Failed to mount ISO ${IsoPath}" + } + return $res + } + $isoDriveLetter += ":" + + Write-Log "Searching for QEMU Guest Agent in VirtIO ISO at ${isoDriveLetter}..." + + # List root directory content for debugging + Write-Log "Listing ISO root directory content:" + try { + $rootItems = Get-ChildItem -Path "${isoDriveLetter}\" -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Name + Write-Log "Root items: $($rootItems -join ', ')" + } catch { + Write-Log "Could not list root directory: $_" + } + + # Check if guest-agent directory exists and list its content + if (Test-Path "${isoDriveLetter}\guest-agent") { + Write-Log "guest-agent directory found, listing content:" + try { + $gaItems = Get-ChildItem -Path "${isoDriveLetter}\guest-agent" -Recurse -ErrorAction SilentlyContinue | Select-Object -ExpandProperty FullName + foreach ($item in $gaItems) { + Write-Log " - $item" + } + } catch { + Write-Log "Could not list guest-agent directory: $_" + } + } + + # Try multiple possible paths with both naming conventions + $possiblePaths = @( + "${isoDriveLetter}\guest-agent\qemu-ga-${arch}.msi", + "${isoDriveLetter}\guest-agent\qemu-ga-${archAlt}.msi", + "${isoDriveLetter}\guest-agent\qemu-ga-${arch}\qemu-ga-${arch}.msi", + "${isoDriveLetter}\guest-agent\${arch}\qemu-ga-${arch}.msi", + "${isoDriveLetter}\qemu-ga\qemu-ga-${arch}.msi", + "${isoDriveLetter}\qemu-ga-${arch}.msi", + "${isoDriveLetter}\${arch}\qemu-ga-${arch}.msi" + ) + + # Also try to find any MSI file containing "qemu-ga" and the architecture + Write-Log "Searching for QEMU Guest Agent MSI files..." + $qemuGaMsiPath = $null + foreach ($path in $possiblePaths) { + Write-Log "Checking path: $path" + if (Test-Path $path) { + $qemuGaMsiPath = $path + Write-Log "Found QEMU Guest Agent at: $path" + break + } + } + + # If not found in standard paths, try to search recursively + if (!$qemuGaMsiPath) { + Write-Log "Not found in standard paths, searching recursively..." + try { + $foundFiles = Get-ChildItem -Path "${isoDriveLetter}\" -Recurse -Filter "*qemu-ga*${arch}*.msi" -ErrorAction SilentlyContinue + if ($foundFiles) { + $qemuGaMsiPath = $foundFiles[0].FullName + Write-Log "Found QEMU Guest Agent via recursive search at: $qemuGaMsiPath" + } + } catch { + Write-Log "Recursive search failed: $_" + } + } + + if (!$qemuGaMsiPath) { + throw "QEMU Guest Agent MSI not found in VirtIO ISO for architecture: $arch. Searched paths: $($possiblePaths -join ', '). Please check the ISO structure and update the search paths if needed." + } + + $dst = Join-Path $ResourcesDir "qemu-ga.msi" + Copy-Item -Path $qemuGaMsiPath -Destination $dst -Force + Write-Log "QEMU Guest Agent copied successfully to: $dst" + + } else { + throw "The $IsoPath is not a valid ISO path." } - $QemuGuestAgentUrl = "https://fedorapeople.org/groups/virt/virtio-win/direct-downloads" + ` - "/archive-qemu-ga/qemu-ga-win-100.0.0.0-3.el7ev/qemu-ga-{0}.msi" -f $arch + } catch { + throw $_ + } finally { + if ($v) { + $v.DetachVirtualDisk() + $v.Close() + } + Remove-Item -Force $IsoPath } +} - Write-Log "Downloading QEMU guest agent installer from ${QemuGuestAgentUrl} ..." - $dst = Join-Path $ResourcesDir "qemu-ga.msi" - Execute-Retry { - (New-Object System.Net.WebClient).DownloadFile($QemuGuestAgentUrl, $dst) +function Download-QemuGuestAgent { + Param( + [Parameter(Mandatory=$true)] + [string]$QemuGuestAgentConfig, + [Parameter(Mandatory=$true)] + [string]$ResourcesDir, + [Parameter(Mandatory=$true)] + [string]$OsArch, + [Parameter(Mandatory=$false)] + [string]$CustomUrl, + [Parameter(Mandatory=$false)] + [string]$Checksum, + [Parameter(Mandatory=$false)] + [string]$Source = "web", + [Parameter(Mandatory=$false)] + [string]$VirtioIsoPath + ) + + $useCustomConfig = $false + + # Priority 1: Check if custom URL and checksum are provided (highest priority) + if ($CustomUrl -and $Checksum) { + $useCustomConfig = $true + Write-Log "Using custom QEMU Guest Agent configuration with checksum verification" + Write-Log "Downloading QEMU guest agent installer from ${CustomUrl} ..." + $dst = Join-Path $ResourcesDir "qemu-ga.msi" + Execute-Retry { + (New-Object System.Net.WebClient).DownloadFile($CustomUrl, $dst) + } + + # Verify SHA256 checksum + Write-Log "Verifying SHA256 checksum..." + $fileHash = (Get-FileHash -Path $dst -Algorithm SHA256).Hash + if ($fileHash -ne $Checksum) { + throw "SHA256 checksum verification failed. Expected: $Checksum, Got: $fileHash" + } + Write-Log "SHA256 checksum verification successful" + } + + # Priority 2: Source-based installation (iso/web/auto) + if (!$useCustomConfig) { + $useSourceConfig = $false + + # Check if source is set to 'iso' or 'auto' + if ($Source -eq "iso" -or $Source -eq "auto") { + if ($VirtioIsoPath -and (Test-Path $VirtioIsoPath)) { + Write-Log "Source set to '$Source': Attempting to extract QEMU Guest Agent from VirtIO ISO" + try { + Copy-QemuGuestAgentFromISO -IsoPath $VirtioIsoPath -ResourcesDir $ResourcesDir -OsArch $OsArch + $useSourceConfig = $true + } catch { + if ($Source -eq "iso") { + # If source is explicitly 'iso', fail with error + throw "Failed to extract QEMU Guest Agent from VirtIO ISO: $_" + } else { + # If source is 'auto', log the error and fall back to web download + Write-Log "Failed to extract QEMU Guest Agent from VirtIO ISO: $_" + Write-Log "Falling back to web download..." + } + } + } else { + if ($Source -eq "iso") { + throw "Source is set to 'iso' but VirtIO ISO path is not provided or does not exist: $VirtioIsoPath" + } else { + Write-Log "VirtIO ISO not available, falling back to web download" + } + } + } + + # Priority 3: Use web download (legacy behavior or fallback from 'auto') + if (!$useSourceConfig) { + Write-Log "Using web download for QEMU Guest Agent" + $QemuGuestAgentUrl = $QemuGuestAgentConfig + if ($QemuGuestAgentConfig -eq 'True') { + $arch = "x86" + if ($OsArch -eq "AMD64") { + $arch = "x64" + } + $QemuGuestAgentUrl = "https://fedorapeople.org/groups/virt/virtio-win/direct-downloads" + ` + "/archive-qemu-ga/qemu-ga-win-100.0.0.0-3.el7ev/qemu-ga-{0}.msi" -f $arch + } + + Write-Log "Downloading QEMU guest agent installer from ${QemuGuestAgentUrl} ..." + $dst = Join-Path $ResourcesDir "qemu-ga.msi" + Execute-Retry { + (New-Object System.Net.WebClient).DownloadFile($QemuGuestAgentUrl, $dst) + } + } } + Write-Log "QEMU guest agent installer path is: $dst" } @@ -1712,7 +1900,9 @@ function New-WindowsCloudImage { } if ($windowsImageConfig.install_qemu_ga -and $windowsImageConfig.install_qemu_ga -ne 'False') { Download-QemuGuestAgent -QemuGuestAgentConfig $windowsImageConfig.install_qemu_ga ` - -ResourcesDir $resourcesDir -OsArch ([string]$image.ImageArchitecture) + -ResourcesDir $resourcesDir -OsArch ([string]$image.ImageArchitecture) ` + -CustomUrl $windowsImageConfig.url -Checksum $windowsImageConfig.checksum ` + -Source $windowsImageConfig.source -VirtioIsoPath $windowsImageConfig.virtio_iso_path } Download-CloudbaseInit -resourcesDir $resourcesDir -osArch ([string]$image.ImageArchitecture) ` -BetaRelease:$windowsImageConfig.beta_release -MsiPath $windowsImageConfig.msi_path ` @@ -1893,7 +2083,9 @@ function New-WindowsFromGoldenImage { } if ($windowsImageConfig.install_qemu_ga -and $windowsImageConfig.install_qemu_ga -ne 'False') { Download-QemuGuestAgent -QemuGuestAgentConfig $windowsImageConfig.install_qemu_ga ` - -ResourcesDir $resourcesDir -OsArch $imageInfo.imageArchitecture + -ResourcesDir $resourcesDir -OsArch $imageInfo.imageArchitecture ` + -CustomUrl $windowsImageConfig.url -Checksum $windowsImageConfig.checksum ` + -Source $windowsImageConfig.source -VirtioIsoPath $windowsImageConfig.virtio_iso_path } Download-CloudbaseInit -resourcesDir $resourcesDir -osArch $imageInfo.imageArchitecture ` -BetaRelease:$windowsImageConfig.beta_release -MsiPath $windowsImageConfig.msi_path `