--- phase: 08-gitea-ci plan: 01 type: execute wave: 1 depends_on: [] files_modified: - .gitea/workflows/runner-probe.yml autonomous: false requirements: [CI-01, CI-02] user_setup: - service: gitea-actions-runner why: "CI cannot run without a registered act_runner; 0 runners currently registered on git.bergerhouse.net" dashboard_config: - task: "Install + register act_runner on the Unraid host against git.bergerhouse.net, prefer Docker-executor mode (service containers require it)" location: "Unraid Community Applications → act_runner template; register with a runner-registration token from Gitea → Site Admin → Actions → Runners" - service: gitea-registry-pat why: "Publish job (CI-02) authenticates to the Gitea container registry; GITHUB_TOKEN/GITEA_TOKEN cannot push packages" env_vars: - name: GITEA_REGISTRY_PAT source: "Gitea → Settings → Applications → Generate Token with write:package (+ read:package) scope; add as repo secret GITEA_REGISTRY_PAT" must_haves: truths: - "A runner-probe workflow runs on the gsd/phase-08-gitea-ci branch and prints Node/pnpm versions, runner mode, Docker access, action resolution, and Playwright WebKit dep installability" - "The probe surfaces whether the runner is Docker-executor (services: works) or host-executor (docker run fallback needed) — the answer that forks W1/W2 DB bring-up" - "An act_runner is registered and visible in the Gitea Actions runners list (operator action)" - "A GITEA_REGISTRY_PAT repo secret with write:package scope exists (operator action)" artifacts: - path: ".gitea/workflows/runner-probe.yml" provides: "Probe-only workflow answering runner unknowns P-01..P-13" contains: "runner-probe" key_links: - from: ".gitea/workflows/runner-probe.yml" to: "the registered act_runner" via: "runs-on: ubuntu-latest (runner has no self-hosted label), on: push to gsd/phase-08-gitea-ci" pattern: "runs-on:\\s*ubuntu-latest" --- Establish the Gitea CI foundation by (a) registering the act_runner and creating the registry PAT (operator actions), and (b) landing a probe-only workflow that answers every runner unknown BEFORE any real test/build/publish step is trusted. This is Pitfall 12 (runner-probe-first) and the critical fork in 08-RESEARCH §Runner-Probe Checklist: several downstream design choices (service containers vs docker run, action resolution, reporter override, artifact upload fork, WebKit deps) depend on the probe's answers. Purpose: De-risk every assumption (A1–A10 in 08-RESEARCH Assumptions Log) on the actual Unraid runner so Waves 1–2 are written against confirmed behavior, not guesses. Per D-03 the real CI lives in one ci.yml; the probe is a separate throwaway workflow on the feature branch. Output: `.gitea/workflows/runner-probe.yml`, a registered runner, and a stored registry PAT. @$HOME/.claude/gsd-core/workflows/execute-plan.md @$HOME/.claude/gsd-core/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/08-gitea-ci/08-CONTEXT.md @.planning/phases/08-gitea-ci/08-RESEARCH.md @.planning/research/PITFALLS.md - `.gitea/workflows/runner-probe.yml` (NEW — this plan) - `.gitea/workflows/ci.yml` (NEW — Plans 02/03/04) Task 1: Register act_runner + create registry PAT (operator-only) Nothing automated — these are infrastructure actions outside the repo that the executor cannot perform (08-VALIDATION Manual-Only table; CI-01/CI-02 prerequisites). 1. On the Unraid host, install/register act_runner against https://git.bergerhouse.net using a runner-registration token from Gitea → Site Admin → Actions → Runners. PREFER Docker-executor mode — service containers (the MariaDB the CI needs) only work in Docker mode (08-RESEARCH §Critical fork, Assumption A1). If only host mode is available, that is acceptable; the probe (Task 2) will detect it and Waves 1–2 will use the docker-run fallback. 2. Confirm the runner appears with status "idle"/online in Gitea → Site Admin → Actions → Runners. 3. In Gitea → Settings → Applications, generate a token with `write:package` (and `read:package`) scope. Add it as a repository secret named `GITEA_REGISTRY_PAT` (repo → Settings → Actions → Secrets). Do NOT paste the token anywhere in the repo. Type "runner registered" once the runner is online AND the GITEA_REGISTRY_PAT secret exists, or describe what is blocking (e.g. host-mode only). Task 2: Author the runner-probe workflow .gitea/workflows/runner-probe.yml - .planning/phases/08-gitea-ci/08-RESEARCH.md (§Runner-Probe Checklist — the P-01..P-13 table is the canonical task list; §Critical fork Docker-vs-host) - .planning/research/PITFALLS.md (Pitfall 12 runner-probe-first, Pitfall 11 MariaDB-11 healthcheck) - apps/pwa/playwright.config.ts (reporter: 'github' under CI — probe must note whether annotations render) Create `.gitea/workflows/runner-probe.yml` as a probe-only, non-destructive workflow. Trigger: `on: push` filtered to `branches: [gsd/phase-08-gitea-ci]` (runs on the current feature branch; never on main). `runs-on: self-hosted`. The job MUST answer every check in 08-RESEARCH §Runner-Probe Checklist P-01..P-13. Implement each as a clearly-labeled step whose output is visible in the Gitea Actions log: - P-01 Node: `node --version` (note if not 22; then test `actions/setup-node@v4` with node-version 22 — P-08). - P-02 pnpm: `pnpm --version || (corepack enable pnpm && pnpm --version)`. - P-03 Runner mode (THE critical fork): print `cat /proc/1/cgroup | head -5`, `hostname`, and `ls -la /.dockerenv 2>&1` so the log shows whether the job runs in a Docker container (Docker-executor → services: works) or on bare host (host-executor → docker run fallback). State the conclusion explicitly in an `echo` line. - P-04 Docker socket: `docker info 2>&1 | head -20` and `docker ps 2>&1 | head`. - P-05 Service container spawn: add `services: mariadb: { image: mariadb:11, env: { MARIADB_ROOT_PASSWORD: root, MARIADB_DATABASE: familysync, MARIADB_USER: familysync, MARIADB_PASSWORD: testpass }, options: --health-cmd="healthcheck.sh --connect --innodb_initialized" --health-interval=10s --health-timeout=5s --health-retries=10 --health-start-period=30s }` to the probe job; a step runs `docker ps | grep -i maria || echo "no mariadb container visible (likely host mode)"`. - P-06 MariaDB reachability: try BOTH `mysql -h mariadb -P 3306 -u familysync -ptestpass -e "SELECT 1" 2>&1 | head` (Docker mode hostname) AND `mysql -h 127.0.0.1 ...` (host mode). Record which hostname resolves (do NOT fail the job if one path errors — capture both, `continue-on-error: true` on the step or `|| true`). - P-07 checkout: `uses: actions/checkout@v4` as the first real step; reaching subsequent steps proves it resolves. - P-08 setup-node: `uses: actions/setup-node@v4` with `node-version: '22'`; print resulting `node --version`. - P-09 cache: `uses: actions/cache@v4` with a throwaway key, wrapped `continue-on-error: true` — log whether it completes or hangs/times out (08-RESEARCH Pitfall 7). - P-10 Playwright WebKit deps: in `apps/pwa`, `npx playwright install --with-deps webkit chromium 2>&1 | tail -30` with `continue-on-error: true` — confirms WebKit system deps install without sudo/apt failure (A10). - P-11 artifact upload: write a dummy file and `uses: https://github.com/ChristopherHX/gitea-upload-artifact@v4` (NOT actions/upload-artifact@v4 — broken on Gitea per 08-RESEARCH) with `continue-on-error: true`; note whether the artifact appears in the Gitea UI. - P-13 short SHA: `echo "short sha = ${GITHUB_SHA:0:7}"` — confirms the D-04 tag expression produces 7 chars. Do NOT include P-12 (docker login/push) here — defer registry login to Plan 04 to avoid exercising the PAT before the publish job is designed. Add a final summary step that echoes a one-line verdict per fork (Docker vs host mode; cache usable y/n; WebKit deps ok y/n; upload-artifact fork works y/n) so the SUMMARY can record the answers. Keep the workflow non-destructive: no migrations, no pushes, no writes to main. All probe steps that may fail on this runner use `continue-on-error: true` or `|| true` so the probe reports findings instead of red-failing on an expected unknown. test -f .gitea/workflows/runner-probe.yml && grep -q "runs-on: self-hosted" .gitea/workflows/runner-probe.yml && grep -q "healthcheck.sh --connect --innodb_initialized" .gitea/workflows/runner-probe.yml && grep -q "ChristopherHX/gitea-upload-artifact@v4" .gitea/workflows/runner-probe.yml && ! grep -q "actions/upload-artifact@v4" .gitea/workflows/runner-probe.yml && ! grep -q "mysqladmin" .gitea/workflows/runner-probe.yml && echo PROBE_OK runner-probe.yml exists, triggers only on the feature branch, uses `healthcheck.sh --connect --innodb_initialized` (never mysqladmin), uses the gitea-upload-artifact fork (never actions/upload-artifact@v4), and contains a step for each of P-01..P-11 + P-13. Task 3: Run the probe and record the fork answers The runner-probe workflow (Task 2), pushed to the gsd/phase-08-gitea-ci branch so the now-registered runner executes it. 1. Ensure the branch is pushed: `git push origin gsd/phase-08-gitea-ci` (this commit triggers the probe). 2. Open Gitea → repo → Actions; find the "runner-probe" workflow run. 3. Read the log and record the answers to the fork questions: - P-03: Docker-executor mode or host-executor mode? (drives Waves 1–2 DB bring-up) - P-05/P-06: did the MariaDB service container appear, and on which hostname (`mariadb` vs `127.0.0.1`)? - P-09: did actions/cache complete or hang? (cache optional decision) - P-10: did `playwright install --with-deps webkit` succeed? (WebKit feasibility) - P-11: did the gitea-upload-artifact fork upload successfully and appear in the UI? 4. Confirm no secret/token is printed anywhere in the probe log (the probe must not touch the PAT). Paste the fork answers (Docker vs host mode; service-container hostname; cache works y/n; WebKit deps y/n; artifact upload y/n) so the executor records them in the SUMMARY for Waves 1–2. Type "probe results recorded" to continue. ## Trust Boundaries | Boundary | Description | |----------|-------------| | CI workflow → self-hosted runner | Untrusted-ish: workflow YAML executes on operator infra with Docker socket access | | Repo secret store → workflow env | PAT crosses into the job; must never echo | ## STRIDE Threat Register | Threat ID | Category | Component | Disposition | Mitigation Plan | |-----------|----------|-----------|-------------|-----------------| | T-08-01 | Information Disclosure | runner-probe.yml | mitigate | Probe NEVER references `secrets.GITEA_REGISTRY_PAT` or any secret; no `docker login` in the probe (P-12 deferred to Plan 04). Verified by checkpoint log audit. | | T-08-02 | Elevation of Privilege | Docker socket on runner | accept | Docker socket access is inherent to act_runner Docker-executor mode; accepted per Gitea self-hosted docs (08-RESEARCH Security Domain). | | T-08-SC | Tampering | gitea-upload-artifact@v4 (only new external action) | mitigate | [VERIFIED] in 08-RESEARCH Package Legitimacy Audit (github.com/ChristopherHX/gitea-upload-artifact) as the cited Gitea fix for the upload-artifact@v4 GHES block; pinned at @v4. All other actions are official GitHub/Docker actions. No [ASSUMED]/[SUS] packages → no install checkpoint required. | - runner-probe.yml present, branch-scoped, non-destructive; passes the Task 2 grep gate. - Probe run observed in Gitea Actions; fork answers recorded in SUMMARY. - Runner online; GITEA_REGISTRY_PAT secret created (operator confirmed). - No secret material printed in any probe log line. - Maps to CI-01/CI-02 prerequisites and Pitfall 12: the runner environment is probed BEFORE any real test/build/publish step is designed. - The Docker-vs-host fork (A1) is answered; the answer is recorded so Plans 02–04 pick the correct DB bring-up path. - Operator infra (runner + PAT) is in place. Create `.planning/phases/08-gitea-ci/08-01-SUMMARY.md` when done. MUST record the probe fork answers (runner mode, service-container hostname, cache usable y/n, WebKit deps y/n, upload-artifact fork y/n) — Plans 02–04 consume them.