Simone f6f8ffc3d2 fix(template): move snapshot conflict check to pre-flight (before WinRM)
pypsrp alters terminal tty state during remote sessions. Any readline()
call after WinRM work receives \r instead of \n on Enter (terminal in
raw mode), causing the prompt to spin printing ^M and never return.

Fix: check for existing snapshot and ask the user at the very beginning
of the command, before any WinRM/pypsrp connections are opened, when the
terminal is still in canonical mode. If the user declines, abort early.
At the end, if the snapshot still exists (race-safe), delete silently
and recreate — no second prompt needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 02:15:51 +02:00

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.

S
Description
Local CI/CD system: VMware Workstation + Gitea Actions + act_runner. Ephemeral build VMs via linked clones.
Readme 6.6 MiB
Languages
Python 50.8%
PowerShell 46.1%
Shell 3.1%