freesailarr: eight image updates, a trustedNetworks value for the arr apps, and one grouped Renovate PR from now on #121
No files matched your search
@@ -255,7 +255,7 @@ What Renovate keeps current:
|
||||
| --- | --- |
|
||||
| Chart app versions (`appVersion:` in `Chart.yaml`) | One `customManagers` entry per chart, hard-coding the image repository |
|
||||
| Sidecar/init images in `values.yaml` (nginx, guacd, busybox, alpine) | Built-in `helm-values` manager |
|
||||
| **All** images of `freesailarr`, including its main one | Built-in `helm-values` manager — the chart pins every tag in `values.yaml` and has no app-version block. One exception: `mediathekarr` is pinned by **digest** (its tags carry no version) and has its own `customManagers` block |
|
||||
| **All** images of `freesailarr`, including its main one | Built-in `helm-values` manager — the chart pins every tag in `values.yaml` and has no app-version block. One exception: `mediathekarr` is pinned by **digest** (its tags carry no version) and has its own `customManagers` block. All ten arrive **grouped into one PR**, see "The one grouped chart" below |
|
||||
| GitHub Actions | Built-in `github-actions` manager |
|
||||
| Tool versions pinned as workflow env vars (`TRIVY_VERSION`) | `# renovate: datasource=… depName=…` comment above the line |
|
||||
|
||||
@@ -265,10 +265,29 @@ One rule those overrides share, learned from `searxng` and worth knowing before
|
||||
|
||||
A consequence of pinning every tag in `values.yaml`: `ci/renovate-bump-chart.sh` sees an unchanged `appVersion` and bumps the chart **patch**. That holds for all ten images alike, the digest-pinned one included — none of them is special — and it is the intended outcome, since `appVersion` here is the chart's own marker rather than a tracked upstream version.
|
||||
|
||||
**An open point this leaves, recorded rather than solved.** That automatic patch is also a classification, and the convention exempts a "plain app-version bump" from the wiki obligation — so an image update that quietly changes a *default* is the one case where nothing reviews it and nothing documents it. It has already happened once: the `sonarr` 4.0.19 bump carried upstream's removal of the hard-coded trusted proxy networks, and it reached `main` unremarked (see [Lessons learned](https://git.jk-dev.de/jlab-cluster/helm-charts/wiki/Lessons-Learned)). Whether the convention should change is a separate decision and deliberately not proposed here.
|
||||
|
||||
Two dependencies are constrained beyond their versioning. **nginx** (the Synapse delegation sidecar) is limited to **even minors** via `allowedVersions`. nginx numbers its release branches by minor — even is stable, odd is mainline — and the chart belongs on stable. Patch updates inside the stable branch still flow through normally. The constraint tolerates a variant suffix (`1.30.4-alpine`) on purpose: docker versioning only ever offers candidates carrying the current tag's suffix, so a suffix-blind constraint would silently freeze nginx the day the sidecar moves to a slimmer base image — a switch to `1.30.4-alpine` that #84 has commissioned a check on — with no error and no warning.
|
||||
|
||||
**linuxserver/qbittorrent** (freesailarr) excludes two tags by name, `14.3.9` and `20.04.1`. They are leftovers from earlier linuxserver tagging schemes, and docker versioning sorts both above every real release, so Renovate offers them as major updates — PRs #152 and #153 were exactly that. They are named rather than cut off with an upper bound like `<10`, because a bound would eventually reject a genuine major, while two finished artifacts cannot grow and therefore cannot hide a future release. The same guard as for nginx applies and was run: a lookup from `currentValue 4.6.4` still offers `5.2.3` as a **major**, so the filter does not silently empty the update list. That fixture also shows the cost of leaving it alone — without the rule the very same lookup offers `20.04.1` and never mentions the real `5.2.3`, since Renovate fills the major bucket only once.
|
||||
|
||||
### The one grouped chart
|
||||
|
||||
`freesailarr` is the only chart whose updates are **grouped**: all ten of its images arrive as one PR per cycle (`groupName: "freesailarr stack"`), where every other chart in this repo gets one PR per image. For the others that is already the same thing — one chart, one image — so this is a rule about a ten-image chart rather than a change of policy. It is also the only `groupName` in `renovate.json5`, and it reverses what the comment above the app-version managers describes: the `helm-values` manager finding those ten images and proposing "one update per image". That sentence described what Renovate does, not what the chart wants.
|
||||
|
||||
**What it fixes, measured on 2026-10-12.** Eight of the ten images had an open Renovate PR simultaneously (#102 bazarr, #103 qbittorrent, #104 sonarr, #105 flaresolverr, #106 recyclarr, #107 prowlarr, #108 radarr, #111 seerr). Each edits `freesailarr/values.yaml` and — through the `postUpgradeTasks` hook — `freesailarr/Chart.yaml`, so the API reported `mergeable: false` for all eight: they rewrite the same `version:` line. One could merge; the other seven then needed a rebase each, and every rebase restarts the most expensive install-test here (a real Wireguard tunnel, ten containers). A sharper second symptom came with it: all eight carried `version: 3.1.1+up1.0.0 → 3.1.2+up1.0.0` while `main` already stood at `3.3.0+up1.0.0`, because the hook derives everything from the **base commit** and those branches predated the Gateway-API rounds. A rebase repairs that, which is precisely the problem — nothing *fails* if the rebase does not happen first, so the repair depends on the bot reaching the branch before a human reaches the merge button. Grouped, there is one branch, one `version:` computed from one base, one publish and one install-test run.
|
||||
|
||||
**Four decisions worth keeping** (the reasoning sits next to the rules in `renovate.json5`):
|
||||
|
||||
- **Matched by file name, not by a list of images.** What causes the collision is "touches `freesailarr/Chart.yaml`", true of every dependency Renovate finds in that `values.yaml` regardless of manager or datasource. A `matchPackageNames` list would be a second place to keep the chart's image set current, and its failure mode is silence — an image added later never joins the group, with no error (#158: a valid rule is not a matching rule).
|
||||
- **`mediathekarr` is in the group** although it comes from its own `customManagers` block: its digest PR edits the same two files and blocks the others identically. `digest` is in `matchUpdateTypes` for exactly that, and its own block is untouched — a group decides which *branch* an update lands on, not how it is found.
|
||||
- **`gluetun` and the `busybox` init image are in too.** Neither "gluetun is the tunnel" nor "busybox is only an init container" has anything to do with the blocking, and a grouped PR hides nothing: the review still sees every changed line, just not eight times over.
|
||||
- **Majors stay out**, via `matchUpdateTypes: ["patch", "minor", "digest"]`. An app major must be taken stepwise in its own PR, and `separateMultipleMajor` splits a multi-major jump to produce that sequence; a group throwing a major in with seven patches would undo both.
|
||||
|
||||
**It takes two `packageRules`, and that is not cosmetic.** Renovate refuses `matchUpdateTypes` and `separateMinorPatch` in one rule — `packageRules cannot combine both matchUpdateTypes and separateMinorPatch`, a config-validation error — which is consistent, since `separateMinorPatch` is what *decides* the split a rule would then be selecting on. So one rule groups the non-major update types and a second sets `separateMinorPatch: false` for the same file. Without that second rule the group still arrives as **two** PRs: verified by dry-run (`--platform=local --dry-run=full`, real registries) against a checkout holding those eight updates, they landed on `renovate/freesailarr-stack` (the three minors) and `renovate/patch-freesailarr-stack` (the five patches) — note that `separateMinorPatch` **prefixes** the branch rather than suffixing it, which makes the second name easy to miss in a branch list. With both rules, one branch carries all eight. The global `separateMinorPatch: true` keeps its reason for every other chart; "one red install-test must not block unrelated updates" simply does not apply within a single chart, where the updates share a pod, a chart version and a publish.
|
||||
|
||||
What this cannot show from inside the repo: Renovate runs as the shared instance outside it, so a valid config and a correct local lookup are the evidence available — not the behaviour of the next real run.
|
||||
|
||||
Not managed: `ci/**` (install-test fixtures pin throwaway infrastructure to major lines on purpose) and `template-chart/**` (placeholder image). A consequence worth knowing: `ci/matrix-synapse/fixtures.yaml` pins a Synapse image that Renovate will not follow — bump it by hand when the chart moves a major.
|
||||
|
||||
### The version-bump hook
|
||||
@@ -308,7 +327,7 @@ The config is aligned with the one in `cluster-config`, because the same `jlab-r
|
||||
|
||||
- **Automerge is global** (`automerge: true`, `automergeType: "pr"`, `platformAutomerge: true`) — every update type, **majors included**. The safeguard is not the absence of automerge, it is the branch protection: `main` requires a **review** and requires `validate-charts` to pass. `platformAutomerge` arms the PR on the forge, and Gitea merges it when *both* conditions are met, so an armed PR without a review simply sits there. This is the setting the older `platformAutomerge: false` comment in `renovate.json5` named as the better one "once required checks exist on `main`" — they now do. Same reasoning as `cluster-config`, which retired its dashboard-approval step for it.
|
||||
- **`minimumReleaseAge: "3 days"`** for `patch`, `minor` and `digest` updates. Renovate does not even open the PR before then. A merge here is never just a merge — a chart publish follows it straight away — so a release that gets yanked hours after it appeared must not reach the registry. Majors carry no age gate: they are held at the review, not by a delay.
|
||||
- **Majors** arrive one line at a time (`separateMultipleMajor`): a v1 → v3 jump produces a PR for the latest v2 first and then one for v3 — the stepwise procedure the versioning section requires. `separateMinorPatch: true` likewise splits minor and patch into separate PRs, so one red install-test does not block unrelated updates.
|
||||
- **Majors** arrive one line at a time (`separateMultipleMajor`): a v1 → v3 jump produces a PR for the latest v2 first and then one for v3 — the stepwise procedure the versioning section requires. `separateMinorPatch: true` likewise splits minor and patch into separate PRs, so one red install-test does not block unrelated updates. **It has one exception**, `freesailarr`, where it is overridden to `false` — see "The one grouped chart" below for why that separation is wrong inside a single chart.
|
||||
- **No PR caps** (`prConcurrentLimit: 100`, `prHourlyLimit: 0`), as in `cluster-config`. The known cost, written down rather than discovered: the first run after a quiet week can open many PRs at once, and every chart PR starts a k3d install-test — which on a Gitea runner queues inside one job rather than fanning out (see "Where this repo lives"). That queue is the accepted trade for not having updates trickle in four per hour.
|
||||
- **`config:best-practices`** is in `extends`. Two of its members matter here: `helpers:pinGitHubActionDigests` moves the workflow files from `actions/checkout@v7` to SHA pins (wanted), and `docker:pinDigests` is switched off again for `custom.regex` and `helm-values` — an `appVersion:` is a tag by construction (`<repo>:{{ .Chart.AppVersion }}`, and the `+up` suffix is built from the same string), so a digest has nowhere to go. Sidecar digests in `values.yaml` *could* be pinned; that is a separate decision, since each digest change would be a chart patch plus a publish.
|
||||
- The manually dispatched `change-app-version.yml` still exists for one-off bumps, but the normal path for app updates is now a Renovate PR.
|
||||
|
||||
+116
@@ -229,6 +229,122 @@
|
||||
// the major bucket once.
|
||||
allowedVersions: "!/^(14\\.3\\.9|20\\.04\\.1)$/",
|
||||
},
|
||||
{
|
||||
description: [
|
||||
"The freesailarr stack arrives as ONE grouped PR per cycle instead of one PR per image.",
|
||||
"This is the only groupName in this file and a deliberate reversal of the stance written",
|
||||
"above the app-version managers, which says the built-in helm-values manager finds the ten",
|
||||
"images in that chart's values.yaml and 'proposes one update per image'. That sentence",
|
||||
"described what Renovate does; it was never an argument that one PR per image is what this",
|
||||
"chart wants. For every other chart in this repo it still is — one chart, one image, one",
|
||||
"PR is already the grouping — and that is why this rule is scoped to one file rather than",
|
||||
"turning into a repo-wide policy.",
|
||||
"",
|
||||
"WHAT CHANGED, measured on 2026-10-12 against the Gitea API: eight of this chart's ten",
|
||||
"images had an open Renovate PR at the same moment — #102 bazarr, #103 qbittorrent, #104",
|
||||
"sonarr, #105 flaresolverr, #106 recyclarr, #107 prowlarr, #108 radarr, #111 seerr. Each",
|
||||
"one edits freesailarr/values.yaml and, through the postUpgradeTasks hook above, also",
|
||||
"freesailarr/Chart.yaml. The API reported `mergeable: false` for all eight, because the",
|
||||
"`version:` line they rewrite is the same line in all eight. One of them can merge; the",
|
||||
"other seven then need a rebase round each, and every rebase restarts the most expensive",
|
||||
"install-test in this repo — a real Wireguard tunnel and ten containers in one pod.",
|
||||
"",
|
||||
"The second symptom is the sharper one. All eight carried",
|
||||
"`version: 3.1.1+up1.0.0 -> 3.1.2+up1.0.0` while main already stood at 3.3.0+up1.0.0:",
|
||||
"the hook derives everything from the BASE COMMIT (see its comment above), and these",
|
||||
"branches were cut before the Gateway-API rounds landed. Merging one unrebased would have",
|
||||
"moved the chart version backwards by two minors and republished the chart under a version",
|
||||
"the registry already has. A rebase repairs it — the point is that nothing FAILS when the",
|
||||
"rebase does not happen first, so the repair depends on the bot reaching the branch before",
|
||||
"a human reaches the merge button.",
|
||||
"",
|
||||
"WHY GROUPING FIXES BOTH: one branch, one `version:` bump computed once from one base, one",
|
||||
"publish, one install-test run for the whole stack. Nothing is left to serialize, so",
|
||||
"nothing is left to rebase.",
|
||||
"",
|
||||
"MATCHED BY FILE, NOT BY A LIST OF IMAGE NAMES. What creates the collision is 'touches",
|
||||
"freesailarr/Chart.yaml', and that is true of every dependency Renovate finds in that",
|
||||
"chart's values.yaml, whatever its manager, datasource or role in the pod. A",
|
||||
"matchPackageNames list would be a second place to keep the chart's image set current, and",
|
||||
"its failure mode is silence: an image added later simply never joins the group, with no",
|
||||
"error and no warning (#158 — a valid rule is not a matching rule). matchFileNames needs",
|
||||
"no maintenance to stay complete.",
|
||||
"",
|
||||
"Consequences of that choice, each one intended and not a side effect:",
|
||||
" - mediathekarr IS in the group, even though it comes from its own customManagers block",
|
||||
" further down rather than from helm-values. Its digest PR edits the same two files and",
|
||||
" blocks the other seven exactly as they block each other, so leaving it beside the",
|
||||
" group would keep a smaller copy of the problem alive. Its own block is untouched; a",
|
||||
" group decides which BRANCH an update lands on, not how it is found. `digest` is in",
|
||||
" matchUpdateTypes precisely so this one is covered. Verified the way that block's own",
|
||||
" comment demands, because while the pin is current a working rule and a broken one look",
|
||||
" identical: with the real digest the run produces no mediathekarr update at all, so it",
|
||||
" proves nothing; with the digest set to sha256:0000... the digest update appears ON THE",
|
||||
" GROUP BRANCH next to the other eight, not on renovate/docker.io-pcjones-mediathekarr-",
|
||||
" beta as it would without this rule.",
|
||||
" - gluetun and the busybox init image are in the group too, against the instinct to hold",
|
||||
" them apart because gluetun is the tunnel every other container's network namespace",
|
||||
" depends on and busybox is 'only' an init container. Neither property has anything to",
|
||||
" do with the blocking, and a grouped PR hides nothing: the review still sees every",
|
||||
" changed line, it just no longer sees them eight times. (The busybox pinned in",
|
||||
" ci/<chart>/ is a different thing and stays unmanaged — see the ci/** rule at the top.)",
|
||||
"",
|
||||
"MAJORS STAY OUT, which is what matchUpdateTypes is doing here. CLAUDE.md and README",
|
||||
"require an app major to be taken stepwise, in its own PR, and separateMultipleMajor above",
|
||||
"splits a multi-major jump into one PR per major line to produce exactly that sequence. A",
|
||||
"group that threw a major in with seven patches would undo both, and a major is the update",
|
||||
"where the review most needs one image at a time. Majors therefore keep arriving",
|
||||
"individually and keep their deliberately missing age gate.",
|
||||
"",
|
||||
"A groupName ALONE DOES NOT DO THE JOB, which is why the rule directly below exists and",
|
||||
"is not a stylistic split. The global `separateMinorPatch: true` at the top of this file",
|
||||
"keeps splitting minor from patch INSIDE the group. Measured (renovate 44.x,",
|
||||
"--platform=local --dry-run=full, real registries, 2026-10-12, against a checkout at the",
|
||||
"eight pending updates): with the group rule and nothing else, the eight land on TWO",
|
||||
"branches —",
|
||||
" renovate/freesailarr-stack prowlarr 2.6.5, radarr 6.4.4, seerr v3.5.0 (minor)",
|
||||
" renovate/patch-freesailarr-stack bazarr, qbittorrent, sonarr, flaresolverr, recyclarr",
|
||||
"— and note the shape: separateMinorPatch prefixes the branch, it does not suffix it, so",
|
||||
"the second name is easy to miss when scanning a branch list. Two PRs editing the same",
|
||||
"`version:` line is the original problem at a quarter of the size, so the override is",
|
||||
"required for the result this rule is for. With it, the same run puts all eight on one",
|
||||
"branch.",
|
||||
"",
|
||||
"And the override CANNOT live in this rule: renovate rejects the combination outright with",
|
||||
" packageRules[n]: packageRules cannot combine both matchUpdateTypes and",
|
||||
" separateMinorPatch",
|
||||
"which is a config-validation error, not a warning — found by running the dry-run instead",
|
||||
"of trusting the draft. It is consistent, too: separateMinorPatch is what DECIDES the",
|
||||
"updateType split, so a rule may not both perform the split and select on its outcome.",
|
||||
"Hence two rules over the same matchFileNames, one selecting updateTypes to group and one",
|
||||
"setting the split. Keep them together and keep them in this order.",
|
||||
"",
|
||||
"WHAT THIS RULE CANNOT DO: it takes effect on the next run of the shared instance, and",
|
||||
"that instance is outside this repository. What is verifiable from here is that the config",
|
||||
"validates and that the lookup groups as intended; that the bot then behaves that way is",
|
||||
"not provable from this side (see README, 'Renovate on Gitea').",
|
||||
],
|
||||
matchFileNames: ["freesailarr/values.yaml"],
|
||||
matchUpdateTypes: ["patch", "minor", "digest"],
|
||||
groupName: "freesailarr stack",
|
||||
groupSlug: "freesailarr-stack",
|
||||
},
|
||||
{
|
||||
description: [
|
||||
"The other half of the freesailarr grouping directly above — read that block first, the",
|
||||
"whole argument is there. Separate only because Renovate refuses matchUpdateTypes and",
|
||||
"separateMinorPatch in one rule ('packageRules cannot combine both matchUpdateTypes and",
|
||||
"separateMinorPatch', a validation error). Without this, the group still arrives as two",
|
||||
"PRs, one of them on the easily missed renovate/patch-freesailarr-stack.",
|
||||
"",
|
||||
"It deliberately carries NO matchUpdateTypes, so it also applies to a major — which costs",
|
||||
"nothing: separateMinorPatch only ever decides whether minor and patch share a branch, and",
|
||||
"a major is in neither bucket. Majors stay single PRs because the rule above does not",
|
||||
"group them, not because of anything here.",
|
||||
],
|
||||
matchFileNames: ["freesailarr/values.yaml"],
|
||||
separateMinorPatch: false,
|
||||
},
|
||||
],
|
||||
|
||||
customManagers: [
|
||||
|
||||
Reference in new issue
Block a user