914072fdd8
Replace all references to CI-WinBuild.vmx with WinBuild2025.vmx. Update template path: F:\CI\Templates\WinBuild\ → F:\CI\Templates\WinBuild2025\ Files affected: - runner/config.yaml: GITEA_CI_TEMPLATE_PATH env var - docs: HOST-SETUP.md, WINDOWS-TEMPLATE-SETUP.md, TEST-PLAN, RUNBOOK - scripts: Measure-CIBenchmark, Set-TemplateSharedFolders, Test-NsinnounpBuild - TODO.md, Setup-Host.ps1, README.md Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
358 lines
15 KiB
PowerShell
358 lines
15 KiB
PowerShell
#Requires -RunAsAdministrator
|
|
#Requires -Version 5.1
|
|
<#
|
|
.SYNOPSIS
|
|
Bootstraps the CI host machine from scratch (run ONCE on the host).
|
|
|
|
.DESCRIPTION
|
|
Creates the F:\CI\ directory structure, installs prerequisites, registers
|
|
Windows Credential Manager entries, and optionally installs + registers
|
|
act_runner as a Windows service (via NSSM).
|
|
|
|
Run this script elevated (as Administrator) on the Windows host before
|
|
provisioning the template VM or running any CI jobs.
|
|
|
|
What this script does:
|
|
1. Create F:\CI\ directory tree
|
|
2. Verify VMware Workstation / vmrun.exe is present
|
|
3. Install PowerShell CredentialManager module (if missing)
|
|
4. Store guest VM credentials in Windows Credential Manager
|
|
5. Install act_runner as a Windows service via NSSM (optional)
|
|
6. Configure SSH alias for Gitea (optional)
|
|
7. Print a post-setup checklist
|
|
|
|
.PARAMETER CIRoot
|
|
Base directory for all CI data. Default: F:\CI
|
|
|
|
.PARAMETER GuestCredentialTarget
|
|
Windows Credential Manager target name for guest VM credentials.
|
|
Default: BuildVMGuest
|
|
|
|
.PARAMETER GuestUsername
|
|
Build user account inside the VM. Default: ci_build
|
|
|
|
.PARAMETER GuestPassword
|
|
Password for the guest build account.
|
|
CHANGE THIS before production use.
|
|
Default: CIBuild!ChangeMe2026
|
|
|
|
.PARAMETER ActRunnerExe
|
|
Path to the act_runner binary. If the file does not exist the script
|
|
warns and skips the service installation step.
|
|
Default: <CIRoot>\act_runner\act_runner.exe
|
|
|
|
.PARAMETER ActRunnerConfigYaml
|
|
Path to act_runner config.yaml to copy into <CIRoot>\act_runner\.
|
|
If not specified, copies runner\config.yaml from the repo (if it exists
|
|
next to this script).
|
|
Default: <ScriptDir>\runner\config.yaml
|
|
|
|
.PARAMETER GiteaUrl
|
|
Gitea server URL used for runner registration.
|
|
Default: http://10.10.20.11:3100
|
|
|
|
.PARAMETER GiteaRunnerToken
|
|
Registration token for act_runner. If not supplied the script prints
|
|
instructions and skips registration (you can register later manually).
|
|
|
|
.PARAMETER SkipRunnerInstall
|
|
Skip act_runner service installation entirely.
|
|
|
|
.PARAMETER SkipSSHConfig
|
|
Skip writing the SSH config alias for Gitea.
|
|
|
|
.PARAMETER GiteaSSHHost
|
|
Gitea SSH hostname/IP. Default: 10.10.20.11
|
|
|
|
.PARAMETER GiteaSSHPort
|
|
Gitea SSH port. Default: 2222
|
|
|
|
.EXAMPLE
|
|
# Minimal — creates dirs, stores credentials, skips runner install:
|
|
.\Setup-Host.ps1 -SkipRunnerInstall
|
|
|
|
# Full setup with runner registration:
|
|
.\Setup-Host.ps1 -GiteaRunnerToken 'abc123token'
|
|
|
|
# Custom CI root on a different drive:
|
|
.\Setup-Host.ps1 -CIRoot 'D:\CI' -GiteaRunnerToken 'abc123token'
|
|
#>
|
|
[CmdletBinding(SupportsShouldProcess)]
|
|
param(
|
|
[string] $CIRoot = 'F:\CI',
|
|
[string] $GuestCredentialTarget = 'BuildVMGuest',
|
|
[string] $GuestUsername = 'ci_build',
|
|
[string] $GuestPassword = '',
|
|
[string] $ActRunnerExe = '',
|
|
[string] $ActRunnerConfigYaml = '',
|
|
[string] $GiteaUrl = 'http://10.10.20.11:3100',
|
|
[string] $GiteaRunnerToken = '',
|
|
[switch] $SkipRunnerInstall,
|
|
[switch] $SkipSSHConfig,
|
|
[string] $GiteaSSHHost = '10.10.20.11',
|
|
[int] $GiteaSSHPort = 2222
|
|
)
|
|
|
|
Set-StrictMode -Version Latest
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
$scriptDir = Split-Path $MyInvocation.MyCommand.Path -Parent
|
|
|
|
function Write-Step {
|
|
param([string]$Message)
|
|
Write-Host "`n=== $Message ===" -ForegroundColor Cyan
|
|
}
|
|
|
|
function Write-OK { param([string]$m) Write-Host " [OK] $m" -ForegroundColor Green }
|
|
function Write-Warn { param([string]$m) Write-Host " [WARN] $m" -ForegroundColor Yellow }
|
|
function Write-Fail { param([string]$m) Write-Host " [FAIL] $m" -ForegroundColor Red }
|
|
|
|
# Resolve defaults that depend on $CIRoot / $scriptDir
|
|
if (-not $ActRunnerExe) { $ActRunnerExe = Join-Path $CIRoot 'act_runner\act_runner.exe' }
|
|
if (-not $ActRunnerConfigYaml) { $ActRunnerConfigYaml = Join-Path $scriptDir 'runner\config.yaml' }
|
|
|
|
# ── Step 1: Create F:\CI\ directory tree ─────────────────────────────────────
|
|
Write-Step "Creating CI directory tree under $CIRoot"
|
|
|
|
$dirs = @(
|
|
"$CIRoot\BuildVMs",
|
|
"$CIRoot\Artifacts",
|
|
"$CIRoot\Logs",
|
|
"$CIRoot\Templates\WinBuild",
|
|
"$CIRoot\act_runner\logs",
|
|
"$CIRoot\Cache\NuGet",
|
|
"$CIRoot\RunnerWork",
|
|
"$CIRoot\ISO"
|
|
)
|
|
|
|
foreach ($dir in $dirs) {
|
|
if (Test-Path $dir) {
|
|
Write-OK "$dir (already exists)"
|
|
} else {
|
|
New-Item -ItemType Directory -Path $dir -Force | Out-Null
|
|
Write-OK "Created: $dir"
|
|
}
|
|
}
|
|
|
|
# ── Step 2: Verify vmrun.exe ──────────────────────────────────────────────────
|
|
Write-Step "Verifying VMware Workstation (vmrun.exe)"
|
|
|
|
$vmrunPath = 'C:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe'
|
|
if (Test-Path $vmrunPath) {
|
|
Write-OK "vmrun.exe found: $vmrunPath"
|
|
} else {
|
|
Write-Fail "vmrun.exe NOT found at expected path: $vmrunPath"
|
|
Write-Host " Install VMware Workstation Pro, then re-run this script." -ForegroundColor Yellow
|
|
}
|
|
|
|
# ── Step 3: Install CredentialManager module ─────────────────────────────────
|
|
Write-Step "Checking PowerShell CredentialManager module"
|
|
|
|
$credMgr = Get-Module -ListAvailable -Name CredentialManager -ErrorAction SilentlyContinue
|
|
if ($credMgr) {
|
|
Write-OK "CredentialManager $($credMgr[0].Version) already installed"
|
|
} else {
|
|
Write-Host " Installing CredentialManager module (CurrentUser scope)..."
|
|
Install-Module -Name CredentialManager -Scope CurrentUser -Force -AllowClobber
|
|
Write-OK "CredentialManager installed"
|
|
}
|
|
|
|
Import-Module CredentialManager -ErrorAction SilentlyContinue
|
|
|
|
# ── Step 4: Store guest VM credentials ───────────────────────────────────────
|
|
Write-Step "Storing guest VM credentials in Windows Credential Manager"
|
|
|
|
$existingCred = Get-StoredCredential -Target $GuestCredentialTarget -ErrorAction SilentlyContinue
|
|
if ($existingCred) {
|
|
Write-OK "Credential '$GuestCredentialTarget' already exists (user: $($existingCred.UserName))"
|
|
Write-Warn "To update: Remove-StoredCredential -Target '$GuestCredentialTarget', then re-run."
|
|
} else {
|
|
if ($GuestPassword -eq '') {
|
|
Write-Host " No -GuestPassword supplied. Enter password for '$GuestUsername' (will be stored in Credential Manager):"
|
|
$secPwd = Read-Host -Prompt " Password" -AsSecureString
|
|
$bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($secPwd)
|
|
$GuestPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($bstr)
|
|
[System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr)
|
|
}
|
|
New-StoredCredential `
|
|
-Target $GuestCredentialTarget `
|
|
-UserName $GuestUsername `
|
|
-Password $GuestPassword `
|
|
-Persist LocalMachine | Out-Null
|
|
Write-OK "Credential '$GuestCredentialTarget' stored (user: $GuestUsername)"
|
|
}
|
|
|
|
# ── Step 5: Copy runner\config.yaml ──────────────────────────────────────────
|
|
Write-Step "Copying act_runner config.yaml to $CIRoot\act_runner\"
|
|
|
|
$destConfig = Join-Path $CIRoot 'act_runner\config.yaml'
|
|
if (Test-Path $ActRunnerConfigYaml) {
|
|
if (Test-Path $destConfig) {
|
|
Write-Warn "config.yaml already exists at $destConfig — skipping copy."
|
|
Write-Warn "Delete it manually if you want to overwrite."
|
|
} else {
|
|
Copy-Item -Path $ActRunnerConfigYaml -Destination $destConfig -Force
|
|
Write-OK "Copied $ActRunnerConfigYaml -> $destConfig"
|
|
}
|
|
} else {
|
|
Write-Warn "Source config.yaml not found at: $ActRunnerConfigYaml"
|
|
Write-Warn "Copy runner\config.yaml manually to $destConfig before starting the runner."
|
|
}
|
|
|
|
# ── Step 6: Install act_runner as Windows service (via NSSM) ─────────────────
|
|
if ($SkipRunnerInstall) {
|
|
Write-Host "`n=== act_runner service setup SKIPPED (-SkipRunnerInstall) ===" -ForegroundColor Yellow
|
|
}
|
|
else {
|
|
Write-Step "Installing act_runner as Windows service"
|
|
|
|
if (-not (Test-Path $ActRunnerExe)) {
|
|
Write-Warn "act_runner binary not found at: $ActRunnerExe"
|
|
Write-Warn "Download act_runner v1.0.2 from https://gitea.com/gitea/act_runner/releases"
|
|
Write-Warn "Place it at $ActRunnerExe, then re-run this script (or install the service manually)."
|
|
}
|
|
else {
|
|
# Check for NSSM
|
|
$nssm = Get-Command nssm -ErrorAction SilentlyContinue
|
|
if (-not $nssm) {
|
|
Write-Warn "NSSM not found in PATH. Attempting install via Chocolatey..."
|
|
$choco = Get-Command choco -ErrorAction SilentlyContinue
|
|
if ($choco) {
|
|
choco install nssm -y | Out-Null
|
|
$env:PATH = [System.Environment]::GetEnvironmentVariable('PATH','Machine') + ';' + $env:PATH
|
|
$nssm = Get-Command nssm -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
|
|
if (-not $nssm) {
|
|
Write-Fail "NSSM not available. Install NSSM manually (https://nssm.cc) and re-run."
|
|
}
|
|
else {
|
|
$serviceName = 'act_runner'
|
|
$existingSvc = Get-Service -Name $serviceName -ErrorAction SilentlyContinue
|
|
|
|
if ($existingSvc) {
|
|
Write-OK "Service '$serviceName' already exists (Status: $($existingSvc.Status))"
|
|
}
|
|
else {
|
|
$actRunnerDir = Split-Path $ActRunnerExe -Parent
|
|
|
|
# Register act_runner
|
|
if ($GiteaRunnerToken) {
|
|
Write-Host " Registering act_runner with Gitea ($GiteaUrl)..."
|
|
Push-Location $actRunnerDir
|
|
& $ActRunnerExe register `
|
|
--no-interactive `
|
|
--instance $GiteaUrl `
|
|
--token $GiteaRunnerToken `
|
|
--name 'local-windows-runner' `
|
|
--labels 'windows-build:host,dotnet:host,msbuild:host'
|
|
$regExit = $LASTEXITCODE
|
|
Pop-Location
|
|
if ($regExit -ne 0) {
|
|
Write-Fail "act_runner registration failed (exit $regExit). Check token and Gitea URL."
|
|
} else {
|
|
Write-OK "act_runner registered with Gitea."
|
|
}
|
|
} else {
|
|
Write-Warn "No -GiteaRunnerToken supplied — skipping registration."
|
|
Write-Warn "Register manually: cd $actRunnerDir ; .\act_runner.exe register"
|
|
}
|
|
|
|
# Install service
|
|
Write-Host " Installing service via NSSM..."
|
|
& nssm install $serviceName $ActRunnerExe 2>&1 | Out-Null
|
|
& nssm set $serviceName AppDirectory $actRunnerDir 2>&1 | Out-Null
|
|
& nssm set $serviceName AppParameters "daemon --config `"$destConfig`"" 2>&1 | Out-Null
|
|
& nssm set $serviceName DisplayName 'Gitea act_runner' 2>&1 | Out-Null
|
|
& nssm set $serviceName Description 'Gitea Actions runner (local CI/CD)' 2>&1 | Out-Null
|
|
& nssm set $serviceName Start SERVICE_AUTO_START 2>&1 | Out-Null
|
|
& nssm set $serviceName AppStdout (Join-Path $actRunnerDir 'logs\stdout.log') 2>&1 | Out-Null
|
|
& nssm set $serviceName AppStderr (Join-Path $actRunnerDir 'logs\stderr.log') 2>&1 | Out-Null
|
|
& nssm set $serviceName AppRotateFiles 1 2>&1 | Out-Null
|
|
& nssm set $serviceName AppRotateBytes 10485760 2>&1 | Out-Null # 10 MB
|
|
|
|
Start-Service -Name $serviceName -ErrorAction SilentlyContinue
|
|
$svc = Get-Service -Name $serviceName -ErrorAction SilentlyContinue
|
|
if ($svc -and $svc.Status -eq 'Running') {
|
|
Write-OK "Service '$serviceName' installed and running."
|
|
} else {
|
|
Write-Warn "Service installed but not running — check logs at $(Join-Path $actRunnerDir 'logs\')."
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# ── Step 7: SSH config alias for Gitea ───────────────────────────────────────
|
|
if ($SkipSSHConfig) {
|
|
Write-Host "`n=== SSH config SKIPPED (-SkipSSHConfig) ===" -ForegroundColor Yellow
|
|
}
|
|
else {
|
|
Write-Step "Configuring SSH alias for Gitea (gitea-ci)"
|
|
|
|
$sshDir = Join-Path $env:USERPROFILE '.ssh'
|
|
$sshConfig = Join-Path $sshDir 'config'
|
|
|
|
if (-not (Test-Path $sshDir)) {
|
|
New-Item -ItemType Directory -Path $sshDir -Force | Out-Null
|
|
}
|
|
|
|
$aliasBlock = @"
|
|
|
|
# Gitea CI alias — added by Setup-Host.ps1
|
|
Host gitea-ci
|
|
HostName $GiteaSSHHost
|
|
Port $GiteaSSHPort
|
|
User git
|
|
IdentityFile ~/.ssh/id_rsa
|
|
"@
|
|
|
|
$existingConfig = ''
|
|
if (Test-Path $sshConfig) {
|
|
$existingConfig = Get-Content $sshConfig -Raw -ErrorAction SilentlyContinue
|
|
}
|
|
|
|
if ($existingConfig -match 'Host gitea-ci') {
|
|
Write-OK "SSH alias 'gitea-ci' already present in $sshConfig"
|
|
} else {
|
|
Add-Content -Path $sshConfig -Value $aliasBlock -Encoding UTF8
|
|
Write-OK "SSH alias 'gitea-ci' written to $sshConfig"
|
|
Write-Warn "Ensure your public SSH key is registered in Gitea under User Settings → SSH Keys."
|
|
}
|
|
}
|
|
|
|
# ── Done ──────────────────────────────────────────────────────────────────────
|
|
Write-Host ""
|
|
Write-Host "============================================================" -ForegroundColor Green
|
|
Write-Host " Host setup complete!" -ForegroundColor Green
|
|
Write-Host "============================================================" -ForegroundColor Green
|
|
Write-Host ""
|
|
Write-Host " POST-SETUP CHECKLIST:" -ForegroundColor White
|
|
Write-Host ""
|
|
Write-Host " 1. Place act_runner binary (if not done):"
|
|
Write-Host " $ActRunnerExe"
|
|
Write-Host " Download: https://gitea.com/gitea/act_runner/releases/tag/v1.0.2"
|
|
Write-Host ""
|
|
Write-Host " 2. Place Windows Server 2025 ISO:"
|
|
Write-Host " $CIRoot\ISO\"
|
|
Write-Host ""
|
|
Write-Host " 3. Provision the template VM:"
|
|
Write-Host " a. Create VM in VMware Workstation (4 vCPU, 6 GB RAM, 80 GB thin)"
|
|
Write-Host " VMX: $CIRoot\Templates\WinBuild\WinBuild2025.vmx"
|
|
Write-Host " NIC: VMnet8 (NAT) — internet required during provisioning"
|
|
Write-Host " b. Install Windows Server 2025 + enable WinRM inside VM"
|
|
Write-Host " c. From this host run:"
|
|
Write-Host " cd $scriptDir\template"
|
|
Write-Host " .\Prepare-WinBuild2025.ps1 -VMIPAddress <VM_IP> -SkipWindowsUpdate"
|
|
Write-Host " d. Shut down VM, take snapshot named exactly: BaseClean"
|
|
Write-Host ""
|
|
Write-Host " 4. Register SSH key with Gitea:"
|
|
Write-Host " Gitea UI → User Settings → SSH Keys → Add Key"
|
|
Write-Host " (key: $env:USERPROFILE\.ssh\id_rsa.pub)"
|
|
Write-Host ""
|
|
Write-Host " 5. Verify act_runner is Online in Gitea:"
|
|
Write-Host " $GiteaUrl/-/admin/runners"
|
|
Write-Host ""
|
|
|