mirror of
https://github.com/actions/runner-images-sangeeth.git
synced 2026-01-06 01:57:28 +08:00
Anka Software Updates (#4256)
* Anka Software Updates * allow to set up video contoller
This commit is contained in:
committed by
GitHub
parent
12b8bece91
commit
b5373b2c29
218
images.CI/macos/anka/Anka.Helpers.psm1
Normal file
218
images.CI/macos/anka/Anka.Helpers.psm1
Normal file
@@ -0,0 +1,218 @@
|
||||
function Push-AnkaTemplateToRegistry {
|
||||
param (
|
||||
[Parameter(Mandatory)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string] $RegistryUrl,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string] $TagVersion,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string] $TemplateName
|
||||
)
|
||||
|
||||
$command = "anka registry -a $RegistryUrl push -t $TagVersion $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" | Out-String
|
||||
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"
|
||||
# Apple Metal is available starting from Big Sur
|
||||
if (-not $ShortMacOSVersion.StartsWith("10.")) {
|
||||
$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 = { 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
|
||||
}
|
||||
}
|
||||
179
images.CI/macos/anka/CreateCleanAnkaTemplate.ps1
Normal file
179
images.CI/macos/anka/CreateCleanAnkaTemplate.ps1
Normal file
@@ -0,0 +1,179 @@
|
||||
[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] $BetaSearch = $false,
|
||||
[bool] $InstallSoftwareUpdate = $true,
|
||||
[bool] $EnableAutoLogon = $true,
|
||||
[int] $CPUCount = 6,
|
||||
[int] $RamSizeGb = 7,
|
||||
[int] $DiskSizeGb = 300,
|
||||
[string] $DisplayResolution = "1920x1080"
|
||||
)
|
||||
|
||||
$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
|
||||
|
||||
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 {
|
||||
if (-not $InstallSoftwareUpdate) {
|
||||
Write-Host "`t[*] Skip installing software updates"
|
||||
return
|
||||
}
|
||||
|
||||
$ipAddress = Get-AnkaVMIPAddress -VMName $TemplateName
|
||||
|
||||
# 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
|
||||
}
|
||||
|
||||
Write-Host "`t[*] Fetching Software Updates ready to install on '$TemplateName' VM:"
|
||||
Show-StringWithFormat $newUpdates
|
||||
Write-Host "`t[*] Installing Software Updates on 'test_vm' VM:"
|
||||
Install-SoftwareUpdate -HostName $ipAddress | Show-StringWithFormat
|
||||
|
||||
# Check if Action: restart
|
||||
if ($newUpdates.Contains("Action: restart")) {
|
||||
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
|
||||
|
||||
# Check software updates have been installed
|
||||
$updates = Get-SoftwareUpdate -HostName $ipAddress
|
||||
if ($updates.Contains("Action: restart")) {
|
||||
Write-Host "`t[x] Software updates failed to install: $updates"
|
||||
Show-StringWithFormat $updates
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "`t[*] Show the install history:"
|
||||
$hUpdates = Get-SoftwareUpdateHistory -HostName $ipAddress
|
||||
Show-StringWithFormat $hUpdates
|
||||
}
|
||||
|
||||
function Invoke-UpdateSettings {
|
||||
$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
|
||||
|
||||
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:"
|
||||
$macOSInstaller = Get-MacOSInstaller -MacOSVersion $MacOSVersion -DownloadLatestVersion $DownloadLatestVersion -BetaSearch $BetaSearch
|
||||
$shortMacOSVersion = Get-ShortMacOSVersion -MacOSVersion $MacOSVersion
|
||||
if ([string]::IsNullOrEmpty($TemplateName)) {
|
||||
$TemplateName = "clean_macos_${shortMacOSVersion}_${DiskSizeGb}gb"
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
# Push a VM template (and tag) to the Cloud
|
||||
Write-Host "`t[*] Pushing '$TemplateName' image with '$ShortMacOSVersion' tag to the '$RegistryUrl' registry..."
|
||||
Push-AnkaTemplateToRegistry -RegistryUrl $registryUrl -TagVersion $shortMacOSVersion -TemplateName $TemplateName
|
||||
315
images.CI/macos/anka/Service.Helpers.psm1
Normal file
315
images.CI/macos/anka/Service.Helpers.psm1
Normal file
@@ -0,0 +1,315 @@
|
||||
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/virtual-environments/main/images/macos/provision/bootstrap-provisioner/kcpassword.py"
|
||||
$script = Invoke-RestMethod -Uri $url
|
||||
$base64 = [Convert]::ToBase64String($script.ToCharArray())
|
||||
$kcpassword = "echo $base64 | base64 --decode > ~/kcpassword;sudo python ./kcpassword '${Password}';rm ./kcpassword"
|
||||
$loginwindow = "sudo /usr/bin/defaults write /Library/Preferences/com.apple.loginwindow autoLoginUser '${UserName}';sudo /usr/bin/defaults write /Library/Preferences/com.apple.loginwindow autoLoginUserScreenLocked -bool false"
|
||||
$command = "${kcpassword};$loginwindow"
|
||||
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-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[*] Reseting the seed before requesting stable versions"
|
||||
sudo $seedutil unenroll | Out-Null
|
||||
}
|
||||
|
||||
# Validate there is no softwareupdate 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
|
||||
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
|
||||
}
|
||||
|
||||
$installerPathPattern = "/Applications/Install*${macOSName}.app"
|
||||
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
|
||||
Write-Host "`t[*] Requested macOS '$MacOSVersion' version installer found, fetching it from Apple Software Update"
|
||||
$result = Invoke-WithRetry { /usr/sbin/softwareupdate --fetch-full-installer --full-installer-version $MacOSVersion } {$LASTEXITCODE -eq 0} | Out-String
|
||||
if (-not $result.Contains("Install finished successfully")) {
|
||||
Write-Host "`t[x] Failed to fetch $MacOSVersion macOS `n$result"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$installerPath = (Get-Item -Path $installerPathPattern).FullName
|
||||
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
|
||||
)
|
||||
|
||||
$command = "sudo /usr/sbin/softwareupdate --all --install --restart --verbose"
|
||||
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 " "
|
||||
bash -c "$sshPassOptions \""$Command\"" 2>&1"
|
||||
}
|
||||
|
||||
function Invoke-WithRetry {
|
||||
param(
|
||||
[scriptblock] $Command,
|
||||
[scriptblock] $BreakCondition,
|
||||
[int] $RetryCount = 20,
|
||||
[int] $Seconds = 60
|
||||
)
|
||||
|
||||
while ($RetryCount -gt 0) {
|
||||
if ($Command) {
|
||||
$result = & $Command
|
||||
}
|
||||
|
||||
if (& $BreakCondition) {
|
||||
return $result
|
||||
}
|
||||
|
||||
$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
|
||||
)
|
||||
|
||||
$command = "sudo reboot"
|
||||
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 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
|
||||
}
|
||||
@@ -1,228 +0,0 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[Version] $MacOSVersion,
|
||||
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[String] $TemplateUsername,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[String] $TemplatePassword,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[String] $RegistryUrl,
|
||||
|
||||
[Bool] $BetaSearch = $false,
|
||||
[Int] $CpuCount = 6,
|
||||
[Int] $RamSizeGb = 7,
|
||||
[Int] $DiskSizeGb = 300,
|
||||
[String] $DisplayResolution = "1920x1080"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function Get-MacOSInstallers {
|
||||
param (
|
||||
[version] $MacOSVersion,
|
||||
[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 "Beta Version requested. Enrolling machine to DeveloperSeed"
|
||||
sudo $seedutil enroll DeveloperSeed | Out-Null
|
||||
} else {
|
||||
Write-Host "Reseting the seed before requesting stable versions"
|
||||
sudo $seedutil unenroll | Out-Null
|
||||
}
|
||||
|
||||
# Validate there is no softwareupdate at the moment
|
||||
Test-SoftwareUpdate
|
||||
|
||||
# Validate availability OSVersion
|
||||
$availableVersions = Get-AvailableVersions -IsBeta $BetaSearch
|
||||
$macOSName = $availableVersions.Where{ $_.OSVersion -eq $MacOSVersion }.OSName
|
||||
if (-not $macOSName) {
|
||||
Write-Host "Requested macOS '$MacOSVersion' version not found in the list of available installers. Available versions are:`n$($availableVersions.OSVersion)"
|
||||
Write-Host 'Make sure to pass "-BetaSearch $true" if you need a beta version installer'
|
||||
exit 1
|
||||
}
|
||||
|
||||
$installerPathPattern = "/Applications/Install*${macOSName}.app"
|
||||
if (Test-Path $installerPathPattern) {
|
||||
$previousInstallerPath = Get-Item -Path $installerPathPattern
|
||||
Write-Host "Removing '$previousInstallerPath' installation app before downloading the new one"
|
||||
sudo rm -rf "$previousInstallerPath"
|
||||
}
|
||||
|
||||
# Download macOS installer
|
||||
Write-Host "Requested macOS '$MacOSVersion' version installer found, fetching it from Apple Software Update"
|
||||
$result = Invoke-WithRetry { softwareupdate --fetch-full-installer --full-installer-version $MacOSVersion } {$LASTEXITCODE -eq 0} | Out-String
|
||||
if (-not $result.Contains("Install finished successfully")) {
|
||||
Write-Host "[Error]: failed to fetch $MacOSVersion macOS '$MacOSVersion' `n$result"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$installerPath = Get-Item -Path $installerPathPattern
|
||||
Write-Host "Installer successfully downloaded to '$installerPath'"
|
||||
|
||||
return $installerPath.FullName
|
||||
}
|
||||
|
||||
function Invoke-WithRetry {
|
||||
param(
|
||||
[scriptblock] $Command,
|
||||
[scriptblock] $BreakCondition,
|
||||
[int] $RetryCount = 20,
|
||||
[int] $Seconds = 60
|
||||
)
|
||||
|
||||
while ($RetryCount -gt 0) {
|
||||
$result = & $Command
|
||||
|
||||
if (& $BreakCondition) {
|
||||
break
|
||||
}
|
||||
|
||||
$RetryCount--
|
||||
Write-Host "Waiting $Seconds seconds before retrying. Retries left: $RetryCount"
|
||||
Start-Sleep -Seconds $Seconds
|
||||
}
|
||||
|
||||
$result
|
||||
}
|
||||
|
||||
function Get-AvailableVersions {
|
||||
param (
|
||||
[Int] $RetryCount = 20,
|
||||
[Int] $RetryInterval = 60,
|
||||
[Bool] $IsBeta = $false
|
||||
)
|
||||
|
||||
if ($IsBeta) {
|
||||
$searchPostfix = " beta"
|
||||
}
|
||||
|
||||
$softwareUpdates = Invoke-WithRetry { softwareupdate --list-full-installers | Where-Object { $_.Contains("Title: macOS") -and $_ -match $searchPostfix } } { {$LASTEXITCODE -eq 0}}
|
||||
$allVersions = $softwareUpdates -replace "(\* )?(Title|Version|Size):" | ConvertFrom-Csv -Header OsName, OsVersion
|
||||
|
||||
return $allVersions
|
||||
}
|
||||
|
||||
function Test-SoftwareUpdate {
|
||||
param (
|
||||
[String] $UpdateProcessName = "softwareupdate",
|
||||
[Int] $RetryCount = 20,
|
||||
[Int] $RetryInterval = 60
|
||||
)
|
||||
|
||||
$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 = ps -o command= $updateProcess
|
||||
Write-Host "Another software update process is in place with the following arguments '$processName', wait $RetryInterval seconds, $RetryCount attempts left"
|
||||
}
|
||||
}
|
||||
$condition = {
|
||||
$null -eq (Get-Process -Name $UpdateProcessName -ErrorAction SilentlyContinue)
|
||||
}
|
||||
|
||||
Invoke-WithRetry -Command $command -BreakCondition $condition
|
||||
}
|
||||
|
||||
function New-AnkaVMTemplate {
|
||||
param (
|
||||
[String] $InstallerPath,
|
||||
[String] $ShortMacOSVersion,
|
||||
[String] $TemplateName,
|
||||
[String] $TemplateUsername,
|
||||
[String] $TemplatePassword,
|
||||
[Int] $CpuCount,
|
||||
[Int] $RamSizeGb,
|
||||
[Int] $DiskSizeGb,
|
||||
[String] $DisplayResolution
|
||||
)
|
||||
|
||||
$isTemplateExists = (Invoke-Anka { anka --machine-readable list } | ConvertFrom-Json).body.name -eq $templateName
|
||||
if ($isTemplateExists) {
|
||||
Write-Host "Deleting existed template with name '$templateName' before creating a new one"
|
||||
Invoke-Anka { anka delete $templateName --yes }
|
||||
}
|
||||
|
||||
Write-Host "Creating Anka VM template with name '$TemplateName' and user $TemplateUsername"
|
||||
$env:ANKA_DEFAULT_USER = $TemplateUsername
|
||||
$env:ANKA_DEFAULT_PASSWD = $TemplatePassword
|
||||
$env:ANKA_CREATE_SUSPEND = 0
|
||||
Write-Host "Cpu Count: $CpuCount, RamSize: ${RamSizeGb}G, DiskSizeGb: ${DiskSizeGb}G, InstallerPath: $InstallerPath, TemplateName: $templateName"
|
||||
Invoke-Anka { anka create --cpu-count $CpuCount --ram-size "${RamSizeGb}G" --disk-size "${DiskSizeGb}G" --app $InstallerPath $templateName }
|
||||
|
||||
# Apple Metal is available starting from Big Sur
|
||||
if (-not $ShortMacOSVersion.StartsWith("10.")) {
|
||||
Write-Host "Enabling Graphics Acceleration with Apple Metal for $templateName"
|
||||
Invoke-Anka { anka modify $templateName set display -c pg }
|
||||
}
|
||||
|
||||
Write-Host "Setting screen resolution to $DisplayResolution for $templateName"
|
||||
Invoke-Anka { anka modify $templateName set display -r $DisplayResolution }
|
||||
|
||||
return $templateName
|
||||
}
|
||||
|
||||
function Add-AnkaImageToRegistry {
|
||||
param (
|
||||
[String] $RegistryUrl,
|
||||
[String] $ShortMacOSVersion,
|
||||
[String] $TemplateName
|
||||
)
|
||||
|
||||
Write-Host "Pushing image to the registry..."
|
||||
Invoke-Anka { anka registry -a $RegistryUrl push -t $ShortMacOSVersion $TemplateName }
|
||||
}
|
||||
|
||||
function Invoke-Anka {
|
||||
param (
|
||||
[scriptblock] $Cmd
|
||||
)
|
||||
|
||||
& $Cmd
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Error "There is an error during command execution"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
function Get-ShortMacOSVersion {
|
||||
param (
|
||||
[Version] $MacOSVersion
|
||||
)
|
||||
|
||||
# Take Major.Minor version for macOS 10 (10.14 or 10.15) and Major for all further versions
|
||||
if ($MacOSVersion.Major -eq 10) {
|
||||
$shortMacOSVersion = $MacOSVersion.ToString(2)
|
||||
}
|
||||
else {
|
||||
$shortMacOSVersion = $MacOSVersion.Major
|
||||
}
|
||||
|
||||
return $shortMacOSVersion
|
||||
}
|
||||
|
||||
$macOSInstaller = Get-MacOSInstallers -MacOSVersion $MacOSVersion -BetaSearch $BetaSearch
|
||||
$shortMacOSVersion = Get-ShortMacOSVersion -MacOSVersion $MacOSVersion
|
||||
$templateName = "clean_macos_${shortMacOSVersion}_${DiskSizeGb}gb"
|
||||
New-AnkaVMTemplate -InstallerPath $macOSInstaller `
|
||||
-ShortMacOSVersion $shortMacOSVersion `
|
||||
-TemplateName $templateName `
|
||||
-TemplateUsername $TemplateUsername `
|
||||
-TemplatePassword $TemplatePassword `
|
||||
-CpuCount $CpuCount `
|
||||
-RamSizeGb $RamSizeGb `
|
||||
-DiskSizeGb $DiskSizeGb `
|
||||
-DisplayResolution $DisplayResolution
|
||||
|
||||
Add-AnkaImageToRegistry -RegistryUrl $registryUrl -ShortMacOSVersion $shortMacOSVersion -TemplateName $templateName
|
||||
Reference in New Issue
Block a user