docs: record the LinuxRust2404 template, its failure modes and open decisions

- AGENTS.md: LinuxRust2404 reference, plus errors #14-#16 -- deploy --force
  against a powered-on VM, the three distinct causes behind the identical
  "VMware Tools are not running" message, and the orphan snapshot deltas
  --force leaves behind after deleting the .vmsd.
- docs/RUNBOOK.md §16: triage for a Step 7 IP timeout, ordered cheapest check
  first, with a Quick Reference entry.
- docs/RUNBOOK-PhaseC.md: deploy-rust/prepare-rust, the ownership and non-login
  shell constraints on /opt/rust, --then-prepare, and how to route a label to a
  template (a second runner instance, with the verification step).
- TODO.md §2.9: retention run covers only artifacts, logs and IP leases, so it
  warned about low disk while freeing 0.0 GB two runs in a row. Mitigated by
  hand; the automated policy is still blind to what actually weighs.
- TODO.md §6.5: act_runner v1.0.4 -> v2.x evaluated and deferred, with the
  trigger for reopening. v2.1.0 exposes no RUNNER_LABELS either (checked
  against the binary), so the two-runner design is not a stopgap.
This commit is contained in:
2026-07-22 14:09:34 +02:00
parent 498b0e9c05
commit 14a67ead79
5 changed files with 350 additions and 0 deletions
+47
View File
@@ -174,6 +174,47 @@ Variante **WinBuild2022**: path `F:\CI\Templates\WinBuild2022\WinBuild2022.vmx`,
| Toolchain | GCC, G++, Clang, CMake, Ninja, Python 3, Git, 7-Zip | | Toolchain | GCC, G++, Clang, CMake, Ninja, Python 3, Git, 7-Zip |
| CI dirs | `/opt/ci/{build,output,scripts,cache}` — owner `ci_build` | | CI dirs | `/opt/ci/{build,output,scripts,cache}` — owner `ci_build` |
### Linux Rust (LinuxRust2404)
Variante di `LinuxBuild2404` per workspace Rust (es. app Tauri): stessa cloud
image e stesso layout guest, provisioning via
`template/Install-CIToolchain-LinuxRust2404.sh` che aggiunge le librerie
webview/GTK di Tauri v2 e una toolchain Rust system-wide sotto `/opt/rust`
(rustup + rustfmt + clippy + binario `cargo-audit`; il DB advisory di
cargo-audit NON e' bakato, viene fetchato a runtime).
| Parametro | Valore |
| -------------- | ------------------------------------------------------------- |
| OS | Ubuntu 24.04 LTS (cloud image) |
| VMX | `LinuxRust2404/LinuxRust2404.vmx` (sotto la dir templates) |
| Snapshot clone | `BaseClean-LinuxRust` |
| Transport | SSH porta 22, utente `ci_build`, stessa chiave di LinuxBuild2404 |
| Toolchain | quella di LinuxBuild2404 + webview/GTK Tauri + Rust in `/opt/rust` |
| Comandi | `ci template deploy-rust` / `ci template prepare-rust` |
`--rust-channel` (default `stable`) controlla la toolchain bakata: pinnare una
versione esatta (es. `1.97.1`) per uno snapshot deterministico; con `stable`
rustup riscarica a runtime ad ogni nuova stable. Rigenerare lo snapshot quando
cambia il pin o la baseline apt di sicurezza.
Due vincoli sull'albero `/opt/rust`, entrambi regressioni gia' occorse:
1. **`chown` DOPO l'installazione.** Lo script gira come root, quindi tutto
cio' che rustup/cargo creano nasce root-owned: un `chown` fatto solo prima
copre le due directory vuote e nient'altro. Senza il `chown` finale
`ci_build` non puo' scrivere ne' in `RUSTUP_HOME` ne' in
`CARGO_HOME/registry`, e **ogni** build Rust con dipendenze esterne muore
con `Permission denied (os error 13)`. Gli assert devono verificare la
**scrivibilita'** (`sudo -u ci_build test -w`), non solo `test -x` sui
binari: altrimenti il template rotto passa la sua build e fallisce nei job
dei consumatori.
2. **`/etc/profile.d` da solo non basta.** L'orchestratore esegue i comandi
con paramiko `exec_command` + `sh -c` (`transport/ssh.py`,
`commands/build.py`): shell **non di login**, `profile.d` non viene mai
sorgiato. `RUSTUP_HOME`/`CARGO_HOME` vanno anche in `/etc/environment`
(letto da `pam_env` all'apertura della sessione SSH) e i proxy rustup
symlinkati in `/usr/local/bin`.
--- ---
## Transport layer ## Transport layer
@@ -236,3 +277,9 @@ Configurazione in `PSScriptAnalyzerSettings.psd1`. Regole principali:
11. **Machine-id identico tra clone Linux** — le cloud image Ubuntu hanno un `/etc/machine-id` fisso. VMware DHCP assegna l'IP tramite DHCP client-id derivato da machine-id: se tutti i clone hanno lo stesso machine-id ottengono lo stesso IP e si bloccano per "IP collision". Fix nel template (prima di prendere lo snapshot): `sudo truncate -s 0 /etc/machine-id && sudo rm -f /var/lib/dbus/machine-id && sudo shutdown -h now`, poi ricreare lo snapshot `BaseClean-Linux` da stato powered-off. 11. **Machine-id identico tra clone Linux** — le cloud image Ubuntu hanno un `/etc/machine-id` fisso. VMware DHCP assegna l'IP tramite DHCP client-id derivato da machine-id: se tutti i clone hanno lo stesso machine-id ottengono lo stesso IP e si bloccano per "IP collision". Fix nel template (prima di prendere lo snapshot): `sudo truncate -s 0 /etc/machine-id && sudo rm -f /var/lib/dbus/machine-id && sudo shutdown -h now`, poi ricreare lo snapshot `BaseClean-Linux` da stato powered-off.
12. **`& nativecmd 2>$null` NON sopprime stderr in PS 5.1 con `$ErrorActionPreference='Stop'`** — la stderr del processo nativo viene convertita in ErrorRecord nel pipeline di PowerShell; sotto `'Stop'` questo diventa un errore terminante PRIMA che `2>$null` possa scartarlo. Esempio: `ssh-keygen -R <ip> -f <file>` stampa "Host X not found in file" su stderr → eccezione terminante. Fix: usare `$ErrorActionPreference = 'SilentlyContinue'` attorno alla chiamata oppure `2>&1 | Out-Null`. Per i job CI su VM ephemere locali, usare direttamente `StrictHostKeyChecking=no UserKnownHostsFile=NUL` (default in `_Transport.psm1` con `KnownHostsFile=''`) — nessun `ssh-keygen` necessario. 12. **`& nativecmd 2>$null` NON sopprime stderr in PS 5.1 con `$ErrorActionPreference='Stop'`** — la stderr del processo nativo viene convertita in ErrorRecord nel pipeline di PowerShell; sotto `'Stop'` questo diventa un errore terminante PRIMA che `2>$null` possa scartarlo. Esempio: `ssh-keygen -R <ip> -f <file>` stampa "Host X not found in file" su stderr → eccezione terminante. Fix: usare `$ErrorActionPreference = 'SilentlyContinue'` attorno alla chiamata oppure `2>&1 | Out-Null`. Per i job CI su VM ephemere locali, usare direttamente `StrictHostKeyChecking=no UserKnownHostsFile=NUL` (default in `_Transport.psm1` con `KnownHostsFile=''`) — nessun `ssh-keygen` necessario.
13. **Quote WinRM/desktop-heap troppo basse per builds Windows paralleli** — sintomo: `MSBuild error MSB6003: CL.exe could not be run. System.ComponentModel.Win32Exception (0x80004005): Not enough quota is available to process this command` (oppure le altre tool MSVC: link.exe, mt.exe) quando il build interno fa fan-out parallelo (es. matrix x86/x64/x86-ansi, MSBuild `/maxcpucount`). Causa: il plugin endpoint Microsoft.PowerShell del guest ha `MaxMemoryPerShellMB=150` di default e l'heap del desktop non-interattivo (Session 0) ha `SharedSection 3rd field=768 KB` — entrambi insufficienti per più CL.exe concorrenti via WinRM. **Non emerge** in pre-migration Win→Win perché `Invoke-Command` su `PSSession` localhost-style eredita quote più larghe; emerge appena il path passa per `pypsrp` Linux→Win. Fix nel template (Deploy-WinBuild2025.ps1 / 2022 lo fa, Install-CIToolchain lo valida): `Set-Item WSMan:\localhost\Plugin\Microsoft.PowerShell\Quotas\MaxMemoryPerShellMB 2048`, `Set-Item WSMan:\localhost\Shell\MaxProcessesPerShell 100`, registry `HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\SubSystems\Windows` patch del 3° campo `SharedSection` a 4096, **reboot** (csrss legge SharedSection solo al boot), nuovo `BaseClean`. Dettagli e procedura completa in `docs/WINDOWS-TEMPLATE-SETUP.md § "WinRM quotas e desktop heap"`. 13. **Quote WinRM/desktop-heap troppo basse per builds Windows paralleli** — sintomo: `MSBuild error MSB6003: CL.exe could not be run. System.ComponentModel.Win32Exception (0x80004005): Not enough quota is available to process this command` (oppure le altre tool MSVC: link.exe, mt.exe) quando il build interno fa fan-out parallelo (es. matrix x86/x64/x86-ansi, MSBuild `/maxcpucount`). Causa: il plugin endpoint Microsoft.PowerShell del guest ha `MaxMemoryPerShellMB=150` di default e l'heap del desktop non-interattivo (Session 0) ha `SharedSection 3rd field=768 KB` — entrambi insufficienti per più CL.exe concorrenti via WinRM. **Non emerge** in pre-migration Win→Win perché `Invoke-Command` su `PSSession` localhost-style eredita quote più larghe; emerge appena il path passa per `pypsrp` Linux→Win. Fix nel template (Deploy-WinBuild2025.ps1 / 2022 lo fa, Install-CIToolchain lo valida): `Set-Item WSMan:\localhost\Plugin\Microsoft.PowerShell\Quotas\MaxMemoryPerShellMB 2048`, `Set-Item WSMan:\localhost\Shell\MaxProcessesPerShell 100`, registry `HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\SubSystems\Windows` patch del 3° campo `SharedSection` a 4096, **reboot** (csrss legge SharedSection solo al boot), nuovo `BaseClean`. Dettagli e procedura completa in `docs/WINDOWS-TEMPLATE-SETUP.md § "WinRM quotas e desktop heap"`.
14. **`deploy-* --force` lanciato mentre la VM target è ACCESA** — corrompe la VM in esecuzione e produce un timeout allo Step 7 che *sembra* un problema di rete. Occorso il 2026-07-22 su `LinuxRust2404`, con un `prepare-rust` ancora in corso sulla stessa VMX. Meccanica: (a) Step 1 vede `<disk>.vmdk.lck` e lo cancella come "stale artifact" — **non era stale**, apparteneva al processo vivo; (b) Step 4 riscrive il VMDK base sotto il guest in esecuzione, che muore; (c) Step 6 lancia il power-on, il nuovo `vmware-vmx` muore subito perché la VMX è già occupata, ma il controllo `[OK] VM appears in vmrun list` **passa lo stesso** perché vede l'istanza vecchia; (d) Step 7 interroga per 10 minuti un guest morto e riporta `Error: The VMware Tools are not running in the virtual machine`. Diagnosi in 5 secondi: `ps -eo pid,lstart,etime,args | grep vmware-vmx` — se il `vmware-vmx` vivo è più vecchio del deploy appena lanciato, è questo il caso. Recupero: `vmrun stop <vmx> hard`, rimuovere i delta orfani `<disk>-00000*.vmdk`, verificare `vmrun list` = 0, poi rilanciare. **Non** interpretarlo come DHCP incastrato (vedi #15) e non risolverlo con un retry automatico: riavviare una VM già corrotta ripete il fallimento con la causa ormai illeggibile.
15. **Step 7 in timeout: distinguere tre cause diverse che danno lo stesso messaggio** — `getGuestIPAddress` risponde `The VMware Tools are not running` sia quando il guest non ha IP, sia quando ha l'IP ma i Tools non rispondono. Ordine di verifica, dal più veloce: (a) **collisione** — `ps | grep vmware-vmx`, processo più vecchio del deploy corrente → vedi #14; (b) **DHCP host incastrato** — `ip -4 addr show vmnet8` deve dare `192.168.79.1/24` e `ps -ef | grep vmnet-dhcpd` deve mostrarlo vivo; se no, `sudo systemctl restart vmware-networks` (o `vmware-host.service`); (c) **guest senza lease o Tools non pronti** — cercare il MAC della VMX in `/etc/vmware/vmnet8/dhcpd/dhcpd.leases` e in `ip neigh show dev vmnet8`: se un lease c'è, il guest è vivo e mancano solo i Tools, quindi sondare TCP/22 direttamente invece di aspettare `getGuestIPAddress`.
16. **Delta di snapshot orfani dopo `--force`** — `deploy-*` cancella `<vm>.vmsd` (il database snapshot) per evitare l'errore di resize sulla catena, ma **non** cancella i file delta `<disk>-00000N.vmdk`. Restano su disco con un `parentCID` che non corrisponde più al base ricreato: innocui (fuori dal percorso di boot, la VMX punta al base con `parentCID=ffffffff`) ma confondono la diagnosi e vanno rimossi a mano. Verifica della catena: `head -c 2000 <disk>.vmdk | strings | grep -E '^CID|parentCID'`.
+8
View File
@@ -106,6 +106,14 @@ The host **never executes build tools directly** — it only orchestrates VM lif
| WinBuild2025 | `F:\CI\Templates\WinBuild2025\WinBuild2025.vmx` | `BaseClean` | WinRM HTTPS/5986 | | WinBuild2025 | `F:\CI\Templates\WinBuild2025\WinBuild2025.vmx` | `BaseClean` | WinRM HTTPS/5986 |
| WinBuild2022 | `F:\CI\Templates\WinBuild2022\WinBuild2022.vmx` | `BaseClean` | WinRM HTTPS/5986 | | WinBuild2022 | `F:\CI\Templates\WinBuild2022\WinBuild2022.vmx` | `BaseClean` | WinRM HTTPS/5986 |
| LinuxBuild2404 | `F:\CI\Templates\LinuxBuild2404\LinuxBuild2404.vmx` | `BaseClean-Linux` | SSH/22 (`ci_build`, key `F:\CI\keys\ci_linux`) | | LinuxBuild2404 | `F:\CI\Templates\LinuxBuild2404\LinuxBuild2404.vmx` | `BaseClean-Linux` | SSH/22 (`ci_build`, key `F:\CI\keys\ci_linux`) |
| LinuxRust2404 | `LinuxRust2404/LinuxRust2404.vmx` (templates dir) | `BaseClean-LinuxRust` | SSH/22 (same user/key as LinuxBuild2404) |
`LinuxRust2404` is a LinuxBuild2404 variant for Rust workspaces (Tauri):
`ci template deploy-rust` / `ci template prepare-rust` provision it via
`template/Install-CIToolchain-LinuxRust2404.sh` (webview/GTK libs + system-wide
rustup under `/opt/rust` + cargo-audit binary; the audit advisory DB is fetched
at runtime, never baked). `--rust-channel` pins the baked toolchain (default
`stable`, a moving target).
Snapshots **must be taken from powered-off state** — presence of `.vmem` in the template dir means the snapshot captured running memory and `vmrun clone ... linked` will fail. Snapshots **must be taken from powered-off state** — presence of `.vmem` in the template dir means the snapshot captured running memory and `vmrun clone ... linked` will fail.
+76
View File
@@ -312,6 +312,34 @@ mai rilasciati. Workaround manuale: reset a `{}` con `build-vms/` vuoto.
cui clone-dir non esiste più in `CloneBaseDir`. Opzionale: allargare il pool cui clone-dir non esiste più in `CloneBaseDir`. Opzionale: allargare il pool
(es. 201–210) per dare margine oltre parallelism. Aggiungere test. (es. 201–210) per dare margine oltre parallelism. Aggiungere test.
### 2.9 [P2] [ ] `retention run` non copre backup/template/ISO — APERTO (2026-07-22)
`ci-retention-policy` agisce solo su **artifact, log e lease IP**. Su questo host
quei tre insiemi pesano 52 KB in tutto, mentre il disco era pieno al 94% per
`backups/` (195 G), `templates/` (128 G) e `iso/` (69 G) — tutti fuori dal suo
raggio d'azione.
Sintomo osservato: il timer girava, rilevava meno di 50 GB liberi, dichiarava
`WARNING: Free space below 50 GB — aggressive retention: 7d` e liberava
**0.0 GB**, due esecuzioni di fila. L'allarme suonava ma il meccanismo non
poteva agire su ciò che pesava. Un guardiano che non sorveglia nulla è peggio di
nessun guardiano: dà l'impressione che il problema sia sotto controllo.
Mitigato il 2026-07-22 (backup svuotati, `--keep-count 1 --exclude WinBuildTest`
nella unit), ma è una correzione manuale: la policy automatica resta cieca.
**Fix proposto**: estendere `retention run` ai backup (per numero di generazioni
o per età) e almeno segnalare — non cancellare — ISO e template che superano una
soglia. In alternativa, se i backup restano governati solo da `--keep-count`,
far sì che il warning sullo spazio dica **quali** directory pesano, invece di
lamentarsi senza indicare dove guardare.
**Nota**: `iso/` contiene coppie originale + `.patched` e versioni April
superate da May (~38 G recuperabili); le `.patched` sono rigenerate da
`deploy-win`. `templates/WinBuildTest` (41 G, ultimo uso 10 giugno, citato solo
in `docs/archived/2026-05-10/`) è candidato alla dismissione: escluso dai backup
ma ancora su disco.
--- ---
## 3. Performance & throughput ## 3. Performance & throughput
@@ -518,6 +546,54 @@ Già supportato lato Gitea, basta replicare il setup.
**Deferred**: in un home lab single-host questa funzionalità non serve. Non pianificare. **Deferred**: in un home lab single-host questa funzionalità non serve. Non pianificare.
### 6.5 [P3] [ ] Upgrade act_runner v1.0.4 → v2.x — VALUTATO E RIMANDATO (2026-07-22)
Versione installata: **v1.0.4** (18 mag 2026). Ultima upstream al momento della
valutazione: **v2.1.0** (16 lug 2026).
Release: https://gitea.com/gitea/runner/releases
**Decisione: non aggiornare ora.** Il beneficio netto per questo sistema è
marginale. Valutazione fatta ispezionando il binario v2.1.0 (checksum
verificato), non solo il changelog.
Cosa **non** cambia — verificato sul binario, non dedotto:
- v2.1.0 esporta al job esattamente le stesse variabili di v1.0.4
(`RUNNER_ARCH/TEMP/DEBUG/OS/TOOL_CACHE`), **nessun `RUNNER_LABELS`**. Il
`GITEA_RUNNER_LABELS` del changelog v2.1.0 è operator-side: imposta le label
del daemon all'avvio, non le espone al workflow. Il routing label → template
continua a richiedere un'istanza runner per label (vedi RUNBOOK-PhaseC,
sezione `deploy-rust`).
Cosa non ci riguarda: il grosso di v2.x è lavoro su Docker/container
(credenziali di pull, service container, dind, IPv6). Qui `container.enabled`
è `false`, host mode puro. Anche l'unico breaking change di v2.0.0
(`DOCKER_USERNAME`/`DOCKER_PASSWORD` non più impliciti nei pull) è inapplicabile.
Cosa ci riguarderebbe:
| Novità | Valore reale |
|---|---|
| Cancellation handling completa (v2.0.0) + reap processi (v1.0.7/1.0.8) | Un job cancellato può lasciare una build VM orfana — ma `ci-cleanup-orphans.timer` gira già ogni 6h |
| `runner.post_task_script` (v2.0.0) | Aggancerebbe `vm cleanup` dopo ogni task: latenza orfani da ≤6h a immediata |
| `jobs.<id>.timeout-minutes` / `continue-on-error` (v2.0.0) | Ergonomia workflow |
| `register --token-file` (v2.1.0) | Già implementato a mano in `scripts/install-rust-runner.sh` |
Schema di configurazione retrocompatibile: il diff v2.1.0 vs il nostro config è
solo additivo, nessuna chiave rimossa.
**Trigger per riaprire**: quando serve `runner.post_task_script` per azzerare la
latenza del cleanup orfani, oppure quando la 2.x è matura (era vecchia di 6
giorni alla valutazione). Non aggiornare "per stare aggiornati".
**Vincolo operativo da ricordare**: i due runner (`act-runner.service` e
`act-runner-rust.service`) condividono **lo stesso binario**
`/opt/ci/act_runner/act_runner`. Sostituirlo aggiorna entrambi insieme. Per un
rollout graduale serve un secondo binario affiancato
(`act_runner-2.1.0`), l'`ExecStart` del solo servizio rust puntato lì, un job di
prova su `runs-on: rust-build`, e solo dopo la promozione per il runner
principale — tenendo v1.0.4 per il rollback.
### 6.4 [P3] [x] Build matrix nel workflow ### 6.4 [P3] [x] Build matrix nel workflow
File: [gitea/workflows/build-ns7zip.yml](gitea/workflows/build-ns7zip.yml). File: [gitea/workflows/build-ns7zip.yml](gitea/workflows/build-ns7zip.yml).
+129
View File
@@ -77,10 +77,12 @@ sudo); `--uninstall` removes the block. The script defines two helpers:
| `ci vmware --vmx ...` | Open a VMX in the VMware Workstation GUI | [§4](#4-manual-vm-lifecycle) | | `ci vmware --vmx ...` | Open a VMX in the VMware Workstation GUI | [§4](#4-manual-vm-lifecycle) |
| `ci creds set --user ...` | Store guest credentials in the keyring | [§5](#5-credentials--template-provisioning) | | `ci creds set --user ...` | Store guest credentials in the keyring | [§5](#5-credentials--template-provisioning) |
| `ci template deploy-linux` | Build the Linux template VM | [§5](#5-credentials--template-provisioning) | | `ci template deploy-linux` | Build the Linux template VM | [§5](#5-credentials--template-provisioning) |
| `ci template deploy-rust` | Build the Rust Linux template VM (LinuxRust2404) | [§5](#5-credentials--template-provisioning) |
| `ci template check-win-iso` | Report the newest WS2025 ISO from massgrave + cache status | [§5](#5-credentials--template-provisioning) | | `ci template check-win-iso` | Report the newest WS2025 ISO from massgrave + cache status | [§5](#5-credentials--template-provisioning) |
| `ci template patch-win-iso` | Inject autounattend + no-prompt boot into a Windows ISO | [§5](#5-credentials--template-provisioning) | | `ci template patch-win-iso` | Inject autounattend + no-prompt boot into a Windows ISO | [§5](#5-credentials--template-provisioning) |
| `ci template deploy-win` | Build the Windows Server 2025 template VM (download→patch→deploy) | [§5](#5-credentials--template-provisioning) | | `ci template deploy-win` | Build the Windows Server 2025 template VM (download→patch→deploy) | [§5](#5-credentials--template-provisioning) |
| `ci template prepare-linux` | Provision the Linux template over SSH | [§5](#5-credentials--template-provisioning) | | `ci template prepare-linux` | Provision the Linux template over SSH | [§5](#5-credentials--template-provisioning) |
| `ci template prepare-rust` | Provision the Rust Linux template over SSH | [§5](#5-credentials--template-provisioning) |
| `ci template prepare-win` | Provision a Windows template over WinRM | [§5](#5-credentials--template-provisioning) | | `ci template prepare-win` | Provision a Windows template over WinRM | [§5](#5-credentials--template-provisioning) |
| `ci template backup` | Timestamped copy of the template directories | [§5](#5-credentials--template-provisioning) | | `ci template backup` | Timestamped copy of the template directories | [§5](#5-credentials--template-provisioning) |
| `ci retention run` | Purge old artifacts and logs | [§6](#6-maintenance) | | `ci retention run` | Purge old artifacts and logs | [§6](#6-maintenance) |
@@ -412,12 +414,139 @@ Run `ci template <sub> --help` for the (many) per-template options.
```bash ```bash
ci template deploy-linux # build the Linux template VM from scratch (Ubuntu 24.04) ci template deploy-linux # build the Linux template VM from scratch (Ubuntu 24.04)
ci template deploy-rust # build the Rust Linux template VM (LinuxRust2404)
ci template deploy-win # build the Windows Server 2025 template VM (see below) ci template deploy-win # build the Windows Server 2025 template VM (see below)
ci template prepare-linux # provision an existing Linux template over SSH ci template prepare-linux # provision an existing Linux template over SSH
ci template prepare-rust # provision the Rust Linux template over SSH
ci template prepare-win # provision a Windows template over WinRM (Linux-host driven) ci template prepare-win # provision a Windows template over WinRM (Linux-host driven)
ci template backup # timestamped backup of the template directories ci template backup # timestamped backup of the template directories
``` ```
### `deploy-rust` / `prepare-rust` — the Rust Linux template (LinuxRust2404)
A separate template for Rust workspaces (e.g. Tauri apps): same Ubuntu 24.04
cloud image and guest layout as `LinuxBuild2404`, but `prepare-rust` runs
`template/Install-CIToolchain-LinuxRust2404.sh`, which additionally bakes:
- the Tauri v2 webview/GTK development libraries
(`libwebkit2gtk-4.1-dev`, `libgtk-3-dev`, `libayatana-appindicator3-dev`,
`librsvg2-dev`);
- a system-wide Rust toolchain via rustup under `/opt/rust`
(`RUSTUP_HOME=/opt/rust/rustup`, `CARGO_HOME=/opt/rust/cargo`) with the
`rustfmt` and `clippy` components. The tree is `chown`ed to `ci_build`
**after** the installs, not only before — otherwise every write the build
user makes (toolchain sync, dependency download into
`CARGO_HOME/registry`, `cargo install`) fails with `Permission denied`.
Exposure is threefold because the orchestrator runs build commands in a
**non-login** shell (`exec_command` + `sh -c`), which never sources
`profile.d`: `/etc/profile.d/rust.sh` for interactive shells,
`/etc/environment` (read by `pam_env` at SSH session open) for the two
home variables, and `/usr/local/bin` symlinks for the rustup proxies;
- the `cargo-audit` binary (skip with `--no-cargo-audit`). The audit
**advisory DB is never baked** — `cargo audit` fetches it at runtime.
```bash
ci template deploy-rust # same flags as deploy-linux; 60 GB disk default
ci template prepare-rust # default VMX: <CI_TEMPLATES>/LinuxRust2404/...
# snapshot: BaseClean-LinuxRust
ci template prepare-rust --rust-channel 1.97.1 # pin an exact toolchain
# Both phases in one run, unattended. deploy and prepare stay separate
# commands — --then-prepare only chains them, reusing the VM deploy left
# running (no second power-on, no second IP detection).
#
# Run from the repo root: the guest toolchain script is looked up under
# <cwd>/template/. With --then-prepare this is checked BEFORE the deploy
# starts, so a wrong directory costs two seconds instead of failing the
# hand-off after a ~45-minute deploy. Pass --toolchain-script to run from
# anywhere.
cd /opt/ci/local-ci-cd-system
ci template deploy-rust --then-prepare --rust-channel 1.97.1
ci template deploy-linux --then-prepare
# from any directory
ci template deploy-rust --then-prepare --rust-channel 1.97.1 \
--toolchain-script /opt/ci/local-ci-cd-system/template/Install-CIToolchain-LinuxRust2404.sh
```
`--snapshot-name` on `deploy-linux`/`deploy-rust` names the snapshot that
`prepare-*` will create (`BaseClean-Linux` / `BaseClean-LinuxRust`) — the
deploy commands take no snapshot of their own, unlike `deploy-win`. With
`--then-prepare` the value is handed to `prepare-*`, so overriding it on the
deploy side changes what actually gets created.
Both `prepare-linux` and `prepare-rust` default `--vmx` to their template VMX
under `<CI_TEMPLATES>` (`LinuxBuild2404` / `LinuxRust2404`) when neither
`--vmx` nor `--ip` is passed — so the plain `ci template prepare-rust` just
works after `deploy-rust`. Pass `--ip` to provision a running guest without
VM lifecycle (no auto power-on/shutdown/snapshot).
`--rust-channel` defaults to `stable` — a moving target: when a new stable
ships, `rustup show` in a job re-downloads the toolchain at runtime. Pin an
exact version for a fully deterministic snapshot (the consumer repo decides
this via its `rust-toolchain.toml`). Refresh the snapshot when the pinned
channel or the apt security baseline moves.
Two ways to route jobs to it:
1. **Per job (no host change).** Pass `template-path` + `snapshot-name` to the
`local-ci-build` composite action. This cannot leave a job unschedulable,
which is why consumer repos default to it.
2. **By label** — `runs-on: rust-build`. This needs a **second act_runner
instance**, not just an extra label on the existing one: act_runner reads a
single `runner.envs` block per process and never tells the workflow which
label picked the job (there is no `RUNNER_LABELS` variable — v1.0.4 exposes
only `RUNNER_OS/ARCH/TEMP/TOOL_CACHE/DEBUG/VERSION`). A label can select a
template only by selecting a runner whose envs name that template.
```bash
# 1. mint a registration token from the stored GiteaPAT (0600 file, never
# printed — pass it by path so it stays out of `ps` and shell history)
sudo -u ci-runner /opt/ci/venv/bin/python tools/gitea-runner-token.py \
--out /var/lib/ci/.rust-runner-regtoken
# 2. provision + register + enable the unit
sudo ./scripts/install-rust-runner.sh \
--token-file /var/lib/ci/.rust-runner-regtoken
# 3. the token is single-use — destroy it
sudo shred -u /var/lib/ci/.rust-runner-regtoken
```
Provisions `/var/lib/ci/runner-rust/` from `deploy/runner-rust-config.yaml`
(label `rust-build:host`, `GITEA_CI_LINUX_TEMPLATE_PATH` →
`LinuxRust2404`, `GITEA_CI_LINUX_SNAPSHOT_NAME` → `BaseClean-LinuxRust`),
registers it, and installs `act-runner-rust.service`. Its `capacity` adds
to the main runner's — keep the sum inside the host RAM budget (currently
2 + 1 = 3 concurrent build VMs).
**Verify the label is announced before any workflow depends on it**: a
`runs-on` value that no runner advertises leaves the job unschedulable with
no error anywhere — silently zero runs.
```bash
sudo -u ci-runner /opt/ci/venv/bin/python tools/gitea-runner-list.py \
--label rust-build # exit 1 unless an ONLINE runner announces it
```
Note that `runner.file` in the config is an **absolute** path. act_runner
resolves a relative `file:` against the process CWD, so registering from
anywhere else drops the registration state (which contains the runner
token) into that directory and the daemon then fails with
`registration file not found`.
Both runner services share **one binary**, `/opt/ci/act_runner/act_runner`.
Replacing it upgrades `act-runner.service` and `act-runner-rust.service`
together — a staged rollout needs a second binary alongside it and the
`ExecStart` of one unit pointed at it. Current version is v1.0.4; the
upgrade to v2.x was evaluated and deferred (TODO §6.5).
Note that `guest-os: 'Auto'` cannot pick a template by itself, for the same
reason: the label is invisible to the action, and the VMX `guestOS` field is
only readable *after* the clone. With `Auto` the action requires
`template-path` **and** `snapshot-name` and fails fast otherwise, instead of
silently falling back to the Windows template as it did before.
### `deploy-win` — build the Windows Server 2025 template ### `deploy-win` — build the Windows Server 2025 template
Takes a Windows Server 2025 install ISO — downloaded from `--iso-url` (cached Takes a Windows Server 2025 install ISO — downloaded from `--iso-url` (cached
+90
View File
@@ -250,6 +250,7 @@ Estimated time: 2-4 hours including Windows Update.
| Template corrupt | Remove `*.lck` files; if persistent, restore from `F:\CI\Backups\` | | Template corrupt | Remove `*.lck` files; if persistent, restore from `F:\CI\Backups\` |
| Snapshot missing | `vmrun listSnapshots <vmx>`; update `GITEA_CI_SNAPSHOT_NAME` | | Snapshot missing | `vmrun listSnapshots <vmx>`; update `GITEA_CI_SNAPSHOT_NAME` |
| IP collision | `Remove-Item F:\CI\State\ip-leases\*.lease`; lower `capacity` | | IP collision | `Remove-Item F:\CI\State\ip-leases\*.lease`; lower `capacity` |
| Step 7: no guest IP | `ps -eo pid,lstart,args \| grep vmware-vmx` — see §16 |
--- ---
@@ -868,3 +869,92 @@ This is **optional** — the default boots-to-Linux behaviour is intentional so
the machine comes back as the Linux CI host after any unattended reboot the machine comes back as the Linux CI host after any unattended reboot
(power-loss, kernel update). Leave it as-is unless Windows becomes the daily (power-loss, kernel update). Leave it as-is unless Windows becomes the daily
driver. driver.
---
## 16. `deploy-*` step 7 in timeout — nessun IP dal guest
**Sintomo.** `deploy-linux` / `deploy-rust` cicla su
`... waiting for guest IP (open-vm-tools) ...` per 10 minuti e chiude con
`Error: [Deploy Step 7] Timed out waiting for guest IP.` Interrogando a mano:
```
$ vmrun -T ws getGuestIPAddress <vmx>
Error: The VMware Tools are not running in the virtual machine
```
Quel messaggio è ambiguo: esce identico sia quando il guest **non ha** un IP,
sia quando ce l'ha ma i Tools non rispondono. Tre cause distinte, in ordine di
verifica dal più veloce al più lento.
### 16.1 Collisione con una VM già accesa (la più insidiosa)
Succede quando si lancia `deploy-* --force` mentre la stessa VMX è in uso —
tipicamente un `prepare-*` ancora in corso. Occorso il 2026-07-22 su
`LinuxRust2404`.
```bash
ps -eo pid,lstart,etime,args | grep vmware-vmx | grep -v grep
```
Se il `vmware-vmx` vivo è **più vecchio** del deploy appena lanciato, è questo
il caso: il deploy ha cancellato il `.vmdk.lck` della VM viva credendolo stale,
ha riscritto il VMDK base sotto il guest in esecuzione, e il suo power-on è
morto subito perché la VMX era occupata. Il controllo
`[OK] VM appears in vmrun list` **non protegge**: vede l'istanza vecchia.
Recupero:
```bash
sudo -u ci-runner vmrun -T ws stop <vmx> hard
sudo rm -f <vm-dir>/<disk>-00000*.vmdk # delta orfani, vedi 16.4
sudo -u ci-runner vmrun -T ws list # deve dire 0
# poi rilanciare il deploy, con nient'altro in esecuzione sulla stessa VMX
```
Non risolvere con un retry: riavviare una VM già corrotta ripete il fallimento
con la causa ormai illeggibile.
### 16.2 DHCP dell'host incastrato
```bash
ip -4 addr show vmnet8 # atteso: inet 192.168.79.1/24
ps -ef | grep vmnet-dhcpd # atteso: due istanze (vmnet1 + vmnet8)
```
Se `vmnet8` manca o `vmnet-dhcpd` non c'è:
```bash
sudo systemctl restart vmware-networks # oppure vmware-host.service
```
### 16.3 Guest senza lease, oppure Tools non ancora pronti
```bash
grep -A1 '^lease' /etc/vmware/vmnet8/dhcpd/dhcpd.leases | tail -20
ip neigh show dev vmnet8
```
Se compare un lease per il MAC della VM, **il guest è vivo e ha rete**: mancano
solo i Tools. Sondare direttamente la porta 22 invece di aspettare
`getGuestIPAddress`:
```bash
timeout 5 bash -c 'cat < /dev/null > /dev/tcp/<ip>/22' && echo "SSH aperta"
```
Se invece non c'è nessun lease e le due verifiche precedenti sono pulite, il
guest non sta completando il boot: cloud-init fallito o immagine corrotta.
### 16.4 Delta di snapshot orfani
`deploy-*` cancella `<vm>.vmsd` per evitare l'errore di resize sulla catena
snapshot, ma **non** rimuove i file delta `<disk>-00000N.vmdk`. Restano con un
`parentCID` che non corrisponde più al base ricreato. Sono innocui — la VMX
punta al base, riconoscibile da `parentCID=ffffffff` — ma confondono la
diagnosi. Verifica della catena:
```bash
grep -i 'scsi0:0.fileName' <vmx>
head -c 2000 <disk>.vmdk | strings | grep -E '^CID|parentCID'
```