HOMELAB-1735: ci: env-block Harbor push secrets (act substitutes textually) #522

Merged
aaron merged 3 commits from plane/HOMELAB-1735-env-fix into live 2026-09-29 08:34:53 +00:00
Owner

Defect B — the publish path cannot carry a $-bearing username

The Harbor robot credentials are now correct in the repo (OpenBao-sourced, byte-verified, registry probe 200), yet the publish still failed. Run 5675 / index 1491 (workflow_dispatch on live @ dd99f5c):

08:06:47.409  Successfully tagged harbor.aaron.reynoza.org/platform/forgejo-runner:dd99f5c
08:06:47.422  /workspace/act/c7f22dc5e5357045/act/workflow/3.sh: line 16: forgejo: unbound variable
-> Job 'build-and-push' failed (Push to Harbor step)

Forgejo/act substitutes ${{ secrets.X }} textually into the generated shell script. The step was:

run: |
  set -euo pipefail
  echo "${{ secrets.HARBOR_PUSH_PASSWORD }}" | docker login harbor.aaron.reynoza.org \
    -u "${{ secrets.HARBOR_PUSH_USERNAME }}" --password-stdin

so the script line became -u "robot$forgejo-runner-push" and bash expanded $forgejo — a hard abort under set -u (run 1491), or a silent degradation to robot-runner-push → 401 unauthorized without it (defect A, run 1490). Robot names always contain $, so this is the first credential in this repo that cannot be safely inlined. Previous run of the same workflow on 2026-09-22 logged Login Succeeded only because that credential was $-free.

Fix

Move both secrets into a step-level env: block and reference $HARBOR_PUSH_USERNAME / $HARBOR_PUSH_PASSWORD in the shell. Env values are handed to the step process verbatim and the shell never re-expands them, so the secret value never enters the script text.

      - name: Push to Harbor
        env:
          HARBOR_PUSH_USERNAME: ${{ secrets.HARBOR_PUSH_USERNAME }}
          HARBOR_PUSH_PASSWORD: ${{ secrets.HARBOR_PUSH_PASSWORD }}
        run: |
          set -euo pipefail
          export DOCKER_CONFIG="$(mktemp -d)"
          echo "$HARBOR_PUSH_PASSWORD" | docker login harbor.aaron.reynoza.org \
            -u "$HARBOR_PUSH_USERNAME" --password-stdin

Applied to .forgejo/workflows/runner-image-build.yaml and .forgejo/workflows/docs-build.yaml (both carry the identical HARBOR_PUSH_* login). Nothing else changes — the mktemp-DOCKER_CONFIG / no-logout structure from #521 is untouched. In-repo precedent: release.yaml already passes an arbitrary PR body (github.event.pull_request.body) through a step env: block.

Why this is safe (mechanism, not assumption)

Forgejo runner v12 host environment executes steps via exec.CommandContext with cmd.Env = getEnvListFromMap(env) (act/container/host_environment.go); step-level env: is merged into that map (act/runner/step.go: mergeEnv). Env values are KEY=value entries in the child process environment — no shell parses them.

Verify plan

  1. Local (done, below): rendered the substituted script for both workflows. Broken form reproduces forgejo: unbound variable; fixed form logs Login Succeeded with the byte-exact robot$forgejo-runner-push and the rendered script contains no secret value. yamllint clean (one pre-existing long line removed).
  2. PR CI green (lint.yaml + ci.yaml; neither lints .forgejo/workflows/).
  3. End-to-end on merge: this file lists itself in on.push.paths, so merging to live self-triggers the runner-image publish at the new merge sha. That run must log Login Succeeded, push, and crane digest harbor.aaron.reynoza.org/platform/forgejo-runner:<new-sha> must resolve.
  4. docs-build.yaml does not self-trigger (its paths: excludes itself) — it will prove out on the next docs/** push. Identical env block; the rendered-script proof covers it.

Local evidence

# broken form (textual substitution, as act does):
  -u "robot$forgejo-runner-push" --password-stdin
push_rendered_broken.sh: line 15: forgejo: unbound variable            exit=1

# fixed form (step env delivered as process environment):
STUB docker argv: [login] [harbor.aaron.reynoza.org] [-u] [robot$forgejo-runner-push] [--password-stdin]
WARNING! Your password will be stored unencrypted in /tmp/tmp.XXX/config.json.
Login Succeeded
exit=0
# and: no secret value appears in the generated script text; no `docker logout`;
#      throwaway DOCKER_CONFIG

No cluster mutation, no pod restart, no secret value printed.

Note on a stale assumption: pi-image-build.yaml is the precedent for the mktemp-DOCKER_CONFIG/no-logout structure, but it still inlines the secrets — it only works because HARBOR_USERNAME is $-free. The env: block is the new part.

## Defect B — the publish path cannot carry a `$`-bearing username The Harbor robot credentials are now correct in the repo (OpenBao-sourced, byte-verified, registry probe 200), yet the publish still failed. Run **5675 / index 1491** (`workflow_dispatch` on `live` @ `dd99f5c`): ``` 08:06:47.409 Successfully tagged harbor.aaron.reynoza.org/platform/forgejo-runner:dd99f5c 08:06:47.422 /workspace/act/c7f22dc5e5357045/act/workflow/3.sh: line 16: forgejo: unbound variable -> Job 'build-and-push' failed (Push to Harbor step) ``` Forgejo/act substitutes `${{ secrets.X }}` **textually into the generated shell script**. The step was: ```yaml run: | set -euo pipefail echo "${{ secrets.HARBOR_PUSH_PASSWORD }}" | docker login harbor.aaron.reynoza.org \ -u "${{ secrets.HARBOR_PUSH_USERNAME }}" --password-stdin ``` so the script line became `-u "robot$forgejo-runner-push"` and bash expanded `$forgejo` — a hard abort under `set -u` (run 1491), or a silent degradation to `robot-runner-push` → `401 unauthorized` without it (**defect A**, run 1490). Robot names always contain `$`, so this is the first credential in this repo that cannot be safely inlined. Previous run of the same workflow on 2026-09-22 logged `Login Succeeded` only because that credential was `$`-free. ## Fix Move both secrets into a **step-level `env:` block** and reference `$HARBOR_PUSH_USERNAME` / `$HARBOR_PUSH_PASSWORD` in the shell. Env values are handed to the step process verbatim and the shell never re-expands them, so the secret value never enters the script text. ```yaml - name: Push to Harbor env: HARBOR_PUSH_USERNAME: ${{ secrets.HARBOR_PUSH_USERNAME }} HARBOR_PUSH_PASSWORD: ${{ secrets.HARBOR_PUSH_PASSWORD }} run: | set -euo pipefail export DOCKER_CONFIG="$(mktemp -d)" echo "$HARBOR_PUSH_PASSWORD" | docker login harbor.aaron.reynoza.org \ -u "$HARBOR_PUSH_USERNAME" --password-stdin ``` Applied to `.forgejo/workflows/runner-image-build.yaml` and `.forgejo/workflows/docs-build.yaml` (both carry the identical `HARBOR_PUSH_*` login). **Nothing else changes** — the mktemp-`DOCKER_CONFIG` / no-logout structure from #521 is untouched. In-repo precedent: `release.yaml` already passes an arbitrary PR body (`github.event.pull_request.body`) through a step `env:` block. ### Why this is safe (mechanism, not assumption) Forgejo runner v12 host environment executes steps via `exec.CommandContext` with `cmd.Env = getEnvListFromMap(env)` (`act/container/host_environment.go`); step-level `env:` is merged into that map (`act/runner/step.go: mergeEnv`). Env values are `KEY=value` entries in the child process environment — no shell parses them. ## Verify plan 1. **Local (done, below):** rendered the substituted script for both workflows. Broken form reproduces `forgejo: unbound variable`; fixed form logs `Login Succeeded` with the byte-exact `robot$forgejo-runner-push` and the rendered script contains no secret value. `yamllint` clean (one pre-existing long line removed). 2. **PR CI** green (lint.yaml + ci.yaml; neither lints `.forgejo/workflows/`). 3. **End-to-end on merge:** this file lists itself in `on.push.paths`, so merging to `live` self-triggers the runner-image publish at the **new merge sha**. That run must log `Login Succeeded`, push, and `crane digest harbor.aaron.reynoza.org/platform/forgejo-runner:<new-sha>` must resolve. 4. `docs-build.yaml` does **not** self-trigger (its `paths:` excludes itself) — it will prove out on the next `docs/**` push. Identical env block; the rendered-script proof covers it. ## Local evidence ``` # broken form (textual substitution, as act does): -u "robot$forgejo-runner-push" --password-stdin push_rendered_broken.sh: line 15: forgejo: unbound variable exit=1 # fixed form (step env delivered as process environment): STUB docker argv: [login] [harbor.aaron.reynoza.org] [-u] [robot$forgejo-runner-push] [--password-stdin] WARNING! Your password will be stored unencrypted in /tmp/tmp.XXX/config.json. Login Succeeded exit=0 # and: no secret value appears in the generated script text; no `docker logout`; # throwaway DOCKER_CONFIG ``` No cluster mutation, no pod restart, no secret value printed. Note on a stale assumption: `pi-image-build.yaml` is the precedent for the mktemp-`DOCKER_CONFIG`/no-logout structure, but it still **inlines** the secrets — it only works because `HARBOR_USERNAME` is `$`-free. The `env:` block is the new part.
HOMELAB-1735: ci: env-block Harbor push secrets (act substitutes textually)
All checks were successful
0/0 projects applied successfully.
CI Review / receiver-test (pull_request) Successful in 3s
CI Review / helm-validate (pull_request) Successful in 6s
Lint & Validate / shellcheck (pull_request) Successful in 4s
Lint & Validate / yaml-lint (pull_request) Successful in 10s
Lint & Validate / terraform-validate (pull_request) Successful in 2m49s
51ee8bf44a
Forgejo/act substitutes `${{ secrets.X }}` TEXTUALLY into the generated step
script, so inlining `robot$forgejo-runner-push` produced a script line
`-u "robot$forgejo-runner-push"` and bash expanded `$forgejo`:
"forgejo: unbound variable" under `set -u` (run 1491, Push to Harbor step).
Without `set -u` it silently degrades to `robot-runner-push` -> 401.

Pass the two secrets through a step-level `env:` block and reference
`$HARBOR_PUSH_USERNAME` / `$HARBOR_PUSH_PASSWORD` in the shell instead. Env
values are handed to the step process verbatim (runner v12 host env:
`cmd.Env = getEnvListFromMap(env)`) and are never re-expanded by the shell, so
the secret value never enters the script text. Same structure as
`.forgejo/workflows/release.yaml` (which already passes an arbitrary PR body
through a step `env:` block).

Both workflows' Harbor login/push steps; nothing else changes.
HOMELAB-1735: docs(ci): tighten env-block comment (review concern)
Some checks failed
CI Review / receiver-test (pull_request) Successful in 3s
CI Review / helm-validate (pull_request) Successful in 5s
Lint & Validate / shellcheck (pull_request) Successful in 4s
Lint & Validate / yaml-lint (pull_request) Successful in 9s
Lint & Validate / terraform-validate (pull_request) Failing after 2m49s
f031bf81ef
The reviewed wording said env values are handed to the step 'verbatim'. They
are passed as process environment variables (never re-parsed by the shell),
but the runner does interpolate a value's ${{ }} expressions once first.
State the accurate, narrower claim. Comment-only; no behavioural change.
Author
Owner

Independent review — VERDICT: CLEAN (no blockers)

Fresh-context reviewer, separate process (pi -p --no-session, different model from the author), briefed only on the diff + the defect story + the binding constraints, working from an isolated clone at the PR head. It was not the implementer and was not handed the implementer's summary as its brief.

Mechanism claim verified against a primary source, not asserted

The reviewer read the runner source (code.forgejo.org/forgejo/runner/v12@v12.6.4) and confirmed the fix works for the code path these jobs actually use (:host mode → HostEnvironment.exec):

Finding Source
Only step.Run is interpolated into the generated script text act/runner/step_run.go setupShellCommand
Step-level env: is merged last (wins over job/workflow env) and its value interpolated once act/runner/step.go setupEnv / mergeEnv
Env reaches the child as a process environment, no shell parses it act/container/host_environment.go:330 cmd.Env = getEnvListFromMap(env)
Interpolate is single-pass (the result is never re-scanned) act/runner/expression.go:377
Secrets supplied via env: are still masked in logs act/runner/logger.go:119,185 valueMasker(… Config.Secrets) — no new leakage

Verification the reviewer performed

  • Exactly 2 files / 2 hunks each (+24/−4); git diff -w identical to plain diff; no mode changes, no whitespace errors, no unrelated reformatting. DOCKER_CONFIG="$(mktemp -d)", the absence of docker logout, the digest step and the docs deploy steps are byte-identical to the base.
  • YAML re-parse of both whole files + regex scan of every run: → zero secret expressions in any script; all remaining secrets.* occurrences are the 2 env: values per file. env: is step-level, sibling of run:, attached only to Push to Harbor; no workflow/job-level env: exists to shadow it.
  • Programmatic name-match: the $HARBOR_* refs in the scripts are exactly the declared env: keys — no typos. Repo-wide grep HARBOR_PUSH → only these two files.
  • Regression analysis, missing/renamed secret: act keeps the key with an empty value, so set -u does not fire — old and new both produce -u "". No new abort path; strictly an improvement.

Concerns (non-blocking)

  1. pi-image-build.yaml:131-132 still inlines secrets.HARBOR_PASSWORD/HARBOR_USERNAME — same latent defect class. Latent, not live: that credential is the Harbor admin ($-free), unlike robot$…. Out of scope for this PR (constraint: two files only) → follow-up.
  2. Comment precision (both files) — the original wording said env values are handed over "verbatim", which overstates: the runner does interpolate a value's ${{ }} expressions once. Addressed in f031bf8, which restates the accurate narrower claim (passed as process environment variables, so the shell never re-parses them). Comment-only delta.
  3. Nit (pre-existing, untouched): echo "$PW" | docker login — unchanged semantics.

Delta after review

f031bf8 is a comment-only delta over the reviewed 51ee8bf — asserted programmatically: every changed line in git diff 51ee8bf f031bf8 is a YAML comment (zero non-comment lines). Behaviour is identical, so the reviewed verdict carries.

No cluster mutation, no pod restart, no secret value printed by the review or the change.

## Independent review — VERDICT: CLEAN (no blockers) Fresh-context reviewer, separate process (`pi -p --no-session`, different model from the author), briefed only on the diff + the defect story + the binding constraints, working from an isolated clone at the PR head. It was not the implementer and was not handed the implementer's summary as its brief. ### Mechanism claim verified against a primary source, not asserted The reviewer read the runner source (`code.forgejo.org/forgejo/runner/v12@v12.6.4`) and confirmed the fix works for the code path these jobs actually use (`:host` mode → `HostEnvironment.exec`): | Finding | Source | |---|---| | Only `step.Run` is interpolated into the generated script text | `act/runner/step_run.go` `setupShellCommand` | | Step-level `env:` is merged last (wins over job/workflow env) and its value interpolated once | `act/runner/step.go` `setupEnv` / `mergeEnv` | | Env reaches the child as a **process environment**, no shell parses it | `act/container/host_environment.go:330` `cmd.Env = getEnvListFromMap(env)` | | `Interpolate` is single-pass (the result is never re-scanned) | `act/runner/expression.go:377` | | Secrets supplied via `env:` are **still masked** in logs | `act/runner/logger.go:119,185` `valueMasker(… Config.Secrets)` — no new leakage | ### Verification the reviewer performed - Exactly 2 files / 2 hunks each (+24/−4); `git diff -w` identical to plain diff; no mode changes, no whitespace errors, no unrelated reformatting. `DOCKER_CONFIG="$(mktemp -d)"`, the absence of `docker logout`, the digest step and the docs deploy steps are **byte-identical** to the base. - YAML re-parse of both **whole** files + regex scan of every `run:` → zero secret expressions in any script; all remaining `secrets.*` occurrences are the 2 `env:` values per file. `env:` is step-level, sibling of `run:`, attached only to `Push to Harbor`; no workflow/job-level `env:` exists to shadow it. - Programmatic name-match: the `$HARBOR_*` refs in the scripts are exactly the declared `env:` keys — no typos. Repo-wide `grep HARBOR_PUSH` → only these two files. - Regression analysis, missing/renamed secret: act keeps the key with an empty value, so `set -u` does **not** fire — old and new both produce `-u ""`. **No new abort path**; strictly an improvement. ### Concerns (non-blocking) 1. **`pi-image-build.yaml:131-132` still inlines `secrets.HARBOR_PASSWORD`/`HARBOR_USERNAME`** — same latent defect class. Latent, not live: that credential is the Harbor *admin* (`$`-free), unlike `robot$…`. Out of scope for this PR (constraint: two files only) → follow-up. 2. **Comment precision (both files)** — the original wording said env values are handed over "verbatim", which overstates: the runner does interpolate a value's `${{ }}` expressions once. **Addressed** in `f031bf8`, which restates the accurate narrower claim (*passed as process environment variables, so the shell never re-parses them*). Comment-only delta. 3. Nit (pre-existing, untouched): `echo "$PW" | docker login` — unchanged semantics. ### Delta after review `f031bf8` is a **comment-only** delta over the reviewed `51ee8bf` — asserted programmatically: every changed line in `git diff 51ee8bf f031bf8` is a YAML comment (zero non-comment lines). Behaviour is identical, so the reviewed verdict carries. No cluster mutation, no pod restart, no secret value printed by the review or the change.
HOMELAB-1735: ci: re-trigger CI (transient terraform provider download flake)
All checks were successful
CI Review / receiver-test (pull_request) Successful in 5s
CI Review / helm-validate (pull_request) Successful in 5s
Lint & Validate / shellcheck (pull_request) Successful in 5s
Lint & Validate / yaml-lint (pull_request) Successful in 10s
Lint & Validate / terraform-validate (pull_request) Successful in 2m42s
dc2c33f30c
terraform-validate failed after 2m49s in core/terraform/modules/talos-cluster:
`terraform init -backend=false` installed 6/7 providers and stopped at
`Installing bpg/proxmox v0.114.0...` with no error line (the job discards
init's stderr via 2>/dev/null). The identical job succeeded in 2m49s on
4646fd7 and the identical inputs pass locally minutes later, including
bpg/proxmox v0.114.0. Unrelated to this PR (which touches only two
.forgejo/workflows files). Empty commit; diff vs dd99f5c is unchanged.
aaron merged commit 75973717f0 into live 2026-09-29 08:34:53 +00:00
Author
Owner

Landed — merge 7597371, publish green, image live at a new tag

Merged: 75973717f0dddcb183c34a8b64bf279058fc34ba (merge commit, PR head dc2c33f).

CI on the PR head: 5/5 green (terraform-validate 2m42s, yaml-lint, shellcheck, helm-validate, receiver-test).

GitOps continuation: the merge self-triggered the publish, and it succeeded

.forgejo/workflows/runner-image-build.yaml lists itself in on.push.paths, so the push to live ran the workflow at the new merge sha. Run index 1499, job build-and-push, event push, job succeeded:

Successfully tagged harbor.aaron.reynoza.org/platform/forgejo-runner:7597371
WARNING! Your password will be stored unencrypted in /tmp/tmp.RpNZv1BMrR/config.json.
Login Succeeded
The push refers to repository [harbor.aaron.reynoza.org/platform/forgejo-runner]
... 16 layers pushed ...
7597371: digest: sha256:964448352603bd7038e9d60896dc6e583444bb613232c9a56ad5cb73f3401d17 size: 3690
=== PASTE THIS DIGEST INTO statefulset.yaml (Task 3) ===
harbor.aaron.reynoza.org/platform/forgejo-runner@sha256:964448352603bd7038e9d60896dc6e583444bb613232c9a56ad5cb73f3401d17
🏁  Job succeeded

Note Login Succeeded — the exact line whose absence was the evidence for defect A in run 1490. And the login wrote to /tmp/tmp.RpNZv1BMrR/config.json, i.e. the throwaway DOCKER_CONFIG, not the pod-shared /home/runner/.docker (the HOMELAB-1735 footgun half is preserved).

Registry verification (read-only crane, independent of the job log)

Check Result
crane digest …/platform/forgejo-runner:7597371 sha256:964448352603bd7038e9d60896dc6e583444bb613232c9a56ad5cb73f3401d17 — byte-identical to the digest the job printed (log ↔ registry cross-check)
tag present in crane ls yes (7597371), 9 tags total
manifest 16 layers, ≈337 MB, application/vnd.docker.distribution.manifest.v2+json
crane digest …:dd99f5c still NOT_FOUND (as expected — the workflow at dd99f5c is the broken one)
crane digest …:latest unchanged at sha256:c7239a38… (this workflow only publishes the short-sha tag)

One CI hiccup, diagnosed and cleared

First CI run on f031bf8 went red on terraform-validate — unrelated to this PR (proof: git diff dd99f5c f031bf8 -- core/ is empty and lint.yaml is untouched). Log evidence: in the last dir, terraform init -backend=false installed 6/7 providers and stopped at Installing bpg/proxmox v0.114.0... with no error line (the job discards init's stderr via 2>/dev/null). It was transient: the identical job succeeded in 2m49s on 4646fd7, all 8 dirs pass locally minutes later including bpg/proxmox v0.114.0, and the re-trigger passed. Cleared with an empty ci: re-trigger CI commit (dc2c33f), matching this repo's existing convention (d5f51db, 37ee5ee). Diff vs dd99f5c is unchanged: the same two files.

Scope as merged

.forgejo/workflows/runner-image-build.yaml + .forgejo/workflows/docs-build.yaml, 3 commits (2 substantive + 1 CI re-trigger). No other repo, no cluster mutation, no pod restart, no secret value printed.

docs-build.yaml was not triggered by this merge — its on.push.paths are docs/**, mkdocs.yml, Dockerfile.docs, Caddyfile and do not include the workflow file itself. It carries the identical env block and the same rendered-script proof, and will prove out on the next docs push.

Remaining (operator)

Cycle the 8 runner pods at a maintenance moment, then the ticket's mtime/.auths check on a restarted pod.

## Landed — merge `7597371`, publish green, image live at a new tag **Merged:** `75973717f0dddcb183c34a8b64bf279058fc34ba` (merge commit, PR head `dc2c33f`). **CI on the PR head:** 5/5 green (terraform-validate 2m42s, yaml-lint, shellcheck, helm-validate, receiver-test). ### GitOps continuation: the merge self-triggered the publish, and it succeeded `.forgejo/workflows/runner-image-build.yaml` lists itself in `on.push.paths`, so the push to `live` ran the workflow at the **new** merge sha. Run index 1499, job `build-and-push`, event `push`, **job succeeded**: ``` Successfully tagged harbor.aaron.reynoza.org/platform/forgejo-runner:7597371 WARNING! Your password will be stored unencrypted in /tmp/tmp.RpNZv1BMrR/config.json. Login Succeeded The push refers to repository [harbor.aaron.reynoza.org/platform/forgejo-runner] ... 16 layers pushed ... 7597371: digest: sha256:964448352603bd7038e9d60896dc6e583444bb613232c9a56ad5cb73f3401d17 size: 3690 === PASTE THIS DIGEST INTO statefulset.yaml (Task 3) === harbor.aaron.reynoza.org/platform/forgejo-runner@sha256:964448352603bd7038e9d60896dc6e583444bb613232c9a56ad5cb73f3401d17 🏁 Job succeeded ``` Note `Login Succeeded` — the exact line whose **absence** was the evidence for defect A in run 1490. And the login wrote to `/tmp/tmp.RpNZv1BMrR/config.json`, i.e. the throwaway `DOCKER_CONFIG`, **not** the pod-shared `/home/runner/.docker` (the HOMELAB-1735 footgun half is preserved). ### Registry verification (read-only `crane`, independent of the job log) | Check | Result | |---|---| | `crane digest …/platform/forgejo-runner:7597371` | `sha256:964448352603bd7038e9d60896dc6e583444bb613232c9a56ad5cb73f3401d17` — **byte-identical to the digest the job printed** (log ↔ registry cross-check) | | tag present in `crane ls` | yes (`7597371`), 9 tags total | | manifest | 16 layers, ≈337 MB, `application/vnd.docker.distribution.manifest.v2+json` | | `crane digest …:dd99f5c` | still `NOT_FOUND` (as expected — the workflow at `dd99f5c` is the broken one) | | `crane digest …:latest` | unchanged at `sha256:c7239a38…` (this workflow only publishes the short-sha tag) | ### One CI hiccup, diagnosed and cleared First CI run on `f031bf8` went red on **`terraform-validate`** — unrelated to this PR (proof: `git diff dd99f5c f031bf8 -- core/` is empty and `lint.yaml` is untouched). Log evidence: in the last dir, `terraform init -backend=false` installed 6/7 providers and stopped at `Installing bpg/proxmox v0.114.0...` with no error line (the job discards init's stderr via `2>/dev/null`). It was transient: the identical job **succeeded in 2m49s** on `4646fd7`, all 8 dirs pass locally minutes later *including* `bpg/proxmox v0.114.0`, and the re-trigger passed. Cleared with an empty `ci: re-trigger CI` commit (`dc2c33f`), matching this repo's existing convention (`d5f51db`, `37ee5ee`). Diff vs `dd99f5c` is unchanged: the same two files. ### Scope as merged `.forgejo/workflows/runner-image-build.yaml` + `.forgejo/workflows/docs-build.yaml`, 3 commits (2 substantive + 1 CI re-trigger). No other repo, no cluster mutation, no pod restart, no secret value printed. `docs-build.yaml` was **not** triggered by this merge — its `on.push.paths` are `docs/**`, `mkdocs.yml`, `Dockerfile.docs`, `Caddyfile` and do not include the workflow file itself. It carries the identical env block and the same rendered-script proof, and will prove out on the next docs push. ### Remaining (operator) Cycle the 8 runner pods at a maintenance moment, then the ticket's mtime/`.auths` check on a restarted pod.
Sign in to join this conversation.
No reviewers
No labels
operator-review
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Homelab/infra-core!522
No description provided.