diff --git a/.gitea/workflows/renovate-ci.yaml b/.gitea/workflows/renovate-ci.yaml index a0b1375..60c29d1 100644 --- a/.gitea/workflows/renovate-ci.yaml +++ b/.gitea/workflows/renovate-ci.yaml @@ -10,30 +10,52 @@ on: jobs: validate-renovate: runs-on: [self-hosted, linux, arch, homelab] + timeout-minutes: 20 steps: - name: Checkout repository uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 - - name: Validate Renovate Compose draft + # renovate/k8s/cronjob.yaml is the single source of truth for the image tag, + # so the same version that runs in the cluster is the one validated here. + - name: Resolve the deployed Renovate image + id: image shell: bash run: | set -euo pipefail - trap 'rm -f renovate/.env' EXIT - printf '%s\n' \ - 'RENOVATE_ENDPOINT=https://gitea.example/api/v1' \ - 'RENOVATE_TOKEN=test-token' \ - 'RENOVATE_REPOSITORIES=forust/homelab' \ - > renovate/.env - docker compose -f renovate/renovate-compose.yaml config --quiet + image="$(sed -n 's|.*image:[[:space:]]*\(renovate/renovate:[^[:space:]]*\).*|\1|p' \ + renovate/k8s/cronjob.yaml | head -1)" + if [ -z "$image" ]; then + echo "::error::no renovate/renovate image found in renovate/k8s/cronjob.yaml" + exit 1 + fi + echo "using $image" + echo "image=$image" >> "$GITHUB_OUTPUT" - - name: Validate Kubernetes manifests + - name: Validate Renovate repository config shell: bash run: | set -euo pipefail docker run --rm \ - -v "$PWD:/work" \ - -w /work \ - ghcr.io/yannh/kubeconform:latest \ + -v "$PWD/renovate:/opt/renovate:ro" \ + -e RENOVATE_CONFIG_FILE=/opt/renovate/renovate.json \ + "${{ steps.image.outputs.image }}" \ + renovate-config-validator /opt/renovate/renovate.json + + # The CronJob cannot read the repository, so renovate/k8s/configmap.yaml + # carries an inlined copy of the config. Fail if it no longer matches. + - name: Check the generated Renovate ConfigMap + shell: bash + run: | + set -euo pipefail + ./.gitea/workflows/sync-renovate-configmap.sh --check + + - name: Validate Renovate Kubernetes manifests + shell: bash + run: | + set -euo pipefail + tools_dir="$(bash .gitea/workflows/install-ci-tools.sh kubeconform)" + export PATH="$tools_dir:$PATH" + kubeconform \ -strict \ -ignore-missing-schemas \ -summary \ @@ -41,12 +63,11 @@ jobs: renovate/k8s/configmap.yaml \ renovate/k8s/cronjob.yaml - - name: Validate Renovate repository config + - name: Validate Renovate Compose file shell: bash run: | set -euo pipefail - docker run --rm \ - -v "$PWD:/work" \ - -w /work \ - renovate/renovate:44.103.0 \ - renovate-config-validator renovate.json + source .gitea/workflows/compose-lint.sh + mapfile -t safe_flags < <(compose_safe_flags) + validate_compose_file renovate/renovate-compose.yaml \ + ${safe_flags[@]+"${safe_flags[@]}"} diff --git a/.gitea/workflows/renovate-run.yaml b/.gitea/workflows/renovate-run.yaml index c9888a0..b5faca9 100644 --- a/.gitea/workflows/renovate-run.yaml +++ b/.gitea/workflows/renovate-run.yaml @@ -28,18 +28,36 @@ concurrency: jobs: run-renovate: runs-on: [self-hosted, linux, arch, homelab] + timeout-minutes: 60 steps: - name: Checkout repository uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + # renovate/k8s/cronjob.yaml is the single source of truth for the image tag. + # Reading it here means this workflow validates and runs the exact version + # that is deployed, instead of a copy that silently goes stale. + - name: Resolve the deployed Renovate image + id: image + shell: bash + run: | + set -euo pipefail + image="$(sed -n 's|.*image:[[:space:]]*\(renovate/renovate:[^[:space:]]*\).*|\1|p' \ + renovate/k8s/cronjob.yaml | head -1)" + if [ -z "$image" ]; then + echo "::error::no renovate/renovate image found in renovate/k8s/cronjob.yaml" + exit 1 + fi + echo "using $image" + echo "image=$image" >> "$GITHUB_OUTPUT" + - name: Validate Renovate config shell: bash run: | set -euo pipefail docker run --rm \ - -v "$PWD/renovate/config.js:/opt/renovate/config.js:ro" \ - -e RENOVATE_CONFIG_FILE=/opt/renovate/config.js \ - renovate/renovate:44.103.0 \ + -v "$PWD/renovate/renovate.json:/opt/renovate/renovate.json:ro" \ + -e RENOVATE_CONFIG_FILE=/opt/renovate/renovate.json \ + "${{ steps.image.outputs.image }}" \ renovate-config-validator - name: Run Renovate @@ -56,14 +74,14 @@ jobs: : "${RENOVATE_TOKEN:?missing RENOVATE_TOKEN secret — add a renovate-bot PAT in repo/org Actions secrets}" docker run --rm \ - -v "$PWD/renovate/config.js:/opt/renovate/config.js:ro" \ + -v "$PWD/renovate/renovate.json:/opt/renovate/renovate.json:ro" \ -e RENOVATE_PLATFORM=gitea \ -e RENOVATE_ENDPOINT=https://gitea.forust.xyz/api/v1 \ -e RENOVATE_TOKEN="$RENOVATE_TOKEN" \ -e RENOVATE_GITHUB_COM_TOKEN="${RENOVATE_GITHUB_COM_TOKEN:-}" \ -e RENOVATE_REPOSITORIES="${RENOVATE_REPOSITORIES:-forust/homelab}" \ -e RENOVATE_DRY_RUN="${RENOVATE_DRY_RUN:-}" \ - -e RENOVATE_CONFIG_FILE=/opt/renovate/config.js \ + -e RENOVATE_CONFIG_FILE=/opt/renovate/renovate.json \ -e RENOVATE_BASE_DIR=/tmp/renovate \ -e LOG_LEVEL="${LOG_LEVEL:-info}" \ - renovate/renovate:44.103.0 + "${{ steps.image.outputs.image }}" diff --git a/.gitea/workflows/sync-renovate-configmap.sh b/.gitea/workflows/sync-renovate-configmap.sh new file mode 100755 index 0000000..ce04c62 --- /dev/null +++ b/.gitea/workflows/sync-renovate-configmap.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +# Regenerates renovate/k8s/configmap.yaml from renovate/renovate.json. +# +# renovate/renovate.json is the single source of truth: the CronJob, the Compose +# file and the renovate-run workflow all mount that exact file. A ConfigMap cannot +# read a file from the repository, so the same bytes are inlined here as a literal +# block. This script keeps the copy honest: +# +# .gitea/workflows/sync-renovate-configmap.sh # rewrite in place +# .gitea/workflows/sync-renovate-configmap.sh --check # fail if out of date +# +# renovate-ci runs the --check form on every PR and push, so a config change that +# forgets to regenerate the ConfigMap cannot be merged. +set -euo pipefail + +here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +repo="$(git -C "$here" rev-parse --show-toplevel)" + +src="$repo/renovate/renovate.json" +dst="$repo/renovate/k8s/configmap.yaml" +[ -f "$src" ] || { + echo "missing $src" >&2 + exit 1 +} + +render() { + cat <<'HEADER' +# GENERATED FILE - do not edit by hand. +# Source: renovate/renovate.json +# Regenerate: .gitea/workflows/sync-renovate-configmap.sh +# Verify: .gitea/workflows/sync-renovate-configmap.sh --check +apiVersion: v1 +kind: ConfigMap +metadata: + name: renovate-config + namespace: renovate +data: + renovate.json: | +HEADER + sed 's/^/ /' "$src" +} + +if [ "${1:-}" = "--check" ]; then + if ! diff -u "$dst" <(render) >/dev/null 2>&1; then + echo "ERROR: $dst is out of sync with renovate/renovate.json" + echo "Run: .gitea/workflows/sync-renovate-configmap.sh" + diff -u "$dst" <(render) || true + exit 1 + fi + echo "renovate/k8s/configmap.yaml is in sync with renovate/renovate.json" + exit 0 +fi + +render >"$dst" +echo "wrote $dst" diff --git a/renovate.json b/renovate.json deleted file mode 100644 index b548672..0000000 --- a/renovate.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": ["config:recommended"], - "enabledManagers": ["dockerfile", "docker-compose", "kubernetes", "helm-values", "custom.regex"], - "helm-values": { - "managerFilePatterns": ["/k8s/.+values\\.ya?ml$/"] - }, - "kubernetes": { - "managerFilePatterns": ["/k8s/.+\\.ya?ml$/"] - }, - "customManagers": [ - { - "customType": "regex", - "description": "singlesource: playwright npm version pinned in npx command (k8s + compose)", - "fileMatch": ["^edu_master/k8s/playwright\\.yaml$", "^edu_master/compose\\.yaml$"], - "matchStrings": ["playwright@(?\\d+\\.\\d+\\.\\d+)"], - "datasourceTemplate": "npm", - "depNameTemplate": "playwright" - }, - { - "customType": "regex", - "description": "singlesource: PLAYWRIGHT_VERSION file", - "fileMatch": ["^edu_master/PLAYWRIGHT_VERSION$"], - "matchStrings": ["^(?\\d+\\.\\d+\\.\\d+)$"], - "datasourceTemplate": "pypi", - "depNameTemplate": "playwright" - } - ], - "packageRules": [ - { - "description": "singlesource playwright - use whichever version is found, keep docker+pypi+npm in sync", - "matchPackageNames": ["playwright", "mcr.microsoft.com/playwright"], - "groupName": "playwright singlesource", - "groupSlug": "playwright" - }, - { - "description": "playwright must not automerge - version skew breaks WS handshake (checker.py:1523 vs playwright.yaml:20)", - "matchPackageNames": ["playwright", "mcr.microsoft.com/playwright"], - "automerge": false - }, - { - "description": "Keep private homelab images unchanged", - "matchDatasources": ["docker"], - "matchPackageNames": ["/gcr\\.forust\\.xyz\\/forust\\/.+/"], - "enabled": false - }, - { - "description": "Require approval for major upgrades", - "matchUpdateTypes": ["major"], - "dependencyDashboardApproval": true, - "automerge": false - }, - { - "description": "Group container patch updates", - "matchDatasources": ["docker"], - "matchUpdateTypes": ["patch"], - "groupName": "container patch updates" - } - ] -} diff --git a/renovate/README.md b/renovate/README.md index bc84aa2..99039b6 100644 --- a/renovate/README.md +++ b/renovate/README.md @@ -26,15 +26,17 @@ 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/config.js`: +Two options, both use the same `renovate/renovate.json`: ```sh 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 `renovate/renovate:44.103.0` on the self-hosted -runner via Docker. Required Actions secrets (repo or org settings): +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. @@ -64,6 +66,33 @@ 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: + +```sh +.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 diff --git a/renovate/config.js b/renovate/config.js deleted file mode 100644 index 8500f74..0000000 --- a/renovate/config.js +++ /dev/null @@ -1,44 +0,0 @@ -module.exports = { - platform: 'gitea', - endpoint: process.env.RENOVATE_ENDPOINT || 'https://gitea.forust.xyz/api/v1', - enabledManagers: ['docker-compose', 'kubernetes', 'helm-values'], - 'helm-values': { - managerFilePatterns: ['/k8s/.+values\\.ya?ml$/'], - }, - kubernetes: { - managerFilePatterns: ['/k8s/.+\\.ya?ml$/'], - }, - repositories: (process.env.RENOVATE_REPOSITORIES || '') - .split(',') - .map((repository) => repository.trim()) - .filter(Boolean), - onboarding: false, - requireConfig: 'optional', - autodiscover: false, - dependencyDashboard: true, - prCreation: 'immediate', - labels: ['dependencies', 'automated'], - extends: [ - 'config:recommended', - ':dependencyDashboard', - ], - packageRules: [ - { - description: 'Do not update private homelab images', - matchDatasources: ['docker'], - matchPackageNames: ['/gcr\\.forust\\.xyz\\/forust\\/.+/'], - enabled: false, - }, - { - description: 'Keep major upgrades manual', - matchUpdateTypes: ['major'], - dependencyDashboardApproval: true, - automerge: false, - }, - { - description: 'Group patch updates', - matchUpdateTypes: ['patch'], - groupName: 'container patch updates', - }, - ], -}; diff --git a/renovate/k8s/configmap.yaml b/renovate/k8s/configmap.yaml index bbba610..9a9964c 100644 --- a/renovate/k8s/configmap.yaml +++ b/renovate/k8s/configmap.yaml @@ -1,51 +1,139 @@ +# GENERATED FILE - do not edit by hand. +# Source: renovate/renovate.json +# Regenerate: .gitea/workflows/sync-renovate-configmap.sh +# Verify: .gitea/workflows/sync-renovate-configmap.sh --check apiVersion: v1 kind: ConfigMap metadata: name: renovate-config namespace: renovate data: - config.js: | - module.exports = { - platform: 'gitea', - endpoint: process.env.RENOVATE_ENDPOINT || 'https://gitea.forust.xyz/api/v1', - enabledManagers: ['docker-compose', 'kubernetes', 'helm-values'], - 'helm-values': { - managerFilePatterns: ['/k8s/.+values\\.ya?ml$/'], + renovate.json: | + { + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["config:recommended", ":dependencyDashboard"], + "enabledManagers": ["dockerfile", "docker-compose", "kubernetes", "helm-values", "custom.regex"], + "onboarding": false, + "requireConfig": "optional", + "autodiscover": false, + "dependencyDashboard": true, + "prCreation": "immediate", + "labels": ["dependencies", "automated"], + "helm-values": { + "managerFilePatterns": ["/k8s/.+values\\.ya?ml$/"] }, - kubernetes: { - managerFilePatterns: ['/k8s/.+\\.ya?ml$/'], + "kubernetes": { + "managerFilePatterns": ["/k8s/.+\\.ya?ml$/"] }, - repositories: (process.env.RENOVATE_REPOSITORIES || '') - .split(',') - .map((repository) => repository.trim()) - .filter(Boolean), - onboarding: false, - requireConfig: 'optional', - autodiscover: false, - dependencyDashboard: true, - prCreation: 'immediate', - labels: ['dependencies', 'automated'], - extends: [ - 'config:recommended', - ':dependencyDashboard', + "customManagers": [ + { + "customType": "regex", + "description": "singlesource: playwright npm version pinned in npx command (k8s + compose)", + "managerFilePatterns": ["^edu_master/k8s/playwright\\.yaml$", "^edu_master/compose\\.yaml$"], + "matchStrings": ["playwright@(?\\d+\\.\\d+\\.\\d+)"], + "datasourceTemplate": "npm", + "depNameTemplate": "playwright" + }, + { + "customType": "regex", + "description": "singlesource: PLAYWRIGHT_VERSION file", + "managerFilePatterns": ["^edu_master/PLAYWRIGHT_VERSION$"], + "matchStrings": ["^(?\\d+\\.\\d+\\.\\d+)$"], + "datasourceTemplate": "pypi", + "depNameTemplate": "playwright" + }, + { + "customType": "regex", + "description": "kube-prometheus-stack chart version pinned in the deploy workflow", + "managerFilePatterns": ["^\\.gitea/workflows/deploy-lib\\.sh$"], + "matchStrings": ["\\|prometheus-community/kube-prometheus-stack\\|prometheus\\|(?[0-9.]+)\\|"], + "datasourceTemplate": "helm", + "depNameTemplate": "kube-prometheus-stack", + "registryUrlTemplate": "https://prometheus-community.github.io/helm-charts" + }, + { + "customType": "regex", + "description": "grafana/loki chart version pinned in the deploy workflow", + "managerFilePatterns": ["^\\.gitea/workflows/deploy-lib\\.sh$"], + "matchStrings": ["\\|grafana/loki\\|prometheus\\|(?[0-9.]+)\\|"], + "datasourceTemplate": "helm", + "depNameTemplate": "loki", + "registryUrlTemplate": "https://grafana.github.io/helm-charts" + }, + { + "customType": "regex", + "description": "grafana/alloy chart version pinned in the deploy workflow", + "managerFilePatterns": ["^\\.gitea/workflows/deploy-lib\\.sh$"], + "matchStrings": ["\\|grafana/alloy\\|prometheus\\|(?[0-9.]+)\\|"], + "datasourceTemplate": "helm", + "depNameTemplate": "alloy", + "registryUrlTemplate": "https://grafana.github.io/helm-charts" + }, + { + "customType": "regex", + "description": "actionlint version used by the ci workflow", + "managerFilePatterns": ["^\\.gitea/workflows/tool-versions\\.env$"], + "matchStrings": ["(?:^|\\n)ACTIONLINT_VERSION=\"(?[0-9.]+)\""], + "datasourceTemplate": "github-tags", + "depNameTemplate": "rhysd/actionlint" + }, + { + "customType": "regex", + "description": "shellcheck version used by the ci workflow", + "managerFilePatterns": ["^\\.gitea/workflows/tool-versions\\.env$"], + "matchStrings": ["(?:^|\\n)SHELLCHECK_VERSION=\"(?[0-9.]+)\""], + "datasourceTemplate": "github-tags", + "depNameTemplate": "koalaman/shellcheck" + }, + { + "customType": "regex", + "description": "kubeconform version used by the ci workflow", + "managerFilePatterns": ["^\\.gitea/workflows/tool-versions\\.env$"], + "matchStrings": ["(?:^|\\n)KUBECONFORM_VERSION=\"(?[0-9.]+)\""], + "datasourceTemplate": "github-tags", + "depNameTemplate": "yannh/kubeconform" + } ], - packageRules: [ + "packageRules": [ { - description: 'Do not update private homelab images', - matchDatasources: ['docker'], - matchPackageNames: ['/gcr\\.forust\\.xyz\\/forust\\/.+/'], - enabled: false, + "description": "Keep private homelab images unchanged", + "matchDatasources": ["docker"], + "matchPackageNames": ["/gcr\\.forust\\.xyz\\/forust\\/.+/"], + "enabled": false }, { - description: 'Keep major upgrades manual', - matchUpdateTypes: ['major'], - dependencyDashboardApproval: true, - automerge: false, + "description": "singlesource playwright - use whichever version is found, keep docker+pypi+npm in sync", + "matchPackageNames": ["playwright", "mcr.microsoft.com/playwright"], + "groupName": "playwright singlesource", + "groupSlug": "playwright" }, { - description: 'Group patch updates', - matchUpdateTypes: ['patch'], - groupName: 'container patch updates', + "description": "playwright must not automerge - version skew breaks the WS handshake (checker.py:1523 vs playwright.yaml:20)", + "matchPackageNames": ["playwright", "mcr.microsoft.com/playwright"], + "automerge": false }, - ], - }; + { + "description": "Renovate updates itself in lockstep across the CronJob and the Compose file", + "matchPackageNames": ["renovate/renovate"], + "groupName": "renovate self-update", + "automerge": false + }, + { + "description": "Helm chart bumps change PVC fields and admission behaviour, keep them reviewable", + "matchDatasources": ["helm"], + "automerge": false + }, + { + "description": "Require approval for major upgrades", + "matchUpdateTypes": ["major"], + "dependencyDashboardApproval": true, + "automerge": false + }, + { + "description": "Group container patch updates", + "matchDatasources": ["docker"], + "matchUpdateTypes": ["patch"], + "groupName": "container patch updates" + } + ] + } diff --git a/renovate/k8s/cronjob.yaml b/renovate/k8s/cronjob.yaml index 8e66405..cbb228d 100644 --- a/renovate/k8s/cronjob.yaml +++ b/renovate/k8s/cronjob.yaml @@ -36,7 +36,7 @@ spec: name: renovate-secrets key: RENOVATE_REPOSITORIES - name: RENOVATE_CONFIG_FILE - value: /opt/renovate/config.js + value: /opt/renovate/renovate.json - name: RENOVATE_BASE_DIR value: /tmp/renovate - name: RENOVATE_GITHUB_COM_TOKEN @@ -49,8 +49,8 @@ spec: value: info volumeMounts: - name: config - mountPath: /opt/renovate/config.js - subPath: config.js + mountPath: /opt/renovate/renovate.json + subPath: renovate.json readOnly: true volumes: - name: config diff --git a/renovate/renovate-compose.yaml b/renovate/renovate-compose.yaml index cbc859c..0d63628 100644 --- a/renovate/renovate-compose.yaml +++ b/renovate/renovate-compose.yaml @@ -1,6 +1,8 @@ services: renovate: - image: renovate/renovate:44.103.0 + # Kept in step with renovate/k8s/cronjob.yaml by the "renovate self-update" + # package rule in renovate/renovate.json. + image: renovate/renovate:44.115.9 container_name: renovate restart: "no" env_file: @@ -10,8 +12,8 @@ services: RENOVATE_ENDPOINT: ${RENOVATE_ENDPOINT:?set RENOVATE_ENDPOINT} RENOVATE_TOKEN: ${RENOVATE_TOKEN:?set RENOVATE_TOKEN} RENOVATE_REPOSITORIES: ${RENOVATE_REPOSITORIES:?set RENOVATE_REPOSITORIES} - RENOVATE_CONFIG_FILE: /opt/renovate/config.js + RENOVATE_CONFIG_FILE: /opt/renovate/renovate.json RENOVATE_BASE_DIR: /tmp/renovate LOG_LEVEL: ${LOG_LEVEL:-info} volumes: - - ./config.js:/opt/renovate/config.js:ro + - ./renovate.json:/opt/renovate/renovate.json:ro diff --git a/renovate/renovate.json b/renovate/renovate.json new file mode 100644 index 0000000..1caa141 --- /dev/null +++ b/renovate/renovate.json @@ -0,0 +1,128 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["config:recommended", ":dependencyDashboard"], + "enabledManagers": ["dockerfile", "docker-compose", "kubernetes", "helm-values", "custom.regex"], + "onboarding": false, + "requireConfig": "optional", + "autodiscover": false, + "dependencyDashboard": true, + "prCreation": "immediate", + "labels": ["dependencies", "automated"], + "helm-values": { + "managerFilePatterns": ["/k8s/.+values\\.ya?ml$/"] + }, + "kubernetes": { + "managerFilePatterns": ["/k8s/.+\\.ya?ml$/"] + }, + "customManagers": [ + { + "customType": "regex", + "description": "singlesource: playwright npm version pinned in npx command (k8s + compose)", + "managerFilePatterns": ["^edu_master/k8s/playwright\\.yaml$", "^edu_master/compose\\.yaml$"], + "matchStrings": ["playwright@(?\\d+\\.\\d+\\.\\d+)"], + "datasourceTemplate": "npm", + "depNameTemplate": "playwright" + }, + { + "customType": "regex", + "description": "singlesource: PLAYWRIGHT_VERSION file", + "managerFilePatterns": ["^edu_master/PLAYWRIGHT_VERSION$"], + "matchStrings": ["^(?\\d+\\.\\d+\\.\\d+)$"], + "datasourceTemplate": "pypi", + "depNameTemplate": "playwright" + }, + { + "customType": "regex", + "description": "kube-prometheus-stack chart version pinned in the deploy workflow", + "managerFilePatterns": ["^\\.gitea/workflows/deploy-lib\\.sh$"], + "matchStrings": ["\\|prometheus-community/kube-prometheus-stack\\|prometheus\\|(?[0-9.]+)\\|"], + "datasourceTemplate": "helm", + "depNameTemplate": "kube-prometheus-stack", + "registryUrlTemplate": "https://prometheus-community.github.io/helm-charts" + }, + { + "customType": "regex", + "description": "grafana/loki chart version pinned in the deploy workflow", + "managerFilePatterns": ["^\\.gitea/workflows/deploy-lib\\.sh$"], + "matchStrings": ["\\|grafana/loki\\|prometheus\\|(?[0-9.]+)\\|"], + "datasourceTemplate": "helm", + "depNameTemplate": "loki", + "registryUrlTemplate": "https://grafana.github.io/helm-charts" + }, + { + "customType": "regex", + "description": "grafana/alloy chart version pinned in the deploy workflow", + "managerFilePatterns": ["^\\.gitea/workflows/deploy-lib\\.sh$"], + "matchStrings": ["\\|grafana/alloy\\|prometheus\\|(?[0-9.]+)\\|"], + "datasourceTemplate": "helm", + "depNameTemplate": "alloy", + "registryUrlTemplate": "https://grafana.github.io/helm-charts" + }, + { + "customType": "regex", + "description": "actionlint version used by the ci workflow", + "managerFilePatterns": ["^\\.gitea/workflows/tool-versions\\.env$"], + "matchStrings": ["(?:^|\\n)ACTIONLINT_VERSION=\"(?[0-9.]+)\""], + "datasourceTemplate": "github-tags", + "depNameTemplate": "rhysd/actionlint" + }, + { + "customType": "regex", + "description": "shellcheck version used by the ci workflow", + "managerFilePatterns": ["^\\.gitea/workflows/tool-versions\\.env$"], + "matchStrings": ["(?:^|\\n)SHELLCHECK_VERSION=\"(?[0-9.]+)\""], + "datasourceTemplate": "github-tags", + "depNameTemplate": "koalaman/shellcheck" + }, + { + "customType": "regex", + "description": "kubeconform version used by the ci workflow", + "managerFilePatterns": ["^\\.gitea/workflows/tool-versions\\.env$"], + "matchStrings": ["(?:^|\\n)KUBECONFORM_VERSION=\"(?[0-9.]+)\""], + "datasourceTemplate": "github-tags", + "depNameTemplate": "yannh/kubeconform" + } + ], + "packageRules": [ + { + "description": "Keep private homelab images unchanged", + "matchDatasources": ["docker"], + "matchPackageNames": ["/gcr\\.forust\\.xyz\\/forust\\/.+/"], + "enabled": false + }, + { + "description": "singlesource playwright - use whichever version is found, keep docker+pypi+npm in sync", + "matchPackageNames": ["playwright", "mcr.microsoft.com/playwright"], + "groupName": "playwright singlesource", + "groupSlug": "playwright" + }, + { + "description": "playwright must not automerge - version skew breaks the WS handshake (checker.py:1523 vs playwright.yaml:20)", + "matchPackageNames": ["playwright", "mcr.microsoft.com/playwright"], + "automerge": false + }, + { + "description": "Renovate updates itself in lockstep across the CronJob and the Compose file", + "matchPackageNames": ["renovate/renovate"], + "groupName": "renovate self-update", + "automerge": false + }, + { + "description": "Helm chart bumps change PVC fields and admission behaviour, keep them reviewable", + "matchDatasources": ["helm"], + "automerge": false + }, + { + "description": "Require approval for major upgrades", + "matchUpdateTypes": ["major"], + "dependencyDashboardApproval": true, + "automerge": false + }, + { + "description": "Group container patch updates", + "matchDatasources": ["docker"], + "matchUpdateTypes": ["patch"], + "groupName": "container patch updates" + } + ] +}