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>
201 lines
8.3 KiB
PowerShell
201 lines
8.3 KiB
PowerShell
#Requires -Version 5.1
|
|
<#
|
|
.SYNOPSIS
|
|
Copies pre-cloned source from host into a build VM and runs dotnet build.
|
|
|
|
.DESCRIPTION
|
|
The caller (Invoke-CIJob.ps1) is responsible for cloning the repository
|
|
on the HOST before calling this script. This script then:
|
|
1. Compresses the local source tree on the host, copies the archive into
|
|
the VM via WinRM (single file = much faster than recursive Copy-Item),
|
|
then expands it inside the VM.
|
|
2. Runs dotnet restore and dotnet build inside the VM
|
|
3. Packages the output as C:\CI\output\artifacts.zip inside the VM
|
|
|
|
The build VM does NOT need internet access or git. All network I/O happens
|
|
on the host before this script is called.
|
|
|
|
.PARAMETER IPAddress
|
|
IP of the build VM (e.g. 192.168.11.101).
|
|
|
|
.PARAMETER Credential
|
|
PSCredential for the build user inside the VM.
|
|
Retrieve from Windows Credential Manager in production:
|
|
$cred = Get-StoredCredential -Target 'BuildVMGuest'
|
|
|
|
.PARAMETER HostSourceDir
|
|
Full path on the HOST to the already-cloned repository directory.
|
|
This directory is copied into the VM as the build working directory.
|
|
Example: C:\Users\user\AppData\Local\Temp\ci-src-run-42
|
|
|
|
.PARAMETER Configuration
|
|
MSBuild/dotnet build configuration. Default: Release
|
|
|
|
.PARAMETER GuestWorkDir
|
|
Working directory inside the VM where source will be placed.
|
|
Default: C:\CI\build
|
|
|
|
.PARAMETER GuestArtifactZip
|
|
Full path inside the VM where the artifact archive will be written.
|
|
Default: C:\CI\output\artifacts.zip
|
|
|
|
.EXAMPLE
|
|
$cred = Get-Credential
|
|
.\Invoke-RemoteBuild.ps1 `
|
|
-IPAddress "192.168.11.101" `
|
|
-Credential $cred `
|
|
-HostSourceDir "C:\Temp\ci-src-run-42"
|
|
#>
|
|
[CmdletBinding()]
|
|
param(
|
|
[Parameter(Mandatory)]
|
|
[ValidatePattern('^(\d{1,3}\.){3}\d{1,3}$')]
|
|
[string] $IPAddress,
|
|
|
|
[Parameter(Mandatory)]
|
|
[System.Management.Automation.PSCredential] $Credential,
|
|
|
|
[Parameter(Mandatory)]
|
|
[ValidateScript({ Test-Path $_ -PathType Container })]
|
|
[string] $HostSourceDir,
|
|
|
|
[string] $Configuration = 'Release',
|
|
|
|
# Custom build command to run inside the VM (working dir = GuestWorkDir).
|
|
# When empty, defaults to: dotnet restore + dotnet build --configuration $Configuration
|
|
# Example: 'python build_plugin.py --final'
|
|
[string] $BuildCommand = '',
|
|
|
|
# Glob pattern (relative to GuestWorkDir) of files to include in the artifact zip.
|
|
# Used only when BuildCommand is set. Default: collect everything under 'dist'.
|
|
# Example: 'dist\**' or 'Contrib\nsInnoUnp\Build\**'
|
|
[string] $GuestArtifactSource = 'dist',
|
|
|
|
[string] $GuestWorkDir = 'C:\CI\build',
|
|
|
|
[string] $GuestArtifactZip = 'C:\CI\output\artifacts.zip'
|
|
)
|
|
|
|
Set-StrictMode -Version Latest
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
# ── Session options: skip SSL certificate checks for self-signed (lab use) ──
|
|
$sessionOptions = New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck
|
|
|
|
Write-Host "[Invoke-RemoteBuild] Connecting to VM at $IPAddress..."
|
|
|
|
$session = New-PSSession `
|
|
-ComputerName $IPAddress `
|
|
-Credential $Credential `
|
|
-SessionOption $sessionOptions `
|
|
-ErrorAction Stop
|
|
|
|
try {
|
|
# ── Ensure working directories exist on guest ────────────────────────
|
|
Invoke-Command -Session $session -ScriptBlock {
|
|
param($workDir, $outputDir)
|
|
# Ensure artifact output parent exists
|
|
$outParent = Split-Path $outputDir -Parent
|
|
if (-not (Test-Path $outParent)) {
|
|
New-Item -ItemType Directory -Path $outParent -Force | Out-Null
|
|
}
|
|
# Clean and recreate build dir (ephemeral VM, but guard against partial prior run)
|
|
if (Test-Path $workDir) { Remove-Item $workDir -Recurse -Force }
|
|
New-Item -ItemType Directory -Path $workDir -Force | Out-Null
|
|
} -ArgumentList $GuestWorkDir, $GuestArtifactZip
|
|
|
|
# ── Copy source tree: compress on host, transfer zip, expand in guest ──
|
|
# Single-file transfer over WinRM is far faster than recursive Copy-Item.
|
|
$hostZip = [System.IO.Path]::GetTempFileName() -replace '\.tmp$', '.zip'
|
|
try {
|
|
Write-Host "[Invoke-RemoteBuild] Compressing source on host..."
|
|
Compress-Archive -Path "$HostSourceDir\*" -DestinationPath $hostZip -CompressionLevel Fastest -Force
|
|
$zipSize = [math]::Round((Get-Item $hostZip).Length / 1MB, 1)
|
|
Write-Host "[Invoke-RemoteBuild] Transferring archive to guest ($zipSize MB)..."
|
|
$guestZip = 'C:\CI\src-transfer.zip'
|
|
Copy-Item -Path $hostZip -Destination $guestZip -ToSession $session -Force
|
|
Write-Host "[Invoke-RemoteBuild] Expanding archive in guest..."
|
|
Invoke-Command -Session $session -ScriptBlock {
|
|
param($zip, $dest)
|
|
Expand-Archive -Path $zip -DestinationPath $dest -Force
|
|
Remove-Item $zip -Force
|
|
} -ArgumentList $guestZip, $GuestWorkDir
|
|
Write-Host "[Invoke-RemoteBuild] Source transfer complete."
|
|
}
|
|
finally {
|
|
Remove-Item $hostZip -Force -ErrorAction SilentlyContinue
|
|
}
|
|
|
|
# ── Build ──────────────────────────────────────────────────────────
|
|
if ($BuildCommand -ne '') {
|
|
# ── Custom build command (e.g. python, msbuild, cmake…) ──────────
|
|
Write-Host "[Invoke-RemoteBuild] Running build command: $BuildCommand"
|
|
$buildExit = Invoke-Command -Session $session -ScriptBlock {
|
|
param($workDir, $cmd, $artifactZip, $artifactSrc)
|
|
# Unbuffered Python output — lines appear in real-time instead of being held in buffer
|
|
$env:PYTHONUNBUFFERED = '1'
|
|
Set-Location $workDir
|
|
# Stream each output line via Write-Host — WinRM forwards Write-Host in real-time
|
|
& cmd /c "$cmd 2>&1" | ForEach-Object { Write-Host $_ }
|
|
$exit = $LASTEXITCODE
|
|
|
|
if ($exit -eq 0) {
|
|
$archiveDir = Split-Path $artifactZip -Parent
|
|
if (-not (Test-Path $archiveDir)) {
|
|
New-Item -ItemType Directory -Path $archiveDir -Force | Out-Null
|
|
}
|
|
$srcPath = Join-Path $workDir $artifactSrc
|
|
Compress-Archive -Path $srcPath -DestinationPath $artifactZip -Force
|
|
}
|
|
|
|
return $exit
|
|
} -ArgumentList $GuestWorkDir, $BuildCommand, $GuestArtifactZip, $GuestArtifactSource
|
|
|
|
if ($buildExit -ne 0) {
|
|
throw "Build command failed (exit $buildExit)"
|
|
}
|
|
}
|
|
else {
|
|
# ── Default: dotnet restore + dotnet build ────────────────────────
|
|
Write-Host "[Invoke-RemoteBuild] Running dotnet restore..."
|
|
$restoreExit = Invoke-Command -Session $session -ScriptBlock {
|
|
param($workDir)
|
|
Set-Location $workDir
|
|
dotnet restore 2>&1 | ForEach-Object { Write-Host $_ }
|
|
return $LASTEXITCODE
|
|
} -ArgumentList $GuestWorkDir
|
|
|
|
if ($restoreExit -ne 0) {
|
|
throw "dotnet restore failed (exit $restoreExit)"
|
|
}
|
|
|
|
Write-Host "[Invoke-RemoteBuild] Running dotnet build (configuration: $Configuration)..."
|
|
$buildExit = Invoke-Command -Session $session -ScriptBlock {
|
|
param($workDir, $config, $artifactZip)
|
|
Set-Location $workDir
|
|
$outputDir = Join-Path $workDir 'bin\CIOutput'
|
|
dotnet build --configuration $config --output $outputDir 2>&1 | ForEach-Object { Write-Host $_ }
|
|
$exit = $LASTEXITCODE
|
|
|
|
if ($exit -eq 0) {
|
|
$archiveDir = Split-Path $artifactZip -Parent
|
|
if (-not (Test-Path $archiveDir)) {
|
|
New-Item -ItemType Directory -Path $archiveDir -Force | Out-Null
|
|
}
|
|
Compress-Archive -Path "$outputDir\*" -DestinationPath $artifactZip -Force
|
|
}
|
|
|
|
return $exit
|
|
} -ArgumentList $GuestWorkDir, $Configuration, $GuestArtifactZip
|
|
|
|
if ($buildExit -ne 0) {
|
|
throw "dotnet build failed (exit $buildExit)"
|
|
}
|
|
}
|
|
|
|
Write-Host "[Invoke-RemoteBuild] Build succeeded. Artifact at $GuestArtifactZip"
|
|
}
|
|
finally {
|
|
Remove-PSSession $session -ErrorAction SilentlyContinue
|
|
}
|