- Add plans/idea-3-powershell-removal.md: Phase C goal is to eliminate the pwsh dependency from the Linux host by porting the remaining PS scripts (bench run, validate host, smoke run) to Python sub-commands. - Update idea-3-esxi-support.md header: Fase C → Fase D. - Update ideas-overview.md: new four-phase table (A/B/C/D) with prerequisiti and criteri for Phase C. - Rename all "Hook futuri Fase C" (ESXi) → "Hook futuri Fase D" in implementation-plan-A-B.md; add Phase C/D entries to summary and references section. - Update PhaseB-user-checklist.md end note and CLAUDE.md to point Phase C → pwsh removal, Phase D → ESXi. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
57 KiB
Piano implementativo unificato — Fase A (Python) + Fase B (host Linux)
Summary esecutivo
Obiettivo combinato: riscrivere l'orchestratore CI/CD in Python 3.11+ cross-platform (Fase A) e poi spostare l'host da Windows 11 a Linux Mint mantenendo VMware Workstation Pro come hypervisor (Fase B). Il risultato è un unico stack Python in produzione su Linux, con i template VM, i workflow Gitea Actions e act_runner invariati nella sostanza.
Sintesi fasi:
- Fase A — Rewrite Python dell'orchestratore (host Windows attuale)
- Output verificabile:
python -m ci_orchestrator job ...sostituisceInvoke-CIJob.ps1;pytest≥70% coverage; workflowbuild-ns7zip.ymlPASS. - Stato: COMPLETATA ✅ — validata end-to-end (vedi
plans/PhaseA-user-checklist.mdPassi 1-9), mergefeature → main2aa12bb, tagv2.0.0-phaseA. Dettaglio storico in §1.
- Output verificabile:
- Fase B — Migrazione host a Linux Mint + Workstation Pro Linux
- Output verificabile: act_runner come
act-runner.servicesystemd; storage/var/lib/ci/; burn-in 4 job concorrenti PASS. - Stato: pronta (gating "A done" soddisfatto). Esecuzione via
plans/PhaseB-user-checklist.md; design/rischi/cronoprogramma in §2/§3/§5/§6 di questo piano.
- Output verificabile: act_runner come
- Fase C — Eliminazione dipendenza
pwshdall'host Linux- Output verificabile:
bench run,validate host,smoke runportati in Python;pwshdisinstallabile senza impatti. - Stato: non in scope (da avviare dopo B stabile). Design in
plans/idea-3-powershell-removal.md.
- Output verificabile:
- Fase D — (Hook only, non implementata) backend ESXi via
pyVmomi- Output verificabile: Protocol
VmBackendrispettato;[backend]selector inconfig.toml. - Stato: non in scope.
- Output verificabile: Protocol
Decisioni architetturali già prese e non rinegoziabili:
- Linguaggio: Python 3.11+, packaging
pyproject.toml, CLI viaclick - Transport WinRM:
pypsrp(no dipendenza daNew-PSSession) - Transport SSH:
paramiko(no dipendenza dassh.exe/scp.exeesterni) - Credential store:
keyring(Credential Manager su Win, Secret Service su Linux) - Hypervisor host Linux: VMware Workstation Pro Linux (binario
/usr/bin/vmrun) - Astrazione hypervisor: Protocol
VmBackendcon prima implementazioneWorkstationVmrunBackend - Lint dual-stack:
ruff+mypy --strictper Python, PSScriptAnalyzer per i PS legacy residui
Vincoli che NON cambiano: act_runner v1.0.2+ come consumer agnostico di
shell, Gitea http://10.10.20.11:3100 / https://gitea.emulab.it,
template VM (WinBuild2025, WinBuild2022, LinuxBuild2404) e relativi
snapshot BaseClean / BaseClean-Linux, workflow YAML in
gitea/workflows/ (cambia solo la shell: e il comando invocato in Fase
A4; cambiano solo path/env vars in Fase B).
Checklist riassuntiva master
- [A1] Creare
pyproject.toml+ packagesrc/ci_orchestrator/+ venvF:\CI\python\venv\ - [A1] Implementare
config.py(env vars +config.toml) con default Windows e hook path Linux - [A1] Implementare
backends/protocol.pycon ProtocolVmBackend(firma neutra, novmrun_*nei nomi pubblici) - [A1] Implementare
backends/workstation.py(WorkstationVmrunBackend) usandosubprocess+shutil.which('vmrun') - [A1] Implementare
transport/winrm.pycon wrapperpypsrp.client.Client - [A1] Implementare
transport/ssh.pycon wrapperparamiko.SSHClient/SFTPClient - [A1] Implementare
credentials.pycon ProtocolCredentialStore+KeyringCredentialStore - [A1] PoC
wait-readyend-to-end viapypsrpcontro un guest Windows reale (smoke self-test PASS) - [A1] Test pytest unitari per
vmrun.py,winrm.py,ssh.py,credentials.pycon mock - [A1] Aggiungere job pytest a
gitea/workflows/lint.yml - [A2] Portare
Wait-VMReady.ps1→python -m ci_orchestrator wait-ready - [A2] Portare
Remove-BuildVM.ps1→vm remove - [A2] Portare
Cleanup-OrphanedBuildVMs.ps1→vm cleanup - [A2] Portare
Watch-DiskSpace.ps1→monitor disk - [A2] Portare
Watch-RunnerHealth.ps1→monitor runner - [A2] Portare
Get-CIJobSummary.ps1→report job - [A2] Sostituire ognuno dei
.ps1portati con shim a 3 righe verso la CLI Python - [A3] Portare
New-BuildVM.ps1→vm new - [A3] Portare
Invoke-RemoteBuild.ps1→build run - [A3] Portare
Get-BuildArtifacts.ps1→artifacts collect - [A3] Convertire test Pester
New-BuildVM.Tests.ps1,Wait-VMReady.Tests.ps1,Remove-BuildVM.Tests.ps1in pytest - [A4] Portare
Invoke-CIJob.ps1→python -m ci_orchestrator job - [A4] Aggiornare
gitea/actions/local-ci-build/action.ymlper invocare la CLI Python direttamente - [A4] Forzare
PYTHONIOENCODING=utf-8inrunner/config.yaml - [A4] Eseguire workflow
build-ns7zip.yml(matrix Win+Linux) end-to-end PASS (+ job release validato con tag throwaway) - [A5] Convertire errori frequenti
AGENTS.md#9, #10, #11, #12 in test pytest dedicati - [A5] Aggiornare
AGENTS.mdcon sezione "Python development" (venv, ruff, mypy, pytest) - [A5] Aggiornare
docs/ARCHITECTURE.mdcon nuovo layout package - [A5] Aggiornare
README.mdcon setup Python - [A5] Eseguire burn-in 4 job concorrenti PASS (Start-BurnInTest: 3 round × 4 = 12/12, 0 orfani)
- [B1] Installare Linux Mint LTS + aggiornamenti su hardware target
- [B1] Installare VMware Workstation Pro Linux (bundle ufficiale Broadcom) e validare
vmrunsu VM di test - [B1] Configurare
vmnet8NAT range192.168.79.0/24(allineato all'host Windows) - [B1] Creare utente
ci-runner(uid dedicato) e storage/var/lib/ci/{build-vms,artifacts,templates,keys}con ownershipci-runner:ci-runnermode 750 - [B1] Installare Python 3.11+ e creare venv in
/opt/ci/venv/conpip install -e .del package portato in Fase A - [B1] Validare
python -m ci_orchestrator --helpda utenteci-runner - [B2] Spegnere fully powered-off i template VM sull'host Windows
- [B2] Trasferire
F:\CI\Templates\→/var/lib/ci/templates/conrsync(preservare snapshotBaseClean/BaseClean-Linux) - [B2] Registrare i
.vmxsu Workstation Linux e validare snapshot viavmrun listSnapshots - [B2] Smoke test manuale:
vm new+wait-ready+vm removeda host Linux - [B3] Copiare
F:\CI\keys\ci_linux*→/var/lib/ci/keys/con perms 600 ownerci-runner - [B3] Re-store credenziali
BuildVMGuesteGiteaPATconkeyrings.alt.file.PlaintextKeyring(headless, no D-Bus —secret-toolscartato) - [B3] PoC accesso headless al keyring sotto systemd —
PlaintextKeyringverificato PASS - [B4] Scaricare act_runner Linux ≥ v1.0.2 e registrarlo verso Gitea con label
windows-build:hostelinux-build:host - [B4] Creare unit
/etc/systemd/system/act-runner.serviceconUser=ci-runner, envCI_*,PYTHONIOENCODING=utf-8;config.yamlconrunner.envsper i path template - [B4]
systemctl enable --now act-runner.servicee validare viajournalctl -u act-runner -f - [B5] Convertire
Register-CIScheduledTasks.ps1in coppie*.service+*.timer(cleanup-orphaned-vms,retention-policy,watch-disk-space,watch-runner-health,backup-template) - [B5] Abilitare tutti i timer con
systemctl enable --nowe validaresystemctl list-timers - [B6] Stop act_runner sull'host Windows e verifica coda Gitea vuota
- [B6] Rsync incrementale finale
F:\CI\Artifacts\→/var/lib/ci/artifacts/ - [B6] Trigger smoke workflow +
build-ns7zip.ymlmatrix da host Linux PASS - [B7] Eseguire burn-in 4 job concorrenti × 10 round su template Windows e Linux con tempi entro ±20% baseline
- [B8] (Facoltativo) Backup periodico di
F:\CI\ - [B8]
Decommissioning host WindowsN/A — la macchina è dual-boot: Windows e Linux sullo stesso hardware, non girano contemporaneamente. Nessuna dismissione. - [X1] Aggiornare
lint.ymlper girare ruff + mypy + pytest oltre a PSSA - [X2] Aggiungere observability: log strutturato
logging+ journald handler in Fase B - [X3] Documentare gestione credenziali headless (DPAPI machine scope su Win, vault
agesu Linux) - [X4] Aggiornare
docs/RUNBOOK.md,docs/HOST-SETUP.md,docs/CI-FLOW.mdend-of-Fase-B
0. Prerequisiti e gating
Da soddisfare prima di iniziare A1:
Prerequisiti Fase A soddisfatti (Fase A completata). Per
BuildVMGuestemerso in validazione: deve stare nel vault LocalSystem (act_runner service), username host-qualificato — vediscripts/Set-CIGuestCredential.ps1.
- Repo
local-ci-cd-systemclonato e workflowlint.yml+self-test.ymlverdi sull'host Windows attuale - Backup integro di
F:\CI\Templates\(snapshotBaseCleanvalidato convmrun listSnapshots) - Python 3.11+ installato sull'host Windows (venv produzione
F:\CI\python\venv, install non-editable) - Accesso amministrativo a Gitea per registrare/de-registrare runner durante A4 e B4
- Credenziali
BuildVMGuesteGiteaPATnel vault SYSTEM (LocalSystem), username host-qualificato AGENTS.mdletto integralmente da chi esegue il piano (errori #1–#12 sono test case obbligatori)- Hardware target Linux Mint disponibile fisicamente (può restare spento fino a B1)
1. Fase A — Rewrite Python (host Windows attuale)
✅ FASE A COMPLETATA E VALIDATA END-TO-END. Merge
feature/python-rewrite-and-linux-migration → main(2aa12bb), tagv2.0.0-phaseA. Validazione:plans/PhaseA-user-checklist.mdPassi 1-9 (smoke wait-ready/job Win+Linux, self-test entrambi i transport,build-ns7zip.ymlmatrix + job release, burn-in 12/12, benchmark). Le sottosezioni A1-A5 sotto sono record storico del piano; lo stato reale è negli[x]della checklist master e nel checklist utente.Deviazioni/scoperte rilevanti in validazione (non previste dal piano originale, ora gestite):
- venv produzione = install non-editable (act_runner=LocalSystem;
-epunta a path non risolti →No module named ci_orchestrator).lint.ymlnon deve installare nella venv condivisa (venv effimera).- Credenziali (
BuildVMGuest,GiteaPAT) nel vault SYSTEM non utente; username WinRM host-qualificato; transport WinRM forzaauth=ntlm. Helper:scripts/Set-CIGuestCredential.ps1,scripts/Test-CIGuestWinRM.ps1.- Gitea:
DEFAULT_ACTIONS_URL=github+uses:URL completo; repo action pubblico;upload/download-artifact@v4→@v3(GHES); artifact github-free (handoff via filesystem host).- Transport sorgente:
use-git-clone/submodulesdefault ON; variante host-side clone (--host-clone) per SSH alias host-only.- Shim PS: i forwarder ciechi NON inoltrano i common param PS.
- Deferred (non bloccanti,
TODO.md): §8.1 mirroractions/checkoutsu Gitea, §8.2 streaming live output build Windows.
A1 — Bootstrap progetto + moduli core
Obiettivo: creare scheletro package Python con VmBackend, transport
WinRM/SSH e credential store funzionanti contro l'ambiente reale via PoC
wait-ready.
Input / dipendenze: prerequisiti §0; scripts/_Common.psm1 e
scripts/_Transport.psm1 come riferimento di comportamento atteso;
AGENTS.md errori #9–#12 da preservare semanticamente.
Attività:
- Creare
pyproject.toml(build backendsetuptoolsohatchling) con dipendenzepypsrp,paramiko,keyring,click,tomli(se Python <3.11),richopzionale per logging - Creare layout
src/ci_orchestrator/con__init__.py,__main__.py(entry pointclick) - Creare venv in
F:\CI\python\venv\e installare il package in editable (pip install -e .[dev]) - Implementare
config.py: caricamento env vars (CI_ROOT,CI_TEMPLATES,CI_BUILD_VMS,CI_ARTIFACTS,CI_KEYS) con merge daconfig.tomlopzionale; default OS-aware (Windows →F:\CI\..., Linux →/var/lib/ci/...) - Implementare
backends/protocol.pycon ProtocolVmBackend(metodiclone_linked,start,stop,delete,get_ip,list_snapshots) e dataclassVmHandle(path/identificatore opaco) - Implementare
backends/workstation.py:WorkstationVmrunBackendche usasubprocess.run([vmrun, '-T', 'ws', op, *args], check=False, capture_output=True, text=True, encoding='utf-8')con check esplicito sureturncode - Implementare
transport/winrm.py: wrapper supypsrp.client.Client(host, username, password, ssl=True, cert_validation=False)con metodirun,copy,fetch - Implementare
transport/ssh.py: wrapper suparamiko.SSHClientconset_missing_host_key_policy(AutoAddPolicy),known_hostsconfigurabile (defaultNoneper non interferire con clone ephemeri — vediAGENTS.mderrore #12) - Implementare
credentials.py: ProtocolCredentialStore+KeyringCredentialStoreche usakeyring.get_credential(target, None)e ritorna oggettoCredential(username, password) - PoC end-to-end: comando
python -m ci_orchestrator wait-ready --vmx <path> --timeout 120su un clone reale del templateWinBuild2025(validare WinRM HTTPS self-signed) - Setup
pytest,pytest-mock,ruff,mypycome dev dependencies; configurarepyproject.tomlcon[tool.ruff]e[tool.mypy]strict - Test pytest unitari:
test_vmrun.py(mock subprocess, casivmrun listcon e senza VMX target — copre errore #10),test_winrm.py(mockpypsrp),test_ssh.py(mockparamiko, copre errore #12),test_credentials.py(mockkeyring) - Aggiungere job
pythonagitea/workflows/lint.yml: setup venv →ruff check src/ tests/→mypy --strict src/→pytest --cov=ci_orchestrator --cov-fail-under=70
Hook futuri Fase D: il Protocol VmBackend deve usare nomi neutri
(clone_linked, non vmrun_clone), accettare template/snapshot/name
come stringhe opache (non path Windows), separare clone_linked da
start (ESXi richiede PowerOn come task distinto). Niente assunzioni
"backend == stesso host del control plane" — WorkstationVmrunBackend
deve esporre la stessa firma anche se di fatto è locale.
Test / validazione:
pytest -q tests/PASSpytest --cov=ci_orchestrator --cov-fail-under=70PASSruff check src/ tests/0 errorimypy --strict src/0 errori- PoC
wait-readyritorna exit 0 entro 120s contro clone reale diWinBuild2025 gitea/workflows/lint.ymlPASS in CI
Rollback: rimuovere cartella src/, tests/, pyproject.toml,
venv F:\CI\python\venv\; ripristinare lint.yml precedente da git
(git checkout HEAD~1 -- gitea/workflows/lint.yml). Nessun PS legacy
modificato in A1.
Definizione di fatto step A1:
- PoC
wait-readyPASS contro VM reale - Coverage pytest ≥70% sui moduli core
lint.ymlaggiornato e verde- Protocol
VmBackendreviewato e congelato - Documentato setup venv in
README.md(sezione minima)
A2 — Script "foglia" (no state condiviso)
Obiettivo: portare in Python tutti gli script PS che non condividono stato con l'orchestratore, sostituendoli con shim minimi.
Input / dipendenze: A1 done. File: Wait-VMReady.ps1,
Remove-BuildVM.ps1, Cleanup-OrphanedBuildVMs.ps1,
Watch-DiskSpace.ps1, Watch-RunnerHealth.ps1, Get-CIJobSummary.ps1.
Attività:
- Implementare
commands/wait.pycon sottocomandowait-ready(parametri--vmx,--timeout,--guest-os) - Implementare
commands/vm.pycon sottocomandovm remove(parametri--vmx,--force) - Implementare
vm cleanup(scanCI_BUILD_VMS, riconoscimento clone orfani per pattern naming + età) - Implementare
commands/monitor.pyconmonitor disk(soglie configurabili, output JSON o human) - Implementare
monitor runner(controllo processo act_runner attivo + ultimo heartbeat) - Implementare
commands/report.pyconreport job(read-only su artifact dir + log job) - Sostituire
scripts/Wait-VMReady.ps1con shim PS 5.1 di 3 righe:& 'F:\CI\python\venv\Scripts\python.exe' -m ci_orchestrator wait-ready @args; exit $LASTEXITCODE - Idem per
Remove-BuildVM.ps1,Cleanup-OrphanedBuildVMs.ps1,Watch-DiskSpace.ps1,Watch-RunnerHealth.ps1,Get-CIJobSummary.ps1 - Aggiungere test pytest per ogni comando (mock backend + mock filesystem via
tmp_path) - Convertire
tests/Wait-VMReady.Tests.ps1intests/test_commands_wait.py(preservando i casi negativi) - Convertire
tests/Remove-BuildVM.Tests.ps1intests/test_commands_vm_remove.py - Verificare che gli scheduled task esistenti (
Register-CIScheduledTasks.ps1) continuino a funzionare invocando gli shim PS
Hook futuri Fase D: vm cleanup non deve assumere scan locale del
filesystem — la firma deve accettare un VmBackend come dipendenza,
così che in Fase D la stessa logica interroghi l'API ESXi (folder-scoped
list).
Test / validazione:
pytest tests/test_commands_*.pyPASS- Coverage globale rimane ≥70%
- Trigger manuale di
Watch-DiskSpacevia shim PS produce stesso output semantico della versione PS originale (json comparabile) - Smoke: clone manuale di una VM →
wait-ready→vm removeend-to-end
Rollback: git checkout HEAD~1 -- scripts/Wait-VMReady.ps1 ...
(ripristino .ps1 originali); rimozione commands/*.py portati;
scheduled tasks restano collegati ai .ps1 ripristinati.
Definizione di fatto step A2:
- Tutti gli script "foglia" hanno shim PS che chiama Python
- Test pytest sostituiscono i Pester corrispondenti
- Scheduled task continuano a funzionare via shim
- Nessuna regressione su
self-test.yml
A3 — Pipeline di build
Obiettivo: portare la pipeline core (clone → build → collect) in
Python preservando il comportamento dei .ps1 esistenti.
Input / dipendenze: A2 done. File: New-BuildVM.ps1,
Invoke-RemoteBuild.ps1, Get-BuildArtifacts.ps1.
Attività:
- Implementare
vm newincommands/vm.py(parametri--template,--snapshot,--name,--guest-os) - Integrare
WorkstationVmrunBackend.clone_linked+start+ attesa IP viaget_ip - Implementare
commands/build.pyconbuild run(parametri--vmx,--script,--workdir,--guest-os); usatransport.winrmper Windows,transport.sshper Linux - Gestione streaming output build verso stdout (act_runner cattura stdout — preservare comportamento
Write-Host) - Implementare
commands/artifacts.pyconartifacts collect(parametri--vmx,--remote-path,--local-dir) - Sostituire
scripts/New-BuildVM.ps1,Invoke-RemoteBuild.ps1,Get-BuildArtifacts.ps1con shim - Convertire
tests/New-BuildVM.Tests.ps1intests/test_commands_vm_new.py - Aggiungere test pytest per
build run(mock transport + capture stdout) - Aggiungere test pytest per
artifacts collect(mock SFTP/WinRM file copy + tmp_path) - Validare end-to-end: clone WinBuild2025 → build script PowerShell trivial → collect artifact ZIP
Hook futuri Fase D: build run deve ricevere un VmHandle opaco,
non un path VMX (ESXi non ha path locale al control plane). Adattare le
CLI a accettare entrambe (--vmx per workstation, --vm-id futuro per
ESXi) con dispatcher nel layer commands/.
Test / validazione:
pytest tests/test_commands_vm_new.py tests/test_commands_build.py tests/test_commands_artifacts.pyPASS- Coverage globale ≥70%
- Smoke: workflow CI manuale end-to-end usando esclusivamente gli shim
Test-NsinnounpBuild.ps1continua a passare (usa shim sotto)
Rollback: ripristino .ps1 originali da git; rimozione comandi
Python; nessuna modifica ai workflow YAML in A3 (solo shim).
Definizione di fatto step A3:
- Pipeline build completa accessibile via Python CLI
- Shim PS preservano l'API per i caller esistenti
- Smoke build PASS contro VM reale Windows e Linux
- Pester
New-BuildVM.Tests.ps1rimosso e sostituito
A4 — Orchestratore + switch workflow
Obiettivo: portare Invoke-CIJob.ps1 e fare switch dei workflow
Gitea per chiamare la CLI Python direttamente.
Input / dipendenze: A3 done. File: Invoke-CIJob.ps1,
gitea/actions/local-ci-build/action.yml, runner/config.yaml.
Attività:
- Implementare
commands/job.pycon sottocomandojob(entry point completo: parsing parametri job, clone, wait, build, collect, cleanup) - Gestione errori e cleanup garantito (try/finally con
vm removeanche su failure) - Aggiornare
gitea/actions/local-ci-build/action.yml: cambiareshell: powershell→shell: cmdo invocazione direttapython -m ci_orchestrator job ... - Aggiungere
PYTHONIOENCODING=utf-8inrunner/config.yamlenv - Lasciare
Invoke-CIJob.ps1come shim per scheduled task / chiamate manuali esterne (rimosso in A5) - Test pytest end-to-end con mock backend + transport:
test_commands_job.py - Eseguire workflow
build-ns7zip.ymlmatrix (Win + Linux) PASS - Eseguire workflow
self-test.ymlPASS - Eseguire workflow
lint.ymlPASS
Hook futuri Fase D: la selezione del backend deve avvenire in
job.py leggendo config.toml [backend].type (default workstation).
Niente import diretto di WorkstationVmrunBackend in job.py — usare
factory backends.load_backend(config).
Test / validazione:
pytest tests/test_commands_job.pyPASS con coverage ≥80% sul comandojob- Workflow
build-ns7zip.ymlend-to-end PASS dal nuovo entry point - Output catturato da act_runner leggibile (no encoding broken, no ANSI rotti)
- Tempi job entro ±10% rispetto al baseline PS (misurati con
Measure-CIBenchmarkancora in PS, lecito)
Rollback: git checkout HEAD~1 -- gitea/actions/local-ci-build/action.yml runner/config.yaml;
Invoke-CIJob.ps1 originale è ancora in git history e referenziabile.
Definizione di fatto step A4:
action.ymlchiama Python direttamente- Workflow matrix Win+Linux PASS
runner/config.yamlhaPYTHONIOENCODING=utf-8- Coverage
job.py≥80% - Misurazione tempo entro ±10% baseline
A5 — Test, documentazione, cleanup
Obiettivo: chiudere debito tecnico, rimuovere shim ridondanti, aggiornare documentazione, eseguire burn-in finale.
Input / dipendenze: A4 done.
Attività:
- Implementare test pytest dedicati per ognuno degli errori frequenti
AGENTS.md#9 (snapshot powered-off), #10 (vmrun listvsgetGuestIPAddress), #11 (machine-id reset → unique clone-name guard), #12 (stderr handling → AutoAddPolicy + no shell-out) - Verificare con
grep_searchche gli shim PS abbiano ancora call site esterni (Register-CIScheduledTasks.ps1, doc,Test-*.ps1) → nessuno candidato a rimozione in A5; rimozione differita a B5 (systemd timers) - Lasciare
Install-CIToolchain-*.ps1,Prepare-*.ps1,Deploy-*.ps1in PowerShell (girano dentro guest o per ricostruzione template — vedi §4 di idea-1) - Aggiornare
AGENTS.md: aggiungere sezione "Python development" (venv path, ruff, mypy strict, pytest, encoding utf-8, mappatura PS → Python) - Aggiornare
docs/ARCHITECTURE.mdcon nuovo layoutsrc/ci_orchestrator/+ Phase C extension point - Aggiornare
README.mdcon setup Python e quick-start CLI per tutti i 10 sub-comandi - Portare
Test-CapacityBurnIn.ps1incommands/burnin.py(o lasciare in PS se ancora utile come driver esterno) — non implementato in A5; lo shim attuale già delega viajobPython - Eseguire burn-in 4 job concorrenti × 10 round PASS sull'host Windows — richiede VM reali, a carico utente
- Aggiornare
docs/RUNBOOK.mdcon sezione "Operare da Python CLI" — deferito a chiusura Fase B (RUNBOOK riscritto end-to-end in B-finale)
Hook futuri Fase D: documentare in docs/ARCHITECTURE.md il
contratto VmBackend come "extension point" e il selettore [backend]
in config.toml come unica via per aggiungere ESXi.
Test / validazione:
pytest -qPASS, coverage globale ≥70%ruff check+mypy --strictpulitilint.ymlPASS (PSSA su PS residui + ruff/mypy/pytest su Python)- Burn-in 4×10 PASS senza VM orfane
grep_search Invoke-CIJobnei workflow YAML ritorna 0 hit
Rollback: ripristino doc da git; ri-aggiungere shim rimossi se qualche caller esterno non ancora migrato emerge.
Definizione di fatto step A5:
- Errori #9–#12 coperti da test pytest (
tests/python/test_agents_errors.py, 12 test) - Documentazione aggiornata (
AGENTS.md,docs/ARCHITECTURE.md,README.md) - Shim non referenziati rimossi → nessuno candidato (tutti referenziati da
Register-CIScheduledTasks.ps1/doc/Test-*.ps1); rimozione differita a B5 con sostituzione systemd - Burn-in PASS —
Start-BurnInTest.ps13 round × 4 job = 12/12, 0 orfani - Fase A done: validata end-to-end su hardware reale; mergiata in
main(2aa12bb), tagv2.0.0-phaseA
2. Fase B — Migrazione host Linux Mint
VINCOLO CATEGORICO (non negoziabile): compatibilità host Windows permanente. Fase B aggiunge il supporto host Linux accanto a Windows — non lo sostituisce né lo degrada. Windows host resta un target di prima classe e supportato, NON solo rollback. In concreto: B8 NON va eseguito come rimozione reale (l'host Windows resta operativo/usabile); nessun assunto Linux-only nel codice condiviso; la rimozione shim (B5) deve lasciare un modo Windows-nativo di invocare la CLI Python; ogni modifica Fase B deve dichiarare l'impatto su host Windows e preferire parità dual-host.
Parallelizzazione: B1 può iniziare durante A3/A4 (setup hardware indipendente dal codice). B2, B3 possono iniziare appena B1 è done e A5 è done. B4–B6 sono strettamente sequenziali dopo "A done".
B1 — Setup host Linux Mint in parallelo
Obiettivo: preparare hardware Linux Mint con Workstation Pro Linux, storage layout e venv Python pronti, senza impatto produzione.
Input / dipendenze: hardware target disponibile. Può girare in
parallelo a A3/A4. Riferimento idea-2-linux-host.md §4.
Attività:
- Installare Linux Mint LTS sull'hardware target + aggiornamenti (
apt full-upgrade) - Scaricare e installare VMware Workstation Pro Linux (bundle
.bundleda Broadcom) - Validare
vmrun -T ws --versionritorna versione coerente - Smoke test Workstation: creare VM trivia, clone, start, stop, delete via UI e via
vmrun - Configurare
vmnet8NAT range192.168.79.0/24(allineato all'host Windows) editando/etc/vmware/vmnet8/dhcpd/dhcpd.confo viavmware-netcfg - Creare utente di servizio
ci-runner(useradd -r -m -s /bin/bash ci-runner) con uid/gid dedicati - Creare layout
/var/lib/ci/{build-vms,artifacts,templates,keys}con ownershipci-runner:ci-runnermode 750 - Applicare ACL POSIX:
setfacl -d -m u:ci-runner:rwX /var/lib/ci/build-vms - Installare Python 3.11+ (
apt install python3.11 python3.11-venv) - Creare venv
/opt/ci/venv/conpython3.11 -m venve installare il package:/opt/ci/venv/bin/pip install -e /path/to/local-ci-cd-system - Validare
sudo -u ci-runner /opt/ci/venv/bin/python -m ci_orchestrator --help - Aggiungere
[paths.linux]aconfig.example.tomlcon i path POSIX
Hook futuri Fase D: lo stesso host Linux farà da control plane se
si attiverà ESXi. Non installare nulla che assuma "VM girano in locale"
(es. condivisioni vmware-shared-folders host-only).
Rischi specifici (da idea-2-linux-host.md §6):
vmrunsu Linux ha differenze sottili nel parsing output → mitigazione: test del modulovmrun.pysu Linux subito in B1, confronto output con Windows- Permessi
/var/lib/ci/build-vms/:vmruncomeci-runnerdeve scrivere VMDK CoW → mitigazione: ACL POSIX + test scrittura vmnet8NAT range diverso → mitigazione: allineamento range a192.168.79.0/24
Test / validazione:
vmrun -T ws listritorna lista vuota o coerentepython -m ci_orchestrator --helpPASS daci-runner- ACL POSIX verificata con
getfacl /var/lib/ci/build-vms pytest(subset cross-platform) PASS sul venv Linux
Rollback: il host Linux è fisicamente separato. Spegnerlo. Nessun impatto su produzione Windows.
Definizione di fatto step B1: ✅ COMPLETATO (2026-05-20)
- Workstation Pro Linux operativa (
/usr/bin/vmrun) - Storage layout
/var/lib/ci/creato con ACL, ownerci-runner - Python venv
/opt/ci/venv/pronto,ci_orchestratorinstallato vmnet8configurato su192.168.79.0/24
B2 — Trasferimento template VM
Obiettivo: copiare i template VMware dall'host Windows a Linux mantenendo snapshot integri.
Input / dipendenze: B1 done. Può girare durante A4/A5. Riferimento
AGENTS.md errore #9 (snapshot solo da powered-off).
Attività:
- Verificare assenza di
*.vmem/*.vmsndi runtime inF:\CI\Templates\<name>\(template fully powered-off) - Eseguire
rsync -av --progress /cygdrive/f/CI/Templates/ ci-runner@<linux-host>:/var/lib/ci/templates/(oscp -rse rsync non disponibile) - Verificare integrità:
find /var/lib/ci/templates -name '*.vmx' -exec vmrun -T ws listSnapshots {} \; - Aprire i
.vmxsu Workstation Linux per registrarli (eventuale prompt "I moved it / I copied it" → "I copied it") - Validare snapshot
BaseCleansuWinBuild2025.vmx,WinBuild2022.vmx - Validare snapshot
BaseClean-LinuxsuLinuxBuild2404.vmx - Smoke test:
python -m ci_orchestrator vm new --template /var/lib/ci/templates/WinBuild2025/WinBuild2025.vmx --snapshot BaseClean --name smoke1+wait-ready+vm remove
Hook futuri Fase D: i template restano in formato VMX. Per ESXi
serviranno OVF (ovftool) — non fare nulla in B2, ma documentare che
il dataset attuale è la base per export futuro.
Rischi specifici:
- Performance I/O linked clone su ext4 vs NTFS → mitigazione: misurare in B7, eventualmente XFS/BTRFS
- Snapshot corrotto in transit → mitigazione: checksum (
sha256sum) prima e dopo rsync
Test / validazione:
vmrun listSnapshotsritorna gli snapshot attesi su tutti i template- Smoke
vm new+wait-readyPASS con WinRM da Linux verso guest Windows - Smoke
vm new+wait-readyPASS con SSH da Linux verso guest Linux
Rollback: i template originali su F:\CI\Templates\ sono intatti.
Spegnere host Linux. Eventualmente eliminare /var/lib/ci/templates/.
Definizione di fatto step B2: ✅ COMPLETATO (2026-05-21)
- Tutti i template registrati su Workstation Linux
- Snapshot integri verificati
- Smoke
vm newPASS per Win e Linux
B3 — Trasferimento credenziali e chiavi
Obiettivo: rendere disponibili sull'host Linux le credenziali
guest e i token Gitea, accessibili da ci-runner headless.
Input / dipendenze: B1 done. Può girare durante A5. Riferimento
idea-2-linux-host.md §6 (rischio Alta su keyring headless).
Attività:
- Copiare
F:\CI\keys\ci_linuxeci_linux.pub→/etc/ci/keys/conchmod 600echown ci-runner:ci-runner - Re-store credenziale guest Windows:
sudo -u ci-runner secret-tool store --label='BuildVMGuest' service ci target BuildVMGuest - Re-store Gitea PAT:
sudo -u ci-runner secret-tool store --label='GiteaPAT' service ci target GiteaPAT - Validare lettura:
sudo -u ci-runner /opt/ci/venv/bin/python -c "import keyring; print(keyring.get_credential('BuildVMGuest', None))" - PoC headless: testare lettura keyring da contesto systemd (no sessione D-Bus utente). Se fallisce, implementare backend
keyringfile-based con vaultageosopscome fallback documentato - Documentare la scelta finale in
docs/HOST-SETUP.md
Hook futuri Fase D: anche ESXi userà credenziali da keyring (user
vSphere). Lo stesso KeyringCredentialStore deve poter caricare EsxiHost
target — niente hard-coding di nomi credenziali.
Rischi specifici:
secret-toolheadless: act_runner systemd non ha D-Bus user session → mitigazione: backend file-based conage(decisione PoC in B3)
Test / validazione:
- Lettura keyring PASS da utente interattivo
- Lettura keyring PASS da contesto systemd (
systemd-run --uid=ci-runner ...come simulazione) - Pytest
test_credentials.pycon backend Linux PASS
Rollback: rimuovere /etc/ci/keys/, secret-tool clear per ogni
target. Credenziali su host Windows intatte.
Definizione di fatto step B3: ✅ COMPLETATO (2026-05-21)
- Chiavi SSH copiate con perms corretti
- Credenziali nel keyring leggibili headless (
PlaintextKeyring—secret-tool/D-Bus scartato) - Strategia documentata in
docs/HOST-SETUP.md(pendente X3/X4)
B4 — Setup act_runner come systemd service
Obiettivo: act_runner Linux registrato verso Gitea e gestito da systemd, equivalente al servizio Windows attuale.
Input / dipendenze: B1, B2, B3 done. Sequenziale dopo "A done" (serve la CLI Python stabile per i comandi invocati dai workflow).
Attività:
- Scaricare binario act_runner Linux ≥ v1.0.2 in
/opt/ci/act_runner/ - Generare token registrazione su Gitea per il nuovo runner
- Registrare runner:
act_runner register --no-interactive --instance <gitea-url> --token <t> --name ci-linux --labels windows-build:host,linux-build:host - Creare
/etc/systemd/system/act-runner.service:[Service]User=ci-runner,WorkingDirectory=/var/lib/ci/runner,ExecStart=/opt/ci/act_runner/act_runner daemonEnvironment="PYTHONIOENCODING=utf-8",Environment="CI_ROOT=/var/lib/ci",Environment="CI_TEMPLATES=/var/lib/ci/templates",Environment="CI_BUILD_VMS=/var/lib/ci/build-vms",Environment="CI_ARTIFACTS=/var/lib/ci/artifacts",Environment="CI_KEYS=/etc/ci/keys"Restart=on-failure,RestartSec=10
systemctl daemon-reload && systemctl enable --now act-runner.service- Validare
journalctl -u act-runner -fmostra connessione a Gitea OK - NON avviare ancora workflow di produzione — il runner Windows è ancora primario
Hook futuri Fase D: env vars CI_* restano valide; per ESXi si
aggiungeranno solo CI_BACKEND=esxi + sezione [backend.esxi] in
config.toml. act-runner.service non cambia.
Rischi specifici:
- Runner Linux in idle che intercetta job destinati a Windows → mitigazione: in B4 il runner viene registrato ma in stato
pausedlato Gitea, oppure label distinte temporanee fino a B6
Test / validazione:
systemctl status act-runner→active (running)- Runner visibile in Gitea admin UI come "online"
- Job di test manuale (workflow
self-test.yml) PASS sul runner Linux journalctl -u act-runner --since "5min ago"senza errori critici
Rollback: systemctl disable --now act-runner, de-registrare
runner da Gitea. Runner Windows resta primario.
Definizione di fatto step B4: ✅ COMPLETATO (2026-05-21)
- act-runner.service attivo e abilitato
- Runner online in Gitea
self-test.ymlPASS dal nuovo runner (Win + Linux, entrambi i transport)- Logging via journald validato
B5 — Conversione scheduled tasks → systemd timers
Obiettivo: tutti i task periodici del runner Windows sono
replicati come coppie *.service + *.timer su Linux.
Input / dipendenze: B4 done. Riferimento Register-CIScheduledTasks.ps1
per l'inventario dei task.
Attività:
- Inventariare i task in
Register-CIScheduledTasks.ps1(identificare cadenza e comando per ognuno) - Per
cleanup-orphaned-vms: creareci-cleanup-orphaned-vms.service(oneshot,ExecStart=/opt/ci/venv/bin/python -m ci_orchestrator vm cleanup) +.timer(OnCalendar=hourly) - Per
retention-policy: creareci-retention-policy.service+.timer(OnCalendar=daily) - Per
watch-disk-space:ci-watch-disk-space.service+.timer(OnCalendar=*:0/15) - Per
watch-runner-health:ci-watch-runner-health.service+.timer(OnCalendar=*:0/5) - Per
backup-template:ci-backup-template.service+.timer(OnCalendar=weekly) systemctl daemon-reloadesystemctl enable --now <ognuno>.timer- Validare
systemctl list-timersmostra tutti i timer schedulati - Documentare il mapping in
docs/HOST-SETUP.md
Hook futuri Fase D: vm cleanup con backend ESXi userà la stessa
unit — il selettore backend è in config.toml, non nel comando.
Rischi specifici:
- Cadenza non perfettamente equivalente tra Task Scheduler e systemd timers → mitigazione: usare
OnCalendaresplicito +Persistent=trueper non perdere esecuzioni durante reboot
Test / validazione:
systemctl list-timers --allmostra tutti i timer attesi- Trigger manuale
systemctl start ci-cleanup-orphaned-vms.serviceesegue senza errori journalctl -u ci-*mostra esecuzioni riuscite
Rollback: systemctl disable --now <name>.timer per ognuno;
rimuovere file unit. Task Scheduler Windows ancora attivo.
Definizione di fatto step B5:
- Tutti i task periodici come timer systemd attivi
- Trigger manuale di ognuno PASS
- Mapping documentato
B6 — Cutover
Obiettivo: transizione produzione dall'host Windows al Linux in una finestra di manutenzione concordata.
Input / dipendenze: A done, B1–B5 done. Strettamente sequenziale.
Attività:
- Annunciare finestra di manutenzione
- Verificare nessun job in coda lato Gitea
- Stop act_runner sull'host Windows:
Stop-Service actions-runner(PS 5.1,$LASTEXITCODEcontrollato) - Disabilitare scheduled task Windows:
Unregister-ScheduledTask -TaskName 'CI-*' -Confirm:$false - Rsync incrementale finale
F:\CI\Artifacts\→/var/lib/ci/artifacts/(preserva permessi) - Verificare che il runner Linux sia "online" e non "paused" su Gitea
- Trigger manuale di workflow smoke (
self-test.yml) PASS - Trigger manuale
build-ns7zip.ymlmatrix Win + Linux PASS - Monitorare
journalctl -u act-runner -fper ≥30 min sotto carico reale
Hook futuri Fase D: nessuno specifico in B6. Cutover non tocca backend.
Rischi specifici:
- Gap di tempo tra stop runner Windows e start runner Linux durante il quale i job vanno in errore → mitigazione: cutover in finestra a zero traffic + Gitea UI "paused" preventivo
- WinRM da Linux verso guest Windows fallisce su edge case TLS → mitigazione: test in B2 + B4, fallback documentato in
idea-2-linux-host.md§7 (dual-host)
Test / validazione:
self-test.ymlPASS dal runner Linuxbuild-ns7zip.ymlmatrix Win+Linux PASS dal runner Linux- Nessun errore in
journalctl -u act-runner --since "1h ago"di severità critica - Artifact correttamente in
/var/lib/ci/artifacts/
Rollback (entro 30 min se PASS critico fallisce):
systemctl stop act-runnersu LinuxStart-Service actions-runnersu WindowsRegister-ScheduledTaskper riabilitare task Windows- Documentare incident e tornare a B1–B5 per fix
Definizione di fatto step B6:
- Runner Linux primario, runner Windows fermo
- Workflow matrix PASS dal nuovo host
- Artifact migrati
- 30 min di esercizio senza incidenti
B7 — Capacity burn-in sul nuovo host
Obiettivo: validare che il nuovo host regga il carico target con tempi paragonabili.
Input / dipendenze: B6 done.
Attività:
- Eseguire burn-in 4 job concorrenti × 10 round su template
WinBuild2025 - Eseguire burn-in 4 job concorrenti × 10 round su template
LinuxBuild2404 - Confrontare tempi medi vs baseline pre-migrazione (registrato in A5)
- Verificare success rate 100% (no VM orfane, no failure transienti)
- Misurare uso disco
/var/lib/ci/build-vms/durante e dopo burn-in (cleanup automatico) - Documentare risultati in
docs/RUNBOOK.md
Hook futuri Fase D: i risultati di B7 sono il baseline contro cui
confrontare un eventuale backend ESXi (criterio di "C done" in
idea-3-esxi-support.md §8).
Rischi specifici:
- Performance I/O ext4 inferiore → mitigazione: se delta >20%, valutare XFS/BTRFS
- VM orfane non pulite → mitigazione: timer
ci-cleanup-orphaned-vmsdeve essere già attivo (B5)
Test / validazione:
- Burn-in PASS con success rate 100%
- Tempo medio entro ±20% baseline Windows
- Nessuna VM orfana al termine
- Uso disco torna al baseline post-cleanup
Rollback: non applicabile (B7 è validazione, non modifica). Se fallisce, rollback a Windows (vedi B6 rollback).
Definizione di fatto step B7:
- Burn-in PASS
- Tempi documentati
- Nessuna VM orfana
B8 — Decommissioning host Windows — ANNULLATO (vincolo categorico)
B8 NON va eseguito. Vincolo categorico: l'host Windows resta supportato di prima classe in modo permanente (non solo rollback). Niente dismissione, niente riallocazione hardware che rimuova la capacità di girare su host Windows. Questo step resta solo come record storico del piano originale.
Obiettivo (annullato): dopo ≥1 settimana di stabilità, dismettere
host Windows. → Host Windows mantenuto operativo a regime.
Attività (NON eseguire la dismissione; resta solo, se utile):
- (Facoltativo) Backup periodico di
F:\CI\su archivio esterno - Host Windows resta installato e utilizzabile (run dual-host)
- Documentare in
docs/RUNBOOK.mdcome usare entrambi gli host Riallocare hardware— NON applicabile (Windows host permanente)
Hook futuri Fase D: nessuno.
Rischi specifici:
- Decommissioning prematuro elimina rollback → mitigazione: gating ≥1 settimana + ≥1 mese spento prima di riallocare
Test / validazione:
- Backup verificato (estrazione di prova in tmp)
- Host Windows spento, hardware integro
- Runbook aggiornato
Rollback: riaccendere host Windows, ripristinare scheduled task, re-registrare runner Windows verso Gitea.
Definizione di fatto step B8:
- Backup
F:\CI\archiviato e validato - Host Windows spento ma intatto
- Runbook rollback documentato
- Fase B done: tutti i criteri di "fatto" §7 soddisfatti
3. Step trasversali (X)
X1 — Lint dual-stack (PSSA + ruff/mypy)
Obiettivo: pipeline di lint unica che copre Python e PowerShell residuo.
Input / dipendenze: A1 (per la parte Python). Aggiornata in A5 e B8.
Attività:
- Aggiornare
gitea/workflows/lint.ymlcon jobpython-lint(ruff + mypy + pytest) e jobpwsh-lint(PSSA suscripts/,template/,tests/) - In B8: rimuovere o relegare a job opzionale la parte PSSA (resta utile solo per
template/Windows) - Configurare
PSScriptAnalyzerSettings.psd1per coprirescripts/,template/,tests/(già presente in repo) - Configurare
pyproject.tomlcon[tool.ruff],[tool.mypy],[tool.pytest.ini_options]
Hook futuri Fase D: nessuno specifico.
Test / validazione:
lint.ymlPASS con entrambi i job- 0 errori PSSA, 0 errori ruff, 0 errori mypy strict
Rollback: ripristino lint.yml da git.
Definizione di fatto step X1:
lint.ymlcon job dual-stack- 0 warning bloccanti
- Documentato in
AGENTS.md
X2 — Observability
Obiettivo: log strutturati + integrazione journald in Fase B.
Input / dipendenze: A1 (logger setup); B4 (systemd journald).
Attività:
- Configurare
loggingPython con formatter strutturato (JSON o key=value), nessun colore ANSI obbligatorio (act_runner non li gestisce sempre) - In B4: configurare service unit per inviare stdout/stderr a journald (default systemd) e validare
journalctl -u act-runner -o json - Documentare query journald utili in
docs/RUNBOOK.md
Hook futuri Fase D: log strutturati facilitano correlazione job ↔ backend.
Test / validazione:
journalctl -u act-runner -o jsonritorna eventi parsabili- Log Python catturato correttamente da act_runner stdout
Rollback: revert formatter logging.
Definizione di fatto step X2:
- Logging strutturato
- journald query documentate
X3 — Gestione credenziali headless
Obiettivo: credenziali accessibili da contesto SYSTEM (Win) e systemd (Linux) senza sessione utente interattiva.
Input / dipendenze: A1 (credential store); B3 (PoC headless Linux).
Attività:
- Documentare in
docs/HOST-SETUP.mdil problema noto: keyring Win sotto SYSTEM e keyring Linux sotto systemd richiedono soluzioni dedicate - In Fase A: documentare l'uso corrente (Credential Manager con utente di servizio + DPAPI machine scope)
- In B3: PoC + decisione finale (Secret Service via D-Bus user@.service, oppure file vault
age/sops) - Implementare backend
keyringfile-based come opzioneKeyringFileBackend(vault_path, age_key_path)se serve - Test pytest che simulano contesto headless (mock
keyring.get_credentialritornaNone→ fallback a file vault)
Hook futuri Fase D: stesso CredentialStore userà credenziali
vSphere — mantenere API generica.
Test / validazione:
- Lettura credenziale da contesto headless PASS in entrambi gli OS
- Test pytest fallback PASS
Rollback: usare credenziali in file plaintext non accettabile — nessun rollback se PoC fallisce, va risolto come blocker.
Definizione di fatto step X3:
- Strategia documentata
- Test pytest fallback verde
- PoC Linux PASS
X4 — Documentazione consolidata
Obiettivo: documentazione docs/ allineata allo stato finale (host
Linux + Python).
Input / dipendenze: A5 e B8.
Attività:
- Aggiornare
docs/ARCHITECTURE.md: layoutsrc/ci_orchestrator/, ProtocolVmBackend, hook ESXi - Aggiornare
docs/HOST-SETUP.md: setup Linux Mint, Workstation Pro Linux, ACL, keyring headless - Aggiornare
docs/CI-FLOW.md: nuovo entry point Python, env vars - Aggiornare
docs/RUNBOOK.md: comandi systemd, journalctl, troubleshooting - Aggiornare
docs/BEST-PRACTICES.md: convenzioni Python (no path hardcoded, ruff, mypy strict) - Aggiornare
AGENTS.md: sezione "Python development" + nota errore #12 N/A in Python - Aggiornare
README.md: quick-start Python + Linux
Hook futuri Fase D: lasciare placeholder "Backend ESXi (futuro)" in
docs/ARCHITECTURE.md e docs/HOST-SETUP.md.
Test / validazione:
- Lettura manuale: un nuovo operatore deve poter setuppare host Linux + lanciare un job seguendo solo
docs/ - Link interni risolvono (
grep_searchsu\[.*\]\(.*\.md\))
Rollback: revert doc da git.
Definizione di fatto step X4:
- Docs aggiornate
- Link interni validi
- Quick-start testato da operatore esterno
4. Hook architetturali per Fase D
Punti di design da rispettare durante A e B per non bloccare un futuro backend ESXi senza implementarlo:
- Protocol
VmBackendinsrc/ci_orchestrator/backends/protocol.py: metodiclone_linked(template, snapshot, name) -> VmHandle,start(vm),stop(vm, hard=True),delete(vm),get_ip(vm, timeout) -> str,list_snapshots(template) -> list[str]. Firma neutra, nessun riferimento avmrun,vmx,esxinei nomi pubblici. VmHandledataclass: campoidopaco (string). Per Workstation contiene il path VMX, per ESXi conterrà il MoRef. Chi consumaVmHandlenon può fare assunzioni sul contenuto.- Selezione backend via config:
config.tomlha sezione[backend]contype = "workstation"(default) o"esxi"(futuro). Factorybackends.load_backend(config)istanzia il backend corretto. Niente import diretto diWorkstationVmrunBackendnei comandi. - Separazione control plane / compute: il control plane (host con
act_runner + Python) non deve assumere che le VM girino in locale.
Le funzioni
vm new/vm cleanupricevono il backend come parametro, non leggono filesystem locale (eccetto perWorkstationVmrunBackendinternamente). - Naming neutro nelle API pubbliche: i sottocomandi CLI sono
vm new,vm remove,vm cleanup(nonvmrun-clone). Le env vars sonoCI_BUILD_VMS,CI_TEMPLATES(nonVMRUN_*). - Credenziali:
CredentialStoreaccetta target name arbitrario. Per ESXi si aggiungerà targetEsxiHostsenza modifiche al codice. - Logging strutturato: include sempre
backend=<type>evm_id=<handle.id>nei log per tracciabilità multi-backend.
Riferimento esplicito: idea-3-esxi-support.md
§3 (interfaccia EsxiBackend) e §4 (mappa operazioni).
5. Cronoprogramma e dipendenze
flowchart TD
P0[0. Prerequisiti] --> A1
A1[A1 Bootstrap + core] --> A2[A2 Script foglia]
A2 --> A3[A3 Pipeline build]
A3 --> A4[A4 Orchestratore + workflow]
A4 --> A5[A5 Test + docs + cleanup]
A1 --> X1[X1 Lint dual-stack]
A1 --> X2[X2 Observability]
A1 --> X3[X3 Credenziali headless]
A3 -.parallelo.-> B1[B1 Setup host Linux]
A4 -.parallelo.-> B1
B1 --> B2[B2 Trasferimento template]
B1 --> B3[B3 Credenziali Linux]
A5 --> B4
B2 --> B4[B4 act_runner systemd]
B3 --> B4
B4 --> B5[B5 Timer systemd]
B5 --> B6[B6 Cutover]
B6 --> B7[B7 Burn-in Linux]
B7 --> B8[B8 Decommissioning Win]
A5 --> X4[X4 Docs consolidate]
B8 --> X4
X3 --> B3
X1 --> A5
X2 --> B4
6. Matrice rischi consolidata
Ogni voce: Rischio — Fase / Severità / Mitigazione / Owner action item.
pypsrpedge case con WinRM HTTPS self-signed- Fase: A1 — Severità: Media
- Mitigazione: PoC
wait-readycome primo deliverable A1 prima di committare al resto. - Owner: A1 attività #10.
keyringsotto SYSTEM (act_runner service) non vede credenziali utente- Fase: A1, B3 — Severità: Alta
- Mitigazione: DPAPI machine scope su Win; PoC vault file
age/sopssu Linux in B3. - Owner: X3 attività complete.
- Perdita di know-how errori
AGENTS.md#9–#12 durante refactor- Fase: A5 — Severità: Media
- Mitigazione: ognuno convertito in test pytest prima di rimuovere
il
.ps1corrispondente. - Owner: A5 attività #1.
- Doppio mantenimento PS+Python durante migrazione
- Fase: A2–A4 — Severità: Alta
- Mitigazione: strategia shim minimizza la finestra;
lint.ymldual-stack. - Owner: X1 attività complete.
- Astrazione
VmBackendover-engineered se Fase D non parte mai- Fase: A1 — Severità: Bassa
- Mitigazione: 1 implementazione concreta + Protocol = ~50 LOC extra; costo trascurabile.
- Owner: accettato.
- act_runner cattura male output Python (encoding cp1252)
- Fase: A4 — Severità: Media
- Mitigazione:
PYTHONIOENCODING=utf-8inrunner/config.yaml. - Owner: A4 attività #4.
vmrunsu Linux ha differenze parsing outputlist/getGuestIPAddress- Fase: B1 — Severità: Media
- Mitigazione: test modulo
vmrun.pysu Linux subito in B1. - Owner: B1 attività #3.
- Performance I/O linked clone su ext4 vs NTFS
- Fase: B7 — Severità: Bassa
- Mitigazione: misurare con burn-in B7; eventualmente XFS/BTRFS.
- Owner: B7 attività #5.
vmnet8NAT range diverso → IP collision LAN- Fase: B1 — Severità: Media
- Mitigazione: allineare
192.168.79.0/24in B1. - Owner: B1 attività #5.
- WinRM HTTPS da Linux: TLS 1.2 cipher non supportati dal Windows guest
- Fase: B2 — Severità: Media
- Mitigazione: smoke test prima del cutover; stesso codice già testato in A1 da Win host.
- Owner: B2 attività #7.
- Permessi
/var/lib/ci/build-vms/perci-runner- Fase: B1 — Severità: Media
- Mitigazione: ACL POSIX con
setfacl -d -m. - Owner: B1 attività #8.
secret-toolheadless senza D-Bus session- Fase: B3 — Severità: Alta
- Mitigazione: PoC + fallback file vault
age. - Owner: B3 attività #5.
- Errore
AGENTS.md#11 (machine-id Linux) si ripropone- Fase: B2 — Severità: Bassa
- Mitigazione: già documentato; fix nel template prima dello snapshot.
- Owner: preservato.
- Errore
AGENTS.md#12 (stderr nativa +'Stop')- Fase: A — Severità: N/A
- Mitigazione: sparisce in Python.
- Owner: dichiarato in A5.
- Cutover gap window (job in errore tra stop Win e start Linux)
- Fase: B6 — Severità: Media
- Mitigazione: finestra a zero traffic + Gitea "paused" preventivo.
- Owner: B6 attività #2.
- Decommissioning prematuro elimina rollback
- Fase: B8 — Severità: Media
- Mitigazione: gating ≥1 settimana + ≥1 mese spento prima di riallocare.
- Owner: B8 attività #3.
- (integrazione A↔B) Shim PS che assume path Windows si rompe in Fase B
- Fase: A2, B6 — Severità: Media
- Mitigazione: shim deve invocare Python via path da env
(
%CI_PYTHON_VENV%), non hardcodedF:\. - Owner: A2 attività #7.
- (integrazione A↔B)
config.tomlcon default Win-only blocca B1- Fase: A1, B1 — Severità: Bassa
- Mitigazione: A1 deve già includere
[paths.linux]con default POSIX. - Owner: A1 attività #4.
- (integrazione A↔B) Test pytest assumono
Path('F:\\CI')e falliscono su Linux- Fase: A1, B1 — Severità: Media
- Mitigazione: fixture parametrizzata
tmp_pathin tutti i test, no path hardcoded. - Owner: A1 attività #11.
- Licenza Workstation Pro Linux cambia post-Broadcom
- Fase: B1 — Severità: Bassa
- Mitigazione: free per uso personale e commerciale dal 2024; monitorare; piano B = KVM (rimandato).
- Owner: B1 monitoring continuo.
7. Definizione di "fatto" globale (A+B)
- Tutti gli script
scripts/*.ps1portati a Python o ridotti a shim (eccettotemplate/*.ps1eInstall-CIToolchain-*.ps1che restano PS by design) Invoke-CIJob.ps1non più referenziato nei workflow YAML (action invoca la CLI Python)pytestverde con coverage ≥70% susrc/ci_orchestrator/ruff check+mypy --strictpuliti su tuttosrc/lint.ymldual-stack (PSSA per PS legacy + ruff/mypy/pytest per Python) PASS- act_runner gira come
act-runner.servicesystemd su Linux Mint - Tutti i template VM (
WinBuild2025,WinBuild2022,LinuxBuild2404) operativi dal nuovo host con snapshot integri - Workflow
build-ns7zip.ymlmatrix Win+Linux PASS dal nuovo host - Burn-in capacity 4 job concorrenti × 10 round PASS, tempi entro ±20% baseline Windows
- Storage migrato a
/var/lib/ci/, host Windows spento come rollback - Tutti i timer systemd attivi e schedulati equivalenti ai Task Scheduler Windows
- Credenziali guest accessibili da
ci-runnerheadless (strategia documentata) README.md,AGENTS.md,docs/ARCHITECTURE.md,docs/HOST-SETUP.md,docs/CI-FLOW.md,docs/RUNBOOK.mdaggiornati allo stato finale- ≥1 settimana di esercizio in produzione su host Linux senza incidenti
- Backup
F:\CI\archiviato e validato - Hook §4 (Protocol
VmBackend, factory backend, naming neutro) presenti nel codice e referenziati indocs/ARCHITECTURE.md
7.1 Auto-check
Domande di auto-verifica a cui il documento risponde "sì":
- Ogni voce della checklist master compare almeno una volta come
- [ ]dentro lo step corrispondente? Sì: voci[A1]…[A5]mappate alle attività in §1,[B1]…[B8]in §2,[X1]…[X4]in §3. - Ogni step
A<n>/B<n>/X<n>ha tutte le sottosezioni richieste? Sì: Obiettivo, Input/dipendenze, Attività, Hook futuri Fase D, Test/validazione, Rollback, Definizione di fatto presenti per ogni step (B*: in più rischi specifici). - I 12 errori frequenti di
AGENTS.mdsono stati indirizzati o dichiarati non applicabili?- #1 (sintassi PS 7+): si applica solo agli shim PS residui in A2/A3 — vincolo PS 5.1 mantenuto
- #2 (
$LASTEXITCODEnon controllato): N/A in Python (subprocess.returncodeesplicito) - #3 (path hardcoded): risolto da A1 attività #4 (env vars +
config.toml) - #4 (mix branch WinRM/SSH): risolto dalla separazione
transport/winrm.pyvstransport/ssh.py - #5 (WinRM verso Linux): impossibile per design — backend selezionato per
guest_os - #6 (seed ISO Linux): preservato nei template, non toccato in A/B
- #7 (
[ordered]@{}PS 2): N/A - #8 (
ForEach-Object -Parallel): N/A in Python (concorrenza viaconcurrent.futuresoasynciose serve) - #9 (snapshot powered-off): coperto da test pytest in A5 + procedura B2 attività #1
- #10 (
vmrun getGuestIPAddress): coperto da test pytest in A1 attività #11 - #11 (machine-id Linux): preservato nel template, citato in B2 e in §6 rischi
- #12 (stderr nativa +
'Stop'): N/A in Python, dichiarato in A5 e §6
- Gli hook §4 sono coerenti con
idea-3-esxi-support.md§3? Sì: ProtocolVmBackendha esattamente i metodiclone_linked,start,stop,delete,get_ipprevisti dalla firmaEsxiBackend; selettore[backend]inconfig.tomlcome da §3 di idea-3.
8. Riferimenti
- AGENTS.md — vincoli host, PowerShell 5.1, errori frequenti #1–#12
- plans/ideas-overview.md — razionale ordine fasi, criteri "fase completata", rollback
- plans/idea-1-python-rewrite.md — Fase A: design, mappa traduzione, step A1–A5, layout repo, rischi
- plans/idea-2-linux-host.md — Fase B: hypervisor, mappa adattamento, step B1–B8, rischi
- plans/idea-3-powershell-removal.md — Fase C: eliminazione
pwshdall'host Linux - plans/idea-3-esxi-support.md — Fase D: §2 architettura, §3 backend pyVmomi (solo per hook)
- docs/ARCHITECTURE.md — layout attuale del sistema
- docs/CI-FLOW.md — flusso job CI corrente
- docs/HOST-SETUP.md — setup host Windows attuale
- docs/RUNBOOK.md — operatività corrente
- scripts/ — inventario PowerShell da portare/preservare
- template/ — script provisioning template (preservati in PS)