Commit Graph

50 Commits

Author SHA1 Message Date
Simone 74331ac301 fix(diagnostics): use StrictHostKeyChecking=no for Linux guest-diag SSH
Diagnostics is best-effort. Using accept-new without UserKnownHostsFile
caused the system known_hosts (~/.ssh/known_hosts) to be consulted,
which holds a stale key from a previous clone of the same IP.
Switch to no+NUL (same as Wait-VMReady) to avoid false host-key errors.
2026-05-12 22:29:57 +02:00
Simone 31a040682e fix(linux): purge stale known_hosts entry before first SSH on each clone
Each Linux BuildVM clone gets a new SSH host key for the same
NAT IP, causing 'host key has changed' errors with accept-new mode.

After IP detection, ssh-keygen -R removes the old entry from the
CI-local known_hosts file (F:\CI\State\known_hosts by default)
before Phase 4/5 make any SSH connection. The file is created if
it does not exist yet. Windows jobs are unaffected.
2026-05-12 22:23:33 +02:00
Simone f765f3e74b feat(burnin): add build.sh for Linux guest + Start-BurnInTest-Linux.ps1
gitea/burnin-dummy/build.sh: bash equivalent of build.ps1 (sleep 30,
writes dist/build-info.txt). Needed because powershell is not present
on LinuxBuild2404 template.

scripts/Start-BurnInTest-Linux.ps1: burn-in wrapper for LinuxBuild2404
template (SnapshotName=BaseClean-Linux, GuestOS=Linux,
BuildCommand='bash build.sh'). Mirrors Start-BurnInTest.ps1.
2026-05-12 22:14:52 +02:00
Simone 9007934dca feat: expose skip-artifact as workflow input in action.yml
action.yml:
  - Add skip-artifact input (default: 'false').
  - Forward as INPUT_SKIP_ARTIFACT env var to the PS run step.
  - Pass SkipArtifact=true to Invoke-CIJob.ps1 when set.
  - Gate 'Upload artifacts' step on inputs.skip-artifact != 'true'.

Invoke-CIJob.ps1:
  - Restore -SkipArtifact switch; skips Phase 6 and logs 'skipped' event.

Invoke-RemoteBuild.ps1:
  - Restore -SkipArtifact switch; skips packaging inside the guest.
  - Error message updated to mention skip-artifact workflow input.
2026-05-12 22:05:17 +02:00
Simone a6acf1eef7 refactor(burn-in): remove -SkipArtifact; build.ps1 always produces dist/ 2026-05-12 22:00:43 +02:00
Simone 9601210baa feat(burn-in): add fake build script for burnin-dummy repo; update default BuildCommand
gitea/burnin-dummy/build.ps1:
  Fake build script to commit to the burnin-dummy repo.
  Creates dist\build-info.txt (date, host, pid) after ~30s ping delay.
  Invoke-RemoteBuild packages dist\ as artifacts.zip.

Start-BurnInTest.ps1:
  Default BuildCommand updated from ping-only delay to build.ps1 invocation.
  Run with -NoSkipArtifact to exercise the full artifact pipeline.
2026-05-12 21:55:38 +02:00
Simone 0a9b3f4348 fix(invoke-cijob): wrap Write-Host format string in parens to avoid -f being parsed as -ForegroundColor 2026-05-12 21:37:03 +02:00
Simone 041f350aa8 fix(burn-in): use hashtable splat for conditional -SkipArtifact in Start-BurnInTest
In PS 5.1, $(if (...) { '-SkipArtifact' }) inside a call produces the
literal string '-SkipArtifact' bound as a positional argument (-> IPList),
not interpreted as a switch name.
Fixed by building a hashtable and setting SkipArtifact = \True when needed.
2026-05-12 21:30:16 +02:00
Simone 80eeb389af fix(burn-in): add -SkipArtifact switch; restore production fail-on-missing-dist
PROBLEM: Invoke-RemoteBuild.ps1 was creating a placeholder artifact when the build
produced no dist/ directory. In production this hides build defects (build exits 0
but produces nothing = silent failure treated as success).

SOLUTION: Add explicit -SkipArtifact switch to the call chain:

Invoke-RemoteBuild.ps1:
  - Add -SkipArtifact switch. When set: skips packaging entirely (no zip created).
  - When absent (production default): missing GuestArtifactSource is a hard error
    with a clear message pointing to -SkipArtifact as the opt-in for no-output builds.

Invoke-CIJob.ps1:
  - Add -SkipArtifact switch. Passes it to Invoke-RemoteBuild.ps1.
  - Phase 6 (Get-BuildArtifacts) is entirely skipped when set; logs 'skipped' event.

Test-CapacityBurnIn.ps1:
  - Add -SkipArtifact switch. Adds '-SkipArtifact' flag to each child job argList.

Start-BurnInTest.ps1:
  - Always passes -SkipArtifact by default (burn-in uses delay commands, no output).
  - Add -NoSkipArtifact override switch for runs that do produce real artifacts.
  - Remove -NoSkipArtifact escape from wrapper: default burn-in never needs artifacts.
2026-05-12 21:28:11 +02:00
Simone e0b45fc71d fix(burn-in): create placeholder artifact when build command produces no output dir
Invoke-RemoteBuild.ps1: when BuildCommand succeeds (exit 0) but GuestArtifactSource
(default: dist) does not exist, instead of silently skipping packaging, now creates a
placeholder 'build-ok.txt' in the artifact output dir and zips it. This ensures
Get-BuildArtifacts.ps1 always finds artifacts.zip regardless of whether the build
produced real output files. Correct for burn-in, smoke, and delay-only build commands.

Start-BurnInTest.ps1: revert default BuildCommand to 'ping 127.0.0.1 -n 31 > nul'
(~30s delay, no artifact output) now that the pipeline handles missing dist gracefully.
The previous 'if not exist dist mkdir dist' workaround is no longer needed.
2026-05-12 21:23:40 +02:00
Simone 8595fbaf45 fix(burn-in): default BuildCommand creates artifact dir to exercise full pipeline
'exit 0' left C:\CI\build\dist non-existent -> packaging skipped -> Get-BuildArtifacts
could not find artifacts.zip -> all 4 jobs FAIL at artifact collection phase.

New default: 'if not exist dist mkdir dist & echo burnin_ok > dist\marker.txt'
This creates dist\marker.txt relative to C:\CI\build (GuestWorkDir), so the
packaging step finds it, creates artifacts.zip, and Get-BuildArtifacts succeeds.

Also updated docblock: clarify command runs via cmd /c; replace Start-Sleep example
(PS cmdlet, does not work via WinRM cmd) with ping delay equivalent.
2026-05-12 21:21:56 +02:00
Simone bb2572f451 feat(burn-in): capacity burn-in harness (Test-CapacityBurnIn.ps1)
scripts/Test-CapacityBurnIn.ps1 (new): N-way parallel burn-in harness using Start-Job
to launch N concurrent Invoke-CIJob.ps1 child processes for R rounds. After each round
asserts: all VMs destroyed, all jobs got distinct IPs, vm-start.lock gone, ip-leases
empty, wall time recorded. Params: -RepoUrl (mandatory), -IPList = @() (optional,
auto-detects via VMware Tools when empty), -GiteaCredentialTarget = 'GiteaPAT',
-Parallelism = 4, -Rounds = 3, -RoundTimeoutMinutes = 60, plus all Invoke-CIJob params.

scripts/Start-BurnInTest.ps1 (new): lab wrapper with all default params hardcoded
(template F:\CI\Templates\WinBuild2025, credentials BuildVMGuest/GiteaPAT, etc.)
for daily use. Exposes -Rounds, -Parallelism, -BuildCommand, -RoundTimeoutMinutes.
2026-05-12 21:13:31 +02:00
Simone c04a2f25aa feat(sprint5): quality pass, smoke test workflow, Get-CIJobSummary
Task 5.1: Delete runner/Install-Runner.ps1 (superseded by Setup-Host.ps1).
Task 5.2: Replace Unicode/Discord emoji in Watch-DiskSpace.ps1, Watch-RunnerHealth.ps1,
          Test-E2E-Section3.3.ps1 with plain-text prefixes ([WARNING], [ALERT], [WARN]).
Task 5.3: docs/TEST-PLAN-v1.3-to-HEAD.md: ForEach-Object -Parallel replaced with Start-Job;
          -RepositoryUrl replaced with -RepoUrl (3 occurrences); PS7 syntax removed.
Task 5.4: scripts/Get-CIJobSummary.ps1 (new): scans JSONL logs in F:\CI\Logs, prints summary
          table per job, -JobId detail view, -Failed filter, -Last N.
Task 3.5: gitea/workflows/self-test.yml (new): workflow_dispatch, smoke-windows + smoke-linux.
          scripts/Test-Smoke.ps1 (new): invokes Invoke-CIJob.ps1 with marker-file build,
          asserts artifact dir and JSONL success event.
Task 5.6: docs/BEST-PRACTICES.md Section 10 added: SHA256 pinning policy, priority table,
          PS 5.1 implementation pattern, update guidance.
2026-05-12 21:13:15 +02:00
Simone 9de86ac289 feat(sprint4): operational hardening, diagnostics, VMDK SHA256, runbook
Task 4.1: Invoke-CIJob.ps1 pre-clone disk gate (needs 10 GB free), JobId/ExtraGuestEnv
          pattern validation, redacted command logger (already included via _Common.psm1).
Task 4.2: Get-GuestDiagnostics helper invoked from catch block (best-effort, no throw).
Task 4.3: UseSharedCache wired through composite action and orchestrator.
Task 4.4: SSH known_hosts in F:\CI\State\known_hosts for CI jobs; accept-new for templates.
Task 4.5: Deploy-LinuxBuild2404.ps1 verifies Ubuntu VMDK SHA256 before import.
Task 4.6: RUNBOOK.md consolidated template-refresh section: stop runner, backup, boot,
          KMS reactivation, Prepare-*, shutdown, snapshot, Validate-DeployState, smoke,
          update config, retain prior snapshot 7 days, rollback procedure.
Validate-SetupState.ps1: minor lint fixes and docblock updates.
Wait-VMReady.ps1: Get-BuildArtifacts.ps1: hardening alignment with Sprint 4 objectives.
2026-05-12 21:12:51 +02:00
Simone 509d1fc284 feat(sprint3): Invoke-VmrunBounded, Get-GuestIPAddress, transport and concurrency hardening
H4: Invoke-VmrunBounded added to _Common.psm1 (Start-Process + Wait-Process + taskkill on
    timeout, returns {ExitCode;Output;TimedOut;ElapsedSeconds}). Wired in Invoke-CIJob.ps1
    for vmrun start (180s) and Remove-BuildVM.ps1 for stop/deleteVM (60s/90s).
H5: Cleanup-OrphanedBuildVMs.ps1 removes stale vm-start.lock (>30 min). ValidateRange
    lowered to 0 to allow emergency -MaxAgeHours 0. Invoke-RetentionPolicy.ps1 same.
H8: _Transport.psm1 SSH hardening: StrictHostKeyChecking=accept-new, F:\CI\State\known_hosts.
H9: Validate-DeployState.ps1 Linux branch added: checks ci-report-ip.service enabled,
    ci-report-ip.sh executable via SSH. New params: -GuestOS, -SshKeyPath, -SshUser.
H12: Get-GuestIPAddress extracted into _Common.psm1 (guestinfo.ci-ip primary, getGuestIPAddress
     fallback). Invoke-CIJob.ps1 Phase 3b replaced with single call. Measure-CIBenchmark.ps1
     updated to match.
Also: _Common.psm1 adds Write-CIRedactedCommand and ConvertTo-SafeShellSingleQuotedString.
     Pester suite updated: 30/30 pass.
2026-05-12 21:12:33 +02:00
Simone 7dff7d3dba feat(sprint2): fix critical and high-priority bugs
C1: composite action selects Linux/Windows template from GITEA_CI_LINUX_TEMPLATE_PATH
    based on resolved guest OS; build-nsis.yml adds belt-and-suspenders guest-os param.
C2: redact ExtraGuestEnv secrets in Invoke-RemoteBuild.ps1 (envPrefix never logged).
H2: composite action outputs block corrected; duplicate artifact-name input removed.
H3: Linux Mode 2 PAT now uses http.extraHeader (mirrors Windows branch, drops GIT_CLONE_URL).
H6: Setup-Host.ps1 directory array updated to canonical layout (adds State, ip-leases, keys,
    Cache\pip, WinBuild2025/2022/LinuxBuild2404; removes obsolete WinBuild).
H7: Backup-CITemplate.ps1 default TemplatePath -> WinBuild2025; -AllTemplates switch added.
H8: Setup-Host.ps1 adds StrictHostKeyChecking=accept-new and Step 8 to replicate SSH config
    to LocalSystem profile for act_runner service account.
H11: runner/config.yaml adds GITEA_CI_SCRIPT_ROOT env var; action.yml uses it in place of
     hardcoded N:\ path.
Also: Invoke-RemoteBuild.ps1 skips 7-Zip/Compress-Archive when artifact source dir does not
     exist (fixes burn-in smoke runs with exit-0 build command and no output).
2026-05-12 21:12:07 +02:00
Simone 7d12dedddd Sprint 13-14: §6.2 composite action + §6.4 build matrix + §6.5 secret injection
§6.2 - New composite action gitea/actions/local-ci-build/action.yml
  Wraps Invoke-CIJob.ps1 for reuse across repos. Inputs: build-command,
  artifact-source, submodules, guest-os, use-git-clone, configuration,
  template-path, snapshot-name, artifact-name, artifact-retention-days.
  Anti-injection: all params via INPUT_* env vars, never interpolated in shell.

§6.4 - Build matrix windows+linux in build-nsis.yml
  gitea/workflows/build-nsis.yml rewritten with strategy.matrix target:
  [windows, linux], fail-fast: false, routes to {target}-build runner.
  Added action inputs: job-id-suffix (matrix disambig for artifact dirs),
  repo-url (SSH URL override for gitea-ci alias).

§6.5 - ExtraGuestEnv secret injection chain
  New param -ExtraGuestEnv [hashtable] in Invoke-CIJob.ps1 and
  Invoke-RemoteBuild.ps1. Windows: SetEnvironmentVariable in WinRM session
  ScriptBlock before build command. Linux: export KEY='val'; prefix before
  cd && buildcmd (POSIX single-quote escaping). Composite action input
  extra-guest-env-json (JSON object -> ConvertFrom-Json -> hashtable),
  never logged.

PSScriptAnalyzer fixes: _Common.psm1, Invoke-RetentionPolicy.ps1,
  Watch-RunnerHealth.ps1 (empty catch, ShouldProcess suppression, etc.)

Docs/test: TEST-PLAN-v1.3-to-HEAD.md updated §3.3/§6.1/§5.4 status.
  TODO.md: §6 fully closed (6.1-6.2 done, 6.3 deferred, 6.4-6.5 done, 6.6 done).
2026-05-11 22:35:36 +02:00
Simone 2693c9dc42 fix: suppress Test-NetConnection progress bar noise (\Continue = SilentlyContinue) 2026-05-11 20:52:22 +02:00
Simone 039fcdb9c9 feat(script): add UseGitClone and GuestRepoUrl parameters for in-VM cloning 2026-05-11 18:50:05 +02:00
Simone db1530cf0f fix(linux): correct PS escape for $GIT_CLONE_URL in UseGitClone clone cmd
In PS double-quoted strings, backslash does NOT escape '$'.
Only backtick does ('`$'). The previous '\' was being
expanded as a PS variable (undefined), triggering StrictMode error:
'Cannot retrieve variable \ because it has not been set.'

Replace '\$' with '`$' so the literal shell variable reference
\ is emitted correctly to the SSH command.
2026-05-11 18:40:19 +02:00
Simone 383d6864ce test: add Linux build E2E test scripts
Test-Ns7zipBuild-Linux.ps1: e2e test for nsis7z plugin build on Linux VM
  (confirmed PASS: nsis7z.dll 2272 KB in 03:09  2026-05-11)

Test-NsinnounpBuild-Linux.ps1: e2e test for nsinnounp plugin build on Linux VM
2026-05-11 18:34:50 +02:00
Simone fee5963a65 feat(linux): update orchestration scripts for dual-OS (Windows/Linux) support
Wait-VMReady.ps1:
- Phase 1: vmrun list + path matching instead of getGuestIPAddress (avoids hang)
- Phase 2/3: WinRM (Windows) or TCP:22 + ssh echo (Linux) based on -Transport param
- -Transport SSH auto-selected by Invoke-CIJob when guestOS=ubuntu-64

Invoke-CIJob.ps1:
- Auto-detect GuestOS from clone VMX guestOS field
- Pass -GuestOS Linux to Wait-VMReady, Invoke-RemoteBuild, Get-BuildArtifacts
- Git clone output streamed line-by-line (fix buffered stdout)

Invoke-RemoteBuild.ps1:
- -GuestOS Linux path: SSH+SCP via _Transport.psm1
- Always uses in-VM git clone for Linux builds

Get-BuildArtifacts.ps1:
- -GuestOS Linux path: scp -r ci_build@<ip>:/opt/ci/output/ to host artifact dir
2026-05-11 18:34:16 +02:00
Simone 08f7bb1757 feat(linux): add SSH/SCP transport module (_Transport.psm1)
Exported functions:
- Invoke-SshCommand   run a command on a Linux guest via ssh.exe (BatchMode=yes)
- Copy-SshItem        scp a local path to a remote path (or vice versa)
- Test-SshReady       test TCP:22 + ssh echo round-trip

Used by Wait-VMReady.ps1 -Transport SSH, Invoke-RemoteBuild.ps1 -GuestOS Linux,
Get-BuildArtifacts.ps1 -GuestOS Linux.
2026-05-11 18:34:00 +02:00
Simone fa2acc4b46 fix(tests): lower ValidateRange minimums and use .ps1 fake vmrun for Pester 2026-05-10 22:50:25 +02:00
Simone a3a24199b3 fix: resolve PSScriptAnalyzer ParseError and StrictMode Count issue 2026-05-10 22:50:12 +02:00
Simone d1f267b67c fix(artifacts): Retry WinRM connect in Get-BuildArtifacts (wsmprovhost delay)
After Invoke-RemoteBuild closes its session, wsmprovhost.exe may take a moment
to release resources. Subsequent New-PSSession in Get-BuildArtifacts fails with
'WSMan service could not launch a host process'.

Add retry loop: 3 attempts, 10s backoff. Enough for WinRM to recover between
the build session and artifact collection session.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 22:23:11 +02:00
Simone 73bb9b182b fix(§3.3): Replace credential-store with http.extraHeader for WinRM auth
credential-store --file fails in WinRM on Windows (path escaping, no helper
binary resolution in non-interactive shell). Switch to http.extraHeader:

  git -c credential.helper=       -c 'http.extraHeader=Authorization: Basic <b64>'       clone ...

Advantages:
- No credential helper binary needed
- No temp file (no cleanup required)
- Token never in URL, command display, or Write-Host output
- Works in WinRM non-interactive sessions (no TTY dependency)
- GCM still cleared by credential.helper= prefix

§1.5 compliant: Base64 is in git process argv (not logged by Write-Host).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 22:16:44 +02:00
Simone 73fca1c74a fix(§3.3): Disable GCM + set GIT_TERMINAL_PROMPT=0 in WinRM guest clone
Git Credential Manager (GCM) installed by Git for Windows tries to open
an interactive credential prompt or wincredman store — both fail in a
WinRM session (no TTY, no interactive desktop).

Fix:
- Set GIT_TERMINAL_PROMPT=0 to suppress all interactive git prompts
- Prepend '-c credential.helper=' (empty string) to reset the helper chain
  before injecting our store-based helper (clearing GCM from the chain)
- Custom credential helper (store --file <tmp>) then appended only when PAT present

Without PAT (public repo): GCM still cleared, no prompt attempted, clean fail
  if auth is actually required.
With PAT: store-based helper used exclusively, GCM never invoked.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 22:10:01 +02:00
Simone eb6fdfb2ca feat(§3.3): Implement Gitea PAT auth for guest-side git clone
Credential Manager target 'GiteaPAT' (default) read on host, passed
securely to guest WinRM session. Token never appears in argv, URL, or logs.

Guest auth flow (§1.5 compliant):
1. Host reads PAT via Get-StoredCredential -Target GiteaPAT
2. Username + password passed as ArgumentList to Invoke-Command
3. Guest writes temp .git-credentials file (https://user:token@host)
4. git clone uses: -c 'credential.helper=store --file <tmp>'
5. finally{} deletes credentials file regardless of clone success/fail

Setup (one-time on host):
  New-StoredCredential -Target 'GiteaPAT' -UserName '<user>' `
      -Password '<pat>' -Persist LocalMachine

Invoke-CIJob.ps1: added -GiteaCredentialTarget param (default: 'GiteaPAT'),
passed through to Invoke-RemoteBuild when -UseGitClone is set.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 22:03:36 +02:00
Simone c10c79d4f4 fix(test): Fix §3.3 e2e — pipeline pollution + SSH URL not usable in guest
Two bugs:
1. Run-Test used '& $orchestrator @params' without Out-Host — orchestrator output
   collected in function pipeline, mixed with returned hashtable → caller received
   array instead of hashtable → .Success property lookup threw under StrictMode.
   Fix: pipe to Out-Host.

2. RepoUrl was ssh://gitea-ci/... (SSH config alias) — alias exists only on
   host SSH config, not in guest VM. Guest-side clone (§3.3) needs HTTP URL
   directly reachable from inside the VM.
   Fix: separate -HostRepoUrl / -GuestRepoUrl params, inject per mode.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 21:59:51 +02:00
Simone 13ecd823dc test(e2e): Add §3.3 comparison script (host-side vs guest-clone)
Create Test-E2E-Section3.3.ps1 for side-by-side measurement:
- Runs nsis-plugin-nsinnounp with default host-side clone (baseline)
- Runs same build with -UseGitClone flag (target: guest-side git)
- Displays elapsed time, artifact size, performance delta

Usage:
  .\scripts\Test-E2E-Section3.3.ps1               # Full comparison
  .\scripts\Test-E2E-Section3.3.ps1 -SkipBaseline  # Guest-clone only
  .\scripts\Test-E2E-Section3.3.ps1 -SkipGuestClone # Baseline only

Prerequisite: Template VM has Git installed (§6.6 Tier-1 Toolchain).

Also: Defer §3.5 (vCPU/RAM tuning) and §3.4 (pre-warm pool) to home lab
section — not required for initial CI operability.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 21:00:03 +02:00
Simone edfd6a3907 feat(§3.3): Complete In-VM Git Clone implementation
Add guest-side clone mode to Invoke-RemoteBuild.ps1:

Changes:
1. Update DESCRIPTION: document Mode 1 (host) vs Mode 2 (guest) clone
2. Add params: CloneUrl, CloneBranch, CloneCommit, CloneSubmodules
3. Mode validation: enforce mutual exclusion (HostSourceDir XOR CloneUrl)
4. Conditional source prep:
   - Mode 1 (default): compress host source, zip-transfer, expand in guest
   - Mode 2 (§3.3): clone repo directly in guest via git

Guest clone implementation (§3.3):
- Clone with --depth 1, --branch specified, optionally --recurse-submodules
- Checkout specific commit if provided (handles shallow history edge case)
- PAT handling per §1.5: read from Credential Manager (placeholder),
  passed as env var in WinRM session, cleaned up immediately after clone
- No PAT in argv, logs, or transcript — only in session env scope

Prerequisite: Git for Windows in template (§6.6, complete)
Testing: E2E with nsis-plugin-nsinnounp (-UseGitClone flag)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 20:55:02 +02:00
Simone 844142b250 feat(§3.3): Partial implementation of In-VM Git Clone
Add -UseGitClone switch to Invoke-CIJob.ps1 + conditional logic:

Changes:
1. Add -UseGitClone [switch] param + docstring (§3.3 mode)
2. Modify Phase 1 to skip host clone when -UseGitClone active
3. Phase 5: Pass CloneUrl/CloneBranch/CloneCommit instead of HostSourceDir
   when -UseGitClone enabled
4. Finally: Skip host cleanup when -UseGitClone (dir never created)

When -UseGitClone is active:
- Phase 1 (host clone) skipped — clone happens in VM instead
- Phase 5 passes git repo parameters to Invoke-RemoteBuild.ps1
- PAT (if needed) read in Invoke-RemoteBuild session (§1.5)

Prerequisite: Git for Windows + 7-Zip in template (§6.6, now complete)

Work in progress: Invoke-RemoteBuild.ps1 modifications pending (source transfer
conditional + guest-side git clone logic).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 20:54:39 +02:00
Simone 914072fdd8 refactor(config): Update VMX path from CI-WinBuild to WinBuild2025
Replace all references to CI-WinBuild.vmx with WinBuild2025.vmx.
Update template path: F:\CI\Templates\WinBuild\ → F:\CI\Templates\WinBuild2025\

Files affected:
- runner/config.yaml: GITEA_CI_TEMPLATE_PATH env var
- docs: HOST-SETUP.md, WINDOWS-TEMPLATE-SETUP.md, TEST-PLAN, RUNBOOK
- scripts: Measure-CIBenchmark, Set-TemplateSharedFolders, Test-NsinnounpBuild
- TODO.md, Setup-Host.ps1, README.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 20:46:36 +02:00
Simone fef8ee6ec5 feat(§3.2): Replace Compress-Archive with 7-Zip (multi-threaded)
§3.2 — Performance: Implement 7-Zip compression for artifacts with fallback
to Compress-Archive for compatibility. Three call sites updated:
1. Host-side source compression: new Compress-BuildArtifact helper function
2. Guest-side custom build artifacts: inline 7-Zip check in scriptblock
3. Guest-side dotnet build output: inline 7-Zip check in scriptblock

Uses 7z.exe with -mmt=on (multi-threaded) and -mx1 (fast, low ratio impact).
Falls back to Compress-Archive if 7-Zip not found (until §6.6 template update).

Expected 10-20s speedup per build once 7-Zip installed in template.

Also defer all security items (§1.2/1.3/1.5/1.7/1.8) to home-lab section
since environment is isolated and excessive for current use case.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 20:23:43 +02:00
Simone 552eb628b7 feat(sprint4-6): quality, reliability, perf baseline
Sprint 4 — qualità codice:
- scripts/_Common.psm1: New-CISessionOption, Resolve-VmrunPath, Invoke-Vmrun
- PSScriptAnalyzerSettings.psd1: project-wide PSSA rules (security + formatting)
- Remove-BuildVM.ps1: SupportsShouldProcess (-WhatIf support)
- Invoke-RemoteBuild.ps1, Get-BuildArtifacts.ps1: use New-CISessionOption from module
- New-BuildVM.ps1: use Resolve-VmrunPath + Invoke-Vmrun from module

Sprint 5 — affidabilità:
- Backup-CITemplate.ps1: stop runner, timestamped VMDK backup, prune old, restart
- Watch-RunnerHealth.ps1: auto-restart act_runner, cooldown 3/h, EventLog + webhook
- Register-CIScheduledTasks.ps1: add CI-RunnerHealth task (every 15 min)

Sprint 6 — perf baseline:
- Set-TemplateSharedFolders.ps1: idempotent VMX editor for NuGet/pip shared folders
- Invoke-RemoteBuild.ps1: -UseSharedCache injects NUGET_PACKAGES + PIP_CACHE_DIR in guest
- Measure-CIBenchmark.ps1: times clone/start/IP/WinRM/destroy, appends to benchmark.jsonl

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 20:08:19 +02:00
Simone 1962c977ba feat(sprint3): §4.1/4.3/4.4 — JSONL logging, disk alert, runbook
§4.1 — Structured JSONL log (Invoke-CIJob.ps1)
  Each job now emits a parallel invoke-ci.jsonl alongside the transcript.
  Write-JobEvent appends one JSON line per phase transition (ts, jobId,
  phase, status, data). Events emitted at: job.start, phase1.clone-repo
  start/success, phase2-3b.vm-start start/success, phase4.wait-ready
  start/success, phase5.build start/success, phase6.artifacts start/success,
  job.success/failure (with elapsedSec and error string).
  Silent no-op if $jsonLog is null (log dir setup failed). Errors in
  Write-JobEvent are swallowed — logging never breaks the build.
  Enables post-hoc per-phase duration analysis with jq or ConvertFrom-Json.

§4.3 — Disk space alert (scripts/Watch-DiskSpace.ps1)
  Checks drive free space every 15 min (scheduled by Register-CIScheduledTasks).
  Below MinFreeGB (default 50 GB): writes Warning to Windows Application Event Log
  (source CI-DiskAlert, EventId 1001) and exits 1 so Task Scheduler flags the run.
  Optional -WebhookUrl for Discord/Gitea webhook notification.
  Register-CIScheduledTasks.ps1 updated with Task 3: CI-DiskSpaceAlert (every 15 min).

§4.4 — Incident runbook (docs/RUNBOOK.md)
  Four scenarios with symptom / triage commands / fix / escalation:
    1. Runner offline in Gitea UI
    2. All builds fail in Phase 2 (clone/start/IP)
    3. Builds are slow (per-phase JSONL analysis, disk/CPU/NAT checks)
    4. Template VMX corrupt after host crash (lock removal + backup restore)
  Quick-reference table at the end.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 19:07:48 +02:00
Simone bc894a8058 feat(sprint2): §2.1/2.2/2.3/2.5 — IP lock/lease, scheduled tasks, retention, versioned snapshot
§2.1 — IP race with capacity>1 (Invoke-CIJob.ps1)
  Phases 2-3b (clone+start+IP) now run inside a file-based exclusive mutex
  (F:\CI\State\vm-start.lock via FileShare.None). Only one job at a time is
  in the VM-start phase, preventing concurrent vmrun starts from racing for
  the same DHCP lease. After IP is confirmed unique it is written to an IP
  lease file (F:\CI\State\ip-leases\<IP>.lease). Lock is released immediately
  so build phases run fully in parallel. Lease released in finally block.
  Collision detection: if a lease file already exists for the detected IP,
  the job fails fast with a clear error rather than silently sharing a WinRM target.

§2.5 — Versioned snapshot (Invoke-CIJob.ps1 + runner/config.yaml)
  $SnapshotName now defaults to GITEA_CI_SNAPSHOT_NAME env var, falling back
  to 'BaseClean'. config.yaml documents the commented-out variable for snapshot
  refresh workflow (BaseClean_<yyyyMMdd> naming convention).

§2.3 — Artifact/log retention (scripts/Invoke-RetentionPolicy.ps1)
  Purges per-job subdirs under F:\CI\Artifacts and F:\CI\Logs older than
  RetentionDays (default 30). Switches to AggressiveRetentionDays (default 7)
  when F: free space drops below MinFreeGB (default 50 GB). Also removes stale
  IP lease files older than 12 hours (defensive cleanup for crashed jobs).
  SupportsShouldProcess (-WhatIf) for dry-run.

§2.2 — Scheduled task registration (scripts/Register-CIScheduledTasks.ps1)
  Registers two tasks under \CI\ task folder as SYSTEM/HighestPrivilege:
    CI-CleanupOrphans  — every 6h + AtStartup, -MaxAgeHours 4
    CI-RetentionPolicy — daily 03:00 (+30min random) + AtStartup (+15min random)
  Idempotent (-Force), SupportsShouldProcess (-WhatIf), validates script paths.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 18:36:38 +02:00
Simone 34b33c5011 fix(winrm-https): PS 5.1 Test-WSMan has no -SessionOption — remove from all call sites
Test-WSMan in Windows PowerShell 5.1 does not support -SessionOption (WSManSessionOption),
causing "Cannot find a parameter matching the name 'SessionOption'" at runtime.

Prepare-WinBuild2025.ps1:
  - Remove Test-WSMan connectivity check step entirely
  - Open New-PSSession directly (handles -SkipCACheck via PSSessionOption)
  - Wrap New-PSSession in try/catch with the same actionable error message
  - Remove $wsmOpt (WSManSessionOption) — no longer needed
  Wait-GuestWinRMReady: replace Test-WSMan probe with New-PSSession probe + Remove-PSSession

Wait-VMReady.ps1:
  - Remove $wsmOpt entirely
  - Replace Test-WSMan -UseSSL -SessionOption with Test-NetConnection -Port 5986
    (TCP open on 5986 = HTTPS listener up = VM ready; credentials not available here)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 15:58:57 +02:00
Simone 68cde01c9d security(sprint1): §1.1/1.3/1.4 — WinRM HTTPS/5986, SHA256 pinning infra, IP regex per-octet
§1.4 — Replace loose IP ValidatePattern with per-octet regex in 4 scripts
  (Invoke-CIJob, Invoke-RemoteBuild, Get-BuildArtifacts, Wait-VMReady)

§1.1 — Migrate all WinRM connections from HTTP/5985 to HTTPS/5986
  - Deploy post-install.ps1: remove AllowUnencrypted=true; self-signed cert + HTTPS listener
    already present; firewall rule restricted to 192.168.79.0/24
  - Setup-WinBuild2025.ps1: assert AllowUnencrypted=false
  - Prepare-WinBuild2025.ps1: preflight uses TCP/5986; Test-WSMan -UseSSL -Port 5986;
    New-PSSession -UseSSL -Port 5986; host AllowUnencrypted save/restore removed (not needed for HTTPS)
  - Invoke-RemoteBuild, Get-BuildArtifacts: New-PSSession -UseSSL -Port 5986 -Authentication Basic
  - Wait-VMReady: New-WSManSessionOption + Test-WSMan -Port 5986 -UseSSL
  - Validate-DeployState, Validate-SetupState: Invoke-Command -UseSSL -Port 5986,
    AllowUnencrypted check → false; AllowUnencrypted host-side removed from both
  - Docs: PLAN, README, ARCHITECTURE, BEST-PRACTICES, HOST-SETUP, WINDOWS-TEMPLATE-SETUP,
    LINUX-TEMPLATE-SETUP, TODO updated

§1.3 — SHA256 hash pinning infrastructure
  - Assert-Hash function + $script:Hashes hashtable added to Setup-WinBuild2025.ps1
  - Assert-Hash called after dotnet-install.ps1, python installer, vs_buildtools.exe downloads
  - Hashes initialized to '' (warn-skip); must be filled before next snapshot refresh

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 14:46:41 +02:00
Simone ea882c99d7 fix(scripts): update TemplatePath to correct directory for WinBuild2025.vmx 2026-05-10 01:07:31 +02:00
Simone 3c4dd68282 fix(scripts): update default VMX path to WinBuild2025.vmx in Test-NsinnounpBuild.ps1 2026-05-10 01:02:20 +02:00
Simone d2b20284d1 fix: wait for vmware-vmx.exe release before deleteVM
After hard stop, vmware-vmx.exe briefly holds file locks causing
deleteVM to fail with "Insufficient permissions". Replace the fixed
3s sleep with a vmrun-list poll loop (up to 20s) that waits until
the VMX is no longer registered as running, then retry deleteVM up
to 3x with 0/3/6s backoff before falling back to directory removal.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 00:53:32 +02:00
Simone e6d44dad06 test: enable -VerifyArtifact by default in Test-NsinnounpBuild.ps1
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 00:47:30 +02:00
Simone 2f2aa2bd9c test: add Test-NsinnounpBuild.ps1 e2e smoke test script
Wraps Invoke-CIJob.ps1 with nsinnounp params, verifies artifact exists.
Optional -VerifyArtifact flag expands zip and checks for expected
DLL/EXE names. Supports -Commit for pinned SHA testing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 00:46:27 +02:00
Simone cc9e5f3969 fix: shallow clone + unshallow fallback for specific commit checkout
Previous fix removed --depth 1 entirely when $Commit was set, causing
full clones on every workflow run (github.sha is always non-empty).

Better approach: keep --depth 1 (fast path), detect checkout failure,
then fetch --unshallow and retry. Only pays full-clone cost when the
requested commit is actually older than HEAD.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 00:45:27 +02:00
Simone f373c0c24b fix: apply all FIX-TODO items (P0/P1/P2)
P0 bugs:
- Invoke-CIJob.ps1: skip --depth 1 when $Commit specified (shallow clone
  + checkout specific commit was silently failing)
- Get-BuildArtifacts.ps1: check .Length -eq 0 not rounded sizeKB (false
  fail on artifacts smaller than 50 bytes)
- build-nsis.yml: upgrade upload-artifact v3 -> v4 (v3 deprecated)

P0 security:
- Remove hardcoded default password CIBuild!ChangeMe2026 from all scripts,
  README, TODO; prompt Read-Host -AsSecureString at runtime instead
- Setup-TemplateVM.ps1: replace `net user $u $p /add` (password in argv /
  audit log 4688) with New-LocalUser + local ConvertTo-SecureString
- Prepare-TemplateSetup.ps1: save and restore WSMan AllowUnencrypted +
  TrustedHosts in finally block (was permanently mutating host WinRM config)
- Setup-TemplateVM.ps1: remove duplicate MaxMemoryPerShellMB (winrm set
  + Set-Item both setting same value)

P1 doc (stale Host-Only / VMnet11 era):
- Setup-TemplateVM.ps1: rewrite NETWORK REQUIREMENT block + final steps
  (system uses VMnet8 NAT permanently, not VMnet11 Host-Only)
- Invoke-CIJob.ps1 Phase 1 comment: correct network model
- ARCHITECTURE.md: remove Git from toolchain list (not installed); clarify
  zip-transfer rationale (no PAT in VM, not network isolation)
- BEST-PRACTICES.md: rewrite Step 8 as NAT topology verification

P1 minor:
- Invoke-RemoteBuild.ps1: remove wasted first New-Item (created then
  immediately removed and recreated)
- Wait-VMReady.ps1: remove unused $elapsed; simplify try/catch around
  native command (exit codes don't throw)
- Install-Runner.ps1: add deprecation notice pointing to Setup-Host.ps1

P2 operational:
- Add scripts/Cleanup-OrphanedBuildVMs.ps1 with -WhatIf support
- Get-BuildArtifacts.ps1 -IncludeLogs: add -Recurse (msbuild logs in subdir)
- Add gitea/workflows/lint.yml (PSScriptAnalyzer on .ps1 push/PR)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 00:39:52 +02:00
Simone 97a852c9f3 fix: use Start-Transcript -Append to allow nested transcript in VS Code session 2026-05-08 23:59:51 +02:00
Simone b64d6c2c5d feat: stream build output in real-time + log capture and retention
Invoke-RemoteBuild.ps1:
- Custom build command: replace buffered collect-then-print with
  Write-Host streaming inside Invoke-Command (WinRM forwards Write-Host
  in real-time to the host console)
- Set PYTHONUNBUFFERED=1 in the remote session to prevent Python from
  holding lines in its internal buffer
- Same streaming approach for dotnet restore and dotnet build paths
- Return value is now just int exit code instead of hashtable

Invoke-CIJob.ps1:
- Add -LogDir parameter (default F:\CI\Logs)
- Add -LogRetentionDays parameter (default 30)
- Start-Transcript at job start -> F:\CI\Logs\<JobId>\invoke-ci.log
- Stop-Transcript in finally block (guaranteed execution)
- Auto-purge log directories older than LogRetentionDays after each job
2026-05-08 23:56:10 +02:00
Simone dd238121b3 chore: initial commit local CI/CD system (e2e verified, production-ready)
Sistema CI locale basato su VMware Workstation + Gitea Actions + act_runner.
Testato e2e con nsis-plugin-nsinnounp (MSBuild + Python, 4 configurazioni parallele).

- scripts/: Invoke-CIJob, Invoke-RemoteBuild, New/Remove-BuildVM, Wait-VMReady, Get-BuildArtifacts
- runner/: act_runner config (windows-build label, capacity 4)
- gitea/workflows/: build-nsis.yml (template per progetti MSBuild/Python)
- template/: script di provisioning template VM (VS BuildTools 2026, .NET SDK 10, Python 3.13)
- docs/: ARCHITECTURE, CI-FLOW, OPTIMIZATION, BEST-PRACTICES, Setup-GiteaSSH

Verificato: e2e-009 SUCCESS in 02:09, cleanup automatico VM confermato.
2026-05-08 23:25:50 +02:00