381 lines
18 KiB
JSON
381 lines
18 KiB
JSON
{
|
|
$schema: 'https://docs.renovatebot.com/renovate-schema.json',
|
|
extends: [
|
|
'config:recommended',
|
|
':dependencyDashboard',
|
|
':semanticCommits',
|
|
'helpers:pinGitHubActionDigests',
|
|
],
|
|
timezone: 'America/New_York',
|
|
schedule: [
|
|
'after 3am and before 6am',
|
|
],
|
|
prConcurrentLimit: 5,
|
|
prHourlyLimit: 2,
|
|
commitMessagePrefix: 'chore(deps):',
|
|
labels: [
|
|
'dependencies',
|
|
'automated-update',
|
|
],
|
|
ignoreUnstable: true,
|
|
respectLatest: true,
|
|
enabledManagers: [
|
|
'flux',
|
|
'kustomize',
|
|
'helmv3',
|
|
'kubernetes',
|
|
'custom.regex',
|
|
'github-actions',
|
|
],
|
|
flux: {
|
|
managerFilePatterns: [
|
|
'/(^|/)platform/.+\\.ya?ml$/',
|
|
'/(^|/)apps/.+\\.ya?ml$/',
|
|
],
|
|
},
|
|
kustomize: {
|
|
managerFilePatterns: [
|
|
'/(^|/)kustomization\\.ya?ml$/',
|
|
],
|
|
},
|
|
kubernetes: {
|
|
managerFilePatterns: [
|
|
'/(^|/)platform/.+\\.ya?ml$/',
|
|
'/(^|/)platform-config/.+\\.ya?ml$/',
|
|
'/(^|/)apps/.+\\.ya?ml$/',
|
|
'/(^|/)catalog/.+\\.ya?ml$/',
|
|
'/(^|/)runbooks/serving/.+\\.ya?ml$/',
|
|
'/(^|/)infra/agent/.+\\.ya?ml$/',
|
|
],
|
|
},
|
|
customManagers: [
|
|
{
|
|
customType: 'regex',
|
|
description: 'Inline `# renovate: datasource=... depName=...` markers on pinned URLs / version literals (e.g. platform/kubevirt/kustomization.yaml release URLs, ci/versions.env, infra/devcontainer/versions.env).',
|
|
managerFilePatterns: [
|
|
'/(^|/)kustomization\\.ya?ml$/',
|
|
'/(^|/)versions\\.env$/',
|
|
],
|
|
matchStrings: [
|
|
'#\\s*renovate:\\s*datasource=(?<datasource>\\S+)\\s+depName=(?<depName>\\S+)(?:\\s+versioning=(?<versioning>\\S+))?\\s*\\n.*?(?<currentValue>v?[0-9]+\\.[0-9]+\\.[0-9]+)',
|
|
],
|
|
versioningTemplate: '{{#if versioning}}{{{versioning}}}{{else}}semver{{/if}}',
|
|
},
|
|
{
|
|
customType: 'regex',
|
|
description: "This repo's own CI-published images, pinned by digest, following the tag named in a `# renovate-digest:` marker on the previous line.",
|
|
managerFilePatterns: [
|
|
'/(^|/)versions\\.env$/',
|
|
'/(^|/)platform/pm-agent/.+\\.ya?ml$/',
|
|
],
|
|
matchStrings: [
|
|
'#\\s*renovate-digest:\\s*image=(?<depName>\\S+)\\s+tag=(?<currentValue>\\S+)\\s*\\n[^\\n]*?(?<currentDigest>sha256:[a-f0-9]{64})',
|
|
],
|
|
datasourceTemplate: 'docker',
|
|
versioningTemplate: 'docker',
|
|
},
|
|
{
|
|
customType: 'regex',
|
|
description: 'ServiceDefinition `source.image: <repo>:<tag>` (catalog/*.yaml) — the source of the rendered apps/wave-* image.',
|
|
managerFilePatterns: [
|
|
'/(^|/)catalog/[^/]+\\.ya?ml$/',
|
|
],
|
|
matchStrings: [
|
|
'\\n\\s+image:\\s*(?<depName>[^\\s:@#"\']+):(?<currentValue>[^\\s@#"\']+)',
|
|
],
|
|
datasourceTemplate: 'docker',
|
|
},
|
|
{
|
|
customType: 'regex',
|
|
description: 'catalog/render.py OAUTH2_PROXY_IMAGE — the per-app oauth2-proxy sidecar every forward-auth app is rendered with.',
|
|
managerFilePatterns: [
|
|
'/(^|/)catalog/render\\.py$/',
|
|
],
|
|
matchStrings: [
|
|
'\\nOAUTH2_PROXY_IMAGE\\s*=\\s*"(?<depName>[^":@]+):(?<currentValue>[^"@]+)"',
|
|
],
|
|
datasourceTemplate: 'docker',
|
|
},
|
|
{
|
|
customType: 'regex',
|
|
description: 'CloudNativePG Cluster `imageName: <repo>:<tag>`.',
|
|
managerFilePatterns: [
|
|
'/(^|/)platform/.+\\.ya?ml$/',
|
|
'/(^|/)apps/.+\\.ya?ml$/',
|
|
],
|
|
matchStrings: [
|
|
'\\n\\s+imageName:\\s*(?<depName>[^\\s:@#"\']+):(?<currentValue>[^\\s@#"\']+)',
|
|
],
|
|
datasourceTemplate: 'docker',
|
|
},
|
|
],
|
|
packageRules: [
|
|
{
|
|
matchCategories: [
|
|
'docker',
|
|
],
|
|
enabled: true,
|
|
},
|
|
{
|
|
description: 'Auto-merge the allowed low-risk classes on green CI — no human authoring (R-UPD-02). The Gitea merge gate (make -C ci gate) is the required check.',
|
|
matchUpdateTypes: [
|
|
'minor',
|
|
'patch',
|
|
'digest',
|
|
],
|
|
automerge: true,
|
|
automergeType: 'pr',
|
|
platformAutomerge: true,
|
|
},
|
|
{
|
|
description: 'MAJOR / potentially-breaking bumps require human review (docs/11 safety rail). Never auto-merged.',
|
|
matchUpdateTypes: [
|
|
'major',
|
|
],
|
|
automerge: false,
|
|
addLabels: [
|
|
'needs-review',
|
|
'breaking',
|
|
],
|
|
},
|
|
{
|
|
description: 'Keep the KubeVirt + CDI operator pair on a compatible track — grouped so they bump together (platform/kubevirt/kustomization.yaml).',
|
|
matchDepNames: [
|
|
'kubevirt/kubevirt',
|
|
'kubevirt/containerized-data-importer',
|
|
],
|
|
groupName: 'kubevirt-cdi',
|
|
},
|
|
{
|
|
description: 'Pinned tool versions (ci/versions.env, infra/devcontainer/versions.env) are a deliberate change surface — label but do not auto-merge; a version bump there changes CI reproducibility.',
|
|
matchFileNames: [
|
|
'ci/versions.env',
|
|
'infra/devcontainer/versions.env',
|
|
],
|
|
automerge: false,
|
|
addLabels: [
|
|
'ci-tooling',
|
|
],
|
|
},
|
|
{
|
|
description: 'ADDED 2026-10-17. NEVER AUTO-MERGE THE BLAST-RADIUS COMPONENTS, not even a patch. The default rule above auto-merges minor/patch/digest on a green gate, which is right for an app image: a bad Sonarr bump inconveniences one pod. It is wrong for these five, because the gate is a STATIC check — it renders manifests and lints them, it does not boot a node, drain a volume or form an etcd quorum (vars.CLUSTER_AVAILABLE is unset, so L2/L3 conformance does not run in CI at all). So for anything whose failure mode is the CLUSTER rather than a service, a green gate is not evidence and a human must read the changelog. kured is here because it reboots control-plane nodes; longhorn because a storage-engine bump migrates live volume data; cilium because it is the CNI and a bad bump is a total partition (and its version is DUAL-PINNED in infra/ansible/roles/cilium/defaults/main.yml, which Renovate does not see, so the two would silently desync); cloudnative-pg because it drives Postgres switchovers; kube-prometheus-stack because ci/versions.env asserts an exact promtool/Prometheus lockstep and a chart bump that moves Prometheus fails the gate until that file moves with it.',
|
|
matchPackageNames: [
|
|
'kured',
|
|
'longhorn',
|
|
'cilium',
|
|
'cloudnative-pg',
|
|
'kube-prometheus-stack',
|
|
'gitea/gitea',
|
|
'gitea/act_runner',
|
|
'ghcr.io/cloudnative-pg/postgresql',
|
|
],
|
|
automerge: false,
|
|
addLabels: [
|
|
'needs-review',
|
|
'blast-radius',
|
|
],
|
|
},
|
|
{
|
|
description: 'Keycloak operator + CRDs + server (lockstep, one branch). IdP blast radius; passkey-reconciler.yaml encodes 26.0.6 Admin API shapes — re-check it against the new tag before merging. Never auto-merge.',
|
|
matchDepNames: [
|
|
'keycloak/keycloak-k8s-resources',
|
|
],
|
|
groupName: 'keycloak-operator',
|
|
automerge: false,
|
|
addLabels: [
|
|
'needs-review',
|
|
'blast-radius',
|
|
],
|
|
},
|
|
{
|
|
description: 'quay.io/keycloak/keycloak appears only in the documentation-only catalog/keycloak.yaml and its undeployed render; the operator (keycloak/keycloak-k8s-resources) chooses the real server image.',
|
|
matchPackageNames: [
|
|
'quay.io/keycloak/keycloak',
|
|
],
|
|
enabled: false,
|
|
},
|
|
{
|
|
description: "ADDED 2026-10-29. This repo's own images are followed ONLY through the `# renovate-digest:` markers above. The `kubernetes` manager also sees platform/pm-agent/deployment.yaml and infra/agent/worker-job-template.yaml, where the image has a digest and no tag; left on, it would look those up as `latest` (a tag CI never pushes) and, for the template, re-open the self-referential loop the custom manager deliberately avoids.",
|
|
matchManagers: [
|
|
'kubernetes',
|
|
],
|
|
matchPackageNames: [
|
|
'gitea.home.keithjackson.dev/home-network/toolbox',
|
|
'gitea.home.keithjackson.dev/home-network/agent-runner',
|
|
'gitea.home.keithjackson.dev/home-network/conformance-runner',
|
|
],
|
|
enabled: false,
|
|
},
|
|
{
|
|
description: "ADDED 2026-10-29. Digest bumps for this repo's own images: the image was built and verified by CI before it was published, so these auto-merge on a green gate like any digest (rule 1). Labelled so they are easy to find. NOTE: the conformance-runner bump edits conformance/versions.env, a sensitive path (infra/gitea/approval-policy.json), so it will wait for an administrator's /approve-override regardless of this rule.",
|
|
matchManagers: [
|
|
'custom.regex',
|
|
],
|
|
matchPackageNames: [
|
|
'gitea.home.keithjackson.dev/home-network/toolbox',
|
|
'gitea.home.keithjackson.dev/home-network/agent-runner',
|
|
'gitea.home.keithjackson.dev/home-network/conformance-runner',
|
|
],
|
|
addLabels: [
|
|
'own-image',
|
|
],
|
|
},
|
|
{
|
|
description: 'ADDED 2026-10-30. The `flux` manager also extracts `image:` lines inside HelmRelease values, which the `kubernetes` manager already owns. Measured: exactly two (alpine in platform/oauth2-proxy/helmrelease.yaml, velero-plugin-for-aws in platform/velero/helmrelease.yaml), each extracted twice; the second rewrite of the same line logged `Cannot find replaceString in current file content` in runs 353/354. Flux keeps its charts (datasource helm).',
|
|
matchManagers: [
|
|
'flux',
|
|
],
|
|
matchDatasources: [
|
|
'docker',
|
|
],
|
|
enabled: false,
|
|
},
|
|
{
|
|
description: "ADDED 2026-10-30. MIRRORS catalog/<svc>.yaml `update.range` — the operator's declared per-service policy (schemas/servicedefinition.schema.json), which Renovate does not read. Without it the dry run proposed gitea 28.2.0, qbittorrent 20.04.1 (both stale/odd tags, not real successors), radarr 6.x and nextcloud 35 (four majors past 31; Nextcloud cannot skip majors). KEEP IN STEP with the catalog ranges.",
|
|
matchPackageNames: [
|
|
'gitea/gitea',
|
|
],
|
|
allowedVersions: '<2.0.0',
|
|
},
|
|
{
|
|
matchPackageNames: [
|
|
'nextcloud',
|
|
],
|
|
allowedVersions: '<32.0.0',
|
|
},
|
|
{
|
|
matchPackageNames: [
|
|
'lscr.io/linuxserver/qbittorrent',
|
|
],
|
|
allowedVersions: '<6.0.0',
|
|
},
|
|
{
|
|
matchPackageNames: [
|
|
'lscr.io/linuxserver/radarr',
|
|
],
|
|
allowedVersions: '<6.0.0',
|
|
},
|
|
{
|
|
matchPackageNames: [
|
|
'lscr.io/linuxserver/sonarr',
|
|
],
|
|
allowedVersions: '<5.0.0',
|
|
},
|
|
{
|
|
matchPackageNames: [
|
|
'semaphoreui/semaphore',
|
|
],
|
|
allowedVersions: '<3.0.0',
|
|
},
|
|
{
|
|
description: "ADDED 2026-10-30. LinuxServer tags: `vX.Y.Z-lsN` is the IMMUTABLE build tag (bare `X.Y.Z` and `latest` are re-pushed for every ls rebuild). Docker versioning would treat `-ls7` as a compatibility suffix and only ever compare tags with the SAME suffix, so it would never move. This is Renovate's own regex-versioning example for linuxserver images: the ls build number becomes the `build` component, so an ls rebuild of the same Jackett is an update too. The `^v` and `$` anchors exclude `latest`, `development`, the bare mutable `X.Y.Z` tags and the arch-prefixed (`amd64-v...`) tags.",
|
|
matchDepNames: [
|
|
'lscr.io/linuxserver/jackett',
|
|
],
|
|
versioning: 'regex:^v(?<major>\\d+)\\.(?<minor>\\d+)\\.(?<patch>\\d+)-ls(?<build>\\d+)$',
|
|
},
|
|
{
|
|
description: "ADDED 2026-10-30. LOOK JACKETT UP ON ghcr.io, NOT lscr.io. lscr.io is LinuxServer's redirector in front of ghcr.io/linuxserver (same manifests: v0.24.2865-ls7 is sha256:4fcf3cc2... on both). Renovate's docker datasource fetches up to 1000 pages of tags from ghcr.io but only `dockerMaxPages` (default 20, a GLOBAL-only option) from any other host (lib/modules/datasource/docker/index.js getTags). Jackett has 32,093 tags at ~1000 per page (measured 2026-10-30), oldest first; the running tag is #31,980, so a lookup via lscr.io never sees it or anything newer and silently proposes NOTHING (measured in a dry run before this rule). overridePackageName changes only the lookup; the file keeps `lscr.io/...`, so the deployed image string does not change. Matched by depName because packageName is what this rule rewrites.",
|
|
matchDepNames: [
|
|
'lscr.io/linuxserver/jackett',
|
|
],
|
|
overridePackageName: 'ghcr.io/linuxserver/jackett',
|
|
},
|
|
{
|
|
description: "ADDED 2026-10-30. Jackett publishes ~daily (13 ls builds in 12 days, measured 2026-10-30) and every one is a `patch` under the regex above, i.e. auto-mergeable. Left on the global daily window that is a PR, a gate run and a jackett pod restart EVERY DAY for an indexer proxy whose individual builds nobody needs promptly. WEEKLY instead: Renovate proposes only the newest tag when the window opens, so nothing is skipped, it is just batched. The window is still inside the global 03:00-06:00 local one that .gitea/workflows/renovate.yaml's 08:15 UTC cron lands in. NOTE: catalog/jackett.yaml `update.cadence` still says daily (`0 4 * * *`); that field drove Flux image-automation, which is retired, and this rule is the effective cadence now.",
|
|
matchDepNames: [
|
|
'lscr.io/linuxserver/jackett',
|
|
],
|
|
schedule: [
|
|
'after 3am and before 6am on sunday',
|
|
],
|
|
},
|
|
{
|
|
description: 'ADDED 2026-10-30. Plex tags are `<major>.<minor>.<patch>.<build>-<commit hex>` (e.g. 1.43.4.10903-e5521bd8c). Docker versioning reads `-e5521bd8c` as a compatibility suffix that differs on EVERY release, so it would never find a successor. The trailing `$` excludes the per-arch variants and the floating `latest`/`public`/`beta`/`plexpass` tags. A version tag does NOT self-update inside the container: cont-init 50-plex-update exits early when /version.txt matches the installed package, so what is pinned is what runs.',
|
|
matchPackageNames: [
|
|
'plexinc/pms-docker',
|
|
],
|
|
versioning: 'regex:^(?<major>\\d+)\\.(?<minor>\\d+)\\.(?<patch>\\d+)\\.(?<build>\\d+)-[0-9a-f]+$',
|
|
},
|
|
{
|
|
description: 'ADDED 2026-10-30. A CNPG Postgres MAJOR is a migration (new cluster + import), not an image bump; the dry run proposed 16.4 -> 18.6. Minors stay (and are blast-radius, above).',
|
|
matchPackageNames: [
|
|
'ghcr.io/cloudnative-pg/postgresql',
|
|
],
|
|
allowedVersions: '<17.0.0',
|
|
},
|
|
{
|
|
description: "ADDED 2026-10-30. A HelmRelease semver RANGE is a policy ceiling, and Renovate does not respect it — it WIDENS it (dry run: external-dns `<1.22.0` -> `<1.24.0`, headlamp `<0.42.0` -> `<0.47.0`), and the default rule would then auto-merge that as a 'minor', silently letting Flux adopt every new minor. Raising a ceiling is deliberate per-component work: propose it, never auto-merge it.",
|
|
matchManagers: [
|
|
'flux',
|
|
],
|
|
matchCurrentValue: '/^[<>=~^]|\\.x$/',
|
|
automerge: false,
|
|
addLabels: [
|
|
'needs-review',
|
|
'range-widen',
|
|
],
|
|
},
|
|
{
|
|
description: 'ADDED 2026-10-30. velero-plugin-for-aws is pinned to a RELEASE CANDIDATE (v1.14.3-rc.2) and Renovate proposed NOTHING for it, although v1.14.3 and v1.14.4 are stable on Docker Hub. Docker versioning treats everything after the first `-` as a compatibility SUFFIX, so only other `*-rc.2` tags qualify and the RC is a fossil forever. Semver versioning reads it as a prerelease, and a prerelease upgrades to the stable release.',
|
|
matchPackageNames: [
|
|
'velero/velero-plugin-for-aws',
|
|
],
|
|
versioning: 'semver',
|
|
},
|
|
{
|
|
description: 'ADDED 2026-10-30. A python MINOR is a language-runtime release (the dry run proposed 3.13 -> 3.15 for the Keycloak reconcilers), and the gate does not execute those scripts. Review it; patches still auto-merge.',
|
|
matchPackageNames: [
|
|
'python',
|
|
],
|
|
matchUpdateTypes: [
|
|
'minor',
|
|
],
|
|
automerge: false,
|
|
addLabels: [
|
|
'needs-review',
|
|
],
|
|
},
|
|
{
|
|
description: 'ADDED 2026-10-30 (operator decision). The oauth2-proxy image, under the ONE depName every source of it emits: catalog/render.py OAUTH2_PROXY_IMAGE (custom regex manager), the rendered apps/wave-* sidecars and platform/crafty/deployment.yaml (kubernetes manager). WHY: since v7.13.0 oauth2-proxy validates REFRESHED OIDC sessions against the ACCESS token instead of the id_token (upstream CHANGELOG, v7.13.0), every sidecar sets --cookie-refresh, and that refresh path has never run against this Keycloak (the platform oauth2-proxy runs v7.15.5 WITHOUT --cookie-refresh). render-golden and render.py --selftest pass on the bump, but both check rendered text, not the binary, so a green gate is not evidence here.',
|
|
matchPackageNames: [
|
|
'quay.io/oauth2-proxy/oauth2-proxy',
|
|
],
|
|
automerge: false,
|
|
addLabels: [
|
|
'needs-review',
|
|
],
|
|
},
|
|
{
|
|
description: "ADDED 2026-10-30 (operator decision). Renovate's OWN version: the job `container.image` pin in .gitea/workflows/renovate.yaml (ghcr.io/renovatebot/renovate), and renovatebot/github-action should it ever be reintroduced (ci/lint.sh rejects it today). WHY: nothing re-validates renovate.json5 against the NEW version before it takes effect. The gate does not run renovate-config-validator, and this exact gap has bitten before: the action's default 39.x rejected this file (managerFilePatterns needs >= 40.2.0) and every run stopped at CONFIG_VALIDATION. Run the validator from the new tag, then merge.",
|
|
matchPackageNames: [
|
|
'ghcr.io/renovatebot/renovate',
|
|
'renovatebot/github-action',
|
|
],
|
|
automerge: false,
|
|
addLabels: [
|
|
'needs-review',
|
|
],
|
|
},
|
|
{
|
|
description: "ADDED 2026-10-30. MAJORS WAIT FOR A TICK ON THE DEPENDENCY DASHBOARD instead of opening a PR. They are never auto-merged anyway, and with prConcurrentLimit 5 the dry run's 10 majors would hold every PR slot open indefinitely, starving the patch/minor/digest PRs that can merge on green.",
|
|
matchUpdateTypes: [
|
|
'major',
|
|
],
|
|
dependencyDashboardApproval: true,
|
|
},
|
|
],
|
|
ignorePaths: [
|
|
'**/*.example',
|
|
'**/*.sops.yaml',
|
|
'conformance/config/**',
|
|
'conformance/incluster/**',
|
|
'conformance/lib/**',
|
|
'conformance/requirements/**',
|
|
'conformance/runs/**',
|
|
'conformance/tests/**',
|
|
'platform/image-automation/per-service/**',
|
|
'platform/kyverno/tests/**',
|
|
],
|
|
}
|