diff --git a/docs/HOST-SETUP.md b/docs/HOST-SETUP.md new file mode 100644 index 0000000..6ae73d0 --- /dev/null +++ b/docs/HOST-SETUP.md @@ -0,0 +1,183 @@ +# Host Setup — Local CI/CD + +Procedura di bootstrap della macchina host Windows (i9-10900X, 64 GB RAM, NVMe). +Eseguire **una volta** prima di qualsiasi provisioning del template VM o esecuzione di job CI. + +--- + +## Prerequisiti host + +| Componente | Versione/Note | +| ----------------------- | ----------------------------------------------------------------- | +| OS | Windows 11 (host fisico) o Windows Server 2022+ | +| VMware Workstation | Pro 17.x — necessario per `vmrun.exe` e supporto linked clone | +| Storage | NVMe dedicato (consigliato `F:\` riservato a CI, ≥ 500 GB libero) | +| RAM | ≥ 32 GB (4 VM parallele × 6-8 GB) | +| Rete | VMnet8 (NAT) configurato in VMware Workstation | +| PowerShell | 5.1+ (built-in) o 7.x | +| Privilegi | Account amministrativo per Setup-Host.ps1 | + +Software opzionali installati on-demand dallo script: +- **NSSM** — installazione automatica via Chocolatey se presente; altrimenti install manuale da https://nssm.cc +- **CredentialManager** module — installato automaticamente (scope CurrentUser) + +--- + +## Directory layout + +`Setup-Host.ps1` crea l'intera struttura sotto `$CIRoot` (default `F:\CI\`): + +``` +F:\CI\ +├── BuildVMs\ # cloni temporanei delle VM build (auto-cleanup post-job) +├── Artifacts\ # output build raccolti dall'host +├── Logs\ # log per-job (retention: 30 giorni) +├── Templates\ +│ └── WinBuild\ # VMX template Windows (immutabile dopo snapshot) +├── act_runner\ +│ ├── act_runner.exe # binario runner Gitea +│ ├── config.yaml # config runner (path, label, capacity) +│ └── logs\ # stdout/stderr del servizio +├── Cache\ +│ └── NuGet\ # cache NuGet condivisa via shared folder (TODO §3.1) +├── RunnerWork\ # working dir di act_runner (job staging) +└── ISO\ # ISO di installazione (Windows Server 2025, ecc.) +``` + +--- + +## Procedura — esecuzione + +### Step 1 — Clonare il repository + +```powershell +git clone https://gitea.emulab.it/Simone/Local-CI-CD-System.git N:\Code\Workspace\Local-CI-CD-System +cd N:\Code\Workspace\Local-CI-CD-System +``` + +### Step 2 — Eseguire Setup-Host.ps1 (elevato) + +Apri PowerShell **come Administrator**, poi: + +```powershell +# Esecuzione minimale (crea dirs, archivia credenziali, salta install runner) +.\Setup-Host.ps1 -SkipRunnerInstall + +# Setup completo con registrazione runner +.\Setup-Host.ps1 -GiteaRunnerToken 'abc123token' + +# CI root su drive diverso +.\Setup-Host.ps1 -CIRoot 'D:\CI' -GiteaRunnerToken 'abc123token' +``` + +### Cosa fa Setup-Host.ps1 — passo per passo + +1. **Crea albero directory** sotto `$CIRoot` (vedi layout sopra) +2. **Verifica VMware Workstation** — controlla presenza di `C:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe` +3. **Installa modulo CredentialManager** (scope CurrentUser, se non presente) +4. **Archivia credenziali guest VM** in Windows Credential Manager (target `BuildVMGuest`) + - Se non passa `-GuestPassword`, prompt interattivo (SecureString) + - Se credenziale già esistente, non sovrascrive — istruzioni per update +5. **Copia `runner/config.yaml`** in `$CIRoot\act_runner\config.yaml` (se non esistente) +6. **Installa act_runner come servizio Windows** via NSSM: + - Verifica presenza NSSM in PATH; tenta install via `choco install nssm -y` + - Registra runner con Gitea (`act_runner register --no-interactive --instance ... --token ... --name local-windows-runner --labels windows-build:host,dotnet:host,msbuild:host`) se token fornito + - Crea servizio: `AppDirectory`, `AppParameters daemon --config `, log rotation 10 MB + - Avvia servizio (`SERVICE_AUTO_START`) +7. **Configura SSH alias `gitea-ci`** in `~/.ssh/config` (HostName, Port, User git) + +--- + +## Parametri principali + +| Parametro | Default | Note | +| ---------------------------- | -------------------------------- | ------------------------------------- | +| `-CIRoot` | `F:\CI` | Base directory per tutti i dati CI | +| `-GuestCredentialTarget` | `BuildVMGuest` | Target Credential Manager | +| `-GuestUsername` | `ci_build` | Account build dentro la VM | +| `-GuestPassword` | (prompt) | Mai hard-codare | +| `-ActRunnerExe` | `\act_runner\act_runner.exe` | Path binario runner | +| `-ActRunnerConfigYaml` | `\runner\config.yaml` | Source del config da copiare | +| `-GiteaUrl` | `http://10.10.20.11:3100` | URL Gitea per registrazione | +| `-GiteaRunnerToken` | (vuoto) | Token registrazione (Admin → Runners) | +| `-SkipRunnerInstall` | `$false` | Salta installazione servizio | +| `-SkipSSHConfig` | `$false` | Salta scrittura SSH alias | +| `-GiteaSSHHost` | `10.10.20.11` | Hostname SSH Gitea | +| `-GiteaSSHPort` | `2222` | Porta SSH Gitea | + +--- + +## Post-setup checklist + +Allo `Setup-Host.ps1` exit 0: + +1. **Posiziona binario act_runner** se non già fatto: + - Path: `F:\CI\act_runner\act_runner.exe` + - Download: https://gitea.com/gitea/act_runner/releases/tag/v1.0.2 + +2. **Posiziona ISO Windows Server 2025** in `F:\CI\ISO\` + (es. `26100.1742.240906-0331.ge_release_svc_refresh_SERVER_VOL_x64FRE_en-us.iso`) + +3. **Configura host WinRM client** (necessario per Prepare-TemplateSetup.ps1): + ```powershell + Set-Item WSMan:\localhost\Client\AllowUnencrypted $true -Force + Set-Item WSMan:\localhost\Client\TrustedHosts -Value '192.168.79.*' -Force -Concatenate + ``` + *Note*: Prepare-TemplateSetup.ps1 imposta queste in modo transitorio e le ripristina nel `finally`. + +4. **Provisiona il template VM** — vedi [WINDOWS-TEMPLATE-SETUP.md](WINDOWS-TEMPLATE-SETUP.md) + +5. **Registra chiave SSH con Gitea**: + - Gitea UI → User Settings → SSH Keys → Add Key + - Chiave pubblica: `%USERPROFILE%\.ssh\id_rsa.pub` + +6. **Verifica runner online** in Gitea: + - `/-/admin/runners` + - Status atteso: **Online**, label `windows-build:host,dotnet:host,msbuild:host` + +--- + +## Reference Paths + +| Item | Path / Value | +| ------------------------- | ------------------------------------------------------------ | +| vmrun.exe | `C:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe` | +| act_runner exe | `F:\CI\act_runner\act_runner.exe` (servizio: `act_runner`) | +| act_runner config | `F:\CI\act_runner\config.yaml` | +| act_runner logs | `F:\CI\act_runner\logs\` (stdout.log, stderr.log) | +| Template VMX | `F:\CI\Templates\WinBuild\CI-WinBuild.vmx` | +| Snapshot name | `BaseClean` | +| Clone base dir | `F:\CI\BuildVMs\` | +| Artifact dir | `F:\CI\Artifacts\` | +| Log dir | `F:\CI\Logs\` (retention: 30 giorni) | +| Gitea URL (LAN) | `http://10.10.20.11:3100` | +| Gitea URL (ext) | `https://gitea.emulab.it` | +| Runner name | `local-windows-runner` (ID: 1) | +| Build VM subnet | `192.168.79.0/24` (VMnet8 — NAT, internet access per build) | +| Credential Manager target | `BuildVMGuest` | +| Guest username | `ci_build` | + +--- + +## Maintenance & operational tasks + +Voci di backlog (vedi [TODO.md](../TODO.md)): + +- **§2.2** Cleanup VM orfane schedulato (`scripts/Cleanup-OrphanedBuildVMs.ps1` ogni 6h, `-MaxAgeHours 4`) +- **§2.3** Retention artifact + log (Artifacts > 30 gg, Logs > 30 gg, guard libero < 50 GB) +- **§2.7** Health check runner (query API Gitea ogni 15 min, restart su offline) +- **§4.3** Disk space alert (`F:` < 50 GB → eventcreate/webhook) +- **§1.2** Restringere `TrustedHosts` da `*` a `192.168.79.*` + +--- + +## Troubleshooting + +| Sintomo | Diagnosi / Fix | +| --------------------------------------- | -------------------------------------------------------------------- | +| `vmrun.exe NOT found` | Installa VMware Workstation Pro, riesegui | +| `NSSM not available` | Install manuale da https://nssm.cc, aggiungi a PATH, riesegui | +| Runner registration `exit != 0` | Verifica token Gitea + URL raggiungibile da host | +| Service `act_runner` non parte | Check `F:\CI\act_runner\logs\stderr.log` | +| `New-StoredCredential` fail | Verifica modulo CredentialManager importato + scope corretto | +| Job CI fallisce su `Test-WSMan` | TrustedHosts host non include subnet VM, AllowUnencrypted=$false | diff --git a/docs/LINUX-TEMPLATE-SETUP.md b/docs/LINUX-TEMPLATE-SETUP.md new file mode 100644 index 0000000..94f358d --- /dev/null +++ b/docs/LINUX-TEMPLATE-SETUP.md @@ -0,0 +1,346 @@ +# Linux Template VM Setup — DRAFT / TODO + +> **Status**: bozza. Non implementato. Vedi [TODO.md §6.1](../TODO.md) (P2). +> Documento di pianificazione per estendere il sistema CI con build su Linux, +> in parallelo al template Windows già operativo. + +--- + +## Obiettivo + +Aggiungere supporto a build su Linux per: +- Compilazione cross-platform (es. versione Linux di plugin C/C++, build check con GCC/Clang) +- Test su distro Linux per progetti che lo richiedono +- Build matrix `[windows, linux]` nei workflow Gitea + +Architettura simmetrica al template Windows: VM template → snapshot `BaseClean-Linux` → +linked clone per ogni job → cleanup automatico. + +--- + +## Decisioni di design (da validare) + +### Distro + +| Opzione | Pro | Contro | +| ------------------------ | ------------------------------------------------------------------ | -------------------------------------------- | +| **Ubuntu 24.04 LTS** | Toolchain recente, supporto LTS fino al 2029, cloud-init ottimo | Snap default (lento per CI) | +| Debian 12 | Stabile, leggero, no snap | Toolchain meno recente | +| Rocky Linux 9 / Alma 9 | RHEL-compat, glibc moderno, dnf | Meno comune in workflow CI tipici | + +**Scelta proposta**: Ubuntu 24.04 LTS minimal. Rationale: maggior parità con runner GitHub +ufficiali (`ubuntu-latest` mappa a 22.04/24.04), toolchain recente out-of-box, cloud-init +maturo per provisioning automatico. + +### Transport host → VM + +| Opzione | Pro | Contro | +| ------------------------ | ------------------------------------------------------------------ | -------------------------------------------- | +| **SSH (chiave pubblica)**| Standard Linux, supporto nativo OpenSSH su Win11/Server 2025 | Diversa API rispetto WinRM (PSSession) | +| WinRM su Linux (PSCore) | Codice script unificato | Setup complesso, non standard, fragile | + +**Scelta proposta**: SSH key-based con `ssh.exe` + `scp.exe` (built-in Windows 10+) o +modulo `Posh-SSH`. WinRM su Linux non è production-ready. + +### Layer di astrazione + +Aggiungere `scripts/_Transport.psm1` con interfaccia comune: + +```powershell +# Pseudo-API +function Invoke-RemoteCommand { + param($Target, $ScriptBlock, $Args, [ValidateSet('WinRM','SSH')] $Transport) +} +function Copy-RemoteItem { + param($Source, $Destination, $Target, $Direction, $Transport) +} +function Test-RemoteReady { + param($Target, $TimeoutSec, $Transport) +} +``` + +Implementazioni: +- **WinRM** wrapper: `New-PSSession` + `Invoke-Command` + `Copy-Item -ToSession` +- **SSH** wrapper: `ssh.exe` + `scp.exe` con `-i $keyfile -o StrictHostKeyChecking=accept-new` + +`Invoke-CIJob.ps1` riceve `-GuestOS Windows|Linux` (o auto-detect dal VMX `guestOS`), +sceglie transport. + +--- + +## Specifiche VM proposte + +| Parametro | Valore | +| ----------------------- | --------------------------------------------------------------- | +| OS | Ubuntu Server 24.04 LTS minimal | +| vCPU | 4 | +| RAM | 4096 MB (4 GB) — meno di Windows, footprint Linux più snello | +| Disco | 40 GB thin | +| NIC | VMnet8 (NAT) — internet per `apt` | +| VMX path | `F:\CI\Templates\LinuxBuild\LinuxBuild.vmx` | +| ISO | `ubuntu-24.04.x-live-server-amd64.iso` (in `F:\CI\ISO\`) | +| Snapshot name | `BaseClean-Linux` | + +--- + +## TODO — Implementazione + +### Pre-requisiti host + +- [ ] **OpenSSH client** verificato presente su host (default Win11/Server 2025) + ```powershell + Get-WindowsCapability -Online -Name OpenSSH.Client* + ``` +- [ ] **Genera SSH key dedicata** per CI (no passphrase, separata da chiavi utente): + ```powershell + ssh-keygen -t ed25519 -f F:\CI\keys\ci_linux -N '""' -C 'ci-linux-runner' + ``` +- [ ] **Aggiungere a `Setup-Host.ps1`** uno step opzionale `-GenerateLinuxCIKey` che crea + la chiave e la archivia con permessi corretti (`F:\CI\keys\` con ACL ristretta) + +### Fase A — Crea VM e installa Ubuntu + +- [ ] Crea nuova VM in VMware Workstation (4 vCPU, 4 GB RAM, 40 GB thin) +- [ ] NIC: VMnet8 (NAT) +- [ ] Boot da ISO Ubuntu Server 24.04 LTS minimal +- [ ] Durante install scegli: + - [ ] **Minimal install** (no extra packages) + - [ ] OpenSSH server: **Yes** + - [ ] Username: `ci_build` + - [ ] Hostname: `ci-linux-template` + - [ ] LVM: opzionale (non necessario per template) +- [ ] Annota IP DHCP (`ip a`) + +### Fase B — Provisioning iniziale (manuale, una volta) + +Dentro la VM, come `ci_build`: + +- [ ] **Disabilita swap permanente** (overhead I/O su NVMe condivisa): + ```bash + sudo swapoff -a + sudo sed -i '/ swap / s/^/#/' /etc/fstab + ``` + +- [ ] **Disabilita servizi non necessari** (snap, motd-news, ecc.): + ```bash + sudo systemctl disable --now snapd.service snapd.socket snapd.seeded.service 2>/dev/null + sudo systemctl disable --now motd-news.timer 2>/dev/null + sudo systemctl disable --now apt-daily.timer apt-daily-upgrade.timer + ``` + Rationale: snap e timer apt rallentano boot e mangiano CPU su VM efimere. + +- [ ] **Sudo passwordless** per `ci_build` (parità con WinRM admin senza prompt UAC): + ```bash + echo 'ci_build ALL=(ALL) NOPASSWD:ALL' | sudo tee /etc/sudoers.d/90-ci_build + sudo chmod 440 /etc/sudoers.d/90-ci_build + ``` + +- [ ] **Authorized SSH key** dell'host CI: + ```bash + mkdir -p ~/.ssh && chmod 700 ~/.ssh + # Incolla la chiave pubblica generata su host (F:\CI\keys\ci_linux.pub) + echo 'ssh-ed25519 AAAA... ci-linux-runner' >> ~/.ssh/authorized_keys + chmod 600 ~/.ssh/authorized_keys + ``` + +- [ ] **SSH hardening** per template (ma non così stretto da bloccare CI): + ```bash + sudo sed -i 's/^#*PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config + sudo sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config + sudo systemctl restart sshd + ``` + +### Fase C — Setup-LinuxTemplateVM.sh (script equivalente a Setup-TemplateVM.ps1) + +Da creare: `template/Setup-LinuxTemplateVM.sh` con `set -euo pipefail` + `assert_step()` helper. + +- [ ] **Helper `assert_step`** (throw on fail, log su success): + ```bash + assert_step() { + local step="$1"; local desc="$2"; shift 2 + if "$@" >/dev/null 2>&1; then + echo " [OK] [$step] $desc" + else + echo " [FAIL] [$step] $desc" >&2; exit 1 + fi + } + ``` + +- [ ] **Step 1 — `apt update` + upgrade** + ```bash + sudo DEBIAN_FRONTEND=noninteractive apt-get update -qq + sudo DEBIAN_FRONTEND=noninteractive apt-get upgrade -y -qq + assert_step 'apt' 'no errors' true + ``` + +- [ ] **Step 2 — Toolchain build essentials** + ```bash + sudo apt-get install -y -qq \ + build-essential gcc g++ clang \ + make cmake ninja-build \ + pkg-config autoconf automake libtool \ + git curl wget ca-certificates \ + python3 python3-pip python3-venv \ + unzip zip xz-utils + assert_step 'toolchain' 'gcc presente' command -v gcc + assert_step 'toolchain' 'clang presente' command -v clang + assert_step 'toolchain' 'cmake presente' command -v cmake + assert_step 'toolchain' 'python3 presente' command -v python3 + ``` + +- [ ] **Step 3 — Toolchain extra opzionale** + - [ ] **mingw-w64** se serve cross-compile per Windows: `sudo apt-get install -y mingw-w64` + - [ ] **.NET SDK** (per parità Windows): script `dotnet-install.sh` con channel pinned + - [ ] **Node.js** se workflow lo richiede: nvm o nodesource repo + +- [ ] **Step 4 — CI working directories** + ```bash + sudo mkdir -p /opt/ci/{build,output,scripts,cache} + sudo chown -R ci_build:ci_build /opt/ci + assert_step 'dirs' 'tutte presenti' test -d /opt/ci/build -a -d /opt/ci/output + ``` + +- [ ] **Step 5 — Disabilita auto-update apt** (no surprise reboot/lock): + ```bash + sudo apt-get remove -y unattended-upgrades 2>/dev/null + sudo systemctl mask apt-daily.service apt-daily-upgrade.service + ``` + +- [ ] **Step 6 — Cleanup pre-snapshot** + ```bash + sudo apt-get clean + sudo rm -rf /var/lib/apt/lists/* + sudo journalctl --vacuum-time=1d + sudo rm -rf /tmp/* /var/tmp/* + history -c && cat /dev/null > ~/.bash_history + # Zero free space (riduce dim VMDK dopo compact, opzionale) + # sudo dd if=/dev/zero of=/zero bs=1M; sudo rm /zero + ``` + +- [ ] **Step 7 — Final pre-snapshot validation** + - SSH server abilitato e in ascolto su 22 + - `sudo` passwordless funzionante per `ci_build` + - Tutti i tool elencati (`gcc --version`, `cmake --version`, ecc.) ritornano exit 0 + - `/opt/ci/{build,output,scripts}` esistono con owner corretto + - No leftover `/tmp/*`, history vuota + +### Fase D — Prepare-LinuxTemplateSetup.ps1 (orchestratore host-side) + +Equivalente di `Prepare-TemplateSetup.ps1` per Linux. Usa SSH invece di WinRM. + +- [ ] **Pre-flight host**: + - SSH key esiste (`F:\CI\keys\ci_linux`) + - `ssh.exe` e `scp.exe` raggiungibili + - IP target risponde a `Test-NetConnection -Port 22` + +- [ ] **Copia script in VM**: + ```powershell + scp -i F:\CI\keys\ci_linux -o StrictHostKeyChecking=accept-new ` + template\Setup-LinuxTemplateVM.sh ci_build@${VMIPAddress}:/tmp/ + ``` + +- [ ] **Esegui script in VM**: + ```powershell + ssh -i F:\CI\keys\ci_linux ci_build@${VMIPAddress} ` + "chmod +x /tmp/Setup-LinuxTemplateVM.sh && sudo /tmp/Setup-LinuxTemplateVM.sh" + ``` + Cattura stdout + exit code, parse `[OK]`/`[FAIL]` lines. + +- [ ] **Post-setup remote validation** (parità Windows): query SSH per state checks: + - `systemctl is-active ssh` = active + - `id ci_build` ritorna gruppo + sudo + - `gcc --version`, `cmake --version`, `python3 --version` exit 0 + - `test -d /opt/ci/build` exit 0 + +### Fase E — Snapshot + +- [ ] Solo se Prepare exit 0 → power off VM: + ```bash + sudo shutdown -h now + ``` +- [ ] Take snapshot in VMware Workstation: **`BaseClean-Linux`** (case-sensitive) + +### Fase F — Adattamento script CI + +- [ ] **`scripts/_Transport.psm1`** (nuovo modulo) con interfaccia comune +- [ ] **`scripts/Invoke-CIJob.ps1`** — aggiungere `-GuestOS Windows|Linux`, branching su transport +- [ ] **`scripts/Wait-VMReady.ps1`** — supporto SSH probe (`Test-NetConnection -Port 22` + `ssh -o ConnectTimeout=5 ...`) +- [ ] **`scripts/Invoke-RemoteBuild.ps1`** — branch SSH+scp per Linux +- [ ] **`scripts/Get-BuildArtifacts.ps1`** — `scp` per Linux invece di `Copy-Item -FromSession` +- [ ] **`scripts/New-BuildVM.ps1`** — already OS-agnostic (vmrun clone), verificare che `BaseClean-Linux` sia parametrizzabile +- [ ] **`scripts/Remove-BuildVM.ps1`** — already OS-agnostic + +### Fase G — Workflow Gitea + +- [ ] **Registrare label `linux-build`** sul runner (oppure secondo runner dedicato): + ```yaml + # runner/config.yaml — aggiungere a labels esistenti + labels: + - 'windows-build:host' + - 'linux-build:host' + ``` + +- [ ] **Workflow matrix**: + ```yaml + jobs: + build: + strategy: + matrix: + target: [windows, linux] + runs-on: ${{ matrix.target }}-build + steps: + - uses: actions/checkout@v4 + - run: pwsh -File scripts/Invoke-CIJob.ps1 -GuestOS ${{ matrix.target }} + ``` + +### Fase H — Test e2e + +- [ ] **Smoke test** clone + SSH ready: + ```powershell + .\scripts\New-BuildVM.ps1 -JobId 'linux-smoke' -SnapshotName 'BaseClean-Linux' + .\scripts\Wait-VMReady.ps1 -JobId 'linux-smoke' -Transport SSH + .\scripts\Remove-BuildVM.ps1 -JobId 'linux-smoke' + ``` + +- [ ] **Build reale**: scegliere repo con build Linux nativo (es. `cmake` based) e + eseguire job e2e via `Invoke-CIJob.ps1 -GuestOS Linux` + +- [ ] **Confronto artifact**: build dello stesso repo target Windows vs Linux, + verifica che entrambi vengano raccolti in `F:\CI\Artifacts\\` + +--- + +## Differenze chiave rispetto al template Windows + +| Aspetto | Windows | Linux | +| ----------------------- | -------------------------------------- | ------------------------------------------- | +| Transport | WinRM HTTP/5985 + Basic | SSH/22 + ed25519 key | +| Auth fluid | `ci_build` admin + UAC off + LATFP=1 | `ci_build` + sudoers NOPASSWD | +| Trasferimento file | `Copy-Item -ToSession` | `scp` o `rsync` | +| Esecuzione remota | `Invoke-Command -Session` | `ssh user@host 'cmd'` | +| AV/Firewall disable | Defender + Win Firewall off | ufw inattivo (default), no AV | +| Update package | Windows Update (COM API + sched task) | `apt-get update && upgrade` non-interactive | +| Toolchain | VS Build Tools 2026 + .NET SDK | gcc/g++/clang + cmake (+ .NET SDK opt) | +| Snapshot name | `BaseClean` | `BaseClean-Linux` | +| Footprint VM | 6 GB RAM, 80 GB disk | 4 GB RAM, 40 GB disk | +| Boot time | ~30-60 s post-clone | ~10-20 s post-clone (atteso) | + +--- + +## Backlog correlato (TODO.md) + +- **§6.1 [P2]** Linux Build VM — entry parent +- **§6.4 [P3]** Build matrix `[windows, linux]` nel workflow +- **§6.3 [P3]** Multi-host runner federation (se 4 VM Linux + 4 Windows non bastano) +- **§5.2 [P2]** Modulo `_Transport.psm1` condiviso (prerequisito architetturale) + +--- + +## Domande aperte + +1. **.NET SDK su Linux** — sempre installato o solo se workflow lo richiede? + Decisione: installare **se** workload richiede parità di build. Default off, opt-in via flag. +2. **Image size** — Ubuntu minimal + toolchain ≈ 5 GB. Compact post-snapshot per ridurre footprint VMDK? + Decisione: sì, `dd if=/dev/zero` + `vmware-vdiskmanager -k` (manuale, una tantum). +3. **Pre-warm pool Linux** — boot Linux è veloce, beneficio minore. Decisione: implementare solo se profile dimostra > 30% overhead. +4. **Cloud-init vs script manuale** — cloud-init richiede ISO seed o NoCloud datasource. Più pulito ma overhead di setup. Decisione: script bash diretto (parità con flusso Windows). Cloud-init valutabile in v2. diff --git a/docs/WINDOWS-TEMPLATE-SETUP.md b/docs/WINDOWS-TEMPLATE-SETUP.md new file mode 100644 index 0000000..774f1c3 --- /dev/null +++ b/docs/WINDOWS-TEMPLATE-SETUP.md @@ -0,0 +1,278 @@ +# Windows Template VM Setup + +Procedura di provisioning della template VM **Windows Server 2025** che alimenta i linked +clone usati per ogni job CI. Il template viene creato **una volta**; lo snapshot +`BaseClean` è la base immutabile per tutte le build successive. + +**Prerequisito**: aver eseguito [HOST-SETUP.md](HOST-SETUP.md) (`F:\CI\` esiste, +credenziali archiviate, ISO Windows Server presente). + +--- + +## Architettura: due script + +| Script | Dove gira | Scopo | +| ---------------------------------------- | --------------- | -------------------------------------------------- | +| `template/Prepare-TemplateSetup.ps1` | **Host** | Orchestratore: copia + esegue lo script in VM | +| `template/Setup-TemplateVM.ps1` | **Dentro VM** | Provisioning effettivo (firewall, defender, tool) | + +`Prepare-TemplateSetup.ps1` apre una WinRM session, copia `Setup-TemplateVM.ps1` in +`C:\CI\`, lo esegue, raccoglie l'exit code, valida lo stato del guest. + +--- + +## Specifiche VM + +| Parametro | Valore | +| ----------------------- | --------------------------------------------------------------- | +| OS | Windows Server 2025 Standard/Datacenter (Core o Desktop OK) | +| vCPU | 4 | +| RAM | 6144 MB (6 GB) | +| Disco | 80 GB thin provisioned | +| NIC | **VMnet8 (NAT)** — internet richiesto per WU + installer | +| VMX path | `F:\CI\Templates\WinBuild\WinBuild.vmx` | +| ISO | Windows Server 2025 (da `F:\CI\ISO\`) | + +--- + +## Fase A — Crea la VM e installa Windows + +### A.1 Crea VM in VMware Workstation +- File → New Virtual Machine → Custom +- Hardware: 4 vCPU, 6144 MB, 80 GB thin +- Network: **VMnet8 (NAT)** +- VMX: `F:\CI\Templates\WinBuild\WinBuild.vmx` +- CD/DVD: monta ISO Windows Server 2025 + +### A.2 Installa Windows Server 2025 +- Edition: Standard o Datacenter (Server Core o con Desktop) +- Activation: vedi A.4 (deve essere fatto prima dello snapshot) + +### A.3 Abilita WinRM dentro la VM +Dentro la VM, come Administrator: + +```cmd +winrm quickconfig -q +``` + +```powershell +Enable-PSRemoting -Force -SkipNetworkProfileCheck +Set-Item WSMan:\localhost\Service\Auth\Basic $true -Force +Set-Item WSMan:\localhost\Service\AllowUnencrypted $true -Force +Set-Item WSMan:\localhost\Client\TrustedHosts -Value '*' -Force +``` + +### A.4 Attiva Windows Server 2025 +**IMPORTANTE**: deve essere fatto **prima** dello snapshot. I linked clone ereditano +l'attivazione. + +```cmd +slmgr /dli +``` + +Se non attivato, scegli il metodo: +- **KMS** (server in rete): `slmgr /skms ` → `slmgr /ato` +- **MAK/Retail key**: `slmgr /ipk XXXXX-XXXXX-XXXXX-XXXXX-XXXXX` → `slmgr /ato` +- **KMS pubblico** (lab): vedi https://github.com/robin113x/Windows-Server-Activation/ + +Verifica: `slmgr /xpr` — deve mostrare attivazione permanente o scadenza lontana. + +### A.5 Annota IP DHCP +Dentro la VM: +```cmd +ipconfig +``` +Annota l'IP `192.168.79.x` assegnato dal DHCP di VMnet8. + +--- + +## Fase B — Provisioning automatico dall'host + +Dall'host (PowerShell elevato): + +```powershell +cd N:\Code\Workspace\Local-CI-CD-System\template +# Provisioning completo + registrazione credenziali automatica: +.\Prepare-TemplateSetup.ps1 -VMIPAddress 192.168.79.128 -StoreCredential + +# Se WU già applicato in precedenza: +.\Prepare-TemplateSetup.ps1 -VMIPAddress 192.168.79.128 -SkipWindowsUpdate -StoreCredential +``` + +### Cosa fa `Prepare-TemplateSetup.ps1` — passo per passo + +Ogni step ha validazione `Assert-Step` (throw on failure, `[OK]` su pass): + +1. **Pre-flight**: script dir presente, `Setup-TemplateVM.ps1` presente, IP ottetti 0-255, TCP/5985 reachable +2. **Configura host WinRM client**: `AllowUnencrypted=true`, aggiunge `$VMIPAddress` ai `TrustedHosts` + - Salva valori precedenti, ripristinati nel `finally` (host posture invariata) +3. **Validazione host WinRM**: `AllowUnencrypted=true`, `TrustedHosts` include IP +4. **Test connettività**: `Test-WSMan -Authentication Basic` con credenziali admin +5. **Apre PSSession** verso la VM +6. **Crea `C:\CI`** in guest, valida creazione +7. **Copia `Setup-TemplateVM.ps1`** in `C:\CI\Setup-TemplateVM.ps1`, valida size match +8. **Esegue `Setup-TemplateVM.ps1`** dentro la VM (vedi Fase C) +9. **Gestisce reboot 3010**: se WU richiede reboot, riavvia VM e suggerisce re-run con `-SkipWindowsUpdate` +10. **Post-setup remote validation** (9 check via singola `Invoke-Command`): + - WinRM Running + - User `$BuildUsername` exists + admin + - UAC `EnableLUA=0` + - Firewall tutti profili Off + - .NET SDK installato + - Python `C:\Python\python.exe` presente + - MSBuild path presente + - `C:\CI\{build,output,scripts}` presenti +11. **Restore host WinRM** nel `finally` (sempre eseguito, anche su failure) + +### Parametri principali + +| Parametro | Default | Note | +| --------------------- | -------------- | ----------------------------------------------------------------------------- | +| `-VMIPAddress` | (mandatory) | IP DHCP della VM su VMnet8 | +| `-AdminUsername` | Administrator | Account admin built-in nella VM | +| `-AdminPassword` | (prompt) | Se vuoto, prompt SecureString | +| `-BuildPassword` | (prompt) | Password per `ci_build` — mai hard-codare | +| `-BuildUsername` | ci_build | Account CI dentro la VM | +| `-DotNetSdkVersion` | 10.0 | Channel .NET SDK | +| `-SkipWindowsUpdate` | (off) | Pass-through a Setup-TemplateVM.ps1 | +| `-StoreCredential` | (off) | Registra `BuildUsername`/`BuildPassword` in Windows Credential Manager | +| `-CredentialTarget` | BuildVMGuest | Target Credential Manager (deve corrispondere a quanto usato da Invoke-CIJob) | + +--- + +## Fase C — Cosa fa `Setup-TemplateVM.ps1` (dentro la VM) + +Eseguito automaticamente da Prepare in Fase B. Ordine ottimizzato per ridurre overhead: +tutti i tweak UI/registro sono raggruppati **prima** di Windows Update (operazione lenta). +Ogni step ha validazione `Assert-Step`. + +``` +Step 1 Firewall off (Domain/Private/Public) ── primo, rimuove blocchi +Step 2 Defender + Antimalware off (Set-MpPreference + GPO) + ── prima di WinRM e WU +Step 3 WinRM hardening (Basic, AllowUnencrypted, 2 GB shell mem, 2h timeout) +Step 4 Build user account (ci_build, admin, no expire) ── prima di WU +Step 4b UAC off (EnableLUA=0, LocalAccountTokenFilterPolicy=1) +Step 5 CI dirs: C:\CI\{build, output, scripts} ── C:\CI deve esistere prima di WU +Step 5b Explorer LaunchTo=1 (This PC) +Step 5c CI UX hardening: Server Manager off, DisableCAD, OOBE/telemetry off +Step 6 Windows Update via Scheduled Task SYSTEM ── senza scan Defender, C:\CI presente +Step 6b Windows Update permanently disabled (post-update lockdown) +Step 7 .NET SDK install (channel via dotnet-install.ps1) +Step 8 Python install (3.13.3, all-users, C:\Python) +Step 9 VS Build Tools 2026 via Scheduled Task SYSTEM +Step 10 Toolchain cross-check (dotnet, python, msbuild) +Cleanup cleanmgr /sagerun:1 + DISM /StartComponentCleanup /ResetBase +Final Pre-snapshot gate — 7 check cross-cutting + no installer leftover +``` + +### Razionale dell'ordine + +- **Firewall first** — Windows blocca alcune chiamate WinRM finché non è classificato il + profilo di rete. Off subito → niente race condition durante Step 3. +- **Defender second** — disabilitato prima di WinRM e WU. WU scarica/installa update + senza scan (5-15 min risparmiati). Step 7-9 (download + install ~200-400 MB di + payload .NET/Python/VS) non vengono scansionati. +- **WinRM third** — già non interferito da firewall/defender. Hardening pulito. +- **User + UAC (4/4b)** — operazioni registro rapide; UAC off prima di dir/tool evita + prompt di elevazione; nessun motivo di aspettare WU. +- **CI dirs prima di WU (Step 5)** — il worker WU scrive file temp in `C:\CI\` + (`wu_worker.ps1`, `wu_result.txt`, ecc.); la directory deve esistere prima. +- **UI tweaks (5b/5c)** — tutti i tweak registro raggruppati prima del WU (lento); + scritture veloci, nessuna dipendenza da WU o toolchain. +- **WU sixth** — gira con tutto già off + `C:\CI` presente; condizioni ottimali. +- **WU disable subito dopo** — snapshot cattura WU permanentemente off. +- **Toolchain (7-9) last** — WU completato, nessun scan su payload installer. + +### Validazioni per step (sintesi) + +| Step | Check chiave | +| ---- | ------------------------------------------------------------------------------- | +| 1 | `Get-NetFirewallProfile -Profile Domain/Private/Public → Enabled=False` | +| 2 | 6 GPO key + `Set-MpPreference DisableRealtimeMonitoring=true` + ExclusionPath | +| 3 | WinRM Running+Automatic, AllowUnencrypted=true, Auth/Basic=true, MaxMem≥2048 | +| 4 | User exists, enabled, PasswordNeverExpires, member of Administrators | +| 4b | EnableLUA=0, LocalAccountTokenFilterPolicy=1 | +| 5 | Ogni dir Test-Path -PathType Container | +| 5b | HKCU LaunchTo=1 | +| 5c | ServerManager machine policy DoNotOpenAtLogon=1, HKCU DoNotOpenServerManagerAtLogon=1, DisableCAD=1, OOBE DisablePrivacyExperience=1, AllowTelemetry=0 | +| 6 | ResultCode ∈ {0,2,3}, no reboot required (else exit 3010) | +| 6b | wuauserv StartType=Disabled, NoAutoUpdate=1, DisableWindowsUpdateAccess=1 | +| 7 | `dotnet --version` channel match, machine PATH contiene `C:\dotnet` | +| 8 | `C:\Python\python.exe` esiste, version match esatto, PATH contiene `C:\Python` | +| 9 | MSBuild.exe esiste, esegue, PATH, `\VC\` dir presente | +| 10 | dotnet+python+msbuild tutti risolvibili (throw, non più warn) | +| Final| WinRM, firewall, user+admin, UAC, WU disabled, tutti tool, no leftover installer in `C:\CI` | + +--- + +## Fase D — Snapshot + +**Solo se `Prepare-TemplateSetup.ps1` exit 0** (tutti gli `Assert-Step` passati): + +1. **Spegni la VM**: Start → Shut down (graceful, non hard-stop) +2. **Take snapshot** in VMware Workstation: + - VM → Snapshot → Take Snapshot + - **Nome esatto: `BaseClean`** (case-sensitive, usato da `New-BuildVM.ps1`) +3. **Lascia la VM spenta** — non modificarla mai più dopo lo snapshot +4. **Verifica `runner/config.yaml`**: `GITEA_CI_TEMPLATE_PATH=F:\CI\Templates\WinBuild\CI-WinBuild.vmx` + +--- + +## Fase E — Verifica finale + +Dall'host: + +```powershell +# Test creazione clone manuale +cd N:\Code\Workspace\Local-CI-CD-System\scripts +.\New-BuildVM.ps1 -JobId 'smoke-test' + +# Verifica WinRM raggiungibile sul clone +.\Wait-VMReady.ps1 -JobId 'smoke-test' -TimeoutSec 120 + +# Cleanup +.\Remove-BuildVM.ps1 -JobId 'smoke-test' +``` + +Se i 3 step exit 0 → template pronto per produzione. + +--- + +## Maintenance — refresh semestrale + +Il template va rinfrescato ogni ~180 giorni (KMS lease) o quando serve aggiornare la +toolchain (.NET, VS Build Tools, Python). + +**Procedura**: +1. Power on della template VM (NIC: VMnet8 NAT) +2. Riattiva: `slmgr /ato` +3. Re-run `Prepare-TemplateSetup.ps1` per applicare update toolchain (passare flag opportuni) +4. Shut down + nuovo snapshot **versionato**: `BaseClean_` (TODO §2.5) +5. Aggiorna `GITEA_CI_SNAPSHOT_NAME` env var in `runner/config.yaml` +6. Tieni i 2 snapshot più recenti, cancella vecchi dopo 1 settimana di uso pulito + +--- + +## Troubleshooting + +| Sintomo | Diagnosi / Fix | +| ------------------------------------------- | ----------------------------------------------------------------------- | +| `Cannot reach $VMIPAddress via WinRM` | VM non avviata, WinRM non abilitato, IP errato — verifica con ipconfig | +| `Setup-TemplateVM.ps1 exited with code 3010`| Reboot richiesto post-WU. Re-run con `-SkipWindowsUpdate` | +| `[Defender] Validation failed: GPO key` | Defender riattivato da Windows Update — disabilita via gpedit + reboot | +| `VS Build Tools installation timed out` | Network slow o cache corrotta. Re-run con cache pulita (`C:\ProgramData\Microsoft\VisualStudio\Packages`) | +| `Python version match` fail | `$PythonVersion` ≠ versione installata. Aggiorna param o reinstalla | +| Post-setup `MSBuild not found` | VS install fallita silenziosa. Check `C:\CI\vs_log.txt` | +| Pre-snapshot Final check fail | Stato VM rotto — non prendere snapshot. Re-run o ricomincia da zero | + +--- + +## Backlog correlato (TODO.md) + +- **§1.1 [P0]** Migrare WinRM da HTTP/Basic a HTTPS/5986 con cert self-signed nel template +- **§1.3 [P0]** Pinning hash SHA256 degli installer (Python, dotnet-install.ps1, vs_buildtools.exe) +- **§1.6 [P2]** Documentare threat model per Defender/Firewall/UAC tutti off +- **§2.5 [P1]** Snapshot versionato `BaseClean_` (rollback in caso di refresh rotto) +- **§2.6 [P2]** Backup automatico VMDK template pre-snapshot +- **In-VM Git Clone** — installare Git for Windows + 7-Zip nel template per ottimizzare flusso