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:
@@ -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: 60–180 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 30–60 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 60–120 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.
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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`
|
||||
@@ -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`
|
||||
@@ -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
|
||||
@@ -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`)
|
||||
@@ -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`)
|
||||
@@ -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
|
||||
@@ -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**: 1–2 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 3–7 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**: 1–2 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**: 30–60 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 (B1–B4, B6–B8) 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; B4–B8 sono
|
||||
> sequenziali e iniziano solo dopo "A done".
|
||||
|
||||
> **Tempo stimato totale**: 1–2 giorni di lavoro distribuiti su una
|
||||
> finestra di 1–2 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 1–5 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 A1–A5, 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 B1–B8, 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 (5–15 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**: 600–1100 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.
|
||||
Reference in New Issue
Block a user