Files
local-ci-cd-system/plans/PhaseB-user-checklist.md
T
Simone 0b69155b02 fix(plans): switch B7 burn-in to burnin-dummy repo
nsis-plugin-ns7zip is a real C++ project — wrong for lifecycle testing.
burnin-dummy simulates ~30s work and writes to dist/ with no external
deps. Documents one-time prerequisites (group, keyring, Gitea push).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 23:44:07 +02:00

579 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Fase B — Checklist finale per l'utente
La Fase B è la migrazione del runner CI dall'host Windows attuale a un
nuovo host **Linux Mint LTS** con VMware Workstation Pro Linux.
> **Stato repo**: solo lo step **B5** (unit `systemd` per i task
> periodici) è già committato in `deploy/systemd/` sul branch
> `feature/python-rewrite-and-linux-migration` (commit `50d37b5`).
> Tutti gli altri step (B1B4, B6B8) sono **operazioni hardware**
> sull'host Linux nuovo e devono essere eseguiti dall'utente.
>
> **Avanzamento**: B1 ✅ B2 ✅ B3 ✅ B4 ✅ — in corso: **B5** (timer systemd).
> **Pre-requisito**: la Fase A deve essere `done` (vedi
> `plans/PhaseA-user-checklist.md` Passo 9, merge + tag).
> B1, B2, B3 possono partire in parallelo a A3/A4/A5; B4B8 sono
> sequenziali e iniziano solo dopo "A done".
> **Tempo stimato totale**: 12 giorni di lavoro distribuiti su una
> finestra di 12 settimane (per consentire il burn-in tra B6 e B8).
---
## Passo 1 — Setup host Linux Mint (B1)
**Obiettivo**: hardware target con Linux Mint LTS, VMware Workstation
Pro Linux, layout storage e venv Python pronti.
- [x] Installare Linux Mint LTS sull'hardware target.
- [x] `sudo apt update && sudo apt full-upgrade -y && sudo reboot`
- [x] Scaricare e installare VMware Workstation Pro Linux (bundle
`.bundle` da Broadcow):
```bash
sudo bash VMware-Workstation-Full-*.bundle
vmrun -T ws --version # deve ritornare versione coerente
```
- [x] Smoke test Workstation (UI): creare VM trivia, clone, start,
stop, delete.
- [x] Configurare `vmnet8` NAT range `192.168.79.0/24` (allineato a
Windows) editando `/etc/vmware/vmnet8/dhcpd/dhcpd.conf` o via
`vmware-netcfg`.
- [x] Creare utente di servizio `ci-runner`:
```bash
sudo useradd -r -m -s /bin/bash ci-runner
```
- [x] Eseguire `setup-host-linux.sh` (copre mount disco, layout, Python venv,
clone repo, PowerShell Core):
```bash
# Clonare il repo prima di eseguire lo script
git clone https://gitea.emulab.it/Simone/local-ci-cd-system.git /tmp/local-ci-cd-system
sudo bash /tmp/local-ci-cd-system/setup-host-linux.sh -d /dev/sdf1
```
Lo script:
- Monta `/dev/sdf1` su `/var/lib/ci` via fstab (boot automatico).
Se la partizione ha una sottocartella `ci/`, ne sposta i contenuti
alla root della partizione prima del mount definitivo.
- Crea il layout `build-vms/`, `artifacts/`, `templates/`, `keys/`,
`logs/`, `runner/` con permessi e ACL corretti.
- Installa Python 3.11, crea `/opt/ci/venv`, clona il repo in
`/opt/ci/local-ci-cd-system` e installa il package.
- Installa PowerShell Core.
Flag utili se alcune dipendenze sono già presenti:
```bash
sudo bash setup-host-linux.sh -d /dev/sdf1 --skip-pwsh --skip-clone
```
Atteso a fine script: `ci_orchestrator --help` lista gli 11 sub-comandi.
---
## Passo 2 — Trasferimento template VM (B2)
**Obiettivo**: copiare i template VMware da `F:\CI\Templates\` su
`/var/lib/ci/templates/` mantenendo gli snapshot.
> ⚠️ AGENTS.md errore #9: i template devono essere **fully
> powered-off** prima della copia. Verificare assenza di `*.vmem` /
> `*.vmsn` di runtime.
- [x] Sull'host Windows:
```powershell
Get-ChildItem 'F:\CI\Templates' -Recurse -Include *.vmem,*.vmsn
# atteso: nessun risultato
```
- [x] Sull'host Linux, eseguire rsync via SSH dall'host Windows
(richiede OpenSSH Server attivo su Windows o, in alternativa,
`scp -r` lanciato da Windows verso Linux):
```bash
sudo -u ci-runner rsync -av --progress \
user@windows-host:/cygdrive/f/CI/Templates/ \
/var/lib/ci/templates/
```
- [x] Validare integrità snapshot:
```bash
find /var/lib/ci/templates -name '*.vmx' -exec vmrun -T ws listSnapshots {} \;
```
Atteso: `BaseClean` su `WinBuild2025.vmx` e `WinBuild2022.vmx`,
`BaseClean-Linux` su `LinuxBuild2404.vmx`.
- [x] Aprire ciascun `.vmx` su Workstation Linux per registrarli;
al prompt rispondere "**I copied it**".
- [x] **Prerequisito smoke test Windows**: il keyring file-based deve essere
configurato e la credenziale `BuildVMGuest` deve essere presente
**prima** di eseguire `wait-ready` (che prova WinRM). Questo è
formalmente il Passo 3 (B3), ma il minimo indispensabile per lo
smoke test è:
```bash
# Configurare il backend file keyring per ci-runner (una sola volta)
sudo -u ci-runner mkdir -p ~/.local/share/python_keyring
sudo -u ci-runner bash -c "cat > ~/.local/share/python_keyring/keyringrc.cfg << 'EOF'
[backend]
default-keyring=keyrings.alt.file.PlaintextKeyring
EOF"
sudo -u ci-runner /opt/ci/venv/bin/pip install keyrings.alt -q
# Salvare la credenziale guest Windows
sudo -u ci-runner /opt/ci/venv/bin/python /opt/ci/local-ci-cd-system/tools/ci-cred-set.py BuildVMGuest "WINBUILD-2025\\ci_build"
# Verificare
sudo -u ci-runner /opt/ci/venv/bin/python /opt/ci/local-ci-cd-system/tools/ci-cred-check.py BuildVMGuest
# atteso: OK (file) username='WINBUILD-2025\ci_build' password=****
```
> Il setup completo delle credenziali (chiavi SSH, GiteaPAT,
> PoC headless systemd) è nel Passo 3.
- [x] Smoke test pipeline VM:
```bash
# Windows smoke
CLONE_VMX=$(sudo -u ci-runner /opt/ci/venv/bin/python -m ci_orchestrator vm new \
--template /var/lib/ci/templates/WinBuild2025/WinBuild2025.vmx \
--snapshot BaseClean \
--clone-base-dir /var/lib/ci/build-vms \
--job-id smoke-b2-win \
--start)
echo "Clone VMX: $CLONE_VMX"
sudo -u ci-runner /opt/ci/venv/bin/python -m ci_orchestrator wait-ready \
--vmx "$CLONE_VMX" --guest-os windows --timeout 300
sudo -u ci-runner /opt/ci/venv/bin/python -m ci_orchestrator vm remove \
--vmx "$CLONE_VMX" --force
```
Ripetere per `LinuxBuild2404` (snapshot `BaseClean-Linux`,
`--guest-os linux`, `--job-id smoke-b2-linux`).
---
## Passo 3 — Trasferimento credenziali e chiavi (B3)
**Obiettivo**: chiavi SSH e credenziali guest disponibili al user
`ci-runner` headless.
- [x] Chiavi SSH guest Linux già presenti in `/var/lib/ci/keys/` (copiate
insieme ai template). Correggere solo i permessi:
```bash
sudo chown ci-runner:ci-runner /var/lib/ci/keys/ci_linux /var/lib/ci/keys/ci_linux.pub
sudo chmod 600 /var/lib/ci/keys/ci_linux
sudo chmod 644 /var/lib/ci/keys/ci_linux.pub
# verifica
ls -la /var/lib/ci/keys/
```
- [x] Credenziale guest Windows `BuildVMGuest` già salvata nel
prerequisito del Passo 2 con `keyrings.alt.file.PlaintextKeyring`
(schema a due entry — `secret-tool` scartato perché non funziona
headless senza D-Bus session).
Per riscrivere la credenziale se necessario:
```bash
sudo -u ci-runner /opt/ci/venv/bin/python /opt/ci/local-ci-cd-system/tools/ci-cred-set.py BuildVMGuest "WINBUILD-2025\\ci_build"
sudo -u ci-runner /opt/ci/venv/bin/python /opt/ci/local-ci-cd-system/tools/ci-cred-check.py BuildVMGuest
```
- [x] Re-store Gitea PAT con lo stesso schema:
```bash
sudo -u ci-runner /opt/ci/venv/bin/python /opt/ci/local-ci-cd-system/tools/ci-cred-set.py GiteaPAT ci-runner-linux
sudo -u ci-runner /opt/ci/venv/bin/python /opt/ci/local-ci-cd-system/tools/ci-cred-check.py GiteaPAT
```
- [x] **PoC headless**: il backend `PlaintextKeyring` non usa D-Bus —
funziona già da `sudo -u ci-runner` e funzionerà da
`act-runner.service`. Verificato durante smoke test Passo 2.
---
## Passo 4 — Setup act_runner come systemd service (B4)
**Obiettivo**: act_runner Linux registrato verso Gitea, gestito da
systemd.
- [x] Scaricare il binario act_runner Linux ≥ v1.0.4:
```bash
sudo mkdir -p /opt/ci/act_runner
sudo wget -O /opt/ci/act_runner/act_runner \
https://gitea.com/gitea/runner/releases/download/v1.0.4/gitea-runner-1.0.4-linux-amd64
sudo chmod +x /opt/ci/act_runner/act_runner
sudo chown -R ci-runner:ci-runner /opt/ci/act_runner
```
- [x] Generare token registrazione su Gitea (Admin → Runners → Create new runner).
- [x] Registrare il runner (sostituire `<TOKEN>`):
```bash
sudo -u ci-runner mkdir -p /var/lib/ci/runner
cd /var/lib/ci/runner
sudo -u ci-runner /opt/ci/act_runner/act_runner register \
--no-interactive \
--instance https://gitea.emulab.it \
--token <TOKEN> \
--name ci-linux \
--labels windows-build:host,linux-build:host
```
> ⚠️ Lasciare il runner in stato **paused** lato Gitea UI fino al
> cutover (Passo 6), per non intercettare job di produzione.
- [x] Creare `/etc/systemd/system/act-runner.service`:
```ini
[Unit]
Description=Gitea Act Runner
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=ci-runner
WorkingDirectory=/var/lib/ci/runner
Environment="PYTHONIOENCODING=utf-8"
Environment="CI_ROOT=/var/lib/ci"
Environment="CI_TEMPLATES=/var/lib/ci/templates"
Environment="CI_BUILD_VMS=/var/lib/ci/build-vms"
Environment="CI_ARTIFACTS=/var/lib/ci/artifacts"
Environment="CI_KEYS=/etc/ci/keys"
ExecStart=/opt/ci/act_runner/act_runner daemon --config /var/lib/ci/runner/config.yaml
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
```
- [x] Creare `/etc/ci/environment` (letto dalle unit dei task periodici
di B5; vedi `deploy/systemd/README.md`):
```bash
sudo tee /etc/ci/environment <<'EOF'
PYTHONIOENCODING=utf-8
CI_ROOT=/var/lib/ci
CI_TEMPLATES=/var/lib/ci/templates
CI_BUILD_VMS=/var/lib/ci/build-vms
CI_ARTIFACTS=/var/lib/ci/artifacts
CI_KEYS=/etc/ci/keys
EOF
sudo chmod 644 /etc/ci/environment
```
- [x] Abilitare e avviare:
```bash
sudo systemctl daemon-reload
sudo systemctl enable --now act-runner.service
sudo systemctl status act-runner
sudo journalctl -u act-runner -f # Ctrl+C dopo conferma OK
```
- [x] Generare e configurare `config.yaml` del runner:
```bash
# 1. Generare il file di default (conserva la registrazione in .runner)
sudo -u ci-runner /opt/ci/act_runner/act_runner generate-config \
| sudo -u ci-runner tee /var/lib/ci/runner/config.yaml > /dev/null
# 2. Sostituire i label Docker generati di default con quelli host corretti
# (generate-config scrive label ubuntu-*:docker:// che fanno cercare Docker)
sudo -u ci-runner sed -i \
-e 's| - "ubuntu-latest:docker://.*"| - "windows-build:host"|' \
-e '/ubuntu-24.04:docker:\/\//d' \
-e '/ubuntu-22.04:docker:\/\//d' \
/var/lib/ci/runner/config.yaml
sudo -u ci-runner sed -i \
'/ - "windows-build:host"/a\ - "linux-build:host"' \
/var/lib/ci/runner/config.yaml
# 3. Sostituire le variabili di esempio nella sezione runner.envs con le variabili CI
# (generate-config scrive due righe A_TEST_ENV_NAME_* come placeholder)
sudo -u ci-runner sed -i \
-e 's/ A_TEST_ENV_NAME_1: a_test_env_value_1/ CI_PYTHON_LAUNCHER: \/opt\/ci\/venv\/bin\/python/' \
-e 's/ A_TEST_ENV_NAME_2: a_test_env_value_2/ GITEA_CI_TEMPLATE_PATH: \/var\/lib\/ci\/templates\/WinBuild2025\/WinBuild2025.vmx\n GITEA_CI_LINUX_TEMPLATE_PATH: \/var\/lib\/ci\/templates\/LinuxBuild2404\/LinuxBuild2404.vmx/' \
/var/lib/ci/runner/config.yaml
# 4. Verificare
grep -A3 'labels:' /var/lib/ci/runner/config.yaml | head -5
grep -A4 'envs:' /var/lib/ci/runner/config.yaml
```
Atteso:
```yaml
labels:
- "windows-build:host"
- "linux-build:host"
envs:
CI_PYTHON_LAUNCHER: /opt/ci/venv/bin/python
GITEA_CI_TEMPLATE_PATH: /var/lib/ci/templates/WinBuild2025/WinBuild2025.vmx
GITEA_CI_LINUX_TEMPLATE_PATH: /var/lib/ci/templates/LinuxBuild2404/LinuxBuild2404.vmx
```
> Le variabili di sistema (`CI_ROOT`, `PYTHONIOENCODING`, ecc.) restano
> nel service unit. `runner.envs` è per la configurazione specifica
> del runner CI (path template, launcher Python).
```bash
# 5. Ricaricare e riavviare
sudo systemctl daemon-reload
sudo systemctl restart act-runner.service
sudo journalctl -u act-runner -n 20 # confermare avvio OK — nessun errore Docker
```
- [x] Smoke job: triggerare manualmente `self-test.yml` selezionando
il runner Linux (mettere temporaneamente in pausa quello Windows
o usare label distinte). Atteso: PASS.
> ⚠️ Prerequisito: le variabili `GITEA_CI_TEMPLATE_PATH` e
> `GITEA_CI_LINUX_TEMPLATE_PATH` devono essere presenti nel service
> unit **prima** di avviare lo smoke test, altrimenti il job fallisce
> con "Got unexpected extra argument".
---
## Passo 5 — Installare i timer systemd (B5)
**Obiettivo**: attivare le coppie `.service`+`.timer` già committate
in `deploy/systemd/` per replicare i task periodici di
`Register-CIScheduledTasks.ps1`.
> **Nota**: `ci-retention-policy` e `ci-backup-template` sono stati portati
> in Python puro (`retention run` / `template backup`) — **pwsh non è
> richiesto**. I backup template vengono compressi con `7z -mx=1` in
> `/var/lib/ci/backups/`. `ci-watch-runner-health` usa `--service-name
> act-runner` (nome Linux, non Windows).
> **Prerequisiti creati sull'host**:
> ```bash
> sudo mkdir -p /var/log/ci /var/lib/ci/backups
> sudo chown ci-runner:ci-runner /var/log/ci
> ```
- [x] Copiare le unit:
```bash
cd /opt/ci/local-ci-cd-system
sudo cp deploy/systemd/ci-*.service deploy/systemd/ci-*.timer /etc/systemd/system/
sudo systemctl daemon-reload
```
- [x] Abilitare e avviare tutti i timer:
```bash
for t in ci-cleanup-orphans ci-retention-policy \
ci-watch-disk-space ci-watch-runner-health \
ci-backup-template; do
sudo systemctl enable --now "${t}.timer"
done
```
- [x] Verificare lo schedule:
```bash
systemctl list-timers --all 'ci-*'
```
Atteso: 5 timer in stato `active`. ✅ PASS
- [x] Trigger manuale di ciascun service per validare l'esecuzione
one-shot:
```bash
for s in ci-cleanup-orphans ci-retention-policy \
ci-watch-disk-space ci-watch-runner-health \
ci-backup-template; do
sudo systemctl start "${s}.service"
sudo systemctl status "${s}.service" --no-pager
done
```
✅ Tutti e 5 PASS.
- [x] Per maggiori dettagli (mapping Windows→Linux, troubleshooting,
rollback) vedi `deploy/systemd/README.md`.
---
## Passo 6 — Cutover (B6)
**Obiettivo**: passare all'uso normale di Linux come OS primario di
produzione CI. Il cutover è un semplice riavvio — i due sistemi non
coesistono mai.
> ⚠️ Eseguire solo dopo che i Passi 15 sono tutti `[x]` PASS.
- [x] Verificare nessun job in coda lato Gitea (Admin → Actions → Tasks).
- [x] Attendere la fine di eventuali job in esecuzione su Windows.
- [x] Sbloccare il runner Linux su Gitea UI (rimuovere `paused`).
- [x] Riavviare la macchina in Linux.
- [x] Verificare runner Linux online su Gitea UI e act-runner attivo:
```bash
sudo systemctl status act-runner
sudo journalctl -u act-runner -n 20
```
- [x] Smoke trigger:
- [x] `self-test.yml` → PASS dal runner Linux
- [x] `build-ns7zip.yml` matrix Win+Linux → PASS dal runner Linux
- [x] Monitorare per ≥30 min:
```bash
sudo journalctl -u act-runner -f
```
> **Rollback**: riavviare in Windows — il runner Windows riprende
> automaticamente. Mettere in pausa il runner Linux su Gitea UI,
> documentare l'incidente in `TODO.md`.
---
## Passo 7 — Capacity burn-in (B7)
**Obiettivo**: validare il carico target con tempi paragonabili al
baseline Windows (entro ±20%). I due burn-in sono sequenziali
(dual-boot: un OS alla volta).
> **Prerequisiti una-tantum** (come `simone`):
> ```bash
> sudo usermod -a -G ci-runner simone
> sudo chmod g+w /var/lib/ci/artifacts/ /var/lib/ci/build-vms/
> mkdir -p ~/.local/share/python_keyring
> sudo cp /home/ci-runner/.local/share/python_keyring/keyring_pass.cfg \
> ~/.local/share/python_keyring/
> sudo chown simone: ~/.local/share/python_keyring/keyring_pass.cfg
> chmod 600 ~/.local/share/python_keyring/keyring_pass.cfg
> ```
> Il repo `burnin-dummy` deve esistere in Gitea (`Simone/burnin-dummy`).
> Push iniziale (una-tantum):
> ```bash
> cd /opt/ci/local-ci-cd-system/gitea/burnin-dummy
> git init && git add . && git commit -m "initial"
> git remote add origin http://10.10.20.11:3100/Simone/burnin-dummy.git
> git push -u origin main
> ```
- [ ] **Avviato in Linux** — burn-in 4 job concorrenti × 10 round su
`WinBuild2025`:
```bash
pwsh /opt/ci/local-ci-cd-system/scripts/Test-CapacityBurnIn.ps1 \
-Parallelism 4 -Rounds 10 \
-TemplatePath /var/lib/ci/templates/WinBuild2025/WinBuild2025.vmx \
-RepoUrl 'http://10.10.20.11:3100/Simone/burnin-dummy.git' \
-CloneBaseDir /var/lib/ci/build-vms/ \
-ArtifactBaseDir /var/lib/ci/artifacts/ \
-VmrunPath /usr/bin/vmrun \
-BuildCommand 'powershell -ExecutionPolicy Bypass -File .\build.ps1'
```
- [ ] **Avviato in Linux** — burn-in 4 × 10 su `LinuxBuild2404`:
```bash
pwsh /opt/ci/local-ci-cd-system/scripts/Test-CapacityBurnIn.ps1 \
-Parallelism 4 -Rounds 10 \
-TemplatePath /var/lib/ci/templates/LinuxBuild2404/LinuxBuild2404.vmx \
-RepoUrl 'http://10.10.20.11:3100/Simone/burnin-dummy.git' \
-CloneBaseDir /var/lib/ci/build-vms/ \
-ArtifactBaseDir /var/lib/ci/artifacts/ \
-VmrunPath /usr/bin/vmrun \
-GuestOS Linux \
-BuildCommand 'bash build.sh'
```
- [ ] 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.