Files
local-ci-cd-system/TODO.md
T
Simone 9f78742111 docs(cache): end-to-end numbers, operator runbook, and a vmrun gotcha
Measured through the real pipeline, not the unit tests: a Rust job goes from
186s to 84s (build phase 150s -> 47s), and 75s with the source modified, which
is the scenario CI actually runs. The entry is 645 MB and costs 8s to restore.

The test that decides whether this ships is row 4: a deliberate type error in
fury-core, built warm. The cache restored 645 MB of dependencies and the build
still went red with E0308, exit 1, publishing nothing. A cache that can turn a
red build green is the worst failure a CI system has because it is silent --
re-run that row whenever the prune rules change.

AGENTS.md #17 records something found while validating: `vmrun list` is
per-user. Run as the wrong user it answers "Total running VMs: 0" while a clone
is very much alive, which makes the "no VMs running" check before a
deploy --force (#14) unreliable. `ps | grep vmware-vmx` is user-independent and
was already the recommended diagnosis.
2026-07-22 22:15:32 +02:00

62 KiB
Raw Blame History

TODO — Local CI/CD System

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 — bootstrap macchina host (Setup-Host.ps1)

  • docs/WINDOWS-TEMPLATE-SETUP.md — provisioning template Windows (Deploy + Prepare + Install-CIToolchain-WinBuild2025)

  • docs/LINUX-TEMPLATE-SETUP.md — provisioning template Linux Ubuntu 24.04 (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


Summary

Last updated: 2026-05-11 (post Sprint 15: §7.5 DONE — Rinomina Setup-* → Install-CIToolchain-* (WinBuild2025/2022/Linux2404); post Sprint 14: §6.4+§6.5 DONE)

Stato generale: §1, §2, §4, §5, §7 chiusi. §3 completo. §6 chiuso: 6.1-6.2 DONE, 6.3 deferred, 6.4-6.6 DONE. Prossimi: Test Plan validation con VM reali.

Stato Area Done Open Note
[x] Setup & Infrastructure 8 0 Gitea + runner + dirs + rete tutti operativi
[x] Template VM (Fasi A→D) 4 0 Snapshot BaseClean preso, credenziali in Credential Manager
[x] Scripts Verification 3 0 e2e-008/009 SUCCESS, cleanup orfani + retry deleteVM ok
[x] Runner Configuration 2 0 config.yaml, capacity 4, BuildVMGuest
[x] Gitea Workflow Integration 5 0 tutti completati — example.yml usa composite action
[x] §1 Sicurezza & hardening 3 0 1.1, 1.4, 1.6 done; 1.2/1.3/1.5/1.7/1.8 deferred (home lab)
[x] §2 Affidabilità & resilienza 7 0 tutto done (2.1-2.7)
[x] §3 Performance & throughput 5 0 3.1/3.2/3.3/3.6/3.8 done; 3.4/3.5 deferred — §3 CHIUSO
[x] §4 Osservabilità & manutenzione 3 0 4.1/4.3/4.4 done (4.2 Prometheus, 4.5 Dashboard rimossi)
[x] §5 Qualità codice & test 5 0 5.1/5.2/5.3/5.4/5.5 done
[x] §6 Scalabilità & estensioni 5 0 6.1-6.2 done, 6.3 deferred (home lab), 6.4-6.5 done, 6.6 done
[x] §7 Refactor Deploy ↔ Setup 5 0 7.1-7.5 done; Setup-* → Install-CIToolchain-* (2026-05-11)
[x] In-VM Git Clone (§3.3) 4 0 COMPLETATO 2026-05-10 — e2e PASS: 34s/25.7% vs baseline
[x] Linux Build VM (§6.1) 5 0 Deploy/Prepare/Setup/Transport + doc + e2e PASS nsis7z.dll 2272 KB

Prossimi passi consigliati (vedi anche "Suggerimento di sequencing" in fondo):

  1. Test Plan — eseguire Invoke-Pester tests\ -Output Detailed + validare §2.1/§3.1/§3.6 con VM reali
  2. Test Plan — validazione con VM reali (§2.1 IP race, §3.1 shared cache, §3.6 benchmark, §4.1 JSONL) — prossima priorità
  3. Security — Rifiori se condizioni cambiano; vedi sezione "Deferred (Home Lab)"
  4. §3.5 — vCPU/RAM tuning per workload (P2, deferred — home lab optimization)
  5. Validate-SetupState.ps1 — update riferimento a Install-CIToolchain-* se compare il vecchio nome

Setup & Infrastructure

  • Gitea Server — già in esecuzione su http://10.10.20.11:3100 (e https://gitea.emulab.it)

    • Runner registration token ottenuto e usato
    • Create at least one test repository with .gitea/workflows/build.yml (Simone/nsis-plugin-ns7zip — testato e2e, workflow in gitea/workflows/build-ns7zip.yml)
    • Gitea Actions abilitato (runner registrato con successo)
  • act_runner — installato e registrato sull'host Windows

    • Binario gitea-runner v1.0.2 scaricato in F:\CI\act_runner\act_runner.exe
    • Registrato con Gitea (http://10.10.20.11:3100) — runner ID 1, nome local-windows-runner
    • config.yaml copiato in F:\CI\act_runner\config.yaml con label windows-build:host
    • Installato come servizio Windows via NSSM — stato: Running, StartType: Automatic
    • Verificare che il runner appaia "Online" nell'UI Gitea Admin → Actions → Runners
    • Confermare riavvio automatico dopo reboot host
  • Directory CI create su F:\CI\

    • F:\CI\BuildVMs\
    • F:\CI\Artifacts\
    • F:\CI\act_runner\
    • F:\CI\Cache\NuGet\
    • F:\CI\RunnerWork\
    • F:\CI\Templates\WinBuild2025\
    • F:\CI\Logs\ — log per job (retention: 30 giorni)
    • F:\CI\ISO\ — contiene 26100.1742.240906-0331.ge_release_svc_refresh_SERVER_VOL_x64FRE_en-us.iso (Windows Server 2025)
  • CredentialManager PowerShell module v2.0 installato (Scope: CurrentUser)

  • Rete VM: VMnet8 (NAT) — internet access per pip/nuget/npm durante i build

Template VM

  • Provision template VM in VMware Workstation — segui questi passi nell'ordine esatto:

    Fase A — Crea e installa la VM (NIC: NAT per internet)

    • Crea nuova VM in VMware Workstation con queste impostazioni: - 4 vCPU, 6144 MB RAM, disco 80 GB thin - VMX path: F:\CI\Templates\WinBuild2025\WinBuild2025.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
    • Installa Windows Server 2025 dall'ISO
    • 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 # AllowUnencrypted left false — Deploy post-install.ps1 configures HTTPS/5986 listener Set-Item WSMan:\localhost\Client\TrustedHosts -Value '*' -Force
    • 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
    • Nota l'IP assegnato dal DHCP di VMnet8 (es. 192.168.79.x) — ipconfig nella VM

    Fase B — Provisioning automatico dall'host

    • 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/5986 raggiungibile, Install-CIToolchain-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.
    • Setup completato con successo (VS Build Tools 2026 exit code 0).
    • Install-CIToolchain-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

    • VM rimane su VMnet8 (NAT) — internet access permanente per build che richiedono pip/nuget/npm
    • Spegni la VM: Start → Shut down
    • 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.
    • Lascia la VM spenta — non modificarla mai più dopo questo snapshot

    Fase D — Credenziali e config

    • Archivia credenziali guest sull'host (PowerShell elevato con CredentialManager): powershell New-StoredCredential -Target "BuildVMGuest" -UserName "ci_build" ` -Password "<your-build-password>" -Persist LocalMachine
    • Verifica GITEA_CI_TEMPLATE_PATH in F:\CI\act_runner\config.yaml — aggiornato a WinBuild2025.vmx (2026-05-08)

Scripts Verification

  • Test New-BuildVM.ps1 standalone — clone Clone_test-002 creato correttamente (2026-05-08)
  • Test Wait-VMReady.ps1 standalone — exit 0 in ~5s, VM pronta (2026-05-08)
  • Test Invoke-RemoteBuild.ps1 — dotnet restore + build OK, artifacts.zip creato in VM (2026-05-08)
  • Test Get-BuildArtifacts.ps1 — artifacts.zip (78 KB) copiato in F:\CI\Artifacts\test-003\ (2026-05-08)
  • Test Remove-BuildVM.ps1 — VM spenta + directory rimossa correttamente (2026-05-08)
  • Test Invoke-CIJob.ps1 end-to-end su una soluzione reale (nsis-plugin-ns7zip)
    • e2e-008: SUCCESS — build OK + artifact raccolto (1632 KB)
    • e2e-009: SUCCESS — cleanup automatico VM + clone confermato (finally block)
  • scripts/Cleanup-OrphanedBuildVMs.ps1 creato e verificato — -WhatIf, soglia -MaxAgeHours, hard stop + deleteVM + rimozione dir (2026-05-09)
  • scripts/Remove-BuildVM.ps1 fix vmrun deleteVM exit -1 — poll vmrun list + retry 3× backoff 0/3/6s (2026-05-09)

Runner Configuration

  • runner/config.yaml aggiornato con percorsi F:\CI\, rete VMnet8 (NAT), label corretti
  • capacity: 4 configurato (64 GB RAM / ~8 GB per VM)
  • Aggiornare GITEA_CI_TEMPLATE_PATH in config.yaml dopo creazione template VM
  • Archiviare credenziali guest in Windows Credential Manager (target: BuildVMGuest)

Gitea Workflow Integration

  • Copiare gitea/workflow-example.yml in .gitea/workflows/build.yml nel repo di test (workflow build-ns7zip.yml già presente e funzionante per nsis-plugin-ns7zip)
  • Push commit e verificare job in Gitea Actions UI
  • Verificare artifact scaricabile da Gitea dopo build riuscita
  • 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. COMPLETATO 2026-05-11: gitea/workflow-example.yml riscritto per usare la composite action Simone/local-ci-cd-system/.gitea/actions/local-ci-build@main. Commenti inline guidano la personalizzazione di build-command, artifact-source, submodules, SSH URL, extra-guest-env-json e artifact-name. Nessuna logica di orchestrazione nei repo chiamanti.

1. Sicurezza & hardening

1.1 [P0] [x] Migrare WinRM da HTTP/Basic a HTTPS/5986 — COMPLETATO 2026-05-10

Azioni:

  • Generare cert self-signed nel template prima dello snapshot (già in Deploy post-install.ps1).
  • Sostituire New-PSSession -ComputerName $ip con -UseSSL -Port 5986 -Authentication Basic in Invoke-RemoteBuild.ps1, Get-BuildArtifacts.ps1, Prepare-WinBuild2025.ps1, Validate-*.ps1.
  • Rimuovere AllowUnencrypted=true da Deploy post-install.ps1 e Install-CIToolchain-WinBuild2025.ps1 (assertion → false).
  • -SkipCACheck/-SkipCNCheck/-SkipRevocationCheck mantenuti nei New-PSSessionOption e New-WSManSessionOption (cert lab self-signed — documentato inline).
  • Regola firewall WinRM-HTTPS in Deploy ristretta a RemoteAddress '192.168.79.0/24'.
  • Wait-VMReady.ps1 usa New-WSManSessionOption + Test-WSMan -Port 5986 -UseSSL.
  • Prepare-WinBuild2025.ps1: preflight TCP/5986, host non imposta più AllowUnencrypted; solo TrustedHosts viene salvato/ripristinato.
  • Validate-DeployState.ps1, Validate-SetupState.ps1: connessione HTTPS/5986, check AllowUnencrypted=false.

1.4 [P1] [x] Validazione IP per-ottetto — COMPLETATO 2026-05-10

Regex per-ottetto sostituita in tutti e 4 i file: '^((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)$'

Estrazione in scripts/_Common.psm1 — vedi §5.2 (P2, backlog).

1.6 [P2] [x] Defender + Firewall + UAC tutti disattivati nel template — documentare il modello di minaccia — COMPLETATO 2026-05-10

File: docs/BEST-PRACTICES.md:§2.1 Threat Model.

COMPLETATO: Sezione "2.1. Threat Model — Disabled Security Features" aggiunta a BEST-PRACTICES.md. Documenta:

  • Stato corrente: Defender, Firewall, UAC disabilitate (costi vs. benefici tabellati).
  • Quando è accettabile: ambiente lab isolato con code trusted.
  • Quando rompe: code non fidate, sharing host, esposizione VMnet8 a LAN.
  • Mitigazioni: come riabilitare Firewall, Defender con esclusioni, UAC con costi specifici.

Future modifiche di sicurezza vanno documentate nello stesso posto.


2. Affidabilità & resilienza

2.1 [P0] [x] Race su IP allocation — COMPLETATO 2026-05-10

File: runner/config.yaml:17, scripts/Invoke-CIJob.ps1:244-253.

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] [x] Cleanup orfani schedulato — COMPLETATO 2026-05-10

File: scripts/Cleanup-OrphanedBuildVMs.ps1 — script pronto, scheduling registrato da scripts/Register-CIScheduledTasks.ps1.

Estendere Setup-Host.ps1 per registrare un Task Scheduler:

$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] [x] Retention artifact + log automatica — COMPLETATO 2026-05-10

File: scripts/Invoke-RetentionPolicy.ps1 — script creato; scheduling via scripts/Register-CIScheduledTasks.ps1.

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] [x] Remove-BuildVM.ps1 — supportare -WhatIf — COMPLETATO 2026-05-10

File: scripts/Remove-BuildVM.ps1:28.

[CmdletBinding(SupportsShouldProcess)] aggiunto; op distruttive (stop, deleteVM, Remove-Item) gated su $PSCmdlet.ShouldProcess.

2.5 [P1] [x] Snapshot versionato BaseClean_<yyyyMMdd> — COMPLETATO 2026-05-10

File: docs/BEST-PRACTICES.md:142-145, scripts/Invoke-CIJob.ps1:100.

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] [x] Backup automatico VMDK template — COMPLETATO 2026-05-10

File: scripts/Backup-CITemplate.ps1.

Copia atomica F:\CI\Templates\WinBuild → F:\CI\Backups\Template_<yyyyMMdd_HHmmss>\. Ferma act_runner prima, lo riavvia nel finally. Prune automatico (default: mantieni 3). SupportsShouldProcess → supporta -WhatIf. Esecuzione manuale pre-refresh.

2.7 [P2] [x] Health check del runner + monitoraggio Event Log — COMPLETATO 2026-05-10

File: scripts/Watch-RunnerHealth.ps1; task registrato da scripts/Register-CIScheduledTasks.ps1 come CI-RunnerHealth (ogni 15 min).

Controlla Get-Service act_runner. Se non Running: EventId 1002 (Warning) + Restart-Service. Cooldown: max 3 restart/h via JSON state file F:\CI\State\runner-restart-log.json. Oltre il limite: EventId 1004 (Error) + webhook :sos: senza restart. Webhook opzionale Discord/Gitea identico a Watch-DiskSpace.ps1.

2.8 [P2] [ ] IP-pool auto-riconciliazione vs clone reali — APERTO (2026-06-07)

ip-pool.json non viene riconciliato contro i clone vivi all'avvio. Un job killato mid-flight (SIGKILL/crash) leaka il lease in modo permanente; con soli 4 IP (192.168.79.201–204) per parallelism 4, un singolo leak esaurisce il pool e ogni round successivo fallisce con IP pool exhausted after 60s. Emerso durante il burn-in B7 (RUNBOOK §9) — pool inquinato da lease del 25 mag mai rilasciati. Workaround manuale: reset a {} con build-vms/ vuoto.

Fix proposto: all'allocazione (o in vm cleanup), rilasciare i lease il cui clone-dir non esiste più in CloneBaseDir. Opzionale: allargare il pool (es. 201–210) per dare margine oltre parallelism. Aggiungere test.

2.9 [P2] [ ] retention run non copre backup/template/ISO — APERTO (2026-07-22)

ci-retention-policy agisce solo su artifact, log e lease IP. Su questo host quei tre insiemi pesano 52 KB in tutto, mentre il disco era pieno al 94% per backups/ (195 G), templates/ (128 G) e iso/ (69 G) — tutti fuori dal suo raggio d'azione.

Sintomo osservato: il timer girava, rilevava meno di 50 GB liberi, dichiarava WARNING: Free space below 50 GB — aggressive retention: 7d e liberava 0.0 GB, due esecuzioni di fila. L'allarme suonava ma il meccanismo non poteva agire su ciò che pesava. Un guardiano che non sorveglia nulla è peggio di nessun guardiano: dà l'impressione che il problema sia sotto controllo.

Mitigato il 2026-07-22 (backup svuotati, --keep-count 1 --exclude WinBuildTest nella unit), ma è una correzione manuale: la policy automatica resta cieca.

Fix proposto: estendere retention run ai backup (per numero di generazioni o per età) e almeno segnalare — non cancellare — ISO e template che superano una soglia. In alternativa, se i backup restano governati solo da --keep-count, far sì che il warning sullo spazio dica quali directory pesano, invece di lamentarsi senza indicare dove guardare.

Nota: iso/ contiene coppie originale + .patched e versioni April superate da May (~38 G recuperabili); le .patched sono rigenerate da deploy-win. templates/WinBuildTest (41 G, ultimo uso 10 giugno, citato solo in docs/archived/2026-05-10/) è candidato alla dismissione: escluso dai backup ma ancora su disco.


3. Performance & throughput

3.1 [P1] [x] NuGet/pip cache su shared folder — COMPLETATO 2026-05-10

File: scripts/Set-TemplateSharedFolders.ps1, scripts/Invoke-RemoteBuild.ps1.

Set-TemplateSharedFolders.ps1: edita il VMX template aggiungendo due shared folder (nuget-cache → F:\CI\Cache\NuGet, pip-cache → F:\CI\Cache\pip), idempotente, .bak del VMX originale mantenuto, SupportsShouldProcess.

Invoke-RemoteBuild.ps1: aggiunto -UseSharedCache switch. Quando attivo, inietta $env:NUGET_PACKAGES nel scriptblock dotnet restore/build e $env:PIP_CACHE_DIR nel scriptblock custom build command.

Attivazione:

  1. Fermare template VM.
  2. .\Set-TemplateSharedFolders.ps1 (una tantum, pre-snapshot).
  3. Passare -UseSharedCache a Invoke-RemoteBuild.ps1 nei job dotnet/pip.

Dopo snapshot refresh: cache si riscalda al primo build — accettabile.

3.2 [P1] [x] Sostituire Compress-Archive con 7-Zip — COMPLETATO 2026-05-10

File: scripts/Invoke-RemoteBuild.ps1 (3 rimpiazzi Compress-Archive → 7-Zip + fallback).

Implementazione:

  • Host-side: nuova funzione Compress-BuildArtifact (check 7-Zip, fallback Compress-Archive) usata per comprimere source tree
  • Guest-side (custom build): inline 7-Zip check in scriptblock per artifact compression
  • Guest-side (dotnet build): inline 7-Zip check in scriptblock per output directory compression
  • Usa -mmt=on -mx1 per multi-thread con ratio simile a Compress-Archive

Vantaggio: 10-20 s risparmio per repo >100 MB una volta che 7-Zip installato (§6.6). Attuale: Fallback a Compress-Archive finché 7-Zip non nel template — zero overhead.

3.3 [P2] [x] In-VM clone (-UseGitClone) — COMPLETATO 2026-05-10

File: scripts/Invoke-CIJob.ps1, scripts/Invoke-RemoteBuild.ps1.

Implementazione:

  • Invoke-CIJob.ps1: -UseGitClone switch + conditional Phase 1 (skip host clone)
  • Invoke-RemoteBuild.ps1: Mode 1 (host) vs Mode 2 (guest) + params (CloneUrl, CloneBranch, CloneCommit, CloneSubmodules)
  • Guest clone: git clone direttamente nel VM, PAT da Credential Manager (§1.5 no-log cleanup)
  • Prerequisiti satisfatti: Git + 7-Zip in template (§6.6, completato)

Beneficio: Elimina host-zip-transfer overhead (rilevante per repo > 200 MB con submoduli). Prossimo: E2E test con nsis-plugin-ns7zip (-UseGitClone flag) per misurare impatto.

3.5 [P2] [ ] vCPU/RAM tuning per workload — DEFERRED (home lab)

File: docs/OPTIMIZATION.md:82-89, VMX template.

numvcpus=4, memsize=6144 è ragionevole ma non ottimo per ogni build. Per nsis-plugin-ns7zip (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.

Deferred: Non necessario per home lab; priorità bassa fino a che performance non è bottleneck.

3.6 [P2] [x] Benchmark baseline — COMPLETATO 2026-05-10

File: scripts/Measure-CIBenchmark.ps1.

Misura 5 fasi per iterazione: clone, start, IP acquire, WinRM ready, destroy. Output: tabella console + append a F:\CI\Logs\benchmark.jsonl (JSONL, una riga per iterazione). Supporta -Iterations N per ridurre varianza. Importa _Common.psm1 (Invoke-Vmrun, Resolve-VmrunPath).

Uso: .\Measure-CIBenchmark.ps1 prima e dopo §3.1/§3.2 per misurare impatto.


4. Osservabilità & manutenzione

4.1 [P1] [x] Log strutturati per parsing — COMPLETATO 2026-05-10

File: scripts/Invoke-CIJob.ps1:178-187.

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:

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.3 [P1] [x] Disk space alert — COMPLETATO 2026-05-10

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] [x] Runbook per incident comuni — COMPLETATO 2026-05-10

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.


5. Qualità codice & test

5.1 [P1] [x] Pester smoke tests sugli script — COMPLETATO 2026-05-10

File: tests/_Common.Tests.ps1, tests/New-BuildVM.Tests.ps1, tests/Remove-BuildVM.Tests.ps1, tests/Wait-VMReady.Tests.ps1.

Test con fake vmrun .cmd (exit code via %FAKE_VMRUN_EXIT% env var — nessuna VM reale):

  • _Common.psm1: New-CISessionOption TLS flags, Resolve-VmrunPath throw/return, Invoke-Vmrun ExitCode/Output/ThrowOnError
  • New-BuildVM.ps1: throw su template mancante, throw + cleanup su clone fail, formato nome Clone_{JobId}_{timestamp}
  • Remove-BuildVM.ps1: no-op su VMX mancante, cleanup dir parziale, -WhatIf non distrugge, destroy rimuove dir
  • Wait-VMReady.ps1: throw su vmrun mancante, throw su timeout, ValidatePattern IP (accetta/rifiuta)

Eseguire: Invoke-Pester tests\ -Output Detailed

5.2 [P2] [x] Modulo PowerShell condiviso scripts/_Common.psm1 — COMPLETATO 2026-05-10

File: scripts/_Common.psm1.

Esporta: New-CISessionOption (WinRM self-signed TLS), Resolve-VmrunPath (valida vmrun.exe), Invoke-Vmrun (wrapper uniforme -T ws, ritorna {ExitCode, Output}, supporta -ThrowOnError).

Importato da: Invoke-RemoteBuild.ps1, Get-BuildArtifacts.ps1, New-BuildVM.ps1.

5.3 [P2] [x] Esecuzione vmrun con check exit code uniforme — COMPLETATO 2026-05-10

Invoke-Vmrun in _Common.psm1. New-BuildVM.ps1 refactored per usarlo. Invoke-RemoteBuild.ps1 e Get-BuildArtifacts.ps1 usano New-CISessionOption.

5.4 [P2] [x] PSScriptAnalyzer rules custom — COMPLETATO 2026-05-10

File: PSScriptAnalyzerSettings.psd1.

Regole attive: PSAvoidUsingInvokeExpression, PSUseShouldProcessForStateChangingFunctions, PSUsePSCredentialType, PSAvoidUsingPlainTextForPassword, PSAvoidUsingConvertToSecureStringWithPlainText, PSAvoidGlobalVars, PSUseConsistentIndentation (4 spazi), PSUseConsistentWhitespace. Soppresso progetto-wide: PSAvoidUsingWriteHost (output strutturato per act_runner).

5.5 [P3] [x] Type hints nei param block — COMPLETATO 2026-05-10

File: tutti script in scripts/ — 15 file, tutti con type hints.

Coverage: Tutti param hanno type hints ([string], [int], [switch]) + validation ([ValidateRange], [ValidatePattern], [ValidateScript]) + [Parameter(Mandatory)] dove necessario.


6. Scalabilità & estensioni

6.1 [P2] [x] Linux Build VM

Documento di pianificazione dedicato: docs/LINUX-TEMPLATE-SETUP.md — bozza completa con fasi A→H, decisioni di design, differenze rispetto al template Windows.

Implementato — vedi 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] [x] Workflow generico riutilizzabile (composite action)

File: gitea/actions/local-ci-build/action.yml.

Status: COMPLETATO 2026-05-11 — Composite action implementata.

Inputs: build-command (required), artifact-source, submodules, guest-os, use-git-clone, configuration, template-path, snapshot-name, artifact-name, artifact-retention-days. Outputs: artifact-path, artifact-name. Inputs passati via variabili d'ambiente (no injection). Upload artifact incluso nell'action.

Usage da qualsiasi repo:

jobs:
  build:
    runs-on: windows-build   # or linux-build
    steps:
      - uses: actions/checkout@v4
      - uses: Simone/local-ci-cd-system/.gitea/actions/local-ci-build@main
        with:
          build-command: 'python build_plugin.py --final --dist-dir dist'
          artifact-source: 'dist'
          submodules: 'true'

6.3 [DEFERRED — home lab, bassissima priorità] 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.

Deferred: in un home lab single-host questa funzionalità non serve. Non pianificare.

6.5 [P3] [ ] Upgrade act_runner v1.0.4 → v2.x — VALUTATO E RIMANDATO (2026-07-22)

Versione installata: v1.0.4 (18 mag 2026). Ultima upstream al momento della valutazione: v2.1.0 (16 lug 2026). Release: https://gitea.com/gitea/runner/releases

Decisione: non aggiornare ora. Il beneficio netto per questo sistema è marginale. Valutazione fatta ispezionando il binario v2.1.0 (checksum verificato), non solo il changelog.

Cosa non cambia — verificato sul binario, non dedotto:

  • v2.1.0 esporta al job esattamente le stesse variabili di v1.0.4 (RUNNER_ARCH/TEMP/DEBUG/OS/TOOL_CACHE), nessun RUNNER_LABELS. Il GITEA_RUNNER_LABELS del changelog v2.1.0 è operator-side: imposta le label del daemon all'avvio, non le espone al workflow. Il routing label → template continua a richiedere un'istanza runner per label (vedi RUNBOOK-PhaseC, sezione deploy-rust).

Cosa non ci riguarda: il grosso di v2.x è lavoro su Docker/container (credenziali di pull, service container, dind, IPv6). Qui container.enabled è false, host mode puro. Anche l'unico breaking change di v2.0.0 (DOCKER_USERNAME/DOCKER_PASSWORD non più impliciti nei pull) è inapplicabile.

Cosa ci riguarderebbe:

Novità Valore reale
Cancellation handling completa (v2.0.0) + reap processi (v1.0.7/1.0.8) Un job cancellato può lasciare una build VM orfana — ma ci-cleanup-orphans.timer gira già ogni 6h
runner.post_task_script (v2.0.0) Aggancerebbe vm cleanup dopo ogni task: latenza orfani da ≤6h a immediata
jobs.<id>.timeout-minutes / continue-on-error (v2.0.0) Ergonomia workflow
register --token-file (v2.1.0) Già implementato a mano in scripts/install-rust-runner.sh

Schema di configurazione retrocompatibile: il diff v2.1.0 vs il nostro config è solo additivo, nessuna chiave rimossa.

Trigger per riaprire: quando serve runner.post_task_script per azzerare la latenza del cleanup orfani, oppure quando la 2.x è matura (era vecchia di 6 giorni alla valutazione). Non aggiornare "per stare aggiornati".

Vincolo operativo da ricordare: i due runner (act-runner.service e act-runner-rust.service) condividono lo stesso binario /opt/ci/act_runner/act_runner. Sostituirlo aggiorna entrambi insieme. Per un rollout graduale serve un secondo binario affiancato (act_runner-2.1.0), l'ExecStart del solo servizio rust puntato lì, un job di prova su runs-on: rust-build, e solo dopo la promozione per il runner principale — tenendo v1.0.4 per il rollback.

6.4 [P3] [x] Build matrix nel workflow

File: gitea/workflows/build-ns7zip.yml.

Status: COMPLETATO 2026-05-11 — build-ns7zip.yml riscritto con matrix strategy.

Matrix target: [windows, linux], runs-on: ${{ matrix.target }}-build, fail-fast: false. Usa la composite action con job-id-suffix: ${{ matrix.target }} per artifact dir separate e repo-url: ssh://gitea-ci/Simone/nsis-plugin-ns7zip.git. Artifact separati: nsis-plugin-ns7zip-windows-<tag> e nsis-plugin-ns7zip-linux-<tag>.

6.5 [P3] [x] Secret injection workflow-level

Status: COMPLETATO 2026-05-11 — ExtraGuestEnv [hashtable] aggiunto a:

  • Invoke-CIJob.ps1 (param -ExtraGuestEnv, forwarded a Invoke-RemoteBuild)
  • Invoke-RemoteBuild.ps1 (param -ExtraGuestEnv, iniettato nei guest):
    • Windows: [System.Environment]::SetEnvironmentVariable nel session ScriptBlock prima del build command
    • Linux: export KEY='val'; prefisso prima della cd && buildcmd
  • action.yml: input extra-guest-env-json (JSON object), parsato via ConvertFrom-Json, mai loggato

Usage dal workflow:

- uses: Simone/local-ci-cd-system/.gitea/actions/local-ci-build@main
  with:
    build-command: 'python sign_and_build.py'
    extra-guest-env-json: '{"SIGN_PASS":"${{ secrets.SIGN_PASS }}","CERT_THUMBPRINT":"${{ secrets.CERT_THUMBPRINT }}"}'

6.6 [P2] [x] Toolchain Tier-1 + Tier-2 in Install-CIToolchain-WinBuild2025

File: template/Install-CIToolchain-WinBuild2025.ps1, template/Prepare-WinBuild2025.ps1.

Status: COMPLETATO 2026-05-11 — Step 11 (Tier-1: Git + 7-Zip) + Step 12 (Tier-2: 8 tools) implementati.

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.

Tier-1 — Step 11 (implementato)

Tool Versione Razionale
Git for Windows 2.54.0.windows.1 Self-clone in VM, submodule fetch, fallback §3.2
7-Zip 26.01 Unpack/zip universale; usato da Step 12 stesso (vcpkg)
PowerShell 7.x 7.6.1 Parallel pipelines, operatori moderni, pwsh in CI
NSIS 3.12 Build installer Windows (es. nsis-plugin-ns7zip)
gh CLI 2.92.0 Upload artifact a release Gitea/GitHub, comment PR

Tier-2 — Step 12 (implementato)

Tool Versione Razionale
CMake 3.31.6 Build system C/C++ cross-platform
Node.js LTS 22.14.0 Frontend/Electron pipelines, npm toolchain
WiX Toolset 4.0.5 (dotnet tool) MSI building da .NET projects
Sysinternals latest (CDN Microsoft) Diagnostica process/handle quando build flaky
vcpkg HEAD (shallow clone) 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] [x] Spostare base OS hardening da Setup a Deploy

File: template/Deploy-WinBuild2025.ps1, template/Install-CIToolchain-WinBuild2025.ps1.

  • Aggiungere Set-NetFirewallProfile -Enabled False (tutti i profili) in Deploy post-install.ps1
  • Aggiungere MaxMemoryPerShellMB=2048, IdleTimeOut=7200000, MaxProcessesPerShell=25, StartupType=Automatic in Deploy dopo Enable-PSRemoting
  • 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
  • Setup Step 1 (Firewall) → Assert-Step only
  • Setup Step 2 (Defender) → già validation-only (refactor 2026-05-09)
  • Setup Step 3 (WinRM tuning) → Assert-Step only (rimuovi Enable-PSRemoting, winrm set, Set-Item, Set-Service)
  • Setup Step 4b (UAC) → Assert-Step only
  • Setup Step 5b (Explorer LaunchTo) → Assert-Step only
  • Setup Step 5c (Server Manager / DisableCAD / OOBE) → Assert-Step only
  • E2e validation — completata §7.4 (2026-05-10)

7.2 [P1] [x] WU lifecycle — strategia B (Deploy: GPO + Manual; Setup: lifecycle)

File: template/Deploy-WinBuild2025.ps1, template/Install-CIToolchain-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.

  • Deploy: rimuovere sc.exe config wuauserv start= disabled (e UsoSvc, WaaSMedicSvc)
  • Deploy: aggiungere GPO DisableWindowsUpdateAccess=1 (era mancante; NoAutoUpdate=1 già presente)
  • Setup Step 6 Step A: già rimuove DisableWindowsUpdateAccess + NoAutoUpdate prima del COM API — nessuna modifica necessaria
  • Setup Step 6 Step D: già fa Set-Service start=Manual + Start-Service — idempotente con servizi già Manual
  • Setup Step 6b: già disabilita wuauserv/UsoSvc + GPO keys — nessuna modifica necessaria
  • E2e validation — completata §7.4 (2026-05-10)

7.3 [P2] [x] Update header docs di entrambi gli script

7.4 [P2] [x] Test e2e refactor — COMPLETATO 2026-05-10

  • 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 (0 update trovati, ResultCode=0), Step 6b disabilita wuauserv/UsoSvc (StartType=Disabled)
  • Final gate del guest passa su tutti e 7 i check; snapshot BaseClean preso

Fix emersi durante §7.4 (applicati):

  • Deploy: UsoSvc impostato a Manual esplicitamente (default Server 2025 = Automatic)
  • Setup cleanup: Set-Service -StartupType Disabled ri-applicato su wuauserv/UsoSvc dopo DISM StartComponentCleanup (DISM/WaaSMedicSvc può resettare StartType)
  • Deploy + Setup: autologin Administrator (AutoAdminLogon=1) aggiunto in post-install.ps1 e validato in Assert-Step 5c
  • Nuovi script: template/Validate-DeployState.ps1, template/Validate-SetupState.ps1

7.5 [P3] [x] Rinomina Setup-* → Install-CIToolchain-* (2026-05-11)

Rinominati i tre script guest-side:

  • template/Setup-WinBuild2025.ps1 → template/Install-CIToolchain-WinBuild2025.ps1
  • template/Setup-WinBuild2022.ps1 → template/Install-CIToolchain-WinBuild2022.ps1
  • template/Setup-LinuxBuild2404.sh → template/Install-CIToolchain-Linux2404.sh

Tutti i riferimenti aggiornati in: Prepare-WinBuild2025/2022.ps1, Prepare-LinuxBuild2404.ps1, Deploy-WinBuild2025/2022.ps1, Deploy-LinuxBuild2404.ps1, README.md, TODO.md, docs/WINDOWS-TEMPLATE-SETUP.md, docs/LINUX-TEMPLATE-SETUP.md, docs/TEST-PLAN-v1.3-to-HEAD.md, gitea/actions/local-ci-build/action.yml.


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 (Install-CIToolchain-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 Install-CIToolchain-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:[email protected]/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 — COMPLETATO 2026-05-10

    • Aggiornare snapshot BaseClean con Git installato
    • Eseguire e2e-010 con -UseGitClone su nsis-plugin-ns7zip (ha submoduli)
    • Confrontare tempo totale con e2e-009 (senza -UseGitClone) — 34s / 25.7% più veloce
    • Verificare che il PAT non appaia nei log di Invoke-CIJob.ps1

Linux Build VM — riferimento §6.1 [IMPLEMENTATO]

Supporto build su Linux aggiunto con le seguenti fasi completate:

  • Template VM Linux — script di provisioning per Ubuntu Server 24.04 LTS

    • template/Deploy-LinuxBuild2404.ps1 — crea VM da cloud VMDK, cloud-init, SSH wait
    • template/Install-CIToolchain-Linux2404.sh — installa toolchain CI (GCC, Clang, CMake, Python 3, git, 7z), hardening SSH, swap off, timer apt off
    • template/Prepare-LinuxBuild2404.ps1 — SCP script, esegue via SSH, valida, snapshot BaseClean-Linux
  • Invoke-CIJob.ps1 — branch Linux

    • Parametro -GuestOS (Windows/Linux/Auto) con auto-detect dal VMX guestOS field
    • Transport SSH via scripts/_Transport.psm1 (Invoke-SshCommand, Copy-SshItem, Test-SshReady)
    • Phase 4/5/6 branch su GuestOS: SSH per Linux, WinRM per Windows (nessuna regressione)
  • Workflow Gitea — build matrix

    • Label linux-build:host aggiunto a runner/config.yaml
    • GITEA_CI_LINUX_TEMPLATE_PATH e GITEA_CI_SSH_KEY_PATH aggiunti alle env vars del runner
    • Matrix strategy cross-platform (windows + linux) documentata in gitea/workflow-example.yml
  • Doc updates

    • docs/ARCHITECTURE.md — SSH transport, _Transport.psm1, Linux VM boxes, topology SSH :22
    • docs/CI-FLOW.md — Step 0: Linux template + SSH key; Step 6 SSH transport note; Steps 7-8 Linux branch
    • docs/HOST-SETUP.md — directory layout: keys\ + LinuxBuild2404; SSH key gen step; Reference Paths Linux
    • docs/LINUX-TEMPLATE-SETUP.md — Status: implementato; tutte le checkbox Fasi A-E marcate [x]
  • Test e2e Linux — COMPLETATO 2026-05-11 — Test-Ns7zipBuild-Linux.ps1 4× PASS

    • Confermare che la build di esempio funzioni su Linux — nsis7z.dll 2272 KB prodotto
    • Confrontare artifact Linux con quelli Windows — 2272 KB su entrambe le piattaforme

Deferred (Home Lab)

Nel contesto di un home lab isolato, i seguenti security task hanno costi eccessivi relativi ai benefici in questo ambiente. Rifiori se le condizioni cambiano (code non fidata, sharing host, esposizione di rete, etc.).

1.2 [P1] [ ] Audit TrustedHosts setting

File: docs/BEST-PRACTICES.md:§2.

Verificare che host Windows abbia WSMan:\localhost\Client\TrustedHosts configurato per 192.168.79.* instead of *. Attualmente impostato a * durante setup template.

Stato attuale: Accettabile per lab — WinRM è isolato su VMnet8 NAT. Rifiori se: Host esposto a LAN / internet.

1.3 [P0] [ ] Pinning hash SHA256 degli installer

File: template/Install-CIToolchain-WinBuild2025.ps1.

SHA256 hash per dotnet-install.ps1, Python installer, vs_buildtools.exe.

Stato attuale: Struttura aggiunta, valori placeholder — funziona con warning. Rifiori quando: Build di codice di terze parti non fidate che scaricano installer.

1.5 [P1] [ ] PAT mai persistito per -UseGitClone

File: sezione "In-VM Git Clone (Ottimizzazione)" in TODO.

Vincoli: PAT da Credential Manager subito prima sessione, non in argv/log, cancellato in finally, grep post-job se compare in transcript.

Stato attuale: Struttura documentata, non implementata. Rifiori quando: -UseGitClone implementato (§3.3).

1.7 [P2] [ ] Rotazione password guest VM

Aggiornare BuildVMGuest in Credential Manager + nel template ogni trimestre.

Stato attuale: Password manuale. Rifiori quando: Change management/compliance lo richiede.

1.8 [P2] [ ] Verifica Get-StoredCredential

Confermare che Get-StoredCredential (CredentialManager module) funzioni in tutti gli script che lo usano post-migrazione HTTPS.

Stato attuale: Usato in template ma non testato in unit test. Rifiori quando: Script fallisce su recovery da Credential Manager.

3.4 [P3] [ ] Pre-warm pool di cloni

File: F:\CI\WarmPool\ (hypothetical); scheduled task via scripts/Register-CIScheduledTasks.ps1.

Mantenere 2 cloni avviati in background, pronti per il binding a job. Salta la fase di New-BuildVM + Wait-VMReady (~30-60s) nel percorso critico. Richiede una VM "watcher" che monitora il pool e ricrea cloni dopo uso.

Stato attuale: Disegnato ma non implementato. Dipende da §2.1 (IP allocator). Rifiori quando: Profiling mostra che New-BuildVM+Wait-VMReady è bottleneck >30% nel tempo totale di job. Con build >2 minuti, overhead stimato ~40% (rilevante solo per 1-min builds).

3.5 [P2] [ ] vCPU/RAM tuning per workload

File: docs/OPTIMIZATION.md, VMX template config.

Esporre parametri -VMCpu / -VMMemory a Invoke-CIJob.ps1 per permettere override da workflow. Attualmente fisso a numvcpus=4, memsize=6144 — subottimo per build leggeri (waste) e pesanti (throttle).

Stato attuale: Progettato, non implementato. Template supporta la modifica. Rifiori quando: Performance profiling mostra contentione CPU/RAM è bottleneck per la workload target. Per home lab, 4 vCPU/6 GB è ragionevole compromesso.

3.6 [P2] [ ] Stabilità WinRM sotto concorrenza 4× su host Windows

File: src/ci_orchestrator/transport/winrm.py, docs/RUNBOOK.md §13.

Burn-in §13 (2026-06-07) su host Windows: guest Windows 36/40 (round 6–8 hanno perso 1–2 job per fault WinRM transitori — ConnectTimeoutError su 5986 e WSManFault 2150858843 "shell not found" — con round inflazionati a 269/266/622 s). Guest Linux 40/40 (SSH stabile). Stesso hardware: host Linux §9 = Win 40/40. Auto-recupero OK (slot IP rilasciati, 0 orfani), ma OVERALL FAIL.

Causa probabile: oversubscription vCPU (4 VM × 4 vCPU = 16) + overhead host-OS Windows che manda in timeout WinRM concorrente (RAM non era il vincolo: 13.8 GB liberi su 63.7). Mitigazioni: ridurre vCPU/VM (lega a §3.5), -Parallelism 3, alzare connect-timeout pypsrp + retry a livello job in _windows_build. Rifiori quando: si vuole concorrenza 4× Windows-guest affidabile su host Windows.

3.7 [P3] [x] Shim Invoke-CIJob.ps1 — $IsWindows non guardato (PS 5.1 + StrictMode) — RISOLTO 2026-06-07 (Fase C)

File: scripts/Invoke-CIJob.ps1:37.

if ($null -ne $IsWindows -and $IsWindows -eq $false) dereferenzia $IsWindows, inesistente su Windows PowerShell 5.1 → con Set-StrictMode -Version Latest lancia VariableIsUndefined e il job fallisce subito. Si manifestava solo quando lo shim girava sotto PS 5.1 senza CI_VENV_PYTHON settato (il ramo auto-detect), cioè nelle invocazioni manuali/burn-in. Risoluzione: il percorso burn-in manuale non passa più dallo shim — bench run (Fase C) lancia i job concorrenti in-process via concurrent.futures, senza Start-Job/pwsh/$IsWindows. Lo shim resta solo per il runner Windows, dove CI_VENV_PYTHON è sempre settato. Il bug non è più raggiungibile sull'host Linux.

3.8 [P2] [x] Cache delle dipendenze cargo per i job Rust — COMPLETATO 2026-07-22

File: src/ci_orchestrator/cargo_cache.py, src/ci_orchestrator/assets/cargo-cache-prune.sh, piano in plans/cargo-dependency-cache.md, numeri in plans/cargo-dependency-cache-measurements.md.

Risultato misurato end-to-end: job Rust da 186 s → 84 s (fase build 150 s → 47 s). Con sorgente modificato — lo scenario CI vero — 75 s. Entry da 645 MB, restore 8 s. --no-cargo-cache e i tag v* restano a freddo.

La prova che conta: errore di compilazione deliberato in fury-core, run a caldo → error[E0308], job exit 1. La cache non può nascondere una regressione. Da ripetere ogni volta che cambiano le regole di potatura.

Misurato in furyrs#12: dei ~133 s di un job Rust, ~120 s sono la compilazione delle ~490 dipendenze esterne, che Cargo.lock committato tiene bit-identiche fra un run e l'altro. I crate del workspace costano 7 s. Il registry — che avevamo indicato per primo — vale 9,3 s (6,8%) e costerebbe +800 MB su template e backup: scartato esplicitamente, non è un rinvio.

Decisione presa con furyrs in #13: riusare solo gli artefatti delle dipendenze, mai quelli del workspace; i tag v* restano a freddo.

Il piano si discosta dalla forma cargo-chef proposta in #12 per un motivo concreto: l'orchestratore riceve build-command come stringa opaca (furyrs ne lancia sei, con tre set di feature diversi) e cargo chef cook va invocato con gli stessi flag del build vero. Invece di indovinarli, si fa il contrario — build normale, poi potatura degli artefatti dei membri del workspace da target/, e si pubblica il resto. Nessuna conoscenza del comando, una sola entry che contiene tutti i grafi di feature, nessun tool nuovo nel template.

La proprietà di sicurezza che furyrs ha chiesto diventa così verificabile: dopo la potatura, un'asserzione controlla che non sopravviva nessun fingerprint di un membro del workspace. Se ne trova uno, il job non pubblica. Fail closed — una entry mancante costa 120 s, una avvelenata costa un verdetto sbagliato, che è il guasto peggiore che un sistema di build possa avere.

Store: un file immutabile per chiave sotto /var/lib/ci/cache/cargo, chiave sha256(Cargo.lock) + rustc -V + host triple, pubblicazione con rename(2) atomico. Niente directory mutabile condivisa, niente lock — che è il punto sollevato da furyrs sul locking avvisorio delle Shared Folders con 3 job concorrenti.

Nota di collegamento con §2.9: il piano include la retention dello store nello stesso ciclo. Aggiungere una directory illimitata (una entry per cambio di Cargo.lock, centinaia di MB l'una) senza estendere retention run ripeterebbe l'errore di §2.9 su scala maggiore.

Nota emersa implementando: l'asserzione prevista dal piano era tautologica — ri-cercava gli stessi glob che aveva appena cancellato, quindi provava solo che rm fosse andato a buon fine, non che i pattern coprissero la realtà. Sostituita con un oracolo indipendente: i depfile .d che cargo scrive dicono da quali sorgenti ogni artefatto è stato compilato. Dettaglio nel documento delle misure.

8.1 [P3] [ ] Mirror GitHub Actions su Gitea locale (rimuovi dipendenza github.com)

File: .gitea/workflows/lint.yml, Gitea (org actions).

Dipendenza residua da github.com: actions/checkout@v4 in lint.yml (2×) — il codice dell'action è scaricato da github.com a runtime (con [actions] DEFAULT_ACTIONS_URL = github). La dipendenza per gli artifact è già stata eliminata (upload/download-artifact rimossi; handoff via filesystem host — vedi commit refactor(ci): drop upload/download-artifact). Questo task è l'alternativa "fix 1" non scelta: mirrorare le action GitHub usate (checkout) in una org actions sull'istanza Gitea e referenziarle via URL Gitea completo, eliminando del tutto github.com.

Stato attuale: Deferred. Artifact già github-free (scelta "fix 2" applicata). Resta solo actions/checkout@v4 in lint; act_runner la cacha dopo il primo fetch, quindi outage GitHub impatta solo cold cache. Rifiori quando: Host air-gapped, GitHub non raggiungibile dall'host CI, o si vuole indipendenza totale da github.com (anche per checkout).

8.2 [P3] [ ] Streaming live output build su Windows (WinRM)

File: src/ci_orchestrator/transport/winrm.py, src/ci_orchestrator/commands/build.py.

Linux/SSH ora streamma l'output del build in tempo reale (SshTransport.run_streaming — recv incrementale + PYTHONUNBUFFERED). Windows resta log-a-fine-job: pypsrp.Client.execute_ps ritorna solo a comando concluso; _windows_build usa _report_build_output (blocco unico) + coda nel messaggio d'errore.

Stato attuale: Deferred. Streaming WinRM richiede API basso livello RunspacePool/PowerShell con poll di ps.output/stream invece di execute_ps, semantica PSRP più complessa; output unbuffered dei tool nativi (MSBuild) scomodo. Diagnostica già adeguata (output completo a fine job + ultime righe nell'errore). Rifiori quando: Build Windows lunghi rendono l'assenza di progress live un problema operativo reale, o si standardizza lo streaming cross-OS.


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\WinBuild2025\WinBuild2025.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