From 425d8bc4f3ea441d6b4c433c8ac5f47af34417e4 Mon Sep 17 00:00:00 2001 From: Simone Date: Sun, 17 May 2026 16:09:15 +0200 Subject: [PATCH] docs: align documentation with session fixes Reconcile docs with the end-to-end pipeline fixes: - upload/download-artifact @v4 -> @v3 (Gitea GHES) in WORKFLOW-AUTHORING and workflow-example.yml; add Common Mistakes rows (v4, action ref form + DEFAULT_ACTIONS_URL, public action repo). - BEST-PRACTICES / README / HOST-SETUP: guest credential must live in the LocalSystem vault with a host-qualified username; document Set-CIGuestCredential.ps1 / Test-CIGuestWinRM.ps1 and auth=ntlm. - README / AGENTS / HOST-SETUP: production venv install is NON-editable (LocalSystem); no CI workflow may install into it. - HOST-SETUP: add DEFAULT_ACTIONS_URL=github + full-URL uses: + public repo + @main requirements discovered during validation. - Correct stale repo name local-ci-system -> local-ci-cd-system. Co-Authored-By: Claude Opus 4.7 --- AGENTS.md | 4 ++-- README.md | 20 +++++++++++------- TODO.md | 6 +++--- docs/BEST-PRACTICES.md | 43 ++++++++++++++++++++++++-------------- docs/HOST-SETUP.md | 26 +++++++++++++++++++++++ docs/WORKFLOW-AUTHORING.md | 15 +++++++------ gitea/workflow-example.yml | 14 ++++++------- 7 files changed, 87 insertions(+), 41 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index b91d5a3..3cb8bc7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -62,9 +62,9 @@ per compatibilita' con Task Scheduler e caller esterni. | Parametro | Valore | | ------------------------ | ------------------------------------------------------------------------------------- | | Python | **3.11+** richiesto (`requires-python` in `pyproject.toml`) | -| Venv produzione | `F:\CI\python\venv\` (interprete `F:\CI\python\venv\Scripts\python.exe`) | +| Venv produzione | `F:\CI\python\venv\` — install **non-editable**: `pip install .` (act_runner=LocalSystem; `-e` rompe con `No module named ci_orchestrator`). Ri-deploy a ogni modifica codice. | | Venv dev locale | `.venv\` (NON committare; gitignored) | -| Install editable | `python -m pip install -e .[dev]` | +| Install (dev locale) | `python -m pip install -e .[dev]` — editable solo nel `.venv` dev | | Test | `python -m pytest tests\python -q --cov=ci_orchestrator --cov-fail-under=90` | | Lint | `python -m ruff check src tests\python` | | Type-check | `python -m mypy --strict src` | diff --git a/README.md b/README.md index 5d3f866..f63731c 100644 --- a/README.md +++ b/README.md @@ -158,12 +158,16 @@ Python sub-commands listed below. ### Setup ```powershell -# Production venv on the runner host (one-time) +# Production venv on the runner host (one-time + on every code deploy). +# NON-editable: act_runner runs as LocalSystem and cannot resolve an +# editable .pth into the dev tree or a transient RunnerWork dir +# (-> "No module named ci_orchestrator"). Re-run `pip install .` after +# each code change. No CI workflow may install into this venv. python -m venv F:\CI\python\venv F:\CI\python\venv\Scripts\python.exe -m pip install --upgrade pip -F:\CI\python\venv\Scripts\python.exe -m pip install -e ".[dev]" +F:\CI\python\venv\Scripts\python.exe -m pip install . -# Local dev venv (NOT committed; see .gitignore) +# Local dev venv (NOT committed; see .gitignore) — editable is fine here python -m venv .venv .\.venv\Scripts\python.exe -m pip install -e ".[dev]" ``` @@ -294,10 +298,12 @@ Dettagli: [docs/LINUX-TEMPLATE-SETUP.md](docs/LINUX-TEMPLATE-SETUP.md). ### 2. Credenziali guest ```powershell -# PowerShell elevato — una volta sola sull'host -Import-Module CredentialManager -New-StoredCredential -Target "BuildVMGuest" -UserName "ci_build" ` - -Password "" -Persist LocalMachine +# PowerShell elevato — una volta sola sull'host. +# Scrive nel vault SYSTEM (act_runner gira come LocalSystem; un +# New-StoredCredential dalla sessione utente NON sarebbe leggibile dal +# runner). Username host-qualificato col computer name del guest +# (bare 'ci_build' -> NTLM SEC_E_UNKNOWN_CREDENTIALS). +.\scripts\Set-CIGuestCredential.ps1 -UserName 'WINBUILD-2025\ci_build' ``` ### 3. act_runner diff --git a/TODO.md b/TODO.md index 3fb87a7..714ae91 100644 --- a/TODO.md +++ b/TODO.md @@ -177,7 +177,7 @@ _Last updated: 2026-05-11 (post Sprint 15: §7.5 DONE — Rinomina Setup-* → I - [x] `gitea/workflows/lint.yml` creato — PSScriptAnalyzer su push/PR che toccano `.ps1` (2026-05-09) - [x] **[P3] Adattare workflow per altri repository** — generalizzare `BUILD_COMMAND` e `ARTIFACT_SOURCE`. COMPLETATO 2026-05-11: `gitea/workflow-example.yml` riscritto per usare la composite action - `Simone/local-ci-system/.gitea/actions/local-ci-build@main`. Commenti inline guidano + `Simone/local-ci-cd-system/.gitea/actions/local-ci-build@main`. Commenti inline guidano la personalizzazione di `build-command`, `artifact-source`, submodules, SSH URL, `extra-guest-env-json` e `artifact-name`. Nessuna logica di orchestrazione nei repo chiamanti. @@ -488,7 +488,7 @@ jobs: runs-on: windows-build # or linux-build steps: - uses: actions/checkout@v4 - - uses: Simone/local-ci-system/.gitea/actions/local-ci-build@main + - uses: Simone/local-ci-cd-system/.gitea/actions/local-ci-build@main with: build-command: 'python build_plugin.py --final --dist-dir dist' artifact-source: 'dist' @@ -526,7 +526,7 @@ Artifact separati: `nsis-plugin-nsinnounp-windows-` e `nsis-plugin-nsinnoun Usage dal workflow: ```yaml -- uses: Simone/local-ci-system/.gitea/actions/local-ci-build@main +- uses: Simone/local-ci-cd-system/.gitea/actions/local-ci-build@main with: build-command: 'python sign_and_build.py' extra-guest-env-json: '{"SIGN_PASS":"${{ secrets.SIGN_PASS }}","CERT_THUMBPRINT":"${{ secrets.CERT_THUMBPRINT }}"}' diff --git a/docs/BEST-PRACTICES.md b/docs/BEST-PRACTICES.md index ca58131..737c768 100644 --- a/docs/BEST-PRACTICES.md +++ b/docs/BEST-PRACTICES.md @@ -4,29 +4,40 @@ ### Do NOT store credentials in scripts or config files -The scripts use `-GuestCredentialTarget` (a Windows Credential Manager target name) -rather than plaintext username/password parameters. Store credentials once: +The guest VM credential is referenced by target name (`BuildVMGuest`, +`GITEA_CI_GUEST_CRED_TARGET`) and read by the Python orchestrator via the +`keyring` library — never as plaintext parameters. + +> **Critical: store it in the LocalSystem vault, not your user vault.** +> act_runner runs as the `LocalSystem` service account. Windows Credential +> Manager / keyring vaults are **per-user**. A credential added with +> `cmdkey` or the Credential Manager UI from an interactive admin session +> lands in *your* vault and is invisible to the runner, which then fails +> with `Credential 'BuildVMGuest' not found in keyring`. + +> **Username must be host-qualified.** The guest is a workgroup machine; +> NTLM rejects a bare `ci_build` with `SEC_E_UNKNOWN_CREDENTIALS`. Store +> it as `WINBUILD-2025\ci_build` (the guest computer name, i.e. the WinRM +> TLS certificate CN). The WinRM transport forces `auth='ntlm'` for the +> same reason (Negotiate→Kerberos is meaningless without a domain). + +Store (or rotate) the credential with the helper, which writes into the +SYSTEM vault via the production venv's `keyring` (run elevated): ```powershell -# Run on host (once, before first CI job) -cmdkey /generic:BuildVMGuest /user:MACHINENAME\ci_build /pass:YourStrongPassword +.\scripts\Set-CIGuestCredential.ps1 -UserName 'WINBUILD-2025\ci_build' ``` -Retrieve in scripts via the `CredentialManager` PowerShell module: - -```powershell -Install-Module CredentialManager -Scope CurrentUser -$cred = Get-StoredCredential -Target 'BuildVMGuest' -``` +It prompts for the password securely, writes it to the SYSTEM vault, and +verifies the read-back as SYSTEM. Diagnose WinRM reachability/auth with +`.\scripts\Test-CIGuestWinRM.ps1 -IpAddress `. ### Rotate credentials quarterly -1. Update password in the template VM (requires rebuilding `BaseClean` snapshot) -2. Update Windows Credential Manager on the host: - ``` - cmdkey /generic:BuildVMGuest /user:MACHINENAME\ci_build /pass:NewPassword - ``` -3. No script changes required — they reference the target name, not the password. +1. Update the password in the template VM (rebuild the `BaseClean` snapshot). +2. Re-run `Set-CIGuestCredential.ps1 -UserName 'WINBUILD-2025\ci_build'` + with the new password. +3. No code changes required — the orchestrator references the target name. --- diff --git a/docs/HOST-SETUP.md b/docs/HOST-SETUP.md index 3ca06ee..1d65fc4 100644 --- a/docs/HOST-SETUP.md +++ b/docs/HOST-SETUP.md @@ -91,6 +91,32 @@ Apri PowerShell **come Administrator**, poi: - Avvia servizio (`SERVICE_AUTO_START`) 7. **Configura SSH alias `gitea-ci`** in `~/.ssh/config` (HostName, Port, User git) +### Note operative (scoperte in validazione end-to-end) + +Setup-Host.ps1 da solo **non basta** per la pipeline Python su runner +LocalSystem. Dopo Setup-Host: + +- **Credenziale guest nel vault SYSTEM, username host-qualificato.** Lo + step 4 scrive in Windows Credential Manager dell'utente interattivo → + invisibile ad act_runner (LocalSystem). Esegui (elevato): + `\.\scripts\Set-CIGuestCredential.ps1 -UserName 'WINBUILD-2025\ci_build'`. + Bare `ci_build` viene rifiutato da NTLM (`SEC_E_UNKNOWN_CREDENTIALS`): + usa il computer name del guest (= CN del cert WinRM). Diagnostica: + `\.\scripts\Test-CIGuestWinRM.ps1 -IpAddress `. +- **venv di produzione = install non-editable.** Popola + `F:\CI\python\venv` con `python -m pip install .` dal repo reale — + **mai** `pip install -e .` (il `.pth` punta a un path che LocalSystem + non risolve → `No module named ci_orchestrator`). Vedi + `plans/PhaseA-user-checklist.md` Passo 1. Nessun workflow CI deve + installare in quel venv (lint usa un venv effimero per-job). +- **Gitea `app.ini`:** imposta `[actions] DEFAULT_ACTIONS_URL = github` + e referenzia l'action col **URL completo** + (`https://gitea.emulab.it/Simone/local-ci-cd-system/.gitea/actions/local-ci-build@main`) + così le action GitHub (checkout, upload-artifact) restano risolvibili + e l'action locale punta all'istanza. Il repo `local-ci-cd-system` deve + essere **pubblico** (il runner clona l'action senza autenticazione) e + l'action deve esistere su `@main` (il ref che il runner clona). + --- ## Parametri principali diff --git a/docs/WORKFLOW-AUTHORING.md b/docs/WORKFLOW-AUTHORING.md index 0680fef..7432b3b 100644 --- a/docs/WORKFLOW-AUTHORING.md +++ b/docs/WORKFLOW-AUTHORING.md @@ -254,7 +254,7 @@ jobs: - name: Upload artifacts if: success() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v3 with: name: build-${{ github.ref_name }} path: F:\CI\Artifacts\${{ github.run_id }}\artifacts.zip @@ -262,7 +262,7 @@ jobs: - name: Upload logs on failure if: failure() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v3 with: name: build-logs-${{ github.run_id }} path: F:\CI\Artifacts\${{ github.run_id }}\*.log @@ -308,7 +308,7 @@ jobs: - name: Upload artifacts if: success() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v3 with: name: build-linux-${{ github.ref_name }} path: F:\CI\Artifacts\${{ github.run_id }}\artifacts.zip @@ -359,7 +359,7 @@ jobs: - name: Upload artifacts if: success() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v3 with: name: build-${{ github.ref_name }} path: F:\CI\Artifacts\${{ github.run_id }}\artifacts.zip @@ -404,7 +404,7 @@ jobs: - name: Upload artifacts if: success() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v3 with: name: build-linux-${{ github.ref_name }} path: F:\CI\Artifacts\${{ github.run_id }}\artifacts.zip @@ -502,7 +502,7 @@ jobs: - name: Upload artifacts if: success() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v3 with: name: build-${{ matrix.config }}-${{ github.ref_name }} path: F:\CI\Artifacts\${{ github.run_id }}-${{ matrix.config }}\artifacts.zip @@ -573,3 +573,6 @@ the cleanup task `Cleanup-OrphanedBuildVMs.ps1` handles these cases. | `-TemplatePath` for Linux without `GITEA_CI_LINUX_TEMPLATE_PATH` | Pass `$env:GITEA_CI_LINUX_TEMPLATE_PATH` or the full path explicitly | | Forgetting `-SnapshotName 'BaseClean-Linux'` for Linux | Linux template uses a different snapshot name than Windows (`BaseClean`) | | Same `-JobId` for matrix jobs | Append matrix key: `${{ github.run_id }}-${{ matrix.config }}` | +| `actions/upload-artifact@v4` / `download-artifact@v4` | Use `@v3`. Gitea reports as GHES; @v4 (`@actions/artifact` v2 / Twirp API) fails with `GHESNotSupportedError`. | +| Short action ref `Simone/local-ci-cd-system/...@main` | Use the full URL `https://gitea.emulab.it/Simone/local-ci-cd-system/.gitea/actions/local-ci-build@main`; with `[actions] DEFAULT_ACTIONS_URL = github` in Gitea's app.ini this keeps GitHub actions (checkout, upload-artifact) resolvable while pointing the composite action at the local instance. The action repo must be on `@main` (the ref the runner clones). | +| Composite action referenced from a private/internal repo | The runner clones the action repo unauthenticated. Make `local-ci-cd-system` public, or the clone fails with `authentication required: Unauthorized`. | diff --git a/gitea/workflow-example.yml b/gitea/workflow-example.yml index 0c71c6a..5eeb2e8 100644 --- a/gitea/workflow-example.yml +++ b/gitea/workflow-example.yml @@ -9,7 +9,7 @@ # windows-build -> WinBuild2025 (WinRM/HTTPS transport) # linux-build -> LinuxBuild2404 (SSH transport) # -# The composite action (Simone/local-ci-system/.gitea/actions/local-ci-build) +# The composite action (Simone/local-ci-cd-system/.gitea/actions/local-ci-build) # handles the full VM lifecycle: clone template -> start -> build -> # collect artifacts -> upload to Gitea -> destroy VM. # No orchestration logic needed in each repo's workflow. @@ -36,7 +36,7 @@ jobs: steps: # ── Invoke the full VM lifecycle via composite action ─────────────────── - - uses: Simone/local-ci-system/.gitea/actions/local-ci-build@main + - uses: https://gitea.emulab.it/Simone/local-ci-cd-system/.gitea/actions/local-ci-build@main with: # --- adapt these two per-repository ----------------------------------- build-command: 'python build.py --dist-dir dist' # command run in VM @@ -63,7 +63,7 @@ jobs: # Step 4: Upload artifacts even on failure (for diagnostics) - name: Upload diagnostic logs on failure if: failure() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v3 with: name: build-logs-${{ github.sha }} path: F:\CI\Artifacts\${{ github.run_id }}-${{ github.run_attempt }}\*.log @@ -140,7 +140,7 @@ jobs: # # - name: Upload artifacts (${{ matrix.target }}) # if: success() -# uses: actions/upload-artifact@v4 +# uses: actions/upload-artifact@v3 # with: # name: build-${{ matrix.target }}-${{ github.sha }} # path: F:\CI\Artifacts\${{ github.run_id }}-${{ github.run_attempt }}-${{ matrix.target }}\ @@ -154,7 +154,7 @@ jobs: # The composite action handles: Invoke-CIJob.ps1 call + artifact upload + log upload. # # Reference: gitea/actions/local-ci-build/action.yml in this repository. -# Deployed path in Gitea: Simone/local-ci-system / .gitea/actions/local-ci-build/action.yml +# Deployed path in Gitea: Simone/local-ci-cd-system / .gitea/actions/local-ci-build/action.yml # # Minimum example (Windows build, Python project with submodules): # @@ -171,7 +171,7 @@ jobs: # runs-on: windows-build # steps: # - uses: actions/checkout@v4 -# - uses: Simone/local-ci-system/.gitea/actions/local-ci-build@main +# - uses: https://gitea.emulab.it/Simone/local-ci-cd-system/.gitea/actions/local-ci-build@main # with: # build-command: 'python build_plugin.py --final --dist-dir dist' # artifact-source: 'dist' @@ -188,7 +188,7 @@ jobs: # runs-on: ${{ matrix.target }}-build # steps: # - uses: actions/checkout@v4 -# - uses: Simone/local-ci-system/.gitea/actions/local-ci-build@main +# - uses: https://gitea.emulab.it/Simone/local-ci-cd-system/.gitea/actions/local-ci-build@main # with: # build-command: 'python build_plugin.py --final --dist-dir dist' # artifact-source: 'dist'