linuxserver/qbittorrent still carries 14.3.9 and 20.04.1 from earlier tagging schemes. Docker versioning sorts both above every real release, so Renovate offers them as major updates - PRs #152 and #153 are exactly those, and they would have to be closed by hand again after every lookup. They cost more than the noise. Renovate fills the major bucket once, so while a stray occupies it the genuine major is never offered at all: measured from currentValue 4.6.4, the unconstrained lookup returns 4.6.7 and 20.04.1, and never mentions 5.2.3. Both are excluded by name. A bound such as "<10" would reject a genuine major sooner or later, whereas two finished artifacts cannot grow and so cannot mask a release that does not exist yet. A shape-based filter was tried first and measured out: requiring a non-padded minor drops 20.04.1 and simply promotes 14.3.9 into its place. Verified with renovate 44.41.0 (dry-run=lookup) against the live registry: currentValue 5.2.1 -> [5.2.3 (patch)], both strays gone currentValue 4.6.4 -> [4.6.7 (patch), 5.2.3 (major)] The second fixture is the guard the nginx rule taught us to run - it proves the filter does not silently empty the update list. A full run over the real repository with the real config confirms it in place: qbittorrent offers 5.2.3 only, nginx is unchanged, and no docker dependency carries a skipReason or a warning.
355 lines
16 KiB
JSON
355 lines
16 KiB
JSON
{
|
|
$schema: "https://docs.renovatebot.com/renovate-schema.json",
|
|
|
|
// JSON5 on purpose: the per-chart managers below only make sense with the
|
|
// reasoning next to them. Renovate reads renovate.json5 natively — there must
|
|
// be no second config file (renovate.json, .github/renovate.json, ...) or
|
|
// Renovate aborts with "found multiple config files".
|
|
//
|
|
// Requires Renovate >= 41 (`managerFilePatterns`, `customType`). See
|
|
// README.md → "Dependency updates (Renovate)" for the whole picture,
|
|
// including the one-time `allowedCommands` change the self-hosted instance
|
|
// needs for the version bump to work.
|
|
|
|
extends: [
|
|
"config:recommended",
|
|
// Commit subjects in this repo are plain sentences, not conventional
|
|
// commits. Renovate's own merged PRs already look like that.
|
|
":semanticCommitsDisabled",
|
|
],
|
|
|
|
timezone: "Europe/Berlin",
|
|
labels: ["dependencies"],
|
|
|
|
// 20 charts plus the workflows: without a cap the first run after a quiet
|
|
// week opens a wall of PRs, and every chart PR spins up a k3d install-test.
|
|
prConcurrentLimit: 10,
|
|
prHourlyLimit: 4,
|
|
|
|
// template-chart is the scaffold. Its image is the literal placeholder
|
|
// ghcr.io/ORG/IMAGE and its appVersion is "xxx" — nothing to update, and the
|
|
// chart is never published.
|
|
ignorePaths: ["template-chart/**"],
|
|
|
|
// CLAUDE.md/README require app major jumps to be taken stepwise: first the
|
|
// last release of the old major line, then the new major. With this flag a
|
|
// v1 -> v3 jump arrives as two PRs (latest v2, then v3), which is exactly
|
|
// that sequence — merge the first, and the second rebases onto it.
|
|
separateMultipleMajor: true,
|
|
|
|
// Any change inside a chart directory must bump `version:` in its
|
|
// Chart.yaml, otherwise the `build-chart` job fails the PR and the publish
|
|
// on main would collide with an already published version. Renovate only
|
|
// rewrites `appVersion:` (or an image tag in values.yaml), so this hook
|
|
// recomputes `version:` afterwards.
|
|
//
|
|
// IMPORTANT: postUpgradeTasks only runs on self-hosted Renovate and only if
|
|
// the command is allow-listed in the *instance* config (not in this repo):
|
|
//
|
|
// allowedCommands: ["^ci/renovate-bump-chart\\.sh"]
|
|
//
|
|
// Without that entry Renovate skips the task silently and every chart PR
|
|
// stays red on the version-bump check.
|
|
postUpgradeTasks: {
|
|
commands: ["ci/renovate-bump-chart.sh {{{packageFileDir}}}"],
|
|
fileFilters: ["**/Chart.yaml"],
|
|
// "update" runs the script once per dependency update with the package
|
|
// file's directory, which is what the script needs to find the chart. The
|
|
// script derives everything from the base commit, so repeated runs on the
|
|
// same branch are idempotent.
|
|
executionMode: "update",
|
|
},
|
|
|
|
packageRules: [
|
|
{
|
|
description: [
|
|
"Install-test fixtures deliberately pin throwaway infrastructure to major lines",
|
|
"(postgres:16-alpine, redis:7-alpine, busybox:1.36). They are test scaffolding, not",
|
|
"shipped artifacts, and churning them only risks breaking the install-test.",
|
|
],
|
|
matchFileNames: ["ci/**"],
|
|
enabled: false,
|
|
},
|
|
{
|
|
description: [
|
|
"Patch, minor and digest updates merge themselves once the PR pipeline is green.",
|
|
"Minor is included because the sidecar images barely move in patch steps —",
|
|
"alpine 3.22 -> 3.24 and nginx 1.30.4 -> 1.31.4 are both minor, so a patch-only",
|
|
"rule would practically never fire. Majors stay manual.",
|
|
],
|
|
matchUpdateTypes: ["patch", "minor", "digest"],
|
|
automerge: true,
|
|
automergeType: "pr",
|
|
// Renovate waits for the checks itself instead of handing the PR to
|
|
// GitHub's auto-merge. GitHub would merge immediately when the branch has
|
|
// no required status checks configured — and validate-charts is not (yet)
|
|
// a required check.
|
|
platformAutomerge: false,
|
|
// A release that is hours old should not walk into main unattended; a
|
|
// chart publish follows the merge straight away.
|
|
minimumReleaseAge: "3 days",
|
|
},
|
|
{
|
|
description: [
|
|
"Chart app versions. Kept out of the automerge grouping decisions above only by label,",
|
|
"so it is visible at a glance which PRs republish a chart.",
|
|
],
|
|
matchDepTypes: ["app-version"],
|
|
addLabels: ["app-version"],
|
|
},
|
|
{
|
|
description: [
|
|
"nginx numbers its release branches by minor: even is the stable branch, odd is",
|
|
"mainline. The Synapse delegation nginx belongs on stable, so only even minors are",
|
|
"allowed — that is what PR #139 (1.30.4 -> 1.31.4) would otherwise have done,",
|
|
"quietly moving the chart onto mainline. Patch updates inside the stable branch",
|
|
"(1.30.4 -> 1.30.5) still come through and still automerge.",
|
|
],
|
|
matchDatasources: ["docker"],
|
|
matchPackageNames: ["nginx", "library/nginx", "docker.io/library/nginx"],
|
|
// Even minor: any minor whose last digit is even, so 1.30.x passes and
|
|
// 1.31.x does not. The trailing (-.+)? is load-bearing: #84 commissioned
|
|
// a check on moving this sidecar to 1.30.4-alpine (outcome open, to be
|
|
// done alongside the seccompProfile PR), and with a variant tag
|
|
// like 1.30.4-alpine docker versioning only ever offers candidates
|
|
// carrying the same suffix. Without the optional suffix, every one of
|
|
// them would fail this filter and nginx would silently stop receiving
|
|
// updates - no error, no warning, green dashboard. Verified: with the
|
|
// anchored form and currentValue 1.28.0-alpine, the lookup returns
|
|
// "updates": [] and "warnings": [].
|
|
allowedVersions: "/^\\d+\\.\\d*[02468]\\.\\d+(-.+)?$/",
|
|
},
|
|
{
|
|
description: [
|
|
"linuxserver/qbittorrent carries two historic tags from earlier tagging schemes,",
|
|
"14.3.9 and 20.04.1, which docker versioning sorts above every real release and",
|
|
"offers as major updates — PRs #152 and #153 are exactly those. Both are excluded",
|
|
"by name.",
|
|
],
|
|
matchDatasources: ["docker"],
|
|
matchPackageNames: [
|
|
"linuxserver/qbittorrent",
|
|
"docker.io/linuxserver/qbittorrent",
|
|
],
|
|
// Excluded by name rather than by a bound such as "<10": the strays are
|
|
// finished artifacts that cannot grow, so naming them can never block a
|
|
// release that does not exist yet, while any upper bound would sooner or
|
|
// later reject a genuine major. Shape-based filters were measured and
|
|
// rejected too: requiring a non-padded minor removes 20.04.1 and leaves
|
|
// 14.3.9 (verified, the lookup then offers 14.3.9 instead).
|
|
//
|
|
// Verified with renovate 44.41.0 (dry-run=lookup, real registry), two
|
|
// fixtures against the live tag list:
|
|
// currentValue 5.2.1 -> updates [5.2.3 (patch)]; both strays gone.
|
|
// currentValue 4.6.4 -> updates [4.6.7 (patch), 5.2.3 (major)].
|
|
// The second fixture is the guard the nginx rule taught us to run: it
|
|
// proves a genuine major still comes through instead of the filter
|
|
// silently emptying the update list. It also shows what the strays cost
|
|
// today — without the rule that same lookup offers 20.04.1 as the major
|
|
// candidate and never mentions the real 5.2.3, because Renovate fills
|
|
// the major bucket once.
|
|
allowedVersions: "!/^(14\\.3\\.9|20\\.04\\.1)$/",
|
|
},
|
|
],
|
|
|
|
customManagers: [
|
|
{
|
|
description: [
|
|
"Tool versions pinned as workflow env vars (TRIVY_VERSION). Annotate the line with",
|
|
"`# renovate: datasource=<ds> depName=<dep>` and Renovate keeps it current.",
|
|
],
|
|
customType: "regex",
|
|
managerFilePatterns: [".github/workflows/*.yml"],
|
|
matchStrings: [
|
|
"# renovate: datasource=(?<datasource>\\S+) depName=(?<depName>\\S+)(?: versioning=(?<versioning>\\S+))?\\s+\\w+: [\"']?(?<currentValue>[^\"'\\s]+)",
|
|
],
|
|
versioningTemplate: "{{#if versioning}}{{{versioning}}}{{else}}semver{{/if}}",
|
|
},
|
|
|
|
// --- Chart app versions -------------------------------------------------
|
|
//
|
|
// Every chart renders its main image as `<repo>:{{ .Chart.AppVersion }}`,
|
|
// so the repository lives in the template and the version in Chart.yaml.
|
|
// Renovate cannot join the two across files, which is why each chart gets
|
|
// its own manager that hard-codes the repository and reads `appVersion:`.
|
|
//
|
|
// Deliberately *not* solved with a `# renovate:` comment inside each
|
|
// Chart.yaml: that would touch all 20 chart directories and force 20
|
|
// version bumps plus 20 publishes for a pure comment change. Keeping the
|
|
// mapping here leaves the charts untouched.
|
|
//
|
|
// When adding a chart, add a block here — see README "Creating a new chart".
|
|
//
|
|
// Exception, deliberately no block: `freesailarr`. It bundles nine
|
|
// independently versioned upstreams, so none of them is "the" app version
|
|
// and no image tag comes from appVersion — all nine are pinned as
|
|
// image.repository/image.tag in its values.yaml, where the built-in
|
|
// helm-values manager already finds them and proposes one update per
|
|
// image. Adding a block here would either duplicate those updates or
|
|
// rewrite an appVersion that selects nothing. Its appVersion is a
|
|
// hand-maintained marker; see freesailarr/Chart.yaml.
|
|
|
|
{
|
|
description: "apache-tika: four-part version plus flavour suffix (3.3.1.0-full); the compatibility group keeps the chart on -full.",
|
|
customType: "regex",
|
|
managerFilePatterns: ["apache-tika/Chart.yaml"],
|
|
matchStrings: ["(?m)^appVersion:\\s*\"?(?<currentValue>[^\"\\s]+)\"?"],
|
|
depNameTemplate: "apache/tika",
|
|
datasourceTemplate: "docker",
|
|
depTypeTemplate: "app-version",
|
|
versioningTemplate: "regex:^(?<major>\\d+)\\.(?<minor>\\d+)\\.(?<patch>\\d+)\\.(?<build>\\d+)-(?<compatibility>full)$",
|
|
},
|
|
{
|
|
customType: "regex",
|
|
managerFilePatterns: ["calibre-web-automated/Chart.yaml"],
|
|
matchStrings: ["(?m)^appVersion:\\s*\"?(?<currentValue>[^\"\\s]+)\"?"],
|
|
depNameTemplate: "ghcr.io/crocodilestick/calibre-web-automated",
|
|
datasourceTemplate: "docker",
|
|
depTypeTemplate: "app-version",
|
|
},
|
|
{
|
|
customType: "regex",
|
|
managerFilePatterns: ["cyberchef/Chart.yaml"],
|
|
matchStrings: ["(?m)^appVersion:\\s*\"?(?<currentValue>[^\"\\s]+)\"?"],
|
|
depNameTemplate: "ghcr.io/gchq/cyberchef",
|
|
datasourceTemplate: "docker",
|
|
depTypeTemplate: "app-version",
|
|
},
|
|
{
|
|
customType: "regex",
|
|
managerFilePatterns: ["gotenberg/Chart.yaml"],
|
|
matchStrings: ["(?m)^appVersion:\\s*\"?(?<currentValue>[^\"\\s]+)\"?"],
|
|
depNameTemplate: "gotenberg/gotenberg",
|
|
datasourceTemplate: "docker",
|
|
depTypeTemplate: "app-version",
|
|
},
|
|
{
|
|
description: "homeassistant: CalVer tags (2026.8.3) sort correctly under docker versioning; the floating stable/beta/dev tags are skipped because they parse as no version at all.",
|
|
customType: "regex",
|
|
managerFilePatterns: ["homeassistant/Chart.yaml"],
|
|
matchStrings: ["(?m)^appVersion:\\s*\"?(?<currentValue>[^\"\\s]+)\"?"],
|
|
depNameTemplate: "ghcr.io/home-assistant/home-assistant",
|
|
datasourceTemplate: "docker",
|
|
depTypeTemplate: "app-version",
|
|
},
|
|
{
|
|
customType: "regex",
|
|
managerFilePatterns: ["matrix-synapse/Chart.yaml"],
|
|
matchStrings: ["(?m)^appVersion:\\s*\"?(?<currentValue>[^\"\\s]+)\"?"],
|
|
depNameTemplate: "ghcr.io/element-hq/synapse",
|
|
datasourceTemplate: "docker",
|
|
depTypeTemplate: "app-version",
|
|
},
|
|
{
|
|
customType: "regex",
|
|
managerFilePatterns: ["minecraft-router/Chart.yaml"],
|
|
matchStrings: ["(?m)^appVersion:\\s*\"?(?<currentValue>[^\"\\s]+)\"?"],
|
|
depNameTemplate: "itzg/mc-router",
|
|
datasourceTemplate: "docker",
|
|
depTypeTemplate: "app-version",
|
|
},
|
|
{
|
|
description: "minecraft-server: tags encode the JRE as a suffix (2026.8.2-java21); the compatibility group prevents a silent runtime switch.",
|
|
customType: "regex",
|
|
managerFilePatterns: ["minecraft-server/Chart.yaml"],
|
|
matchStrings: ["(?m)^appVersion:\\s*\"?(?<currentValue>[^\"\\s]+)\"?"],
|
|
depNameTemplate: "itzg/minecraft-server",
|
|
datasourceTemplate: "docker",
|
|
depTypeTemplate: "app-version",
|
|
versioningTemplate: "regex:^(?<major>\\d+)\\.(?<minor>\\d+)\\.(?<patch>\\d+)-(?<compatibility>java\\d+)$",
|
|
},
|
|
{
|
|
customType: "regex",
|
|
managerFilePatterns: ["outline/Chart.yaml"],
|
|
matchStrings: ["(?m)^appVersion:\\s*\"?(?<currentValue>[^\"\\s]+)\"?"],
|
|
depNameTemplate: "outlinewiki/outline",
|
|
datasourceTemplate: "docker",
|
|
depTypeTemplate: "app-version",
|
|
},
|
|
{
|
|
customType: "regex",
|
|
managerFilePatterns: ["paperless-ngx/Chart.yaml"],
|
|
matchStrings: ["(?m)^appVersion:\\s*\"?(?<currentValue>[^\"\\s]+)\"?"],
|
|
depNameTemplate: "ghcr.io/paperless-ngx/paperless-ngx",
|
|
datasourceTemplate: "docker",
|
|
depTypeTemplate: "app-version",
|
|
},
|
|
{
|
|
customType: "regex",
|
|
managerFilePatterns: ["patchmon/Chart.yaml"],
|
|
matchStrings: ["(?m)^appVersion:\\s*\"?(?<currentValue>[^\"\\s]+)\"?"],
|
|
depNameTemplate: "ghcr.io/patchmon/patchmon-server",
|
|
datasourceTemplate: "docker",
|
|
depTypeTemplate: "app-version",
|
|
},
|
|
{
|
|
customType: "regex",
|
|
managerFilePatterns: ["rallly/Chart.yaml"],
|
|
matchStrings: ["(?m)^appVersion:\\s*\"?(?<currentValue>[^\"\\s]+)\"?"],
|
|
depNameTemplate: "lukevella/rallly",
|
|
datasourceTemplate: "docker",
|
|
depTypeTemplate: "app-version",
|
|
},
|
|
{
|
|
description: "rss-bridge: date-based tags (2025-06-03). The bump script normalizes the dashes to dots for the +up suffix, as the versioning scheme requires.",
|
|
customType: "regex",
|
|
managerFilePatterns: ["rss-bridge/Chart.yaml"],
|
|
matchStrings: ["(?m)^appVersion:\\s*\"?(?<currentValue>[^\"\\s]+)\"?"],
|
|
depNameTemplate: "docker.io/rssbridge/rss-bridge",
|
|
datasourceTemplate: "docker",
|
|
depTypeTemplate: "app-version",
|
|
versioningTemplate: "regex:^(?<major>\\d{4})-(?<minor>\\d{2})-(?<patch>\\d{2})$",
|
|
},
|
|
{
|
|
customType: "regex",
|
|
managerFilePatterns: ["samba/Chart.yaml"],
|
|
matchStrings: ["(?m)^appVersion:\\s*\"?(?<currentValue>[^\"\\s]+)\"?"],
|
|
depNameTemplate: "ghcr.io/dockur/samba",
|
|
datasourceTemplate: "docker",
|
|
depTypeTemplate: "app-version",
|
|
},
|
|
{
|
|
customType: "regex",
|
|
managerFilePatterns: ["satisfactory/Chart.yaml"],
|
|
matchStrings: ["(?m)^appVersion:\\s*\"?(?<currentValue>[^\"\\s]+)\"?"],
|
|
depNameTemplate: "ghcr.io/wolveix/satisfactory-server",
|
|
datasourceTemplate: "docker",
|
|
depTypeTemplate: "app-version",
|
|
},
|
|
{
|
|
description: "teamspeak-server: the 6.0.0 line only ships beta tags (6.0.0-beta12.1). Loose versioning compares the numeric parts left to right, so beta12.1 sorts above beta9; semver would rank every beta as an equal prerelease.",
|
|
customType: "regex",
|
|
managerFilePatterns: ["teamspeak-server/Chart.yaml"],
|
|
matchStrings: ["(?m)^appVersion:\\s*\"?(?<currentValue>[^\"\\s]+)\"?"],
|
|
depNameTemplate: "teamspeaksystems/teamspeak6-server",
|
|
datasourceTemplate: "docker",
|
|
depTypeTemplate: "app-version",
|
|
versioningTemplate: "loose",
|
|
},
|
|
{
|
|
customType: "regex",
|
|
managerFilePatterns: ["urlaubsverwaltung/Chart.yaml"],
|
|
matchStrings: ["(?m)^appVersion:\\s*\"?(?<currentValue>[^\"\\s]+)\"?"],
|
|
depNameTemplate: "urlaubsverwaltung/urlaubsverwaltung",
|
|
datasourceTemplate: "docker",
|
|
depTypeTemplate: "app-version",
|
|
},
|
|
{
|
|
customType: "regex",
|
|
managerFilePatterns: ["voucher-vault/Chart.yaml"],
|
|
matchStrings: ["(?m)^appVersion:\\s*\"?(?<currentValue>[^\"\\s]+)\"?"],
|
|
depNameTemplate: "l4rm4nd/vouchervault",
|
|
datasourceTemplate: "docker",
|
|
depTypeTemplate: "app-version",
|
|
},
|
|
{
|
|
customType: "regex",
|
|
managerFilePatterns: ["zipline/Chart.yaml"],
|
|
matchStrings: ["(?m)^appVersion:\\s*\"?(?<currentValue>[^\"\\s]+)\"?"],
|
|
depNameTemplate: "ghcr.io/diced/zipline",
|
|
datasourceTemplate: "docker",
|
|
depTypeTemplate: "app-version",
|
|
},
|
|
],
|
|
}
|