# 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: ```powershell #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\` (interprete `F:\CI\python\venv\Scripts\python.exe`) | | Venv dev locale | `.venv\` (NON committare; gitignored) | | Install editable | `python -m pip install -e .[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 `Any` se esiste un tipo concreto/Protocol; `Any` solo per fixture pytest e mock. - Niente variabili globali. Lo state vive in oggetti passati esplicitamente (factory `backends.load_backend(config)`, `Config` dataclass). - Niente `print()`: usare `click.echo` (rispetta il flag `-q` di click e il redirect `err=True` per 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.yaml` esporta `PYTHONIOENCODING=utf-8` ad act_runner. **Mappatura script PowerShell → sotto-comando Python** (Fase A2/A3/A4): | PowerShell legacy (shim) | 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` | Restano in PowerShell (girano sull'host fuori dal flusso CI o nel guest): `Setup-Host.ps1`, `Register-CIScheduledTasks.ps1`, `Backup-CITemplate.ps1`, `Invoke-RetentionPolicy.ps1`, `Measure-CIBenchmark.ps1`, `Test-CapacityBurnIn.ps1`, tutti i `template/Deploy-*.ps1`, `template/Prepare-*.ps1`, `template/Install-CIToolchain-*.ps1` e i `tests/Test-*.ps1` di smoke locale. **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` | --- ## Transport layer - **Windows guest**: WinRM (`New-PSSession` con `New-CISessionOption` da `_Common.psm1`). Usare `Invoke-Command -Session`, `Copy-Item -ToSession / -FromSession`. - **Linux guest**: SSH/SCP via `ssh.exe` / `scp.exe`. Usare le funzioni di `scripts/_Transport.psm1`: `Invoke-SshCommand`, `Copy-SshItem`, `Test-SshReady`. - **vmrun.exe**: sempre invocato tramite `Invoke-Vmrun` da `_Common.psm1` (wrapper uniforme che gestisce `$LASTEXITCODE` e 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 template - `GITEA_CI_LINUX_TEMPLATE_PATH` — VMX Linux template - `GITEA_CI_CLONE_BASE_DIR` — `F:\CI\BuildVMs` - `GITEA_CI_ARTIFACT_DIR` — `F:\CI\Artifacts` - `GITEA_CI_GUEST_CRED_TARGET` — `BuildVMGuest` - `GITEA_CI_SSH_KEY_PATH` — `F:\CI\keys\ci_linux` - `CI_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 (no `python`/`py.exe` risolvibili), 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-Host` e' permesso (output catturato da act_runner; `Write-Output` interferisce con i return value) - `Invoke-Expression` vietato - Funzioni state-changing devono supportare `-WhatIf` / `ShouldProcess` - Credenziali solo come `[PSCredential]`, mai plain-text string - Nessuna variabile globale (`$global:`) --- ## Errori frequenti da evitare 1. **Sintassi PS 7+** in script PS 5.1 — vedi tabella sopra. 2. **`$LASTEXITCODE` non controllato** dopo `vmrun`, `ssh`, `scp` — controllare sempre e fare `throw` se non zero. 3. **Percorsi hardcoded senza parametro** — ogni path critico deve avere un parametro con default documentato. 4. **Modificare entrambi i branch (WinRM e SSH) nello stesso script** — mantenerli separati con `if ($GuestOS -eq 'Linux')`. 5. **Usare `New-PSSession` / WinRM verso host Linux** — Linux usa solo SSH tramite `_Transport.psm1`. 6. **Non rimuovere il seed ISO dal VMX** dopo il primo boot Linux — `ide1:0.present = "FALSE"` deve essere settato dopo cloud-init. 7. **`[ordered]@{}` non disponibile in PS 2** — non e' un problema qui (PS 5.1), ma ricordare che `[ordered]` e' PS 3+. 8. **`ForEach-Object -Parallel`** — disponibile solo in PS 7+; usare `foreach` classico o `Start-Job`. 9. **Snapshot template catturato con VM accesa** — produce file `*.vmem` / `*.vmsn` con memoria; `vmrun clone ... linked -snapshot=` rifiuta con `Error: The virtual machine should not be powered on. It is already running.` anche se `vmrun list` mostra 0 VM. Catturare snapshot SOLO da stato fully powered-off (verificare assenza di `.vmem` nella dir template). 10. **`vmrun getGuestIPAddress` per check "VM running"** — richiede VMware Tools attivi nel guest; in headless Tools puo' impiegare 30-60s a rispondere e il check appare bloccato. Usare `vmrun list` e cercare il path del VMX nell'output. 11. **Machine-id identico tra clone Linux** — le cloud image Ubuntu hanno un `/etc/machine-id` fisso. 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 snapshot `BaseClean-Linux` da stato powered-off. 12. **`& nativecmd 2>$null` NON 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 che `2>$null` possa scartarlo. Esempio: `ssh-keygen -R -f ` stampa "Host X not found in file" su stderr → eccezione terminante. Fix: usare `$ErrorActionPreference = 'SilentlyContinue'` attorno alla chiamata oppure `2>&1 | Out-Null`. Per i job CI su VM ephemere locali, usare direttamente `StrictHostKeyChecking=no UserKnownHostsFile=NUL` (default in `_Transport.psm1` con `KnownHostsFile=''`) — nessun `ssh-keygen` necessario.