Files
local-ci-cd-system/TODO.md
T
Simone 96b65d40de feat(template): add Deploy-WinBuild2025.ps1 + autounattend; update TODO
template/Deploy-WinBuild2025.ps1:
  New host-side script that drives the unattended Windows install phase:
  creates the VM, injects autounattend.xml (disabling Defender/firewall/UAC
  before Setup-WinBuild2025.ps1 runs), boots from ISO, waits for first-boot.

template/autounattend.template.xml:
  WiM-based answer file template for Windows Server 2025 unattended install.
  Sets DisableAntiSpyware GPO + RTP off so Defender is fully off at first
  logon (prerequisite for Setup-WinBuild2025.ps1 Step-2 validation-only path).

TODO.md:
  - Date + wording updated (2026-05-10)
  - §1.1: file refs updated to post-refactor line numbers (Step 3 WinRM,
    Deploy-WinBuild2025 Enable-PSRemoting, Invoke-RemoteBuild, Get-BuildArtifacts)
  - §1.2: TrustedHosts audit status corrected (Setup-Host.ps1 never sets '*';
    Prepare appends IP and restores in finally)
  - §1.3: Python/dotnet/VS Build Tools line refs updated
  - §1.5: PAT security constraints expanded with grep-on-log safety net rule
  - §1.6: Defender/Firewall/UAC state updated to reflect Deploy vs Setup split
  - §3.3 deploy reference: VMX path corrected to CI-WinBuild.vmx
  - §3.3 doc ref: WINDOWS-TEMPLATE-SETUP updated to list Deploy step
2026-05-10 01:01:43 +02:00

43 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 + Setup-WinBuild2025)

  • 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

  • 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-nsinnounp — testato e2e, workflow in gitea/workflows/build-nsis.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\WinBuild\
    • 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\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
    • 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 Set-Item WSMan:\localhost\Service\AllowUnencrypted $true -Force 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/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.
    • Setup completato con successo (VS Build Tools 2026 exit code 0).
    • 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

    • 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 CI-WinBuild.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-nsinnounp)
    • 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-nsis.yml già presente e funzionante per nsis-plugin-nsinnounp)
  • 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. 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 (Step 3 WinRM), template/Deploy-WinBuild2025.ps1:524-534 (Enable-PSRemoting + AllowUnencrypted/Basic), scripts/Invoke-RemoteBuild.ps1:83-91, scripts/Get-BuildArtifacts.ps1:66-75.

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:

Azione residua: audit eventuali host già configurati con * (eredità manuale) e sostituire con la subnet build:

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 (Step 8 Python), template/Setup-WinBuild2025.ps1:757-885 (Step 9 VS Build Tools), template/Setup-WinBuild2025.ps1:660-705 (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:

$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-RemoteBuild.ps1:52, scripts/Get-BuildArtifacts.ps1:42, scripts/Wait-VMReady.ps1:43.

Motivazione: '^(\d{1,3}\.){3}\d{1,3}$' accetta 999.999.999.999. Sostituire con regex per-ottetto:

$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 (UAC, ServerManager, DisableCAD, OOBE — set da Deploy), template/Setup-WinBuild2025.ps1:176-336 (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, 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] Cleanup orfani schedulato

File: scripts/Cleanup-OrphanedBuildVMs.ps1 — script pronto, manca lo scheduling.

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] Retention artifact + log automatica

File: docs/OPTIMIZATION.md:160-181 — 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.

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, 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] Backup automatico VMDK template

File: docs/BEST-PRACTICES.md:130-138 — 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 — script presente in doc, non operativo.

Schedulare ogni 15 min: query gitea/api/v1/admin/runners, se local-windows-runner non onlineRestart-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, scripts/Invoke-RemoteBuild.ps1:158-194.

Motivazione: F:\CI\Cache\NuGet esiste come dir, non viene mai usata.

Aggiungere a CI-WinBuild.vmx:

sharedFolder0.present = "TRUE"
sharedFolder0.hostPath = "F:\\CI\\Cache\\NuGet"
sharedFolder0.guestName = "nuget-cache"

Poi nello scriptblock di build:

$env:NUGET_PACKAGES = '\\vmware-host\Shared Folders\nuget-cache'

Equivalente per pip:

$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 (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):
    & '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.

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, 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.

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.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:

$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 (già esiste).

Aggiungere regole specifiche progetto (PSScriptAnalyzerSettings.psd1 in root):

  • PSAvoidUsingPlainTextForPassword — rilevante per Setup-WinBuild2025.ps1:123 ([string] $BuildPasswordConvertTo-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 — 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.

Trasformare in composite action Gitea (.gitea/actions/local-ci-build/action.yml) richiamabile da qualsiasi repo:

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

Attualmente single job. Quando arriverà la VM Linux:

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.

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/Setup-WinBuild2025.ps1.

Da MUOVERE Setup → Deploy:

Concern Setup Step attuale Setup Azione Deploy
Firewall off all profiles Step 1 Aggiungere Set-NetFirewallProfile -Profile Domain,Private,Public -Enabled False in post-install (ora Deploy aggiunge solo regole RDP/ICMP)
WinRM tuning memoria/timeout Step 3 (parziale) Aggiungere MaxMemoryPerShellMB=2048, IdleTimeOut=7200000, MaxProcessesPerShell=25 in post-install dopo Enable-PSRemoting

Da CONVERTIRE in Setup a Assert-Step (validation-only):

Step Setup Diventa
Step 1 Firewall Assert-Step 3 profili Enabled=False (richiede prima la riga "Da MUOVERE" → Deploy: Set-NetFirewallProfile -Enabled False)
Step 2 Defender Già validation-only (refactor 2026-05-09)
Step 3 WinRM tuning Assert-Step MaxMemoryPerShellMB, IdleTimeOut, AllowUnencrypted, Basic
Step 4b UAC Assert-Step EnableLUA=0, LocalAccountTokenFilterPolicy=1
Step 5b Explorer LaunchTo Assert-Step HKCU LaunchTo=1
Step 5c Server Manager / DisableCAD / OOBE Assert-Step HKLM machine policy + DisableCAD=1 + OOBE policy

Da MANTENERE in Setup (build env, non base OS):

  • Step 4: build user ci_build + admin
  • Step 5: C:\CI\{build,output,scripts} dirs
  • Step 7-9: .NET SDK, Python, VS Build Tools
  • Step 10: Toolchain cross-check
  • Cleanup, Final pre-snapshot gate

Test acceptance:

  • Deploy stand-alone produce VM con tutte le validation Setup che passano (no Setup run)
  • Setup run dopo Deploy: tutte Assert-Step pass [OK] immediato (no work)
  • Test --Skip orthogonali (no regressione)

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

File: template/Deploy-WinBuild2025.ps1, template/Setup-WinBuild2025.ps1.

Stato attuale (conflitto ordering):

  • Deploy: sc.exe config wuauserv start= disabled + sc.exe stop wuauserv (idem UsoSvc, WaaSMedicSvc) + GPO NoAutoUpdate=1
  • Setup Step 6: necessita servizi UP per SchTask SYSTEM run WU
  • Setup Step 6b: re-disabilita servizi → doppio lavoro, fragile

Strategia B — separazione pulita:

Deploy (post-install.ps1):

  • Lascia servizi WU start=demand (Manual, default Windows) — NON disabled, NON stop
  • Setta solo GPO NoAutoUpdate=1, DisableWindowsUpdateAccess=1 → no background scheduled WU
  • Risultato: WU è invocabile on-demand ma non parte mai da solo

Setup (Step 6 / 6b):

  • Step 6 (skip se -SkipWindowsUpdate): sc.exe config wuauserv start= demand (idempotent), avvia SchTask SYSTEM, attende exit code, parse 0/2/3/3010
  • Step 6b (sempre, anche con -Skip): sc.exe config wuauserv start= disabled + sc.exe stop wuauserv (idem UsoSvc, WaaSMedicSvc), conferma GPO presenti — snapshot capture WU permanently off

Pattern run periodico patches (allineato §2.5 snapshot versionato):

  • Re-deploy template BaseClean_<yyyyMMdd> ogni N mesi via Deploy + Prepare (no -Skip) → patch applicate al template fresh

Validazione finale (Setup Final gate):

  • wuauserv.StartType == Disabled
  • UsoSvc.StartType == Disabled
  • GPO NoAutoUpdate=1, DisableWindowsUpdateAccess=1

Test acceptance:

  • Deploy stand-alone: Get-Service wuauserv | Select StartTypeManual, Get-ItemProperty -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU' -Name NoAutoUpdate → 1
  • Setup -SkipWindowsUpdate: Final gate vede StartType=Disabled (impostato da Step 6b)
  • Setup senza -Skip: WU runs, exit 0/2/3, poi servizi disabled

7.3 [P2] Update header docs di entrambi gli script

7.4 [P2] Test e2e refactor

  • Run Deploy stand-alone su VM test → verifica tutte le hardening voci presenti (reg query per chiavi UAC/OOBE/Server Manager, Get-NetFirewallProfile, Get-Service WMUauserv)
  • Run Prepare-WinBuild2025 dopo Deploy → tutti Assert-Step pass [OK] senza work
  • Run Prepare con -SkipWindowsUpdate → comportamento invariato
  • Run Prepare senza -Skip → WU runs, services lifecycled correttamente

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