powershell.exe does not exist on Linux — child process launch failed instantly (ExitCode null, Elapsed 00:00). Now selects powershell.exe on Windows and pwsh on Linux via $IsWindows automatic variable. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Local CI/CD System
Sistema CI/CD self-hosted su Windows per build isolati in VM efimere VMware.
Stato: production-ready — build Windows e Linux verificati e2e; burn-in concorrente a 4 job PASS; §3.3 in-VM git clone attivabile via -UseGitClone
Panoramica
Il runner act_runner riceve i job da Gitea Actions.
Per ogni job, lo script Invoke-CIJob.ps1 orchestra l'intero ciclo:
Gitea push/tag
│
▼
act_runner (label: windows-build | linux-build)
│
▼
Invoke-CIJob.ps1
│
├─ 1. git clone repo su host (con submoduli)
├─ 2. Linked clone da snapshot BaseClean / BaseClean-Linux
├─ 3. Avvio VM headless
├─ 4. Attesa readiness (WinRM/5986 per Windows, SSH/22 per Linux)
├─ 5. Source transfer + build (WinRM per Windows, SSH/SCP per Linux)
├─ 6. Copia artifacts in F:\CI\Artifacts\<jobid>\
└─ finally: stop + rimozione VM clone
Ogni build gira in una VM usa e getta ripristinata dallo snapshot: Windows Server 2025/2022 (WinRM) o Ubuntu 24.04 (SSH).
Requisiti Host
| Componente | Versione / Dettaglio |
|---|---|
| OS | Windows 11 |
| CPU/RAM | i9-10900X, 64 GB RAM |
| VMware Workstation Pro | installato, vmrun.exe disponibile |
| PowerShell | 5.1 |
| act_runner | v1.0.2 — F:\CI\act_runner\act_runner.exe |
| Gitea | self-hosted http://10.10.20.11:3100 / https://gitea.emulab.it |
Template VM
Windows (snapshot BaseClean)
| Componente | Versione |
|---|---|
| OS | Windows Server 2025 |
| VMX | F:\CI\Templates\WinBuild2025\WinBuild2025.vmx (env GITEA_CI_TEMPLATE_PATH) |
| VS Build Tools | 2026 (MSBuild 18.5.4 / toolset v145) |
| .NET SDK | 10.0.203 |
| Python | 3.13.3 |
| Rete | VMnet8 NAT — 192.168.79.0/24 |
| WinRM | HTTPS/5986, Basic auth, self-signed cert (SkipCACheck — lab-only) |
Variante Windows Server 2022: *WinBuild2022.ps1 + VMX F:\CI\Templates\WinBuild2022\WinBuild2022.vmx.
Linux (snapshot BaseClean-Linux)
| Componente | Versione |
|---|---|
| OS | Ubuntu 24.04 LTS (cloud image) |
| VMX | F:\CI\Templates\LinuxBuild2404\LinuxBuild2404.vmx (env GITEA_CI_LINUX_TEMPLATE_PATH) |
| Toolchain | GCC, G++, Clang, CMake, Ninja, Python 3, Git, 7-Zip |
| Rete | VMnet8 NAT — 192.168.79.0/24 |
| SSH | porta 22, utente ci_build, chiave F:\CI\keys\ci_linux |
| Runner label | linux-build:host |
Struttura repository
├── Setup-Host.ps1 # Bootstrap host: dirs F:\CI\, credenziali, act_runner service
├── PSScriptAnalyzerSettings.psd1 # Regole linting condivise (§5.4)
│
├── scripts/
│ ├── _Common.psm1 # Shared module (vmrun wrapper, diagnostics)
│ ├── _Transport.psm1 # SSH/SCP helpers per guest Linux (Invoke-SshCommand, Copy-SshItem)
│ ├── Invoke-CIJob.ps1 # Orchestratore principale (Windows WinRM + Linux SSH, -UseGitClone)
│ ├── New-BuildVM.ps1 # Linked clone da template
│ ├── Wait-VMReady.ps1 # Polling readiness (WinRM/5986 o SSH/22)
│ ├── Invoke-RemoteBuild.ps1 # Build remoto (WinRM per Windows, SSH/SCP per Linux)
│ ├── Get-BuildArtifacts.ps1 # Raccolta artifacts (WinRM o SCP)
│ ├── Remove-BuildVM.ps1 # Stop + rimozione VM clone (-WhatIf)
│ ├── Cleanup-OrphanedBuildVMs.ps1 # Cleanup VM orfane (§2.2)
│ ├── Invoke-RetentionPolicy.ps1 # Retention artifact + log (§2.3)
│ ├── Watch-DiskSpace.ps1 # Alert spazio libero F: (§4.3)
│ ├── Watch-RunnerHealth.ps1 # Health check runner (§2.7)
│ ├── Backup-CITemplate.ps1 # Backup VMDK template (§2.6)
│ ├── Set-TemplateSharedFolders.ps1 # Cache NuGet/pip via shared folders (§3.1)
│ ├── Measure-CIBenchmark.ps1 # Benchmark fasi pipeline (§3.6)
│ ├── Register-CIScheduledTasks.ps1 # Task Scheduler bootstrap
│ ├── Test-CapacityBurnIn.ps1 # Burn-in concorrente N job × R round
│ ├── Start-BurnInTest-Linux.ps1 # Wrapper burn-in specifico per LinuxBuild2404
│ └── Get-CIJobSummary.ps1 # Report sintetico ultima esecuzione job
│
├── template/
│ ├── autounattend.template.xml # Template XML per installazione Windows unattended
│ ├── Deploy-WinBuild2025.ps1 # Deploy VM + install Windows 2025 unattended (HOST)
│ ├── Prepare-WinBuild2025.ps1 # Provisioning toolchain 2025 via WinRM (HOST-side)
│ ├── Install-CIToolchain-WinBuild2025.ps1 # Toolchain CI 2025 nella VM (GUEST-side)
│ ├── Deploy-WinBuild2022.ps1 # Deploy VM + install Windows 2022 unattended (HOST)
│ ├── Prepare-WinBuild2022.ps1 # Provisioning toolchain 2022 via WinRM (HOST-side)
│ ├── Install-CIToolchain-WinBuild2022.ps1 # Toolchain CI 2022 nella VM (GUEST-side)
│ ├── Deploy-LinuxBuild2404.ps1 # Deploy cloud image Ubuntu 24.04 su VMware (HOST)
│ ├── Prepare-LinuxBuild2404.ps1# Provisioning toolchain Linux via SSH + snapshot (HOST-side)
│ ├── Install-CIToolchain-Linux2404.sh # Toolchain CI Ubuntu 24.04 nel guest (GUEST-side)
│ ├── Validate-DeployState.ps1 # Validazione stato post-Deploy (2025, 2022, Linux)
│ └── Validate-SetupState.ps1 # Validazione stato post-Setup (pre-snapshot)
│
├── tests/ # Pester v5 unit tests (§5.1)
│
├── gitea/
│ ├── actions/
│ │ └── local-ci-build/action.yml # Composite action riusabile (wrap Invoke-CIJob.ps1)
│ ├── workflows/
│ │ ├── build-ns7zip.yml # Workflow matrix Windows+Linux per nsis-plugin-ns7zip
│ │ └── lint.yml # PSScriptAnalyzer su push/PR (§5.4)
│ └── workflow-example.yml # Template generico adattabile
│
├── runner/
│ ├── config.yaml # Configurazione act_runner (no secrets)
│ └── Install-Runner.ps1
│
├── docs/
│ ├── ARCHITECTURE.md # Diagramma sistema e componenti
│ ├── CI-FLOW.md # Flusso dettagliato passo-passo
│ ├── HOST-SETUP.md # Bootstrap macchina host
│ ├── WINDOWS-TEMPLATE-SETUP.md # Provisioning template Windows
│ ├── LINUX-TEMPLATE-SETUP.md # Bozza template Linux (§6.1, non implementato)
│ ├── OPTIMIZATION.md # Layout disco, snapshot, cache, tuning
│ ├── BEST-PRACTICES.md # Sicurezza, WinRM, credenziali, threat model
│ ├── RUNBOOK.md # Triage incident comuni (§4.4)
│ ├── TEST-PLAN-v1.3-to-HEAD.md # Test plan completo features post-v1.3
│ ├── Setup-GiteaSSH.md # Configurazione SSH alias per Gitea
│ └── archived/ # Doc storici / sign-off
│
└── TODO.md # Roadmap, audit trail task completati, backlog
Python orchestrator (Phase A — in progress)
A new Python 3.11+ rewrite of the orchestrator lives under src/ci_orchestrator/.
Phase A (see implementation-plan-A-B) is on
branch feature/python-rewrite-and-linux-migration. As of A5 the Python CLI is the
production entry point invoked by gitea/actions/local-ci-build/action.yml;
the PowerShell scripts in scripts/ are 3-line shims that forward to the
Python sub-commands listed below.
Setup
# Production venv on the runner host (one-time + on every code deploy).
# NON-editable: act_runner runs as LocalSystem and cannot resolve an
# editable .pth into the dev tree or a transient RunnerWork dir
# (-> "No module named ci_orchestrator"). Re-run `pip install .` after
# each code change. No CI workflow may install into this venv.
python -m venv F:\CI\python\venv
F:\CI\python\venv\Scripts\python.exe -m pip install --upgrade pip
F:\CI\python\venv\Scripts\python.exe -m pip install .
# Local dev venv (NOT committed; see .gitignore) — editable is fine here
python -m venv .venv
.\.venv\Scripts\python.exe -m pip install -e ".[dev]"
Configuration: copy config.example.toml to
F:\CI\config.toml (or set $env:CI_CONFIG). Environment variables
(CI_ROOT, CI_TEMPLATES, CI_BUILD_VMS, CI_ARTIFACTS, CI_KEYS)
override the file. Dependency manifest and tool config (ruff, mypy,
pytest, coverage) live in pyproject.toml.
CLI sub-commands
# Discover commands
python -m ci_orchestrator --help
# Wait for a VM to become reachable (WinRM 5986 or SSH 22 — auto-detected)
python -m ci_orchestrator wait-ready `
--vmx F:\CI\BuildVMs\smoke\smoke.vmx --guest-os windows --timeout 300
# Linked-clone lifecycle
python -m ci_orchestrator vm new `
--template-path F:\CI\Templates\WinBuild2025\WinBuild2025.vmx `
--snapshot-name BaseClean `
--clone-base-dir F:\CI\BuildVMs `
--job-id smoke-001
python -m ci_orchestrator vm remove --vmx F:\CI\BuildVMs\Clone_smoke-001_*\Clone_smoke-001_*.vmx
python -m ci_orchestrator vm cleanup --max-age-hours 12
# Build + artifact collection
python -m ci_orchestrator build run `
--vmx F:\CI\BuildVMs\Clone_smoke-001_*\*.vmx `
--script .\build.ps1 --workdir C:\build --guest-os windows
python -m ci_orchestrator artifacts collect `
--vmx F:\CI\BuildVMs\Clone_smoke-001_*\*.vmx `
--remote-path C:\build\dist --local-dir F:\CI\Artifacts\smoke-001
# Monitoring (driven by Task Scheduler / systemd timers in B5)
python -m ci_orchestrator monitor disk --min-free-gb 50 --drive-letter F
python -m ci_orchestrator monitor runner --state-dir F:\CI\act_runner
# Reporting
python -m ci_orchestrator report job --job-id smoke-001
# End-to-end orchestrator (replaces Invoke-CIJob.ps1)
python -m ci_orchestrator job `
--job-id smoke-001 `
--repo-url ssh://gitea-ci/Simone/nsis-plugin-ns7zip.git `
--branch main `
--template-path F:\CI\Templates\WinBuild2025\WinBuild2025.vmx `
--build-command "python build_plugin.py --final --dist-dir dist" `
--guest-artifact-source dist
The PowerShell wrappers (scripts\Invoke-CIJob.ps1,
scripts\New-BuildVM.ps1, scripts\Wait-VMReady.ps1,
scripts\Remove-BuildVM.ps1, scripts\Cleanup-OrphanedBuildVMs.ps1,
scripts\Invoke-RemoteBuild.ps1, scripts\Get-BuildArtifacts.ps1,
scripts\Watch-DiskSpace.ps1, scripts\Watch-RunnerHealth.ps1,
scripts\Get-CIJobSummary.ps1) are kept as 3-line shims for Task
Scheduler and external callers — they translate PascalCase parameters
to the Python kebab-case CLI and propagate the exit code.
Local validation gates
.\.venv\Scripts\python.exe -m ruff check src tests\python
.\.venv\Scripts\python.exe -m mypy --strict src
.\.venv\Scripts\python.exe -m pytest tests\python -q --cov=ci_orchestrator --cov-fail-under=90
Coverage gate is 90%. Regression tests for the
AGENTS.md "Errori frequenti da evitare" #9–#12 live in
tests/python/test_agents_errors.py
and must stay green.
Setup rapido
1a. Template VM Windows
Opzione A — Deploy automatico (crea VM + installa Windows unattended da ISO):
cd N:\Code\Workspace\Local-CI-CD-System\template
# Windows Server 2025 (corrente):
.\Deploy-WinBuild2025.ps1 -ISOPath F:\CI\ISO\WinSrv2025.iso `
-VMPath F:\CI\Templates\WinBuild2025\WinBuild2025.vmx
# Windows Server 2022 (alternativa):
.\Deploy-WinBuild2022.ps1 -ISOPath F:\CI\ISO\WinSrv2022.iso `
-VMPath F:\CI\Templates\WinBuild2022\WinBuild2022.vmx
Opzione B — installazione manuale: installa Windows nella VM, abilita WinRM
(winrm quickconfig -q + Enable-PSRemoting -Force), annota l'IP VMnet8.
Provisioning toolchain (dopo A o B, dalla directory template/):
# Windows Server 2025:
.\Prepare-WinBuild2025.ps1 -VMIPAddress 192.168.79.x -StoreCredential
# Windows Server 2022:
.\Prepare-WinBuild2022.ps1 -VMIPAddress 192.168.79.x -StoreCredential
Poi spegni la VM e prendi lo snapshot con nome esatto BaseClean.
1b. Template VM Linux
cd N:\Code\Workspace\Local-CI-CD-System\template
# Deploy cloud image Ubuntu 24.04 (scarica ISO/cloud img, crea VM):
.\Deploy-LinuxBuild2404.ps1 -VMPath F:\CI\Templates\LinuxBuild2404\LinuxBuild2404.vmx
Provisioning toolchain (SSH — la VM deve essere avviata e raggiungibile):
.\Prepare-LinuxBuild2404.ps1 -VMIPAddress 192.168.79.x `
-VMXPath F:\CI\Templates\LinuxBuild2404\LinuxBuild2404.vmx `
-TakeSnapshot
-TakeSnapshot esegue automaticamente: reset machine-id → shutdown → snapshot BaseClean-Linux.
Dettagli: docs/LINUX-TEMPLATE-SETUP.md.
2. Credenziali guest
# PowerShell elevato — una volta sola sull'host.
# Scrive nel vault SYSTEM (act_runner gira come LocalSystem; un
# New-StoredCredential dalla sessione utente NON sarebbe leggibile dal
# runner). Username host-qualificato col computer name del guest
# (bare 'ci_build' -> NTLM SEC_E_UNKNOWN_CREDENTIALS).
.\scripts\Set-CIGuestCredential.ps1 -UserName 'WINBUILD-2025\ci_build'
3. act_runner
# Registra il runner con Gitea (interattivo, usa il registration token da Gitea Admin)
F:\CI\act_runner\act_runner.exe register
# Installa come servizio Windows
nssm install act_runner "F:\CI\act_runner\act_runner.exe" daemon
nssm start act_runner
Imposta queste variabili d'ambiente nel servizio (runner/config.yaml):
| Variabile | Valore |
|---|---|
GITEA_CI_TEMPLATE_PATH |
F:\CI\Templates\WinBuild2025\WinBuild2025.vmx |
GITEA_CI_LINUX_TEMPLATE_PATH |
F:\CI\Templates\LinuxBuild2404\LinuxBuild2404.vmx |
GITEA_CI_CLONE_BASE_DIR |
F:\CI\BuildVMs |
GITEA_CI_ARTIFACT_DIR |
F:\CI\Artifacts |
GITEA_CI_GUEST_CRED_TARGET |
BuildVMGuest |
GITEA_CI_SSH_KEY_PATH |
F:\CI\keys\ci_linux |
Labels runner: windows-build:host (job Windows) e linux-build:host (job Linux).
Dettagli completi: docs/HOST-SETUP.md.
4. SSH per Gitea
Vedi docs/Setup-GiteaSSH.md per configurare l'alias gitea-ci
(SSH port 2222, usato dai workflow live in git clone ssh://gitea-ci/...).
Uso — build manuale (test)
Windows guest:
& 'N:\Code\Workspace\Local-CI-CD-System\scripts\Invoke-CIJob.ps1' `
-JobId 'test-win-001' `
-RepoUrl 'ssh://gitea-ci/Simone/nsis-plugin-ns7zip.git' `
-Branch 'main' `
-TemplatePath 'F:\CI\Templates\WinBuild2025\WinBuild2025.vmx' `
-Submodules `
-BuildCommand 'python build_plugin.py --final --dist-dir dist' `
-GuestArtifactSource 'dist' `
-GuestCredentialTarget 'BuildVMGuest'
Linux guest:
& 'N:\Code\Workspace\Local-CI-CD-System\scripts\Invoke-CIJob.ps1' `
-JobId 'test-linux-001' `
-RepoUrl 'ssh://gitea-ci/Simone/nsis-plugin-ns7zip.git' `
-Branch 'main' `
-TemplatePath 'F:\CI\Templates\LinuxBuild2404\LinuxBuild2404.vmx' `
-SnapshotName 'BaseClean-Linux' `
-GuestOS 'Linux' `
-BuildCommand 'bash build.sh' `
-GuestArtifactSource '/opt/ci/output'
Artifacts raccolti in: F:\CI\Artifacts\<jobid>\.
Aggiungi -UseGitClone per clonare direttamente in VM (skip host-zip-transfer; richiede Git in template — già incluso nel toolchain).
Aggiungi -SkipArtifact per job che non producono output (es. lint, test puri).
Workflow Gitea
Il file gitea/workflows/build-ns7zip.yml è il workflow
attivo per Simone/nsis-plugin-ns7zip. Si attiva su push di tag v* o manualmente.
Usa una strategy matrix [windows, linux] che esegue in parallelo su entrambi i runner.
La composite action gitea/actions/local-ci-build/action.yml asconde tutta la logica Invoke-CIJob.ps1 e accetta questi input principali:
| Input | Descrizione |
|---|---|
build-command |
Comando di build da eseguire nel guest |
guest-os |
Windows (default) o Linux |
artifact-source |
Path artifacts nel guest |
skip-artifact |
true per job senza output |
repo-url |
URL SSH del repository |
job-id-suffix |
Suffisso per disambiguare artifact dir in matrix |
Per adattare ad altri repository, copia gitea/workflow-example.yml
e aggiorna build-command, artifact-source, repo-url.
Documentazione
| File | Contenuto |
|---|---|
| docs/ARCHITECTURE.md | Diagramma componenti e flusso dati |
| docs/CI-FLOW.md | Descrizione dettagliata di ogni fase del pipeline |
| docs/HOST-SETUP.md | Bootstrap macchina host (Setup-Host.ps1) |
| docs/WINDOWS-TEMPLATE-SETUP.md | Provisioning template Windows (Deploy + Prepare + Setup) |
| docs/LINUX-TEMPLATE-SETUP.md | Template Ubuntu 24.04: Deploy-LinuxBuild2404, Prepare-LinuxBuild2404, snapshot BaseClean-Linux |
| docs/OPTIMIZATION.md | Layout NVMe, snapshot strategy, cache, tuning |
| docs/BEST-PRACTICES.md | Sicurezza WinRM, threat model, credenziali |
| docs/RUNBOOK.md | Triage incident comuni (runner offline, clone fail, ecc.) |
| docs/TEST-PLAN-v1.3-to-HEAD.md | Test plan completo features post-v1.3 |
| docs/Setup-GiteaSSH.md | Configurazione SSH alias gitea-ci |
| TODO.md | Roadmap, audit trail task completati, backlog |
Note di sicurezza
WinRM usa HTTPS/5986 con certificato self-signed e Basic auth.
-SkipCACheck/-SkipCNCheck sono accettabili in lab isolato (il cert non è di una CA trusted).
Per ambienti condivisi o di produzione, usare un certificato CA valida e rimuovere -SkipCACheck.
Vedi docs/BEST-PRACTICES.md.