diff --git a/images.CI/download-repo.ps1 b/images.CI/download-repo.ps1 deleted file mode 100644 index 14374702..00000000 --- a/images.CI/download-repo.ps1 +++ /dev/null @@ -1,14 +0,0 @@ -param( - [String] [Parameter (Mandatory=$true)] $RepoUrl, - [String] [Parameter (Mandatory=$true)] $RepoBranch -) - -Write-Host "Clean up default repository" -Remove-Item -path './*' -Recurse -Force - -Write-Host "Download ${RepoBranch} branch from ${RepoUrl}" -$env:GIT_REDIRECT_STDERR = '2>&1' -git clone $RepoUrl . -b $RepoBranch --single-branch --depth 1 - -Write-Host "Latest commit:" -git --no-pager log --pretty=format:"Date: %cd; Commit: %H - %s; Author: %an <%ae>" -1 \ No newline at end of file diff --git a/images.CI/linux-and-win/azure-pipelines/image-generation.yml b/images.CI/linux-and-win/azure-pipelines/image-generation.yml deleted file mode 100644 index 62af3d3e..00000000 --- a/images.CI/linux-and-win/azure-pipelines/image-generation.yml +++ /dev/null @@ -1,181 +0,0 @@ -# Ideally we would use GitHub Actions for this, but since we use self-hosted machines to run image builds -# we need the following features to use GitHub Actions for Images CI: -# - https://github.community/t5/GitHub-Actions/Make-secrets-available-to-builds-of-forks/m-p/30678#M508 -# - https://github.community/t5/GitHub-Actions/GitHub-Actions-Manual-Trigger-Approvals/td-p/31504 -# - https://github.community/t5/GitHub-Actions/Protecting-github-workflows/td-p/30290 - -parameters: - - name: job_id - type: string - default: 'generate_image' - - - name: image_type - type: string - - - name: image_template_name - type: string - - - name: image_readme_name - type: string - - - name: agent_pool - type: object - default: - name: 'ci-agent-pool' - - - name: variable_group_name - type: string - default: 'Image Generation Variables' - - - name: create_release - type: boolean - default: true - - - name: repository_ref - type: string - default: 'self' - -jobs: -- job: ${{ parameters.job_id }} - displayName: Image Generation (${{ parameters.image_type }}) - timeoutInMinutes: 600 - cancelTimeoutInMinutes: 30 - pool: ${{ parameters.agent_pool }} - variables: - - group: ${{ parameters.variable_group_name }} - - steps: - - checkout: ${{ parameters.repository_ref }} - clean: true - fetchDepth: 0 - fetchTags: false - - - task: PowerShell@2 - displayName: 'Download custom repository' - condition: and(ne(variables['CUSTOM_REPOSITORY_URL'], ''), ne(variables['CUSTOM_REPOSITORY_BRANCH'], '')) - inputs: - targetType: 'filePath' - filePath: ./images.CI/download-repo.ps1 - arguments: -RepoUrl $(CUSTOM_REPOSITORY_URL) ` - -RepoBranch $(CUSTOM_REPOSITORY_BRANCH) - - - task: AzureCLI@2 - displayName: 'Set variables' - inputs: - azureSubscription: 'spn-hosted-runners' - scriptType: 'pscore' - scriptLocation: 'inlineScript' - inlineScript: | - $ImageType = "${{ parameters.image_type }}" - $TemplateDirectoryName = if ($ImageType.StartsWith("ubuntu")) { "ubuntu/templates" } else { "windows/templates" } - $TemplateDirectoryPath = Join-Path "images" $TemplateDirectoryName | Resolve-Path - - $TemplateFileName = "${{ parameters.image_template_name }}" - $TemplatePath = Join-Path $TemplateDirectoryPath $TemplateFileName - Write-Host "##vso[task.setvariable variable=TemplateDirectoryPath;]$TemplateDirectoryPath" - Write-Host "##vso[task.setvariable variable=TemplatePath;]$TemplatePath" - - $ManagedImageName = "${{ parameters.image_type }}-$(Build.BuildId)" - Write-Host "##vso[task.setvariable variable=ManagedImageName;]$ManagedImageName" - - $TempResourceGroupName = "packer-temp-$ManagedImageName" - Write-Host "##vso[task.setvariable variable=TempResourceGroupName;]$TempResourceGroupName" - - $clientSecret = $(az keyvault secret show --name "spnhostedrunners" --vault-name "gh-imagegeneration" --query value -o tsv) - Write-Host "##vso[task.setvariable variable=ClientSecret;issecret=true]$clientSecret" - - - task: PowerShell@2 - displayName: 'Build VM' - inputs: - targetType: filePath - filePath: ./images.CI/linux-and-win/build-image.ps1 - arguments: -ClientId $(CLIENT_ID) ` - -ClientSecret "$(ClientSecret)" ` - -TemplatePath $(TemplatePath) ` - -ImageName "$(ManagedImageName)" ` - -ImageResourceGroupName $(AZURE_RESOURCE_GROUP) ` - -TempResourceGroupName "$(TempResourceGroupName)" ` - -SubscriptionId $(AZURE_SUBSCRIPTION) ` - -TenantId $(AZURE_TENANT) ` - -Location $(AZURE_LOCATION) ` - -VirtualNetworkName $(BUILD_AGENT_VNET_NAME) ` - -VirtualNetworkRG $(BUILD_AGENT_VNET_RESOURCE_GROUP) ` - -VirtualNetworkSubnet $(BUILD_AGENT_SUBNET_NAME) - - env: - PACKER_LOG: 1 - PACKER_LOG_PATH: "$(Agent.TempDirectory)/packer-log.txt" - - - task: PowerShell@2 - displayName: 'Copy image artifacts to the separate directory' - inputs: - targetType: 'inline' - script: | - $ImageType = "${{ parameters.image_type }}" - $rootDirectoryName = if ($ImageType.StartsWith("ubuntu")) { "ubuntu" } else { "windows" } - $rootDirectoryPath = Join-Path "images" $rootDirectoryName | Resolve-Path - - $readmePath = Join-Path $rootDirectoryPath "${{ parameters.image_readme_name }}" - $softwareReportPath = Join-Path $rootDirectoryPath "software-report.json" - - Copy-Item -Path $readmePath -Destination "$(Build.ArtifactStagingDirectory)/" - if (Test-Path $softwareReportPath) { - Copy-Item -Path $softwareReportPath -Destination "$(Build.ArtifactStagingDirectory)/" - } - - - task: PowerShell@2 - displayName: 'Print markdown software report' - inputs: - targetType: 'inline' - script: | - Get-Content -Path "$(Build.ArtifactStagingDirectory)/${{ parameters.image_readme_name }}" - - - task: PowerShell@2 - displayName: 'Print json software report' - inputs: - targetType: 'inline' - script: | - $softwareReportPath = "$(Build.ArtifactStagingDirectory)/software-report.json" - if (Test-Path $softwareReportPath) { - Get-Content -Path $softwareReportPath - } - - - task: PublishBuildArtifacts@1 - inputs: - ArtifactName: 'Built_VM_Artifacts' - displayName: Publish Artifacts - - - task: PowerShell@2 - displayName: 'Print provisioners duration' - inputs: - targetType: 'filePath' - filePath: ./images.CI/measure-provisioners-duration.ps1 - arguments: -PackerLogPath "$(Agent.TempDirectory)/packer-log.txt" ` - -PrefixToPathTrim "$(TemplateDirectoryPath)" ` - -PrintTopNLongest 25 - - - ${{ if eq(parameters.create_release, true) }}: - - task: PowerShell@2 - displayName: 'Create release for VM deployment' - inputs: - targetType: filePath - filePath: ./images.CI/linux-and-win/create-release.ps1 - arguments: -BuildId $(Build.BuildId) ` - -Organization $(RELEASE_TARGET_ORGANIZATION) ` - -DefinitionId $(RELEASE_TARGET_DEFINITION_ID) ` - -Project $(RELEASE_TARGET_PROJECT) ` - -ImageType "${{ parameters.image_type }}" ` - -ManagedImageName "$(ManagedImageName)" ` - -AccessToken $(RELEASE_TARGET_TOKEN) - - - task: PowerShell@2 - displayName: 'Clean up resources' - condition: always() - inputs: - targetType: filePath - filePath: ./images.CI/linux-and-win/cleanup.ps1 - arguments: -TempResourceGroupName "$(TempResourceGroupName)" ` - -SubscriptionId $(AZURE_SUBSCRIPTION) ` - -ClientId $(CLIENT_ID) ` - -ClientSecret "$(ClientSecret)" ` - -TenantId $(AZURE_TENANT) diff --git a/images.CI/linux-and-win/azure-pipelines/ubuntu2004.yml b/images.CI/linux-and-win/azure-pipelines/ubuntu2004.yml deleted file mode 100644 index 948e7640..00000000 --- a/images.CI/linux-and-win/azure-pipelines/ubuntu2004.yml +++ /dev/null @@ -1,21 +0,0 @@ -schedules: -- cron: "0 0 * * *" - displayName: Daily - branches: - include: - - main - always: true - -trigger: none -pr: - autoCancel: true - branches: - include: - - main - -jobs: -- template: image-generation.yml - parameters: - image_type: ubuntu2004 - image_readme_name: Ubuntu2004-Readme.md - image_template_name: ubuntu-20.04.pkr.hcl diff --git a/images.CI/linux-and-win/azure-pipelines/ubuntu2204.yml b/images.CI/linux-and-win/azure-pipelines/ubuntu2204.yml deleted file mode 100644 index b9e12815..00000000 --- a/images.CI/linux-and-win/azure-pipelines/ubuntu2204.yml +++ /dev/null @@ -1,21 +0,0 @@ -schedules: -- cron: "0 0 * * *" - displayName: Daily - branches: - include: - - main - always: true - -trigger: none -pr: - autoCancel: true - branches: - include: - - main - -jobs: -- template: image-generation.yml - parameters: - image_type: ubuntu2204 - image_readme_name: Ubuntu2204-Readme.md - image_template_name: ubuntu-22.04.pkr.hcl diff --git a/images.CI/linux-and-win/azure-pipelines/ubuntu2404.yml b/images.CI/linux-and-win/azure-pipelines/ubuntu2404.yml deleted file mode 100644 index 5856d31b..00000000 --- a/images.CI/linux-and-win/azure-pipelines/ubuntu2404.yml +++ /dev/null @@ -1,21 +0,0 @@ -schedules: -- cron: "0 0 * * *" - displayName: Daily - branches: - include: - - main - always: true - -trigger: none -pr: - autoCancel: true - branches: - include: - - main - -jobs: -- template: image-generation.yml - parameters: - image_type: ubuntu2404 - image_readme_name: Ubuntu2404-Readme.md - image_template_name: ubuntu-24.04.pkr.hcl diff --git a/images.CI/linux-and-win/azure-pipelines/windows2019.yml b/images.CI/linux-and-win/azure-pipelines/windows2019.yml deleted file mode 100644 index c8bb7f94..00000000 --- a/images.CI/linux-and-win/azure-pipelines/windows2019.yml +++ /dev/null @@ -1,21 +0,0 @@ -schedules: -- cron: "0 0 * * *" - displayName: Daily - branches: - include: - - main - always: true - -trigger: none -pr: - autoCancel: true - branches: - include: - - main - -jobs: -- template: image-generation.yml - parameters: - image_type: windows2019 - image_readme_name: Windows2019-Readme.md - image_template_name: windows-2019.pkr.hcl diff --git a/images.CI/linux-and-win/azure-pipelines/windows2022.yml b/images.CI/linux-and-win/azure-pipelines/windows2022.yml deleted file mode 100644 index b24e1d48..00000000 --- a/images.CI/linux-and-win/azure-pipelines/windows2022.yml +++ /dev/null @@ -1,21 +0,0 @@ -schedules: -- cron: "0 0 * * *" - displayName: Daily - branches: - include: - - main - always: true - -trigger: none -pr: - autoCancel: true - branches: - include: - - main - -jobs: -- template: image-generation.yml - parameters: - image_type: windows2022 - image_readme_name: Windows2022-Readme.md - image_template_name: windows-2022.pkr.hcl diff --git a/images.CI/macos/anka/Anka.Helpers.psm1 b/images.CI/macos/anka/Anka.Helpers.psm1 deleted file mode 100644 index f566e8c2..00000000 --- a/images.CI/macos/anka/Anka.Helpers.psm1 +++ /dev/null @@ -1,248 +0,0 @@ -function Push-AnkaTemplateToRegistry { - param ( - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] $RegistryUrl, - - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] $TagName, - - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] $TemplateName - ) - - # if registry uuid doesn't match then delete an image in registry - $AnkaCaCrtPath="$HOME/.config/anka/certs/anka-ca-crt.pem" - $images = anka --machine-readable registry --cacert $AnkaCaCrtPath --registry-path $RegistryUrl list | ConvertFrom-Json | ForEach-Object body - $images | Where-Object name -eq $TemplateName | ForEach-Object { - $id = $_.uuid - Show-StringWithFormat "Deleting '$TemplateName[$id]' VM and '$TagName' tag" - $curlCommand='curl -s -X DELETE -k "{0}/registry/vm?id={1}"' -f $RegistryUrl, $id - Invoke-AnkaCommand -Command $curlCommand - } - - $command = "anka registry --cacert $AnkaCaCrtPath --registry-path $RegistryUrl push --force --tag $TagName $TemplateName" - Invoke-AnkaCommand -Command $command -} - -function Get-AnkaVM { - param( - [string] $VMName - ) - - $command = "anka --machine-readable list" - if (-not [string]::IsNullOrEmpty($VMName)) { - $command = "anka --machine-readable show $VMName" - } - Invoke-AnkaCommand -Command $command | ConvertFrom-Json | Foreach-Object body -} - -function Get-AnkaVMStatus { - param( - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] $VMName - ) - - $command = "anka --machine-readable list $VMName" - Invoke-AnkaCommand -Command $command | ConvertFrom-Json | Foreach-Object { $_.body.status } -} - -function Get-AnkaVMIPAddress { - param( - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] $VMName - ) - - Get-AnkaVM -VMName $VMName | Foreach-Object ip -} - -function Invoke-AnkaCommand { - param ( - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] $Command - ) - - $result = bash -c "$Command 2>&1" - if ($LASTEXITCODE -ne 0) { - Write-Error "There is an error during command execution:`n$result" - exit 1 - } - $result -} - -function New-AnkaVMTemplate { - param ( - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] $InstallerPath, - - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] $TemplateName, - - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] $TemplateUsername, - - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] $TemplatePassword, - - [Parameter(Mandatory)] - [int] $CPUCount, - - [Parameter(Mandatory)] - [int] $RamSizeGb, - - [Parameter(Mandatory)] - [int] $DiskSizeGb - ) - - $env:ANKA_DEFAULT_USER = $TemplateUsername - $env:ANKA_DEFAULT_PASSWD = $TemplatePassword - $env:ANKA_CREATE_SUSPEND = 0 - $command = "anka create --cpu-count '$CPUCount' --ram-size '${RamSizeGb}G' --disk-size '${DiskSizeGb}G' --app '$InstallerPath' $TemplateName" - Invoke-AnkaCommand -Command $command -} - -function Remove-AnkaVM { - param( - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] $VMName - ) - - $command = "anka delete $VMName --yes" - $isTemplateExists = Get-AnkaVM | Where-Object name -eq $VMName - if ($isTemplateExists) { - $null = Invoke-AnkaCommand -Command $command - } -} - -function Set-AnkaVMVideoController { - param( - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] $VMName, - - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] $ShortMacOSVersion, - - [ValidateSet("fbuf", "pg")] - [string] $Controller = "pg" - ) - - $command = "anka modify $VMName set display -c $Controller" - $null = Invoke-AnkaCommand -Command $command -} - -function Set-AnkaVMDisplayResolution { - param ( - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] $VMName, - - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] $DisplayResolution - ) - - $command = "anka modify $VMName set display -r $DisplayResolution" - $null = Invoke-AnkaCommand -Command $command -} - -function Start-AnkaVM { - param( - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] $VMName - ) - - $command = "anka start $VMName" - $vmStatus = Get-AnkaVMStatus -VMName $VMName - if ($vmStatus -eq "stopped") { - $null = Invoke-AnkaCommand -Command $command - } -} - -function Stop-AnkaVM { - param( - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] $VMName - ) - - $command = "anka stop $VMName" - $vmStatus = Get-AnkaVMStatus -VMName $VMName - if ($vmStatus -eq "running") { - $null = Invoke-AnkaCommand -Command $command - } -} - -function Wait-AnkaVMIPAddress { - param( - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] $VMName, - - [int] $RetryCount = 20, - [int] $Seconds = 60 - ) - - $condition = { - $vmStatus = Get-AnkaVMStatus -VMName $VMName - if ($vmStatus -eq "failed") { - Write-Host "`t [-] $VMName is in failed status" - exit 1 - } - Get-AnkaVMIPAddress -VMName $VMName - } - $null = Invoke-WithRetry -BreakCondition $condition -RetryCount $RetryCount -Seconds $Seconds -} - -function Wait-AnkaVMSSHService { - param ( - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] $VMName, - - [int] $RetryCount = 20, - [int] $Seconds = 60 - ) - - Start-Sleep -Seconds $Seconds - Write-Host "`t[*] Waiting for '$VMName' VM to get an IP address" - Wait-AnkaVMIPAddress -VMName $VMName -RetryCount $RetryCount -Seconds $Seconds - - $ipAddress = Get-AnkaVMIPAddress -VMName $VMName - Write-Host "`t[*] The '$ipAddress' IP address for '$VMName' VM" - - Write-Host "`t[*] Checking if SSH on a port is open" - $isSSHPortOpen = Test-SSHPort -IPAddress $ipAddress - if (-not $isSSHPortOpen) { - Write-Host "`t[x] SSH port is closed" - exit 1 - } -} - -function Set-AnkaVMUuid { - param( - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] $VMName, - - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] $Uuid - ) - - $command = "anka modify $VMName set custom-variable hw.uuid $Uuid" - Write-Host "`t[*] Setting $VMName uuid to $Uuid" - Invoke-AnkaCommand -Command $command -} diff --git a/images.CI/macos/anka/CreateCleanAnkaTemplate.ps1 b/images.CI/macos/anka/CreateCleanAnkaTemplate.ps1 deleted file mode 100644 index 4d7e545e..00000000 --- a/images.CI/macos/anka/CreateCleanAnkaTemplate.ps1 +++ /dev/null @@ -1,227 +0,0 @@ -[CmdletBinding()] -param( - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [version] $MacOSVersion, - - [ValidateNotNullOrEmpty()] - [string] $TemplateUsername, - - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] $TemplatePassword, - - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] $RegistryUrl, - - [ValidateNotNullOrEmpty()] - [string] $TemplateName, - - [bool] $DownloadLatestVersion = $true, - [bool] $PushToRegistry = $true, - [bool] $BetaSearch = $false, - [bool] $InstallSoftwareUpdate = $true, - [bool] $EnableAutoLogon = $true, - [int] $CPUCount = 6, - [int] $RamSizeGb = 7, - [int] $DiskSizeGb = 325, - [string] $DisplayResolution = "1920x1080", - [string] $TagName = [DateTimeOffset]::Now.ToUnixTimeSeconds(), - [string] $Uuid = "4203018E-580F-C1B5-9525-B745CECA79EB" -) - -$ErrorActionPreference = "Stop" -$WarningPreference = "SilentlyContinue" - -# Import helper modules -Import-Module "$PSScriptRoot/Anka.Helpers.psm1" -Import-Module "$PSScriptRoot/Service.Helpers.psm1" - -# Helper functions -function Invoke-EnableAutoLogon { - if (-not $EnableAutoLogon) { - Write-Host "`t[*] Skip configuring AutoLogon" - return - } - - $ipAddress = Get-AnkaVMIPAddress -VMName $TemplateName - - Wait-AnkaVMSSHService -VMName $TemplateName -Seconds 30 - - Write-Host "`t[*] Enable AutoLogon" - Enable-AutoLogon -HostName $ipAddress -UserName $TemplateUsername -Password $TemplatePassword - - Write-Host "`t[*] Reboot '$TemplateName' VM to enable AutoLogon" - Restart-VMSSH -HostName $ipAddress | Show-StringWithFormat - - Wait-AnkaVMSSHService -VMName $TemplateName -Seconds 30 - - Write-Host "`t[*] Checking if AutoLogon is enabled" - Test-AutoLogon -VMName $TemplateName -UserName $TemplateUsername -} - -function Invoke-SoftwareUpdate { - param ( - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] $Password - ) - - if (-not $InstallSoftwareUpdate) { - Write-Host "`t[*] Skip installing software updates" - return - } - - $ipAddress = Get-AnkaVMIPAddress -VMName $TemplateName - - # Unenroll Seed - Write-Host "`t[*] Resetting the seed before requesting stable versions" - Remove-CurrentBetaSeed -HostName $ipAddress | Show-StringWithFormat - - # Install Software Updates - # Security updates may not be able to install(hang, freeze) when AutoLogon is turned off - Write-Host "`t[*] Finding available software" - $newUpdates = Get-SoftwareUpdate -HostName $ipAddress - - if (-not $newUpdates) { - Write-Host "`t[*] No Updates Available" - return - } - - # Define the next macOS version - $command = "sw_vers" - $guestMacosVersion = Invoke-SSHPassCommand -HostName $ipAddress -Command $command - switch -regex ($guestMacosVersion[1]) { - '12.\d' { $nextOSVersion = 'macOS Ventura|macOS Sonoma|macOS Sequoia' } - '13.\d' { $nextOSVersion = 'macOS Sonoma|macOS Sequoia' } - '14.\d' { $nextOSVersion = 'macOS Sequoia' } - } - - Write-Host "`t[*] Fetching Software Updates ready to install on '$TemplateName' VM:" - Show-StringWithFormat $newUpdates - $listOfNewUpdates = $($($newUpdates.Split("*")).Split("Title").where({$_ -match "Label:"}).Replace("Label: ", '').where({$_ -notmatch $nextOSVersion})) - Write-Host "`t[*] Installing Software Updates on '$TemplateName' VM:" - Install-SoftwareUpdate -HostName $ipAddress -listOfUpdates $listOfNewUpdates -Password $Password | Show-StringWithFormat - Write-Host "`t[*] Sleep 60 seconds before the software updates have been installed" - Start-Sleep -Seconds 60 - Write-Host "`t[*] Waiting for loginwindow process" - Wait-LoginWindow -HostName $ipAddress | Show-StringWithFormat - # Re-enable AutoLogon after installing a new security software update - Invoke-EnableAutoLogon - - foreach ($newupdate in $listOfNewUpdates) { - # Check software updates have been installed - $updates = Get-SoftwareUpdate -HostName $ipAddress - if ($updates.Contains("Action: restart") -and !($updates -match $nextOSVersion)) { - Write-Host "`t[x] Software updates failed to install: " - Show-StringWithFormat $updates - exit 1 - } - } - - Write-Host "`t[*] Show the install history:" - $hUpdates = Get-SoftwareUpdateHistory -HostName $ipAddress - Show-StringWithFormat $hUpdates - - Write-Host "`t[*] The current macOS version:" - $command = "sw_vers" - Invoke-SSHPassCommand -HostName $ipAddress -Command $command | Show-StringWithFormat -} - - -function Invoke-UpdateSettings { - param ( - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] $Password - ) - $isConfRequired = $InstallSoftwareUpdate -or $EnableAutoLogon - if (-not $isConfRequired) { - Write-Host "`t[*] Skip additional configuration" - return - } - - Write-Host "`t[*] Starting '$TemplateName' VM" - Start-AnkaVM -VMName $TemplateName - - Write-Host "`t[*] Waiting for SSH service on '$TemplateName' VM" - Wait-AnkaVMSSHService -VMName $TemplateName -Seconds 30 - - # Configure AutoLogon - Invoke-EnableAutoLogon - - # Install software updates - Invoke-SoftwareUpdate -Password $Password - - Write-Host "`t[*] Stopping '$TemplateName' VM" - Stop-AnkaVM -VMName $TemplateName -} - -function Test-VMStopped { - $vmStatus = Get-AnkaVMStatus -VMName $TemplateName - if ($vmStatus -ne "stopped") { - Write-Host "`t[x] VM '$TemplateName' state is not stopped. The current state is '$vmStatus'" - exit 1 - } -} - -# Password is passed as env-var "SSHPASS" -$env:SSHUSER = $TemplateUsername -$env:SSHPASS = $TemplatePassword - -Write-Host "`n[#1] Download macOS application installer:" -$shortMacOSVersion = Get-ShortMacOSVersion -MacOSVersion $MacOSVersion -if ([string]::IsNullOrEmpty($TemplateName)) { - $osArch = $(arch) - if ($osArch -eq "arm64") { - $macOSInstaller = Get-MacOSIPSWInstaller -MacOSVersion $MacOSVersion -DownloadLatestVersion $DownloadLatestVersion -BetaSearch $BetaSearch - $TemplateName = "clean_macos_${shortMacOSVersion}_${osArch}" - } else { - $macOSInstaller = Get-MacOSInstaller -MacOSVersion $MacOSVersion -DownloadLatestVersion $DownloadLatestVersion -BetaSearch $BetaSearch - $TemplateName = "clean_macos_${shortMacOSVersion}" - } -} - -Write-Host "`n[#2] Create a VM template:" -Write-Host "`t[*] Deleting existed template with name '$TemplateName' before creating a new one" -Remove-AnkaVM -VMName $TemplateName - -# Temporary disable VNC for macOS 14 -# It's probably Anka's bug fixed in 3.3.2 -if ($shortMacOSVersion -eq "14") { - $env:ANKA_CREATE_VNC = 0 -} - -Write-Host "`t[*] Creating Anka VM template with name '$TemplateName' and '$TemplateUsername' user" -Write-Host "`t[*] CPU Count: $CPUCount, RamSize: ${RamSizeGb}G, DiskSizeGb: ${DiskSizeGb}G, InstallerPath: $macOSInstaller, TemplateName: $TemplateName" -New-AnkaVMTemplate -InstallerPath "$macOSInstaller" ` - -TemplateName $TemplateName ` - -TemplateUsername $TemplateUsername ` - -TemplatePassword $TemplatePassword ` - -CPUCount $CPUCount ` - -RamSizeGb $RamSizeGb ` - -DiskSizeGb $DiskSizeGb | Show-StringWithFormat - -Write-Host "`n[#3] Configure AutoLogon and/or install software updates:" -Invoke-UpdateSettings -Password $TemplatePassword - -Write-Host "`n[#4] Finalization '$TemplateName' configuration and push to the registry:" -Write-Host "`t[*] The '$TemplateName' VM status is stopped" -Test-VMStopped - -# Configure graphics settings -Write-Host "`t[*] Enabling Graphics Acceleration with Apple Metal for '$TemplateName' VM" -Set-AnkaVMVideoController -VMName $TemplateName -ShortMacOSVersion $ShortMacOSVersion - -Write-Host "`t[*] Setting screen resolution to $DisplayResolution for $TemplateName" -Set-AnkaVMDisplayResolution -VMName $TemplateName -DisplayResolution $DisplayResolution - -# Set static UUID -Set-AnkaVMUuid -VMName $TemplateName -Uuid $Uuid - -if ($PushToRegistry) { - # Push a VM template (and tag) to the Cloud - Write-Host "`t[*] Pushing '$TemplateName' image with '$TagName' tag to the '$RegistryUrl' registry..." - Push-AnkaTemplateToRegistry -RegistryUrl $registryUrl -TagName $TagName -TemplateName $TemplateName -} diff --git a/images.CI/macos/anka/Service.Helpers.psm1 b/images.CI/macos/anka/Service.Helpers.psm1 deleted file mode 100644 index cabf0f9a..00000000 --- a/images.CI/macos/anka/Service.Helpers.psm1 +++ /dev/null @@ -1,476 +0,0 @@ -function Enable-AutoLogon { - param ( - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] $HostName, - - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] $UserName, - - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] $Password - ) - - $url = "https://raw.githubusercontent.com/actions/runner-images/main/images/macos/assets/bootstrap-provisioner/setAutoLogin.sh" - $script = Invoke-RestMethod -Uri $url - $base64 = [Convert]::ToBase64String($script.ToCharArray()) - $command = "echo $base64 | base64 --decode > ./setAutoLogin.sh;sudo bash ./setAutoLogin.sh '${UserName}' '${Password}';rm ./setAutoLogin.sh" - Invoke-SSHPassCommand -HostName $HostName -Command $command -} - -function Invoke-SoftwareUpdateArm64 { - param ( - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] $HostName, - - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] $Password, - - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [array] $ListOfUpdates - ) - - # Define the next macOS version - $command = "sw_vers" - $guestMacosVersion = Invoke-SSHPassCommand -HostName $HostName -Command $command - switch -regex ($guestMacosVersion[1]) { - '12.\d' { $nextOSVersion = 'macOS Ventura|macOS Sonoma|macOS Sequoia' } - '13.\d' { $nextOSVersion = 'macOS Sonoma|macOS Sequoia' } - '14.\d' { $nextOSVersion = 'macOS Sequoia' } - } - - $url = "https://raw.githubusercontent.com/actions/runner-images/main/images/macos/assets/auto-software-update-arm64.exp" - $script = Invoke-RestMethod -Uri $url - foreach ($update in $ListOfUpdates) { - if ($update -notmatch $nextOSVersion) { - $updatedScript = $script.Replace("MACOSUPDATE", $($($update.trim()).Replace(" ","\ "))) - $base64 = [Convert]::ToBase64String($updatedScript.ToCharArray()) - $command = "echo $base64 | base64 --decode > ./auto-software-update-arm64.exp;chmod +x ./auto-software-update-arm64.exp; ./auto-software-update-arm64.exp ${Password};rm ./auto-software-update-arm64.exp" - Invoke-SSHPassCommand -HostName $HostName -Command $command - } - } -} - -function Get-AvailableVersions { - param ( - [bool] $IsBeta = $false - ) - - if ($IsBeta) { - $searchPostfix = " beta" - } - - $command = { /usr/sbin/softwareupdate --list-full-installers | grep "macOS" } - $condition = { $LASTEXITCODE -eq 0 } - $softwareUpdates = Invoke-WithRetry -Command $command -BreakCondition $condition | Where-Object { $_.Contains("Title: macOS") -and $_ -match $searchPostfix } - $allVersions = $softwareUpdates -replace "(\* )?(Title|Version|Size):" | ConvertFrom-Csv -Header OSName, OSVersion | Select-Object OSName, OSVersion -Unique - - $allVersions -} - -function Get-AvailableIPSWVersions { - param ( - [bool] $IsBeta = $false, - [bool] $IsLatest = $true, - [string] $MacOSCodeNameOrVersion - ) - - if ($IsBeta) { - $command = { mist list firmware "$MacOSCodeNameOrVersion" --compatible --include-betas --latest --export "/Applications/export.json" } - } elseif ($IsLatest) { - $command = { mist list firmware "$MacOSCodeNameOrVersion" --compatible --latest --export "/Applications/export.json" } - } else { - $command = { mist list firmware "$MacOSCodeNameOrVersion" --compatible --export "/Applications/export.json" } - } - - $condition = { $LASTEXITCODE -eq 0 } - Invoke-WithRetry -Command $command -BreakCondition $condition | Out-Null - $softwareList = get-content -Path "/Applications/export.json" - $availableBuilds = ($softwareList | ConvertFrom-Json).build - if ($null -eq $availableBuilds) { - Write-Host "Requested macOS '$MacOSCodeNameOrVersion' version not found in the list of available installers." - $command = { mist list firmware "$($MacOSCodeNameOrVersion.split('.')[0])" } - Invoke-WithRetry -Command $command -BreakCondition $condition - exit 1 - } - return $availableBuilds -} - -function Get-MacOSIPSWInstaller { - param ( - [Parameter(Mandatory)] - [version] $MacOSVersion, - - [bool] $DownloadLatestVersion = $false, - [bool] $BetaSearch = $false - ) - - if ($MacOSVersion -eq [version] "13.0") { - $MacOSName = "macOS Ventura" - } elseif ($MacOSVersion -eq [version] "14.0") { - $MacOSName = "macOS Sonoma" - } else { - $MacOSName = $MacOSVersion.ToString() - } - - Write-Host "`t[*] Finding available full installers" - if ($DownloadLatestVersion -eq $true) { - $targetBuild = Get-AvailableIPSWVersions -IsLatest $true -MacOSCodeNameOrVersion $MacOSName - Write-Host "`t[*] The 'DownloadLatestVersion' flag is set to true. Latest compatible macOS build of '$MacOSName' is '$targetBuild'" - } elseif ($BetaSearch -eq $true) { - $targetBuild = Get-AvailableIPSWVersions -IsBeta $true -MacOSCodeNameOrVersion $MacOSName - Write-Host "`t[*] The 'BetaSearch' flag is set to true. Latest compatible beta macOS build of '$MacOSName' is '$targetBuild'" - } else { - $targetBuild = Get-AvailableIPSWVersions -MacOSCodeNameOrVersion $MacOSName -IsLatest $false - Write-Host "`t[*] Available compatible macOS builds of '$MacOSName' are: $($targetBuild -join ', ')" - if ($targetBuild.Count -gt 1) { - Write-Error "`t[*] Please specify the exact build number of macOS you want to install" - exit 1 - } - } - - $installerPathPattern = "/Applications/Install ${macOSName}*.ipsw" - if (Test-Path $installerPathPattern) { - $previousInstallerPath = Get-Item -Path $installerPathPattern - Write-Host "`t[*] Removing '$previousInstallerPath' installation app before downloading the new one" - sudo rm -rf "$previousInstallerPath" - } - - # Download macOS installer - $installerDir = "/Applications/" - $installerName = "Install ${macOSName}.ipsw" - Write-Host "`t[*] Requested macOS '$targetBuild' version installer found, fetching it from mist database" - Invoke-WithRetry { mist download firmware "$targetBuild" --output-directory $installerDir --firmware-name "$installerName" } { $LASTEXITCODE -eq 0 } | Out-Null - if (Test-Path "$installerDir$installerName") { - $result = "$installerDir$installerName" - } else { - Write-Error "`t[*] Requested macOS '$targetBuild' version installer failed to download" - exit 1 - } - return $result -} - -function Get-MacOSInstaller { - param ( - [Parameter(Mandatory)] - [version] $MacOSVersion, - - [bool] $DownloadLatestVersion = $false, - [bool] $BetaSearch = $false - ) - - # Enroll machine to DeveloperSeed if we need beta and unenroll otherwise - $seedutil = "/System/Library/PrivateFrameworks/Seeding.framework/Versions/Current/Resources/seedutil" - if ($BetaSearch) { - Write-Host "`t[*] Beta Version requested. Enrolling machine to DeveloperSeed" - sudo $seedutil enroll DeveloperSeed | Out-Null - } else { - Write-Host "`t[*] Resetting the seed before requesting stable versions" - sudo $seedutil unenroll | Out-Null - } - - # Validate there is no software update at the moment - Test-SoftwareUpdate - - # Validate availability OSVersion - Write-Host "`t[*] Finding available full installers" - $availableVersions = Get-AvailableVersions -IsBeta $BetaSearch - if ($DownloadLatestVersion) { - $shortMacOSVersion = Get-ShortMacOSVersion -MacOSVersion $MacOSVersion - $filterSearch = "${shortMacOSVersion}." - $filteredVersions = $availableVersions.Where{ $_.OSVersion.StartsWith($filterSearch) } - if (-not $filteredVersions) { - Write-Host "`t[x] Failed to find any macOS versions using '$filterSearch' search condition" - Show-StringWithFormat $availableVersions - exit 1 - } - Show-StringWithFormat $filteredVersions - $osVersions = $filteredVersions.OSVersion | Sort-Object { [version]$_ } - $MacOSVersion = $osVersions | Select-Object -Last 1 - Write-Host "`t[*] The 'DownloadLatestVersion' flag is set. Latest macOS version is '$MacOSVersion' now" - } - - $macOSName = $availableVersions.Where{ $MacOSVersion -eq $_.OSVersion }.OSName.Split(" ")[1] - if (-not $macOSName) { - Write-Host "`t[x] Requested macOS '$MacOSVersion' version not found in the list of available installers. Available versions are:`n$($availableVersions.OSVersion)" - Write-Host "`t[x] Make sure to pass '-BetaSearch `$true' if you need a beta version installer" - exit 1 - } - - # Clear LastRecommendedMajorOSBundleIdentifier to prevent error during fetching updates - # Install failed with error: Update not found - Update-SoftwareBundle - - # Download macOS installer - Write-Host "`t[*] Requested macOS '$MacOSVersion' version installer found, fetching it from Apple Software Update" - Invoke-WithRetry -Command { sudo /usr/local/bin/mist download installer $MacOSVersion application --force --export installer.json --output-directory /Applications } -BreakCondition { $LASTEXITCODE -eq 0 } | Out-Null - if (-not(Test-Path installer.json -PathType leaf)) { - Write-Host "`t[x] Failed to fetch $MacOSVersion macOS" - exit 1 - } - - $installerPath = (Get-Content installer.json | Out-String | ConvertFrom-Json).options.applicationPath - if (-not $installerPath) { - Write-Host "`t[x] Path not found using '$installerPathPattern'" - exit 1 - } - Write-Host "`t[*] Installer successfully downloaded to '$installerPath'" - - $installerPath -} - -function Get-ShortMacOSVersion { - param ( - [Parameter(Mandatory)] - [version] $MacOSVersion - ) - - # Take Major.Minor version for macOS 10 (10.14 or 10.15) and Major for all further versions - $MacOSVersion.Major -eq 10 ? $MacOSVersion.ToString(2) : $MacOSVersion.ToString(1) -} - -function Get-SoftwareUpdate { - param( - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] $HostName - ) - - $command = "/usr/sbin/softwareupdate --list" - $result = Invoke-SSHPassCommand -HostName $HostName -Command $command - $result | Where-Object { $_ -match "(Label|Title):" } | Out-String -} - -function Get-SoftwareUpdateHistory { - param ( - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] $HostName - ) - - $command = "/usr/sbin/softwareupdate --history" - Invoke-SSHPassCommand -HostName $HostName -Command $command | Out-String -} - -function Install-SoftwareUpdate { - param( - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] $HostName, - [array] $listOfUpdates, - [string] $Password - ) - # If an update is happening on macOS arm64 we will use the additional tool to install updates. - $osArch = $(arch) - if ($osArch -eq "arm64") { - Invoke-SoftwareUpdateArm64 -HostName $HostName -Password $Password -ListOfUpdates $listOfUpdates - } else { - foreach ($update in $listOfUpdates) { - $command = "sudo /usr/sbin/softwareupdate --restart --verbose --install '$($update.trim())'" - Invoke-SSHPassCommand -HostName $HostName -Command $command - } - } -} - -function Invoke-SSHPassCommand { - param( - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] $HostName, - - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] $Command, - - [int] $ConnectTimeout = 10, - [int] $ConnectionAttempts = 10, - [int] $ServerAliveInterval = 30 - ) - - $sshArg = @( - "sshpass" - "-e" - "ssh" - "-o UserKnownHostsFile=/dev/null" - "-o StrictHostKeyChecking=no" - "-o ConnectTimeout=$ConnectTimeout" - "-o ConnectionAttempts=$ConnectionAttempts" - "-o LogLevel=ERROR" - "-o ServerAliveInterval=$ServerAliveInterval" - "${env:SSHUSER}@${HostName}" - ) - $sshPassOptions = $sshArg -join " " - if ($PSVersionTable.PSVersion.Major -eq 7 -and $PSVersionTable.PSVersion.Minor -le 2) { - $result = bash -c "$sshPassOptions \""$Command\"" 2>&1" - } else { - $result = bash -c "$sshPassOptions `"$Command`" 2>&1" - } - if ($LASTEXITCODE -ne 0) { - Write-Error "There is an error during command execution:`n$result" - exit 1 - } - $result -} - -function Invoke-WithRetry { - param( - [scriptblock] $Command, - [scriptblock] $BreakCondition, - [int] $RetryCount = 20, - [int] $Seconds = 60 - ) - while ($RetryCount -gt 0) { - try { - if ($Command) { - $result = & $Command - } - - if (& $BreakCondition) { - return $result - } - } catch { - Write-Host "`t [!] Error during command execution: $_" - } - - $RetryCount-- - if ($RetryCount -eq 0) { - Write-Error "No more attempts left: $BreakCondition" - } - Write-Host "`t [/] Waiting $Seconds seconds before retrying. Retries left: $RetryCount" - Start-Sleep -Seconds $Seconds - } - - $result -} - -function Restart-VMSSH { - param( - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] $HostName - ) - - # - # https://unix.stackexchange.com/questions/58271/closing-connection-after-executing-reboot-using-ssh-command - # - $command = '(sleep 1 && sudo reboot &) && exit' - Invoke-SSHPassCommand -HostName $HostName -Command $command -} - -function Show-StringWithFormat { - param( - [Parameter(ValuefromPipeline)] - [object] $string - ) - - process { - ($string | Out-String).Trim().split("`n") | ForEach-Object { Write-Host "`t $_" } - } -} - -function Remove-CurrentBetaSeed { - param( - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] $HostName - ) - - $command = "sudo /System/Library/PrivateFrameworks/Seeding.framework/Versions/Current/Resources/seedutil unenroll" - Invoke-SSHPassCommand -HostName $HostName -Command $command | Out-String -} - -function Test-AutoLogon { - param( - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] $VMName, - - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] $UserName - ) - - Invoke-WithRetry -BreakCondition { - # pwsh crashes if it invokes directly - # https://github.com/dotnet/runtime/issues/59059 - $ankaUser = "" | bash -c "anka run $VMName /usr/bin/id -nu" - $UserName -eq $ankaUser - } -} - -function Test-SoftwareUpdate { - param ( - [string] $UpdateProcessName = "softwareupdate" - ) - - $command = { - $updateProcess = (Get-Process -Name $UpdateProcessName -ErrorAction SilentlyContinue).id - if ($updateProcess) { - # Workaround to get commandline param as it doesn't work for macOS atm https://github.com/PowerShell/PowerShell/issues/13943 - $processName = /bin/ps -o command= $updateProcess - Write-Host "`t[*] Another software update process with '$updateProcess' id is in place with the following arguments '$processName'" - } - } - $condition = { - $null -eq (Get-Process -Name $UpdateProcessName -ErrorAction SilentlyContinue) - } - - Invoke-WithRetry -Command $command -BreakCondition $condition -} - -function Test-SSHPort { - param( - [Parameter(Mandatory)] - [ipaddress] $IPAddress, - - [int] $Port = 22, - [int] $Timeout = 2000 - ) - - Invoke-WithRetry -Command {$true} -BreakCondition { - try { - $client = [System.Net.Sockets.TcpClient]::new() - $client.ConnectAsync($IPAddress, $Port).Wait($Timeout) - } - catch { - $false - } - finally { - $client.Close() - } - } -} - -function Wait-LoginWindow { - param ( - [Parameter(Mandatory)] - [ValidateNotNullOrEmpty()] - [string] $HostName, - - [int] $RetryCount = 60, - [int] $Seconds = 60 - ) - - $condition = { - $psCommand = "/bin/ps auxww" - $lw = "/System/Library/CoreServices/loginwindow.app/Contents/MacOS/loginwindow" - $ctk = "/System/Library/Frameworks/CryptoTokenKit.framework/ctkahp.bundle/Contents/MacOS/ctkahp" - $proc = Invoke-SSHPassCommand -HostName $HostName -Command $psCommand | Out-String - $proc.Contains($lw) -and $proc.Contains($ctk) - } - Invoke-WithRetry -RetryCount $RetryCount -Seconds $Seconds -BreakCondition $condition -} - -function Update-SoftwareBundle { - $productVersion = sw_vers -productVersion - - if ( $productVersion.StartsWith('11.') ) { - sudo rm -rf /Library/Preferences/com.apple.commerce.plist - sudo /usr/bin/defaults delete /Library/Preferences/com.apple.SoftwareUpdate.plist LastRecommendedMajorOSBundleIdentifier | Out-Null - } -}