# Linux Template VM Setup Piano di implementazione > **Status**: implementato (2026-05-11) — Fasi A-E complete. Fase F (test e2e) richiede provisioning fisico VM. Vedi [TODO.md §6.1](../TODO.md) (P2). > Distro scelta: **Ubuntu 24.04 LTS** (cloud image, cloud-init). Approccio simmetrico a Windows. --- ## Architettura: tre script + modulo transport | Script / Modulo | Dove gira | Ruolo | | ------------------------------------- | ---------------- | --------------------------------------------------------------------------- | | `template/Deploy-LinuxBuild2404.ps1` | **Host** (PS) | Crea VM da cloud image VMDK, genera seed ISO cloud-init, avvia, aspetta SSH | | `template/Prepare-LinuxBuild2404.ps1` | **Host** (PS) | Copia + esegue setup via SSH, valida stato, prende snapshot | | `template/Install-CIToolchain-Linux2404.sh` | **Guest** (Bash) | Installa toolchain CI, dirs, hardening, gate finale | | `scripts/_Transport.psm1` | **Host** (PS) | Funzioni SSH-only per il branch Linux (WinRM esistente invariato per ora) | Perche tre script separati (non tutto-in-uno): cloud-init copre solo il bootstrap OS (utente, SSH key, pacchetti base). Il toolchain CI e le validazioni per step sono piu leggibili e manutenibili in un secondo script dedicato identico alla divisione Deploy/Setup/Prepare di Windows. --- ## Specifiche VM | Parametro | Valore | | ------------- | ---------------------------------------------------- | | OS | Ubuntu 24.04 LTS (cloud image, non ISO installer) | | Immagine base | `noble-server-cloudimg-amd64.vmdk` | | Download | `https://cloud-images.ubuntu.com/noble/current/` | | vCPU | 4 | | RAM | 4096 MB | | Disco | 40 GB thin (resize del cloud VMDK) | | NIC | VMnet8 (NAT) vmxnet3 diretto (driver nativo kernel) | | VMX path | `F:\CI\Templates\LinuxBuild2404\LinuxBuild2404.vmx` | | Snapshot name | `BaseClean-Linux` | Differenza chiave da Windows: l'immagine di partenza e gia installata (cloud VMDK, ~600 MB). Nessuna fase di installazione OS cloud-init provvede al bootstrap al primo boot in 30-60 secondi. La NIC non richiede lo swap e1000e vmxnet3 perche il kernel Linux ha il driver vmxnet3 nativo. --- ## Prerequisiti host - [x] **SSH key CI dedicata** generare una volta sola (no passphrase): ```powershell New-Item -ItemType Directory -Force F:\CI\keys | Out-Null ssh-keygen -t ed25519 -f F:\CI\keys\ci_linux -N '""' -C 'ci-linux-runner' # Risultato: F:\CI\keys\ci_linux (privata) + F:\CI\keys\ci_linux.pub (pubblica) ``` - [x] **OpenSSH client** verificato presente sull'host (default su Windows 11): ```powershell Get-WindowsCapability -Online -Name OpenSSH.Client* | Select-Object State # Atteso: State = Installed ``` - [x] **`F:\CI\Templates\LinuxBuild2404\`** creata (Setup-Host.ps1 o manuale) - [x] **`F:\CI\ISO\`** creata (il VMDK viene scaricato automaticamente da Deploy se mancante) --- ## Fase A Deploy-LinuxBuild2404.ps1 Script host-side. Produce una VM pronta con cloud-init completato e SSH raggiungibile. Non richiede installazione OS parte dal cloud VMDK pre-built. - [x] **Step 1 Validate prerequisites** - Parametro `-VMwareWorkstationDir` (default `C:\Program Files (x86)\VMware\VMware Workstation`) — stesso pattern di Deploy-WinBuild2025.ps1 - `vmrun.exe` presente in `$VMwareWorkstationDir` - `vmware-vdiskmanager.exe` presente in `$VMwareWorkstationDir` (`$vdiskMgr = Join-Path $VMwareWorkstationDir 'vmware-vdiskmanager.exe'`) - SSH key `F:\CI\keys\ci_linux.pub` presente - Cartella destinazione VMX non gia occupata (o `-Force` per sovrascrivere) - (il VMDK sorgente viene gestito in Step 1b — nessun prerequisito manuale) - [x] **Step 1b Download cloud VMDK se assente** (`F:\CI\ISO\` come cache locale) ```powershell $VmdkUrl = 'https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.vmdk' $VmdkPath = 'F:\CI\ISO\noble-server-cloudimg-amd64.vmdk' if (-not (Test-Path $VmdkPath)) { Write-Host "[Deploy] VMDK non trovato, avvio download..." New-Item -ItemType Directory -Force F:\CI\ISO | Out-Null $tmp = "$VmdkPath.part" try { Invoke-WebRequest -Uri $VmdkUrl -OutFile $tmp -UseBasicParsing Move-Item $tmp $VmdkPath Write-Host "[Deploy] Download completato: $VmdkPath" } catch { Remove-Item $tmp -ErrorAction SilentlyContinue throw "[Deploy] Download VMDK fallito: $_" } } else { Write-Host "[Deploy] VMDK trovato in cache: $VmdkPath" } ``` - Usa `Invoke-WebRequest` (nativo PowerShell, no dipendenze esterne) - Scrittura atomica: file `.part` durante download, rinomina solo a completamento - In caso di errore: il file `.part` viene rimosso, l'eccezione termina lo script - Parametro opzionale `-VmdkUrl` per sovrascrivere l'URL (es. mirror locale) - [x] **Step 2 Genera cloud-init user-data + meta-data** (inline, no template esterno) - `meta-data`: `instance-id: linuxbuild-001`, `local-hostname: ci-linux-template` - `user-data` (YAML): - `hostname: ci-linux-template` - `users`: utente `ci_build`, shell `/bin/bash`, `sudo: ALL=(ALL) NOPASSWD:ALL`, `ssh_authorized_keys` con contenuto di `ci_linux.pub` - Nessun `packages`, nessun `package_update`, nessun `runcmd`: tutto il provisioning (apt, toolchain, SSH hardening, swap) e' delegato a `Prepare-LinuxBuild2404.ps1` via `Install-CIToolchain-Linux2404.sh` - Nessun `power_state`: il reboot non e' necessario, cloud-init completa tutto al primo boot - [x] **Step 3 Costruisce seed ISO nocloud** via IMAPI2FS COM - Due file: `user-data` + `meta-data` (volume label: `cidata`) - Stesso codice COM gia usato in Deploy-WinBuild2025.ps1 Step 3 - Output: `F:\CI\Templates\LinuxBuild2404\cloud-init-seed.iso` - [x] **Step 4 Converti e ridimensiona cloud VMDK** - Il cloud VMDK e' in formato **streamOptimized** (distribuzione), non direttamente resizeable. - Converti a **monolithicSparse** (tipo 0) contestualmente alla copia: `& $vdiskMgr -r noble-server-cloudimg-amd64.vmdk -t 0 LinuxBuild2404.vmdk` - Ridimensiona a 40 GB: `& $vdiskMgr -x 40GB LinuxBuild2404.vmdk` (stessa variabile `$vdiskMgr` di Deploy-WinBuild2025.ps1, valorizzata in Step 1) - Il resize avviene PRIMA del primo boot: l'immagine Ubuntu 24.04 ha i moduli cloud-init `growpart` + `resizefs` abilitati di default — espandono automaticamente la partizione root e il filesystem durante il primo avvio, senza `runcmd` aggiuntivi - [x] **Step 5 Genera .vmx** - `guestOS = "ubuntu-64"`, `firmware = "efi"` (Ubuntu 24.04 cloud image usa GPT con partizione EFI; il boot sotto BIOS legacy fallirebbe) - NIC: `vmxnet3`, VMnet8 - Disco: `LinuxBuild2404.vmdk` (scsi0:0) - CD-ROM secondario: `cloud-init-seed.iso` (connesso) - Nessun USB controller, nessuna sound card - `memsize = 4096`, `numvcpus = 4` - [x] **Step 6 Power on** (`vmrun start nogui`) - [x] **Step 7 Polling SSH port 22** fino a ready - `Test-NetConnection -ComputerName $IP -Port 22` ogni 10 secondi - Timeout: 5 minuti (cloud-init + reboot finale) - IP rilevato via `vmrun getGuestIPAddress` > open-vm-tools e pre-installato nell'Ubuntu 24.04 cloud image. > `vmrun getGuestIPAddress` funziona senza installazione aggiuntiva. - [x] **Step 8 Verifica cloud-init completato** ```powershell ssh -i F:\CI\keys\ci_linux -o StrictHostKeyChecking=accept-new ` -o ConnectTimeout=5 ci_build@$IP ` "test -f /var/lib/cloud/instance/boot-finished && echo OK" # Atteso: OK ``` - [x] **Step 9 Rimuove seed ISO dal VMX** - Setta `ide1:0.present = "FALSE"` nel VMX - Evita che linked clone ricevano il seed ISO e ri-eseguano cloud-init - [x] **Step 10 Print summary** (VMX path, IP, SSH fingerprint) --- ## Fase B Prepare-LinuxBuild2404.ps1 Script host-side. Copia il setup script, lo esegue via SSH, valida stato, prende snapshot. Equivalente di `Prepare-WinBuild2025.ps1` con SSH al posto di WinRM. - [x] **Step 1 Pre-flight** - VMX presente; se VM spenta, avvia con `vmrun start nogui` - SSH key `F:\CI\keys\ci_linux` presente - `ssh.exe` e `scp.exe` nel PATH host - TCP/22 raggiungibile (`Test-NetConnection`) - [x] **Step 2 Auto-detect IP** via `vmrun getGuestIPAddress $VMXPath` - Se `-VMIPAddress` e fornito esplicitamente, salta il rilevamento automatico - [x] **Step 3 Aggiungi host key a known_hosts** ```powershell ssh -i F:\CI\keys\ci_linux -o StrictHostKeyChecking=accept-new ` ci_build@$IP "echo connected" | Out-Null ``` Solo al primo run; idempotente se gia presente. - [x] **Step 4 Copia Install-CIToolchain-Linux2404.sh in VM** ```powershell scp -i F:\CI\keys\ci_linux ` "$PSScriptRoot\Install-CIToolchain-Linux2404.sh" ` "ci_build@${IP}:/tmp/Install-CIToolchain-Linux2404.sh" ``` Valida: confronta size locale vs remota via `ssh ... stat -c %s /tmp/...` - [x] **Step 5 Esegui setup script** nel guest ```powershell ssh -i F:\CI\keys\ci_linux ci_build@$IP ` "chmod +x /tmp/Install-CIToolchain-Linux2404.sh && sudo /tmp/Install-CIToolchain-Linux2404.sh" # Cattura exit code; != 0 => abort con errore leggibile ``` Flag opzionali passabili: `--skip-update`, `--dotnet`, `--mingw` - [x] **Step 6 Post-setup remote validation** (batch via singola sessione SSH) - `systemctl is-active ssh` = `active` - `id ci_build` contiene `sudo` - `sudo -n true` exit 0 (passwordless sudo) - `which gcc g++ clang cmake python3 git 7z` tutti trovati - `test -d /opt/ci/build -a -d /opt/ci/output -a -d /opt/ci/scripts` exit 0 - `swapon --show` output vuoto (swap disabilitato) - `grep -q "^PasswordAuthentication no" /etc/ssh/sshd_config` - [x] **Step 7 Shutdown VM** ```powershell & $vmrun stop $VMXPath soft # Polling vmrun list fino a VM non piu presente ``` - [x] **Step 8 Snapshot BaseClean-Linux** ```powershell & $vmrun snapshot $VMXPath 'BaseClean-Linux' ``` - [x] **Final Print summary** (VMX, snapshot name, toolchain versions rilevate) --- ## Fase C Install-CIToolchain-Linux2404.sh Script guest-side (Bash). Installazione toolchain CI e hardening. Eseguito da Prepare via SSH come `ci_build` con sudo. Header: `#!/usr/bin/env bash` + `set -euo pipefail` Helper `assert_step()`: stampa `[OK] [Step N] desc` su successo, `exit 1` su fallimento. - [x] **Step 1 apt update + upgrade** (saltabile con `--skip-update`) ```bash sudo DEBIAN_FRONTEND=noninteractive apt-get update -qq sudo DEBIAN_FRONTEND=noninteractive apt-get upgrade -y -qq ``` - [x] **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 ``` - assert: `command -v gcc`, `command -v clang`, `command -v cmake` - [x] **Step 3 Python** ```bash sudo apt-get install -y -qq python3 python3-pip python3-venv sudo ln -sf /usr/bin/python3 /usr/local/bin/python ``` - assert: `python3 --version`, `pip3 --version` - [x] **Step 4 Tier-1 Toolchain** (speculare a §6.6 Windows: Git + 7-Zip) ```bash sudo apt-get install -y -qq git p7zip-full curl wget ca-certificates ``` - assert: `command -v git`, `command -v 7z` - [x] **Step 4b .NET SDK** (opzionale, flag `--dotnet`) ```bash curl -fsSL https://dotnet.microsoft.com/download/dotnet/scripts/v1/dotnet-install.sh \ | sudo bash -s -- --channel 10.0 --install-dir /usr/local/dotnet sudo ln -sf /usr/local/dotnet/dotnet /usr/local/bin/dotnet ``` - assert: `dotnet --version` - [x] **Step 4c mingw-w64** (opzionale, flag `--mingw` cross-compile per Windows) ```bash sudo apt-get install -y -qq mingw-w64 ``` - assert: `command -v x86_64-w64-mingw32-gcc` - [x] **Step 5 CI directories** ```bash sudo mkdir -p /opt/ci/{build,output,scripts,cache} sudo chown -R ci_build:ci_build /opt/ci ``` - assert: `test -d /opt/ci/build`, `test -d /opt/ci/output`, `test -d /opt/ci/scripts` - [x] **Step 6 SSH hardening** (PasswordAuth off gia impostato da cloud-init, ri-validato) ```bash sudo sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config sudo sed -i 's/^#*PubkeyAuthentication.*/PubkeyAuthentication yes/' /etc/ssh/sshd_config sudo systemctl restart sshd ``` - assert: `grep -q "^PasswordAuthentication no" /etc/ssh/sshd_config` - [x] **Step 7 Disabilita swap permanente** ```bash sudo swapoff -a sudo sed -i '/ swap / s/^/#/' /etc/fstab ``` - assert: `swapon --show` output vuoto - [x] **Step 8 Disabilita apt auto-update** (no lock durante build) ```bash sudo systemctl mask apt-daily.service apt-daily-upgrade.service \ apt-daily.timer apt-daily-upgrade.timer sudo apt-get remove -y unattended-upgrades 2>/dev/null || true ``` - [x] **Step 9 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 ``` - [x] **Final Gate pre-snapshot** (exit 1 se qualcosa manca) - Ogni tool richiesto raggiungibile con `command -v` - `/opt/ci/{build,output,scripts}` esistenti con owner `ci_build` - `swapon --show` vuoto - `sshd` attivo - `PasswordAuthentication no` in sshd_config - Root filesystem >= 38 GB (verifica che cloud-init `growpart` abbia funzionato): ```bash df -BG / | awk 'NR==2{gsub("G","",$2); if ($2+0 < 38) {print "FAIL: root fs too small"; exit 1}}' ``` - Nessun installer residuo in `/tmp` --- ## Fase D Adattamento script CI - [x] **`scripts/_Transport.psm1`** nuovo modulo SSH-only per il branch Linux. Gli script Windows esistenti NON vengono modificati ora; il refactoring WinRM e' rinviato. ```powershell function Invoke-SshCommand { param($IP, $User, $KeyPath, $Command, ...) } function Copy-SshItem { param($Source, $Dest, $IP, $User, $KeyPath, [ValidateSet('ToGuest','FromGuest')] $Direction, ...) } function Test-SshReady { param($IP, $User, $KeyPath, $TimeoutSec, ...) } ``` Gli script CI (`Invoke-CIJob`, `Invoke-RemoteBuild`, `Get-BuildArtifacts`) chiameranno queste funzioni nel branch Linux senza toccare il branch WinRM esistente. - [x] **`scripts/Invoke-CIJob.ps1`** aggiungere parametro `-GuestOS Windows|Linux` - Auto-detect da VMX `guestOS` field se non fornito - Branch su transport (WinRM vs SSH) in ogni fase - [x] **`scripts/Wait-VMReady.ps1`** aggiungere `-Transport SSH` - Probe: `Test-NetConnection -Port 22` + `ssh -o ConnectTimeout=5 ... "echo ok"` - [x] **`scripts/Invoke-RemoteBuild.ps1`** branch SSH+scp per Linux - Transfer: `scp` invece di `Copy-Item -ToSession` - Build: `ssh ... "cd /opt/ci/build && $BuildCommand"` - [x] **`scripts/Get-BuildArtifacts.ps1`** branch `scp` per Linux - `scp -r ci_build@$IP:/opt/ci/output/* $LocalArtifactDir\` - [ ] **`scripts/New-BuildVM.ps1`** gia OS-agnostic (vmrun clone) - Verificare che `-SnapshotName BaseClean-Linux` funzioni correttamente - [ ] **`scripts/Remove-BuildVM.ps1`** gia OS-agnostic, nessuna modifica prevista --- ## Fase E Runner e workflow Gitea - [x] **Label `linux-build`** aggiunta al runner in `runner/config.yaml`: ```yaml labels: - 'windows-build:host' - 'linux-build:host' ``` - [x] **Variabile d'ambiente** `GITEA_CI_LINUX_TEMPLATE_PATH` nel servizio act_runner: `F:\CI\Templates\LinuxBuild2404\LinuxBuild2404.vmx` - [x] **Workflow matrix** di esempio (`gitea/workflow-example.yml` aggiornato): ```yaml strategy: matrix: target: [windows, linux] runs-on: ${{ matrix.target }}-build ``` --- ## Fase F Test e2e - [ ] **Smoke test** linked clone + SSH ready: ```powershell .\scripts\New-BuildVM.ps1 -JobId 'linux-smoke-001' ` -TemplatePath 'F:\CI\Templates\LinuxBuild2404\LinuxBuild2404.vmx' ` -SnapshotName 'BaseClean-Linux' ``` - [ ] **Build test** comando reale nella VM: ```powershell .\scripts\Invoke-CIJob.ps1 -JobId 'linux-e2e-001' -GuestOS Linux ` -RepoUrl 'ssh://gitea-ci/Simone/.git' -Branch 'main' ` -TemplatePath 'F:\CI\Templates\LinuxBuild2404\LinuxBuild2404.vmx' ` -BuildCommand 'make -C /opt/ci/build all' ` -GuestArtifactSource '/opt/ci/output' ``` - [ ] **Cleanup** linked clone rimosso dopo build (`Remove-BuildVM.ps1`) - [ ] **Matrix build** run parallela Windows + Linux sullo stesso push --- ## Differenze da Windows riepilogo | Aspetto | Windows | Linux | | ----------------------- | ----------------------------------------------------- | ---------------------------------------------- | | Immagine di partenza | ISO installer (Deploy ~60-90 min unattended) | Cloud VMDK pre-built (boot + cloud-init ~60 s) | | Provisioning unattended | `autounattend.xml` (Windows Setup) | cloud-init `user-data` (seed ISO nocloud) | | Transport host VM | WinRM HTTPS/5986 + PSSession | `ssh.exe` + `scp.exe` (built-in Windows 11) | | NIC strategia | e1000e install vmxnet3 swap post-Tools | vmxnet3 diretto (driver nativo kernel) | | Setup script | `.ps1` (PowerShell, via WinRM) | `.sh` (Bash, via SSH) | | Rilevamento IP | `vmrun getGuestIPAddress` (VMware Tools) | `vmrun getGuestIPAddress` (open-vm-tools) | | Wait completamento | polling flag file via `vmrun copyFileFromGuestToHost` | polling SSH + `boot-finished` cloud-init | | CI dirs | `C:\CI\{build,output,scripts}` | `/opt/ci/{build,output,scripts,cache}` | | Snapshot name | `BaseClean` | `BaseClean-Linux` | --- ## Riferimenti - [ARCHITECTURE.md](ARCHITECTURE.md) diagramma componenti (aggiornare dopo implementazione) - [CI-FLOW.md](CI-FLOW.md) flusso pipeline (aggiornare Step 0 prerequisites) - [HOST-SETUP.md](HOST-SETUP.md) Setup-Host.ps1 (aggiungere step generazione SSH key) - [TODO.md §6.1](../TODO.md) tracking implementazione