chore: initial commit local CI/CD system (e2e verified, production-ready)
Sistema CI locale basato su VMware Workstation + Gitea Actions + act_runner. Testato e2e con nsis-plugin-nsinnounp (MSBuild + Python, 4 configurazioni parallele). - scripts/: Invoke-CIJob, Invoke-RemoteBuild, New/Remove-BuildVM, Wait-VMReady, Get-BuildArtifacts - runner/: act_runner config (windows-build label, capacity 4) - gitea/workflows/: build-nsis.yml (template per progetti MSBuild/Python) - template/: script di provisioning template VM (VS BuildTools 2026, .NET SDK 10, Python 3.13) - docs/: ARCHITECTURE, CI-FLOW, OPTIMIZATION, BEST-PRACTICES, Setup-GiteaSSH Verificato: e2e-009 SUCCESS in 02:09, cleanup automatico VM confermato.
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
#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)
|
||||
foreach ($dir in @($workDir, (Split-Path $outputDir -Parent))) {
|
||||
if (-not (Test-Path $dir)) {
|
||||
New-Item -ItemType Directory -Path $dir -Force | Out-Null
|
||||
}
|
||||
}
|
||||
# Clean previous build if present
|
||||
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"
|
||||
$buildResult = Invoke-Command -Session $session -ScriptBlock {
|
||||
param($workDir, $cmd, $artifactZip, $artifactSrc)
|
||||
Set-Location $workDir
|
||||
$output = Invoke-Expression "$cmd 2>&1" | ForEach-Object { "$_" }
|
||||
$buildExit = $LASTEXITCODE
|
||||
|
||||
if ($buildExit -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 @{ ExitCode = $buildExit; Output = ($output -join "`n") }
|
||||
} -ArgumentList $GuestWorkDir, $BuildCommand, $GuestArtifactZip, $GuestArtifactSource
|
||||
|
||||
Write-Host $buildResult.Output
|
||||
if ($buildResult.ExitCode -ne 0) {
|
||||
throw "Build command failed (exit $($buildResult.ExitCode))"
|
||||
}
|
||||
}
|
||||
else {
|
||||
# ── Default: dotnet restore + dotnet build ────────────────────────
|
||||
Write-Host "[Invoke-RemoteBuild] Running dotnet restore..."
|
||||
$restoreResult = Invoke-Command -Session $session -ScriptBlock {
|
||||
param($workDir)
|
||||
Set-Location $workDir
|
||||
$output = dotnet restore 2>&1
|
||||
return @{ ExitCode = $LASTEXITCODE; Output = ($output -join "`n") }
|
||||
} -ArgumentList $GuestWorkDir
|
||||
|
||||
Write-Host $restoreResult.Output
|
||||
if ($restoreResult.ExitCode -ne 0) {
|
||||
throw "dotnet restore failed (exit $($restoreResult.ExitCode))"
|
||||
}
|
||||
|
||||
Write-Host "[Invoke-RemoteBuild] Running dotnet build (configuration: $Configuration)..."
|
||||
$buildResult = Invoke-Command -Session $session -ScriptBlock {
|
||||
param($workDir, $config, $artifactZip)
|
||||
Set-Location $workDir
|
||||
$outputDir = Join-Path $workDir 'bin\CIOutput'
|
||||
$output = dotnet build --configuration $config --output $outputDir 2>&1
|
||||
$buildExit = $LASTEXITCODE
|
||||
|
||||
if ($buildExit -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 @{ ExitCode = $buildExit; Output = ($output -join "`n") }
|
||||
} -ArgumentList $GuestWorkDir, $Configuration, $GuestArtifactZip
|
||||
|
||||
Write-Host $buildResult.Output
|
||||
if ($buildResult.ExitCode -ne 0) {
|
||||
throw "dotnet build failed (exit $($buildResult.ExitCode))"
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "[Invoke-RemoteBuild] Build succeeded. Artifact at $GuestArtifactZip"
|
||||
}
|
||||
finally {
|
||||
Remove-PSSession $session -ErrorAction SilentlyContinue
|
||||
}
|
||||
Reference in New Issue
Block a user