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