diff --git a/README.md b/README.md index 1307494..46b58f0 100644 --- a/README.md +++ b/README.md @@ -120,7 +120,7 @@ Variante **Windows Server 2022**: `*WinBuild2022.ps1` + VMX `F:\CI\Templates\Win │ ├── actions/ │ │ └── local-ci-build/action.yml # Composite action riusabile (wrap Invoke-CIJob.ps1) │ ├── workflows/ -│ │ ├── build-nsis.yml # Workflow matrix Windows+Linux per nsis-plugin-nsinnounp +│ │ ├── build-nsInnoUnp.yml # Workflow matrix Windows+Linux per nsis-plugin-nsinnounp │ │ └── lint.yml # PSScriptAnalyzer su push/PR (§5.4) │ └── workflow-example.yml # Template generico adattabile │ @@ -276,7 +276,7 @@ Aggiungi `-SkipArtifact` per job che non producono output (es. lint, test puri). ## Workflow Gitea -Il file [gitea/workflows/build-nsis.yml](gitea/workflows/build-nsis.yml) è il workflow +Il file [gitea/workflows/build-nsInnoUnp.yml](gitea/workflows/build-nsInnoUnp.yml) è il workflow attivo per `Simone/nsis-plugin-nsinnounp`. Si attiva su push di tag `v*` o manualmente. Usa una **strategy matrix** `[windows, linux]` che esegue in parallelo su entrambi i runner. diff --git a/TODO.md b/TODO.md index 465aa50..2574f2f 100644 --- a/TODO.md +++ b/TODO.md @@ -1,7 +1,7 @@ # TODO — Local CI/CD System - + @@ -56,7 +56,7 @@ _Last updated: 2026-05-11 (post Sprint 15: §7.5 DONE — Rinomina Setup-* → I - [x] **Gitea Server** — già in esecuzione su `http://10.10.20.11:3100` (e `https://gitea.emulab.it`) - [x] Runner registration token ottenuto e usato - [x] Create at least one test repository with `.gitea/workflows/build.yml` - (`Simone/nsis-plugin-nsinnounp` — testato e2e, workflow in `gitea/workflows/build-nsis.yml`) + (`Simone/nsis-plugin-nsinnounp` — testato e2e, workflow in `gitea/workflows/build-nsInnoUnp.yml`) - [x] Gitea Actions abilitato (runner registrato con successo) - [x] **act_runner** — installato e registrato sull'host Windows @@ -171,7 +171,7 @@ _Last updated: 2026-05-11 (post Sprint 15: §7.5 DONE — Rinomina Setup-* → I ## Gitea Workflow Integration - [x] Copiare `gitea/workflow-example.yml` in `.gitea/workflows/build.yml` nel repo di test - (workflow `build-nsis.yml` già presente e funzionante per `nsis-plugin-nsinnounp`) + (workflow `build-nsInnoUnp.yml` già presente e funzionante per `nsis-plugin-nsinnounp`) - [x] Push commit e verificare job in Gitea Actions UI - [x] Verificare artifact scaricabile da Gitea dopo build riuscita - [x] `gitea/workflows/lint.yml` creato — PSScriptAnalyzer su push/PR che toccano `.ps1` (2026-05-09) @@ -506,9 +506,9 @@ Già supportato lato Gitea, basta replicare il setup. **Deferred**: in un home lab single-host questa funzionalità non serve. Non pianificare. ### 6.4 [P3] [x] Build matrix nel workflow -File: [gitea/workflows/build-nsis.yml](gitea/workflows/build-nsis.yml). +File: [gitea/workflows/build-nsInnoUnp.yml](gitea/workflows/build-nsInnoUnp.yml). -**Status**: COMPLETATO 2026-05-11 — `build-nsis.yml` riscritto con matrix strategy. +**Status**: COMPLETATO 2026-05-11 — `build-nsInnoUnp.yml` riscritto con matrix strategy. Matrix `target: [windows, linux]`, `runs-on: ${{ matrix.target }}-build`, `fail-fast: false`. Usa la composite action con `job-id-suffix: ${{ matrix.target }}` diff --git a/gitea/workflows/build-nsInnoUnp.yml b/gitea/workflows/build-nsInnoUnp.yml new file mode 100644 index 0000000..27654ee --- /dev/null +++ b/gitea/workflows/build-nsInnoUnp.yml @@ -0,0 +1,103 @@ +# Build (Local CI) — pipeline per nsis-plugin-nsinnounp +# +# Cross-platform matrix build: runs in parallel on both windows-build (WinRM) +# and linux-build (SSH) runners using the reusable composite action. +# Uses: Simone/local-ci-system/.gitea/actions/local-ci-build@main +# +# Trigger: push su tag v*, oppure run manuale + +name: Build (Local CI) + +on: + push: + tags: + - 'v*' + workflow_dispatch: + +jobs: + build: + strategy: + matrix: + target: [windows, linux] + fail-fast: false + + # Runner label must match config.yaml labels: windows-build or linux-build + runs-on: ${{ matrix.target }}-build + + steps: + - uses: Simone/local-ci-system/.gitea/actions/local-ci-build@main + with: + build-command: 'python build_plugin.py --final --dist-dir dist' + artifact-source: 'dist' + submodules: 'true' + guest-os: ${{ matrix.target == 'linux' && 'Linux' || 'Windows' }} + # SSH URL alias configured in host ~/.ssh/config — same for both runners + repo-url: 'ssh://gitea-ci/Simone/nsis-plugin-nsinnounp.git' + # Suffix disambiguates artifact dirs: F:\CI\Artifacts\{run_id}-{attempt}-windows + job-id-suffix: '${{ matrix.target }}' + artifact-name: 'nsis-plugin-nsinnounp-${{ matrix.target }}-${{ github.ref_name }}' + + # ── Publish Gitea release with build artifacts ──────────────────────────── + # Runs only on tag pushes, after both matrix legs succeed. + # Requires a GITEA_TOKEN secret with repo write permissions. + release: + needs: build + if: github.ref_type == 'tag' + runs-on: windows-build + + steps: + - name: Download Windows artifact + uses: actions/download-artifact@v4 + with: + name: nsis-plugin-nsinnounp-windows-${{ github.ref_name }} + path: artifacts/windows + + - name: Download Linux artifact + uses: actions/download-artifact@v4 + with: + name: nsis-plugin-nsinnounp-linux-${{ github.ref_name }} + path: artifacts/linux + + - name: Create Gitea release and upload assets + shell: powershell + env: + GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} + GITEA_URL: ${{ github.server_url }} + REPO: ${{ github.repository }} + TAG: ${{ github.ref_name }} + run: | + #Requires -Version 5.1 + Set-StrictMode -Version Latest + $ErrorActionPreference = 'Stop' + + $authHeader = @{ Authorization = "token $env:GITEA_TOKEN" } + $apiBase = "$env:GITEA_URL/api/v1/repos/$env:REPO" + + # Create the release (non-draft, non-prerelease) + $releaseBody = @{ + tag_name = $env:TAG + name = $env:TAG + draft = $false + prerelease = $false + } | ConvertTo-Json + $jsonHeaders = $authHeader + @{ 'Content-Type' = 'application/json' } + $release = Invoke-RestMethod -Method Post -Uri "$apiBase/releases" ` + -Headers $jsonHeaders -Body $releaseBody + $releaseId = $release.id + Write-Host "Release created: id=$releaseId name=$($release.name)" + + # Compress each artifact directory and upload as a release asset + foreach ($target in @('windows', 'linux')) { + $srcDir = "artifacts\$target" + $zipName = "nsis-plugin-nsinnounp-$target-$env:TAG.zip" + Compress-Archive -Path "$srcDir\*" -DestinationPath $zipName -Force + + $uploadUrl = "$apiBase/releases/$releaseId/assets?name=$zipName" + $uploadHeaders = $authHeader + @{ 'Content-Type' = 'application/zip' } + $zipBytes = [System.IO.File]::ReadAllBytes( + (Resolve-Path $zipName).ProviderPath) + Invoke-RestMethod -Method Post -Uri $uploadUrl ` + -Headers $uploadHeaders -Body $zipBytes | Out-Null + Write-Host "Uploaded asset: $zipName" + } + diff --git a/gitea/workflows/build-nsis.yml b/gitea/workflows/build-nsis.yml deleted file mode 100644 index 4d5dc94..0000000 --- a/gitea/workflows/build-nsis.yml +++ /dev/null @@ -1,39 +0,0 @@ -# Build (Local CI) — pipeline per nsis-plugin-nsinnounp -# -# Cross-platform matrix build: runs in parallel on both windows-build (WinRM) -# and linux-build (SSH) runners using the reusable composite action. -# Uses: Simone/local-ci-system/.gitea/actions/local-ci-build@main -# -# Trigger: push su tag v*, oppure run manuale - -name: Build (Local CI) - -on: - push: - tags: - - 'v*' - workflow_dispatch: - -jobs: - build: - strategy: - matrix: - target: [windows, linux] - fail-fast: false - - # Runner label must match config.yaml labels: windows-build or linux-build - runs-on: ${{ matrix.target }}-build - - steps: - - uses: Simone/local-ci-system/.gitea/actions/local-ci-build@main - with: - build-command: 'python build_plugin.py --final --dist-dir dist' - artifact-source: 'dist' - submodules: 'true' - guest-os: ${{ matrix.target == 'linux' && 'Linux' || 'Windows' }} - # SSH URL alias configured in host ~/.ssh/config — same for both runners - repo-url: 'ssh://gitea-ci/Simone/nsis-plugin-nsinnounp.git' - # Suffix disambiguates artifact dirs: F:\CI\Artifacts\{run_id}-{attempt}-windows - job-id-suffix: '${{ matrix.target }}' - artifact-name: 'nsis-plugin-nsinnounp-${{ matrix.target }}-${{ github.ref_name }}' - diff --git a/plans/final-master-plan.md b/plans/final-master-plan.md index 648ec7e..225cee9 100644 --- a/plans/final-master-plan.md +++ b/plans/final-master-plan.md @@ -1,4 +1,4 @@ -# Final Master Plan — Local CI/CD System +# Final Master Plan — Local CI/CD System Date: 2026-05-11 Last updated: 2026-05-12 @@ -12,7 +12,7 @@ This is the single authoritative document for finishing v1.0 of the system. When The Local CI/CD System is a well-engineered, single-host homelab CI built on Gitea Actions, act_runner v1.0.2, VMware Workstation linked clones, Windows PowerShell 5.1, WinRM HTTPS for Windows guests and SSH for Linux guests. The architecture is the right shape for the constraint set: a thin orchestrator on the host, ephemeral linked clones for isolation, two transports for two guest families, and file-based state for everything else. Both reviewers independently graded the architecture A- and operational maturity B-/C+. That matches what the source shows. -The system is *feature-complete* and *operator-usable*, not yet *unattended-daily-trustworthy*. The gap is small in code volume but real in operational consequence. Five concrete defects can break a live build today: the Linux matrix leg of [gitea/workflows/build-nsis.yml](../gitea/workflows/build-nsis.yml) silently selects the Windows template; Linux `ExtraGuestEnv` secrets are logged in clear text; the composite action outputs metadata is malformed YAML; the Linux Mode 2 PAT injection writes credentials into a remote command string; [Setup-Host.ps1](../Setup-Host.ps1) creates a directory layout that no longer matches the live system. None of these are architectural failures. Each is a 5-to-45-minute fix that the design otherwise supports cleanly. +The system is *feature-complete* and *operator-usable*, not yet *unattended-daily-trustworthy*. The gap is small in code volume but real in operational consequence. Five concrete defects can break a live build today: the Linux matrix leg of [gitea/workflows/build-nsInnoUnp.yml](../gitea/workflows/build-nsInnoUnp.yml) silently selects the Windows template; Linux `ExtraGuestEnv` secrets are logged in clear text; the composite action outputs metadata is malformed YAML; the Linux Mode 2 PAT injection writes credentials into a remote command string; [Setup-Host.ps1](../Setup-Host.ps1) creates a directory layout that no longer matches the live system. None of these are architectural failures. Each is a 5-to-45-minute fix that the design otherwise supports cleanly. The system makes one large unvalidated claim: `capacity: 4` in [runner/config.yaml](../runner/config.yaml). The IP-allocation mutex and lease design in [scripts/Invoke-CIJob.ps1](../scripts/Invoke-CIJob.ps1) is sound on paper, but the only evidence is single-job e2e runs. Four concurrent real builds have not been measured. This is the single most consequential outstanding question. The remediation is a burn-in, not a redesign. @@ -120,15 +120,15 @@ Do not add: These must be fixed before the system is trustworthy for unattended daily use. Severity *Critical* means the issue actively breaks production today. -### C1. Linux matrix leg of build-nsis.yml selects the Windows template +### C1. Linux matrix leg of build-nsInnoUnp.yml selects the Windows template -**Description**: [gitea/workflows/build-nsis.yml](../gitea/workflows/build-nsis.yml) runs a matrix over `[windows, linux]` and dispatches on `runs-on: ${{ matrix.target }}-build`, but does NOT pass `guest-os` or `template-path` to the composite action. The action defaults `template-path` to empty, so [scripts/Invoke-CIJob.ps1](../scripts/Invoke-CIJob.ps1) reads `$env:GITEA_CI_TEMPLATE_PATH` — the Windows path. The orchestrator never reads `GITEA_CI_LINUX_TEMPLATE_PATH`. Result: the Linux leg clones `WinBuild2025` and tries to SSH into a Windows VM (or via Auto-detect, into a Windows VM with WinRM credentials), which fails or produces nonsense. +**Description**: [gitea/workflows/build-nsInnoUnp.yml](../gitea/workflows/build-nsInnoUnp.yml) runs a matrix over `[windows, linux]` and dispatches on `runs-on: ${{ matrix.target }}-build`, but does NOT pass `guest-os` or `template-path` to the composite action. The action defaults `template-path` to empty, so [scripts/Invoke-CIJob.ps1](../scripts/Invoke-CIJob.ps1) reads `$env:GITEA_CI_TEMPLATE_PATH` — the Windows path. The orchestrator never reads `GITEA_CI_LINUX_TEMPLATE_PATH`. Result: the Linux leg clones `WinBuild2025` and tries to SSH into a Windows VM (or via Auto-detect, into a Windows VM with WinRM credentials), which fails or produces nonsense. -**Location**: [gitea/workflows/build-nsis.yml](../gitea/workflows/build-nsis.yml) L20-L40; [gitea/actions/local-ci-build/action.yml](../gitea/actions/local-ci-build/action.yml) L77-L83; [scripts/Invoke-CIJob.ps1](../scripts/Invoke-CIJob.ps1) L108. +**Location**: [gitea/workflows/build-nsInnoUnp.yml](../gitea/workflows/build-nsInnoUnp.yml) L20-L40; [gitea/actions/local-ci-build/action.yml](../gitea/actions/local-ci-build/action.yml) L77-L83; [scripts/Invoke-CIJob.ps1](../scripts/Invoke-CIJob.ps1) L108. **Impact**: The advertised Windows+Linux matrix workflow is broken for the Linux leg. The only evidence of "Linux works" is direct invocation of `Invoke-CIJob.ps1` with explicit `-TemplatePath`. End-to-end through the workflow has never worked for Linux. -**Fix**: Either (a) update the composite action to select between `GITEA_CI_TEMPLATE_PATH` and `GITEA_CI_LINUX_TEMPLATE_PATH` based on `guest-os` (or runner label inference) and similarly for snapshot name; or (b) update [gitea/workflows/build-nsis.yml](../gitea/workflows/build-nsis.yml) to pass `guest-os: ${{ matrix.target == 'linux' && 'Linux' || 'Windows' }}` plus an explicit `template-path`. Option (a) is the better one because it makes the action self-sufficient for any caller. Add an `INPUT_GUEST_OS_RESOLVED` step that selects the template-path env var, then pass it as `-TemplatePath`. Also set `BaseClean-Linux` as the snapshot name on Linux. +**Fix**: Either (a) update the composite action to select between `GITEA_CI_TEMPLATE_PATH` and `GITEA_CI_LINUX_TEMPLATE_PATH` based on `guest-os` (or runner label inference) and similarly for snapshot name; or (b) update [gitea/workflows/build-nsInnoUnp.yml](../gitea/workflows/build-nsInnoUnp.yml) to pass `guest-os: ${{ matrix.target == 'linux' && 'Linux' || 'Windows' }}` plus an explicit `template-path`. Option (a) is the better one because it makes the action self-sufficient for any caller. Add an `INPUT_GUEST_OS_RESOLVED` step that selects the template-path env var, then pass it as `-TemplatePath`. Also set `BaseClean-Linux` as the snapshot name on Linux. **Estimated effort**: 1 hour including a manual smoke test of both legs. @@ -236,9 +236,9 @@ These are *High* priority items. Fix in the same stabilization pass as the Criti ### H8. act_runner service account vs SSH alias mismatch -**Description**: [Setup-Host.ps1](../Setup-Host.ps1) L294-L306 writes the `gitea-ci` SSH alias to `$env:USERPROFILE\.ssh\config` — the interactive user's profile. The NSSM install at L264-L273 does not set an explicit service account, which on Windows commonly means LocalSystem. LocalSystem's home is `C:\Windows\System32\config\systemprofile\`, not the user profile. [gitea/workflows/build-nsis.yml](../gitea/workflows/build-nsis.yml) uses `repo-url: 'ssh://gitea-ci/Simone/...'`. If the service runs as LocalSystem, the alias resolution fails and every Mode 1 (host-side clone) attempt errors out. +**Description**: [Setup-Host.ps1](../Setup-Host.ps1) L294-L306 writes the `gitea-ci` SSH alias to `$env:USERPROFILE\.ssh\config` — the interactive user's profile. The NSSM install at L264-L273 does not set an explicit service account, which on Windows commonly means LocalSystem. LocalSystem's home is `C:\Windows\System32\config\systemprofile\`, not the user profile. [gitea/workflows/build-nsInnoUnp.yml](../gitea/workflows/build-nsInnoUnp.yml) uses `repo-url: 'ssh://gitea-ci/Simone/...'`. If the service runs as LocalSystem, the alias resolution fails and every Mode 1 (host-side clone) attempt errors out. -**Location**: [Setup-Host.ps1](../Setup-Host.ps1) L264-L273 (NSSM install), L294-L306 (SSH config write); [gitea/workflows/build-nsis.yml](../gitea/workflows/build-nsis.yml) L35. +**Location**: [Setup-Host.ps1](../Setup-Host.ps1) L264-L273 (NSSM install), L294-L306 (SSH config write); [gitea/workflows/build-nsInnoUnp.yml](../gitea/workflows/build-nsInnoUnp.yml) L35. **Impact**: Potentially blocks all Mode 1 host-side clone builds. Severity is HIGH-PENDING-VERIFICATION; first action is verification. @@ -302,7 +302,7 @@ Every item is concrete, references a file, and ends with an effort estimate. ### Critical [x] -- [x] Fix Linux matrix template selection — [gitea/actions/local-ci-build/action.yml](../gitea/actions/local-ci-build/action.yml) + [gitea/workflows/build-nsis.yml](../gitea/workflows/build-nsis.yml) — action selects `GITEA_CI_TEMPLATE_PATH` or `GITEA_CI_LINUX_TEMPLATE_PATH` based on `guest-os` input or runner label, also resolves the correct `snapshot-name` (`BaseClean` or `BaseClean-Linux`) — done 2026-05-12. +- [x] Fix Linux matrix template selection — [gitea/actions/local-ci-build/action.yml](../gitea/actions/local-ci-build/action.yml) + [gitea/workflows/build-nsInnoUnp.yml](../gitea/workflows/build-nsInnoUnp.yml) — action selects `GITEA_CI_TEMPLATE_PATH` or `GITEA_CI_LINUX_TEMPLATE_PATH` based on `guest-os` input or runner label, also resolves the correct `snapshot-name` (`BaseClean` or `BaseClean-Linux`) — done 2026-05-12. - [x] Redact Linux ExtraGuestEnv secret logging — [scripts/Invoke-RemoteBuild.ps1](../scripts/Invoke-RemoteBuild.ps1) Linux branch — replace `Write-Host "...$buildCmd"` with redacted version; never log `$envPrefix` — done 2026-05-12. ### High @@ -393,7 +393,7 @@ Goal: clear both Criticals plus the highest-impact Highs that touch production p **Task 2.1 — Fix Linux matrix template selection (C1)** [x] done 2026-05-12 - [gitea/actions/local-ci-build/action.yml](../gitea/actions/local-ci-build/action.yml): added `$resolvedGuestOS` / `$resolvedTemplatePath` / `$resolvedSnapshotName` resolution block; `Auto` infers from `$env:RUNNER_LABELS`. -- [gitea/workflows/build-nsis.yml](../gitea/workflows/build-nsis.yml): explicit `guest-os: ${{ matrix.target == 'linux' && 'Linux' || 'Windows' }}` added as belt-and-suspenders. +- [gitea/workflows/build-nsInnoUnp.yml](../gitea/workflows/build-nsInnoUnp.yml): explicit `guest-os: ${{ matrix.target == 'linux' && 'Linux' || 'Windows' }}` added as belt-and-suspenders. - Live smoke test (Task 1.4) still required. **Task 2.2 — Redact Linux ExtraGuestEnv logging (C2)** [x] done 2026-05-12 @@ -541,7 +541,7 @@ Will NOT do: Concrete improvements to workflows and the composite action. -1. **Make the composite action self-sufficient for guest OS selection** (C1 fix). After this, [gitea/workflows/build-nsis.yml](../gitea/workflows/build-nsis.yml) does not need to pass `guest-os`, `template-path`, or `snapshot-name` for the standard Windows/Linux matrix; the action infers from the runner label or accepts the explicit input. +1. **Make the composite action self-sufficient for guest OS selection** (C1 fix). After this, [gitea/workflows/build-nsInnoUnp.yml](../gitea/workflows/build-nsInnoUnp.yml) does not need to pass `guest-os`, `template-path`, or `snapshot-name` for the standard Windows/Linux matrix; the action infers from the runner label or accepts the explicit input. 2. **Add a top-level `outputs:` block** so downstream consumers can chain (H2 fix). @@ -717,7 +717,7 @@ Execute in this exact order. Each phase has a single success criterion that gate **Phase 1 — Validation (Sprint 1).** Verify the act_runner service account, confirm the directory inventory, run the 4-way concurrency burn-in, manually trigger the broken Linux matrix to confirm C1. Output: a section appended to [docs/RUNBOOK.md](../docs/RUNBOOK.md) titled "Validation Results 2026-05-XX" documenting findings. Gate: burn-in passed three times (keep `capacity: 4`) or was set to 2 (with reasoning recorded). -**Phase 2 — Critical Fixes (Sprint 2).** Fix C1 and C2, plus H2/H3/H6/H7. Manually trigger [gitea/workflows/build-nsis.yml](../gitea/workflows/build-nsis.yml) for both matrix legs after each fix. Gate: both Critical items closed and `[x]` in this plan's Critical section; both legs green in the live matrix workflow. +**Phase 2 — Critical Fixes (Sprint 2).** Fix C1 and C2, plus H2/H3/H6/H7. Manually trigger [gitea/workflows/build-nsInnoUnp.yml](../gitea/workflows/build-nsInnoUnp.yml) for both matrix legs after each fix. Gate: both Critical items closed and `[x]` in this plan's Critical section; both legs green in the live matrix workflow. **Phase 3 — High Priority Fixes (Sprint 3).** Complete the rest of the High items: H4 (bounded vmrun), H5 (stale lock), H8 (SSH alias fix), H9 (Linux validator), H10 (smoke-test workflow), H11 (script-root env var), H12 (benchmark alignment). Gate: smoke-test workflow green; all High items `[x]`. diff --git a/plans/gpt55-analysis.md b/plans/gpt55-analysis.md index caa4cb6..4bd3c1b 100644 --- a/plans/gpt55-analysis.md +++ b/plans/gpt55-analysis.md @@ -1,4 +1,4 @@ -# Technical Review: Local CI/CD System +# Technical Review: Local CI/CD System Date: 2026-05-11 @@ -154,7 +154,7 @@ The composite action idea is excellent. A reusable [gitea/actions/local-ci-build The current action has a structural YAML problem: it appears to define outputs under `inputs`, and duplicates `artifact-name`. Composite action outputs should be top-level `outputs`. As written, the `Upload artifacts` step may not receive `steps.invoke-ci.outputs.artifact-path` and `artifact-name` as intended. This can make builds succeed inside the VM but fail at artifact upload. -Workflow flexibility is otherwise good. [gitea/workflows/build-nsis.yml](../gitea/workflows/build-nsis.yml) uses a Windows/Linux matrix and disambiguates job IDs with `job-id-suffix`. That is the right pattern. Missing patterns: PR checks are not represented in the live workflow, scheduled builds are not represented, and dependency caching via `actions/cache` is not shown even though act_runner cache is enabled. For this system, PR checks and manual rebuild are more valuable than scheduled builds. +Workflow flexibility is otherwise good. [gitea/workflows/build-nsInnoUnp.yml](../gitea/workflows/build-nsInnoUnp.yml) uses a Windows/Linux matrix and disambiguates job IDs with `job-id-suffix`. That is the right pattern. Missing patterns: PR checks are not represented in the live workflow, scheduled builds are not represented, and dependency caching via `actions/cache` is not shown even though act_runner cache is enabled. For this system, PR checks and manual rebuild are more valuable than scheduled builds. Artifact upload to Gitea is done through `actions/upload-artifact@v4` in the composite action. The VM pipeline collects files into `F:\CI\Artifacts\`, then upload-artifact reads that host-side directory. That is a good bridge between local VM isolation and Gitea's artifact store, once the outputs block is fixed. diff --git a/plans/gpt55-review-of-opus.md b/plans/gpt55-review-of-opus.md index 6d59a1e..c409886 100644 --- a/plans/gpt55-review-of-opus.md +++ b/plans/gpt55-review-of-opus.md @@ -1,4 +1,4 @@ -# Cross-Review of Opus 4.7 Analysis +# Cross-Review of Opus 4.7 Analysis Date: 2026-05-11 @@ -34,7 +34,7 @@ Opus claim: "The alias lives in the user's `~/.ssh/config` - which user? `act_ru Verdict: partially correct and over-prioritized as written. -The code confirms the alias is written under the interactive user's profile: [Setup-Host.ps1](../Setup-Host.ps1#L294-L306) uses `$env:USERPROFILE\.ssh\config`. The live workflow does use the alias: [gitea/workflows/build-nsis.yml](../gitea/workflows/build-nsis.yml#L31-L35) sets `repo-url: 'ssh://gitea-ci/Simone/nsis-plugin-nsinnounp.git'`. If act_runner runs as LocalSystem, OpenSSH will not read the interactive user's SSH config or keys. That would break host-side clone mode. +The code confirms the alias is written under the interactive user's profile: [Setup-Host.ps1](../Setup-Host.ps1#L294-L306) uses `$env:USERPROFILE\.ssh\config`. The live workflow does use the alias: [gitea/workflows/build-nsInnoUnp.yml](../gitea/workflows/build-nsInnoUnp.yml#L31-L35) sets `repo-url: 'ssh://gitea-ci/Simone/nsis-plugin-nsinnounp.git'`. If act_runner runs as LocalSystem, OpenSSH will not read the interactive user's SSH config or keys. That would break host-side clone mode. The assumption that act_runner definitely runs as SYSTEM is weaker. [scripts/Register-CIScheduledTasks.ps1](../scripts/Register-CIScheduledTasks.ps1#L38-L43) says scheduled maintenance tasks run as SYSTEM; it does not install act_runner. The act_runner NSSM installation in [Setup-Host.ps1](../Setup-Host.ps1#L264-L273) does not set an explicit service account. On Windows, a service installed without an explicit account commonly runs as LocalSystem, so Opus's concern is plausible. But Opus should not have derived act_runner's identity from the scheduled-task script. The correct verification is `Get-CimInstance Win32_Service -Filter "Name='act_runner'" | Select StartName` on the host, plus a test clone under that identity. @@ -118,7 +118,7 @@ Opus missed stale `vm-start.lock` cleanup. The lock handle is released correctly Opus missed broken or stale test-plan commands that matter because this review is about trust. [docs/TEST-PLAN-v1.3-to-HEAD.md](../docs/TEST-PLAN-v1.3-to-HEAD.md#L56-L58) uses PS7-only `ForEach-Object -Parallel` and the wrong `-RepositoryUrl` parameter, while [scripts/Invoke-CIJob.ps1](../scripts/Invoke-CIJob.ps1#L80) defines `RepoUrl`. That makes the validation plan non-copy-pasteable on the mandated PS5.1 host. -Both analyses missed a likely Linux matrix wiring bug. [gitea/workflows/build-nsis.yml](../gitea/workflows/build-nsis.yml#L20-L35) runs a matrix over `windows` and `linux`, but it does not pass `guest-os`, `template-path`, or `snapshot-name`. The action defaults `guest-os` to `Auto` in [gitea/actions/local-ci-build/action.yml](../gitea/actions/local-ci-build/action.yml#L52-L57) and leaves `template-path` empty so `Invoke-CIJob.ps1` uses `GITEA_CI_TEMPLATE_PATH` in [scripts/Invoke-CIJob.ps1](../scripts/Invoke-CIJob.ps1#L114). The runner config has a separate `GITEA_CI_LINUX_TEMPLATE_PATH` in [runner/config.yaml](../runner/config.yaml#L27-L30), but the action does not consume it. Unless act_runner has per-label environment behavior not shown here, the Linux leg can clone the Windows template. This is more urgent than several Opus HIGH items because it affects the advertised Windows/Linux matrix directly. +Both analyses missed a likely Linux matrix wiring bug. [gitea/workflows/build-nsInnoUnp.yml](../gitea/workflows/build-nsInnoUnp.yml#L20-L35) runs a matrix over `windows` and `linux`, but it does not pass `guest-os`, `template-path`, or `snapshot-name`. The action defaults `guest-os` to `Auto` in [gitea/actions/local-ci-build/action.yml](../gitea/actions/local-ci-build/action.yml#L52-L57) and leaves `template-path` empty so `Invoke-CIJob.ps1` uses `GITEA_CI_TEMPLATE_PATH` in [scripts/Invoke-CIJob.ps1](../scripts/Invoke-CIJob.ps1#L114). The runner config has a separate `GITEA_CI_LINUX_TEMPLATE_PATH` in [runner/config.yaml](../runner/config.yaml#L27-L30), but the action does not consume it. Unless act_runner has per-label environment behavior not shown here, the Linux leg can clone the Windows template. This is more urgent than several Opus HIGH items because it affects the advertised Windows/Linux matrix directly. Both analyses also under-emphasized that Mode 1 host-side cloning with `ssh://gitea-ci/...` depends on host SSH identity, while Mode 2 in-guest clone for Linux should probably use HTTPS reachable from the guest plus PAT or deploy key. The test helper [scripts/Test-E2E-Section3.3.ps1](../scripts/Test-E2E-Section3.3.ps1#L113-L115) distinguishes guest HTTPS and host SSH URLs, but the composite workflow presents one `repo-url` for both matrix legs. diff --git a/plans/opus47-analysis.md b/plans/opus47-analysis.md index 2a105e6..4a4784d 100644 --- a/plans/opus47-analysis.md +++ b/plans/opus47-analysis.md @@ -1,4 +1,4 @@ -# Local-CI-CD-System — Comprehensive Technical Review +# Local-CI-CD-System — Comprehensive Technical Review Reviewer role: lead architect / senior DevOps engineer. Scope: complete repository audit at HEAD. @@ -40,7 +40,7 @@ The system declares itself "production-ready" in `README.md` line 1. Measured ag | In-VM `git clone` (Mode 2) | Complete | `-UseGitClone`, PAT via `http.extraHeader` | | Credential storage (CredentialManager) | Complete | `BuildVMGuest`, `GiteaPAT` | | Reusable composite action | Complete | `gitea/actions/local-ci-build/action.yml` | -| Build matrix Windows + Linux | Complete | `gitea/workflows/build-nsis.yml` | +| Build matrix Windows + Linux | Complete | `gitea/workflows/build-nsInnoUnp.yml` | | Shared NuGet/pip cache via HGFS | Complete | `Set-TemplateSharedFolders.ps1`, `-UseSharedCache` | | Structured JSONL logs | Complete | `Write-JobEvent` in `Invoke-CIJob.ps1` | | Scheduled maintenance | Complete | `Register-CIScheduledTasks.ps1` (4 tasks) | @@ -112,7 +112,7 @@ This is a clean pipeline. The orchestrator is a single script that owns the try/ ### 3.2 Layering — What Is Right - **`_Common.psm1` and `_Transport.psm1`** correctly separate two distinct concerns: WinRM/vmrun helpers (host-side, used by Windows path) and SSH helpers (host→guest, used by Linux path). `Invoke-Vmrun` returning `[pscustomobject]@{ExitCode, Output}` is the right abstraction — most callers want both, and `$LASTEXITCODE` is fragile across pipelines. -- **The composite action `local-ci-build`** is the right boundary. The calling repository (`nsis-plugin-nsinnounp`, per `build-nsis.yml`) declares intent (`build-command`, `artifact-source`) and the action handles transport. Adding a second consumer would require zero changes here. +- **The composite action `local-ci-build`** is the right boundary. The calling repository (`nsis-plugin-nsinnounp`, per `build-nsInnoUnp.yml`) declares intent (`build-command`, `artifact-source`) and the action handles transport. Adding a second consumer would require zero changes here. - **The file-based IP mutex + lease** is conceptually correct. Serialize the racey phase (clone + start + IP acquisition), then release. Build phases run parallel. That is the right granularity. - **The Mode 1 / Mode 2 source-transfer split**. Mode 1 (host clone → tar/zip → ship) is the safe default. Mode 2 (`-UseGitClone`, PAT via `http.extraHeader`) is the right optimization for large repos with submodules — and the `http.extraHeader` Basic-auth approach keeps the PAT out of the URL, argv, and `git config`. - **Phase 1 readiness check uses `vmrun list`, not `getGuestIPAddress`**. This is correct and the rationale in the comments of `scripts/Wait-VMReady.ps1` is exactly right. The author has clearly been burned by this in the past. @@ -406,7 +406,7 @@ Concerns: - No input validation. `inputs.guest-os` accepts any string; if a typo `'Linus'` is passed, it propagates to `Invoke-CIJob.ps1` which rejects it via `ValidateSet`. The error is clear at runtime but late. **LOW**. - No timeouts at the action level. Relies entirely on the runner-level `timeout: 2h` in `runner/config.yaml`. **LOW**. -### 8.2 The Calling Workflow `build-nsis.yml` +### 8.2 The Calling Workflow `build-nsInnoUnp.yml` - Matrix on `[windows, linux]`, `fail-fast: false`. **GOOD**. - `job-id-suffix: '${{ matrix.target }}'` to disambiguate the artifact directories. **GOOD** — this is exactly the right use of the suffix. @@ -512,7 +512,7 @@ In priority order: ### HIGH - **No automated end-to-end test**. `Measure-CIBenchmark.ps1` exists but is not wired to CI and runs without assertions. (`scripts/Measure-CIBenchmark.ps1`) -- **`act_runner` service account vs SSH alias mismatch potential**. If runner runs as SYSTEM, the `gitea-ci` SSH alias written by `Setup-Host.ps1` lives in the wrong `~/.ssh/config`. (`Setup-Host.ps1`, `gitea/workflows/build-nsis.yml`) +- **`act_runner` service account vs SSH alias mismatch potential**. If runner runs as SYSTEM, the `gitea-ci` SSH alias written by `Setup-Host.ps1` lives in the wrong `~/.ssh/config`. (`Setup-Host.ps1`, `gitea/workflows/build-nsInnoUnp.yml`) - **No CI for the CI**: Pester tests never run on commit, no integration test. (`gitea/workflows/`) - **Template-refresh procedure not codified**. KMS re-activation, snapshot rebuild, validation steps live in operator memory. (`docs/`, missing) - **`Validate-DeployState.ps1` does not assert `ci-report-ip.service` presence on Linux templates**. Silent fallback to 120 s timeout if the service is missing. (`template/Validate-DeployState.ps1`) diff --git a/plans/opus47-review-of-gpt55.md b/plans/opus47-review-of-gpt55.md index 0b37da7..17aed94 100644 --- a/plans/opus47-review-of-gpt55.md +++ b/plans/opus47-review-of-gpt55.md @@ -1,4 +1,4 @@ -# Cross-Review: GPT-5.5 Analysis of Local-CI-CD-System +# Cross-Review: GPT-5.5 Analysis of Local-CI-CD-System Reviewer: Claude Opus 4.7, acting as lead architect. Subject under review: [plans/gpt55-analysis.md](gpt55-analysis.md). @@ -32,7 +32,7 @@ The YAML structure issue is real. In [gitea/actions/local-ci-build/action.yml](. However, GPT-5.5 escalates this to "VM build can succeed but `actions/upload-artifact` may receive empty `steps.invoke-ci.outputs.artifact-path` or `artifact-name`, causing workflow failure or missing artifacts." That part is wrong. The upload-artifact step explicitly reads `steps.invoke-ci.outputs.artifact-name` and `steps.invoke-ci.outputs.artifact-path`, which are **step-level outputs** set unconditionally in the inner PowerShell block by `"artifact-path=$artifactPath" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8`. Step-level outputs do not depend on the action having a top-level `outputs:` block. The upload works today. -What the missing `outputs:` block actually breaks is the **caller-facing** action interface: a downstream workflow cannot read `steps.local-ci-build.outputs.artifact-path` from outside this composite action. Since the only consumer today is [gitea/workflows/build-nsis.yml](../gitea/workflows/build-nsis.yml), which does not consume action outputs (artifact upload happens inside the composite step), the user-visible impact is zero today. +What the missing `outputs:` block actually breaks is the **caller-facing** action interface: a downstream workflow cannot read `steps.local-ci-build.outputs.artifact-path` from outside this composite action. Since the only consumer today is [gitea/workflows/build-nsInnoUnp.yml](../gitea/workflows/build-nsInnoUnp.yml), which does not consume action outputs (artifact upload happens inside the composite step), the user-visible impact is zero today. Correct severity: **HIGH** (latent breakage waiting for a second consumer), not **CRITICAL**. The fix is still trivial — sub-30 minutes — so this distinction does not change the quick-wins list. But the framing matters for trust in the report's prioritisation. @@ -161,7 +161,7 @@ Out of 18 verifiable factual claims I cross-checked, 16 are correct, 1 is partia What GPT-5.5 missed that my own analysis caught: -1. **`act_runner` service-account vs SSH alias mismatch** (my §8.2 HIGH). If [Setup-Host.ps1](../Setup-Host.ps1) installs `act_runner` as a Windows service running under `SYSTEM`, the `~/.ssh/config` alias `gitea-ci` referenced in [gitea/workflows/build-nsis.yml](../gitea/workflows/build-nsis.yml) (`repo-url: 'ssh://gitea-ci/Simone/...'`) lives in the *interactive user's* `~/.ssh/config`, not `SYSTEM`'s. This is a runtime-reachable bug: every Linux Mode 2 clone fails with "unknown host gitea-ci" until the alias is reinstalled in `C:\Windows\System32\config\systemprofile\.ssh\config`. GPT-5.5 never mentions this. It is potentially HIGH-severity and definitely worth a verification step. +1. **`act_runner` service-account vs SSH alias mismatch** (my §8.2 HIGH). If [Setup-Host.ps1](../Setup-Host.ps1) installs `act_runner` as a Windows service running under `SYSTEM`, the `~/.ssh/config` alias `gitea-ci` referenced in [gitea/workflows/build-nsInnoUnp.yml](../gitea/workflows/build-nsInnoUnp.yml) (`repo-url: 'ssh://gitea-ci/Simone/...'`) lives in the *interactive user's* `~/.ssh/config`, not `SYSTEM`'s. This is a runtime-reachable bug: every Linux Mode 2 clone fails with "unknown host gitea-ci" until the alias is reinstalled in `C:\Windows\System32\config\systemprofile\.ssh\config`. GPT-5.5 never mentions this. It is potentially HIGH-severity and definitely worth a verification step. 2. **`Measure-CIBenchmark.ps1` uses `getGuestIPAddress` instead of `guestVar ci-ip`** (my §3.3, §11.3). GPT-5.5 mentions this almost as a footnote ("It still uses `getGuestIPAddress` rather than the newer `guestinfo.ci-ip` path"), but does not connect it to the larger issue: the benchmark measures a different IP-discovery path than production, so benchmark results do not represent real timing. This makes capacity-planning data unreliable.