Files
homelab/renovate
forustandClaude Opus 4.8 30995ee009 ci: pin the last four linters instead of trusting the runner
prettier, ruff, yamllint and hadolint were the only CI tools still called bare,
straight off whatever the runner happened to have installed. Pin them in
tool-versions.env like the other three and install them the same way, so the
versions Renovate moves are the versions CI runs.

Each pinned version equals what is already on the runner, so this changes what
CI does not at all today. It changes what CI does on a rebuilt runner: the
pinned one gets installed over the drift.

The four need four different mechanisms, which is why this is not one pattern:

  hadolint  a bare binary per platform, like actionlint
  ruff,
  yamllint  PyPI wheels, unpacked by uv
  prettier  an npm tarball, unpacked by tar

prettier is the awkward one. Its entry point requires ../package.json relative
to its own real path, so copying the single file out -- which is what every
other installer here does -- yields a module-not-found at the first run. It
keeps its package directory in a versioned one next to a relative symlink, and
the tarball ships bin/ without the exec bit, so that needs chmod too.

hadolint's release names one platform uname-style and the other Go-style
(x86_64 but arm64), which 404s on the first architecture if you assume
otherwise.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-09-27 10:28:42 +02:00
..

Renovate for Gitea

Renovate runs as a Kubernetes CronJob and creates container image update pull requests in Gitea. It does not deploy changes itself.

Kubernetes

Create a dedicated Gitea user named renovate-bot, create a repository access token, and grant it repository read/write plus issue read/write permissions. Add read:packages if Renovate must inspect private Gitea registry images.

Create the ignored Secret locally; never commit the PAT:

cp renovate/k8s/secrets.yaml.example renovate/k8s/secrets.yaml
$EDITOR renovate/k8s/secrets.yaml
kubectl apply -f renovate/k8s/namespace.yaml
kubectl apply -f renovate/k8s/secrets.yaml
kubectl apply -f renovate/k8s/configmap.yaml
kubectl apply -f renovate/k8s/cronjob.yaml

The renovate/k8s/active marker makes the normal deployment workflow include the namespace, ConfigMap, and CronJob. The Secret is intentionally excluded from Git and must be applied separately after every new cluster.

Run it immediately instead of waiting for the six-hour schedule.

Two options, both use the same renovate/renovate.json:

kubectl create job --from=cronjob/renovate renovate-manual-$(date +%s) -n renovate

or the renovate-run Actions workflow (Actions tab → renovate-run → Run workflow). It runs the same image as the CronJob on the self-hosted runner via Docker — the tag is read out of renovate/k8s/cronjob.yaml at run time rather than hardcoded, so the two cannot drift apart. Required Actions secrets (repo or org settings):

  • RENOVATE_TOKEN — renovate-bot PAT (repository + issue read/write).
  • RENOVATE_GITHUB_COM_TOKEN — optional, for changelogs and GitHub rate limits.

Inputs: repositories (default forust/homelab), log_level (info/debug). Only one run at a time (concurrency group renovate-run), same as the CronJob Forbid policy.

Inspect runs with:

kubectl get cronjob,jobs,pods -n renovate
kubectl logs -n renovate job/<job-name>

RENOVATE_GITHUB_COM_TOKEN is optional but recommended for changelogs and GitHub API rate limits. Set it in the Kubernetes Secret if available.

Compose

Copy .env.example to .env, set the PAT, and run:

docker compose -f renovate-compose.yaml run --rm renovate

The Compose file is intentionally named renovate-compose.yaml, so the repository's automatic deployment discovery does not start it accidentally.

Configuration

renovate/renovate.json is the single source of truth. The Compose file and the renovate-run workflow mount that file directly.

A ConfigMap cannot read from the repository, so the CronJob needs the config inlined. renovate/k8s/configmap.yaml is therefore a generated copy:

.gitea/workflows/sync-renovate-configmap.sh          # regenerate after editing
.gitea/workflows/sync-renovate-configmap.sh --check  # fail if out of date

The renovate-ci workflow runs the --check form on every PR and push, so a config edit that forgets to regenerate the ConfigMap cannot be merged.

Beyond images, customManagers in the config track:

  • Helm chart versions pinned in .gitea/workflows/deploy-lib.sh. The built-in helmv3 manager only reads Chart.yaml and helm-values only reads values files, so neither sees a version written into a helm upgrade command — these are declared as custom.regex managers against the helm datasource.
  • CI linter versions in .gitea/workflows/tool-versions.env.

The Renovate image tag is deliberately not in tool-versions.env: renovate/k8s/cronjob.yaml owns it, and the workflows read it from there.

How updates flow

Renovate scans both compose.yaml files and Kubernetes manifests, opens a branch and PR with image tag changes, and waits for CI. After merge, the existing deployment workflow applies Kubernetes changes or redeploys Compose stacks. Renovate never updates running workloads directly.