- ARCHITECTURE.md: fix WinRM subnet (192.168.1179), VM box shows VS 2026/Python 3.13/unzip+python build instead of .NET SDK 8/git clone/dotnet build - BEST-PRACTICES.md: fix VMnet11/192.168.11 VMnet8 NAT/192.168.79 - CI-FLOW.md: Step 3 workflow YAML updated to actual Invoke-CIJob.ps1 params (Submodules, BuildCommand, GuestArtifactSource); note that -VMIPAddress is auto-detected - OPTIMIZATION.md: BaseClean tier updated to VS BuildTools 2026, .NET SDK 10.0.203, Python 3.13.3 - gitea/workflow-example.yml: remove BUILD_VM_IP (auto-detected), replace -VMIPAddress/-Configuration with -Submodules/-BuildCommand/-GuestArtifactSource
9.6 KiB
CI Pipeline Flow — Step by Step
Trigger to Artifact: Complete Pipeline Walk-Through
Step 0 — Prerequisites (one-time setup)
Before any CI job runs, the following must be in place:
| Component | State |
|---|---|
| Gitea server | Running, accessible on LAN |
| act_runner | Registered, running as Windows service on host |
| Template VM | Provisioned, snapshot "BaseClean" taken, VM powered off |
| WinRM | Enabled on template VM (inherits to all clones) |
| vmrun.exe | Present at C:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe |
F:\CI\BuildVMs\ |
Directory exists and writable |
F:\CI\Artifacts\ |
Directory exists and writable |
Step 1 — Developer Pushes Code
Developer → git push origin main
- Git push lands on Gitea server
- Gitea evaluates
.gitea/workflows/build.ymlin the repository - If the workflow trigger matches (e.g.
on: pushtomain), Gitea enqueues a new Actions job - Job is tagged with the label defined in the workflow (
runs-on: windows-build)
Step 2 — act_runner Picks Up the Job
act_runner (host) ─polling─► Gitea Actions API
◄─job data─
- act_runner polls the Gitea API approximately every 5–10 seconds
- Detects a pending job matching its labels (
windows-build) - Downloads the workflow YAML and job parameters
- If
capacityslots are available, starts job execution immediately - If all slots are busy, job waits in Gitea queue until a slot frees
Step 3 — act_runner Executes Workflow Steps
The workflow YAML defines steps that run as PowerShell commands on the host.
The first substantive step calls Invoke-CIJob.ps1:
- name: Run CI Job
shell: pwsh
run: |
& 'N:\Code\Workspace\Local-CI-CD-System\scripts\Invoke-CIJob.ps1' `
-JobId '${{ github.run_id }}' `
-RepoUrl 'ssh://gitea-ci/Simone/<repo>.git' `
-Branch '${{ github.ref_name }}' `
-Commit '${{ github.sha }}' `
-TemplatePath $env:GITEA_CI_TEMPLATE_PATH `
-Submodules `
-BuildCommand 'python build_plugin.py --final --dist-dir dist' `
-GuestArtifactSource 'dist'
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
Il parametro
-VMIPAddressnon è più necessario — l'IP viene rilevato automaticamente tramitevmrun getGuestIPAddressdopo l'avvio della VM.
Step 4 — Invoke-CIJob.ps1: Clone VM
New-BuildVM.ps1
vmrun.exe -T ws clone <template.vmx> <clone.vmx> linked -snapshot "BaseClean"
- Generates unique clone name:
Clone_{JobId}_{yyyyMMdd_HHmmss} - Creates clone directory under
F:\CI\BuildVMs\ - Linked clone creation takes ~5–15 seconds
- The template snapshot "BaseClean" is not modified
- Returns the full path to the new
.vmxfile
Step 5 — Start VM
vmrun.exe -T ws start <clone.vmx> nogui
- Starts the cloned VM in headless (no GUI) mode
- VM boots from the "BaseClean" state: clean OS, build tools installed, WinRM enabled
- Boot takes ~20–40 seconds
Step 6 — Wait for VM Readiness
Wait-VMReady.ps1 -VMPath <clone.vmx> -IPAddress <dynamic-NAT-ip> -TimeoutSeconds 300
IP is discovered dynamically via vmrun getGuestIPAddress (VMnet8 NAT, subnet 192.168.79.0/24).
No hardcoded IPs — each clone gets a DHCP-assigned address from VMware NAT.
Three-phase readiness check (retried every 5 seconds, up to 300s timeout):
Phase 1: vmrun getState → must return "running"
Phase 2: Test-Connection (ICMP ping) → must succeed
Phase 3: Test-WSMan → WinRM listener must respond
- Total wait is typically 30–90 seconds after
vmrun start - If timeout exceeded, VM is destroyed and job fails with a clear error message
- On success, IP and credentials are ready for PSSession
Step 7 — Remote Build Execution
Invoke-RemoteBuild.ps1
-IPAddress <dynamic-NAT-ip> # from vmrun getGuestIPAddress
-Credential (from Windows Credential Manager)
-BuildCommand 'python build_plugin.py --final --dist-dir dist'
-GuestArtifactSource 'dist'
Sequence inside the PSSession:
1. New-PSSession → open WinRM session to VM
2. Compress-Archive (host) → copy zip to C:\CI\build.zip (guest) → Expand-Archive to C:\CI\build\
(source code è già clonato sull'host in Phase 1 — non si fa git clone nella VM)
3. Invoke-Expression "$BuildCommand" in C:\CI\build\
Esempio: python build_plugin.py --final --dist-dir dist
a. Build 4 configurazioni MSBuild in parallelo (x86-unicode, x64-unicode, cli-x86, cli-x64)
b. Raccoglie artifact in C:\CI\build\dist\
c. Cleanup build dir (--final)
4. Compress-Archive C:\CI\build\$GuestArtifactSource C:\CI\output\artifacts.zip
5. Cattura exit code; se non-zero il job fallisce con errore chiaro
6. Remove-PSSession
- Output standard e stderr dalla build sono streamati all'host e catturati da act_runner per i log Gitea Actions
- Se la build fallisce (exit code ≠ 0), il controllo passa al blocco
finally
Step 8 — Collect Artifacts
Get-BuildArtifacts.ps1
-IPAddress <dynamic-NAT-ip>
-Credential (same as above)
-GuestArtifactPath C:\CI\output\artifacts.zip
-HostArtifactDir F:\CI\Artifacts\{JobId}\
- Apre PSSession e copia
C:\CI\output\artifacts.zipdall'host - Valida che il file esista e sia non-vuoto
- Il zip contiene la struttura
dist/con tutti gli artifact di build
Step 9 — Destroy VM
Remove-BuildVM.ps1 -VMPath <clone.vmx>
Sequence:
1. vmrun stop <clone.vmx> soft (graceful shutdown attempt)
2. Wait 5 seconds
3. vmrun getState → if still "running":
vmrun stop <clone.vmx> hard (force off)
4. vmrun deleteVM <clone.vmx> (removes VMX + VMDK delta files)
5. Remove-Item <clone dir> -Recurse (removes any leftover files)
- VM is gone from disk within ~5–10 seconds
- No state, no temp files, no credentials remain in the clone
- Template VM and "BaseClean" snapshot are unaffected
Note: This step runs in a
finallyblock insideInvoke-CIJob.ps1, so it executes even if Steps 6–8 failed. A build VM is never left running.
Step 10 — Report Status & Upload Artifacts
act_runner ─► Gitea Actions API (job status: success / failure)
act_runner ─► Gitea artifact store (uploads F:\CI\Artifacts\{JobId}\*)
- act_runner reads the exit code from
Invoke-CIJob.ps1 - Reports
successorfailureto Gitea - Uploads artifacts using
actions/upload-artifactstep in the workflow YAML - Build log (stdout from all steps) is stored in Gitea Actions UI
- Commit/PR status badge is updated
Parallel Build Flow
When multiple pushes happen concurrently (or multiple PRs are open):
act_runner (capacity: 4)
├── Job #1 ──► Clone_Job_001 (192.168.79.x) → build → destroy
├── Job #2 ──► Clone_Job_002 (192.168.79.x) → build → destroy
├── Job #3 ──► Clone_Job_003 (192.168.79.x) → build → destroy
└── Job #4 ──► Clone_Job_004 (192.168.79.x) → build → destroy
(Job #5 waits in Gitea queue)
Note: each clone gets a dynamic IP from VMware NAT DHCP (192.168.79.0/24). IP is discovered
automatically via vmrun getGuestIPAddress — no static assignment required.
- Each job creates its own independently named clone
- Clones share the parent "BaseClean" snapshot (read-only CoW)
- No filesystem or process overlap between concurrent builds
- act_runner enforces the
capacitylimit
Failure Scenarios
| Failure Point | Behavior |
|---|---|
| VM clone fails | Invoke-CIJob.ps1 throws; no VM to destroy; job fails immediately |
| VM never reaches ready state (timeout) | Wait-VMReady.ps1 throws; finally destroys the (running) VM |
| Build fails inside VM | Invoke-RemoteBuild.ps1 throws; finally collects partial artifacts (if any), destroys VM |
| Artifact collection fails | Job marked failed; VM is still destroyed in finally |
| Host machine loses power mid-job | act_runner resumes on reboot, marks in-flight job as failed; orphan VMs must be cleaned up manually (see TODO.md) |
Timing Summary (typical build)
| Phase | Typical Duration |
|---|---|
| Gitea → act_runner poll | 5–10 s |
| Linked clone creation | 5–15 s |
| VM boot | 20–40 s |
| WinRM readiness | 10–30 s (after boot) |
| git clone (from local Gitea) | 2–10 s |
| dotnet restore + build | project-dependent |
| Artifact collection | 2–10 s |
| VM destruction | 5–10 s |
| Overhead total | ~60–120 s |