source "azure-arm" "windows-x64" { // Authentication (from env vars or -var flags) client_id = var.client_id client_secret = var.client_secret subscription_id = var.subscription_id tenant_id = var.tenant_id // Source image — Windows Server 2019 Gen2 os_type = "Windows" image_publisher = "MicrosoftWindowsServer" image_offer = "WindowsServer" image_sku = "2019-datacenter-gensecond" image_version = "latest" // Build VM — only used during image creation, not for CI runners. // CI runner VM sizes are set in ci.mjs (azureVmSizes). // D4as_v7 (AMD): D4ds_v6 hit repeated AllocationFailed (no capacity for // that size in the region); Azure's allocation-guidance suggested this // size as an in-region alternative. Build-only VM, so the CPU vendor // doesn't affect the produced image. vm_size = "Standard_D4as_v7" // Use existing resource group instead of creating a temp one build_resource_group_name = var.resource_group os_disk_size_gb = 150 // Security security_type = "TrustedLaunch" secure_boot_enabled = true vtpm_enabled = true // Networking — Packer creates a temp VNet + public IP + NSG automatically. // WinRM needs the public IP to connect from CI runners. // WinRM communicator — Packer auto-configures via temp Key Vault communicator = "winrm" winrm_use_ssl = true winrm_insecure = true winrm_timeout = "15m" winrm_username = "packer" // Output — Managed Image (x64 supports this) // SIG replication to 27 regions takes longer than the 60m default; the // CreateOrUpdate poll was hitting "context deadline exceeded" at exactly 1h. shared_image_gallery_timeout = "3h" // Also publish to Compute Gallery shared_image_gallery_destination { subscription = var.subscription_id resource_group = var.gallery_resource_group gallery_name = var.gallery_name image_name = var.image_name != "" ? var.image_name : "windows-x64-2019-build-${var.build_number}" image_version = "1.0.0" // Premium_LRS: SSD-backed gallery storage — faster provisioning when // robobun launches runners from this image, and faster cross-region // replication during the publish step above. storage_account_type = "Premium_LRS" target_region { name = var.location } target_region { name = "australiaeast" } target_region { name = "brazilsouth" } target_region { name = "canadacentral" } target_region { name = "canadaeast" } target_region { name = "centralindia" } target_region { name = "centralus" } target_region { name = "francecentral" } target_region { name = "germanywestcentral" } target_region { name = "italynorth" } target_region { name = "japaneast" } target_region { name = "japanwest" } target_region { name = "koreacentral" } target_region { name = "mexicocentral" } target_region { name = "northcentralus" } target_region { name = "northeurope" } target_region { name = "southcentralus" } target_region { name = "southeastasia" } target_region { name = "spaincentral" } target_region { name = "swedencentral" } target_region { name = "switzerlandnorth" } target_region { name = "uaenorth" } target_region { name = "ukwest" } target_region { name = "westeurope" } target_region { name = "westus" } target_region { name = "westus2" } target_region { name = "westus3" } } azure_tags = { os = "windows" arch = "x64" build = var.build_number } } build { sources = ["source.azure-arm.windows-x64"] // Step 1: Run bootstrap — installs all build dependencies provisioner "powershell" { script = var.bootstrap_script valid_exit_codes = [0, 3010] environment_vars = ["CI=true", "BUN_BOOTSTRAP_REPO_REF=${var.repo_ref}"] } // Step 2: Upload agent.mjs provisioner "file" { source = var.agent_script destination = "C:\\buildkite-agent\\agent.mjs" } // Step 3: Install agent service via nssm provisioner "powershell" { inline = [ "C:\\Scoop\\apps\\nodejs\\current\\node.exe C:\\buildkite-agent\\agent.mjs install" ] valid_exit_codes = [0] } // Step 4: Reboot to clear pending updates (VS Build Tools, Windows Updates) provisioner "windows-restart" { restart_timeout = "10m" } // Step 5: Sysprep — MUST be last provisioner provisioner "powershell" { inline = [ "Remove-Item -Recurse -Force C:\\Windows\\Panther -ErrorAction SilentlyContinue", "Write-Output '>>> Clearing pending reboot flags...'", "Remove-Item 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Component Based Servicing\\RebootPending' -Recurse -Force -ErrorAction SilentlyContinue", "Remove-ItemProperty 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\WindowsUpdate\\Auto Update' -Name 'RebootRequired' -Force -ErrorAction SilentlyContinue", "Remove-Item 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\WindowsUpdate\\Auto Update\\RebootRequired' -Recurse -Force -ErrorAction SilentlyContinue", "Remove-ItemProperty 'HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Session Manager' -Name 'PendingFileRenameOperations' -Force -ErrorAction SilentlyContinue", "Write-Output '>>> Waiting for Azure Guest Agent...'", "while ((Get-Service RdAgent).Status -ne 'Running') { Start-Sleep -s 5 }", "while ((Get-Service WindowsAzureGuestAgent).Status -ne 'Running') { Start-Sleep -s 5 }", "Write-Output '>>> Running Sysprep...'", "$global:LASTEXITCODE = 0", "& $env:SystemRoot\\System32\\Sysprep\\Sysprep.exe /oobe /generalize /quiet /quit /mode:vm", "$timeout = 300; $elapsed = 0", "while ($true) {", " $imageState = (Get-ItemProperty HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Setup\\State).ImageState", " Write-Output \"ImageState: $imageState ($${elapsed}s)\"", " if ($imageState -eq 'IMAGE_STATE_GENERALIZE_RESEAL_TO_OOBE') { break }", " if ($elapsed -ge $timeout) {", " Write-Error \"Timed out after $${timeout}s -- stuck at $imageState\"", " Get-Content \"$env:SystemRoot\\System32\\Sysprep\\Panther\\setupact.log\" -Tail 100 -ErrorAction SilentlyContinue", " exit 1", " }", " Start-Sleep -s 10", " $elapsed += 10", "}", "Write-Output '>>> Sysprep complete.'" ] } }