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.
22 KiB
AGENTS.md — Environment Reference
Questo file descrive l'ambiente su cui e' costruito il progetto. Leggerlo prima di generare o modificare qualsiasi script.
Istruzione per gli agenti: questo file va tenuto aggiornato. Se durante il lavoro si scopre un errore nuovo, un vincolo non documentato, un pattern che ha causato problemi o una best-practice utile, aggiungerla nella sezione "Errori frequenti da evitare" o in una sezione apposita. Non aggiungere dettagli ovvi o gia' coperti; solo informazioni che impedirebbero errori concreti in sessioni future.
Host
| Parametro | Valore |
|---|---|
| OS | Windows 11 |
| Hardware | i9-10900X, 64 GB RAM, NVMe SSD |
| PowerShell | 5.1 (Windows PowerShell — NON Core/7) |
| VMware Workstation | Pro, vmrun.exe in C:\Program Files (x86)\VMware\VMware Workstation\ |
| SSH client | ssh.exe / scp.exe built-in Windows 11 (PATH) |
| act_runner | v1.0.2 — F:\CI\act_runner\act_runner.exe |
| Gitea | http://10.10.20.11:3100 / https://gitea.emulab.it |
PowerShell 5.1 — Vincoli critici
Tutti gli script devono girare su Windows PowerShell 5.1. Le seguenti sintassi sono disponibili solo in PS 7+ e NON vanno usate:
| Costrutto PS 7+ | Alternativa PS 5.1 corretta |
|---|---|
$x ??= 'default' |
if ($null -eq $x) { $x = 'default' } |
$a ?? $b |
if ($null -ne $a) { $a } else { $b } |
cmd1 && cmd2 |
cmd1; if ($LASTEXITCODE -eq 0) { cmd2 } |
cmd1 || cmd2 |
cmd1; if ($LASTEXITCODE -ne 0) { cmd2 } |
$x is [int] |
$x -is [int] |
Ternary $a ? $b : $c |
if ($a) { $b } else { $c } |
foreach-object con && |
pipeline classico con -ErrorAction Stop |
Ogni script deve avere in testa:
#Requires -Version 5.1
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
Python development
L'orchestratore CI vive in src/ci_orchestrator/ (package ci-orchestrator,
entry point python -m ci_orchestrator). Tutta la logica applicativa
nuova va scritta in Python, non in PowerShell. Gli script PS in scripts/
sono ridotti a shim a 3 righe verso la CLI Python e sono mantenuti solo
per compatibilita' con Task Scheduler e caller esterni.
| Parametro | Valore |
|---|---|
| Python | 3.11+ richiesto (requires-python in pyproject.toml) |
| Venv produzione | F:\CI\python\venv\ — install non-editable: pip install . (act_runner=LocalSystem; -e rompe con No module named ci_orchestrator). Ri-deploy a ogni modifica codice. |
| Venv dev locale | .venv\ (NON committare; gitignored) |
| Install (dev locale) | python -m pip install -e .[dev] — editable solo nel .venv dev |
| Test | python -m pytest tests\python -q --cov=ci_orchestrator --cov-fail-under=90 |
| Lint | python -m ruff check src tests\python |
| Type-check | python -m mypy --strict src |
Convenzioni Python (enforced da ruff + mypy --strict):
- Type hints obbligatori su funzioni pubbliche e helper esportati. Evitare
Anyse esiste un tipo concreto/Protocol;Anysolo per fixture pytest e mock. - Niente variabili globali. Lo state vive in oggetti passati esplicitamente
(factory
backends.load_backend(config),Configdataclass). - Niente
print(): usareclick.echo(rispetta il flag-qdi click e il redirecterr=Trueper stderr). - Niente
subprocess.run(..., shell=True). Niente shell-out per cose che esistono come libreria Python (paramiko per SSH, pypsrp per WinRM, keyring per credenziali). - Encoding stdio sempre UTF-8: i moduli Python lo assumono e
runner/config.yamlesportaPYTHONIOENCODING=utf-8ad act_runner.
Mappatura script PowerShell → sotto-comando Python (Fase A2/A3/A4 + C).
I .ps1 qui sotto sono archiviati in scripts/archived/ (congelati, esenti
da lint, nessun caller vivo — l'action Gitea chiama Python direttamente). Non
chiamarli né estenderli; vedi scripts/archived/README.md.
| PowerShell archiviato | Python sub-command |
|---|---|
Wait-VMReady.ps1 |
python -m ci_orchestrator wait-ready |
New-BuildVM.ps1 |
python -m ci_orchestrator vm new |
Remove-BuildVM.ps1 |
python -m ci_orchestrator vm remove |
Cleanup-OrphanedBuildVMs.ps1 |
python -m ci_orchestrator vm cleanup |
Invoke-RemoteBuild.ps1 |
python -m ci_orchestrator build run |
Get-BuildArtifacts.ps1 |
python -m ci_orchestrator artifacts collect |
Watch-DiskSpace.ps1 |
python -m ci_orchestrator monitor disk |
Watch-RunnerHealth.ps1 |
python -m ci_orchestrator monitor runner |
Get-CIJobSummary.ps1 |
python -m ci_orchestrator report job |
Invoke-CIJob.ps1 |
python -m ci_orchestrator job |
Start-BurnInTest.ps1 / -Linux |
python -m ci_orchestrator bench run |
Test-CapacityBurnIn.ps1 |
python -m ci_orchestrator bench run |
Test-Smoke.ps1 |
python -m ci_orchestrator smoke run |
Validate-HostState.ps1 |
python -m ci_orchestrator validate host |
Set-CIGuestCredential.ps1 |
python -m ci_orchestrator creds set |
Restano in PowerShell (girano sull'host Windows fuori dal flusso CI o nel guest,
non ancora portati): Setup-Host.ps1, scripts/Register-CIScheduledTasks.ps1,
scripts/Backup-CITemplate.ps1, scripts/Invoke-RetentionPolicy.ps1,
scripts/Set-TemplateSharedFolders.ps1, scripts/Test-CIGuestWinRM.ps1, gli
harness e2e scripts/Test-E2E-Section3.3.ps1 / Test-Ns7zipBuild-Linux.ps1 /
Test-NsinnounpBuild*.ps1, tutti i template/Deploy-*.ps1,
template/Prepare-*.ps1, template/Install-CIToolchain-*.ps1.
Test gate: coverage totale ≥90% (alzato in A5, poi a 90%). Nuovi moduli devono
includere unit test con mock; gli errori frequenti di questa stessa
sezione sono coperti da tests/python/test_agents_errors.py come
regression test — non rimuoverli senza aggiornare la voce in
"Errori frequenti da evitare" e motivare la rimozione.
Closeout fasi A: vedi plans/A1-closeout.md, plans/A2-closeout.md,
plans/A3-closeout.md, plans/A4-closeout.md, plans/A5-closeout.md
per stato per-fase, decisioni prese e voci pendenti a carico utente.
Struttura directory host (fissa)
F:\CI\
act_runner\ — binario e stato act_runner
Templates\
WinBuild2025\ — template Windows Server 2025
WinBuild2022\ — template Windows Server 2022
LinuxBuild2404\ — template Ubuntu 24.04
BuildVMs\ — clone efimeri (creati/distrutti per ogni job)
Artifacts\ — artifact raccolti (per job-id)
ISO\ — ISO/VMDK di bootstrap (cache download)
keys\
ci_linux — chiave SSH privata per guest Linux (no passphrase)
ci_linux.pub — chiave pubblica corrispondente
VM Template
Windows (WinBuild2025 — primario)
| Parametro | Valore |
|---|---|
| OS | Windows Server 2025 |
| VMX | F:\CI\Templates\WinBuild2025\WinBuild2025.vmx |
| Snapshot clone | BaseClean |
| Transport | WinRM HTTPS porta 5986, cert self-signed (lab-only) |
| Credenziali | Windows Credential Manager target BuildVMGuest |
| Rete | VMnet8 NAT — 192.168.79.0/24 |
| Toolchain | VS Build Tools 2026 (MSBuild 18.5 / v145), .NET SDK 10, Python 3.13, Git, 7-Zip |
Variante WinBuild2022: path F:\CI\Templates\WinBuild2022\WinBuild2022.vmx, stesso snapshot BaseClean.
Linux (LinuxBuild2404)
| Parametro | Valore |
|---|---|
| OS | Ubuntu 24.04 LTS (cloud image) |
| VMX | F:\CI\Templates\LinuxBuild2404\LinuxBuild2404.vmx |
| Snapshot clone | BaseClean-Linux |
| Transport | SSH porta 22, utente ci_build, chiave F:\CI\keys\ci_linux |
| Rete | VMnet8 NAT — 192.168.79.0/24 |
| Toolchain | GCC, G++, Clang, CMake, Ninja, Python 3, Git, 7-Zip |
| CI dirs | /opt/ci/{build,output,scripts,cache} — owner ci_build |
Linux Rust (LinuxRust2404)
Variante di LinuxBuild2404 per workspace Rust (es. app Tauri): stessa cloud
image e stesso layout guest, provisioning via
template/Install-CIToolchain-LinuxRust2404.sh che aggiunge le librerie
webview/GTK di Tauri v2 e una toolchain Rust system-wide sotto /opt/rust
(rustup + rustfmt + clippy + binario cargo-audit; il DB advisory di
cargo-audit NON e' bakato, viene fetchato a runtime).
| Parametro | Valore |
|---|---|
| OS | Ubuntu 24.04 LTS (cloud image) |
| VMX | LinuxRust2404/LinuxRust2404.vmx (sotto la dir templates) |
| Snapshot clone | BaseClean-LinuxRust |
| Transport | SSH porta 22, utente ci_build, stessa chiave di LinuxBuild2404 |
| Toolchain | quella di LinuxBuild2404 + webview/GTK Tauri + Rust in /opt/rust |
| Comandi | ci template deploy-rust / ci template prepare-rust |
--rust-channel (default stable) controlla la toolchain bakata: pinnare una
versione esatta (es. 1.97.1) per uno snapshot deterministico; con stable
rustup riscarica a runtime ad ogni nuova stable. Rigenerare lo snapshot quando
cambia il pin o la baseline apt di sicurezza.
Due vincoli sull'albero /opt/rust, entrambi regressioni gia' occorse:
chownDOPO l'installazione. Lo script gira come root, quindi tutto cio' che rustup/cargo creano nasce root-owned: unchownfatto solo prima copre le due directory vuote e nient'altro. Senza ilchownfinaleci_buildnon puo' scrivere ne' inRUSTUP_HOMEne' inCARGO_HOME/registry, e ogni build Rust con dipendenze esterne muore conPermission denied (os error 13). Gli assert devono verificare la scrivibilita' (sudo -u ci_build test -w), non solotest -xsui binari: altrimenti il template rotto passa la sua build e fallisce nei job dei consumatori./etc/profile.dda solo non basta. L'orchestratore esegue i comandi con paramikoexec_command+sh -c(transport/ssh.py,commands/build.py): shell non di login,profile.dnon viene mai sorgiato.RUSTUP_HOME/CARGO_HOMEvanno anche in/etc/environment(letto dapam_envall'apertura della sessione SSH) e i proxy rustup symlinkati in/usr/local/bin.
Transport layer
- Windows guest: WinRM (
New-PSSessionconNew-CISessionOptionda_Common.psm1). UsareInvoke-Command -Session,Copy-Item -ToSession / -FromSession. - Linux guest: SSH/SCP via
ssh.exe/scp.exe. Usare le funzioni discripts/_Transport.psm1:Invoke-SshCommand,Copy-SshItem,Test-SshReady. - vmrun.exe: sempre invocato tramite
Invoke-Vmrunda_Common.psm1(wrapper uniforme che gestisce$LASTEXITCODEe output).
NON modificare il branch WinRM esistente quando si lavora sul branch Linux e viceversa.
act_runner / Gitea Actions
- Labels runner:
windows-build:host,linux-build:host - Env vars iniettate in ogni job (da
runner/config.yaml):GITEA_CI_TEMPLATE_PATH— VMX Windows templateGITEA_CI_LINUX_TEMPLATE_PATH— VMX Linux templateGITEA_CI_CLONE_BASE_DIR—F:\CI\BuildVMsGITEA_CI_ARTIFACT_DIR—F:\CI\ArtifactsGITEA_CI_GUEST_CRED_TARGET—BuildVMGuestGITEA_CI_SSH_KEY_PATH—F:\CI\keys\ci_linuxCI_PYTHON_LAUNCHER— absolute path al Python di sistema usato per bootstrap del venv produzione (F:\CI\python\venv). act_runner gira come SYSTEM con PATH sanitizzato (nopython/py.exerisolvibili), quindi i workflow Python leggono questo env come single source of truth. Aggiornare solo quando si fa upgrade dell'interprete.
Regole di stile (PSScriptAnalyzer)
Configurazione in PSScriptAnalyzerSettings.psd1. Regole principali:
- Indentazione: 4 spazi (no tab)
Write-Hoste' permesso (output catturato da act_runner;Write-Outputinterferisce con i return value)Invoke-Expressionvietato- Funzioni state-changing devono supportare
-WhatIf/ShouldProcess - Credenziali solo come
[PSCredential], mai plain-text string - Nessuna variabile globale (
$global:)
Errori frequenti da evitare
-
Sintassi PS 7+ in script PS 5.1 — vedi tabella sopra.
-
$LASTEXITCODEnon controllato dopovmrun,ssh,scp— controllare sempre e farethrowse non zero. -
Percorsi hardcoded senza parametro — ogni path critico deve avere un parametro con default documentato.
-
Modificare entrambi i branch (WinRM e SSH) nello stesso script — mantenerli separati con
if ($GuestOS -eq 'Linux'). -
Usare
New-PSSession/ WinRM verso host Linux — Linux usa solo SSH tramite_Transport.psm1. -
Non rimuovere il seed ISO dal VMX dopo il primo boot Linux —
ide1:0.present = "FALSE"deve essere settato dopo cloud-init. -
[ordered]@{}non disponibile in PS 2 — non e' un problema qui (PS 5.1), ma ricordare che[ordered]e' PS 3+. -
ForEach-Object -Parallel— disponibile solo in PS 7+; usareforeachclassico oStart-Job. -
Snapshot template catturato con VM accesa — produce file
*.vmem/*.vmsncon memoria;vmrun clone ... linked -snapshot=<name>rifiuta conError: The virtual machine should not be powered on. It is already running.anche sevmrun listmostra 0 VM. Catturare snapshot SOLO da stato fully powered-off (verificare assenza di.vmemnella dir template). -
vmrun getGuestIPAddressper check "VM running" — richiede VMware Tools attivi nel guest; in headless Tools puo' impiegare 30-60s a rispondere e il check appare bloccato. Usarevmrun liste cercare il path del VMX nell'output peris_running. Per recuperare l'IP usaregetGuestIPAddress -wait(blocca internamente finché Tools registrano l'IP) e filtrare le righe che iniziano con"Error:"perché vmrun in modalità headless può tornare exit code 0 con"Error: The VMware Tools are not running..."su stdout invece di usare un exit code non-zero. -
Machine-id identico tra clone Linux — le cloud image Ubuntu hanno un
/etc/machine-idfisso. VMware DHCP assegna l'IP tramite DHCP client-id derivato da machine-id: se tutti i clone hanno lo stesso machine-id ottengono lo stesso IP e si bloccano per "IP collision". Fix nel template (prima di prendere lo snapshot):sudo truncate -s 0 /etc/machine-id && sudo rm -f /var/lib/dbus/machine-id && sudo shutdown -h now, poi ricreare lo snapshotBaseClean-Linuxda stato powered-off. -
& nativecmd 2>$nullNON sopprime stderr in PS 5.1 con$ErrorActionPreference='Stop'— la stderr del processo nativo viene convertita in ErrorRecord nel pipeline di PowerShell; sotto'Stop'questo diventa un errore terminante PRIMA che2>$nullpossa scartarlo. Esempio:ssh-keygen -R <ip> -f <file>stampa "Host X not found in file" su stderr → eccezione terminante. Fix: usare$ErrorActionPreference = 'SilentlyContinue'attorno alla chiamata oppure2>&1 | Out-Null. Per i job CI su VM ephemere locali, usare direttamenteStrictHostKeyChecking=no UserKnownHostsFile=NUL(default in_Transport.psm1conKnownHostsFile='') — nessunssh-keygennecessario. -
Quote WinRM/desktop-heap troppo basse per builds Windows paralleli — sintomo:
MSBuild error MSB6003: CL.exe could not be run. System.ComponentModel.Win32Exception (0x80004005): Not enough quota is available to process this command(oppure le altre tool MSVC: link.exe, mt.exe) quando il build interno fa fan-out parallelo (es. matrix x86/x64/x86-ansi, MSBuild/maxcpucount). Causa: il plugin endpoint Microsoft.PowerShell del guest haMaxMemoryPerShellMB=150di default e l'heap del desktop non-interattivo (Session 0) haSharedSection 3rd field=768 KB— entrambi insufficienti per più CL.exe concorrenti via WinRM. Non emerge in pre-migration Win→Win perchéInvoke-CommandsuPSSessionlocalhost-style eredita quote più larghe; emerge appena il path passa perpypsrpLinux→Win. Fix nel template (Deploy-WinBuild2025.ps1 / 2022 lo fa, Install-CIToolchain lo valida):Set-Item WSMan:\localhost\Plugin\Microsoft.PowerShell\Quotas\MaxMemoryPerShellMB 2048,Set-Item WSMan:\localhost\Shell\MaxProcessesPerShell 100, registryHKLM\SYSTEM\CurrentControlSet\Control\Session Manager\SubSystems\Windowspatch del 3° campoSharedSectiona 4096, reboot (csrss legge SharedSection solo al boot), nuovoBaseClean. Dettagli e procedura completa indocs/WINDOWS-TEMPLATE-SETUP.md § "WinRM quotas e desktop heap". -
deploy-* --forcelanciato mentre la VM target è ACCESA — corrompe la VM in esecuzione e produce un timeout allo Step 7 che sembra un problema di rete. Occorso il 2026-07-22 suLinuxRust2404, con unprepare-rustancora in corso sulla stessa VMX. Meccanica: (a) Step 1 vede<disk>.vmdk.lcke lo cancella come "stale artifact" — non era stale, apparteneva al processo vivo; (b) Step 4 riscrive il VMDK base sotto il guest in esecuzione, che muore; (c) Step 6 lancia il power-on, il nuovovmware-vmxmuore subito perché la VMX è già occupata, ma il controllo[OK] VM appears in vmrun listpassa lo stesso perché vede l'istanza vecchia; (d) Step 7 interroga per 10 minuti un guest morto e riportaError: The VMware Tools are not running in the virtual machine. Diagnosi in 5 secondi:ps -eo pid,lstart,etime,args | grep vmware-vmx— se ilvmware-vmxvivo è più vecchio del deploy appena lanciato, è questo il caso. Recupero:vmrun stop <vmx> hard, rimuovere i delta orfani<disk>-00000*.vmdk, verificarevmrun list= 0, poi rilanciare. Non interpretarlo come DHCP incastrato (vedi #15) e non risolverlo con un retry automatico: riavviare una VM già corrotta ripete il fallimento con la causa ormai illeggibile. -
Step 7 in timeout: distinguere tre cause diverse che danno lo stesso messaggio —
getGuestIPAddressrispondeThe VMware Tools are not runningsia quando il guest non ha IP, sia quando ha l'IP ma i Tools non rispondono. Ordine di verifica, dal più veloce: (a) collisione —ps | grep vmware-vmx, processo più vecchio del deploy corrente → vedi #14; (b) DHCP host incastrato —ip -4 addr show vmnet8deve dare192.168.79.1/24eps -ef | grep vmnet-dhcpddeve mostrarlo vivo; se no,sudo systemctl restart vmware-networks(ovmware-host.service); (c) guest senza lease o Tools non pronti — cercare il MAC della VMX in/etc/vmware/vmnet8/dhcpd/dhcpd.leasese inip neigh show dev vmnet8: se un lease c'è, il guest è vivo e mancano solo i Tools, quindi sondare TCP/22 direttamente invece di aspettaregetGuestIPAddress. -
Delta di snapshot orfani dopo
--force—deploy-*cancella<vm>.vmsd(il database snapshot) per evitare l'errore di resize sulla catena, ma non cancella i file delta<disk>-00000N.vmdk. Restano su disco con unparentCIDche non corrisponde più al base ricreato: innocui (fuori dal percorso di boot, la VMX punta al base conparentCID=ffffffff) ma confondono la diagnosi e vanno rimossi a mano. Verifica della catena:head -c 2000 <disk>.vmdk | strings | grep -E '^CID|parentCID'. -
vmrun listè per-utente e mente se lo lanci con l'utente sbagliato — le VM della CI le avviaci-runner;vmrun -T ws listeseguito comesimone(o come root) rispondeTotal running VMs: 0anche mentre un clone sta girando, perché vede solo l'istanza VMware dell'utente corrente. Occorso il 2026-07-22 durante la validazione della cache cargo:vmrun listdiceva 0 epsmostrava unvmware-vmxvivo da 19 minuti. Conseguenza pratica: il check "0 VM accese" prima di undeploy --force(vedi #14) non è affidabile se non lo si esegue comeci-runner. Usaresudo -u ci-runner vmrun -T ws list, oppure — meglio, perché indipendente dall'utente —ps -eo pid,lstart,etime,args | grep '[v]mware-vmx', che è anche la diagnosi già raccomandata in #14 e #15.