Commit Graph
100 Commits
Author SHA1 Message Date
Simone cfce307c5b docs(runbook): use a real repo in job/build examples
Replace the non-existent myrepo.git placeholder (which fails with
"repository not found") with this repo's URL and a harmless
`echo build-ok` build command, so §3 and §4.3 run as pasted.
2026-06-08 21:43:34 +02:00
Simone 9ecb767b18 docs(runbook): fix §3 job example — required template flags
The `job` command enforces --template-path (job.py:447) and defaults
--snapshot-name to BaseClean, which is wrong for Linux. The §3 example
omitted both and failed with "--template-path is required". Add both
flags (BaseClean-Linux) and note them as required.
2026-06-08 21:37:24 +02:00
Simone 027e2ff4b6 docs(runbook): rewrite Phase C runbook for clarity
Replace the terse, abbreviation-heavy style with clean prose and
consistent formatting. Make command blocks copy-paste-ready on this host:
capture the guest IP into a shell variable for `validate guest` / `build
run`, expand globbed VMX paths, and group the `job` flags into readable
lists. Add intra-doc anchor links throughout. Point the pwsh-rollback note
at scripts/archived/. No commands or flags changed.
2026-06-08 21:31:25 +02:00
Simone c274d334d3 docs(agents): point PS→Python map at scripts/archived/
The shim mapping table now notes the .ps1 live in scripts/archived/
(frozen, lint-exempt, no live caller) and gains the Phase C host tools
(bench/smoke/validate host/creds set). Fix the "stays in PowerShell"
list: drop Test-CapacityBurnIn (archived) and the non-existent
Measure-CIBenchmark; keep retention/backup/template/e2e/Windows-side.
2026-06-08 21:26:07 +02:00
Simone 7f615333b6 docs(runbook): add guest IP discovery to §2
Document `vmrun getGuestIPAddress` (run as ci-runner) for templates and
clones as the prerequisite for `validate guest`, with the AGENTS.md #1
"Error: on stdout" gotcha. Update the validate-guest Linux example to the
live template IP.
2026-06-08 21:26:07 +02:00
Simone 0b0d5486f0 chore(scripts): archive PowerShell scripts superseded by the Python CLI
Move 16 .ps1 files to scripts/archived/ — they have no live caller:

- 10 pure shims (New-BuildVM, Remove-BuildVM, Cleanup-OrphanedBuildVMs,
  Wait-VMReady, Invoke-RemoteBuild, Get-BuildArtifacts, Watch-DiskSpace,
  Watch-RunnerHealth, Get-CIJobSummary, Invoke-CIJob) that only forwarded
  to `ci_orchestrator`; the live Gitea composite action now calls
  `python -m ci_orchestrator job` directly.
- 6 Phase-C host tools reimplemented natively in Python (Start-BurnInTest
  [+ -Linux], Test-CapacityBurnIn → bench; Test-Smoke → smoke run;
  Validate-HostState → validate host; Set-CIGuestCredential → creds set).

Add scripts/archived/README.md mapping each file to its `ci` replacement,
and exempt scripts/archived/ from the PSScriptAnalyzer lint job.

Kept (still used / not yet ported): Register-CIScheduledTasks,
Backup-CITemplate, Invoke-RetentionPolicy, Set-TemplateSharedFolders,
Test-CIGuestWinRM, the e2e build harnesses, and all template/ guest
provisioning scripts.
2026-06-08 21:15:05 +02:00
Simone cda4276d9e docs(runbook): document persistent xhost grant for vmware GUI
§4.7 now explains the grant is per-session and points to setup Step 8 /
the standalone install-vmware-gui-access.sh, keeping the manual one-off
xhost command as a fallback.
2026-06-08 21:11:07 +02:00
Simone 3f43b1ca12 feat(setup): persist ci-runner X grant for ci vmware GUI
`ci vmware` runs as ci-runner and relies on host-based X auth (xhost),
but the grant is per-session and wiped on logout / X restart, so the GUI
launch fails with "cannot open X display" after every desktop relogin.

Add scripts/install-vmware-gui-access.sh: installs an XDG autostart entry
in the desktop user's home that re-applies `xhost +SI:localuser:ci-runner`
at each graphical login, and applies it immediately when a display is
reachable. Desktop user resolves from --desktop-user / $CI_DESKTOP_USER /
$SUDO_USER (never root); service user from $CI_RUN_USER. --uninstall
removes the autostart entry and revokes the live grant.

Wire it into setup-host-linux.sh as Step 8, with --desktop-user (default
$SUDO_USER) and --skip-gui-access for headless hosts.
2026-06-08 21:11:07 +02:00
Simone de5348c36c refactor(cli): rename vmgui command to vmware
Rename the GUI launcher command from `vmgui` to `vmware` (`ci vmware ...`).
Updates the click command name, function, log-file prefix, __main__
registration, tests and runbook references. Behaviour unchanged.
2026-06-08 00:32:13 +02:00
Simone b74e854d48 docs(vmgui): clarify it works like sudo -u ci-runner vmware
The GUI connects via the X host ACL (xhost), not the inherited XAUTHORITY
cookie (unreadable by ci-runner). On a normal session the grant is already
present and `ci vmgui` works with no --display/xhost dance — sudo forwards
DISPLAY. xhost is only needed if you hit "cannot open X display". Drop the
overstated "--display :0 required" framing from §4.7.
2026-06-08 00:30:21 +02:00
Simone 23b5858802 Merge branch 'fix/vmgui-x-display' (vmgui: X preflight, no silent failure) 2026-06-08 00:26:14 +02:00
Simone 41d646577d fix(vmgui): pre-flight X display, stop failing silently
`vmgui` sent the child's stderr to /dev/null, so when the X display was
unreachable (the common case: sudo -u ci-runner loses the desktop user's
XAUTHORITY and xhost wasn't granted) the GUI died with "cannot open display"
and the command still printed success — "nothing opens".

Now it:
- pre-flights the display with xdpyinfo and aborts with the exact
  `xhost +SI:localuser:ci-runner` hint instead of spawning a doomed process;
- drops an inherited XAUTHORITY that this user can't read (falls back to
  host-based xhost auth);
- logs the GUI's stdio to /tmp/vmgui-*.log and grace-polls 1.5s, surfacing the
  log if the GUI exits immediately;
- errors (non-zero) when no DISPLAY is resolvable.

Runbook §4.7 updated with the "if nothing opens" guidance. 13 tests; green ≥90%.
2026-06-08 00:25:37 +02:00
Simone b612ea9d2c Merge branch 'feat/vm-open-no-vmx' (vmgui: GUI launcher, optional VMX) 2026-06-08 00:16:54 +02:00
Simone 41d64fc212 feat(vmgui): GUI launcher command, VMX optional
Add top-level `vmgui` to open the VMware Workstation GUI (renamed from the
earlier `vm open`). --vmx is optional: with it the GUI opens that VM, without
it the GUI starts bare. Power-on/fullscreen flags ignored (with a notice) when
no VMX is given. Needs an X display — sudo -u ci-runner strips $DISPLAY, so
--display / xhost; see runbook §4.7. 9 tests; suite green ≥90%.
2026-06-08 00:16:50 +02:00
Simone dc06a560d8 Merge branch 'feat/phase-c-runbook-tooling' (vm open + ci alias + manual runbook) 2026-06-08 00:12:01 +02:00
Simone 60c829887c docs: add complete manual CLI runbook (all commands + ci alias + vm open)
Rewrite docs/RUNBOOK-PhaseC.md as a full manual-command reference: every
ci_orchestrator command with a working example as the ci-runner user, the `ci`
alias setup, quick-reference table, manual VM lifecycle (incl. vm open GUI
launch with the xhost/--display procedure), templating, maintenance, Phase C
testing, cutover checklist and a troubleshooting map.
2026-06-08 00:11:36 +02:00
Simone 22b1b797d0 feat(setup): add ci shell alias helper + install it from setup-host
scripts/install-ci-alias.sh installs `ci` (runs the orchestrator as ci-runner
via the prod venv) and `ci-me` (current user). Idempotent, marker-bounded;
supports --system (/etc/profile.d), --uninstall. setup-host-linux.sh gains a
step [7/7] that installs it system-wide, passing CI_PROD_PYTHON/CI_RUN_USER.
2026-06-08 00:11:36 +02:00
Simone cac38f7c0c feat(vm): add vm open to launch the VMware Workstation GUI
Open a VMX in the interactive Workstation GUI (not the headless vmrun path) for
hands-on debugging of a clone. Spawns `vmware` detached (own session, stdio
detached) and returns the child PID.

Flags: --vmx (required), --power-on (-x), --fullscreen (-X), --new-window (-n),
--display (sudo -u ci-runner strips $DISPLAY, so allow forcing it),
--vmware-path. Warns when no X display is set. 7 tests; vm.py 96%.
2026-06-08 00:11:36 +02:00
Simone 07053c993f Merge branch 'feat/phase-c-powershell-removal' (Phase C: pwsh-free Linux host) 2026-06-07 20:16:41 +02:00
Simone 10e7240a70 docs: record Phase C cutover; close $IsWindows shim bug (Phase C9)
- CLAUDE.md: Linux host no longer runs pwsh; PS 5.1 constraints scoped to
  Windows guests + Windows runner.
- RUNBOOK: add "Phase C — Python equivalents" table; §6-§13 benchmark records
  kept as historical provenance.
- TODO §3.7: close $IsWindows shim bug (resolved-by-replacement — bench run
  bypasses the shim).
- plans: mark C1-C8 done + docs C9; flag operational cutover/uninstall pending.
2026-06-07 20:15:10 +02:00
Simone af1960bbf3 feat(setup): make pwsh opt-in on the Linux host (Phase C8)
Invert step 6 default: pwsh is no longer installed. Add --with-pwsh to opt in;
keep --skip-pwsh as a deprecated no-op alias. Update usage/help.
2026-06-07 20:15:10 +02:00
Simone a697491d4f ci(lint): move PSScriptAnalyzer off the Linux runner (Phase C7)
Apply analysis §4 option 1: pssa job runs on windows-build with
shell: powershell, gated to workflow_dispatch so push/PR don't queue it when
no Windows runner is registered. The python job stays on linux-build. No job
on a Linux runner uses pwsh anymore.
2026-06-07 20:15:10 +02:00
Simone 799e8a755e chore(scripts): retire PS benchmark modules superseded by bench (Phase C3)
Remove _Common.psm1, _Transport.psm1 and their Pester test, plus their sole
consumer Measure-CIBenchmark.ps1 — all replaced by `bench measure`. Leaving
the script would dangle an Import-Module on a deleted .psm1.
2026-06-07 20:15:10 +02:00
Simone 575cc81b8f feat(cli): port burn-in/smoke/validate/creds to Python (Phase C1-C6)
Add four native command groups so the Linux host no longer needs pwsh for
manual ops. __main__ registers all four at once, so they ship together.

- bench run     (C2): N concurrent jobs in-process via concurrent.futures,
                      per-round orphan-clone + stale-lock assertions. Replaces
                      Test-CapacityBurnIn.ps1 + Start-BurnInTest*.ps1. No
                      Start-Job/pwsh/$IsWindows.
- bench measure (C3): clone/start/IP/transport/destroy timings appended to
                      benchmark.jsonl (legacy field names kept). Replaces
                      Measure-CIBenchmark.ps1.
- smoke run     (C4): one E2E job; asserts exit 0 + artifact dir + job/success
                      event. ns7zip/nsinnounp presets. Replaces Test-Smoke.ps1
                      + Test-*Build-Linux.ps1.
- validate host (C5): vmrun/snapshot/keyring/permissions/systemd checks.
- validate guest(C6): transport probe with explicit failure cause.
- creds set     (C6): keyring writer matching credentials.py read scheme;
                      password via stdin/prompt only. Replaces
                      Set-CIGuestCredential.ps1.

All groups import backends.load_backend only (Phase D ESXi path stays open).
Suite green, coverage 95.10% (gate 90%); new modules at 100%.
2026-06-07 20:15:10 +02:00
Simone 0bc88c66b7 chore(plans): archive completed Phase B docs
Phase B is closed (checklist Passi 1–9 [x], benchmark matrix §6–§14
recorded). Move the two finished working docs into the dated archive:

  plans/PhaseB-user-checklist.md   → plans/archived/2026-06-07/
  plans/benchmark-windows-host.md  → plans/archived/2026-06-07/

plans/ now holds only the forward-looking plans (idea-3 Phase C pwsh
removal, idea-4 Phase D ESXi, ideas-overview). Fixed the live links that
pointed at the moved/relocated files: idea-3 §4, idea-2 §8 (relative path),
and ideas-overview (idea-1/idea-2 now under archived/, Phase A+B marked ✅).
2026-06-07 19:41:24 +02:00
Simone fce03be201 docs: close Phase B Passo 9 — dual-boot procedure (RUNBOOK §15)
RUNBOOK §15: dual-boot operation — Linux⇄Windows switch procedure, per-OS
CI-stack table (roots/runner/vmrun), current GRUB behaviour (DEFAULT=0,
TIMEOUT=0 hidden → boots straight to Linux), post-boot verification commands
for both OSes, and an optional GRUB tweak (left unapplied — boot-to-Linux is
intentional so the box returns as the Linux CI host after any unattended
reboot).

Checklist: Passo 9 done; fixed the stale global tracking table (Passi 6–9
were still [ ] despite being complete). Phase B is now fully closed (1–9 [x]).
2026-06-07 19:38:32 +02:00
Simone 3c0470351b docs: Linux-guest benchmark (RUNBOOK §10) + Windows-host benchmark plan
§10: single-job Measure-CIBenchmark on LinuxBuild2404 (DHCP, 10 iter) on the
Linux host — boot total 8.82s avg, near-deterministic even on DHCP (IP-acquire
7.45s, σ≈0.01). Guest-vs-guest table: Linux guest ~63% faster boot total than
the Windows static-IP guest (§8) on the same host.

plans/benchmark-windows-host.md: dual-boot plan to collect the symmetric
Windows-host numbers (§11 Win static, §12 Linux DHCP, §13 concurrent burn-in)
so the host×guest×IP-mode matrix is complete and Linux-vs-Windows is a
column-for-column comparison. Includes Windows paths, Credential Manager
prereqs, and the BaseClean-Linux snapshot caveat.
2026-06-07 01:57:16 +02:00
Simone 8db927f843 docs: close Phase B (B7 concurrent burn-in PASS + B8 stability)
Concurrent capacity burn-in run on the Linux host as ci-runner with the
static-IP pool: WinBuild2025 40/40 PASS (~78.6s/round) and LinuxBuild2404
40/40 PASS (~70.2s/round) — 80/80 jobs, 20/20 rounds, near-deterministic
(static IP eliminates the DHCP IP-acquire variance of the §6/§7 baselines).

- RUNBOOK §9: concurrent burn-in result + ip-pool reset/recovery note.
- PhaseB-user-checklist Passo 7/8 marked done; fixed two checklist bugs:
  run as ci-runner (the simone path needs g+w on the /var/lib/ci root for
  ip-pool.tmp, not just the sub-dirs), and the Linux command was missing
  -SnapshotName BaseClean-Linux (default BaseClean is Windows-only).
- idea-2-linux-host §8 DoD: Phase B complete (B1–B7 + >2 weeks stability).
- TODO §2.8 [P2]: ip-pool is not reconciled against live clones; a killed
  job leaks its lease permanently and exhausts the 4-IP pool. Proposed fix.
2026-06-07 01:44:56 +02:00
Simone 514f811d1a docs: postmortem + changelog for 2026-06-06 template/DHCP session
Postmortem of the Linux template deploy/prepare 'no IP' investigation:
separates the three distinct causes (apt index stale from --skip-update,
deploy --force snapshot-chain, wedged host vmnet-dhcpd) and documents the
recovery (restart vmware-networks). Changelog entry summarising the
toolchain, xvfb, --force cleanup and cloud-init seal changes.
2026-06-06 13:38:34 +02:00
Simone a211833316 feat(build): optional xvfb-run for Linux GUI builds
Add an --xvfb switch to 'build run' and 'job' that wraps the Linux build
command in 'xvfb-run -a sh -c ...', giving GUI toolkits (GTK4/PyGObject)
a headless $DISPLAY. Exported env vars precede xvfb-run so they
propagate into the virtual-display session. No-op (with a notice) for
Windows guests.

The local-ci-build composite action exposes it as the 'xvfb' input
(default false), forwarded as INPUT_XVFB -> --xvfb, mirroring the
use-shared-cache/skip-artifact wiring.

Docs: WORKFLOW-AUTHORING.md parameter table + Linux example note;
LINUX-TEMPLATE-SETUP.md Step 4a (the template ships python3-gi
gir1.2-gtk-4.0 xvfb). Adds build-run unit tests (wrap, plain, Windows
no-op).
2026-06-06 13:38:34 +02:00
Simone 1ff02e97a8 fix(template): clean snapshot chain on deploy --force; seal cloud-init; harden first-boot IP
deploy-linux --force reused the VM directory but left any prior snapshot
chain in place, so vmware-vdiskmanager refused the Step 4 resize with
'disk is part of a snapshot chain'. Add _clean_force_artifacts to remove
snapshot deltas (.vmdk), .vmsn, .vmsd and stale .lck before resize.

prepare-linux Step 7d seals the image: drop the stale cloud-init netplan
(leaving only the MAC-agnostic 99-ci-dhcp-allnics), regenerate networkd
config, and disable cloud-init so it never re-runs on linked clones.

deploy user-data sets package_update/upgrade: false so cloud-init does
not hold the dpkg lock at first boot and delay open-vm-tools past the
IP-detection poll; the step-7 IP timeout is raised 300s -> 600s as a
safety net for slow first boots.

Adds unit tests for _clean_force_artifacts (snapshot chain, lock dir,
clean-dir no-op) and updates prepare-linux stubs for the seal step.
2026-06-06 12:55:34 +02:00
Simone 43442eecd4 feat(template): add GTK4/PyGObject/Xvfb toolchain deps; always apt-update
Step 4a installs python3-gi gir1.2-gtk-4.0 xvfb for headless PyGObject
GUI tests, with import-gi and Xvfb assertions.

Step 1 now always runs apt-get update; --skip-update only skips the
(slow) full upgrade. Previously --skip-update skipped the index refresh
too, so a fresh package (gir1.2-gtk-4.0/xvfb) could not be located.
2026-06-06 12:55:22 +02:00
Simone 2b2d8d9968 fix: eliminate Linux build clock skew from RTC/NTP race
Linux clones could boot with the RTC interpreted as local time (guest
runs hours ahead of UTC). NTP then steps the clock backward at runtime,
landing after source mtimes were stamped, so make reported "Clock skew
detected" and future-dated files.

- build run: enable NTP and block until NTPSynchronized before stamping
  the sources, so the backward correction happens pre-touch. Drop
  `hwclock -s`, which read the local-time RTC and pushed the clock further
  ahead.
- template prepare-linux: set-local-rtc 0 so clones interpret the RTC as
  UTC and boot at the correct time; NTP then only nudges, never steps 2h.

Linux/SSH path only; WinRM branch untouched.
2026-05-31 17:45:32 +02:00
Simone 85003d1ea2 feat: add guest IP retrieval functions and enhance VM preparation logic 2026-05-27 23:34:37 +02:00
Simone c391c9147e Add tests for SSH transport and template deployment functionality
- Introduced a new test to verify behavior when SSH transport connects but is not ready, ensuring it returns False after the deadline.
- Added comprehensive tests for the `deploy-linux` template, covering various scenarios including missing public keys, VM directory existence, SHA256 mismatches, and failures during ISO creation and VM conversion.
- Implemented tests for SSH-related functions, including connection retries and streaming command execution, ensuring proper handling of exit statuses and progress indications.
- Enhanced existing tests to cover edge cases and ensure robustness in the deployment process.
2026-05-27 22:53:24 +02:00
Simone cf165e9651 fix: improve data extraction in _read_events function for better type handling 2026-05-26 21:34:07 +02:00
Simone bc67e1ac29 test: bypass real polling loops in job timeout tests and adjust timeout parameter 2026-05-26 21:32:38 +02:00
Simone 6041bca11e fix: streamline elapsed time extraction in _read_events function 2026-05-26 21:27:13 +02:00
Simone e2ab05e297 Add coverage gap tests for command functionalities
- Implement tests for disk alert JSON output to include a message field when below threshold.
- Add tests for runner status when Gitea is online but no webhook URL is provided.
- Introduce tests for job report details to ensure no error line appears when there are no failure events.
- Add tests to skip empty JSONL files in job report listing.
2026-05-26 21:18:20 +02:00
Simone d0314351e4 refactor: simplify shutil usage in workstation backend tests 2026-05-26 19:00:38 +02:00
Simone 0e609b22d1 test: add unit tests for VmState and VmHandle functionality in protocol.py 2026-05-26 18:57:58 +02:00
Simone d6194cfb6d test: update IP pool test to use Windows VMX and clarify test purpose 2026-05-26 18:51:01 +02:00
Simone cb242a2cb4 chore: remove accidentally tracked C:test-vcpkg submodule ref 2026-05-26 18:43:59 +02:00
Simone 839d0c2166 refactor: streamline error handling with contextlib in template preparation 2026-05-26 18:42:32 +02:00
Simone c81bc948fc fix(lint): change Python job runner from Windows to Linux and update shell to Bash
fix(script): add CmdletBinding attribute to Set-GuestInfoIP function for better process support
2026-05-26 18:38:56 +02:00
Simone 78c240d6ac fix(lint): change job runner from windows to linux and update shell to pwsh 2026-05-26 18:35:19 +02:00
Simone a89e6f77ce fix(job): handle SIGTERM and skip static IP for Linux guests
- Install SIGTERM handler that raises KeyboardInterrupt so act_runner
  workflow cancellations trigger the existing VM cleanup path
- Resolve guest_os before the ip_pool block and skip guestinfo IP
  injection for Linux (template doesn't read VMX guestinfo to configure
  network, causing SSH probe to fail against the wrong IP)
2026-05-26 01:03:16 +02:00
Simone 954d411b69 docs(runbook): add three-way B6/B7/B8 comparison to static IP baseline 2026-05-25 22:28:24 +02:00
Simone 5fe1d8807c docs(runbook): fix B8 template name (WinBuild2025, not 2022) 2026-05-25 22:27:00 +02:00
Simone 9577b8ef14 docs(runbook): add B8 static IP baseline (WinBuild2022, 4 iterations)
Static IP via ip_pool: 24 s boot-to-ready vs 102 s DHCP (4x faster),
IP acquire now deterministic (σ < 0.03 s vs σ ≈ 57 s with DHCP).
2026-05-25 22:26:09 +02:00
Simone 591e5a7205 feat(benchmark): add static IP support to Measure-CIBenchmark
- Add -StaticIP/-Netmask/-Gateway params; injects guestinfo into cloned
  VMX before start, mirroring job.py _inject_guestinfo_ip behaviour
- Add -GuestInfoOnly switch to Get-GuestIPAddress: skips vmrun
  getGuestIPAddress fallback to prevent race where transient DHCP address
  is returned before ci-static-ip.ps1 finishes reconfiguring the NIC
- Benchmark passes -GuestInfoOnly automatically when -StaticIP is set
- staticIP field added to benchmark.jsonl for DHCP vs static comparison
2026-05-25 22:21:27 +02:00
Simone 918c46968b chore: add .venv and .coverage to .gitignore 2026-05-25 22:04:22 +02:00
Simone 387f93f3c6 fix(template): make prepare-win robust without prior Deploy run
- WinRM MaxProcessesPerShell: apply 100 if below threshold before asserting
- DesktopHeap SharedSection: apply 4096 KB registry value if below threshold before asserting
- Replace cleanmgr (hangs in WinRM Session 0, no window station) with direct
  PowerShell cleanup for dumps, WER files and Recycle Bin; DISM already covers
  component store and WU cache
- vcpkg git clone/checkout: wrap with EAP=Continue to suppress PS5.1
  NativeCommandError on git stderr (gotcha #3 from AGENTS.md)
- template.py: filter CI_EXITCODE:N marker from displayed output
- ci-static-ip.ps1: switch from vmware-rpctool (removed in recent Tools) to
  vmtoolsd --cmd; use temp-file redirect to capture output from native cmd
2026-05-25 22:03:49 +02:00
Simone e1afd61392 fix(template): move disk cleanup after final reboot, before shutdown
Cleanup now runs after the system reboots post-install (fully settled
state), not right after the WU loop. Sequence: WU loop → static IP →
validation → final reboot → WinRM ready → [cleanup] → autologin →
shutdown → snapshot.
2026-05-24 02:47:45 +02:00
Simone 5867fe1e12 feat(template): cleanup-after-update + ruff fixes
Move disk cleanup out of the Windows Update loop:
- PS scripts: extract Invoke-DiskCleanup function, add -CleanupOnly switch
  for standalone post-update cleanup pass.
- Python command: always pass -SkipCleanup:$true during WU loop; after
  loop exits 0, run a separate -CleanupOnly invocation (skipped if
  --skip-cleanup). Fails fast if cleanup-only returns nonzero.
- Tests: 3 new tests for cleanup-runs-after-loop, skip-cleanup suppresses
  it, and cleanup-failure propagates as ClickException.
- ruff --fix: remove unused shutil import, sort imports, fix UP037/UP035.
2026-05-24 02:41:15 +02:00
Simone 3c700e0560 fix(template): restore tty state before snapshot prompt to fix ^M issue
pypsrp alters terminal tty settings during WinRM sessions. This causes
readline() at the snapshot prompt to receive \r (not \n) on Enter,
printing ^M repeatedly without ever unblocking.

Fix: save termios attributes at command start (before any WinRM work,
terminal is clean) and restore them with tcsetattr(TCSADRAIN) before
the interactive snapshot prompt. Gracefully skips save/restore when
stdin is not a tty (tests, pipes, CI).

Revert the pre-flight snapshot check approach (commit 97d9dab) — checking
at start was architecturally wrong (snapshot state may change during
provisioning). The prompt stays at the end where it belongs.
2026-05-24 02:19:05 +02:00
Simone 97d9daba65 fix(template): move snapshot conflict check to pre-flight (before WinRM)
pypsrp alters terminal tty state during remote sessions. Any readline()
call after WinRM work receives \r instead of \n on Enter (terminal in
raw mode), causing the prompt to spin printing ^M and never return.

Fix: check for existing snapshot and ask the user at the very beginning
of the command, before any WinRM/pypsrp connections are opened, when the
terminal is still in canonical mode. If the user declines, abort early.
At the end, if the snapshot still exists (race-safe), delete silently
and recreate — no second prompt needed.
2026-05-24 02:15:51 +02:00
Simone f24f7fd07a fix(template): use sys.stdin.readline() for snapshot confirm prompt
input() with a prompt string on some terminals (zsh + Linux Mint) echoes
^M and the prompt doesn't flush before reading, causing the prompt to
appear stuck or not accept input.

Replace with explicit sys.stdout.write()+flush() + sys.stdin.readline()
which guarantees the prompt is flushed before blocking on stdin, and
readline() + .strip() handles both \n and \r\n line endings correctly.

Tests updated to use CliRunner(input=) which populates sys.stdin inside
the runner context (monkeypatching sys.stdin.readline directly is ignored
by CliRunner which replaces sys.stdin entirely).
2026-05-24 02:07:36 +02:00
Simone ce0434e2d5 fix(template): replace click.confirm with input() to avoid ^M on Linux terminals
click.confirm() doesn't strip \r — "y\r" doesn't match "y" and the prompt
loops or fails. Replace with raw input().strip().rstrip('\r').lower() which
handles both \n and \r\n line endings.

Also add --recreate-snapshot flag to skip the interactive prompt entirely
(useful for scripted re-provisioning).

Update snapshot_exists_skip/recreate tests to monkeypatch builtins.input
instead of using CliRunner input=.
2026-05-24 02:02:22 +02:00
Simone 73af48b029 feat(template): heartbeat thread for long-running toolchain install
execute_ps is fully blocking with no streaming. Add _run_with_heartbeat()
which runs the WinRM call in a daemon thread and prints
"  ... still running (Ns elapsed) ..." every 30 s so the operator can
distinguish a hung session from a slow Windows Update pass.

Used only for the toolchain script invocation (the slow step); all other
transport.run() calls keep the direct path.
2026-05-24 01:52:27 +02:00
Simone 2fa9a080d6 feat(template): add --store-credential to prepare-win + full docstring
- --store-credential: after snapshot, writes build-user credentials into
  the system keyring using the two-entry scheme (service:meta + service)
  that KeyringCredentialStore.get() reads. Works on Windows (Credential
  Manager) and Linux (SecretService / file backend).
- --credential-target: keyring target name, default BuildVMGuest (matches
  config.guest_cred_target).
- Rewrote module docstring: full prose documentation of both `template
  backup` and `template prepare-win` including all 12 provisioning steps.
- 2 new tests covering --store-credential with custom and default targets.
- mypy --strict clean; coverage 90.03%.
2026-05-24 01:30:57 +02:00
Simone 347608d5ac feat(template): add template prepare-win Python command via pypsrp
Replaces the abandoned PSWSMan approach (New-PSSession hangs on Linux)
with a Python-native implementation using the proven pypsrp WinRmTransport.

- New `template prepare-win` command in commands/template.py: starts VM,
  waits for WinRM, uploads and runs Install-CIToolchain-*.ps1, handles
  Windows Update 3010 reboot loop (max 10 iterations), installs CI-StaticIp
  scheduled task, runs post-setup validation, graceful shutdown, snapshot.
- Helpers: _ps_escape, _wait_tcp, _parse_exit_marker, _wait_winrm.
- 40 new tests in test_commands_template.py covering all major branches.
- Fix test_commands_job.py: patch load_config in _patch_common so tests
  don't pick up live /var/lib/ci/config.toml ip_pool and fail on lock.
- Coverage gate: 90.06% (was 88.95% before fix).
2026-05-24 01:22:53 +02:00
Simone 5014c86f26 fix(templates): import PSWSMan module on Linux before New-PSSession
Without Import-Module PSWSMan the native WSMan library isn't registered
and New-PSSession hangs/fails silently. Import guarded by $IsWindows so
Windows PS 5.1 is unaffected.
2026-05-24 00:53:19 +02:00
Simone a464484c91 fix(templates): skip TrustedHosts config on Linux host
WSMan TrustedHosts is a Windows WinRM client concept — does not exist
on Linux. Guard the configure+restore blocks with $isLinuxHost so the
Prepare scripts run without elevation errors from a Linux host.
-SkipCACheck/-SkipCNCheck in sessionOptions is sufficient on Linux.
2026-05-24 00:50:25 +02:00
Simone 68df07c623 fix(templates): drop -SkipRevocationCheck from New-PSSessionOption
Parameter not available in PS 7 on Linux. -SkipCACheck -SkipCNCheck
sufficient for self-signed lab certs; revocation check is irrelevant
for self-signed certs regardless.
2026-05-24 00:48:13 +02:00
Simone 773ee2cf3d fix(templates): replace Test-NetConnection with TcpClient in Prepare scripts
Test-NetConnection is Windows-only. Replace with cross-platform TcpClient
async-connect helper (same pattern as Measure-CIBenchmark.ps1) so the
Prepare scripts run on the Linux host under PowerShell.
2026-05-24 00:46:19 +02:00
Simone 5aa2d9c697 feat(templates): install ci-static-ip/ci-report-ip via Prepare+Install scripts
Windows (Prepare-WinBuild2025/2022): read ci-static-ip.ps1 from
guest-setup/windows/ on the host, push content over WinRM, register the
CI-StaticIp scheduled task (AtStartup/SYSTEM). Adds StaticIpTask and
StaticIpScript post-setup assertions.

Linux (Install-CIToolchain-Linux2404.sh): embed ci-report-ip.sh and
ci-report-ip.service content inline via heredoc tee, replacing the broken
cp-from-relative-path approach (the script runs inside the VM where the
host-side guest-setup/ directory does not exist).
2026-05-24 00:37:07 +02:00
Simone 4228578a42 feat(ip-pool): add guest-side static-IP boot scripts
template/guest-setup/linux/ci-report-ip.sh
  Updated version: checks guestinfo.ip-assignment first (static path),
  falls back to DHCP detection if absent.  Applies static IP before
  network-online.target so SSH starts on the correct address immediately.

template/guest-setup/linux/ci-report-ip.service
  Drops 'network.target' from After= — runs before network is up so the
  static IP is configured before SSH/other services start.

template/guest-setup/windows/ci-static-ip.ps1
  Reads guestinfo.ip-assignment via vmware-rpctool at system startup (SYSTEM
  account Task Scheduler task).  Disables DHCP, applies static IP+gateway,
  reports back via guestinfo.ci-ip.  No-op when ip-assignment is absent.

template/Install-CIToolchain-Linux2404.sh
  Step 7b now copies scripts from guest-setup/linux/ instead of embedding
  the old DHCP-only inline script.
2026-05-24 00:27:10 +02:00
Simone 32c723147f feat(ip-pool): pre-assign static IPs to build VMs via VMX guestinfo
Eliminates VMware-Tools IP-detect polling (observed: 39–177 s variance on
Linux host B7 benchmark).  When [ip_pool] is configured the orchestrator:

  1. Allocates a slot IP from a JSON-backed pool (fcntl-locked on Linux)
  2. Injects guestinfo.ip-assignment / netmask / gateway into the cloned VMX
     before vmrun start
  3. Skips _wait_for_ip entirely — uses the pre-assigned IP immediately
  4. Releases the slot in _destroy_clone (all paths: success/failure/interrupt)

New modules / changes:
  src/ci_orchestrator/ip_pool.py      IpSlotPool + _file_lock context manager
  src/ci_orchestrator/config.py       IpPoolConfig dataclass; [ip_pool] TOML
  src/ci_orchestrator/commands/job.py _inject_guestinfo_ip helper; pool wiring
  tests/python/test_ip_pool.py        12 unit tests (acquire/release/persist)
  tests/python/test_config.py         ip_pool config parsing tests
  tests/python/test_commands_job.py   pool path + _inject_guestinfo_ip tests
  config.example.toml                 [ip_pool] section (commented example)

Coverage 90.02%; mypy --strict clean.
2026-05-24 00:26:59 +02:00
Simone efb3f55c03 docs(runbook): add Linux host benchmark baseline (B7 result)
4-iteration run on Linux Mint host, WinBuild2025/BaseClean.
Clone/Start/Ready/Destroy within ±20% of Windows baseline.
IP-acquire avg 99.7 s vs 58.2 s Windows — high variance (39–177 s),
attributed to VMware Tools reporting latency, not orchestrator regression.
2026-05-24 00:08:42 +02:00
Simone f26e976a85 fix(benchmark): totalBootSec null when readySec=0 (PS falsy 0.0)
if ($t.ready) evaluates false when ready=0.0 — use $null -ne guard.
2026-05-24 00:07:04 +02:00
Simone 048b6334c0 fix(benchmark): adapt Measure-CIBenchmark to Linux host
Measure-CIBenchmark.ps1:
- Default params now Linux-aware via $IsWindows conditional
  (TemplatePath, CloneBaseDir, VmrunPath, OutputDir)
- Add -GuestOS Auto/Windows/Linux param; Auto reads VMX guestOS field
  (ubuntu-* → Linux, else Windows)
- Replace Test-NetConnection (Windows-only) with Test-TcpPort helper
  using raw TcpClient async connect — works on PS5.1 and PS7/Linux
- Probe SSH/22 for Linux guests, WinRM/5986 for Windows guests
- Rename winrm→ready throughout ($t, JSONL field readySec, column header)
- Add guestOS field to JSONL output

_Common.psm1 / Invoke-VmrunBounded:
- Replace taskkill /F /T with platform-aware kill:
  Windows → taskkill; Linux → Process.Kill(entireProcessTree) (.NET 5+)
2026-05-23 23:57:32 +02:00
Simone fdbb0369f6 docs(runbook): fix table alignment in B7 baseline section 2026-05-23 23:50:01 +02:00
Simone 6e6f3fbf49 fix(burn-in): migrate Start-BurnInTest wrappers to Linux host paths
Replace Windows-hardcoded paths with Linux equivalents:
- RepoUrl → local Gitea instance (10.10.20.11:3100/Simone/burnin-dummy)
- TemplatePath/CloneBaseDir/ArtifactBaseDir/VmrunPath → /var/lib/ci/…
- `$scriptDir\Test-CapacityBurnIn.ps1` → Join-Path $PSScriptRoot (cross-platform)
- Remove `Split-Path -Parent $MyInvocation.MyCommand.Path` (redundant)
2026-05-23 23:49:59 +02:00
Simone f50b7bb962 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).
2026-05-23 23:44:07 +02:00
Simone 36a988171a fix(plans): add -BuildCommand to B7 burn-in commands
Without an explicit build command, job.py runs dotnet build (which fails
on nsis-plugin-ns7zip in a fresh VM) and expects bin\CIOutput as artifact
source. With a trivial BuildCommand, artifact_source defaults to "dist"
and the lightweight command always succeeds.

Also documents one-time simone group/keyring prerequisites for running
the burn-in as simone with ci-runner group access.
2026-05-23 23:03:52 +02:00
Simone 1166ba7ed8 fix(plans): prefix B7 burn-in commands with sudo -u ci-runner
/var/lib/ci/ is owned by ci-runner; running as simone causes
PermissionError on artifact dir creation.
2026-05-23 22:45:43 +02:00
Simone 62e7273ea2 fix(shims): add Linux fallback for venv Python path in all PS shims
All shims defaulted to F:\CI\python\venv\Scripts\python.exe when
CI_VENV_PYTHON was not set. On Linux host this caused instant failure.

Now uses $IsWindows (PS 6+ automatic var; absent on PS 5.1 = Windows)
to select /opt/ci/venv/bin/python on Linux. Windows paths unchanged.

Affects: Invoke-CIJob, Get-BuildArtifacts, Cleanup-OrphanedBuildVMs,
New-BuildVM, Remove-BuildVM, Wait-VMReady, Get-CIJobSummary,
Invoke-RemoteBuild, Watch-DiskSpace, Watch-RunnerHealth,
Set-CIGuestCredential, Test-CIGuestWinRM.
2026-05-23 22:43:48 +02:00
Simone 3706587a97 fix(burn-in): move psExe detection inside Start-Job scriptblock
Passing $psExe as a second -ArgumentList item caused double-wrapping of
the string array, collapsing all args into one string when splatted.
Computing $IsWindows inside the worker avoids the extra param entirely.
2026-05-23 22:41:28 +02:00
Simone 9dde16303b fix(burn-in): use pwsh instead of powershell.exe on Linux host
powershell.exe does not exist on Linux — child process launch failed
instantly (ExitCode null, Elapsed 00:00). Now selects powershell.exe
on Windows and pwsh on Linux via $IsWindows automatic variable.
2026-05-23 22:39:32 +02:00
Simone 3e7af81520 fix(plans): correct Test-CapacityBurnIn.ps1 commands in B7
-Concurrency → -Parallelism, -Template → -TemplatePath; add mandatory
-RepoUrl and Linux-host paths (-CloneBaseDir, -ArtifactBaseDir,
-VmrunPath). Add explicit LinuxBuild2404 command with -GuestOS Linux.
2026-05-23 22:37:18 +02:00
Simone 82db3a8649 docs(runbook): record Windows host pre-migration baseline for B7
Data from Measure-CIBenchmark.ps1 × 4 (2026-05-17, commit 5b46bba):
avg boot 60.6s, IP-acquire dominant (20–85s, σ≈26s). Adds ±20% ranges
for B7 comparison and guidance on sample size given IP-phase variance.
2026-05-23 22:25:11 +02:00
Simone f81fcca572 fix(plans): restore PhaseB-user-checklist.md — phase not fully complete
Burn-in (4 jobs × 10 rounds), 7-day stability run, and RUNBOOK.md
Linux section are still open. Checklist stays active until all
acceptance criteria are met.
2026-05-23 22:02:38 +02:00
Simone 8ef159567c docs(lifecycle): add CI/CD job lifecycle documentation with flowchart and phase breakdown 2026-05-23 21:56:59 +02:00
Simone 70e89879dc chore(plans): archive Phase A and B completed documents
Moves all A/B closeouts, checklists, idea docs, and implementation plans
to plans/archived/2026-05-23/. Both phases are production-stable.

Active plans remaining in plans/:
  - idea-3-powershell-removal.md  (Phase C — in progress)
  - idea-3-esxi-support.md        (Phase D — future)
  - ideas-overview.md             (roadmap reference)
2026-05-23 19:52:09 +02:00
Simone 8d0cc3cca4 chore(tools): add graph_query.py dev utility under tools/
Relocates the graphify BFS/path query script from scripts/ (a PowerShell
folder) into a new tools/ directory for Python developer utilities.

The script is not part of the CI pipeline — it's a local query tool for
navigating graphify-out/graph.json during codebase investigation.
2026-05-23 19:50:21 +02:00
Simone cafdd738b0 chore(gitignore): ignore graphify-out/ generated directory
graphify-out/ contains the knowledge graph, AST/semantic cache, and
HTML report — all regenerated by /graphify. No value in tracking them.
2026-05-23 19:50:14 +02:00
Simone 05defec65b docs(plan): close Phase B Passo 6 (cutover smoke + monitoring)
ns7zip matrix Win+Linux passes after the WinRM quotas + desktop-heap
fixes landed in the template scripts. Monitoring window completed
without regressions.
2026-05-21 23:43:28 +02:00
Simone 93f5a88f77 fix(templates): bump WinRM quotas + desktop heap for parallel Win builds
Parallel MSBuild fan-out (e.g. ns7zip x86/x64/x86-ansi) over pypsrp
Linux→Win fails with MSBuild MSB6003 / Win32 1816 'Not enough quota'
because two default guest limits are too tight for concurrent native
processes via WinRM:

  - Plugin Microsoft.PowerShell MaxMemoryPerShellMB = 150 MB
    (pypsrp uses the plugin endpoint, not the Shell endpoint that
    Deploy was previously bumping)
  - Non-interactive desktop heap SharedSection 3rd field = 768 KB
    (Session 0 csrss; CreateProcess fails when 3+ cl.exe spawn)

Pre-migration Win→Win went through Invoke-Command on PSSession with
quotas inherited from the local process, so the limits never bit.

Changes:
  - Deploy-WinBuild2025.ps1 / Deploy-WinBuild2022.ps1: also set the
    plugin-level MaxMemoryPerShellMB=2048, bump MaxProcessesPerShell
    25→100, patch SharedSection 3rd field 768→4096 (reboot needed for
    csrss to pick up, applied by post-install shutdown).
  - Install-CIToolchain-WinBuild2025/2022.ps1: add Assert-Step for
    plugin quota, MaxProcessesPerShell≥100, SharedSection≥4096; fail
    hard if any are under threshold.
  - docs/WINDOWS-TEMPLATE-SETUP.md: new "WinRM quotas e desktop heap"
    section explaining the diagnosis + one-shot patch for legacy
    templates; updated Step 3 validation row + troubleshooting entry.
  - AGENTS.md: error #13 with symptom, root cause, and pointer to docs.
2026-05-21 23:35:57 +02:00
Simone 7f0697d718 fix(backup): bring act-runner back up on every termination path
If ci-backup-template.service is killed mid-run (SIGTERM), the Python
finally block that restarts act-runner.service is skipped. A subsequent
backup then finds the runner already inactive, _stop_runner returns
False, and the finally restart is gated off — leaving the runner down
indefinitely (incident: 21 mag 2026, runner stayed off ~80 min).

Add ExecStopPost=-/bin/systemctl start act-runner.service so systemd
guarantees the runner is restarted regardless of how the unit exits
(success, failure, signal). The `-` prefix preserves the unit's own
exit status.
2026-05-21 23:35:43 +02:00
Simone 72af01aa2b docs: add B1–B4 closeout docs and rewrite B5-closeout as completed
B1: Linux Mint + VMware Workstation + setup-host-linux.sh (Python 3.12 note)
B2: rsync template transfer, VMX registration, smoke tests; keyrings.alt choice
B3: SSH key perms, BuildVMGuest + GiteaPAT via PlaintextKeyring (D-Bus-free)
B4: act-runner.service, config.yaml labels/envs fix, smoke job PASS
B5: rewritten — Python-only timers (no pwsh), 7z compression, --service-name
    act-runner fix, prerequisite dirs, root service for backup
2026-05-21 20:57:19 +02:00
Simone 07d5179c2b docs: introduce Phase C (pwsh removal), rename ESXi to Phase D
- Add plans/idea-3-powershell-removal.md: Phase C goal is to eliminate
  the pwsh dependency from the Linux host by porting the remaining PS
  scripts (bench run, validate host, smoke run) to Python sub-commands.
- Update idea-3-esxi-support.md header: Fase C → Fase D.
- Update ideas-overview.md: new four-phase table (A/B/C/D) with
  prerequisiti and criteri for Phase C.
- Rename all "Hook futuri Fase C" (ESXi) → "Hook futuri Fase D" in
  implementation-plan-A-B.md; add Phase C/D entries to summary and
  references section.
- Update PhaseB-user-checklist.md end note and CLAUDE.md to point
  Phase C → pwsh removal, Phase D → ESXi.
2026-05-21 20:46:01 +02:00
Simone b555784de0 feat: B5 — port retention/template-backup to Python, deploy systemd timers
- Add `retention run` command (ports Invoke-RetentionPolicy.ps1): purges
  old artifact/log dirs with aggressive mode when disk space is low.
- Add `template backup` command (ports Backup-CITemplate.ps1): 7z -mx=1
  compressed archives in /var/lib/ci/backups/, prunes to keep-count=3,
  stops/restarts act-runner.service around the copy.
- Update ci-retention-policy.service and ci-backup-template.service to use
  Python; pwsh is no longer required on the Linux host.
- Fix ci-watch-runner-health.service: pass --service-name act-runner
  (Linux service name, not Windows act_runner default).
- Fix _list_orphans in vm.py: wrap is_dir() inside the OSError try block
  so a stat() failure on an entry is silently skipped rather than raised.
- Mark B5 complete in PhaseB-user-checklist.md.
2026-05-21 20:35:07 +02:00
Simone dcf8a6606b feat: update runner configuration steps and add config.yaml generation instructions 2026-05-21 00:45:43 +02:00
Simone 9f74ef4ab4 fix: correct order of arguments for template path in local CI build action 2026-05-21 00:34:19 +02:00
Simone 6ff2089fab fix: update example for extra environment variables and improve Python interpreter resolution for OS compatibility 2026-05-21 00:30:38 +02:00
Simone 8a911ab2d8 feat: update action.yml to use pwsh shell and enhance vm.py with start option; add credential management scripts 2026-05-21 00:11:20 +02:00
Simone cc159be0c1 feat: add setup-host-linux.sh script for CI host bootstrap and update Phase B checklist 2026-05-20 22:13:37 +02:00
Simone 958e4ae035 test: cover easy missing branches in report/config/credentials/ssh/wait/job 2026-05-14 23:23:55 +02:00
Simone 9dcd793e84 test: add coverage for monitor/vm/winrm to reach 90%% gate
Bring monitor.py 49->99%%, vm.py 79->98%%, winrm.py 76->100%%. Total project coverage 80.10%% -> 90.69%%, satisfying the 90%% gate set in commit 01c8011.
2026-05-14 23:14:08 +02:00