f373c0c24b
P0 bugs: - Invoke-CIJob.ps1: skip --depth 1 when $Commit specified (shallow clone + checkout specific commit was silently failing) - Get-BuildArtifacts.ps1: check .Length -eq 0 not rounded sizeKB (false fail on artifacts smaller than 50 bytes) - build-nsis.yml: upgrade upload-artifact v3 -> v4 (v3 deprecated) P0 security: - Remove hardcoded default password CIBuild!ChangeMe2026 from all scripts, README, TODO; prompt Read-Host -AsSecureString at runtime instead - Setup-TemplateVM.ps1: replace `net user $u $p /add` (password in argv / audit log 4688) with New-LocalUser + local ConvertTo-SecureString - Prepare-TemplateSetup.ps1: save and restore WSMan AllowUnencrypted + TrustedHosts in finally block (was permanently mutating host WinRM config) - Setup-TemplateVM.ps1: remove duplicate MaxMemoryPerShellMB (winrm set + Set-Item both setting same value) P1 doc (stale Host-Only / VMnet11 era): - Setup-TemplateVM.ps1: rewrite NETWORK REQUIREMENT block + final steps (system uses VMnet8 NAT permanently, not VMnet11 Host-Only) - Invoke-CIJob.ps1 Phase 1 comment: correct network model - ARCHITECTURE.md: remove Git from toolchain list (not installed); clarify zip-transfer rationale (no PAT in VM, not network isolation) - BEST-PRACTICES.md: rewrite Step 8 as NAT topology verification P1 minor: - Invoke-RemoteBuild.ps1: remove wasted first New-Item (created then immediately removed and recreated) - Wait-VMReady.ps1: remove unused $elapsed; simplify try/catch around native command (exit codes don't throw) - Install-Runner.ps1: add deprecation notice pointing to Setup-Host.ps1 P2 operational: - Add scripts/Cleanup-OrphanedBuildVMs.ps1 with -WhatIf support - Get-BuildArtifacts.ps1 -IncludeLogs: add -Recurse (msbuild logs in subdir) - Add gitea/workflows/lint.yml (PSScriptAnalyzer on .ps1 push/PR) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
265 lines
12 KiB
PowerShell
265 lines
12 KiB
PowerShell
#Requires -Version 5.1
|
|
<#
|
|
.SYNOPSIS
|
|
HOST-side script: delivers and runs Setup-TemplateVM.ps1 inside the template VM.
|
|
|
|
.DESCRIPTION
|
|
Automates the template VM provisioning from the host machine:
|
|
1. Verifies the VM is reachable via WinRM (must already be enabled in guest)
|
|
2. Copies Setup-TemplateVM.ps1 into the VM via WinRM
|
|
3. Runs Setup-TemplateVM.ps1 inside the VM with the given parameters
|
|
4. Handles the reboot-required case (exit 3010) and optionally re-runs
|
|
|
|
PREREQUISITES (do these manually before running this script):
|
|
a. VM NIC is set to VMnet8 (NAT) — the VM needs internet for downloads
|
|
b. Windows Server is installed and booted
|
|
c. WinRM is enabled inside the VM. From an elevated cmd/PS in the VM run:
|
|
winrm quickconfig -q
|
|
Enable-PSRemoting -Force -SkipNetworkProfileCheck
|
|
d. You know the IP address the VM got from DHCP on VMnet8 (NAT)
|
|
(check via: ipconfig inside the VM, or VMware → VM → Settings →
|
|
Network Adapter → Advanced → MAC address, then check DHCP leases)
|
|
|
|
AFTER THIS SCRIPT COMPLETES:
|
|
1. Shut down the VM
|
|
2. Take snapshot: VM → Snapshot → Take Snapshot → name: BaseClean
|
|
(VM stays on VMnet8 NAT — internet access is required for builds)
|
|
3. Store credentials on host (use the same password chosen during provisioning):
|
|
New-StoredCredential -Target 'BuildVMGuest' -UserName 'ci_build' `
|
|
-Password '<your-build-password>' -Persist LocalMachine
|
|
|
|
.PARAMETER VMIPAddress
|
|
IP address of the template VM while it is on NAT (VMnet8).
|
|
Example: 192.168.79.130
|
|
|
|
.PARAMETER AdminUsername
|
|
Administrator username inside the VM. Default: Administrator
|
|
|
|
.PARAMETER AdminPassword
|
|
Administrator password inside the VM. If not supplied, prompts interactively.
|
|
|
|
.PARAMETER BuildPassword
|
|
Password to set for the ci_build user inside the VM.
|
|
Default: CIBuild!ChangeMe2026 — CHANGE THIS before production use.
|
|
|
|
.PARAMETER DotNetSdkVersion
|
|
.NET SDK channel to install in the VM. Default: 10.0 (matches host SDK).
|
|
|
|
.PARAMETER SkipWindowsUpdate
|
|
Pass through to Setup-TemplateVM.ps1 to skip the Windows Update step.
|
|
|
|
.EXAMPLE
|
|
# Interactive (prompts for admin password):
|
|
.\Prepare-TemplateSetup.ps1 -VMIPAddress 192.168.79.130
|
|
|
|
# With explicit credentials:
|
|
.\Prepare-TemplateSetup.ps1 -VMIPAddress 192.168.79.130 `
|
|
-AdminPassword 'YourAdminPass' -BuildPassword 'StrongCIPass!2026'
|
|
|
|
# Skip Windows Update (already applied):
|
|
.\Prepare-TemplateSetup.ps1 -VMIPAddress 192.168.79.130 -SkipWindowsUpdate
|
|
#>
|
|
[CmdletBinding()]
|
|
param(
|
|
[Parameter(Mandatory)]
|
|
[ValidatePattern('^(\d{1,3}\.){3}\d{1,3}$')]
|
|
[string] $VMIPAddress,
|
|
|
|
[string] $AdminUsername = 'Administrator',
|
|
|
|
[string] $AdminPassword = '',
|
|
|
|
[string] $BuildPassword = '',
|
|
|
|
[string] $DotNetSdkVersion = '10.0',
|
|
|
|
[switch] $SkipWindowsUpdate
|
|
)
|
|
|
|
Set-StrictMode -Version Latest
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
|
|
# Prompt for BuildPassword if not supplied — never use a hardcoded default.
|
|
if ($BuildPassword -eq '') {
|
|
Write-Host "[Prepare] No -BuildPassword supplied. Enter password for the CI build account inside the VM:"
|
|
$secPwd = Read-Host -Prompt " BuildPassword" -AsSecureString
|
|
$bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($secPwd)
|
|
$BuildPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($bstr)
|
|
[System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr)
|
|
}
|
|
|
|
# ── Build PSCredential ────────────────────────────────────────────────────────
|
|
if ($AdminPassword -eq '') {
|
|
Write-Host "[Prepare] Enter administrator credentials for the template VM ($VMIPAddress):"
|
|
$credential = Get-Credential -UserName $AdminUsername -Message "Template VM administrator ($VMIPAddress)"
|
|
}
|
|
else {
|
|
$secPwd = ConvertTo-SecureString $AdminPassword -AsPlainText -Force
|
|
$credential = New-Object System.Management.Automation.PSCredential($AdminUsername, $secPwd)
|
|
}
|
|
|
|
$sessionOptions = New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck
|
|
|
|
# ── Configure HOST-side WinRM client (required for Basic auth over HTTP) ──────
|
|
# AllowUnencrypted is needed for HTTP/5985 Basic auth. We save the prior value
|
|
# and restore it in the finally block so this script doesn't permanently change
|
|
# the host security posture.
|
|
Write-Host "[Prepare] Configuring host WinRM client for unencrypted Basic auth (will be restored on exit)..."
|
|
$prevAllowUnencrypted = $null
|
|
$prevTrustedHosts = $null
|
|
try {
|
|
$prevAllowUnencrypted = (Get-Item WSMan:\localhost\Client\AllowUnencrypted -ErrorAction Stop).Value
|
|
$prevTrustedHosts = (Get-Item WSMan:\localhost\Client\TrustedHosts -ErrorAction Stop).Value
|
|
Set-Item WSMan:\localhost\Client\AllowUnencrypted $true -Force -ErrorAction Stop
|
|
# Add VM IP to TrustedHosts if not already present
|
|
if ($prevTrustedHosts -ne '*' -and $prevTrustedHosts -notlike "*$VMIPAddress*") {
|
|
$newTrusted = if ($prevTrustedHosts -eq '') { $VMIPAddress } else { "$prevTrustedHosts,$VMIPAddress" }
|
|
Set-Item WSMan:\localhost\Client\TrustedHosts -Value $newTrusted -Force -ErrorAction Stop
|
|
}
|
|
Write-Host "[Prepare] Host WinRM client configured."
|
|
}
|
|
catch {
|
|
Write-Warning "[Prepare] Could not configure host WinRM client settings (need elevation?)."
|
|
Write-Warning "Run once in an elevated PowerShell on the HOST:"
|
|
Write-Warning " Set-Item WSMan:\localhost\Client\AllowUnencrypted `$true -Force"
|
|
Write-Warning " Set-Item WSMan:\localhost\Client\TrustedHosts -Value '*' -Force"
|
|
Write-Warning "Then re-run this script."
|
|
exit 1
|
|
}
|
|
|
|
|
|
Write-Host "[Prepare] Testing WinRM connectivity to $VMIPAddress..."
|
|
try {
|
|
Test-WSMan -ComputerName $VMIPAddress -Credential $credential `
|
|
-Authentication Basic -ErrorAction Stop | Out-Null
|
|
Write-Host "[Prepare] WinRM reachable."
|
|
}
|
|
catch {
|
|
Write-Error @"
|
|
[Prepare] Cannot reach $VMIPAddress via WinRM.
|
|
|
|
Ensure:
|
|
- The VM is running and on VMnet8 (NAT) with internet access
|
|
- WinRM is enabled inside the VM (run as Administrator in VM):
|
|
winrm quickconfig -q
|
|
Enable-PSRemoting -Force -SkipNetworkProfileCheck
|
|
Set-Item WSMan:\localhost\Client\TrustedHosts -Value '*' -Force
|
|
- Windows Firewall allows port 5985 inbound
|
|
- The IP is correct (check ipconfig inside the VM)
|
|
|
|
Error: $_
|
|
"@
|
|
exit 1
|
|
}
|
|
|
|
# ── Open session ──────────────────────────────────────────────────────────────
|
|
Write-Host "[Prepare] Opening PSSession..."
|
|
$session = New-PSSession `
|
|
-ComputerName $VMIPAddress `
|
|
-Credential $credential `
|
|
-SessionOption $sessionOptions `
|
|
-Authentication Basic `
|
|
-ErrorAction Stop
|
|
|
|
try {
|
|
# ── Copy Setup-TemplateVM.ps1 into the VM ─────────────────────────────────
|
|
$setupScript = Join-Path $scriptDir 'Setup-TemplateVM.ps1'
|
|
$guestScriptPath = 'C:\CI\Setup-TemplateVM.ps1'
|
|
|
|
Write-Host "[Prepare] Ensuring C:\CI exists on guest..."
|
|
Invoke-Command -Session $session -ScriptBlock {
|
|
if (-not (Test-Path 'C:\CI')) {
|
|
New-Item -ItemType Directory -Path 'C:\CI' -Force | Out-Null
|
|
}
|
|
}
|
|
|
|
Write-Host "[Prepare] Copying Setup-TemplateVM.ps1 -> guest $guestScriptPath ..."
|
|
Copy-Item -Path $setupScript -Destination $guestScriptPath -ToSession $session -Force
|
|
|
|
# ── Build argument list for Setup-TemplateVM.ps1 ─────────────────────────
|
|
$setupArgs = @{
|
|
BuildPassword = $BuildPassword
|
|
DotNetSdkVersion = $DotNetSdkVersion
|
|
}
|
|
if ($SkipWindowsUpdate) { $setupArgs['SkipWindowsUpdate'] = $true }
|
|
|
|
# ── Run Setup-TemplateVM.ps1 inside the VM ────────────────────────────────
|
|
Write-Host "[Prepare] Running Setup-TemplateVM.ps1 inside VM (this will take a while)..."
|
|
Write-Host "[Prepare] Output from guest:"
|
|
Write-Host "------------------------------------------------------------"
|
|
|
|
$result = Invoke-Command -Session $session -ScriptBlock {
|
|
param($scriptPath, $scriptArgs)
|
|
Set-ExecutionPolicy Bypass -Scope Process -Force
|
|
$exitCode = 0
|
|
try {
|
|
& $scriptPath @scriptArgs
|
|
$exitCode = $LASTEXITCODE
|
|
if ($null -eq $exitCode) { $exitCode = 0 }
|
|
}
|
|
catch {
|
|
Write-Error $_
|
|
$exitCode = 1
|
|
}
|
|
return $exitCode
|
|
} -ArgumentList $guestScriptPath, $setupArgs
|
|
|
|
Write-Host "------------------------------------------------------------"
|
|
|
|
# ── Handle reboot required (exit 3010) ───────────────────────────────────
|
|
if ($result -eq 3010) {
|
|
Write-Host ""
|
|
Write-Warning "*** REBOOT REQUIRED after Windows Update. ***"
|
|
Write-Warning " The VM will reboot. Wait for it to come back up, then run:"
|
|
Write-Warning " .\Prepare-TemplateSetup.ps1 -VMIPAddress $VMIPAddress -SkipWindowsUpdate"
|
|
|
|
# Reboot the VM
|
|
Write-Host "[Prepare] Sending reboot command to VM..."
|
|
Invoke-Command -Session $session -ScriptBlock { Restart-Computer -Force }
|
|
exit 3010
|
|
}
|
|
|
|
if ($result -ne 0) {
|
|
throw "Setup-TemplateVM.ps1 exited with code $result"
|
|
}
|
|
|
|
# ── Success ───────────────────────────────────────────────────────────────
|
|
Write-Host ""
|
|
Write-Host "============================================================" -ForegroundColor Green
|
|
Write-Host " Template VM provisioning complete!" -ForegroundColor Green
|
|
Write-Host "============================================================" -ForegroundColor Green
|
|
Write-Host ""
|
|
Write-Host " NOW DO THESE STEPS (manual):"
|
|
Write-Host ""
|
|
Write-Host " 1. Shut down the VM: Start -> Shut down"
|
|
Write-Host " (VM stays on VMnet8 NAT for internet access during builds)"
|
|
Write-Host ""
|
|
Write-Host " 2. Take snapshot:"
|
|
Write-Host " VM -> Snapshot -> Take Snapshot"
|
|
Write-Host " Name it EXACTLY: BaseClean"
|
|
Write-Host ""
|
|
Write-Host " 3. Store CI credentials on this HOST:"
|
|
Write-Host " (run in elevated PowerShell with CredentialManager module)"
|
|
Write-Host " New-StoredCredential -Target 'BuildVMGuest' ``"
|
|
Write-Host " -UserName 'ci_build' -Password '<your-build-password>' ``"
|
|
Write-Host " -Persist LocalMachine"
|
|
Write-Host ""
|
|
Write-Host " 4. Verify runner online in Gitea: http://10.10.20.11:3100"
|
|
Write-Host " Admin -> Runners -> local-windows-runner -> should show Online"
|
|
Write-Host ""
|
|
}
|
|
finally {
|
|
Remove-PSSession $session -ErrorAction SilentlyContinue
|
|
|
|
# Restore host WinRM client settings to their original values
|
|
if ($null -ne $prevAllowUnencrypted) {
|
|
Set-Item WSMan:\localhost\Client\AllowUnencrypted $prevAllowUnencrypted -Force -ErrorAction SilentlyContinue
|
|
}
|
|
if ($null -ne $prevTrustedHosts) {
|
|
Set-Item WSMan:\localhost\Client\TrustedHosts -Value $prevTrustedHosts -Force -ErrorAction SilentlyContinue
|
|
}
|
|
Write-Host "[Prepare] Host WinRM client settings restored."
|
|
}
|