chore(plans): archive Phase A and B completed documents

Moves all A/B closeouts, checklists, idea docs, and implementation plans
to plans/archived/2026-05-23/. Both phases are production-stable.

Active plans remaining in plans/:
  - idea-3-powershell-removal.md  (Phase C — in progress)
  - idea-3-esxi-support.md        (Phase D — future)
  - ideas-overview.md             (roadmap reference)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-23 19:52:09 +02:00
parent 222c41dd16
commit 0d6486b19b
17 changed files with 0 additions and 0 deletions
+146
View File
@@ -0,0 +1,146 @@
# Fase A1 — Closeout operativo
Checklist per chiudere lo step **A1** di [implementation-plan-A-B](implementation-plan-A-B.md)
prima di iniziare A2. Il codice è già committato sul branch
`feature/python-rewrite-phase-a` (commit `9fbe952`); manca solo la
validazione contro l'ambiente reale.
## Stato attuale
- [x] Codice + 35 unit test (coverage 82.65%) committati
- [x] `ruff` clean, `mypy --strict` clean su 12 file sorgente
- [x] Job `python` aggiunto a `.gitea/workflows/lint.yml`
- [x] Job `python` di `lint.yml` PASS su act_runner (commit `8586ff8`)
- [ ] PoC `wait-ready` PASS contro VM reale Windows
- [ ] PoC `wait-ready` PASS contro VM reale Linux
## Perché completare A1 prima di A2
A2 riusa `WorkstationVmrunBackend`, `WinRmTransport`, `SshTransport` e
`KeyringCredentialStore` esattamente come sono ora. Se uno di questi ha
un bug di integrazione (TLS, encoding, parsing output `vmrun list`,
permessi venv, ecc.), trovarlo dopo A2 costa: **1 fix nei moduli core +
retest di tutti i 6 comandi portati in A2**. Trovarlo ora costa: 1 fix.
## 1. PoC `wait-ready` su VM Windows reale
**Obiettivo**: validare il path `vmrun.is_running``get_ip` → WinRM
HTTPS con cert self-signed end-to-end.
```powershell
# 1. Setup venv (una tantum) sull'host CI
python -m venv F:\CI\python\venv
F:\CI\python\venv\Scripts\python.exe -m pip install --upgrade pip
F:\CI\python\venv\Scripts\python.exe -m pip install -e ".[dev]"
# 2. Clone manuale di una VM smoke da template
$vmrun = 'C:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe'
$tpl = 'F:\CI\Templates\WinBuild2025\WinBuild2025.vmx'
$dst = 'F:\CI\BuildVMs\smoke-a1\smoke-a1.vmx'
& $vmrun -T ws clone $tpl $dst linked -snapshot=BaseClean -cloneName=smoke-a1
if ($LASTEXITCODE -ne 0) { throw "clone fallito" }
& $vmrun -T ws start $dst nogui
if ($LASTEXITCODE -ne 0) { throw "start fallito" }
# 3. PoC wait-ready
$env:PYTHONIOENCODING = 'utf-8'
F:\CI\python\venv\Scripts\python.exe -m ci_orchestrator wait-ready `
--vmx $dst --guest-os windows --timeout 300
# 4. Cleanup
& $vmrun -T ws stop $dst hard
& $vmrun -T ws deleteVM $dst
```
**Atteso**:
- Exit code `0`
- Ultima riga stdout: `[wait-ready] WinRM ready.`
- Tempo totale tra start VM e WinRM ready: 60180 s
**Failure mode più probabili e fix**:
| Sintomo | Causa probabile | Fix |
| -------------------------------------------------- | ------------------------------------------------------ | -------------------------------------------------------------------------------------------------- |
| `is_running` resta `False` per sempre | Path mismatch in `vmrun list` (slash diversi, casing) | Aggiungere normalizzazione `os.path.normcase` in `WorkstationVmrunBackend.is_running` |
| `get_ip` ritorna sempre `None` | VMware Tools non ancora pronti | Atteso nei primi 3060 s; se persiste >180 s, problema nel template |
| `WinRM probe failed: ... certificate verify` | `cert_validation=False` non basta su TLS handshake | Ispezionare cipher; eventualmente pinnare versione `pypsrp` o passare `auth='ntlm'` esplicito |
| `KeyError: 'BuildVMGuest'` | Credenziale non leggibile dall'utente che lancia il PoC | Eseguire come l'utente del runner (`runas /user:ci-runner ...`) o re-store credential nel suo profilo |
| `KeyringCredentialStore` ritorna username vuoto | Backend keyring usa `get_password`-only | Verificato in `test_credentials.py::test_get_falls_back_to_password`; in tal caso impostare username separatamente nel target |
## 2. PoC `wait-ready` su VM Linux reale
```powershell
$tpl = 'F:\CI\Templates\LinuxBuild2404\LinuxBuild2404.vmx'
$dst = 'F:\CI\BuildVMs\smoke-a1-lnx\smoke-a1-lnx.vmx'
& $vmrun -T ws clone $tpl $dst linked -snapshot=BaseClean-Linux -cloneName=smoke-a1-lnx
& $vmrun -T ws start $dst nogui
$env:CI_SSH_KEY_PATH = 'F:\CI\keys\ci_linux'
F:\CI\python\venv\Scripts\python.exe -m ci_orchestrator wait-ready `
--vmx $dst --guest-os linux --ssh-user ci_build --timeout 300
& $vmrun -T ws stop $dst hard
& $vmrun -T ws deleteVM $dst
```
**Atteso**: ultima riga `[wait-ready] SSH ready.` entro 60120 s.
**Failure mode più probabili**:
| Sintomo | Causa | Fix |
| ------------------------------------ | ---------------------------------------------------- | ---------------------------------------------------------------------------------- |
| `SSH connection ... timed out` | DHCP collision (vedi `AGENTS.md` errore #11) | Verificare snapshot `BaseClean-Linux` con machine-id resettato |
| `paramiko.AuthenticationException` | Permessi chiave SSH errati (Windows ACL su `ci_linux`) | `icacls F:\CI\keys\ci_linux /inheritance:r /grant:r "<utente-runner>:R"` |
| `is_ready()` False ma SSH manuale OK | `BatchMode=yes` lato `ssh.exe` non equivalente in paramiko | Già coperto: paramiko non chiede prompt; verificare `look_for_keys=False` settato |
## 3. Job `python` di `lint.yml` su act_runner
```powershell
# Push del branch
git push -u origin feature/python-rewrite-phase-a
```
Poi su Gitea: aprire una PR contro `main` (o trigger manuale del
workflow). Verificare che il job `python` completi entro
`timeout-minutes: 15`.
**Failure mode più probabili**:
| Sintomo | Causa | Fix |
| ------------------------------------------------------ | -------------------------------------------------------- | ------------------------------------------------------------------------------ |
| `python: command not found` | `python` non in PATH dell'utente del runner | Installare Python 3.11+ machine-wide o aggiornare `PATH` del servizio |
| `pip install -e .[dev]` >15 min al primo run | Cache assente, network lento | Bumpare `timeout-minutes: 30` solo per il primo run; cache stabile dopo |
| `Access denied` su `F:\CI\python\venv` | Utente del runner senza scrittura su `F:\CI\python` | `icacls F:\CI\python /grant "<utente-runner>:(OI)(CI)F"` |
| Coverage <70% | Differenza Python 3.14 (locale) vs 3.11 (CI) | Rilanciare con `--cov-report=term-missing` per identificare branch mancante |
## 4. Definizione di "A1 done"
Spuntabile solo quando **tutti** e tre i punti sopra sono PASS:
- [ ] PoC Windows: `wait-ready` exit 0 contro `WinBuild2025` reale
- [ ] PoC Linux: `wait-ready` exit 0 contro `LinuxBuild2404` reale
- [ ] CI: workflow `lint.yml` job `python` PASS su act_runner
A quel punto:
1. Aggiornare la checklist master in [implementation-plan-A-B](implementation-plan-A-B.md)
spuntando le righe `[A1]`.
2. Mergeare `feature/python-rewrite-phase-a` su `main` (o lasciare il
branch live se A2 verrà committato sopra).
3. Procedere con A2 (script "foglia": `Wait-VMReady`, `Remove-BuildVM`,
`Cleanup-OrphanedBuildVMs`, `Watch-DiskSpace`, `Watch-RunnerHealth`,
`Get-CIJobSummary`).
## 5. Workaround se A1 non si può chiudere ora
Se l'hardware/VM template non sono accessibili in questo momento:
- **Non rimuovere** alcuno script PS in A2.
- Marcare A1 come "code-complete, validation pending" in
[implementation-plan-A-B](implementation-plan-A-B.md).
- Procedere con A2 ma trattare ogni bug scoperto su `WorkstationVmrunBackend`
/ `WinRmTransport` / `SshTransport` come **fix di A1** (commit separato,
no nuove feature A2 nello stesso commit).
- Riservare una sessione dedicata di smoke testing prima di A3 (pipeline
di build), perché A3 introduce `clone_linked` + `start` reali.
+36
View File
@@ -0,0 +1,36 @@
# A2 — Closeout
Fase A2 di `plans/implementation-plan-A-B.md`: porting in Python degli script
"foglia" (no state condiviso). Branch: `feature/python-rewrite-phase-a`.
## Stato attuale
- [x] `commands/wait.py``wait-ready` (sostituisce `Wait-VMReady.ps1`)
- [x] `commands/vm.py``vm remove` + `vm cleanup` (sostituisce `Remove-BuildVM.ps1`, `Cleanup-OrphanedBuildVMs.ps1`)
- [x] `commands/monitor.py``monitor disk` + `monitor runner` (sostituisce `Watch-DiskSpace.ps1`, `Watch-RunnerHealth.ps1`)
- [x] `commands/report.py``report job` (sostituisce `Get-CIJobSummary.ps1`)
- [x] Tutti i 6 `.ps1` ridotti a shim a 3 righe verso la CLI Python (preservano `$LASTEXITCODE`)
- [x] Test pytest per ogni nuovo modulo (`test_commands_*.py`), 69 test totali, ruff/mypy --strict clean, coverage 74.5%
- [x] Hook Fase C: `vm cleanup` accetta `VmBackend` iniettato (no assunzione filesystem locale)
- [ ] Conversione esplicita Pester → pytest dei file `tests/Wait-VMReady.Tests.ps1` e `tests/Remove-BuildVM.Tests.ps1` (rimossi dal repo)
- [ ] Validazione manuale: scheduled task registrati da `Register-CIScheduledTasks.ps1` continuano a funzionare via shim
- [ ] Smoke end-to-end manuale: clone VM → `wait-ready``vm remove`
## Voce per voce — Definizione di "fatto" A2
| Criterio | Stato | Note |
| --- | --- | --- |
| Tutti gli script "foglia" hanno shim PS che chiama Python | ✅ | 6/6 |
| Test pytest sostituiscono i Pester corrispondenti | ⚠️ parziale | I Pester legacy sono ancora presenti come safety net; equivalenza funzionale coperta dai nuovi test pytest. Rimozione esplicita rinviata insieme ad A3 (che ri-tocca anche `New-BuildVM.Tests.ps1`) |
| Scheduled task continuano a funzionare via shim | ⏳ da validare | Richiede esecuzione manuale sull'host CI |
| Nessuna regressione su `self-test.yml` | ⏳ da validare | Workflow `lint.yml` PASS; `self-test.yml` non ri-eseguito post-A2 |
## Cosa resta a carico utente (validazione hardware/runtime)
1. Eseguire manualmente uno dei task in `Register-CIScheduledTasks.ps1` (es. `Watch-DiskSpace`) e verificare che lo shim invochi correttamente la CLI Python e produca output equivalente al `.ps1` originale.
2. Smoke end-to-end: clone manuale di una VM template, `python -m ci_orchestrator wait-ready --vmx ...`, `python -m ci_orchestrator vm remove --vmx ...`.
3. Decidere se rimuovere i Pester `tests/Wait-VMReady.Tests.ps1` / `tests/Remove-BuildVM.Tests.ps1` ora o aspettare A3 (che li ri-cita).
## Riferimenti commit
- `794db1a` — feat(a2): port leaf PS scripts to ci_orchestrator CLI
+31
View File
@@ -0,0 +1,31 @@
# A3 — Closeout
Fase A3 di `plans/implementation-plan-A-B.md`: pipeline core (clone → build → collect) in Python.
Branch: `feature/python-rewrite-phase-a`.
## Stato attuale
- [x] `commands/vm.py` esteso con `vm new` (sostituisce `New-BuildVM.ps1`)
- [x] `commands/build.py` con `build run` (sostituisce `Invoke-RemoteBuild.ps1`)
- [x] `commands/artifacts.py` con `artifacts collect` (sostituisce `Get-BuildArtifacts.ps1`)
- [x] Tutti i 3 `.ps1` ridotti a shim verso la CLI Python
- [x] Pester `tests/{New-BuildVM,Wait-VMReady,Remove-BuildVM}.Tests.ps1` rimossi (casi negativi coperti dai pytest equivalenti)
- [x] 91 pytest, ruff/mypy --strict clean, coverage 78.27%
- [x] Hook Fase C: `build run` + `artifacts collect` accettano `VmHandle`/`vmx` opaco; nessuna assunzione path Windows
- [ ] Smoke end-to-end manuale: clone WinBuild2025 → build script trivial → collect artifact ZIP
- [ ] Smoke end-to-end Linux equivalente
## Definizione di "fatto" A3
| Criterio | Stato |
| --- | --- |
| Pipeline build completa via Python CLI | ✅ |
| Shim PS preservano API per caller esistenti | ✅ |
| Smoke build PASS contro VM reale Windows e Linux | ⏳ validazione hardware |
| Pester `New-BuildVM.Tests.ps1` rimosso e sostituito | ✅ |
## Cosa resta a carico utente
1. Smoke manuale end-to-end Windows: `python -m ci_orchestrator vm new ... && build run ... && artifacts collect ...`
2. Stesso smoke su Linux template
3. Verificare che `Test-NsinnounpBuild.ps1` continui a passare invocando gli shim
+48
View File
@@ -0,0 +1,48 @@
# A4 — Closeout
Fase A4 di `plans/implementation-plan-A-B.md`: orchestratore di job end-to-end in Python e switch del workflow Gitea sulla CLI.
Branch: `feature/python-rewrite-phase-a`.
## Stato attuale
- [x] `commands/job.py` implementato (entry point completo: clone → start → wait → probe → build → collect → cleanup garantito in `try/except/else/finally`)
- [x] Selezione backend via `backends.load_backend(config)` — nessun import diretto di `WorkstationVmrunBackend` nel comando (hook Fase C)
- [x] Auto-detect guest OS dal VMX (`guestOS = "ubuntu-*"` → Linux, altrimenti Windows)
- [x] Override CPU/RAM (`--guest-cpu`, `--guest-memory-mb`) via patch idempotente del clone VMX
- [x] Iniezione env extra (`--extra-env-json`) con validazione regex chiavi
- [x] `__main__.py` registra il sottocomando `job`
- [x] `scripts/Invoke-CIJob.ps1` ridotto a shim PS 5.1 (delega a `python -m ci_orchestrator job`, traduce flag PascalCase → kebab-case)
- [x] `gitea/actions/local-ci-build/action.yml` invoca direttamente `& $venvPython -m ci_orchestrator job ...` (più nessuno `@params` splatting verso lo shim PS)
- [x] `runner/config.yaml` esporta `PYTHONIOENCODING=utf-8` per garantire stdio UTF-8 nei log act_runner → Gitea
- [x] `tests/python/test_commands_job.py` aggiunto: 13 test (happy path Linux, happy path Windows + override, skip-artifact, cleanup su build failure, cleanup su clone failure, validazione template/extra-env, helper `_apply_vmx_overrides` / `_read_guest_os_from_vmx` / `_parse_extra_env_json`)
- [x] Suite completa: ruff clean, mypy --strict clean, **104 pytest PASS**, coverage totale **79.25%**, coverage `commands/job.py` **83%**
- [ ] Workflow `build-ns7zip.yml` matrix (Win + Linux) end-to-end PASS contro VM reali — richiede host con runner registrato
- [ ] Workflow `self-test.yml` / `lint.yml` PASS sul nuovo entry point — stessa dipendenza
- [ ] Misurazione tempo job entro ±10% del baseline PowerShell (`Measure-CIBenchmark.ps1`)
## Definizione di "fatto" A4
| Criterio | Stato |
| --------------------------------------------------- | ------------------------------------ |
| `action.yml` chiama Python direttamente | ✅ |
| Shim `Invoke-CIJob.ps1` preservato per uso manuale | ✅ |
| `runner/config.yaml` ha `PYTHONIOENCODING=utf-8` | ✅ |
| Coverage `commands/job.py` ≥80% | ✅ 83% |
| Cleanup VM garantito su tutti i path di errore | ✅ (test `test_job_cleanup_on_*`) |
| Hook Fase C (factory backend, no import workstation diretto) | ✅ |
| Workflow `build-ns7zip.yml` matrix PASS | ⏳ validazione hardware utente |
| Tempo entro ±10% baseline | ⏳ misurazione utente |
## Cosa resta a carico utente
1. Eseguire `build-ns7zip.yml` (matrix Win + Linux) sul runner host registrato e verificare che il job concluda con artifact pubblicato — il flusso deve passare dal nuovo entry point Python.
2. Lanciare `self-test.yml` e `lint.yml` per validare che gli altri workflow restino verdi dopo lo switch.
3. Eseguire `scripts/Measure-CIBenchmark.ps1` (resta in PS) prima/dopo lo switch e confermare delta tempo entro ±10%.
4. Aggiornare `Measure-CIBenchmark.ps1` se la wall-clock del nuovo entry point la rende necessaria; in caso di regressione >10%, aprire issue in `TODO.md` per profiling A5.
5. Coverage `commands/job.py` portata a ≥80% in A5 (cleanup test e branch monitor/timeout aggiuntivi).
## Riferimenti commit
- `feat(a4): port Invoke-CIJob.ps1 to ci_orchestrator job``commands/job.py`, `__main__.py`, shim `scripts/Invoke-CIJob.ps1`, `tests/python/test_commands_job.py`
- `ci(a4): switch local-ci-build action.yml to Python CLI; add PYTHONIOENCODING=utf-8``gitea/actions/local-ci-build/action.yml`, `runner/config.yaml`
- `docs(a4): mark A4 as code-complete; add A4-closeout.md``plans/implementation-plan-A-B.md`, `plans/A4-closeout.md`
+100
View File
@@ -0,0 +1,100 @@
# A5 — Closeout
Fase A5 di `plans/implementation-plan-A-B.md`: polish, regression test
per gli errori frequenti `AGENTS.md` #9-#12, documentazione Python
allineata, gate coverage alzato a 80%.
Branch: `feature/python-rewrite-phase-a`.
## Stato attuale
- [x] `tests/python/test_agents_errors.py` aggiunto: 12 test dedicati che
coprono regressioni per `AGENTS.md` "Errori frequenti da evitare"
#9, #10, #11, #12.
- **#9** (snapshot template con VM accesa): `clone_linked` propaga
"should not be powered on" come `BackendOperationFailed` (subclass
di `BackendError`) con `operation == "clone"` e `output` che
contiene il messaggio originale di `vmrun`.
- **#10** (`vmrun getGuestIPAddress` lento): `is_running` usa solo
`vmrun list`. Tre asserzioni — happy path, VM non listata, errore
di `vmrun list` — verificano che `getGuestIPAddress` non venga MAI
chiamato come fallback.
- **#11** (machine-id duplicato → DHCP collision): la safety net
orchestrator-side è che `vm new` non riusi mai un clone-name
(timestamp suffix); il test invoca `vm new` due volte con lo stesso
`--job-id` e verifica che `clone_linked` riceva due
`name`/`destination` distinti. Il fix template-side
(`truncate -s 0 /etc/machine-id`) resta documentato in
`template/Prepare-LinuxBuild2404.ps1`.
- **#12** (`& nativecmd 2>$null` non sopprime stderr in PS 5.1):
`SshTransport` usa `paramiko.AutoAddPolicy` di default con
`known_hosts=None`, non chiama mai `subprocess.run`/`Popen`
(incluso `ssh-keygen`/`ssh.exe`/`scp.exe`), e wrappa errori
paramiko (import mancante, `exec_command` exception, SFTP
`put`/`get`) come `TransportConnectError` invece di propagare
eccezioni nude.
- [x] `AGENTS.md` aggiornato: nuova sezione "Python development" subito
dopo "PowerShell 5.1 — Vincoli critici", con tabella parametri,
convenzioni (type hints, no `Any`/globali/`print`/`shell=True`),
mappatura completa PS → Python sub-command, elenco PS che restano
e gate coverage 80%.
- [x] `docs/ARCHITECTURE.md` esteso con sezione "Python orchestrator
(Phase A)": layout package, `VmBackend` Protocol, factory
`load_backend(config)`, ricetta Phase C per backend ESXi
(`backends/esxi.py` + selettore `[backend]` in `config.toml`),
transport selection, gate CI.
- [x] `README.md` ampliato: setup venv produzione + dev, esempi CLI per
tutti i 10 sub-comandi (`wait-ready`, `vm new/remove/cleanup`,
`build run`, `artifacts collect`, `monitor disk/runner`,
`report job`, `job`), comandi validazione locale con gate 80%,
link a `test_agents_errors.py` e `pyproject.toml`.
- [x] Suite completa: ruff clean, mypy --strict clean, **116 pytest
PASS**, coverage totale **80.10%** (gate 80% raggiunto).
- [ ] Burn-in 4 job concorrenti × 10 round (versione Python di
`Test-CapacityBurnIn`) — **richiede VM reali, lasciato a carico
utente**.
- [ ] Workflow `build-ns7zip.yml` matrix (Win + Linux) end-to-end
PASS — eredita pendenza da A4 (richiede runner registrato).
## Definizione di "fatto" A5
| Criterio | Stato |
| ----------------------------------------------------- | -------------------------------- |
| Errori #9#12 coperti da test pytest | ✅ |
| `AGENTS.md` ha sezione "Python development" | ✅ |
| `docs/ARCHITECTURE.md` aggiornato (layout + hook C) | ✅ |
| `README.md` aggiornato (setup + esempi CLI) | ✅ |
| Coverage globale ≥80% (gate alzato in pyproject) | ✅ 80.10% |
| Shim PS non referenziati rimossi | ⏭️ nessuno candidato (vedi nota) |
| Burn-in 4×10 PASS | ⏳ utente — richiede VM reali |
**Nota shim PS**: tutti i 10 shim (`Wait-VMReady.ps1`, `New-BuildVM.ps1`,
`Remove-BuildVM.ps1`, `Cleanup-OrphanedBuildVMs.ps1`,
`Invoke-RemoteBuild.ps1`, `Get-BuildArtifacts.ps1`, `Watch-DiskSpace.ps1`,
`Watch-RunnerHealth.ps1`, `Get-CIJobSummary.ps1`, `Invoke-CIJob.ps1`)
sono ancora referenziati: `Register-CIScheduledTasks.ps1` registra
scheduled task che invocano `Watch-DiskSpace.ps1`,
`Watch-RunnerHealth.ps1`, `Cleanup-OrphanedBuildVMs.ps1`; `README.md`
documenta `Invoke-CIJob.ps1` per uso manuale; `Test-*.ps1` di smoke
chiamano gli shim. Nessuno shim è candidato a rimozione in A5 senza
prima migrare anche `Register-CIScheduledTasks.ps1` (Fase B5 lo
sostituirà con `*.service` + `*.timer` su systemd).
## Cosa resta a carico utente
1. Eseguire un burn-in concorrente (4 job × 10 round) usando il workflow
`build-ns7zip.yml` matrix o `scripts/Test-CapacityBurnIn.ps1` (che
ora delega via shim alla CLI Python `job`) e verificare:
- PASS senza VM orfane (`vm cleanup` non rimuove nulla a fine round).
- Wall-clock entro ±20% del baseline pre-Python (`Measure-CIBenchmark.ps1`).
2. Validare `build-ns7zip.yml` matrix (Win + Linux) end-to-end PASS
sul runner reale — pendenza ereditata da A4.
3. Validare `self-test.yml` e `lint.yml` post-switch.
## Riferimenti commit
- `test(a5): add pytest regression tests for AGENTS.md errors #9-#12`
`tests/python/test_agents_errors.py`
- `docs(a5): document Python orchestrator in AGENTS.md, ARCHITECTURE.md, README.md`
`AGENTS.md`, `docs/ARCHITECTURE.md`, `README.md`
- `docs(a5): mark A5 as code-complete; add A5-closeout.md`
`plans/implementation-plan-A-B.md`, `plans/A5-closeout.md`
+40
View File
@@ -0,0 +1,40 @@
# B1 — Closeout
Passo B1 di `plans/PhaseB-user-checklist.md`: setup hardware target con
Linux Mint LTS, VMware Workstation Pro Linux, layout storage e venv Python.
## Stato
**COMPLETATO** — tutti i prerequisiti hardware e software soddisfatti.
## Cosa è stato fatto
- [x] Linux Mint LTS installato sull'hardware target (`ws1-mint`).
- [x] Sistema aggiornato (`apt full-upgrade`) e riavviato.
- [x] VMware Workstation Pro Linux installato via bundle `.bundle`; `vmrun -T ws --version` OK.
- [x] Smoke test Workstation UI: VM trivia creata, clonata, avviata, spenta, eliminata.
- [x] `vmnet8` NAT configurato su `192.168.79.0/24` (stesso range di Windows) via `vmware-netcfg`.
- [x] Utente di servizio `ci-runner` creato (`useradd -r -m -s /bin/bash`).
- [x] `setup-host-linux.sh -d /dev/sdf1` eseguito:
- `/dev/sdf1` montata su `/var/lib/ci` via fstab con boot automatico.
- Layout directory creato: `build-vms/`, `artifacts/`, `templates/`, `keys/`, `logs/`, `runner/`.
- Python 3.12 installato, `/opt/ci/venv` creato.
- Repo clonato in `/opt/ci/local-ci-cd-system`, package installato in modalità editabile.
- PowerShell Core (`pwsh`) installato (necessario per B5 nel piano originale;
sarà rimosso nella Fase C).
- [x] Verifica finale: `sudo -u ci-runner /opt/ci/venv/bin/python -m ci_orchestrator --help`
lista tutti i sub-comandi.
## Deviazioni / gotcha
- Lo script `setup-host-linux.sh` è stato scritto e committato durante questa
fase (commit `b9d6994`) — non era pre-esistente nel repo.
- La partizione `/dev/sdf1` conteneva una sottocartella `ci/` residua dalla
configurazione Windows; lo script la ha rilevata e spostata alla root prima
del mount definitivo.
- Python 3.12 (non 3.11 come da piano) installato perché è la versione LTS
disponibile su Linux Mint LTS — il package è compatibile.
## Riferimenti commit
- `b9d6994` — feat: add setup-host-linux.sh script for CI host bootstrap
+46
View File
@@ -0,0 +1,46 @@
# B2 — Closeout
Passo B2 di `plans/PhaseB-user-checklist.md`: trasferimento template VM da
`F:\CI\Templates\` a `/var/lib/ci/templates/` con integrità snapshot.
## Stato
**COMPLETATO** — 3 template trasferiti, snapshot validati, smoke test PASS.
## Cosa è stato fatto
- [x] Verificato assenza di `*.vmem`/`*.vmsn` di runtime sui template Windows
prima della copia (VM spente).
- [x] Trasferimento via `rsync` SSH da Windows → Linux per tutti e 3 i template:
- `WinBuild2025` (snapshot `BaseClean`)
- `WinBuild2022` (snapshot `BaseClean`)
- `LinuxBuild2404` (snapshot `BaseClean-Linux`)
- [x] Snapshot validati con `vmrun -T ws listSnapshots` su ciascun `.vmx`.
- [x] Ogni `.vmx` aperto in Workstation Linux GUI; risposto "**I copied it**"
al prompt di registrazione.
- [x] Keyring file-based configurato anticipatamente per `ci-runner`
(prerequisito del smoke test Windows; il setup completo è in B3):
- `keyrings.alt.file.PlaintextKeyring` scelto al posto di `secret-tool`
(vedi Deviazioni).
- Credenziale `BuildVMGuest` salvata con `tools/ci-cred-set.py`.
- [x] Smoke test `vm new` + `wait-ready` + `vm remove` PASS su:
- `WinBuild2025` (WinRM HTTPS/5986)
- `LinuxBuild2404` (SSH/22)
## Deviazioni / gotcha
- **`secret-tool` scartato**: il piano originale usava `secret-tool` (GNOME
Secret Service) per le credenziali headless. Non funziona senza una D-Bus
session attiva, che non è disponibile quando il processo gira come servizio
systemd (`act-runner.service`). Soluzione adottata: `keyrings.alt` con
`PlaintextKeyring` (file `~/.local/share/python_keyring/keyring_pass.cfg`),
che non dipende da D-Bus e funziona correttamente in contesto headless.
Questa scelta è documentata in B3-closeout.md.
- **AGENTS.md errore #9 (machine-id)**: lo snapshot `BaseClean-Linux` era già
stato preso con `machine-id` troncato — nessuna collisione DHCP rilevata
durante lo smoke test.
## Riferimenti commit
- `3921758` — feat: add credential management scripts (`tools/ci-cred-set.py`,
`tools/ci-cred-check.py`)
+52
View File
@@ -0,0 +1,52 @@
# B3 — Closeout
Passo B3 di `plans/PhaseB-user-checklist.md`: trasferimento credenziali
e chiavi SSH al user `ci-runner` headless.
## Stato
**COMPLETATO** — chiavi SSH, credenziale guest Windows e GiteaPAT
disponibili a `ci-runner`; PoC headless verificato.
## Cosa è stato fatto
- [x] Chiavi SSH guest Linux già presenti in `/var/lib/ci/keys/` (copiate
con i template in B2). Permessi corretti:
- `ci_linux`: `600` (owner `ci-runner`)
- `ci_linux.pub`: `644` (owner `ci-runner`)
- [x] Credenziale guest Windows `BuildVMGuest` già salvata come
prerequisito del Passo 2 con `tools/ci-cred-set.py`.
- Backend: `keyrings.alt.file.PlaintextKeyring`
(`~/.local/share/python_keyring/keyring_pass.cfg`)
- Verificata con `tools/ci-cred-check.py BuildVMGuest`
`OK (file) username='WINBUILD-2025\ci_build'`
- [x] Gitea PAT salvato:
```bash
sudo -u ci-runner /opt/ci/venv/bin/python \
/opt/ci/local-ci-cd-system/tools/ci-cred-set.py GiteaPAT ci-runner-linux
```
Verificato con `ci-cred-check.py GiteaPAT` → `OK (file)`.
- [x] PoC headless confermato: il backend `PlaintextKeyring` non dipende
da D-Bus e funziona sia da `sudo -u ci-runner` che dall'interno di
`act-runner.service` (verificato durante smoke test B2 e B4).
## Deviazioni / gotcha
- **`secret-tool` scartato**: il piano originale usava `secret-tool`
(GNOME Secret Service). Non funziona senza una D-Bus session attiva,
che non è disponibile in contesti systemd headless. Alternativa adottata:
`keyrings.alt.file.PlaintextKeyring` — nessuna dipendenza da D-Bus,
file in chiaro in `~/.local/share/python_keyring/keyring_pass.cfg`
(accessibile solo a `ci-runner`). Configurazione in
`~/.local/share/python_keyring/keyringrc.cfg`.
- **Credenziale B2**: il prerequisito del Passo 2 anticipava la parte
minima di B3 (solo `BuildVMGuest`) per sbloccare lo smoke test Windows.
In B3 si è completato aggiungendo il GiteaPAT e fissando i permessi
chiavi SSH.
## Riferimenti commit
- `3921758` — feat: add credential management scripts (`tools/ci-cred-set.py`,
`tools/ci-cred-check.py`)
+65
View File
@@ -0,0 +1,65 @@
# B4 — Closeout
Passo B4 di `plans/PhaseB-user-checklist.md`: act_runner Linux
registrato verso Gitea e gestito da systemd.
## Stato
**COMPLETATO**`act-runner.service` attivo, runner registrato,
`config.yaml` corretto, smoke job PASS.
## Cosa è stato fatto
- [x] Binario act_runner Linux v1.0.4 scaricato in `/opt/ci/act_runner/`
con permessi `ci-runner`.
- [x] Runner registrato verso `https://gitea.emulab.it` con nome
`ci-linux` e label `windows-build:host`, `linux-build:host`.
- [x] `/etc/systemd/system/act-runner.service` creato con:
- `User=ci-runner`, `WorkingDirectory=/var/lib/ci/runner`
- Variabili d'ambiente CI (`CI_ROOT`, `CI_TEMPLATES`, `CI_BUILD_VMS`,
`CI_ARTIFACTS`, `CI_KEYS`, `PYTHONIOENCODING`)
- `ExecStart=/opt/ci/act_runner/act_runner daemon --config /var/lib/ci/runner/config.yaml`
- `Restart=on-failure`, `RestartSec=10`
- [x] `/etc/ci/environment` creato con le stesse variabili CI (usato dai
timer systemd di B5).
- [x] `config.yaml` generato con `generate-config` e poi patchato via
`sed` per:
- Rimuovere i label `ubuntu-*:docker://` generati di default e
impostare `windows-build:host` + `linux-build:host`
- Sostituire le variabili placeholder `A_TEST_ENV_NAME_*` con:
- `CI_PYTHON_LAUNCHER: /opt/ci/venv/bin/python`
- `GITEA_CI_TEMPLATE_PATH: /var/lib/ci/templates/WinBuild2025/WinBuild2025.vmx`
- `GITEA_CI_LINUX_TEMPLATE_PATH: /var/lib/ci/templates/LinuxBuild2404/LinuxBuild2404.vmx`
- [x] `act-runner.service` abilitato e avviato; log confermano connessione
Gitea senza errori Docker.
- [x] Smoke job `self-test.yml` triggerato manualmente → PASS dal runner
Linux.
## Deviazioni / gotcha
- **Nome servizio `act-runner` (non `act_runner`)**: il binario Linux
si registra come `act-runner.service` (trattino), mentre su Windows
il servizio si chiama `act_runner` (underscore). Questa differenza ha
causato un bug nel comando `monitor runner`, che usava `act_runner` come
default — corretto aggiungendo `--service-name act-runner` nell'`ExecStart`
di `ci-watch-runner-health.service`.
- **`generate-config` scrive label Docker**: `act_runner generate-config`
produce label `ubuntu-latest:docker://...` che fanno cercare Docker (non
installato). È necessario patchare il file con `sed` dopo la generazione.
Le istruzioni sono nel Passo 4 del checklist B.
- **`GITEA_CI_TEMPLATE_PATH` nel service unit prima dello smoke test**:
se le variabili template non sono presenti in `config.yaml` o nel service
unit, il job fallisce con `"Got unexpected extra argument"` (i path non
vengono passati al CLI Python). Prerequisito obbligatorio prima di
trigggerare lo smoke job.
- **`config.yaml` separato da `.runner`**: il file `.runner` contiene il
token di registrazione (non toccare). `config.yaml` contiene solo la
configurazione operativa del runner.
## Riferimenti commit
- `536fd68` — feat: update runner configuration steps and add config.yaml
generation instructions
- `a6aa926` — fix: improve Python interpreter resolution for OS compatibility
- `b2803c3` — fix: correct order of arguments for template path in local
CI build action
+78
View File
@@ -0,0 +1,78 @@
# B5 — Closeout
Passo B5 di `plans/PhaseB-user-checklist.md`: conversione scheduled task
Windows in coppie `*.service` + `*.timer` systemd su Linux.
## Stato
**COMPLETATO** — 5 timer attivi, tutti e 5 i service PASS al trigger
manuale.
## Cosa è stato fatto
- [x] Inventario dei 4 task in `scripts/Register-CIScheduledTasks.ps1`
(cadenze + comandi) + aggiunta `ci-backup-template` (non era in PS).
- [x] `Invoke-RetentionPolicy.ps1` portato in Python come sub-comando
`retention run` (`src/ci_orchestrator/commands/retention.py`).
- [x] `Backup-CITemplate.ps1` portato in Python come sub-comando
`template backup` (`src/ci_orchestrator/commands/template.py`) con
compressione `7z a -mx=1 -mmt=on` → archivi `.7z` in
`/var/lib/ci/backups/`.
- [x] 5 coppie `*.service` + `*.timer` deployate in
`/etc/systemd/system/`:
| Unit | Cadenza | Comando |
| --------------------------- | ---------------------------------------- | ------------------------------------------------------- |
| `ci-cleanup-orphans` | ogni 6h + al boot | `ci_orchestrator vm cleanup --max-age-hours 6` |
| `ci-retention-policy` | daily 03:00 + 30min jitter | `ci_orchestrator retention run` |
| `ci-watch-disk-space` | ogni 15min | `ci_orchestrator monitor disk` |
| `ci-watch-runner-health` | ogni 15min | `ci_orchestrator monitor runner --service-name act-runner` |
| `ci-backup-template` | domenica 02:00 + 1h jitter | `ci_orchestrator template backup --all-templates` |
- [x] Prerequisiti creati sull'host prima del deploy:
```bash
sudo mkdir -p /var/log/ci /var/lib/ci/backups
sudo chown ci-runner:ci-runner /var/log/ci
```
- [x] `systemctl list-timers --all 'ci-*'` → 5 timer `active`. ✅ PASS
- [x] Trigger manuale di tutti e 5 i service → ✅ tutti PASS.
- [x] `deploy/systemd/README.md` aggiornato: tabella mapping, prerequisiti,
istruzioni install/test/rollback — senza riferimenti a PowerShell.
## Deviazioni / gotcha
- **pwsh non richiesto**: `Invoke-RetentionPolicy.ps1` e
`Backup-CITemplate.ps1` sono stati portati integralmente a Python in
questa fase, eliminando la dipendenza da PowerShell Core per tutti e 5
i timer. Questo è il primo passo verso la Fase C
(`plans/idea-3-powershell-removal.md`).
- **`--service-name act-runner`**: il default del comando `monitor runner`
era `act_runner` (nome Windows). Il service Linux si chiama `act-runner`
(trattino). Il fix è nel parametro `ExecStart` di
`ci-watch-runner-health.service`.
- **`ci-backup-template` gira come root**: il backup richiede
`systemctl stop/start act-runner.service` (gestito con try/finally in
Python). Gli altri 4 service girano come `ci-runner`.
- **Prerequisiti directory**: `ReadWritePaths` in `ProtectSystem=strict`
richiede che le directory esistano *prima* che la unit venga avviata.
`ci-watch-runner-health` e `ci-cleanup-orphans` usano `/var/log/ci`;
`ci-backup-template` usa `/var/lib/ci/backups`. Entrambe vanno create
prima del deploy (vedi sopra).
- **Bug pre-esistente in `vm.py`** (`_list_orphans`): `is_dir()` era
chiamato fuori dal blocco `try/except OSError`, causando un'eccezione
su symlink rotti. Corretto spostando `is_dir()` all'interno del try.
## Cadenze systemd (vs Windows originale)
| Task | Windows originale | systemd timer |
| --------------------- | -------------------------- | ----------------------------------------------------- |
| cleanup-orphans | ogni 6h + al boot | `OnBootSec=2min, OnUnitActiveSec=6h, Persistent` |
| retention-policy | daily 03:00 + 30min delay | `OnCalendar=*-*-* 03:00:00, RandomizedDelaySec=30min` |
| watch-disk-space | ogni 15min | `OnBootSec=5min, OnUnitActiveSec=15min` |
| watch-runner-health | ogni 15min | `OnBootSec=3min, OnUnitActiveSec=15min` |
| backup-template | *(non era in PS)* | `OnCalendar=Sun *-*-* 02:00:00, RandomizedDelaySec=1h`|
## Riferimenti commit
- `43a69b8` — feat: B5 — port retention/template-backup to Python, deploy
systemd timers
@@ -0,0 +1,349 @@
# Fase A — Checklist finale per l'utente
La Fase A è **code-complete e pushata** sul branch `feature/python-rewrite-and-linux-migration`.
Restano solo le validazioni che richiedono accesso fisico all'host CI con VMware
Workstation, snapshot template attivi e act_runner registrato.
Questo documento elenca i passi da eseguire in ordine, con checklist e comandi
copiabili. Stop a primo errore: ogni passo è un gate per il successivo.
> **Tempo stimato totale**: 12 ore (escluse build lunghe nel passo 5).
---
## Passo 1 — Aggiornare il venv di produzione sull'host CI
Il codice nuovo va installato nel venv che act_runner usa
(`F:\CI\python\venv\`).
- [x] Aprire PowerShell **come amministratore** sull'host CI.
- [x] Posizionarsi nella copia git aggiornata del repo (pull del branch
`feature/python-rewrite-and-linux-migration`):
```powershell
cd <path-locale-repo>
git fetch origin
git checkout feature/python-rewrite-and-linux-migration
git pull
```
- [x] Installare il package nel venv di produzione (**non editable**):
```powershell
& 'F:\CI\python\venv\Scripts\python.exe' -m pip install .
```
> ⚠️ **Non usare `pip install -e .` su questo host.** act_runner gira
> come `LocalSystem`. Un install editable scrive un `.pth` verso la
> sorgente; se il `cd` era in una dir di lavoro di act_runner
> (`F:\CI\RunnerWork\<hash>\hostexecutor`, transitoria) o sul drive
> utente `N:` (non risolto da LocalSystem), il runner fallisce con
> `No module named ci_orchestrator`. L'install regolare copia il
> package in `site-packages` su `F:` (accessibile a LocalSystem).
> Conseguenza: ogni modifica al codice richiede di ri-eseguire
> `pip install .` — è uno step di deploy. Assicurarsi che il `cd` del
> passo precedente punti alla copia git reale del repo, non a una dir
> sotto `F:\CI\RunnerWork`.
- [x] Smoke check rapido che la CLI risponda:
```powershell
& 'F:\CI\python\venv\Scripts\python.exe' -m ci_orchestrator --help
```
Atteso: lista degli 11 sub-comandi (`wait-ready`, `vm`, `build`,
`artifacts`, `monitor`, `report`, `job`).
---
## Passo 2 — Riavviare act_runner
L'act_runner gira come servizio SYSTEM e legge `runner/config.yaml`
all'avvio. La modifica di A4 (`PYTHONIOENCODING=utf-8`) ha effetto solo
dopo restart.
- [x] Trovare il servizio:
```powershell
Get-Service | Where-Object { $_.Name -like '*act*runner*' }
```
- [x] Restart (sostituisci `<NomeServizio>` con quello trovato):
```powershell
Restart-Service -Name '<NomeServizio>'
```
- [x] Verificare che sia partito:
```powershell
Get-Service -Name '<NomeServizio>'
```
Atteso: `Status = Running`.
---
## Passo 3 — Smoke `wait-ready` su VM Windows reale
PoC pendente da A1.
- [x] Clonare manualmente un template Windows:
```powershell
& 'C:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe' `
-T ws clone `
'F:\CI\Templates\WinBuild2025\WinBuild2025.vmx' `
'F:\CI\BuildVMs\smoke-win\smoke-win.vmx' `
linked -snapshot=BaseClean -cloneName=smoke-win
```
- [x] Avviare la VM:
```powershell
& 'C:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe' `
-T ws start 'F:\CI\BuildVMs\smoke-win\smoke-win.vmx' nogui
```
- [x] Eseguire `wait-ready`:
```powershell
& 'F:\CI\python\venv\Scripts\python.exe' -m ci_orchestrator wait-ready `
--vmx 'F:\CI\BuildVMs\smoke-win\smoke-win.vmx' `
--guest-os windows --timeout 180
```
Atteso: exit code `0` entro 3 minuti.
- [x] Cleanup VM di smoke:
```powershell
& 'F:\CI\python\venv\Scripts\python.exe' -m ci_orchestrator vm remove `
--vmx 'F:\CI\BuildVMs\smoke-win\smoke-win.vmx' --force
```
---
## Passo 4 — Smoke `wait-ready` su VM Linux reale
Stesso schema, template Linux. PoC pendente da A1.
- [x] Clonare:
```powershell
& 'C:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe' `
-T ws clone `
'F:\CI\Templates\LinuxBuild2404\LinuxBuild2404.vmx' `
'F:\CI\BuildVMs\smoke-linux\smoke-linux.vmx' `
linked -snapshot=BaseClean-Linux -cloneName=smoke-linux
```
- [x] Start + wait-ready:
```powershell
& 'C:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe' `
-T ws start 'F:\CI\BuildVMs\smoke-linux\smoke-linux.vmx' nogui
& 'F:\CI\python\venv\Scripts\python.exe' -m ci_orchestrator wait-ready `
--vmx 'F:\CI\BuildVMs\smoke-linux\smoke-linux.vmx' `
--guest-os linux --timeout 180
```
Atteso: exit code `0`.
> **Prerequisito scoperto**: `F:\CI\config.toml` deve esistere con `ssh_key_path`
> impostato — altrimenti SSH fallisce con `No authentication methods available`.
> Creato durante questa validazione; vedi HOST-SETUP.md step 6.
- [x] Cleanup:
```powershell
& 'F:\CI\python\venv\Scripts\python.exe' -m ci_orchestrator vm remove `
--vmx 'F:\CI\BuildVMs\smoke-linux\smoke-linux.vmx' --force
```
---
## Passo 5 — Smoke end-to-end pipeline (`job`)
Valida A3+A4 insieme: clone → wait → build → collect → cleanup.
- [x] Lanciare un job sul template Windows con uno script trivial:
```powershell
& 'F:\CI\python\venv\Scripts\python.exe' -m ci_orchestrator job `
--template-path 'F:\CI\Templates\WinBuild2025\WinBuild2025.vmx' `
--snapshot-name BaseClean `
--job-id smoke-job-win `
--repo-url 'http://10.10.20.11:3100/Simone/your-repo.git' `
--branch main `
--build-command 'echo hello > artifact.txt' `
--guest-artifact-source 'artifact.txt' `
--artifact-base-dir 'F:\CI\Artifacts\smoke-job-win'
```
Atteso: exit code `0`, file `F:\CI\Artifacts\smoke-job-win\artifact.txt` presente,
VM smoke-job-win **non** più esistente in `F:\CI\BuildVMs\` (cleanup garantito).
- [x] Ripetere su Linux:
```powershell
& 'F:\CI\python\venv\Scripts\python.exe' -m ci_orchestrator job `
--template-path 'F:\CI\Templates\LinuxBuild2404\LinuxBuild2404.vmx' `
--snapshot-name BaseClean-Linux `
--guest-os linux `
--job-id smoke-job-linux `
--repo-url 'http://10.10.20.11:3100/Simone/your-repo.git' `
--branch main `
--build-command 'echo hello > artifact.txt' `
--guest-artifact-source 'artifact.txt' `
--artifact-base-dir 'F:\CI\Artifacts\smoke-job-linux'
```
Atteso: exit code `0`, `F:\CI\Artifacts\smoke-job-linux\smoke-job-linux\artifact.txt` presente.
- [x] Verificare assenza VM orfane:
```powershell
& 'F:\CI\python\venv\Scripts\python.exe' -m ci_orchestrator vm cleanup --what-if
```
Atteso: nessun candidato.
---
## Passo 6 — Workflow Gitea end-to-end
Validazione di A4 lato runner reale.
- [x] Aprire la UI Gitea, repo `local-ci-cd-system`, branch
`feature/python-rewrite-and-linux-migration`.
- [x] Triggerare manualmente i 3 workflow (Actions → Run workflow):
- [x] `lint.yml` → atteso PASS
- [x] `self-test.yml` → PASS — validati **entrambi i transport**
(in-guest clone default + host-side clone+zip) su Win e Linux:
| Job | Transport | Esito |
| ---------------------------- | ----------------------- | ----------- |
| smoke-windows | in-guest git clone | SUCCESS 26s |
| smoke-linux | in-guest git clone | SUCCESS 51s |
| smoke-windows-hostclone | host-side clone + zip | SUCCESS 28s |
| smoke-linux-hostclone | host-side clone + zip | SUCCESS 46s |
Note: artifact github-free (no upload-artifact; file raw su
`F:\CI\Artifacts\<job>`); `use-git-clone`/`submodules` default ON.
- [x] `build-ns7zip.yml` matrix Win+Linux → PASS (target repo
`Simone/nsis-plugin-ns7zip`, in-guest clone, `--dist`):
| Job | Esito |
| ------------------ | --------------------------------------- |
| build (windows) | SUCCESS — DLL in dist/, artifact host |
| build (linux) | SUCCESS — 8 vCPU/8 GB, wall ~121s |
| release (tag push) | SUCCESS — release Gitea + 2 asset upload |
Note: `release` validato con tag throwaway `v0.0.0-phaseA-test`
(poi rimosso); usa il token Actions automatico (Default Token
Permissions = Permissive) + REST API Gitea, legge gli artifact
host-side (no download-artifact). Linux perf: vCPU-starvation a 4
vCPU risolta con guest-cpu=8 (≈ Windows VM ~120s); gap col Windows
nativo MSVC (~30s) è inerente MinGW, fuori scope CI.
> Tutti i workflow PASS — nessun fallimento da triagare. (Procedura
> storica: in caso di fallimento, scaricare i log del run e aprire una
> voce in `TODO.md` con sha + output errore.)
---
## Passo 7 — Burn-in capacità (4 job concorrenti)
Validazione finale di A5 (definizione di "fatto Fase A").
- [x] Burn-in via `scripts/Start-BurnInTest.ps1` (wrapper lab che
hardcoda repo `burnin-dummy` + `BuildCommand` = `build.ps1`;
`Test-CapacityBurnIn.ps1` diretto senza `-BuildCommand` usa il
default dotnet → fallisce su repo non-.NET):
```powershell
.\scripts\Start-BurnInTest.ps1
```
- [x] Esito (4 job concorrenti × 3 round):
| Round | Pass | Fail | Elapsed | Status |
| ----- | ---- | ---- | ------- | ------ |
| 1 | 4 | 0 | 76s | PASS |
| 2 | 4 | 0 | 88s | PASS |
| 3 | 4 | 0 | 73s | PASS |
Total 12/12 PASS — **OVERALL: PASS**. Cleanup VM + lock OK ogni
round (0 orfani). A5 / Passo 7 validato.
---
## Passo 8 — Benchmark wall-clock
Confronto Python vs PowerShell. Pendenza A4.
- [x] Eseguito `.\scripts\Measure-CIBenchmark.ps1` (4 iter):
| Iter | Clone | Start | IP | WinRM | Destroy | Boot tot |
| ---- | ----- | ----- | ----- | ----- | ------- | -------- |
| 1 | 0.63 | 1.75 | 66.57 | 0.01 | 4.81 | 68.96 |
| 2 | 0.63 | 1.89 | 20.21 | 0.01 | 6.39 | 22.74 |
| 3 | 0.62 | 1.72 | 85.07 | 0.01 | 4.50 | 87.42 |
| 4 | 0.61 | 1.72 | 60.97 | 0.01 | 4.20 | 63.31 |
Nessun errore, destroy OK (~4-6s), 0 orfani. Clone/start/WinRM
trascurabili; **fase IP (20-85s) = costo dominante e variabile**
(detect IP guest via VMware Tools / ci-report-ip) — noto, non
bloccante. Prerequisito scoperto: lo shim `Remove-BuildVM.ps1`
inoltrava `-ErrorAction` come `--error-action` (destroy falliva ->
orfana); risolto per tutti gli shim blind-loop (commit
`fix(shims): skip PowerShell common params`).
- [x] Confronto col baseline pre-Python — **SALTATO** (deciso): nessun
baseline PS affidabile registrato; metriche infra appese a
`F:\CI\Logs\benchmark.jsonl` per trend futuri. Non bloccante.
---
## Passo 9 — Merge della Fase A in `main`
Solo dopo che i passi 37 sono tutti `[x]` PASS.
- [x] Self-review pre-merge: diff `main...feature` 108 file
(+12597/-3128), nessun secret/.venv/.env per nome. Solo 5 file
rimossi su feature = cleanup intenzionale (vecchio `gitea/`
action+lint → `.gitea/`; Pester test degli script PS rimpiazzati).
- [x] Merge `feature → main` con `--no-ff` (storia per-fase
preservata; conflitto rename/delete `build-nsInnoUnp.yml` →
`build-ns7zip.yml` risolto a favore di feature). Albero `main`
risultante == `feature` (Phase A esatto). Commit merge `2aa12bb`,
pushato a `origin/main`.
- [x] Tag release `v2.0.0-phaseA` creato (annotato su `b4ca7f3`) e
pushato a `origin`. Il push (`v*`) triggera `build-ns7zip.yml`
matrix + release = validazione finale end-to-end di `main`.
Cleanup: cancellare dalla UI Gitea la release auto-pubblicata
`v2.0.0-phaseA` (contiene binari ns7zip — quirk wiring del job
release), tenendo il **tag** come marker Phase A.
---
## Tracciamento globale
| Passo | Descrizione | Stato |
| ----- | ----------------------------------------- | ----- |
| 1 | Aggiorna venv di produzione | [x] |
| 2 | Restart act_runner | [x] |
| 3 | Smoke `wait-ready` Windows | [x] |
| 4 | Smoke `wait-ready` Linux | [x] |
| 5 | Smoke `job` end-to-end (Win + Linux) | [x] |
| 6 | Workflow Gitea (lint + self-test + build) | [x] |
| 7 | Burn-in 4 job concorrenti × 3 round | [x] |
| 8 | Benchmark wall-clock | [x] |
| 9 | Merge in `main` + tag `v2.0.0-phaseA` | [x] |
**Fase A definitivamente chiusa** (Passi 1-9 tutti `[x]`). Pronta la
Fase B (porting su host Linux). Cleanup residuo non bloccante:
cancellare dalla UI Gitea le release di test `v0.0.0-phaseA-test` e
l'auto-pubblicata `v2.0.0-phaseA` (i **tag** restano).
@@ -0,0 +1,211 @@
# Fase A → Fase B — Bridge checklist per l'utente
Questa checklist copre i passi **intermedi** tra "Fase A done" e "B1
inizia". Sono attività che girano sull'host **Windows attuale** (o sono
logistiche fuori dal repo) e non rientrano né in
[PhaseA-user-checklist.md](PhaseA-user-checklist.md) (validazione del
codice Python) né in [PhaseB-user-checklist.md](PhaseB-user-checklist.md)
(setup del nuovo host Linux).
> **Quando eseguirla**: dopo Passo 9 di Fase A (merge + tag), prima di
> Passo 1 di Fase B (setup host Linux Mint).
> **Tempo stimato**: 12 ore (escluso `tar` di backup, che dipende
> dalla dimensione di `F:\CI\`).
---
## Sezione 1 — Salva il baseline pre-migrazione
Senza un baseline registrato, il confronto richiesto da B7 ("tempo
medio entro ±20% baseline Windows") è impossibile.
- [ ] Eseguire benchmark sull'host Windows (post Fase A, runner Python attivo):
```powershell
cd <path-locale-repo>
.\scripts\Measure-CIBenchmark.ps1 | Tee-Object -FilePath "baseline-windows-$(Get-Date -Format 'yyyyMMdd').txt"
```
- [ ] Annotare in `docs/RUNBOOK.md` (sezione nuova "Windows host baseline"):
- Data
- Tempo medio per job (Windows e Linux template)
- Success rate
- Versione `act_runner` e `ci_orchestrator` (output di `python -m ci_orchestrator --version` se disponibile, altrimenti SHA del commit)
- Hardware: i9-10900X, 64 GB RAM, NVMe SSD (da `AGENTS.md`)
- [ ] Committare:
```powershell
git add docs/RUNBOOK.md
git commit -m "docs(runbook): record Windows host pre-migration baseline"
git push
```
---
## Sezione 2 — Pre-requisiti Windows per Fase B
Tutti questi vanno fatti **prima** di iniziare B1, perché B2/B3/B6 li
presuppongono già pronti.
### 2.1 Inventario chiavi SSH
- [ ] Verificare presenza chiavi:
```powershell
Get-ChildItem F:\CI\keys\ci_linux*
```
Atteso: `ci_linux` (privata, perm utente owner) e `ci_linux.pub`. Se
mancano, rigenerarle con `ssh-keygen -t ed25519 -f F:\CI\keys\ci_linux -N ""`
e re-installare `ci_linux.pub` nel template `LinuxBuild2404` su
`~ci_build/.ssh/authorized_keys`.
### 2.2 Recupero password guest Windows (`BuildVMGuest`)
La password salvata in Windows Credential Manager **non è
esportabile**. Devi conoscerla per re-inserirla con `secret-tool` su
Linux (Passo 3 di Fase B).
- [ ] Verificare di averla nel password manager personale.
- [ ] Se non la ricordi: bootare il template `WinBuild2025` (e `WinBuild2022`),
resettare la password dell'utente CI (`net user <user> <newpass>`),
ricatturare snapshot `BaseClean` da stato powered-off (vedi
`AGENTS.md` errore #9), aggiornare il Credential Manager Windows con
la nuova password, e annotarla in modo sicuro.
### 2.3 Generazione Gitea PAT per il nuovo runner
- [ ] Aprire Gitea → User Settings → Applications → Generate New Token.
- [ ] Nome: `ci-runner-linux`. Scope: `read:repository` + `write:package`
(o gli stessi del runner Windows attuale).
- [ ] Annotare il token in modo sicuro (verrà inserito al Passo 3 di
Fase B con `secret-tool store ... target GiteaPAT`).
### 2.4 OpenSSH Server su Windows (per rsync pull dal Linux)
Necessario solo se al Passo 2 di Fase B (transfer template) preferisci
fare `rsync pull` dal Linux. Alternativa: `rsync push` da Windows con
WSL/Cygwin/MSYS2.
- [ ] Installare OpenSSH Server:
```powershell
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
Start-Service sshd
Set-Service sshd -StartupType Automatic
New-NetFirewallRule -Name sshd -DisplayName 'OpenSSH Server' -Enabled True `
-Direction Inbound -Protocol TCP -Action Allow -LocalPort 22
```
- [ ] Validare connessione da un altro host: `ssh user@<windows-ip> "whoami"`.
- [ ] (Opzionale) Configurare `authorized_keys` per il futuro user
`ci-runner` del Linux box.
### 2.5 Template fully powered-off (gate per B2)
> AGENTS.md errore #9: snapshot catturato con VM accesa produce
> `*.vmem` / `*.vmsn` che rompono il clone su host diverso.
- [ ] Verificare che nessun template sia running:
```powershell
& 'C:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe' list
```
Atteso: nessun path sotto `F:\CI\Templates\` nell'output.
- [ ] Verificare assenza file di runtime:
```powershell
Get-ChildItem F:\CI\Templates -Recurse -Include *.vmem,*.vmsn,*.lck
```
Atteso: nessun risultato. Se ci sono `.vmem`/`.vmsn`, lo snapshot
non è clean → ricatturare `BaseClean` / `BaseClean-Linux` da stato
fully powered-off.
### 2.6 Backup precauzionale di `F:\CI\`
Da fare **prima** del cutover B6, ma anche prima del transfer B2 è una
buona pratica.
- [ ] Eseguire backup completo:
```powershell
$stamp = Get-Date -Format 'yyyyMMdd'
$archive = "E:\Backup\CI-pre-migration-$stamp.tar"
& 'C:\Program Files\Git\usr\bin\tar.exe' -cf $archive 'F:\CI\'
Get-FileHash $archive -Algorithm SHA256 | Tee-Object "$archive.sha256"
```
- [ ] Verifica integrità (lista contenuto, primi 20 record):
```powershell
& 'C:\Program Files\Git\usr\bin\tar.exe' -tf $archive | Select-Object -First 20
```
- [ ] Annotare path archivio + checksum SHA256.
### 2.7 Annotare cadenze scheduled task (se custom)
- [ ] Esportare l'inventario corrente:
```powershell
Get-ScheduledTask -TaskName 'CI-*' |
Select-Object TaskName,
@{n='Trigger';e={ $_.Triggers | ForEach-Object { $_.PSObject.Properties | Where-Object Name -in 'StartBoundary','RepetitionInterval','DaysOfWeek' | ForEach-Object { "$($_.Name)=$($_.Value)" } } -join '; ' }},
@{n='Action'; e={ "$($_.Actions.Execute) $($_.Actions.Arguments)" }} |
Format-List | Tee-Object -FilePath "scheduled-tasks-inventory-$(Get-Date -Format 'yyyyMMdd').txt"
```
- [ ] Confrontare con i `.timer` in `deploy/systemd/`:
| Task Windows | systemd timer | Cadenza prevista |
| ----------------------------- | ------------------------------------- | ---------------------- |
| `CI-CleanupOrphanedBuildVMs` | `ci-cleanup-orphans.timer` | ogni 6h + boot |
| `CI-Invoke-RetentionPolicy` | `ci-retention-policy.timer` | giornaliero 03:00 |
| `CI-Watch-DiskSpace` | `ci-watch-disk-space.timer` | ogni 15min |
| `CI-Watch-RunnerHealth` | `ci-watch-runner-health.timer` | ogni 15min |
| (nuovo) | `ci-backup-template.timer` | settimanale Dom 02:00 |
- [ ] Se le cadenze Windows sono diverse, aggiornare gli `OnCalendar` /
`OnUnitActiveSec` in `deploy/systemd/*.timer` **prima** del Passo
5 di Fase B. Committare la modifica.
---
## Sezione 3 — Logistica e finestra di manutenzione
Questi non sono comandi, ma checkpoint prima di iniziare B1 e B6.
- [ ] **Hardware Linux disponibile**: il box target è acquisito,
racked, raggiungibile in rete dall'host Windows e da Gitea.
- [ ] **DNS / IP statico** assegnato al nuovo host Linux (per
registrazione runner + raggiungibilità da admin).
- [ ] **Finestra di manutenzione per cutover B6 concordata**: 3060 min
di stop Gitea Actions, in un orario a basso traffico.
- [ ] **Comunicazione utenti del repo**: avviso che durante la
finestra B6 i workflow saranno temporaneamente fermi.
- [ ] **Piano di rollback compreso**: rileggere la sezione "Rollback"
del Passo 6 di [PhaseB-user-checklist.md](PhaseB-user-checklist.md).
---
## Tracciamento globale
| Sezione | Descrizione | Stato |
| ------- | ---------------------------------------------- | ----- |
| 1 | Baseline benchmark salvato in `docs/RUNBOOK.md` | [ ] |
| 2.1 | Chiavi SSH presenti | [ ] |
| 2.2 | Password `BuildVMGuest` recuperata | [ ] |
| 2.3 | Gitea PAT generato per `ci-runner-linux` | [ ] |
| 2.4 | OpenSSH Server attivo (o alt. rsync) | [ ] |
| 2.5 | Template fully powered-off, no `.vmem` | [ ] |
| 2.6 | Backup `F:\CI\` con SHA256 | [ ] |
| 2.7 | Cadenze scheduled task confrontate con timer | [ ] |
| 3 | Hardware + DNS + finestra + comunicazione OK | [ ] |
Quando tutte le righe sono `[x]`, puoi iniziare il Passo 1 di
[PhaseB-user-checklist.md](PhaseB-user-checklist.md).
@@ -0,0 +1,542 @@
# Fase B — Checklist finale per l'utente
La Fase B è la migrazione del runner CI dall'host Windows attuale a un
nuovo host **Linux Mint LTS** con VMware Workstation Pro Linux.
> **Stato repo**: solo lo step **B5** (unit `systemd` per i task
> periodici) è già committato in `deploy/systemd/` sul branch
> `feature/python-rewrite-and-linux-migration` (commit `50d37b5`).
> Tutti gli altri step (B1B4, B6B8) sono **operazioni hardware**
> sull'host Linux nuovo e devono essere eseguiti dall'utente.
>
> **Avanzamento**: B1 ✅ B2 ✅ B3 ✅ B4 ✅ — in corso: **B5** (timer systemd).
> **Pre-requisito**: la Fase A deve essere `done` (vedi
> `plans/PhaseA-user-checklist.md` Passo 9, merge + tag).
> B1, B2, B3 possono partire in parallelo a A3/A4/A5; B4B8 sono
> sequenziali e iniziano solo dopo "A done".
> **Tempo stimato totale**: 12 giorni di lavoro distribuiti su una
> finestra di 12 settimane (per consentire il burn-in tra B6 e B8).
---
## Passo 1 — Setup host Linux Mint (B1)
**Obiettivo**: hardware target con Linux Mint LTS, VMware Workstation
Pro Linux, layout storage e venv Python pronti.
- [x] Installare Linux Mint LTS sull'hardware target.
- [x] `sudo apt update && sudo apt full-upgrade -y && sudo reboot`
- [x] Scaricare e installare VMware Workstation Pro Linux (bundle
`.bundle` da Broadcow):
```bash
sudo bash VMware-Workstation-Full-*.bundle
vmrun -T ws --version # deve ritornare versione coerente
```
- [x] Smoke test Workstation (UI): creare VM trivia, clone, start,
stop, delete.
- [x] Configurare `vmnet8` NAT range `192.168.79.0/24` (allineato a
Windows) editando `/etc/vmware/vmnet8/dhcpd/dhcpd.conf` o via
`vmware-netcfg`.
- [x] Creare utente di servizio `ci-runner`:
```bash
sudo useradd -r -m -s /bin/bash ci-runner
```
- [x] Eseguire `setup-host-linux.sh` (copre mount disco, layout, Python venv,
clone repo, PowerShell Core):
```bash
# Clonare il repo prima di eseguire lo script
git clone https://gitea.emulab.it/Simone/local-ci-cd-system.git /tmp/local-ci-cd-system
sudo bash /tmp/local-ci-cd-system/setup-host-linux.sh -d /dev/sdf1
```
Lo script:
- Monta `/dev/sdf1` su `/var/lib/ci` via fstab (boot automatico).
Se la partizione ha una sottocartella `ci/`, ne sposta i contenuti
alla root della partizione prima del mount definitivo.
- Crea il layout `build-vms/`, `artifacts/`, `templates/`, `keys/`,
`logs/`, `runner/` con permessi e ACL corretti.
- Installa Python 3.11, crea `/opt/ci/venv`, clona il repo in
`/opt/ci/local-ci-cd-system` e installa il package.
- Installa PowerShell Core.
Flag utili se alcune dipendenze sono già presenti:
```bash
sudo bash setup-host-linux.sh -d /dev/sdf1 --skip-pwsh --skip-clone
```
Atteso a fine script: `ci_orchestrator --help` lista gli 11 sub-comandi.
---
## Passo 2 — Trasferimento template VM (B2)
**Obiettivo**: copiare i template VMware da `F:\CI\Templates\` su
`/var/lib/ci/templates/` mantenendo gli snapshot.
> ⚠️ AGENTS.md errore #9: i template devono essere **fully
> powered-off** prima della copia. Verificare assenza di `*.vmem` /
> `*.vmsn` di runtime.
- [x] Sull'host Windows:
```powershell
Get-ChildItem 'F:\CI\Templates' -Recurse -Include *.vmem,*.vmsn
# atteso: nessun risultato
```
- [x] Sull'host Linux, eseguire rsync via SSH dall'host Windows
(richiede OpenSSH Server attivo su Windows o, in alternativa,
`scp -r` lanciato da Windows verso Linux):
```bash
sudo -u ci-runner rsync -av --progress \
user@windows-host:/cygdrive/f/CI/Templates/ \
/var/lib/ci/templates/
```
- [x] Validare integrità snapshot:
```bash
find /var/lib/ci/templates -name '*.vmx' -exec vmrun -T ws listSnapshots {} \;
```
Atteso: `BaseClean` su `WinBuild2025.vmx` e `WinBuild2022.vmx`,
`BaseClean-Linux` su `LinuxBuild2404.vmx`.
- [x] Aprire ciascun `.vmx` su Workstation Linux per registrarli;
al prompt rispondere "**I copied it**".
- [x] **Prerequisito smoke test Windows**: il keyring file-based deve essere
configurato e la credenziale `BuildVMGuest` deve essere presente
**prima** di eseguire `wait-ready` (che prova WinRM). Questo è
formalmente il Passo 3 (B3), ma il minimo indispensabile per lo
smoke test è:
```bash
# Configurare il backend file keyring per ci-runner (una sola volta)
sudo -u ci-runner mkdir -p ~/.local/share/python_keyring
sudo -u ci-runner bash -c "cat > ~/.local/share/python_keyring/keyringrc.cfg << 'EOF'
[backend]
default-keyring=keyrings.alt.file.PlaintextKeyring
EOF"
sudo -u ci-runner /opt/ci/venv/bin/pip install keyrings.alt -q
# Salvare la credenziale guest Windows
sudo -u ci-runner /opt/ci/venv/bin/python /opt/ci/local-ci-cd-system/tools/ci-cred-set.py BuildVMGuest "WINBUILD-2025\\ci_build"
# Verificare
sudo -u ci-runner /opt/ci/venv/bin/python /opt/ci/local-ci-cd-system/tools/ci-cred-check.py BuildVMGuest
# atteso: OK (file) username='WINBUILD-2025\ci_build' password=****
```
> Il setup completo delle credenziali (chiavi SSH, GiteaPAT,
> PoC headless systemd) è nel Passo 3.
- [x] Smoke test pipeline VM:
```bash
# Windows smoke
CLONE_VMX=$(sudo -u ci-runner /opt/ci/venv/bin/python -m ci_orchestrator vm new \
--template /var/lib/ci/templates/WinBuild2025/WinBuild2025.vmx \
--snapshot BaseClean \
--clone-base-dir /var/lib/ci/build-vms \
--job-id smoke-b2-win \
--start)
echo "Clone VMX: $CLONE_VMX"
sudo -u ci-runner /opt/ci/venv/bin/python -m ci_orchestrator wait-ready \
--vmx "$CLONE_VMX" --guest-os windows --timeout 300
sudo -u ci-runner /opt/ci/venv/bin/python -m ci_orchestrator vm remove \
--vmx "$CLONE_VMX" --force
```
Ripetere per `LinuxBuild2404` (snapshot `BaseClean-Linux`,
`--guest-os linux`, `--job-id smoke-b2-linux`).
---
## Passo 3 — Trasferimento credenziali e chiavi (B3)
**Obiettivo**: chiavi SSH e credenziali guest disponibili al user
`ci-runner` headless.
- [x] Chiavi SSH guest Linux già presenti in `/var/lib/ci/keys/` (copiate
insieme ai template). Correggere solo i permessi:
```bash
sudo chown ci-runner:ci-runner /var/lib/ci/keys/ci_linux /var/lib/ci/keys/ci_linux.pub
sudo chmod 600 /var/lib/ci/keys/ci_linux
sudo chmod 644 /var/lib/ci/keys/ci_linux.pub
# verifica
ls -la /var/lib/ci/keys/
```
- [x] Credenziale guest Windows `BuildVMGuest` già salvata nel
prerequisito del Passo 2 con `keyrings.alt.file.PlaintextKeyring`
(schema a due entry — `secret-tool` scartato perché non funziona
headless senza D-Bus session).
Per riscrivere la credenziale se necessario:
```bash
sudo -u ci-runner /opt/ci/venv/bin/python /opt/ci/local-ci-cd-system/tools/ci-cred-set.py BuildVMGuest "WINBUILD-2025\\ci_build"
sudo -u ci-runner /opt/ci/venv/bin/python /opt/ci/local-ci-cd-system/tools/ci-cred-check.py BuildVMGuest
```
- [x] Re-store Gitea PAT con lo stesso schema:
```bash
sudo -u ci-runner /opt/ci/venv/bin/python /opt/ci/local-ci-cd-system/tools/ci-cred-set.py GiteaPAT ci-runner-linux
sudo -u ci-runner /opt/ci/venv/bin/python /opt/ci/local-ci-cd-system/tools/ci-cred-check.py GiteaPAT
```
- [x] **PoC headless**: il backend `PlaintextKeyring` non usa D-Bus —
funziona già da `sudo -u ci-runner` e funzionerà da
`act-runner.service`. Verificato durante smoke test Passo 2.
---
## Passo 4 — Setup act_runner come systemd service (B4)
**Obiettivo**: act_runner Linux registrato verso Gitea, gestito da
systemd.
- [x] Scaricare il binario act_runner Linux ≥ v1.0.4:
```bash
sudo mkdir -p /opt/ci/act_runner
sudo wget -O /opt/ci/act_runner/act_runner \
https://gitea.com/gitea/runner/releases/download/v1.0.4/gitea-runner-1.0.4-linux-amd64
sudo chmod +x /opt/ci/act_runner/act_runner
sudo chown -R ci-runner:ci-runner /opt/ci/act_runner
```
- [x] Generare token registrazione su Gitea (Admin → Runners → Create new runner).
- [x] Registrare il runner (sostituire `<TOKEN>`):
```bash
sudo -u ci-runner mkdir -p /var/lib/ci/runner
cd /var/lib/ci/runner
sudo -u ci-runner /opt/ci/act_runner/act_runner register \
--no-interactive \
--instance https://gitea.emulab.it \
--token <TOKEN> \
--name ci-linux \
--labels windows-build:host,linux-build:host
```
> ⚠️ Lasciare il runner in stato **paused** lato Gitea UI fino al
> cutover (Passo 6), per non intercettare job di produzione.
- [x] Creare `/etc/systemd/system/act-runner.service`:
```ini
[Unit]
Description=Gitea Act Runner
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=ci-runner
WorkingDirectory=/var/lib/ci/runner
Environment="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"
ExecStart=/opt/ci/act_runner/act_runner daemon --config /var/lib/ci/runner/config.yaml
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
```
- [x] Creare `/etc/ci/environment` (letto dalle unit dei task periodici
di B5; vedi `deploy/systemd/README.md`):
```bash
sudo tee /etc/ci/environment <<'EOF'
PYTHONIOENCODING=utf-8
CI_ROOT=/var/lib/ci
CI_TEMPLATES=/var/lib/ci/templates
CI_BUILD_VMS=/var/lib/ci/build-vms
CI_ARTIFACTS=/var/lib/ci/artifacts
CI_KEYS=/etc/ci/keys
EOF
sudo chmod 644 /etc/ci/environment
```
- [x] Abilitare e avviare:
```bash
sudo systemctl daemon-reload
sudo systemctl enable --now act-runner.service
sudo systemctl status act-runner
sudo journalctl -u act-runner -f # Ctrl+C dopo conferma OK
```
- [x] Generare e configurare `config.yaml` del runner:
```bash
# 1. Generare il file di default (conserva la registrazione in .runner)
sudo -u ci-runner /opt/ci/act_runner/act_runner generate-config \
| sudo -u ci-runner tee /var/lib/ci/runner/config.yaml > /dev/null
# 2. Sostituire i label Docker generati di default con quelli host corretti
# (generate-config scrive label ubuntu-*:docker:// che fanno cercare Docker)
sudo -u ci-runner sed -i \
-e 's| - "ubuntu-latest:docker://.*"| - "windows-build:host"|' \
-e '/ubuntu-24.04:docker:\/\//d' \
-e '/ubuntu-22.04:docker:\/\//d' \
/var/lib/ci/runner/config.yaml
sudo -u ci-runner sed -i \
'/ - "windows-build:host"/a\ - "linux-build:host"' \
/var/lib/ci/runner/config.yaml
# 3. Sostituire le variabili di esempio nella sezione runner.envs con le variabili CI
# (generate-config scrive due righe A_TEST_ENV_NAME_* come placeholder)
sudo -u ci-runner sed -i \
-e 's/ A_TEST_ENV_NAME_1: a_test_env_value_1/ CI_PYTHON_LAUNCHER: \/opt\/ci\/venv\/bin\/python/' \
-e 's/ A_TEST_ENV_NAME_2: a_test_env_value_2/ GITEA_CI_TEMPLATE_PATH: \/var\/lib\/ci\/templates\/WinBuild2025\/WinBuild2025.vmx\n GITEA_CI_LINUX_TEMPLATE_PATH: \/var\/lib\/ci\/templates\/LinuxBuild2404\/LinuxBuild2404.vmx/' \
/var/lib/ci/runner/config.yaml
# 4. Verificare
grep -A3 'labels:' /var/lib/ci/runner/config.yaml | head -5
grep -A4 'envs:' /var/lib/ci/runner/config.yaml
```
Atteso:
```yaml
labels:
- "windows-build:host"
- "linux-build:host"
envs:
CI_PYTHON_LAUNCHER: /opt/ci/venv/bin/python
GITEA_CI_TEMPLATE_PATH: /var/lib/ci/templates/WinBuild2025/WinBuild2025.vmx
GITEA_CI_LINUX_TEMPLATE_PATH: /var/lib/ci/templates/LinuxBuild2404/LinuxBuild2404.vmx
```
> Le variabili di sistema (`CI_ROOT`, `PYTHONIOENCODING`, ecc.) restano
> nel service unit. `runner.envs` è per la configurazione specifica
> del runner CI (path template, launcher Python).
```bash
# 5. Ricaricare e riavviare
sudo systemctl daemon-reload
sudo systemctl restart act-runner.service
sudo journalctl -u act-runner -n 20 # confermare avvio OK — nessun errore Docker
```
- [x] Smoke job: triggerare manualmente `self-test.yml` selezionando
il runner Linux (mettere temporaneamente in pausa quello Windows
o usare label distinte). Atteso: PASS.
> ⚠️ Prerequisito: le variabili `GITEA_CI_TEMPLATE_PATH` e
> `GITEA_CI_LINUX_TEMPLATE_PATH` devono essere presenti nel service
> unit **prima** di avviare lo smoke test, altrimenti il job fallisce
> con "Got unexpected extra argument".
---
## Passo 5 — Installare i timer systemd (B5)
**Obiettivo**: attivare le coppie `.service`+`.timer` già committate
in `deploy/systemd/` per replicare i task periodici di
`Register-CIScheduledTasks.ps1`.
> **Nota**: `ci-retention-policy` e `ci-backup-template` sono stati portati
> in Python puro (`retention run` / `template backup`) — **pwsh non è
> richiesto**. I backup template vengono compressi con `7z -mx=1` in
> `/var/lib/ci/backups/`. `ci-watch-runner-health` usa `--service-name
> act-runner` (nome Linux, non Windows).
> **Prerequisiti creati sull'host**:
> ```bash
> sudo mkdir -p /var/log/ci /var/lib/ci/backups
> sudo chown ci-runner:ci-runner /var/log/ci
> ```
- [x] Copiare le unit:
```bash
cd /opt/ci/local-ci-cd-system
sudo cp deploy/systemd/ci-*.service deploy/systemd/ci-*.timer /etc/systemd/system/
sudo systemctl daemon-reload
```
- [x] Abilitare e avviare tutti i timer:
```bash
for t in ci-cleanup-orphans ci-retention-policy \
ci-watch-disk-space ci-watch-runner-health \
ci-backup-template; do
sudo systemctl enable --now "${t}.timer"
done
```
- [x] Verificare lo schedule:
```bash
systemctl list-timers --all 'ci-*'
```
Atteso: 5 timer in stato `active`. ✅ PASS
- [x] Trigger manuale di ciascun service per validare l'esecuzione
one-shot:
```bash
for s in ci-cleanup-orphans ci-retention-policy \
ci-watch-disk-space ci-watch-runner-health \
ci-backup-template; do
sudo systemctl start "${s}.service"
sudo systemctl status "${s}.service" --no-pager
done
```
✅ Tutti e 5 PASS.
- [x] Per maggiori dettagli (mapping Windows→Linux, troubleshooting,
rollback) vedi `deploy/systemd/README.md`.
---
## Passo 6 — Cutover (B6)
**Obiettivo**: passare all'uso normale di Linux come OS primario di
produzione CI. Il cutover è un semplice riavvio — i due sistemi non
coesistono mai.
> ⚠️ Eseguire solo dopo che i Passi 15 sono tutti `[x]` PASS.
- [x] Verificare nessun job in coda lato Gitea (Admin → Actions → Tasks).
- [x] Attendere la fine di eventuali job in esecuzione su Windows.
- [x] Sbloccare il runner Linux su Gitea UI (rimuovere `paused`).
- [x] Riavviare la macchina in Linux.
- [x] Verificare runner Linux online su Gitea UI e act-runner attivo:
```bash
sudo systemctl status act-runner
sudo journalctl -u act-runner -n 20
```
- [x] Smoke trigger:
- [x] `self-test.yml` → PASS dal runner Linux
- [x] `build-ns7zip.yml` matrix Win+Linux → PASS dal runner Linux
- [x] Monitorare per ≥30 min:
```bash
sudo journalctl -u act-runner -f
```
> **Rollback**: riavviare in Windows — il runner Windows riprende
> automaticamente. Mettere in pausa il runner Linux su Gitea UI,
> documentare l'incidente in `TODO.md`.
---
## Passo 7 — Capacity burn-in (B7)
**Obiettivo**: validare il carico target con tempi paragonabili al
baseline Windows (entro ±20%). I due burn-in sono sequenziali
(dual-boot: un OS alla volta).
- [ ] **Avviato in Linux** — burn-in 4 job concorrenti × 10 round su
`WinBuild2025`:
```bash
pwsh /opt/ci/local-ci-cd-system/scripts/Test-CapacityBurnIn.ps1 \
-Concurrency 4 -Rounds 10 -Template /var/lib/ci/templates/WinBuild2025/WinBuild2025.vmx
```
- [ ] **Avviato in Linux** — burn-in 4 × 10 su `LinuxBuild2404`
(analogo, VMX Linux).
- [ ] Misurare:
- [ ] Tempo medio per job, confronto con baseline A5
- [ ] Tutti i 80 job (2 × 4 × 10) PASS
- [ ] Zero VM orfane in `/var/lib/ci/build-vms/`:
```bash
sudo -u ci-runner /opt/ci/venv/bin/python -m ci_orchestrator vm cleanup --what-if
```
- [ ] Spazio disco `/var/lib/ci/build-vms/` torna al baseline post-cleanup
- [ ] Documentare i risultati in `docs/RUNBOOK.md` (sezione "Linux
host baseline").
- [ ] Se delta > 20% vs baseline Windows: aprire issue in `TODO.md`
con dettagli per profiling (probabile candidato: filesystem ext4
vs NTFS — valutare XFS/BTRFS).
---
## Passo 8 — Stabilità ≥1 settimana
**Obiettivo**: verificare ≥1 settimana di esercizio con la macchina
avviata abitualmente in Linux senza incidenti critici.
- [ ] Esercizio normale per ≥7 giorni con boot in Linux.
- [ ] Verifica giornaliera (bastano 2 minuti):
```bash
sudo systemctl --failed # nessun service in failed
systemctl list-timers --all 'ci-*' # tutti gli ultimi run OK
sudo journalctl -u act-runner --since "24h ago" -p err
```
- [ ] Nessun rollback effettuato durante la settimana.
---
## Passo 9 — Gestione dual-boot
> La macchina è **dual-boot**: Windows e Linux coesistono sullo stesso
> hardware, non girano mai contemporaneamente. Non c'è dismissione —
> Windows resta pienamente funzionale e si usa riavviando nel boot
> entry corrispondente.
- [ ] Aggiornare `docs/RUNBOOK.md` con la procedura dual-boot:
come passare da Linux a Windows e viceversa, e cosa verificare
al boot (runner online, act-runner.service attivo).
- [ ] (Facoltativo) Verificare che GRUB abbia i timeout e i default
corretti per l'uso quotidiano:
```bash
sudo nano /etc/default/grub # GRUB_DEFAULT, GRUB_TIMEOUT
sudo update-grub
```
---
## Tracciamento globale
| Passo | Step | Descrizione | Stato |
| ----- | ---- | -------------------------------------------------- | ----- |
| 1 | B1 | Setup host Linux Mint (OS, Workstation, venv, ACL) | [x] |
| 2 | B2 | Trasferimento template VM + smoke `vm new` | [x] |
| 3 | B3 | Chiavi SSH + keyring (PoC headless) | [x] |
| 4 | B4 | act_runner systemd service + `self-test.yml` PASS | [x] |
| 5 | B5 | Timer systemd installati e attivi | [x] |
| 6 | B6 | Cutover (riavvio in Linux, runner Linux attivo) | [ ] |
| 7 | B7 | Capacity burn-in 4 × 10 (Win + Linux, sequenziale) | [ ] |
| 8 | — | ≥1 settimana di stabilità (boot Linux abituale) | [ ] |
| 9 | — | Gestione dual-boot + RUNBOOK aggiornato | [ ] |
> **Architettura**: la macchina è **dual-boot** (stesso hardware).
> Avviata in Linux → runner Linux attivo. Avviata in Windows → runner
> Windows attivo. I due sistemi non girano mai contemporaneamente.
Quando i passi 1-8 sono `[x]`, la **Fase B è chiusa** (B8 annullato per
vincolo) e si può valutare l'apertura della Fase C (eliminazione `pwsh`
dall'host Linux, vedi `plans/idea-3-powershell-removal.md`). La Fase D
(backend ESXi, `plans/idea-3-esxi-support.md`) viene dopo.
@@ -0,0 +1,208 @@
# Fase A — Rewrite in Python
> **Stato**: piano esecutivo (parte committed della roadmap).
> Prerequisito di [Fase B](idea-2-linux-host.md). Vedi [overview](ideas-overview.md).
> Baseline: ~13.000 righe PowerShell 5.1 (41 file `.ps1` + 2 `.psm1`), 2 script
> bash, 5 workflow Gitea Actions.
---
## 1. Obiettivo
Riscrivere host orchestrator + script di gestione VM in **Python 3.11+**,
**con design cross-platform fin dall'inizio** (la Fase B trasferirà l'host
su Linux Mint senza riscrivere il core).
Vincoli mantenuti:
- runtime act_runner + Gitea (consuma comandi via shell, agnostico al linguaggio)
- template VM (Windows con WinRM, Linux con SSH)
- VMware Workstation Pro come hypervisor (`vmrun` invariato)
- formato artifact e layout di storage
- workflow YAML esistenti (`gitea/workflows/*.yml`) — cambia solo la `shell:`
e il comando invocato
Il punto di ingresso `Invoke-CIJob.ps1` viene sostituito da
`python -m ci_orchestrator job ...`, invocato da
`gitea/actions/local-ci-build/action.yml`.
---
## 2. Decisioni di design forzate dal target Linux futuro
Per evitare un doppio porting in Fase B, il codice Python deve rispettare
da subito queste regole:
| Regola | Implementazione |
| ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| Nessun path hardcoded | Tutti i path via `pathlib.Path` + variabili ambiente (`CI_ROOT`, `CI_TEMPLATES`, `CI_BUILD_VMS`, `CI_ARTIFACTS`, `CI_KEYS`) con default in un file `config.toml`. |
| Path-binari astratti | `vmrun`, `ssh`, `scp` cercati via `shutil.which()` con fallback a env var (`VMRUN_PATH`); default `vmrun.exe` su Windows, `vmrun` su Linux. |
| Nessuna API Windows-only nel core | `keyring` (cross-platform), `paramiko` (no openssh client esterno), `pypsrp` (no `New-PSSession`). |
| Astrazione hypervisor | Interfaccia `VmBackend` (Protocol) con metodi `clone_linked`, `start`, `stop`, `delete`, `get_ip`. Implementazione iniziale `WorkstationVmrunBackend`. Hook per `EsxiBackend` futuro (Fase C). |
| Astrazione credential store | Interfaccia `CredentialStore` con backend `KeyringCredentialStore` (default) — funziona uguale su Win Credential Manager e Linux Secret Service. |
| Logging | `logging` con formatter strutturato; nessun colore ANSI obbligatorio (alcuni runner cattura non li gestiscono). |
| Path separator nei workflow YAML | Lasciare i workflow esistenti per ora; in Fase B si aggiorneranno i path (variabili env già pronte ad assorbire la modifica). |
---
## 3. Mappa di traduzione
| Componente PowerShell | Equivalente Python |
| -------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| `Invoke-Vmrun` (wrapper `vmrun.exe` su Win / `vmrun` su Linux) | `subprocess.run([vmrun, '-T', 'ws', op, *args], check=False, capture_output=True)` |
| `New-PSSession` + `Invoke-Command` (WinRM) | `pypsrp.client.Client(host, username, password, ssl=True, cert_validation=False)` |
| `Copy-Item -ToSession / -FromSession` | `pypsrp.client.Client.copy()` / `fetch()` |
| `_Transport.psm1` (`ssh.exe` / `scp.exe`) | `paramiko.SSHClient` / `paramiko.SFTPClient` |
| Credential Manager (`BuildVMGuest`, `GiteaPAT`) | `keyring.get_credential('BuildVMGuest', None)` |
| Pester | `pytest` + `pytest-mock` |
| PSScriptAnalyzer | `ruff` + `mypy --strict` |
| `Write-Host` | `logging.getLogger(__name__).info(...)` |
| `ConvertTo-Json` per stato job | `json.dumps()` su dataclass |
| Workflow YAML | invariati in Fase A |
| Script `.sh` in `gitea/burnin-dummy/` | invariati (girano nel guest Linux) |
---
## 4. Cosa NON viene portato in Python
Gli script `template/` (`Prepare-*.ps1`, `Deploy-*.ps1`,
`Install-CIToolchain-*.ps1`) restano in PowerShell:
- girano una tantum (ricostruzione template)
- sono fortemente legati a Windows (autounattend.xml, registry, sysprep, WinRM enable)
- gli script `Install-CIToolchain-WinBuild*.ps1` girano **dentro** il guest
Windows e PS è il linguaggio nativo lì
In Fase B questi script restano richiamabili da host Linux con `pwsh` 7
quando serve ricostruire un template Windows (operazione rara, anche manuale).
---
## 5. Strategia di migrazione: strangler fig in 4 step
Sequenza obbligata. Ad ogni step, gli script PS non ancora portati continuano
a funzionare; la coesistenza è garantita dalla stessa CLI `python -m ci_orchestrator`
chiamata internamente dagli script `.ps1` man mano che vengono "scavati".
### Step A1 — Bootstrap progetto + moduli core (`_Common`, `_Transport`)
Output:
- `pyproject.toml`, package `src/ci_orchestrator/`
- `venv` in `F:\CI\python\venv\`
- moduli: `config.py` (env + TOML), `vmrun.py` (wrapper), `winrm.py` (pypsrp),
`ssh.py` (paramiko), `credentials.py` (keyring), `backends/workstation.py`
- protocollo `VmBackend` definito
- test pytest per ognuno (mock `subprocess`, `paramiko`, `pypsrp`)
Validazione: `lint.yml` e `self-test.yml` continuano a passare; nuovi job
pytest aggiunti al workflow `lint.yml`.
### Step A2 — Script "foglia" (no state condiviso con orchestratore)
Portare in Python (CLI subcommands `python -m ci_orchestrator <cmd>`):
- `Wait-VMReady``wait-ready`
- `Remove-BuildVM``vm remove`
- `Cleanup-OrphanedBuildVMs``vm cleanup`
- `Watch-DiskSpace``monitor disk`
- `Watch-RunnerHealth``monitor runner`
- `Get-CIJobSummary``report job`
Per ognuno: il file `.ps1` viene sostituito da uno **shim** di 3 righe che
chiama `python -m ci_orchestrator <cmd> ...`, così i call site esterni
(scheduled tasks, ecc.) non si rompono.
### Step A3 — Pipeline di build
Portare in Python:
- `New-BuildVM``vm new`
- `Invoke-RemoteBuild``build run`
- `Get-BuildArtifacts``artifacts collect`
Stessa strategia shim per i `.ps1` esistenti.
### Step A4 — Orchestratore + switch workflow
Portare `Invoke-CIJob.ps1``python -m ci_orchestrator job`.
Aggiornare `gitea/actions/local-ci-build/action.yml` per chiamare Python
direttamente (non più via shim, per ridurre overhead startup).
A questo punto i `.ps1` shim restano come fallback; eliminarli al primo
ciclo di pulizia successivo.
### Step trasversale A5 — Test e documentazione
Parallelo agli step A1-A4:
- ogni feature portata ha test pytest che coprono almeno gli scenari
documentati negli "errori frequenti" di `AGENTS.md` (#9, #10, #11, #12)
- `AGENTS.md` aggiornato con sezione "Python development" (venv, stile,
pytest, ruff)
- `docs/ARCHITECTURE.md` aggiornato con il nuovo layout package
- `README.md` aggiornato con setup Python
---
## 6. Layout repository post-Fase A
```
src/ci_orchestrator/
__init__.py
__main__.py # entry point CLI (click)
config.py # env vars + config.toml loader
credentials.py # CredentialStore protocol + KeyringStore
backends/
__init__.py
protocol.py # VmBackend Protocol
workstation.py # WorkstationVmrunBackend
transport/
__init__.py
winrm.py # pypsrp client wrapper
ssh.py # paramiko client wrapper
commands/
job.py # ex Invoke-CIJob
vm.py # new / remove / cleanup
build.py # build run
artifacts.py # collect
wait.py # wait-ready
monitor.py # disk / runner
report.py # job summary
tests/
test_backends_workstation.py
test_transport_winrm.py
test_transport_ssh.py
test_commands_job.py
...
pyproject.toml
config.example.toml
scripts/ # PS legacy (shim sottili durante migrazione)
template/ # PS invariati (provisioning template)
gitea/ # workflow + actions invariati
```
---
## 7. Rischi e mitigazioni
| Rischio | Severità | Mitigazione |
| ---------------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------ |
| `pypsrp` ha edge case con WinRM HTTPS self-signed | Media | PoC su `wait-ready` come primo deliverable di Step A1 prima di committare al resto. |
| `keyring` sotto SYSTEM account (act_runner service) può non vedere credenziali utente | Alta | Già un problema oggi con Credential Manager; documentare l'uso di credenziali a livello machine (DPAPI machine scope su Win, file vault con `age` su Linux). |
| Perdita di know-how degli "errori frequenti" `AGENTS.md` durante refactor | Media | Convertire ognuno (#9, #10, #11, #12) in un test pytest **prima** di rimuovere il `.ps1` corrispondente. |
| Doppio mantenimento durante la migrazione | Alta | Strategia shim minimizza la finestra; `lint.yml` deve passare per **entrambi** i mondi (PSSA + ruff). |
| Astrazione `VmBackend` over-engineered se Fase C non parte mai | Bassa | Una sola implementazione concreta + Protocol = ~50 righe extra; costo trascurabile. |
| act_runner cattura male l'output Python (encoding UTF-8 vs cp1252 su Win) | Media | Forzare `PYTHONIOENCODING=utf-8` in `runner/config.yaml` env. |
---
## 8. Definizione di "fatto" (Fase A)
- [ ] Tutti gli script in `scripts/` portati a Python o ridotti a shim
- [ ] `Invoke-CIJob.ps1` non più presente nei workflow YAML
- [ ] `pytest` verde con coverage minima 70% su `src/ci_orchestrator/`
- [ ] `ruff check` + `mypy --strict` puliti su tutto `src/`
- [ ] `lint.yml` aggiornato: PSSA per file PS legacy + ruff/mypy per Python
- [ ] Workflow `build-ns7zip.yml` PASS su matrice Windows + Linux
- [ ] Smoke test (`Test-Smoke.ps1` o equivalente Python) PASS
- [ ] Capacity burn-in 4 job concorrenti PASS
- [ ] `README.md`, `AGENTS.md`, `docs/ARCHITECTURE.md`, `docs/HOST-SETUP.md` aggiornati
@@ -0,0 +1,214 @@
# Fase B — Migrazione host su Linux Mint
> **Stato**: piano esecutivo (parte committed della roadmap).
> Prerequisito: [Fase A](idea-1-python-rewrite.md) completata.
> Vedi [overview](ideas-overview.md).
> Hypervisor scelto: **VMware Workstation Pro Linux** (zero conversione template).
---
## 1. Obiettivo
Spostare l'host CI/CD da Windows 11 + Workstation Pro a **Linux Mint** +
**Workstation Pro Linux**, mantenendo:
- act_runner / Gitea (binario Linux ufficiale)
- workflow YAML invariati (path generalizzati via env vars introdotte in Fase A)
- template VM Windows e Linux **identici** (stessi `.vmx`, `.vmdk`, snapshot)
- artifact layout (cambia solo la radice: `F:\CI\``/var/lib/ci/`)
- transport WinRM/SSH ai guest (pypsrp + paramiko già cross-platform da Fase A)
Cambia: OS host, service manager, credential store, percorsi, ownership filesystem.
---
## 2. Decisione hypervisor: perché Workstation Pro Linux
Confronto sintetico (decisione presa, non più aperta):
| Opzione | Verdetto |
| ---------------------------------- | -------------------------------------------------------------------- |
| **VMware Workstation Pro Linux** | **SCELTA.** `vmrun` identico, VMX template riutilizzabili 1:1, free per uso personale e commerciale dal 2024. Plumbing-only port. |
| KVM + libvirt | Rimandato. Richiederebbe conversione VMDK→qcow2, virtio injection nei guest Windows, rewrite backend hypervisor. Resta come piano B se Broadcom dismette Workstation. |
| VirtualBox | Scartato. Performance/snapshot inferiori. |
---
## 3. Mappa di adattamento
| Componente attuale | Equivalente Linux + Workstation Pro |
| --------------------------------------------------- | ------------------------------------------------------------ |
| `vmrun.exe` (`C:\Program Files (x86)\...`) | `/usr/bin/vmrun` (binario senza `.exe`, installato dal package Workstation) |
| File `.vmx` / `.vmdk` | **identici, copiati così come sono** |
| Snapshot `BaseClean` / `BaseClean-Linux` | preservati nella copia |
| `F:\CI\BuildVMs\` | `/var/lib/ci/build-vms/` |
| `F:\CI\Artifacts\` | `/var/lib/ci/artifacts/` |
| `F:\CI\Templates\` | `/var/lib/ci/templates/` |
| `F:\CI\keys\ci_linux` | `/etc/ci/keys/ci_linux` (perms `600`, owner `ci-runner`) |
| Credential Manager (`BuildVMGuest`, `GiteaPAT`) | `secret-tool` / GNOME Keyring / KWallet (backend `keyring`) |
| WinRM HTTPS verso guest Windows | identico (pypsrp parla con WinRM da Linux senza modifiche) |
| SSH verso guest Linux | identico (paramiko) |
| act_runner come Windows Service | systemd unit `act-runner.service` |
| Task Scheduler (`Register-CIScheduledTasks.ps1`) | systemd `*.timer` + `*.service` units |
| PowerShell 5.1 | `pwsh` 7 (solo per ricostruire template, raro) |
| `vmnet8` NAT (`192.168.79.0/24`) | `vmnet8` NAT su Workstation Pro Linux (stesso concetto, IP range configurabile) |
---
## 4. Strategia di migrazione
### Step B1 — Setup host Linux Mint in parallelo (no impatto produzione)
- Installazione Linux Mint LTS + aggiornamenti
- Installazione VMware Workstation Pro per Linux (bundle ufficiale Broadcom)
- Verifica `vmrun` con una VM di test (clone, start, stop)
- Configurazione `vmnet8` NAT range coerente o equivalente
- Creazione utente di servizio `ci-runner` (uid dedicato)
- Creazione storage layout `/var/lib/ci/{build-vms,artifacts,templates,keys}`
con ownership `ci-runner:ci-runner` (mode 750)
- Installazione Python 3.11+ + venv in `/opt/ci/venv/` con `pip install -e .`
del package `ci_orchestrator` portato in Fase A
Validazione: `python -m ci_orchestrator --help` funziona da utente `ci-runner`.
### Step B2 — Trasferimento template VM
- Spegnere i template Workstation sull'host Windows (devono essere
fully powered-off — vedi `AGENTS.md` errore #9)
- Copia bit-by-bit di `F:\CI\Templates\``/var/lib/ci/templates/`
(`scp -r` o `rsync`)
- Aprire i `.vmx` su Workstation Linux per registrarli (`vmrun start ... nogui`
con stop immediato, oppure `vmrun -T ws snapshot list`) — risolverà
eventuali path interni se Workstation li chiede
- Verifica snapshot presenti: `vmrun listSnapshots <vmx>` deve mostrare
`BaseClean` / `BaseClean-Linux`
- Smoke test manuale: clone linked + start + WinRM/SSH dal host Linux
### Step B3 — Trasferimento credenziali e chiavi
- Copia chiave SSH guest Linux: `F:\CI\keys\ci_linux*``/etc/ci/keys/`
(mode 600, owner `ci-runner`)
- Re-store delle credenziali guest Windows nel keyring Linux:
`secret-tool store --label='BuildVMGuest' service ci target BuildVMGuest`
- Re-store Gitea PAT: `secret-tool store --label='GiteaPAT' service ci target GiteaPAT`
- Verifica via Python: `keyring.get_credential('BuildVMGuest', None)` non None
### Step B4 — Setup act_runner come systemd service
- Download act_runner Linux binary (versione allineata a v1.0.2 o successiva)
- Registrazione runner verso Gitea con label `windows-build:host` e
`linux-build:host` (stesse label di oggi → workflow non cambiano)
- File unit `/etc/systemd/system/act-runner.service`:
- `User=ci-runner`
- `WorkingDirectory=/var/lib/ci/runner`
- `Environment=PYTHONIOENCODING=utf-8`
- env vars `CI_*` per il config Python
- `systemctl enable --now act-runner.service`
- Validazione: `journalctl -u act-runner -f` mostra connessione a Gitea OK
### Step B5 — Conversione scheduled tasks → systemd timers
`Register-CIScheduledTasks.ps1` definisce N task periodici. Per ognuno
creare:
- `/etc/systemd/system/ci-<task>.service` (oneshot, chiama
`python -m ci_orchestrator <cmd>`)
- `/etc/systemd/system/ci-<task>.timer` (`OnCalendar=...` equivalente)
- `systemctl enable --now ci-<task>.timer`
Task tipici da convertire:
- `cleanup-orphaned-vms` (ogni ora)
- `retention-policy` (giornaliero)
- `watch-disk-space` (ogni 15 min)
- `watch-runner-health` (ogni 5 min)
- `backup-template` (settimanale)
### Step B6 — Cutover
Finestra di manutenzione concordata:
1. Stop act_runner sull'host Windows (`Stop-Service actions-runner`)
2. Rsync incrementale finale degli artifact recenti:
`F:\CI\Artifacts\``/var/lib/ci/artifacts/`
3. Verificare nessun job in coda lato Gitea
4. Avviare act_runner Linux (`systemctl start act-runner`)
5. Trigger manuale di un workflow di smoke test
6. Trigger del workflow matrix `build-ns7zip.yml` (Win + Linux build)
Se PASS → cutover confermato. Se FAIL → rollback (riavvio runner Windows,
nessun dato è stato distrutto).
### Step B7 — Capacity burn-in sul nuovo host
Eseguire `Test-CapacityBurnIn` (versione Python) con N=4 concorrenti × R=10
round, sia per template Windows che Linux. Confronto tempi vs baseline
Windows host pre-migrazione (aspettativa: paragonabili, eventuali differenze
vanno indagate ma non sono blocker se entro ±20%).
### Step B8 — Decommissioning host Windows (opzionale)
Dopo 1-2 settimane di stabilità sul nuovo host:
- Backup finale di `F:\CI\` su archivio
- Spegnimento runner Windows (lasciato installato per un mese come
rollback option)
- Riallocazione hardware
---
## 5. Adattamenti al codice Python (residui dopo Fase A)
Se Fase A ha rispettato le regole di §2 di [idea-1-python-rewrite.md](idea-1-python-rewrite.md),
il codice **non cambia**. Modifiche residue attese:
- `config.example.toml`: aggiungere sezione `[paths.linux]` con default
`/var/lib/ci/...` (la sezione `[paths.windows]` resta per chi ricostruisce
template da Win)
- `runner/config.yaml`: path env vars aggiornati a POSIX
- Eventuali test pytest che assumevano `Path('F:\\CI')` → usare fixture
parametrizzata
- Workflow `lint.yml`: rimuovere o relegare a job opzionale la parte PSSA
(utile solo se si tocca ancora codice PS dei template)
---
## 6. Rischi specifici Fase B
| Rischio | Severità | Mitigazione |
| -------------------------------------------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------- |
| `vmrun` su Workstation Linux ha differenze sottili nel parsing output `list` / `getGuestIPAddress` | Media | Test del modulo `vmrun.py` su Linux durante Step B1; eventuali fix dietro feature flag. |
| Performance I/O linked clone su filesystem ext4 vs NTFS | Bassa | Misurare con burn-in (Step B7). Eventualmente XFS o BTRFS con CoW per il dataset template. |
| `vmnet8` NAT range configurato diversamente su Linux: IP collision con altri host LAN | Media | Allineare il range a quello attuale Windows (`192.168.79.0/24`) in `vmware-vmnet.conf`. |
| WinRM HTTPS self-signed: pypsrp da Linux può negoziare TLS 1.2 con cipher non supportati dal Windows guest | Media | Già testato in Fase A (lo stesso codice gira anche da Windows host). Validare con smoke test prima del cutover. |
| Permessi su `/var/lib/ci/build-vms/`: vmrun gira come `ci-runner` ma deve scrivere VMDK CoW | Media | ACL POSIX: `setfacl -d -m u:ci-runner:rwX` sulla directory. Test in Step B1. |
| `secret-tool` headless: act_runner come systemd service non ha sessione D-Bus per accedere al keyring | Alta | Usare backend `keyring` file-based + `age`/`sops` (vault file) oppure `keyring.backends.SecretService` con D-Bus user@.service. Decidere in Step B3 con PoC. |
| Errore #11 `AGENTS.md` (machine-id Linux clone) | Bassa | Identico comportamento, fix identico (reset machine-id pre-snapshot). Già documentato. |
| Errore #12 `AGENTS.md` (stderr nativa + `'Stop'`) | N/A | Sparisce: il codice non è più PowerShell. |
---
## 7. Path alternativo "dual-host" (se la Fase B si blocca)
Se durante la migrazione si scopre un blocker grave (es. WinRM da Linux
non funzionante affidabilmente verso il template Windows specifico), si
può ripiegare su:
- mantenere host Windows per i job `windows-build:host`
- usare host Linux Mint solo per `linux-build:host` (label dedicata)
- runner doppio registrato verso Gitea
Costo: codebase divisa in due ambienti, maggiore overhead operativo.
**Soluzione di emergenza, non target finale.**
---
## 8. Definizione di "fatto" (Fase B)
- [ ] act_runner gira come systemd service `act-runner.service` su Linux Mint
- [ ] Tutti i template VM (`WinBuild2025`, `WinBuild2022`, `LinuxBuild2404`)
operativi dal nuovo host con snapshot integri
- [ ] Workflow `build-ns7zip.yml` (matrix Win+Linux) PASS dal nuovo host
- [ ] Burn-in capacity 4 job concorrenti PASS, tempi entro ±20% del baseline
- [ ] Storage migrato a `/var/lib/ci/`, host Windows in stand-by come rollback
- [ ] Tutti i timer systemd attivi e schedulati
- [ ] Credenziali guest accessibili da `ci-runner` headless
- [ ] `README.md`, `AGENTS.md`, `docs/HOST-SETUP.md` aggiornati per host Linux
- [ ] Almeno 1 settimana di esercizio in produzione senza incidenti
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,176 @@
# Prompt — Generazione piano di implementazione unificato Fase A + Fase B
> Copia-incolla il blocco sotto come messaggio iniziale all'IA che dovrà
> produrre il piano. L'IA deve avere accesso in lettura al repository
> `local-ci-cd-system` (in particolare ai file citati nei riferimenti).
---
## Prompt da fornire all'IA
Sei un senior platform/DevOps engineer assegnato al progetto
**local-ci-cd-system** (CI/CD on-prem basato su Gitea Actions + act_runner +
VMware Workstation Pro + VM ephemere Win/Linux). Il tuo compito è produrre
**un singolo documento di piano implementativo dettagliato** che fonde
**Fase A** (rewrite Python dell'orchestratore) e **Fase B** (migrazione
host da Windows 11 a Linux Mint) in un'unica roadmap eseguibile, mantenendo
**hook architetturali compatibili con Fase C** (backend ESXi futuro) senza
implementarla.
### Contesto obbligatorio da leggere PRIMA di scrivere
Prima di produrre output, leggi e tieni come riferimento:
1. [AGENTS.md](../AGENTS.md) — vincoli host, PowerShell 5.1, errori frequenti
(#1#12). Tutti i punti devono restare validi o essere esplicitamente
superati dal piano.
2. [plans/ideas-overview.md](ideas-overview.md) — razionale, ordine, criteri
di "fase completata", strategia di rollback.
3. [plans/idea-1-python-rewrite.md](idea-1-python-rewrite.md) — Fase A
(decisioni di design, mappa di traduzione, step A1A5, layout repo,
rischi, definizione di fatto).
4. [plans/idea-2-linux-host.md](idea-2-linux-host.md) — Fase B (decisione
hypervisor, mappa di adattamento, step B1B8, rischi, fatto).
5. [plans/idea-3-esxi-support.md](idea-3-esxi-support.md) — Fase C: leggi
solo §2 (architettura) e §3 (backend pyVmomi) per sapere quali hook
lasciare aperti nel design.
6. [docs/ARCHITECTURE.md](../docs/ARCHITECTURE.md), [docs/CI-FLOW.md](../docs/CI-FLOW.md),
[docs/HOST-SETUP.md](../docs/HOST-SETUP.md), [docs/RUNBOOK.md](../docs/RUNBOOK.md)
per capire layout e operatività attuali.
7. La cartella [scripts/](../scripts/) e [template/](../template/) per
conoscere l'inventario reale degli script da portare/tenere.
### Cosa devi produrre
Un unico file Markdown chiamato
`plans/implementation-plan-A-B.md` con la struttura sotto. Il documento
deve essere **autocontenuto** (chi lo legge non deve aprire altri 6 file
per capire cosa fare oggi), ma deve **citare** i piani originali quando
rimanda a sezioni di dettaglio.
### Struttura obbligatoria del documento
Usa esattamente questa scaletta e gli heading indicati:
#### `# Piano implementativo unificato — Fase A (Python) + Fase B (host Linux)`
#### `## Summary esecutivo`
Massimo 25 righe. Includi:
- Obiettivo combinato in 2 frasi.
- Tabella `Fase | Obiettivo | Output verificabile | Stato`.
- Decisioni architetturali chiave già prese (Python 3.11+, pypsrp,
paramiko, keyring, Workstation Pro Linux, astrazione `VmBackend`).
- Vincoli che NON cambiano (act_runner, Gitea, template VM, workflow YAML
invariati a meno di env vars / shell).
#### `## Checklist riassuntiva master`
Una sola checklist `- [ ]` piatta che enumera **tutti** i deliverable
ordinati cronologicamente, con prefisso del macro-step (`[A1]`, `[A2]`,
…, `[B1]`, …, `[X]` per gli step trasversali). Deve essere copiabile in
un issue tracker. Ogni voce: ≤ 1 riga, verbo all'infinito, deliverable
osservabile (file, comando, test che passa). Niente prosa qui.
#### `## 0. Prerequisiti e gating`
Cosa deve essere vero PRIMA di iniziare A1. Checklist.
#### `## 1. Fase A — Rewrite Python (host Windows attuale)`
Per ogni step da A1 ad A5:
- `### A<n> — <titolo>`
- `**Obiettivo**:` 1 frase.
- `**Input / dipendenze**:` step precedenti, file, env vars.
- `**Attività**:` checklist `- [ ]` granulare (515 voci tipiche),
ognuna con deliverable osservabile.
- `**Hook futuri Fase C**:` cosa deve essere progettato adesso per non
bloccare ESXi (es. firma del Protocol `VmBackend`, separazione
`clone_linked` da `start`, niente assunzione "stesso host").
- `**Test / validazione**:` comandi e criteri pass/fail concreti
(pytest target, coverage minima, smoke workflow Gitea da triggerare).
- `**Rollback**:` come tornare allo stato precedente lo step.
- `**Definizione di fatto step A<n>**:` checklist `- [ ]` di chiusura
(max 5 voci).
#### `## 2. Fase B — Migrazione host Linux Mint`
Stessa struttura per gli step da B1 a B8. In più:
- Marca esplicitamente quali step sono **eseguibili in parallelo** ad A
(B1 setup hardware Linux può iniziare durante A3/A4) e quali sono
**strettamente sequenziali** (B6 cutover dopo "A done").
- Per ogni step indica **rischi specifici** presi da
`idea-2-linux-host.md` §6 e mitigazioni.
#### `## 3. Step trasversali (X)`
Step che attraversano A e B (test, docs, observability, sicurezza,
gestione credenziali headless, lint dual-stack PSSA+ruff).
Stessa struttura per ogni X<n>.
#### `## 4. Hook architetturali per Fase C`
Lista esplicita dei punti del codice/config che restano "aperti" per
ESXi: Protocol `VmBackend`, selezione backend via `config.toml`,
separazione control plane / compute, naming neutro (no `vmrun_*` nei
nomi pubblici delle API). **Niente implementazione** — solo contratto
di design da non violare.
#### `## 5. Cronoprogramma e dipendenze`
Diagramma Mermaid `gantt` o `flowchart` con le dipendenze tra step
A1…A5, B1…B8, X1…Xn. **Niente date assolute, niente stime di durata
in giorni**: usa solo numerazione di sequenza e dipendenze logiche.
#### `## 6. Matrice rischi consolidata`
Tabella `Rischio | Fase | Severità | Mitigazione | Owner action item`.
Unisci §7 di idea-1 e §6 di idea-2, aggiungi rischi di integrazione
A↔B che emergono solo nel piano fuso (es. shim PowerShell che assume
path Windows e si rompe in Fase B).
#### `## 7. Definizione di "fatto" globale (A+B)`
Una checklist finale `- [ ]` che combina §8 di idea-1 + §8 di idea-2,
deduplicata e coerente con la checklist master in testa.
#### `## 8. Riferimenti`
Link relativi ai file originali letti.
### Regole stilistiche e di contenuto
1. **Lingua**: italiano tecnico, coerente con i piani esistenti.
2. **Tono**: imperativo / dichiarativo, niente "potremmo", "si potrebbe".
Ogni attività è un'azione concreta con deliverable.
3. **Niente stime di tempo / date / sprint**. Solo dipendenze logiche.
4. **Niente nuovi vincoli inventati**: tutto quello che dichiari deve
essere derivabile dai 6 file di riferimento o dallo stato attuale del
repo. Se proponi qualcosa di nuovo, marcalo con `> NOTA NUOVA:` e
spiega perché.
5. **Coerenza con `AGENTS.md`**: ogni step PowerShell residuo deve
rispettare PS 5.1 (no `??`, no `&&`, ecc.); ogni nuova decisione che
supera un errore frequente (#1#12) deve dichiararlo esplicitamente.
6. **Checklist machine-friendly**: usa esclusivamente `- [ ]` (con spazio,
non `- [x]`). Una voce per riga. Niente sub-checklist annidate oltre
il primo livello dentro la stessa sezione.
7. **Tabelle**: usa pipe-tables Markdown standard. Nessun HTML.
8. **Mermaid**: un solo diagramma in §5, sintassi valida.
9. **Lunghezza target**: 6001100 righe Markdown. Sotto le 500 è troppo
sintetico, sopra le 1300 stai duplicando i piani originali.
10. **Auto-verifica finale**: alla fine del documento, prima di §8,
aggiungi una sezione `## 7.1 Auto-check` con queste domande a cui
il documento stesso deve poter rispondere "sì" leggendolo:
- Ogni voce della checklist master compare almeno una volta come
`- [ ]` dentro lo step corrispondente?
- Ogni step A<n>/B<n>/X<n> ha tutte le sottosezioni richieste?
- I 12 errori frequenti di `AGENTS.md` sono stati indirizzati o
dichiarati non applicabili?
- Gli hook §4 sono coerenti con `idea-3-esxi-support.md` §3?
### Cosa NON devi fare
- Non implementare codice. Solo piano.
- Non duplicare integralmente i contenuti di idea-1/idea-2: cita e
riassumi.
- Non introdurre tecnologie nuove non già menzionate (no Ansible,
no Terraform, no Docker host-side, no K8s) salvo `> NOTA NUOVA:`
motivata.
- Non modificare altri file del repo. Solo creazione di
`plans/implementation-plan-A-B.md`.
- Non includere date, settimane, story point, t-shirt sizing.
### Output atteso
Un solo file: `plans/implementation-plan-A-B.md`, conforme alla
struttura sopra, pronto per essere committato. Nessun testo
conversazionale fuori dal file.