Files
local-ci-cd-system/TODO.md
T
Simone ad39aa5cf4 docs(todo): §7 — replace emoji with [x]/[ ] checkboxes, reformat items
Convert §7.1/7.2/7.3 from emoji  + prose into standard [x] checkbox lists.
§7.4 items converted to [ ] open checkboxes. Consistent with rest of TODO.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 07:11:03 +02:00

799 lines
42 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# TODO — Local CI/CD System
<!-- Last updated: 2026-05-10 — line refs e wording allineati al refactor 2026-05-09 -->
> Documento unico di lavoro: roadmap, audit trail dei task completati, e backlog post-v1.0
> con priorità e razionale. Le voci aperte sono raggruppate per area e marcate **P0..P3**:
>
> **Doc di setup operativi**:
> - [docs/HOST-SETUP.md](docs/HOST-SETUP.md) — bootstrap macchina host (Setup-Host.ps1)
> - [docs/WINDOWS-TEMPLATE-SETUP.md](docs/WINDOWS-TEMPLATE-SETUP.md) — provisioning template Windows (Deploy + Prepare + Setup-WinBuild2025)
> - [docs/LINUX-TEMPLATE-SETUP.md](docs/LINUX-TEMPLATE-SETUP.md) — bozza/TODO template Linux (non implementato)
>
> - **P0** — sicurezza o affidabilità: affrontare prima di estendere il sistema
> - **P1** — operatività quotidiana: automatizzare manutenzione e debug
> - **P2** — performance e qualità del codice
> - **P3** — estensioni nice-to-have
---
## Setup & Infrastructure
- [x] **Gitea Server** — già in esecuzione su `http://10.10.20.11:3100` (e `https://gitea.emulab.it`)
- [x] Runner registration token ottenuto e usato
- [x] Create at least one test repository with `.gitea/workflows/build.yml`
(`Simone/nsis-plugin-nsinnounp` — testato e2e, workflow in `gitea/workflows/build-nsis.yml`)
- [x] Gitea Actions abilitato (runner registrato con successo)
- [x] **act_runner** — installato e registrato sull'host Windows
- [x] Binario `gitea-runner v1.0.2` scaricato in `F:\CI\act_runner\act_runner.exe`
- [x] Registrato con Gitea (`http://10.10.20.11:3100`) — runner ID 1, nome `local-windows-runner`
- [x] `config.yaml` copiato in `F:\CI\act_runner\config.yaml` con label `windows-build:host`
- [x] Installato come servizio Windows via NSSM — stato: **Running**, StartType: Automatic
- [x] Verificare che il runner appaia "Online" nell'UI Gitea Admin → Actions → Runners
- [x] Confermare riavvio automatico dopo reboot host
- [x] **Directory CI** create su `F:\CI\`
- [x] `F:\CI\BuildVMs\`
- [x] `F:\CI\Artifacts\`
- [x] `F:\CI\act_runner\`
- [x] `F:\CI\Cache\NuGet\`
- [x] `F:\CI\RunnerWork\`
- [x] `F:\CI\Templates\WinBuild\`
- [x] `F:\CI\Logs\` — log per job (retention: 30 giorni)
- [x] `F:\CI\ISO\` — contiene `26100.1742.240906-0331.ge_release_svc_refresh_SERVER_VOL_x64FRE_en-us.iso` (Windows Server 2025)
- [x] **CredentialManager** PowerShell module v2.0 installato (Scope: CurrentUser)
- [x] **Rete VM**: VMnet8 (NAT) — internet access per pip/nuget/npm durante i build
## Template VM
- [x] **Provision template VM** in VMware Workstation — segui questi passi nell'ordine esatto:
### Fase A — Crea e installa la VM (NIC: NAT per internet)
- [x] Crea nuova VM in VMware Workstation con queste impostazioni:
- 4 vCPU, 6144 MB RAM, disco 80 GB thin
- VMX path: `F:\CI\Templates\WinBuild\CI-WinBuild.vmx`
- **NIC: VMnet8 (NAT)** ← internet necessario per Windows Update e installer
- CD-ROM: `F:\CI\ISO\26100.1742.240906-0331.ge_release_svc_refresh_SERVER_VOL_x64FRE_en-us.iso`
- [x] Installa Windows Server 2025 dall'ISO
- [x] All'interno della VM (come Administrator), abilita WinRM:
```cmd
winrm quickconfig -q
```
```powershell
Enable-PSRemoting -Force -SkipNetworkProfileCheck
Set-Item WSMan:\localhost\Service\Auth\Basic $true -Force
Set-Item WSMan:\localhost\Service\AllowUnencrypted $true -Force
Set-Item WSMan:\localhost\Client\TrustedHosts -Value '*' -Force
```
- [x] **Attiva Windows Server 2025** (prima dello snapshot — i linked clone ereditano l'attivazione):
```cmd
slmgr /dli
```
Se non attivato, scegli il metodo appropriato:
- **KMS** (server KMS in rete): `slmgr /skms <IP_KMS>` poi `slmgr /ato`
- **MAK/Retail key**: `slmgr /ipk XXXXX-XXXXX-XXXXX-XXXXX-XXXXX` poi `slmgr /ato`
- **KMS pubblico** (alternativa lab): vedi https://github.com/robin113x/Windows-Server-Activation/
Verifica: `slmgr /xpr` — deve mostrare attivazione permanente o scadenza lontana
- [x] Nota l'IP assegnato dal DHCP di VMnet8 (es. `192.168.79.x`) — `ipconfig` nella VM
### Fase B — Provisioning automatico dall'host
- [x] Dall'host, esegui:
```powershell
cd n:\Code\Workspace\Local-CI-CD-System\template
.\Prepare-WinBuild2025.ps1 -VMIPAddress 192.168.79.128 -SkipWindowsUpdate
```
Installa: WinRM config, utente `ci_build`, .NET SDK 10.0, VS Build Tools 2026, Python 3.13.3.
Lo script esegue validazione automatica ad ogni passaggio (`Assert-Step`):
- Pre-flight: IP ottetti 0-255, TCP/5985 raggiungibile, Setup-WinBuild2025.ps1 presente
- Host WinRM: AllowUnencrypted, TrustedHosts
- Guest prep: `C:\CI` esiste, file copiato, size match
- Post-setup remoto (9 check): WinRM, user+admin, UAC, firewall, .NET, Python, MSBuild, CI dirs
Se qualsiasi check fallisce → script termina con errore → **non prendere lo snapshot**.
- [x] Setup completato con successo (VS Build Tools 2026 exit code 0).
- [x] `Setup-WinBuild2025.ps1` ora esegue validazione `Assert-Step` dopo ogni step interno
(WinRM, Firewall, User, UAC, Dirs, Defender, .NET, Python, VS Build Tools, Toolchain,
Cleanup, Final pre-snapshot gate) — throws se qualsiasi check fallisce (2026-05-09)
### Fase C — Spegni e prendi lo snapshot
- [x] VM rimane su **VMnet8 (NAT)** — internet access permanente per build che richiedono pip/nuget/npm
- [x] Spegni la VM: Start → Shut down
- [x] Prendi snapshot: VM → Snapshot → Take Snapshot
**Nome esatto: `BaseClean`**
**Prerequisito: `Prepare-WinBuild2025.ps1` deve essere uscito con exit 0** — tutti
gli `Assert-Step` passati, incluso il Final pre-snapshot gate nel guest.
- [x] Lascia la VM spenta — non modificarla mai più dopo questo snapshot
### Fase D — Credenziali e config
- [x] Archivia credenziali guest sull'host (PowerShell elevato con CredentialManager):
```powershell
New-StoredCredential -Target "BuildVMGuest" -UserName "ci_build" `
-Password "<your-build-password>" -Persist LocalMachine
```
- [x] Verifica `GITEA_CI_TEMPLATE_PATH` in `F:\CI\act_runner\config.yaml` — aggiornato a `CI-WinBuild.vmx` (2026-05-08)
## Scripts Verification
- [x] Test `New-BuildVM.ps1` standalone — clone `Clone_test-002` creato correttamente (2026-05-08)
- [x] Test `Wait-VMReady.ps1` standalone — exit 0 in ~5s, VM pronta (2026-05-08)
- [x] Test `Invoke-RemoteBuild.ps1` — dotnet restore + build OK, artifacts.zip creato in VM (2026-05-08)
- [x] Test `Get-BuildArtifacts.ps1` — artifacts.zip (78 KB) copiato in F:\CI\Artifacts\test-003\ (2026-05-08)
- [x] Test `Remove-BuildVM.ps1` — VM spenta + directory rimossa correttamente (2026-05-08)
- [x] Test `Invoke-CIJob.ps1` end-to-end su una soluzione reale (nsis-plugin-nsinnounp)
- [x] e2e-008: SUCCESS — build OK + artifact raccolto (1632 KB)
- [x] e2e-009: SUCCESS — cleanup automatico VM + clone confermato (finally block)
- [x] `scripts/Cleanup-OrphanedBuildVMs.ps1` creato e verificato — `-WhatIf`, soglia `-MaxAgeHours`, hard stop + deleteVM + rimozione dir (2026-05-09)
- [x] `scripts/Remove-BuildVM.ps1` fix `vmrun deleteVM` exit -1 — poll `vmrun list` + retry 3× backoff 0/3/6s (2026-05-09)
## Runner Configuration
- [x] `runner/config.yaml` aggiornato con percorsi `F:\CI\`, rete VMnet8 (NAT), label corretti
- [x] `capacity: 4` configurato (64 GB RAM / ~8 GB per VM)
- [x] Aggiornare `GITEA_CI_TEMPLATE_PATH` in `config.yaml` dopo creazione template VM
- [x] Archiviare credenziali guest in Windows Credential Manager (target: `BuildVMGuest`)
## Gitea Workflow Integration
- [x] Copiare `gitea/workflow-example.yml` in `.gitea/workflows/build.yml` nel repo di test
(workflow `build-nsis.yml` già presente e funzionante per `nsis-plugin-nsinnounp`)
- [x] Push commit e verificare job in Gitea Actions UI
- [x] Verificare artifact scaricabile da Gitea dopo build riuscita
- [x] `gitea/workflows/lint.yml` creato — PSScriptAnalyzer su push/PR che toccano `.ps1` (2026-05-09)
- [ ] **[P3] Adattare workflow per altri repository** — generalizzare `BUILD_COMMAND` e `ARTIFACT_SOURCE`.
Vedi anche §6.2 (composite action riutilizzabile).
---
## 1. Sicurezza & hardening
### 1.1 [P0] Migrare WinRM da HTTP/Basic a HTTPS/5986
File: [template/Setup-WinBuild2025.ps1:204-241](template/Setup-WinBuild2025.ps1) (Step 3 WinRM), [template/Deploy-WinBuild2025.ps1:524-534](template/Deploy-WinBuild2025.ps1) (Enable-PSRemoting + AllowUnencrypted/Basic), [scripts/Invoke-RemoteBuild.ps1:83-91](scripts/Invoke-RemoteBuild.ps1), [scripts/Get-BuildArtifacts.ps1:66-75](scripts/Get-BuildArtifacts.ps1).
**Motivazione**: la combo `AllowUnencrypted=true` + `Auth/Basic=true` invia credenziali del
`ci_build` (admin) in chiaro su VMnet8. Anche in lab isolato, l'host può essere compromesso
via altri vettori e scrapare la rete VMware. Il piano è già in `docs/BEST-PRACTICES.md §2`;
manca solo l'esecuzione.
**Azioni**:
- [ ] Generare cert self-signed nel template *prima* dello snapshot.
- [ ] Sostituire `New-PSSession -ComputerName $ip` con `-UseSSL -Port 5986` in `Invoke-RemoteBuild.ps1` e `Get-BuildArtifacts.ps1`.
- [ ] Rimuovere `AllowUnencrypted=true` dal `Setup-WinBuild2025.ps1`.
- [ ] Tenere `-SkipCACheck` lato host: per cert lab self-signed è accettabile, ma documentare la motivazione inline.
- [ ] Regole firewall — limitare WinRM alla subnet build VM (`192.168.79.0/24` — VMnet8 NAT).
### 1.2 [P0] Restringere `TrustedHosts` lato host (audit-only — già coperto)
File: nessuno script imposta `*` sull'host. Stato corrente:
- `Setup-Host.ps1` non tocca TrustedHosts (verificato 2026-05-10)
- [template/Prepare-WinBuild2025.ps1:270-294](template/Prepare-WinBuild2025.ps1:270) appende `$VMIPAddress` e ripristina nel `finally` (riga 617)
- [docs/HOST-SETUP.md:121-126](docs/HOST-SETUP.md:121) raccomanda `'192.168.79.*'` in setup manuale
**Azione residua**: audit eventuali host già configurati con `*` (eredità manuale) e
sostituire con la subnet build:
```powershell
Set-Item WSMan:\localhost\Client\TrustedHosts -Value '192.168.79.*' -Force -Concatenate
```
### 1.3 [P0] Pinning hash SHA256 degli installer
File: [template/Setup-WinBuild2025.ps1:707-755](template/Setup-WinBuild2025.ps1) (Step 8 Python), [template/Setup-WinBuild2025.ps1:757-885](template/Setup-WinBuild2025.ps1) (Step 9 VS Build Tools), [template/Setup-WinBuild2025.ps1:660-705](template/Setup-WinBuild2025.ps1) (Step 7 dotnet-install.ps1).
**Motivazione**: Python, `dotnet-install.ps1`, `vs_buildtools.exe` vengono scaricati senza
verifica integrità. Un MITM nella rete dell'host (anche temporaneo) può iniettare binari
compromessi nel template, che poi viene snapshottato e usato da *ogni* build.
Pattern minimale:
```powershell
$expected = '6F25A7DF...' # SHA256 pinnato per la versione esatta
$actual = (Get-FileHash $pyInstallerPath -Algorithm SHA256).Hash.ToLower()
if ($actual -ne $expected.ToLower()) { throw "Hash mismatch: $actual" }
```
Aggiungere un blocco hash per ogni download e bloccarli in costanti `$script:Hashes`.
Aggiornare al cambio versione (è raro).
### 1.4 [P1] Validazione IP per-ottetto
File: [scripts/Invoke-CIJob.ps1:106](scripts/Invoke-CIJob.ps1), [scripts/Invoke-RemoteBuild.ps1:52](scripts/Invoke-RemoteBuild.ps1), [scripts/Get-BuildArtifacts.ps1:42](scripts/Get-BuildArtifacts.ps1), [scripts/Wait-VMReady.ps1:43](scripts/Wait-VMReady.ps1).
**Motivazione**: `'^(\d{1,3}\.){3}\d{1,3}$'` accetta `999.999.999.999`. Sostituire con regex
per-ottetto:
```powershell
$ipv4 = '^((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$'
[ValidatePattern($ipv4)]
```
Estrarre la costante in un modulo condiviso `scripts/_Common.psm1` per evitare drift fra i 4 file (vedi §5.2).
### 1.5 [P1] PAT mai persistito quando si abilita `-UseGitClone`
Requisiti di sicurezza per l'implementazione del clone in-VM. La sequenza operativa
(quando, dove, come) è dettagliata nella sezione "In-VM Git Clone (Ottimizzazione) — riferimento §3.3".
Vincoli da rispettare in qualsiasi implementazione di `-UseGitClone`:
- [ ] PAT recuperato da Credential Manager **subito prima** della WinRM session, mai prima.
- [ ] Iniettato via `$using:cred` o env var di sessione, mai in argv né in log/transcript.
- [ ] Cancellato in `finally` interno alla scriptblock guest.
- [ ] **Grep automatico post-job sui log**: se il PAT compare grezzo in `$jobLog` o
`transcript.txt`, marcare la build come fallita e ruotare il PAT (regola di safety net,
non sostituisce le precedenti).
### 1.6 [P2] Defender + Firewall + UAC tutti disattivati nel template — documentare il modello di minaccia
File: [template/Deploy-WinBuild2025.ps1:485-551](template/Deploy-WinBuild2025.ps1) (UAC, ServerManager, DisableCAD, OOBE — set da Deploy), [template/Setup-WinBuild2025.ps1:176-336](template/Setup-WinBuild2025.ps1) (Firewall Step 1, Defender Step 2 validation-only post-refactor 2026-05-09, WinRM Step 3, User Step 4, UAC Step 4b, Dirs Step 5).
Stato attuale (post-refactor §7):
- **Defender**: disabled da Deploy (`DisableAntiSpyware=1` GPO + RTP off) — Setup Step 2 è validation-only
- **Firewall**: disabled da Setup Step 1 (`Set-NetFirewallProfile ... -Enabled False`); §7.1 prevede spostamento a Deploy
- **UAC**: `EnableLUA=0`, `LocalAccountTokenFilterPolicy=1` — duplicato Deploy + Setup Step 4b (§7.1 lo riduce a Assert-Step in Setup)
Le scelte sono ragionevoli per un template lab efimero, ma vanno raccolte in un'unica sezione
`BEST-PRACTICES.md §X — Threat Model & Hardening Trade-offs` che indichi:
- Cosa è disattivato e perché (costo build vs. superficie d'attacco).
- Quali condizioni rendono il modello inaccettabile (es. condivisione dell'host, esposizione
VMnet8 a LAN aziendale, build di codice di terze parti non fidate).
- Mitigazioni se uno di quei vincoli salta (Firewall On con regola WinRM esplicita,
Defender con esclusione `C:\CI`, UAC On con `LocalAccountTokenFilterPolicy=1`).
Senza questa nota, future modifiche possono accumulare ulteriori riduzioni di sicurezza
senza tracciamento.
### 1.7 [P2] Rotazione password guest VM
- [ ] Trimestrale. Aggiornare credenziale `BuildVMGuest` in Credential Manager + password
effettiva nel template (richiede refresh snapshot — coordinare con §2.5).
### 1.8 [P2] Verifica Get-StoredCredential
- [ ] Confermare che `Get-StoredCredential` funzioni correttamente in tutti gli script che
lo usano (post-migrazione HTTPS).
---
## 2. Affidabilità & resilienza
### 2.1 [P0] Race su IP allocation con `capacity: 4`
File: [runner/config.yaml:17](runner/config.yaml), [scripts/Invoke-CIJob.ps1:244-253](scripts/Invoke-CIJob.ps1).
**Stato osservato**: race non riprodotta in e2e-008/009 (un job alla volta). `capacity: 4`
resta attivo in produzione — bug teorico finché non si esegue stress test parallelo.
**Motivazione**: con 4 job paralleli e DHCP VMnet8, due VM possono ottenere lo stesso IP se
la lease pool è stretta o se due `vmrun start` rispondono troppo vicini.
`getGuestIPAddress` ritorna l'IP visto dalla VM stessa, quindi la collisione non viene
rilevata in fase di Wait-VMReady — apparirà come "WinRM risponde ma è la VM sbagliata".
**Opzioni**:
- **A** — IP allocator file-based con lock: prendere un IP libero da `192.168.79.101..104`,
scriverlo in `F:\CI\State\ip-leases\<jobid>` e iniettarlo come `bootArgs` o via
`vmrun writeVariable` prima di start. Rilascio nel `finally`.
- **B** — DHCP reservation per MAC nel VMware NAT DHCP server (`vmnetdhcp.conf`): assegnare
4 MAC noti, hard-coded nei VMX dei cloni. Più semplice ma richiede gestire il VMX clonato.
- **C** — pool fissato a `capacity: 1` finché non si implementa A o B.
L'opzione A è la più portabile e si integra con il pre-warm pool (§3.4).
### 2.2 [P1] Cleanup orfani schedulato
File: [scripts/Cleanup-OrphanedBuildVMs.ps1](scripts/Cleanup-OrphanedBuildVMs.ps1) — script pronto, manca lo scheduling.
Estendere `Setup-Host.ps1` per registrare un Task Scheduler:
```powershell
$action = New-ScheduledTaskAction -Execute 'powershell.exe' `
-Argument '-NoProfile -NonInteractive -ExecutionPolicy Bypass -File "N:\Code\Workspace\Local-CI-CD-System\scripts\Cleanup-OrphanedBuildVMs.ps1"'
$trigger = New-ScheduledTaskTrigger -Daily -At 4am -RandomDelay (New-TimeSpan -Minutes 30)
$principal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -RunLevel Highest
Register-ScheduledTask -TaskName 'CI-CleanupOrphans' -Action $action -Trigger $trigger -Principal $principal -Force
```
Aggiungere anche un trigger `AtStartup` per gestire crash dell'host.
Suggerito: ogni 6 ore, `-MaxAgeHours 4`.
### 2.3 [P1] Retention artifact + log automatica
File: [docs/OPTIMIZATION.md:160-181](docs/OPTIMIZATION.md) — la logica c'è solo come snippet, non eseguita.
Stessa pattern di §2.2 ma su `F:\CI\Artifacts` con `-AddDays(-30)` e su `F:\CI\Logs` con la
finestra già configurata in `Invoke-CIJob.ps1`. Aggiungere
`Get-PSDrive F | Where-Object { $_.Free -lt 50GB }` come *guard*: se libero < 50 GB,
retention più aggressiva (7 giorni) e log warning.
### 2.4 [P1] `Remove-BuildVM.ps1` — supportare `-WhatIf`
File: [scripts/Remove-BuildVM.ps1:28](scripts/Remove-BuildVM.ps1).
Aggiungere `[CmdletBinding(SupportsShouldProcess)]` come fatto in `Cleanup-OrphanedBuildVMs.ps1`.
Permette debug e dry-run senza forkare un secondo script.
### 2.5 [P1] Snapshot versionato `BaseClean_<yyyyMMdd>`
File: [docs/BEST-PRACTICES.md:142-145](docs/BEST-PRACTICES.md), [scripts/Invoke-CIJob.ps1:100](scripts/Invoke-CIJob.ps1).
**Motivazione**: quando si refresha il template (KMS lease semestrale, update toolchain), il
flusso attuale richiede di sovrascrivere `BaseClean` — i cloni esistenti diventano invalidi
e non c'è rollback.
**Strategia**:
- Naming: `BaseClean_20260509`.
- `runner/config.yaml` aggiunge `GITEA_CI_SNAPSHOT_NAME` come env var.
- `Invoke-CIJob.ps1` legge da env, default `'BaseClean'` per compat.
- Pratica: tieni gli ultimi 2 snapshot, cancella il più vecchio dopo 1 settimana di uso pulito del nuovo.
Si lega al task: refresh semestrale snapshot template VM (KMS lease = 180 giorni).
Boot template su VMnet8 (NAT) → `slmgr /ato` → nuovo snapshot.
### 2.6 [P2] Backup automatico VMDK template
File: [docs/BEST-PRACTICES.md:130-138](docs/BEST-PRACTICES.md) — solo come snippet manuale.
Pre-snapshot: copia atomica del `parent` VMDK in `F:\CI\Backups\Template_<date>\`. Su errore
di refresh (es. installer rotto), rollback in 1 minuto.
### 2.7 [P2] Health check del runner + monitoraggio Event Log
File: [docs/BEST-PRACTICES.md:101-115](docs/BEST-PRACTICES.md) — script presente in doc, non operativo.
Schedulare ogni 15 min: query `gitea/api/v1/admin/runners`, se `local-windows-runner` non
`online``Restart-Service act_runner` + log evento. Limitare a 3 restart/h con cooldown.
Inoltre: monitoraggio Windows Event Log per fallimenti servizio `act_runner` (alert via
EventLog query schedulata o webhook).
---
## 3. Performance & throughput
### 3.1 [P1] NuGet/pip cache su shared folder
File: [docs/OPTIMIZATION.md:93-128](docs/OPTIMIZATION.md), [scripts/Invoke-RemoteBuild.ps1:158-194](scripts/Invoke-RemoteBuild.ps1).
**Motivazione**: `F:\CI\Cache\NuGet` esiste come dir, non viene mai usata.
Aggiungere a `CI-WinBuild.vmx`:
```ini
sharedFolder0.present = "TRUE"
sharedFolder0.hostPath = "F:\\CI\\Cache\\NuGet"
sharedFolder0.guestName = "nuget-cache"
```
Poi nello scriptblock di build:
```powershell
$env:NUGET_PACKAGES = '\\vmware-host\Shared Folders\nuget-cache'
```
Equivalente per pip:
```powershell
$env:PIP_CACHE_DIR = '\\vmware-host\Shared Folders\pip-cache'
```
Dopo lo snapshot refresh la cache va riscaldata una volta — accettabile.
### 3.2 [P1] Sostituire `Compress-Archive` con 7-Zip o robocopy
File: [scripts/Invoke-RemoteBuild.ps1:112,148,185](scripts/Invoke-RemoteBuild.ps1) (3 chiamate `Compress-Archive`).
Prerequisito: 7-Zip nel template — vedi §6.6 e sezione "In-VM Git Clone".
**Motivazione**: `Compress-Archive` è single-threaded e su repo medio-grandi (>100 MB sorgente)
può prendere 20-40 s.
**Alternative**:
- **7-Zip** (richiede installazione nel template — vedi sezione "In-VM Git Clone" §B):
```powershell
& 'C:\Program Files\7-Zip\7z.exe' a -mmt=on -mx1 $hostZip "$HostSourceDir\*"
```
Vantaggio: multi-thread, ratio simile con `-mx1`.
- **robocopy via admin share UNC** (`\\<vmIP>\C$\CI\build`): nessun zip/unzip, supporto delta,
ma richiede SMB aperto sulla VM — confliggente con §1.2.
Misurare con un repo reale: probabile risparmio 10-20 s/build su `nsis-plugin-nsinnounp`.
### 3.3 [P2] In-VM clone (`-UseGitClone`)
Implementazione disegnata di seguito (sezione "In-VM Git Clone (Ottimizzazione)").
Vincoli sicurezza PAT: vedi §1.5. Tool prerequisiti (Git, 7-Zip): vedi §6.6.
Beneficio reale solo per repo > 200 MB con submoduli grandi; misurare prima.
### 3.4 [P3] Pre-warm pool di cloni
File: [docs/OPTIMIZATION.md:133-156](docs/OPTIMIZATION.md).
Solo se profilo dimostra che `New-BuildVM` + `Wait-VMReady` (~30-60 s) è il collo di bottiglia.
Con build di 2 minuti, l'overhead è ~40% — vale lo sforzo. Con build di 10+ minuti, no.
Implementazione minima: scheduled task ogni 5 min mantiene 2 cloni avviati in
`F:\CI\WarmPool\`; `Invoke-CIJob` fa `Move-Item` di un clone caldo nel suo job dir e parte
da Phase 4 saltando Phase 2-3.
### 3.5 [P2] vCPU/RAM tuning per workload
File: [docs/OPTIMIZATION.md:82-89](docs/OPTIMIZATION.md), VMX template.
`numvcpus=4`, `memsize=6144` è ragionevole ma non ottimo per ogni build. Per
`nsis-plugin-nsinnounp` (4 build paralleli interni con thread divisi) 4 vCPU sono già saturati.
Considerare:
- VMX pinning: 6 vCPU per build C++ pesanti, 2 vCPU per build .NET piccoli.
- Esporre `-VMCpu` / `-VMMemory` a `Invoke-CIJob.ps1` con override via workflow.
### 3.6 [P2] Benchmark baseline
- [ ] Tempo creazione clone + tempo WinRM readiness (per stabilire baseline prima di §3.1/3.2/3.4).
---
## 4. Osservabilità & manutenzione
### 4.1 [P1] Log strutturati per parsing
File: [scripts/Invoke-CIJob.ps1:178-187](scripts/Invoke-CIJob.ps1).
**Motivazione**: i log attuali sono `Write-Host` testuale. Per dashboard / alerting servono
log machine-readable.
Aggiungere a fianco del transcript un secondo file `invoke-ci.jsonl`:
```powershell
function Write-JobEvent {
param([string]$Phase, [string]$Status, [hashtable]$Data = @{})
$event = @{
ts = (Get-Date).ToString('o')
jobId = $JobId
phase = $Phase
status = $Status
data = $Data
}
$event | ConvertTo-Json -Compress | Add-Content $jsonLog
}
```
Emettere in ogni transizione di fase. Permette grafici (durata media per fase, tasso di
fallimento) con `jq` o Loki/Grafana se in futuro.
### 4.2 [P2] Metriche su Prometheus textfile
Generare `F:\CI\Metrics\runner.prom` da uno scheduled task ogni 60s:
```
ci_runner_orphan_vms 0
ci_runner_disk_free_gb 423
ci_runner_artifacts_total 247
ci_runner_active_jobs 1
```
Se l'homelab ha già Prometheus + node_exporter, basta
`--collector.textfile.directory=F:\CI\Metrics`.
### 4.3 [P1] Disk space alert
File: estendere `Setup-Host.ps1` o creare `scripts/Watch-DiskSpace.ps1`.
`F:` riempito = build silenziosamente fallite (linked clone fallirà su `vmrun clone` con
messaggi criptici). Alert via `eventcreate` o webhook Gitea/Discord quando libero < 50 GB.
### 4.4 [P2] Runbook per incident comuni
File: nuovo `docs/RUNBOOK.md`.
Documentare con copy-pasta:
- "Runner offline in Gitea UI" → check service, log, restart.
- "Tutte le build falliscono in Phase 2" → `vmrun -T ws list`, parent VMDK lock, snapshot mancante.
- "Build lente" → check disk free, vmware-vmx.exe CPU, rete VMnet8.
- "VMX corrotto post-crash host" → restore from `F:\CI\Backups\Template_<latest>`.
Ogni voce: sintomo, comando di triage, fix, escalation.
### 4.5 [P3] Dashboard read-only
Una pagina HTML statica generata da `Invoke-CIJob.ps1` (append a `F:\CI\dashboard.html`)
con ultime 50 build, durata, esito, link agli artifact. Servita da IIS Express o solo
file:// — utile per debug a colpo d'occhio senza aprire Gitea UI.
---
## 5. Qualità codice & test
### 5.1 [P1] Pester smoke tests sugli script
File: nuovo `tests/` directory.
**Motivazione**: manca un livello di test sotto l'e2e.
Pester può coprire:
- `New-BuildVM` con vmrun mockato — verifica che il VMX path costruito sia ben formato
per JobId con caratteri speciali.
- `Wait-VMReady` con `Test-WSMan` mockato — verifica timeout, fasi, ritorni.
- `Remove-BuildVM` con `vmrun list` mockato — verifica retry deleteVM.
- Validazione IP regex (§1.4) — table tests.
Eseguire in CI tramite il runner stesso (workflow `lint.yml` esiste già — espandere).
### 5.2 [P2] Modulo PowerShell condiviso `scripts/_Common.psm1`
**Duplicazioni da estrarre**:
- `New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck` (3 file).
- `ValidatePattern` IP (4 file — vedi §1.4).
- Risoluzione `vmrun.exe` (3 file).
- `Test-Path` + `New-Item` per dir lazy creation.
- Function `Invoke-VmrunCommand` con gestione `$LASTEXITCODE`.
Risultato: meno LoC, fix in un punto solo, più facile da testare.
### 5.3 [P2] Esecuzione `vmrun` con check exit code uniforme
Pattern attuale ripetuto in tutti gli script che invocano `vmrun`:
```powershell
$out = & $VmrunPath ... 2>&1
if ($LASTEXITCODE -ne 0) { throw "..." }
```
Wrapping in `Invoke-VmrunCommand -Operation 'clone' -Args @(...)` riduce errori di copy-paste
e centralizza retry/log.
### 5.4 [P2] PSScriptAnalyzer rules custom
File: [gitea/workflows/lint.yml](gitea/workflows/lint.yml) (già esiste).
Aggiungere regole specifiche progetto (`PSScriptAnalyzerSettings.psd1` in root):
- `PSAvoidUsingPlainTextForPassword` — rilevante per [Setup-WinBuild2025.ps1:123](template/Setup-WinBuild2025.ps1:123) (`[string] $BuildPassword``ConvertTo-SecureString -AsPlainText` riga 253).
- `PSAvoidUsingInvokeExpression`.
- `PSUseShouldProcessForStateChangingFunctions`.
- Regole custom: vietare hardcoded `F:\CI\` fuori da config (forzare param/env).
### 5.5 [P3] Type hints nei param block
Già parzialmente fatto. Estendere a tutti gli script per migliorare IntelliSense e
validazione runtime.
---
## 6. Scalabilità & estensioni
### 6.1 [P2] Linux Build VM
**Documento di pianificazione dedicato**: [docs/LINUX-TEMPLATE-SETUP.md](docs/LINUX-TEMPLATE-SETUP.md)
— bozza completa con fasi A→H, decisioni di design, differenze rispetto al template Windows.
Vedi anche sezione "Linux Build VM" sotto per il riassunto inline.
**Considerazione architetturale**: SSH su WSL2 non basta — serve VM vera per parità con il
flusso Windows. Ubuntu 24.04 minimal con script bash diretto (parità con flusso Windows).
Sostituire WinRM con SSH key-based; `Invoke-Command` non funziona — usare `ssh.exe` +
`scp.exe` o il modulo `Posh-SSH`.
Aggiungere `scripts/_Transport.psm1` con due implementazioni (`WinRM`, `SSH`) dietro la
stessa interfaccia. `Invoke-CIJob.ps1` sceglie dal parametro `-GuestOS`.
### 6.2 [P3] Workflow generico riutilizzabile (composite action)
File: [gitea/workflow-example.yml](gitea/workflow-example.yml).
Trasformare in *composite action* Gitea (`.gitea/actions/local-ci-build/action.yml`)
richiamabile da qualsiasi repo:
```yaml
- uses: ./Simone/local-ci-build@v1
with:
build-command: 'python build_plugin.py --final'
artifact-source: 'dist'
submodules: true
```
Riduce la duplicazione su nuovi repo e centralizza l'evoluzione del wrapper.
### 6.3 [P3] Multi-host runner federation
Quando 4 VM in parallelo non bastano, l'opzione naturale è un secondo host Windows con
stesso `Setup-Host.ps1` registrato come secondo runner Gitea (label `windows-build:host-2`).
Già supportato lato Gitea, basta replicare il setup.
**Premessa**: prima di scalare orizzontale, profilare se il collo di bottiglia è CPU
(i9-10900X 20T è abbondante) o I/O (NVMe). Se è I/O, una seconda NVMe sullo stesso host basta.
### 6.4 [P3] Build matrix nel workflow
File: [gitea/workflows/build-nsis.yml](gitea/workflows/build-nsis.yml).
Attualmente single job. Quando arriverà la VM Linux:
```yaml
strategy:
matrix:
target: [windows, linux]
runs-on: ${{ matrix.target }}-build
```
### 6.5 [P3] Secret injection workflow-level
Per chiavi di firma (Authenticode, GPG), usare i Gitea secrets nel workflow e passarli a
`Invoke-CIJob.ps1` come param + env, mai loggati. Stesso modello di §1.5 per il PAT.
### 6.6 [P2] Toolchain Tier-1 in Setup-WinBuild2025
File: [template/Setup-WinBuild2025.ps1](template/Setup-WinBuild2025.ps1).
**Motivazione**: toolchain attuale (`.NET SDK`, `Python`, `VS Build Tools`) copre solo C#/Python/MSBuild.
Aggiungere tools generici-CI riduce il bisogno di installazione ad-hoc nei workflow e abilita
build più ampi senza toccare il template.
Tool da aggiungere come step `Setup-WinBuild2025` (ognuno con `Assert-Step`):
| Tool | Versione | Razionale |
| ------------------- | ----------------------- | -------------------------------------------------------------- |
| **Git for Windows** | latest LTS | Self-clone in VM (alt a host-zip-transfer), submodule fetch |
| **7-Zip** | latest | Universale unpack/zip; molti installer e workflow ne dipendono |
| **PowerShell 7.x** | latest LTS | Più veloce di 5.1, parallel pipelines, operatori moderni |
| **NSIS** | latest | Test build installer (es. `nsis-plugin-nsinnounp`) |
| **CMake** | latest | Build system cross-platform per progetti C/C++ nativi |
| **Node.js LTS** | latest LTS | Frontend/Electron pipelines, JS toolchain (npm) |
| **WiX Toolset** | v4 stable | MSI building da .NET projects |
| **gh CLI** | latest | Upload artifact a release Gitea/GitHub, comment PR |
| **Sysinternals** | sysinternals.com `live` | Diagnostica process/handle quando build flaky |
| **vcpkg** | latest | Package manager C++ per dipendenze native |
**Approccio**:
- Pinning hash SHA256 per ogni installer (vedi §1.3)
- Convenzione path guest: tool in `C:\BuildTools\<tool>\` (separato da `C:\CI\` runtime
`C:\CI\` resta per artefatti job e worker temp; `C:\BuildTools\` per software statico).
Documentare in `docs/BEST-PRACTICES.md` prima di implementare.
- Aggiungere a `PATH` machine-wide
- Defender già off → no scan overhead durante install
- Step esegue in seriale dopo `Toolchain cross-check` esistente, prima di `Cleanup`
- `Final pre-snapshot gate`: aggiungere check `where.exe` per ogni tool
- Cross-link: Git for Windows e 7-Zip sono richiesti anche da §3.2 e dalla sezione
"In-VM Git Clone" — implementare qui per evitare doppio lavoro
**Validazione per-tool**:
- Eseguibile risolvibile via `where.exe`
- Versione exact match al param `-<Tool>Version`
- (per gh) `gh --version` returns 0
- (per vcpkg) `vcpkg.exe version`
**Param da aggiungere** a Setup + Prepare:
- `-GitVersion`, `-7ZipVersion`, `-Pwsh7Version`, `-NSISVersion`, `-CMakeVersion`,
`-NodeJSVersion`, `-WiXVersion`, `-GhCliVersion`, `-VcpkgRev` (rev SHA del repo)
**Tier-2 (opzionali, separati)**: Java JDK, Maven/Gradle, Rust, Go, LLVM, Docker, Azure/AWS CLI, WinDbg.
Non includere in Setup base — workflow-level via `choco install` o download diretto.
---
## 7. Refactor confine Deploy ↔ Setup
**Obiettivo**: separazione netta delle responsabilità — Deploy produce **template OS**
stand-alone, Setup fa **build customization** (user, dirs, toolchain). Eliminare drift e
duplicazioni; ogni concern ha **una sola** sorgente di verità.
**Stato attuale (duplicati)**: UAC, Explorer LaunchTo, Server Manager off, DisableCAD,
OOBE telemetry, WU services disable — entrambi gli script li toccano.
### 7.1 [P1] Spostare base OS hardening da Setup a Deploy
File: [template/Deploy-WinBuild2025.ps1](template/Deploy-WinBuild2025.ps1),
[template/Setup-WinBuild2025.ps1](template/Setup-WinBuild2025.ps1).
- [x] Aggiungere `Set-NetFirewallProfile -Enabled False` (tutti i profili) in Deploy post-install.ps1
- [x] Aggiungere `MaxMemoryPerShellMB=2048`, `IdleTimeOut=7200000`, `MaxProcessesPerShell=25`, `StartupType=Automatic` in Deploy dopo Enable-PSRemoting
- [x] Aggiungere in Deploy items mancanti rispetto a Setup 5c: lock screen (`NoLockScreen`), SM GPO policy key (`DoNotOpenAtLogon`), SM scheduled task disable, `DisableCAD` in Winlogon, OOBE non-policy key
- [x] Setup Step 1 (Firewall) → Assert-Step only
- [x] Setup Step 2 (Defender) → già validation-only (refactor 2026-05-09)
- [x] Setup Step 3 (WinRM tuning) → Assert-Step only (rimuovi Enable-PSRemoting, winrm set, Set-Item, Set-Service)
- [x] Setup Step 4b (UAC) → Assert-Step only
- [x] Setup Step 5b (Explorer LaunchTo) → Assert-Step only
- [x] Setup Step 5c (Server Manager / DisableCAD / OOBE) → Assert-Step only
- [ ] E2e validation — vedi §7.4
### 7.2 [P1] WU lifecycle — strategia B (Deploy: GPO + Manual; Setup: lifecycle)
File: [template/Deploy-WinBuild2025.ps1](template/Deploy-WinBuild2025.ps1),
[template/Setup-WinBuild2025.ps1](template/Setup-WinBuild2025.ps1).
Strategia B: Deploy lascia servizi WU `start=demand` (Manual, default Windows) e scrive solo
GPO `NoAutoUpdate=1` + `DisableWindowsUpdateAccess=1`. Setup Step 6 clears i GPO locks (Step A),
avvia WU via SchTask SYSTEM, poi Step 6b disabilita servizi permanentemente post-update.
- [x] Deploy: rimuovere `sc.exe config wuauserv start= disabled` (e UsoSvc, WaaSMedicSvc)
- [x] Deploy: aggiungere GPO `DisableWindowsUpdateAccess=1` (era mancante; `NoAutoUpdate=1` già presente)
- [x] Setup Step 6 Step A: già rimuove `DisableWindowsUpdateAccess` + `NoAutoUpdate` prima del COM API — nessuna modifica necessaria
- [x] Setup Step 6 Step D: già fa `Set-Service start=Manual` + `Start-Service` — idempotente con servizi già Manual
- [x] Setup Step 6b: già disabilita wuauserv/UsoSvc + GPO keys — nessuna modifica necessaria
- [ ] E2e validation — vedi §7.4
### 7.3 [P2] Update header docs di entrambi gli script
- [x] [template/Deploy-WinBuild2025.ps1](template/Deploy-WinBuild2025.ps1) `.DESCRIPTION`: aggiornato con tutte le hardening voci (firewall, WinRM tuning, WU GPO strategy B, lock screen, SM, OOBE)
- [x] [template/Setup-WinBuild2025.ps1](template/Setup-WinBuild2025.ps1) `.DESCRIPTION`: Steps 1/3/4b/5b/5c marcati "validation-only — set by Deploy"; step ordering rationale aggiornato
- [x] [docs/WINDOWS-TEMPLATE-SETUP.md](docs/WINDOWS-TEMPLATE-SETUP.md): architettura "tre script", VMX path `CI-WinBuild.vmx`, Fase C step list + razionale + tabella validazioni aggiornati
### 7.4 [P2] Test e2e refactor
- [ ] Deploy stand-alone su VM test → `Get-NetFirewallProfile` tutti `Enabled=False`, `Get-Service wuauserv | Select StartType``Manual`, `NoAutoUpdate=1` GPO presente
- [ ] Prepare dopo Deploy con `-SkipWindowsUpdate` → tutti `Assert-Step` passano `[OK]` senza eseguire Set (log mostra solo validation output)
- [ ] Prepare senza `-SkipWindowsUpdate` → WU gira via SchTask SYSTEM, exit code 0/2/3, Step 6b disabilita wuauserv/UsoSvc (`StartType=Disabled`)
- [ ] Final gate del guest passa su tutti e 7 i check
### 7.5 [P3] Rinomina Setup → Setup-CITools (opzionale, futuro)
Una volta che Setup fa solo build customization, nome `Setup-WinBuild2025` è impreciso —
non setup-a la build VM, **estende** un template OS già pronto con toolchain CI.
Rinominare in `Setup-CITools.ps1` o `Install-CIToolchain.ps1` rifletterebbe meglio la
responsabilità ridotta. Tracciare in TODO post-7.1/7.2.
---
## In-VM Git Clone (Ottimizzazione) — riferimento §3.3
Sfruttare l'accesso internet della VM (VMnet8 NAT) per fare il `git clone` direttamente
nella VM, eliminando il ciclo host-clone → zip → WinRM-transfer → unzip.
### Strategia
**Flusso attuale:**
```
host: git clone → host: Compress-Archive → WinRM: copia zip → VM: Expand-Archive → VM: build
```
**Flusso proposto:**
```
VM avviata → WinRM: git clone --recurse-submodules https://<token>@gitea/... → VM: build
```
**Punti chiave:**
- Il PAT **non deve essere nello snapshot** — viene iniettato a runtime come env var via WinRM
(`git clone https://Simone:<pat>@gitea.emulab.it/<repo>.git`) e non viene mai persistito
- Git deve essere installato nel template (`Setup-WinBuild2025.ps1`) — ora è assente per design
- L'implementazione deve essere **non-breaking**: nuovo switch opt-in `-UseGitClone` in `Invoke-CIJob.ps1`
- Si perde il parallelismo parziale (clone host avviene mentre VM boota), ma si elimina
tutto l'overhead di zip/transfer (rilevante su repo grandi o con molti submoduli)
- Submoduli funzionano nativamente con `--recurse-submodules` (nessun workaround)
### Task
- [ ] **Tool prerequisiti nel template** — implementati in §6.6 (Tier-1 Toolchain):
- Git for Windows + 7-Zip + gh CLI installati con hash pinning e `Assert-Step`
- Rimuovere il commento "Git is NOT installed" dal docblock di Setup-WinBuild2025.ps1
una volta applicato §6.6
- curl.exe: già presente in Windows Server 2025 — verificare con `where.exe curl`
- [ ] **Invoke-CIJob.ps1 — aggiungere switch `-UseGitClone`**
- [ ] Quando `-UseGitClone` è attivo:
- Saltare Phase 1 (host git clone) e la creazione del `HostSourceDir` temporaneo
- Dopo Wait-VMReady, leggere PAT da Windows Credential Manager sull'host
(es. target `git:https://gitea.emulab.it/...` o un target dedicato CI)
- Passare PAT a `Invoke-RemoteBuild.ps1` rispettando i vincoli sicurezza di §1.5
- [ ] Comportamento default invariato (`-UseGitClone` = `$false`) — nessuna regressione
- [ ] **Invoke-RemoteBuild.ps1 — modalità in-VM clone**
- [ ] Aggiungere parametri: `-CloneUrl`, `-CloneBranch`, `-CloneCommit`, `-GitPat`
(tutti opzionali, usati solo quando `-UseGitClone` è attivo)
- [ ] Quando presenti: anziché copiare zip, eseguire nella VM:
```powershell
git clone --recurse-submodules `
"https://Simone:$env:CI_GIT_PAT@gitea.emulab.it/Simone/<repo>.git" `
C:\CI\build
git -C C:\CI\build checkout $CloneCommit # pin al commit esatto
```
- [ ] Implementare iniezione/pulizia PAT secondo §1.5 (env var sessione, no argv, no log,
cleanup `[System.Environment]::SetEnvironmentVariable('CI_GIT_PAT', $null, 'Process')`)
- [ ] **Test e2e con `-UseGitClone`**
- [ ] Aggiornare snapshot `BaseClean` con Git installato
- [ ] Eseguire `e2e-010` con `-UseGitClone` su `nsis-plugin-nsinnounp` (ha submoduli)
- [ ] Confrontare tempo totale con e2e-009 (senza `-UseGitClone`)
- [ ] Verificare che il PAT non appaia nei log di `Invoke-CIJob.ps1`
---
## Linux Build VM (Future) — riferimento §6.1
Aggiungere supporto per build su Linux per testare la compilazione cross-platform
(es. versione Linux del plugin o build check con GCC/Clang).
- [ ] **Template VM Linux** — Provisionare template VM con Ubuntu Server 24.04 LTS
- [ ] Scegliere distro: Ubuntu 24.04 LTS (raccomandato) o Debian 12
- [ ] Installare toolchain: GCC, Clang, CMake, Python 3.x, build-essential
- [ ] Configurare accesso SSH (chiave pubblica da host) in alternativa/integrazione a WinRM
- [ ] Prendere snapshot `BaseClean-Linux`
- [ ] Archiviare credenziali SSH in Credential Manager (o key file su host)
- [ ] **Invoke-CIJob.ps1 — branch Linux**
- [ ] Aggiungere parametro `-GuestOS` (`Windows` / `Linux`) o rilevamento automatico da VMX
- [ ] Sostituire WinRM con SSH + SCP per trasferimento zip e raccolta artifacts
- [ ] Aggiungere supporto a comandi Linux (`bash`, `make`, `cmake --build`)
- [ ] Astrarre transport in `scripts/_Transport.psm1` (vedi §6.1)
- [ ] **Workflow Gitea — build matrix**
- [ ] Definire matrix strategy `[windows-build, linux-build]` nel workflow (vedi §6.4)
- [ ] Aggiungere label `linux-build` al runner (o registrare runner separato se necessario)
- [ ] Verificare che artifacts da entrambe le piattaforme vengano raccolti correttamente
- [ ] **Test e2e Linux** — eseguire almeno un build end-to-end su VM Linux
- [ ] Confermare che `build_plugin.py` (o analogo script) funzioni su Linux
- [ ] Confrontare artifact Linux con quelli Windows
---
## Suggerimento di sequencing
Sprint progressivi per ridurre rischio / ottenere valore prima:
1. **Sprint 1 (sicurezza)** — §1.1, §1.2, §1.3, §1.4 — superficie di attacco e bug noti.
2. **Sprint 2 (operatività)** — §2.1, §2.2, §2.3, §2.5 — rendere il sistema "fire and forget".
3. **Sprint 3 (osservabilità)** — §4.1, §4.3, §4.4 — visibilità prima di estendere.
4. **Sprint 4 (perf)** — §3.1, §3.2 misurati su un repo reale (baseline §3.6).
5. **Sprint 5 (qualità)** — §5.1, §5.2 — rifattorizzazione con safety net di test.
6. **Sprint 6+ (estensioni)** — Linux VM, workflow generico/composite action, federation.
Tenere ogni sprint piccolo: 1-2 voci alla volta, validare e2e dopo ognuna prima di passare
alla successiva.
---
## Reference Paths
| Item | Path / Value |
| ------------------------- | ------------------------------------------------------------ |
| vmrun.exe | `C:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe` |
| act_runner | `F:\CI\act_runner\act_runner.exe` (servizio: `act_runner`) |
| act_runner config | `F:\CI\act_runner\config.yaml` |
| act_runner logs | `F:\CI\act_runner\logs\` |
| Template VMX | `F:\CI\Templates\WinBuild\CI-WinBuild.vmx` |
| Snapshot name | `BaseClean` |
| Clone base dir | `F:\CI\BuildVMs\` |
| Artifact dir | `F:\CI\Artifacts\` |
| Log dir | `F:\CI\Logs\` (retention: 30 giorni) |
| Gitea URL (LAN) | `http://10.10.20.11:3100` |
| Gitea URL (ext) | `https://gitea.emulab.it` |
| Runner name | `local-windows-runner` (ID: 1) |
| Build VM subnet | `192.168.79.0/24` (VMnet8 — NAT, internet access per build) |
| Credential Manager target | `BuildVMGuest` |
| Guest username | `ci_build` — configurato e verificato |