Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c6ee4921d | ||
|
|
d550a501e2 | ||
|
|
365e43cdea | ||
|
|
dfc432c6ea |
No files matched your search
+138
-26
@@ -60,9 +60,21 @@
|
|||||||
# entry's "What I did NOT verify" — that gap is stated, not papered over.
|
# entry's "What I did NOT verify" — that gap is stated, not papered over.
|
||||||
#
|
#
|
||||||
# OPERATOR PREREQUISITE — ONE SECRET, NOT COMMITTED HERE:
|
# OPERATOR PREREQUISITE — ONE SECRET, NOT COMMITTED HERE:
|
||||||
# `RENOVATE_TOKEN` — a Gitea access token for the bot account, scopes `write:repository`,
|
# `RENOVATE_TOKEN` — a Gitea access token for the bot account (`renovate-bot`) with FIVE scopes.
|
||||||
# `read:user` and (since 2026-10-29) `read:package`. Set it as an Actions secret on this
|
# Each is required by a call Renovate 44.x makes against Gitea 1.24.3, and Gitea refuses a
|
||||||
# repository. Nothing in this repo mints,
|
# token missing one with `403 token does not have at least one of required scope(s)`:
|
||||||
|
# read:user GET /user (platform init: initPlatform)
|
||||||
|
# read:organization GET /orgs/{owner}, /orgs/{o}/labels (initRepo -> isOrg, then org labels)
|
||||||
|
# write:repository clone/push branches, /pulls, /statuses
|
||||||
|
# write:issue /issues, /labels, /issues/{n}/labels (dependency dashboard, PR labels;
|
||||||
|
# Gitea files all of these under the ISSUE scope, not repository)
|
||||||
|
# read:package the Gitea container registry (RENOVATE_HOST_RULES below)
|
||||||
|
# CORRECTED 2026-10-30. This list used to name only write:repository + read:user (+ read:package),
|
||||||
|
# and the operator minted exactly that. Run 350 then failed at
|
||||||
|
# `GET /api/v1/orgs/Home-Network 403 ... required=[read:organization], token scope=read:package,
|
||||||
|
# write:repository,read:user`; write:issue would have been the next wall. The preflight below now
|
||||||
|
# PROBES these scopes, so a short token fails in seconds with the missing scope named.
|
||||||
|
# Set it as an Actions secret on this repository. Nothing in this repo mints,
|
||||||
# stores or references its value; it is consumed by name only.
|
# stores or references its value; it is consumed by name only.
|
||||||
# Until it exists, this workflow FAILS LOUDLY on every scheduled run. That is intended: a
|
# Until it exists, this workflow FAILS LOUDLY on every scheduled run. That is intended: a
|
||||||
# visible red is the correct state for "automation configured but not credentialed".
|
# visible red is the correct state for "automation configured but not credentialed".
|
||||||
@@ -116,15 +128,37 @@ concurrency:
|
|||||||
jobs:
|
jobs:
|
||||||
renovate:
|
renovate:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
# RENOVATE RUNS AS THE JOB CONTAINER, NOT THROUGH renovatebot/github-action. FIXED 2026-10-30.
|
||||||
|
# That action does exactly one thing: `docker run ghcr.io/renovatebot/renovate:<version>`. On
|
||||||
|
# this runner a job has NO Docker daemon, by decision (decisions/2026-10-29-image-pipeline.md:
|
||||||
|
# act_runner sets container.docker_host to `-` when DOCKER_HOST is tcp://, and the DinD daemon
|
||||||
|
# is privileged, so a job that could reach it would be root on the node). So every run of the
|
||||||
|
# action died before Renovate started. Dispatch run 343 (2026-10-30), verbatim:
|
||||||
|
# [command]/usr/bin/docker run --env LOG_LEVEL ... --env RENOVATE_TOKEN=*** ...
|
||||||
|
# failed to connect to the docker API at unix:///var/run/docker.sock; ... no such file or directory
|
||||||
|
# ::error::Error: The process '/usr/bin/docker' failed with exit code 1
|
||||||
|
# No auth, scope, config or network question was ever reached. Making the Renovate image the
|
||||||
|
# job container needs no daemon: act starts it through the runner's own DinD exactly as it
|
||||||
|
# starts the default ubuntu image, then `exec`s each step inside it.
|
||||||
|
#
|
||||||
|
# PINNED here (was the action's `renovate-version: 44.167.0`). renovate.json5 is validated
|
||||||
|
# against this exact tag — keep them in step. Renovate's github-actions manager reads
|
||||||
|
# `.gitea/workflows` and extracts `container.image`, so this pin is still self-maintained.
|
||||||
|
#
|
||||||
|
# The image runs as uid 12021 (`ubuntu`), and that is fine under act: the step command files
|
||||||
|
# ($GITHUB_STEP_SUMMARY etc.) are created mode 0666 (gitea/act v0.261.2 pkg/runner/step.go),
|
||||||
|
# HOME comes from the image env, and no step writes the workspace. There is deliberately NO
|
||||||
|
# actions/checkout: Renovate clones the repo itself from RENOVATE_ENDPOINT, and as uid 12021
|
||||||
|
# checkout could not `git init` the root-owned workspace volume anyway.
|
||||||
|
container:
|
||||||
|
image: ghcr.io/renovatebot/renovate:44.167.0
|
||||||
# Renovate scans every manager across the whole repo; 40m is generous for this repo's size and
|
# Renovate scans every manager across the whole repo; 40m is generous for this repo's size and
|
||||||
# well under a runner's patience. Without a timeout a wedged run holds the `renovate`
|
# well under a runner's patience. Without a timeout a wedged run holds the `renovate`
|
||||||
# concurrency group and silently blocks every subsequent scheduled run.
|
# concurrency group and silently blocks every subsequent scheduled run.
|
||||||
timeout-minutes: 40
|
timeout-minutes: 40
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Preflight - the token must exist, and its absence must be LOUD
|
- name: Preflight - the token must exist, and its absence must be LOUD
|
||||||
|
shell: bash
|
||||||
env:
|
env:
|
||||||
RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }}
|
RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
@@ -139,33 +173,94 @@ jobs:
|
|||||||
That is the failure mode that let renovate.json sit inert in this repo for months while
|
That is the failure mode that let renovate.json sit inert in this repo for months while
|
||||||
seven files claimed "Renovate maintains this pin".
|
seven files claimed "Renovate maintains this pin".
|
||||||
|
|
||||||
TO FIX: create a Gitea access token for the bot account with scopes
|
TO FIX: create a Gitea access token for the bot account (renovate-bot) with scopes
|
||||||
`write:repository`, `read:user` and `read:package` (the last one lets Renovate read
|
read:user, read:organization, write:repository, write:issue and read:package (the
|
||||||
this repo's own images in the Gitea registry), then add it to this repository as the
|
header of .gitea/workflows/renovate.yaml says which call needs which), then add it to
|
||||||
Actions secret RENOVATE_TOKEN. Do not commit it anywhere.
|
this repository as the Actions secret RENOVATE_TOKEN. Do not commit it anywhere.
|
||||||
MSG
|
MSG
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo "RENOVATE_TOKEN is present (value not printed)."
|
echo "RENOVATE_TOKEN is present (value not printed)."
|
||||||
|
# Proves the step is running INSIDE the pinned Renovate image, not the default job image.
|
||||||
|
echo "renovate $(renovate --version)"
|
||||||
|
|
||||||
|
- name: Preflight - the token has every scope Renovate needs
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }}
|
||||||
|
# ADDED 2026-10-30. Renovate reports EVERY platform-init failure as the bare
|
||||||
|
# "Authentication failure" (lib/modules/platform/gitea/index.ts initPlatform swallows the
|
||||||
|
# HTTP error), and a later scope gap surfaces as "Repository has unknown error". Two
|
||||||
|
# dispatches were spent learning one missing scope at a time. These probes are GETs only,
|
||||||
|
# and they name the missing scope. The token never reaches argv: curl reads it from stdin
|
||||||
|
# (`--config -`), so it is not in `ps` output or in the log.
|
||||||
|
run: |
|
||||||
|
set -uo pipefail
|
||||||
|
GITEA=https://gitea.home.keithjackson.dev
|
||||||
|
REPO=Home-Network/home-network-k3s-impl
|
||||||
|
fail=0
|
||||||
|
body=$(mktemp)
|
||||||
|
probe() { # <scope it proves> <path> <auth: bearer|basic>
|
||||||
|
local code
|
||||||
|
if [ "$3" = basic ]; then
|
||||||
|
code=$(printf 'user = "renovate:%s"\n' "$RENOVATE_TOKEN" \
|
||||||
|
| curl -sS --config - -o "$body" -w '%{http_code}' "$GITEA$2") || code=000
|
||||||
|
else
|
||||||
|
code=$(printf 'header = "Authorization: Bearer %s"\n' "$RENOVATE_TOKEN" \
|
||||||
|
| curl -sS --config - -o "$body" -w '%{http_code}' "$GITEA$2") || code=000
|
||||||
|
fi
|
||||||
|
if [ "$code" = 200 ]; then
|
||||||
|
echo "ok $1 (GET ${2%%\?*} -> 200)"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
fail=1
|
||||||
|
echo "::error::scope $1: GET ${2%%\?*} -> $code"
|
||||||
|
case "$code" in
|
||||||
|
401) echo " 401 = Gitea rejected the token VALUE (wrong, expired or revoked)." ;;
|
||||||
|
403) if grep -q 'Access denied' "$body"; then
|
||||||
|
echo " 403 'Access denied' = a Cilium/Envoy policy denial, not Gitea."
|
||||||
|
else
|
||||||
|
echo " Gitea: $(jq -r '.message // empty' "$body" 2>/dev/null | head -c 300)"
|
||||||
|
fi ;;
|
||||||
|
*) echo " body: $(head -c 200 "$body")" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
probe read:user "/api/v1/user" bearer
|
||||||
|
probe read:organization "/api/v1/orgs/${REPO%%/*}" bearer
|
||||||
|
probe read:repository "/api/v1/repos/$REPO" bearer
|
||||||
|
probe read:issue "/api/v1/repos/$REPO/labels?limit=1" bearer
|
||||||
|
probe read:package "/v2/token?service=container_registry&scope=repository:home-network/toolbox:pull" basic
|
||||||
|
# WRITE scopes cannot be proven by a GET. Gitea's scope-refusal message, however, lists
|
||||||
|
# the token's scopes (names only, never the value): ask for an admin route this bot
|
||||||
|
# cannot have, and read `token scope=` from the 403. Advisory if the format changes.
|
||||||
|
printf 'header = "Authorization: Bearer %s"\n' "$RENOVATE_TOKEN" \
|
||||||
|
| curl -sS --config - -o "$body" "$GITEA/api/v1/admin/orgs?limit=1" || true
|
||||||
|
scopes=$(jq -r '.message // empty' "$body" 2>/dev/null | sed -n 's/.*token scope=//p')
|
||||||
|
if [ -n "$scopes" ]; then
|
||||||
|
echo "token scopes (from Gitea): $scopes"
|
||||||
|
for want in write:repository write:issue; do
|
||||||
|
case ",$scopes," in
|
||||||
|
*",$want,"*|*",all,"*) echo "ok $want" ;;
|
||||||
|
*) echo "::error::scope $want is missing (token scope=$scopes)"; fail=1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
else
|
||||||
|
echo "::warning::could not read the token's scope list; write:repository and write:issue are UNVERIFIED"
|
||||||
|
fi
|
||||||
|
rm -f "$body"
|
||||||
|
if [ "$fail" -ne 0 ]; then
|
||||||
|
echo "::error::RENOVATE_TOKEN failed the checks above (a 401 is the value, a 403 is a scope). Re-mint it for renovate-bot with read:user, read:organization, write:repository, write:issue, read:package and overwrite the secret."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Run Renovate
|
- name: Run Renovate
|
||||||
uses: renovatebot/[email protected]
|
shell: bash
|
||||||
with:
|
# The repo config is discovered by Renovate from the cloned repo (renovate.json5,
|
||||||
# Pinned, and Renovate maintains this pin itself: renovate.json's enabledManagers
|
# RENOVATE_REQUIRE_CONFIG=required). The action used to ALSO load it as the global config
|
||||||
# includes `github-actions`, and `helpers:pinGitHubActionDigests` is in its `extends`.
|
# via RENOVATE_CONFIG_FILE; that double-loading is gone. ci/lint.sh's `configurationFile:`
|
||||||
# This is the one dependency in the repo whose updater is its own updater.
|
# check therefore has nothing to resolve in this file, and passes.
|
||||||
# MUST match the file's real name. #79 renamed it to renovate.json5 and left this line
|
|
||||||
# saying `renovate.json`; every scheduled run from 2026-10-18 then died in ~1s with
|
|
||||||
# ENOENT before Renovate started, and nothing in the gate noticed. ci/lint.sh now
|
|
||||||
# asserts that every `configurationFile:` here names a file that exists.
|
|
||||||
configurationFile: renovate.json5
|
|
||||||
token: ${{ secrets.RENOVATE_TOKEN }}
|
|
||||||
# PINNED 2026-10-29. Unset, this action version runs Renovate `39` (action.yml
|
|
||||||
# `renovate-version` default at v41.0.9), i.e. 39.264.0, and renovate.json5 uses
|
|
||||||
# `managerFilePatterns` (>= 40.2.0): under 39.x validation fails with five errors and
|
|
||||||
# the run stops at CONFIG_VALIDATION. #79 validated against `latest`, not this.
|
|
||||||
renovate-version: 44.167.0
|
|
||||||
env:
|
env:
|
||||||
|
RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }}
|
||||||
# Self-hosted Gitea. `platform` + `endpoint` were the two values renovate.json's own
|
# Self-hosted Gitea. `platform` + `endpoint` were the two values renovate.json's own
|
||||||
# header said were "set at runtime by the operator's renovate runner" — this is that
|
# header said were "set at runtime by the operator's renovate runner" — this is that
|
||||||
# runner, so they are set here rather than in the repo config, which keeps renovate.json
|
# runner, so they are set here rather than in the repo config, which keeps renovate.json
|
||||||
@@ -189,6 +284,23 @@ jobs:
|
|||||||
# token; it therefore needs scope `read:package` in addition to the ones listed in the
|
# token; it therefore needs scope `read:package` in addition to the ones listed in the
|
||||||
# preflight above. Gitea ignores the username for token auth (services/auth/basic.go).
|
# preflight above. Gitea ignores the username for token auth (services/auth/basic.go).
|
||||||
RENOVATE_HOST_RULES: '[{"hostType":"docker","matchHost":"gitea.home.keithjackson.dev","username":"renovate","password":"${{ secrets.RENOVATE_TOKEN }}"}]'
|
RENOVATE_HOST_RULES: '[{"hostType":"docker","matchHost":"gitea.home.keithjackson.dev","username":"renovate","password":"${{ secrets.RENOVATE_TOKEN }}"}]'
|
||||||
|
# OPTIONAL, ADDED 2026-10-30. A github.com token (a fine-grained PAT with NO permissions,
|
||||||
|
# public-repo read only) for lookups of GitHub-hosted dependencies. Without it Renovate
|
||||||
|
# SKIPS them with `github-token-required` — measured in a local dry run: 17 deps, i.e.
|
||||||
|
# every `uses: actions/*` here plus kubevirt/kubevirt, kubevirt/containerized-data-importer
|
||||||
|
# and intel/intel-device-plugins-for-kubernetes. Unset is safe: the step below drops the
|
||||||
|
# empty variable, and those deps simply stay unmaintained, with a WARN in the log.
|
||||||
|
GITHUB_COM_TOKEN: ${{ secrets.RENOVATE_GITHUB_COM_TOKEN }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
# An expression that evaluates to '' still EXPORTS the variable, as an empty string.
|
||||||
|
# Drop those so an unset value means "Renovate's default", never "set to empty".
|
||||||
|
for v in RENOVATE_DRY_RUN GITHUB_COM_TOKEN; do
|
||||||
|
[ -n "${!v:-}" ] || unset "$v"
|
||||||
|
done
|
||||||
|
# Through the image's entrypoint, as `docker run` would have gone: it sources the
|
||||||
|
# containerbase env and initialises the bundled tools before exec'ing Renovate.
|
||||||
|
exec renovate-entrypoint.sh renovate
|
||||||
|
|
||||||
- name: Summary
|
- name: Summary
|
||||||
if: always()
|
if: always()
|
||||||
|
|||||||
+16
@@ -42,3 +42,19 @@ while IFS=: read -r wf _ value; do
|
|||||||
done < <(grep -nE '^[[:space:]]*configurationFile:' .gitea/workflows/*.y*ml 2>/dev/null || true)
|
done < <(grep -nE '^[[:space:]]*configurationFile:' .gitea/workflows/*.y*ml 2>/dev/null || true)
|
||||||
[ "$missing" -eq 0 ] || exit 1
|
[ "$missing" -eq 0 ] || exit 1
|
||||||
ok "workflow configurationFile references resolve"
|
ok "workflow configurationFile references resolve"
|
||||||
|
|
||||||
|
# A workflow must not use an action that needs a Docker daemon: jobs on this runner get NONE, by
|
||||||
|
# decision (decisions/2026-10-29-image-pipeline.md; act_runner maps docker_host to `-`). Such an
|
||||||
|
# action is valid YAML and passes every other stage, then dies at runtime with
|
||||||
|
# "failed to connect to the docker API at unix:///var/run/docker.sock". renovatebot/github-action
|
||||||
|
# did exactly that on every Renovate run until 2026-10-30 (run 343). Run the tool's image as the
|
||||||
|
# job `container:` instead (see .gitea/workflows/renovate.yaml). Container (`docker://`) actions
|
||||||
|
# and Docker's own daemon-bound actions are rejected for the same reason. Comments are ignored.
|
||||||
|
daemon_uses='uses:[[:space:]]*["'\'']?(docker://|renovatebot/github-action|docker/(build-push|setup-buildx|setup-qemu|login)-action)'
|
||||||
|
bad=0
|
||||||
|
while IFS= read -r hit; do
|
||||||
|
err "${hit%%:*}: needs a Docker daemon, which jobs here do not get: $(echo "${hit#*:*:}" | sed -E 's/^[[:space:]]+//')"
|
||||||
|
bad=1
|
||||||
|
done < <(grep -HnE "^[^#]*${daemon_uses}" .gitea/workflows/*.y*ml 2>/dev/null || true)
|
||||||
|
[ "$bad" -eq 0 ] || exit 1
|
||||||
|
ok "no workflow uses a Docker-daemon-bound action"
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
---
|
||||||
|
date: 2026-10-30
|
||||||
|
branch: fix/renovate-auth
|
||||||
|
title: Renovate's token needs five scopes, not three; the workflow documented the wrong list, and now probes it
|
||||||
|
---
|
||||||
|
|
||||||
|
## What changed
|
||||||
|
|
||||||
|
**The fix is operator-side.** `RENOVATE_TOKEN` lacks `read:organization`, and it will next lack
|
||||||
|
`write:issue`. The operator must re-mint it with five scopes:
|
||||||
|
|
||||||
|
- `read:user`
|
||||||
|
- `read:organization`
|
||||||
|
- `write:repository`
|
||||||
|
- `write:issue`
|
||||||
|
- `read:package`
|
||||||
|
|
||||||
|
The git half fixes the two things that sent the operator round this loop twice.
|
||||||
|
|
||||||
|
**`.gitea/workflows/renovate.yaml`**:
|
||||||
|
- **The header's scope list is corrected**, with the call each scope serves. The old list named
|
||||||
|
only `write:repository`, `read:user` and `read:package`, and the operator minted exactly that.
|
||||||
|
The preflight's "TO FIX" text is corrected the same way.
|
||||||
|
- **A new step, "Preflight - the token has every scope Renovate needs".**
|
||||||
|
- It probes read-only GETs: `/user` for read:user, `/orgs/Home-Network` for read:organization,
|
||||||
|
`/repos/{repo}` for read:repository, `/repos/{repo}/labels` for read:issue, and registry
|
||||||
|
`/v2/token` for read:package.
|
||||||
|
- It reads the token's own scope list out of Gitea's 403 from `/admin/orgs` (names only), and
|
||||||
|
requires `write:repository` and `write:issue` in it.
|
||||||
|
- It says what a failure means: 401 is the value; a Gitea 403 is a scope, and the probe prints
|
||||||
|
Gitea's message; an "Access denied" 403 is a Cilium/Envoy denial.
|
||||||
|
- The token is fed to curl on stdin (`--config -`), never in argv.
|
||||||
|
|
||||||
|
**`ci/lint.sh`**: a new guard rejects any non-comment `uses:` in `.gitea/workflows` that needs a
|
||||||
|
Docker daemon: `docker://`, `renovatebot/github-action` and Docker's daemon-bound
|
||||||
|
`docker/*-action`s. That is the class of bug fixed in #84.
|
||||||
|
|
||||||
|
### Root cause, with evidence
|
||||||
|
|
||||||
|
Run 350 (task 567, 06:49 UTC) **got further than 348 and 349.** Gitea's router log, filtered to
|
||||||
|
the act-runner pod `10.0.0.38`:
|
||||||
|
|
||||||
|
06:49:31 GET /api/v1/user 200 OK
|
||||||
|
06:49:31 GET /api/v1/version 200 OK
|
||||||
|
06:49:31 GET /api/v1/repos/Home-Network/home-network-k3s-impl 200 OK
|
||||||
|
06:49:31 GET /api/v1/orgs/Home-Network 403 Forbidden
|
||||||
|
|
||||||
|
There were no `/v2/` requests: Renovate stopped before extraction. From the job log, verbatim:
|
||||||
|
|
||||||
|
ERROR: Repository has unknown error (repository=Home-Network/home-network-k3s-impl)
|
||||||
|
"url": "https://gitea.home.keithjackson.dev/api/v1/orgs/Home-Network",
|
||||||
|
"message": "token does not have at least one of required scope(s), required=[read:organization], token scope=read:package,write:repository,read:user"
|
||||||
|
"x-envoy-upstream-service-time": "27", "server": "envoy"
|
||||||
|
INFO: Repository finished ... "result": "unknown-error", "exitCode": 8
|
||||||
|
|
||||||
|
**This is not Cilium.** The body is Gitea's JSON scope message, Gitea logged the request, and
|
||||||
|
`x-envoy-upstream-service-time` shows that Envoy forwarded it upstream.
|
||||||
|
|
||||||
|
**The secret WAS updated.** Runs 348 and 349 got `GET /user 403` from `tokenRequiresScopes`. Run 350
|
||||||
|
got `/user 200`, and its token scope list now includes `read:user`.
|
||||||
|
|
||||||
|
**Why `read:organization` is needed.** Renovate 44.167.0, `lib/modules/platform/gitea/index.ts`
|
||||||
|
`initRepo`, calls `helper.isOrg` → `GET /orgs/{owner}` and rethrows on error. Gitea 1.24.3,
|
||||||
|
`routers/api/v1/api.go`, puts `/orgs/{org}` behind
|
||||||
|
`tokenRequiresScopes(AccessTokenScopeCategoryOrganization)`.
|
||||||
|
|
||||||
|
**Why `write:issue` is needed next.** In the same `api.go`, `/repos/{o}/{r}/issues…`, `/labels`
|
||||||
|
and `/milestones` sit in a group with `tokenRequiresScopes(AccessTokenScopeCategoryIssue)`
|
||||||
|
(lines 1466–1582). Renovate's gitea-helper uses `/issues`, `/issues/{n}/labels`, `/labels` and
|
||||||
|
`/issues/{n}/comments`, for the dependency dashboard (`:dependencyDashboard` is extended) and for
|
||||||
|
PR labels. A dry run only reads them; a scheduled run writes them.
|
||||||
|
|
||||||
|
The other causes the coordinator listed are ruled out:
|
||||||
|
- **Schedule window and dry-run.** The run never reached branch processing.
|
||||||
|
- **Repository config.** The run never reached the clone (`"cloned": undefined`).
|
||||||
|
- **The Summary step.** It is not involved; the failure is Renovate's own exit 8.
|
||||||
|
|
||||||
|
## What I verified
|
||||||
|
|
||||||
|
- I read the whole run 350 job log (204 lines), and the router lines above. Runs 348 and 349 are
|
||||||
|
as recorded in `decisions/2026-10-30-renovate-first-run.md`'s follow-up: `/user` got 403 from
|
||||||
|
`tokenRequiresScopes`.
|
||||||
|
- **The probe step, in the pinned image.** I extracted the step body from the YAML and ran it in
|
||||||
|
`ghcr.io/renovatebot/renovate:44.167.0` (`curl` 8.5.0 and `jq` are present) with a deliberately
|
||||||
|
**bogus** token. Every probe reported 401 with the "token VALUE" diagnosis, the scope-list read
|
||||||
|
fell back to a warning, and the step exited 1.
|
||||||
|
- **The scope-list parser.** Against run 350's exact message format it reports `write:issue`
|
||||||
|
MISSING. Against a five-scope list it reports both OK. Against `all` it reports both OK.
|
||||||
|
- **The lint guard.** It flags `renovatebot/github-action` in the pre-#84 `renovate.yaml`
|
||||||
|
(dfc432c, line 152). It reports nothing on the current workflows, and it ignores commented
|
||||||
|
lines. `bash -n ci/lint.sh` passes.
|
||||||
|
- **Account facts, read-only.** `renovate-bot` is a collaborator with `write`, and the org is
|
||||||
|
public.
|
||||||
|
|
||||||
|
## What I did NOT verify
|
||||||
|
|
||||||
|
- **The probe with a real token. UNVERIFIED.** I never had the token. In particular, I have not
|
||||||
|
observed that `GET /api/v1/admin/orgs` gives renovate-bot's token a 403 with a `token scope=`
|
||||||
|
list. The source says the scope check runs before `reqSiteAdmin`. If the message format
|
||||||
|
differs, the step warns and does not fail.
|
||||||
|
- **That five scopes are enough for a full non-dry run.** They cover every helper endpoint Renovate
|
||||||
|
44.167.0 calls, mapped to Gitea 1.24.3's route categories by reading source. Branch pushes go
|
||||||
|
over git-over-HTTPS with the token; I believe that needs write:repository, but I have not
|
||||||
|
observed it.
|
||||||
|
- **Registry reads as renovate-bot.** `read:package` on a public org is expected to work. The new
|
||||||
|
probe is the first real test of it.
|
||||||
|
- **Sensitive paths.** `.gitea/**` and `ci/**` are both sensitive. CI's `approval-policy` will block
|
||||||
|
until the operator posts `/approve-override` as a PR review.
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
---
|
||||||
|
date: 2026-10-30
|
||||||
|
branch: fix/renovate-first-run
|
||||||
|
title: Renovate never started because its action needs a Docker daemon jobs here do not get; it now runs as the job container
|
||||||
|
---
|
||||||
|
|
||||||
|
## What changed
|
||||||
|
|
||||||
|
**`.gitea/workflows/renovate.yaml`**: the job no longer uses `renovatebot/github-action`. The
|
||||||
|
pinned Renovate image (`ghcr.io/renovatebot/renovate:44.167.0`, the same version the action was
|
||||||
|
pinned to) is now the job's `container:`. A `run:` step calls `renovate-entrypoint.sh renovate`
|
||||||
|
inside it. `actions/checkout` is gone, because Renovate clones the repo itself.
|
||||||
|
`GITHUB_COM_TOKEN` is wired from a new **optional** secret, `RENOVATE_GITHUB_COM_TOKEN`. The step
|
||||||
|
unsets it, and `RENOVATE_DRY_RUN`, when they evaluate to the empty string. The preflight also
|
||||||
|
prints `renovate --version`, which proves the step ran inside the pinned image.
|
||||||
|
|
||||||
|
**`renovate.json5`**: one comment now names `container.image` as the version pin, in place of
|
||||||
|
`renovate-version`. The config itself is unchanged.
|
||||||
|
|
||||||
|
### Why: the root cause
|
||||||
|
|
||||||
|
Every run of the action died before Renovate started. That includes the operator's dispatch,
|
||||||
|
run 343 (task 555, 2026-10-30 06:14 UTC), and the 11 scheduled runs before it (312–322). From
|
||||||
|
run 343's log:
|
||||||
|
|
||||||
|
RENOVATE_TOKEN is present (value not printed).
|
||||||
|
[command]/usr/bin/docker run --env LOG_LEVEL ... --env RENOVATE_TOKEN=*** --env RENOVATE_CONFIG_FILE=/github-action/renovate.json5 ...
|
||||||
|
failed to connect to the docker API at unix:///var/run/docker.sock; check if the path is correct and if the daemon is running: dial unix /var/run/docker.sock: connect: no such file or directory
|
||||||
|
Error: The process '/usr/bin/docker' failed with exit code 1
|
||||||
|
|
||||||
|
The action is only a wrapper around `docker run`. On this runner a job deliberately gets no
|
||||||
|
Docker daemon (`decisions/2026-10-29-image-pipeline.md`): act_runner maps `docker_host` to `-` when
|
||||||
|
`DOCKER_HOST` is `tcp://`, and the DinD daemon is privileged. So this was a
|
||||||
|
platform-compatibility failure. The run never reached authentication, token scope, config or the
|
||||||
|
network. **The re-minted token is not implicated, and nothing in this log can say whether its
|
||||||
|
scopes are right.**
|
||||||
|
|
||||||
|
A job `container:` needs no daemon. act creates it through the runner's DinD the same way it
|
||||||
|
creates the default `ubuntu-latest` image, then `exec`s each step inside it. I checked this
|
||||||
|
against gitea/act v0.261.2:
|
||||||
|
|
||||||
|
- The job container's entrypoint is `/bin/sleep` (`run_context.go`). The Renovate image has
|
||||||
|
`/usr/bin/sleep`.
|
||||||
|
- The step command files are written `0o666` (`step.go`), so the image's uid 12021 can write
|
||||||
|
`$GITHUB_STEP_SUMMARY`.
|
||||||
|
- `HOME` comes from the image env.
|
||||||
|
|
||||||
|
## What I verified
|
||||||
|
|
||||||
|
- **The root cause.** I read all of run 343's job log (162 lines, job 0; job 1 is 404). It has one
|
||||||
|
error, quoted above, and no Renovate output at all. Runs 312–322 are all `failure` from the
|
||||||
|
`schedule` event.
|
||||||
|
- **The config validates against the pinned tag.** `podman run ... ghcr.io/renovatebot/renovate:44.167.0
|
||||||
|
renovate-config-validator /tmp/renovate.json5` printed `Config validated successfully`. It also
|
||||||
|
printed the known migration diff for the top-level `docker` block.
|
||||||
|
- **A dry run with the fix extracts and looks up dependencies.** This was a local reproduction. I
|
||||||
|
made a clean snapshot of this branch's tracked files. I ran it in a podman container of the pinned
|
||||||
|
image with entrypoint `/bin/sleep`, as the image's own uid 12021 with `HOME=/home/ubuntu`.
|
||||||
|
Inside it I `exec`ed the workflow step's `run:` body, extracted verbatim from the YAML, with:
|
||||||
|
- `RENOVATE_PLATFORM=local`, `RENOVATE_DRY_RUN=full` and `GITHUB_COM_TOKEN=` (empty, to
|
||||||
|
exercise the unset);
|
||||||
|
- a docker host rule for `gitea.home.keithjackson.dev` using the `gitea-admin` credential from
|
||||||
|
`~/.git-credentials`, passed through env and never printed.
|
||||||
|
|
||||||
|
The run exited 0 and logged `Dependency extraction complete`:
|
||||||
|
- flux 15 deps / 13 files
|
||||||
|
- github-actions 24 / 4
|
||||||
|
- kubernetes 131 / 67
|
||||||
|
- kustomize 2 / 2
|
||||||
|
- regex (both custom managers) 7 / 6
|
||||||
|
- **total 179 deps / 92 files**
|
||||||
|
|
||||||
|
It found **42 updates**: 29 kubernetes, 6 flux, 2 kustomize, 1 github-actions and 4 regex
|
||||||
|
`digest`. 7 of them are `major`.
|
||||||
|
- **The `# renovate-digest:` lookups against the Gitea registry succeed.** All three
|
||||||
|
`/v2/token?scope=repository:home-network/<img>:pull` requests succeeded, and so did
|
||||||
|
`HEAD /v2/home-network/<img>/manifests/main`. They proposed `digest` updates for
|
||||||
|
`conformance/versions.env`, `infra/agent/versions.env`, `platform/pm-agent/configmap.yaml` and
|
||||||
|
`platform/pm-agent/deployment.yaml`, all of which carry the zero placeholder digest today. The
|
||||||
|
`kubernetes` manager's copies of those images report `disabled`, which is the intended effect of
|
||||||
|
the packageRule.
|
||||||
|
- **The new pin is still maintained by Renovate.** The github-actions manager extracts
|
||||||
|
`ghcr.io/renovatebot/renovate:44.167.0` from `renovate.yaml` as depType `container`, and proposes
|
||||||
|
44.198.0.
|
||||||
|
- **The `regex` vs `custom.regex` question from #73/#77 is closed.** Both custom managers produced
|
||||||
|
dependencies.
|
||||||
|
- **Missing GitHub token.** The local run logged `GitHub token is required for some dependencies`.
|
||||||
|
That accounts for 17 deps skipped with `github-token-required`, which is why
|
||||||
|
`RENOVATE_GITHUB_COM_TOKEN` is offered as an optional secret.
|
||||||
|
|
||||||
|
## What I did NOT verify
|
||||||
|
|
||||||
|
- **The workflow on the real runner. UNVERIFIED.** My reproduction ran with podman, not
|
||||||
|
act_runner + DinD, and used `--platform=local`. The act behaviour (sleep entrypoint, `exec` as the
|
||||||
|
image user, 0666 command files) comes from reading the gitea/act v0.261.2 source, not from a run.
|
||||||
|
The first real evidence will be a `workflow_dispatch` after merge.
|
||||||
|
- **The Gitea platform path and `RENOVATE_TOKEN`'s scopes. UNVERIFIED.** I have no access token, only
|
||||||
|
the `gitea-admin` password, which Renovate's Gitea platform cannot use. So these were not
|
||||||
|
exercised: platform init (`GET /user`, which needs `read:user`), repo clone, and the registry
|
||||||
|
lookups using the **token** with `read:package` rather than a password.
|
||||||
|
- **Behaviour inside the schedule window.** A dispatch outside 03:00–06:00 America/New_York
|
||||||
|
(07:00–10:00 UTC during EDT) will extract and look up, but will log "not within schedule"
|
||||||
|
instead of `DRY-RUN: Would create ...` lines. I did not change this.
|
||||||
|
- **Renovate as uid 12021 doing artifact updates** (for example helm lockfiles) on a real,
|
||||||
|
non-dry run. `--dry-run=full` does not run the tools.
|
||||||
|
- **The self-bump.** Renovate will propose `ghcr.io/renovatebot/renovate` 44.167.0 → 44.198.0 as a
|
||||||
|
minor, which the default rule auto-merges. `.gitea/**` is a sensitive path, so it will in fact
|
||||||
|
wait for an administrator override. Nothing re-runs `renovate-config-validator` on that bump,
|
||||||
|
and no gate check runs the validator.
|
||||||
|
- **No alert for "Renovate red every day".** That gap is still open. This failure ran for 12 days
|
||||||
|
without anyone noticing.
|
||||||
|
- **Sensitive path.** `.gitea/**` is in `infra/gitea/approval-policy.json`, so CI's
|
||||||
|
`approval-policy` stage will block until the operator posts `/approve-override` as a PR review.
|
||||||
+4
-3
@@ -29,10 +29,11 @@
|
|||||||
// `managerFilePatterns` key ("Custom Manager contains disallowed fields", "Invalid configuration
|
// `managerFilePatterns` key ("Custom Manager contains disallowed fields", "Invalid configuration
|
||||||
// option: flux.managerFilePatterns", ...), because 39.x only knows `fileMatch`; the rename landed
|
// option: flux.managerFilePatterns", ...), because 39.x only knows `fileMatch`; the rename landed
|
||||||
// in 40.2.0. So even after the workflow found this file, every run would have stopped at
|
// in 40.2.0. So even after the workflow found this file, every run would have stopped at
|
||||||
// CONFIG_VALIDATION. renovate.yaml now pins `renovate-version` to the exact release this file is
|
// CONFIG_VALIDATION. renovate.yaml now pins the exact release this file is validated against —
|
||||||
// validated against. VALIDATE WITH THAT SAME TAG:
|
// since 2026-10-30 as the job's `container.image` tag (the action is gone: it needed a Docker
|
||||||
|
// daemon jobs here do not get). VALIDATE WITH THAT SAME TAG:
|
||||||
// podman run --rm -v "$PWD/renovate.json5:/tmp/renovate.json5:ro,Z" \
|
// podman run --rm -v "$PWD/renovate.json5:/tmp/renovate.json5:ro,Z" \
|
||||||
// ghcr.io/renovatebot/renovate:<renovate-version from renovate.yaml> \
|
// ghcr.io/renovatebot/renovate:<container.image tag in renovate.yaml> \
|
||||||
// renovate-config-validator /tmp/renovate.json5
|
// renovate-config-validator /tmp/renovate.json5
|
||||||
// (Not `--strict`: that also fails on a pending migration of the pre-existing top-level
|
// (Not `--strict`: that also fails on a pending migration of the pre-existing top-level
|
||||||
// `"docker": {"enabled": true}` block at the bottom, which is a warning, not an error.)
|
// `"docker": {"enabled": true}` block at the bottom, which is a warning, not an error.)
|
||||||
|
|||||||
Reference in new issue
Block a user