Cloud.ps1
function Get-CloudImage { param ( [string] $ImageVersion = "24.04" # $ImageName ="noble" ) $cloud_path = Get-BoxPath -Path "cloud" Switch ($ImageVersion) { "22.04" { $_ = "jammy" $ImageVersion = "22.04" } "jammy" { $ImageOS = "ubuntu" $ImageVersionName = "jammy" $ImageVersion = "22.04" $ImageRelease = "release" # default option is get latest but could be fixed to some specific version for example "release-20210413" $ImageBaseUrl = "http://cloud-images.ubuntu.com/releases" # alternative https://mirror.scaleuptech.com/ubuntu-cloud-images/releases $ImageUrlRoot = "$ImageBaseUrl/$ImageVersionName/$ImageRelease/" # latest $ImageFileName = "$ImageOS-$ImageVersion-server-cloudimg-amd64" $ImageFileExtension = "ova" # Manifest file is used for version check based on last modified HTTP header $ImageHashFileName = "SHA256SUMS" $ImageManifestSuffix = "manifest" } "24.04" { $_ = "noble" $ImageVersion = "24.04" } "noble" { $ImageOS = "ubuntu" $ImageVersionName = "noble" $ImageVersion = "24.04" $ImageRelease = "release" # default option is get latest but could be fixed to some specific version for example "release-20210413" $ImageBaseUrl = "http://cloud-images.ubuntu.com/releases" # alternative https://mirror.scaleuptech.com/ubuntu-cloud-images/releases $ImageUrlRoot = "$ImageBaseUrl/$ImageVersionName/$ImageRelease/" # latest $ImageFileName = "$ImageOS-$ImageVersion-server-cloudimg-amd64" $ImageFileExtension = "ova" # Manifest file is used for version check based on last modified HTTP header $ImageHashFileName = "SHA256SUMS" $ImageManifestSuffix = "manifest" } default { throw "Image version $ImageVersion not supported." } } $ImagePath = "$($ImageUrlRoot)$($ImageFileName)" $ImageHashPath = "$($ImageUrlRoot)$($ImageHashFileName)" # storage location for base images $ImageCachePath = Join-Path $cloud_path $("$ImageOS-$ImageVersion") if (!(test-path $ImageCachePath)) { mkdir -Path $ImageCachePath | out-null } # Get the timestamp of the target build on the cloud-images site $BaseImageStampFile = join-path $ImageCachePath "baseimagetimestamp.txt" [string]$stamp = '' if (test-path $BaseImageStampFile) { $stamp = (Get-Content -Path $BaseImageStampFile | Out-String).Trim() Write-Verbose "Timestamp from cache: $stamp" } if ($BaseImageCheckForUpdate -or ($stamp -eq '')) { $stamp = (Invoke-WebRequest -UseBasicParsing "$($ImagePath).$($ImageManifestSuffix)").BaseResponse.LastModified.ToUniversalTime().ToString("yyyyMMddHHmmss") Set-Content -path $BaseImageStampFile -value $stamp -force Write-Verbose "Timestamp from web (new): $stamp" } # check if local cached cloud image is the target one per $stamp if (!(test-path "$($ImageCachePath)\$($ImageOS)-$($stamp).$($ImageFileExtension)")) { try { # If we do not have a matching image - delete the old ones and download the new one Write-Verbose "Did not find: $($ImageCachePath)\$($ImageOS)-$($stamp).$($ImageFileExtension)" Write-Host 'Removing old images from cache...' -NoNewline Remove-Item "$($ImageCachePath)" -Exclude 'baseimagetimestamp.txt', "$($ImageOS)-$($stamp).*" -Recurse -Force Write-Host -ForegroundColor Green " Done." # get headers for content length Write-Host 'Check new image size ...' -NoNewline $response = Invoke-WebRequest "$($ImagePath).$($ImageFileExtension)" -UseBasicParsing -Method Head $downloadSize = [int]$response.Headers["Content-Length"] Write-Host -ForegroundColor Green " Done." Write-Host "Downloading new Cloud image $ImageVersion ($([int]($downloadSize / 1024 / 1024)) MB)..." -NoNewline Write-Verbose $(Get-Date) Start-BitsTransfer "$($ImagePath).$($ImageFileExtension)" -Destination "$($ImageCachePath)\$($ImageOS)-$($stamp).$($ImageFileExtension).tmp" #$ProgressPreference = "SilentlyContinue" #Disable progress indicator because it is causing Invoke-WebRequest to be very slow # download new image #Invoke-WebRequest "$($ImagePath).$($ImageFileExtension)" -OutFile "$($ImageCachePath)\$($ImageOS)-$($stamp).$($ImageFileExtension).tmp" -UseBasicParsing #$ProgressPreference = "Continue" #Restore progress indicator. # rename from .tmp to $($ImageFileExtension) Remove-Item "$($ImageCachePath)\$($ImageOS)-$($stamp).$($ImageFileExtension)" -Force -ErrorAction 'SilentlyContinue' Rename-Item -path "$($ImageCachePath)\$($ImageOS)-$($stamp).$($ImageFileExtension).tmp" ` -newname "$($ImageOS)-$($stamp).$($ImageFileExtension)" Write-Host -ForegroundColor Green " Done." # check file hash Write-Host "Checking file hash for downloaded image..." -NoNewline Write-Verbose $(Get-Date) $hashSums = [System.Text.Encoding]::UTF8.GetString((Invoke-WebRequest $ImageHashPath -UseBasicParsing).Content) Switch -Wildcard ($ImageHashPath) { '*SHA256*' { $fileHash = Get-FileHash "$($ImageCachePath)\$($ImageOS)-$($stamp).$($ImageFileExtension)" -Algorithm SHA256 } '*SHA512*' { $fileHash = Get-FileHash "$($ImageCachePath)\$($ImageOS)-$($stamp).$($ImageFileExtension)" -Algorithm SHA512 } default { throw "$ImageHashPath not supported." } } if (($hashSums | Select-String -pattern $fileHash.Hash -SimpleMatch).Count -eq 0) { throw "File hash check failed" } Write-Verbose $(Get-Date) Write-Host -ForegroundColor Green " Done." } catch { cleanupFile "$($ImageCachePath)\$($ImageOS)-$($stamp).$($ImageFileExtension)" $ErrorMessage = $_.Exception.Message Write-Host "Error: $ErrorMessage" exit 1 } } $ova = "$($ImageCachePath)\$($ImageOS)-$($stamp).$($ImageFileExtension)" return $ova } function New-CloudInit { param( [Parameter(mandatory = $true)] [string] $Path, [Parameter(mandatory = $true)] [string] $Hostname, [Parameter(mandatory = $true)] [string] $Address ) if (-not(Test-Path -Path $path -PathType Container)) { New-Item -Path $path -ItemType Directory | Out-Null } $seedIso = Join-Path $Path "seed.iso" $metadata = Join-Path $Path "meta-data" $userdata = Join-Path $Path "user-data" "local-hostname: ${Hostname})" | Out-File -FilePath $metadata @" #cloud-config users: - name: box groups: sudo, docker sudo: ["ALL=(ALL) NOPASSWD:ALL"] plain_text_passwd: password lock_passwd: false shell: /bin/bash #ssh_pwauth: true ssh_authorized_keys: - $(Get-Content (Get-SshKey -Public)) # package_update: false bootcmd: # - echo "blacklist floppy" > /etc/modprobe.d/blacklist-floppy.conf - rmmod floppy - update-initramfs -u # Ubuntu cloud ova comes with its own netplan config for enp0s3 - printf "network:\n ethernets:\n enp0s8:\n addresses:\n - ${Address}\n" > /etc/netplan/60-host-only.yaml - netplan apply # Disable snapd - systemctl disable snapd - systemctl mask snapd runcmd: - touch /etc/cloud/cloud-init.disabled "@ | Out-File -FilePath $userdata # https://blog.idera.com/database-tools/powershell/powertips/creating-iso-files/ # https://thedotsource.com/2021/03/16/building-iso-files-with-powershell-7/ # https://gitlab.com/xtec/box/-/tree/207febefe8500f17d847ef44aff9c3d5bc145b3d/cloud-init $image = New-Object -ComObject IMAPI2FS.MsftFileSystemImage $image.VolumeName = "cidata" # create CDROM, Joliet and UDF file systems #$image.FileSystemsToCreate = 7 $MediaType = @('UNKNOWN','CDROM','CDR','CDRW','DVDROM','DVDRAM','DVDPLUSR','DVDPLUSRW','DVDPLUSR_DUALLAYER','DVDDASHR','DVDDASHRW','DVDDASHR_DUALLAYER','DISK','DVDPLUSRW_DUALLAYER','HDDVDROM','HDDVDR','HDDVDRAM','BDROM','BDR','BDRE') $image.ChooseImageDefaultsForMediaType($MediaType.IndexOf(1)) $image.Root.AddTree($metadata, $true) $image.Root.AddTree($userdata, $true) if (!('ISOFile' -as [type])) { ($compiler = new-object System.CodeDom.Compiler.CompilerParameters).CompilerOptions = '/unsafe' Add-Type -CompilerParameters $compiler -TypeDefinition @' public class ISOFile { public unsafe static void Create(string Path, object Stream, int BlockSize, int TotalBlocks) { int bytes = 0; byte[] buf = new byte[BlockSize]; var ptr = (System.IntPtr)(&bytes); var o = System.IO.File.OpenWrite(Path); var i = Stream as System.Runtime.InteropServices.ComTypes.IStream; if (o != null) { while (TotalBlocks-- > 0) { i.Read(buf, BlockSize, ptr); o.Write(buf, 0, bytes); } o.Flush(); o.Close(); } } } '@ } $image = $image.CreateResultImage() [ISOFile]::Create($seedIso, $image.ImageStream, $image.BlockSize, $image.TotalBlocks) Remove-Item $metadata, $userdata return $seedIso } |