freesailarr: eight image updates, a trustedNetworks value for the arr apps, and one grouped Renovate PR from now on #121

Merged
jklein merged 2 commits from chore/freesailarr-renovate-group into main 2026-10-13 10:46:57 +02:00
Member

Collapses the eight open Renovate PRs for the freesailarr stack into one change, together with the grouping rule that stops them arriving eight at a time — and answers what two of those eight turned out to be carrying.

No issue exists for this; the maintainer set the scope directly, both halves in one PR. No Fixes/Part of keyword on purpose.

Replaces these eight PRs

PR Image Change Type
#102 docker.io/linuxserver/bazarr 1.6.0 → 1.6.2 patch
#103 docker.io/linuxserver/qbittorrent 5.2.3 → 5.2.4 patch
#104 docker.io/linuxserver/sonarr 4.0.19 → 4.0.20 patch
#105 ghcr.io/flaresolverr/flaresolverr v3.5.0 → v3.5.2 patch
#106 ghcr.io/recyclarr/recyclarr 8.7.1 → 8.7.3 patch
#107 docker.io/linuxserver/prowlarr 2.5.2 → 2.6.5 minor
#108 docker.io/linuxserver/radarr 6.3.0 → 6.4.4 minor
#111 ghcr.io/seerr-team/seerr v3.4.1 → v3.5.0 minor

They are deliberately not closed here. Renovate tidies up on its next run, and a hand-closed Renovate PR can suppress that exact version being offered again. Closing them is the maintainer's call after the merge.

Why they could not simply be merged one by one: each edits freesailarr/values.yaml and, through the postUpgradeTasks hook, freesailarr/Chart.yaml. The API reported mergeable: false for all eight — they rewrite the same version: line. All eight also 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 predate the Gateway-API rounds.

Untouched, as no update is pending for them: docker.io/qmcgaw/gluetun, docker.io/busybox (init) and docker.io/pcjones/mediathekarr — the only digest-pinned image, whose own customManagers block is not modified.

Why this is a major, not the patch it looked like

prowlarr 2.6.3 and radarr 6.4.3 are not plain bumps. Both removed the hard-coded trusted proxy networks and replaced them with a TrustedNetworks setting that defaults to empty. Read in the upstream source at the tags this chart pins, not from the release notes alone — src/NzbDrone.Host/Startup.cs at prowlarr v2.5.2.5491 carries

options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("10.0.0.0"), 8));
options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("172.16.0.0"), 12));
options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("192.168.0.0"), 16));
options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("fc00::"), 7));
options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("fe80::"), 10));

while v2.6.5.5623 has ForwardedHeadersConfigurator.Configure return before registering a single KnownNetwork whenever TrustedNetworks is empty — and ConfigFileProvider defaults it to string.Empty. Radarr 6.3.0 → 6.4.4 is the identical change. The chart serves both behind a routing object at a sub-path by default, so the consequence lands on the default deployment: the client address each app reports is the ingress controller's pod IP, the scheme reads as http behind TLS termination, and under Disabled for Local Addresses a proxied request stops counting as local and is asked to authenticate.

The release note reads "New: Add Trusted Networks setting", which looks purely additive — the removal of the five networks is visible only in the diff.

The new value — and what it does not promise

A <app>.trustedNetworks value for prowlarr, radarr and sonarr: comma-separated CIDRs, rendered as <APP>__SERVER__TRUSTEDNETWORKS and only when non-empty, since an empty variable cannot be told apart from an unset one by the app but looks handled to anyone reading the manifest. The env form was verified per app rather than inferred from the pattern: all three bind ServerOptions to <App>:Server next to a plain AddEnvironmentVariables(). A helper suppresses the chart's entry when the app's own env block already carries the variable — the reason the UrlBase helper already states, that two identically named variables in one container are not a defined state.

The default is empty, so this PR does not restore the behaviour from before the upgrade. It is not meant to. prowlarr and radarr lose the forwarded-header trust with this upgrade, and sonarr stays without it; the chart follows upstream's reason — trusting X-Forwarded-For from any private network is a spoofing vector — and makes the state visible instead of quietly reinstating it. NOTES.txt warns in exactly the combination that costs something (the app published through an Ingress or an HTTPRoute, and the value empty) and stays silent otherwise, so it is not a banner to learn to ignore.

sonarr is included although its own bump is only a patch, because it is already in the empty state: 4.0.19 — the version this chart shipped before — has no hard-coded networks either. That arrived in what the convention classes as a plain app-version bump, so nothing reviewed it and nothing documented it. Fixing two apps and knowingly leaving the third would repeat the same silent pattern.

AllowedHosts is deliberately not exposed. Its upstream default is string.Empty as well and the app then accepts any host, so there is nothing to repair. The trap is documented instead: if it is ever set, the app appends only localhost, 127.0.0.1, [::1] and its own hostname — neither the pod IP nor the in-cluster Service DNS name — and the kubelet addresses the probes to the pod IP, which an incomplete list answers with HTTP 400.

The grouping rule

All ten images now arrive as one PR per cycle (groupName: "freesailarr stack"), the first and only groupName in renovate.json5. It reverses, for this one chart, the stance recorded above the app-version managers — that the helm-values manager finds those ten images and proposes "one update per image". That sentence described what Renovate does, not what the chart wants.

It takes two packageRules, and that is not cosmetic: Renovate rejects the combination outright with packageRules cannot combine both matchUpdateTypes and separateMinorPatch — a config-validation error, found by running the dry-run instead of trusting the draft. Consistent, too, since separateMinorPatch is what decides the split a rule would then select on.

Measured with renovate 44, --platform=local --dry-run=full, real registries, against a checkout holding those eight updates:

Config Branches
group rule alone renovate/freesailarr-stack (the 3 minors) and renovate/patch-freesailarr-stack (the 5 patches)
both rules renovate/freesailarr-stack — all eight

The setting prefixes the branch rather than suffixing it, which is what makes the second name easy to miss in a branch list.

Four decisions, reasoned at the rules themselves: matched by file name rather than a list of image names, so no second copy of the chart's image set has to be kept current (an image added later would silently never join — a valid rule is not a matching rule); mediathekarr in the group, since its digest PR blocks the others identically — verified with a deliberately stale digest, because a current pin makes a working and a broken rule look identical; gluetun and busybox in too, as neither's role in the pod has anything to do with the blocking; majors out via matchUpdateTypes, so the stepwise app-major procedure and separateMultipleMajor keep working.

What the rule cannot show from here: Renovate runs as the shared instance outside this repository. The available evidence is a config that validates and a local lookup that groups as intended — not the behaviour of the next real run.

Render diff

helm template with the CI values, against origin/main: 1168 lines before and after, and the diff is exactly the eight image lines. The new value renders nothing until it is set.

Checked besides: helm lint --strict clean; the value drops out of the NOTES.txt warning once set and the env var then appears; no warning when nothing is published; no variable at all at default values; and no duplicate env entry when an operator sets the variable through the env block as well. Gateway mode was checked through the condition's own inputs, since helm install cannot render NOTES.txt offline here: the HTTPRoute renders, all three apps are routed and all three URLBASE variables are present, so both inputs to the warning hold in that mode too.

Documentation

README's "Dependency updates (Renovate)" gains "The one grouped chart", and the separateMinorPatch bullet now names its one exception. Wiki: Renovate (the grouped chart, and the same exception in the PR policy), freesailarr (the new value in the per-app table plus an operating note), and Lessons-Learned — a patch bump switched off a security default, and the convention exempts exactly that class from the wiki obligation, which is why nobody saw it.

Left open on purpose, named rather than proposed: whether that exemption should change. It is the mechanism that let the sonarr case through, and it is a decision of its own rather than an appendix to a version PR.

Collapses the eight open Renovate PRs for the `freesailarr` stack into one change, together with the grouping rule that stops them arriving eight at a time — and answers what two of those eight turned out to be carrying. No issue exists for this; the maintainer set the scope directly, both halves in one PR. No `Fixes`/`Part of` keyword on purpose. ## Replaces these eight PRs | PR | Image | Change | Type | | --- | --- | --- | --- | | #102 | `docker.io/linuxserver/bazarr` | `1.6.0 → 1.6.2` | patch | | #103 | `docker.io/linuxserver/qbittorrent` | `5.2.3 → 5.2.4` | patch | | #104 | `docker.io/linuxserver/sonarr` | `4.0.19 → 4.0.20` | patch | | #105 | `ghcr.io/flaresolverr/flaresolverr` | `v3.5.0 → v3.5.2` | patch | | #106 | `ghcr.io/recyclarr/recyclarr` | `8.7.1 → 8.7.3` | patch | | #107 | `docker.io/linuxserver/prowlarr` | `2.5.2 → 2.6.5` | **minor** | | #108 | `docker.io/linuxserver/radarr` | `6.3.0 → 6.4.4` | **minor** | | #111 | `ghcr.io/seerr-team/seerr` | `v3.4.1 → v3.5.0` | **minor** | **They are deliberately not closed here.** Renovate tidies up on its next run, and a hand-closed Renovate PR can suppress that exact version being offered again. Closing them is the maintainer's call after the merge. Why they could not simply be merged one by one: each edits `freesailarr/values.yaml` **and**, through the `postUpgradeTasks` hook, `freesailarr/Chart.yaml`. The API reported `mergeable: false` for all eight — they rewrite the same `version:` line. All eight also 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 predate the Gateway-API rounds. **Untouched**, as no update is pending for them: `docker.io/qmcgaw/gluetun`, `docker.io/busybox` (init) and `docker.io/pcjones/mediathekarr` — the only digest-pinned image, whose own `customManagers` block is not modified. ## Why this is a major, not the patch it looked like `prowlarr 2.6.3` and `radarr 6.4.3` are **not plain bumps**. Both removed the hard-coded trusted proxy networks and replaced them with a `TrustedNetworks` setting that defaults to empty. Read in the upstream source at the tags this chart pins, not from the release notes alone — `src/NzbDrone.Host/Startup.cs` at prowlarr `v2.5.2.5491` carries ```csharp options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("10.0.0.0"), 8)); options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("172.16.0.0"), 12)); options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("192.168.0.0"), 16)); options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("fc00::"), 7)); options.KnownNetworks.Add(new IPNetwork(IPAddress.Parse("fe80::"), 10)); ``` while `v2.6.5.5623` has `ForwardedHeadersConfigurator.Configure` return before registering a single `KnownNetwork` whenever `TrustedNetworks` is empty — and `ConfigFileProvider` defaults it to `string.Empty`. Radarr `6.3.0` → `6.4.4` is the identical change. The chart serves both behind a routing object at a sub-path by default, so the consequence lands on the default deployment: the client address each app reports is the ingress controller's pod IP, the scheme reads as `http` behind TLS termination, and under *Disabled for Local Addresses* a proxied request stops counting as local and is asked to authenticate. The release note reads *"New: Add Trusted Networks setting"*, which looks purely additive — the removal of the five networks is visible only in the diff. ## The new value — and what it does not promise A `<app>.trustedNetworks` value for **prowlarr, radarr and sonarr**: comma-separated CIDRs, rendered as `<APP>__SERVER__TRUSTEDNETWORKS` and **only when non-empty**, since an empty variable cannot be told apart from an unset one by the app but looks handled to anyone reading the manifest. The env form was verified per app rather than inferred from the pattern: all three bind `ServerOptions` to `<App>:Server` next to a plain `AddEnvironmentVariables()`. A helper suppresses the chart's entry when the app's own `env` block already carries the variable — the reason the `UrlBase` helper already states, that two identically named variables in one container are not a defined state. **The default is empty, so this PR does not restore the behaviour from before the upgrade.** It is not meant to. prowlarr and radarr lose the forwarded-header trust with this upgrade, and sonarr stays without it; the chart follows upstream's reason — trusting `X-Forwarded-For` from any private network is a spoofing vector — and makes the state visible instead of quietly reinstating it. `NOTES.txt` warns in exactly the combination that costs something (the app published through an Ingress **or** an HTTPRoute, and the value empty) and stays silent otherwise, so it is not a banner to learn to ignore. **sonarr is included although its own bump is only a patch**, because it is already in the empty state: `4.0.19` — the version this chart shipped before — has no hard-coded networks either. That arrived in what the convention classes as a plain app-version bump, so nothing reviewed it and nothing documented it. Fixing two apps and knowingly leaving the third would repeat the same silent pattern. **`AllowedHosts` is deliberately not exposed.** Its upstream default is `string.Empty` as well and the app then accepts any host, so there is nothing to repair. The trap is documented instead: if it is ever set, the app appends only `localhost`, `127.0.0.1`, `[::1]` and its own hostname — neither the pod IP nor the in-cluster Service DNS name — and the kubelet addresses the probes to the pod IP, which an incomplete list answers with HTTP 400. ## The grouping rule All ten images now arrive as one PR per cycle (`groupName: "freesailarr stack"`), the first and only `groupName` in `renovate.json5`. It reverses, for this one chart, the stance recorded above the app-version managers — that the `helm-values` manager finds those ten images and proposes "one update per image". That sentence described what Renovate does, not what the chart wants. **It takes two `packageRules`, and that is not cosmetic:** Renovate rejects the combination outright with `packageRules cannot combine both matchUpdateTypes and separateMinorPatch` — a config-validation error, found by running the dry-run instead of trusting the draft. Consistent, too, since `separateMinorPatch` is what *decides* the split a rule would then select on. Measured with `renovate 44`, `--platform=local --dry-run=full`, real registries, against a checkout holding those eight updates: | Config | Branches | | --- | --- | | group rule alone | `renovate/freesailarr-stack` (the 3 minors) **and** `renovate/patch-freesailarr-stack` (the 5 patches) | | both rules | `renovate/freesailarr-stack` — all eight | The setting **prefixes** the branch rather than suffixing it, which is what makes the second name easy to miss in a branch list. Four decisions, reasoned at the rules themselves: **matched by file name** rather than a list of image names, so no second copy of the chart's image set has to be kept current (an image added later would silently never join — a valid rule is not a matching rule); **mediathekarr in the group**, since its digest PR blocks the others identically — verified with a deliberately stale digest, because a current pin makes a working and a broken rule look identical; **gluetun and busybox in too**, as neither's role in the pod has anything to do with the blocking; **majors out** via `matchUpdateTypes`, so the stepwise app-major procedure and `separateMultipleMajor` keep working. **What the rule cannot show from here:** Renovate runs as the shared instance outside this repository. The available evidence is a config that validates and a local lookup that groups as intended — not the behaviour of the next real run. ## Render diff `helm template` with the CI values, against `origin/main`: 1168 lines before and after, and the diff is **exactly the eight image lines**. The new value renders nothing until it is set. Checked besides: `helm lint --strict` clean; the value drops out of the `NOTES.txt` warning once set and the env var then appears; no warning when nothing is published; no variable at all at default values; and no duplicate env entry when an operator sets the variable through the `env` block as well. Gateway mode was checked through the condition's own inputs, since `helm install` cannot render `NOTES.txt` offline here: the HTTPRoute renders, all three apps are routed and all three `URLBASE` variables are present, so both inputs to the warning hold in that mode too. ## Documentation README's "Dependency updates (Renovate)" gains "The one grouped chart", and the `separateMinorPatch` bullet now names its one exception. Wiki: `Renovate` (the grouped chart, and the same exception in the PR policy), `freesailarr` (the new value in the per-app table plus an operating note), and `Lessons-Learned` — a patch bump switched off a security default, and the convention exempts exactly that class from the wiki obligation, which is why nobody saw it. **Left open on purpose, named rather than proposed:** whether that exemption should change. It is the mechanism that let the sonarr case through, and it is a decision of its own rather than an appendix to a version PR.
jlab-bot added 2 commits 2026-10-13 00:02:32 +02:00
Collapses the eight open Renovate PRs for this stack into one change, and
answers what two of them turned out to carry.

The eight tags: qbittorrent 5.2.3 -> 5.2.4, bazarr 1.6.0 -> 1.6.2,
sonarr 4.0.19 -> 4.0.20, flaresolverr v3.5.0 -> v3.5.2,
recyclarr 8.7.1 -> 8.7.3, prowlarr 2.5.2 -> 2.6.5, radarr 6.3.0 -> 6.4.4,
seerr v3.4.1 -> v3.5.0. gluetun, the busybox init image and the
digest-pinned mediathekarr have no update pending and are untouched.

Major rather than patch, because prowlarr 2.6.3 and radarr 6.4.3 are not
plain bumps: they removed the hard-coded trusted proxy networks
(10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, fc00::/7, fe80::/10, read in
Startup.cs at the previously pinned tags) and replaced them with a
TrustedNetworks setting whose own default is string.Empty -
ForwardedHeadersConfigurator returns before registering a single
KnownNetwork while it is empty. Behind an in-cluster ingress controller
that means the forwarded headers are ignored: the client address the app
reports is the controller's pod IP, the scheme reads as http behind TLS
termination, and under "Disabled for Local Addresses" a proxied request
stops counting as local.

So a <app>.trustedNetworks value is added for prowlarr, radarr AND sonarr,
rendered as <APP>__SERVER__TRUSTEDNETWORKS and only when non-empty - an
empty variable cannot be told apart from an unset one by the app, but looks
handled to a reader of the manifest. The env form was verified per app
rather than inferred: all three bind ServerOptions to <App>:Server next to
a plain AddEnvironmentVariables(). A helper suppresses the chart's entry
when the app's own env block already carries the variable, for the reason
the UrlBase helper states - two identically named variables in one
container are not a defined state.

sonarr is included because it is already in the empty state: 4.0.19, the
version this chart shipped before, has no hard-coded networks either. It
arrived in what the convention classes as a plain app-version bump, so
nothing reviewed or documented it. Repairing two and knowingly leaving the
third would be the same silent pattern.

The default is empty, and this therefore does NOT restore the behaviour
from before the upgrade. That is deliberate and follows upstream's reason -
trusting X-Forwarded-For from any private network is a spoofing vector -
and it is made visible instead of reinstated: NOTES.txt warns in exactly
the combination that costs something, the app published through an Ingress
or an HTTPRoute and the value empty, and stays quiet otherwise.

AllowedHosts, added by the same upstream releases, is deliberately not
exposed. Its default is string.Empty as well and the app then accepts any
host, so there is nothing to repair; the trap is documented instead,
because if it is ever set the app appends only localhost, 127.0.0.1, [::1]
and its own hostname - neither the pod IP nor the Service DNS name - and
the kubelet probes the pod by IP, which an incomplete list answers with
HTTP 400.

At default values the rendered manifest changes in exactly eight lines, the
image tags; the new value renders nothing until it is set.
chore(renovate): group the freesailarr stack into one PR per cycle
Validate Helm Charts / Detect changed charts (pull_request) Successful in 6s
Validate Helm Charts / Resource quantities (pull_request) Successful in 30s
Validate Helm Charts / Selector match (pull_request) Successful in 17s
Validate Helm Charts / HTTPRoute match (pull_request) Successful in 14s
Validate Helm Charts / Build freesailarr (pull_request) Successful in 7s
Validate Helm Charts / Trivy config freesailarr (pull_request) Successful in 24s
Install-test / Install-test freesailarr (pull_request) Successful in 4m9s
Validate Helm Charts / Install-test (pull_request) Successful in 4m9s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 12s
Publish Freesailarr Helm Chart / Publish Freesailarr Helm Chart (push) Successful in 12s
SonarQube Analysis / SonarQube Analysis (push) Successful in 50s
d36112aaf6
The first groupName in this config, and a deliberate reversal for one chart
of the stance the file records above its app-version managers: that the
helm-values manager finds freesailarr's ten images and proposes one update
per image. That described what Renovate does, not what the chart wants. For
every other chart here one PR per image is already one PR per chart.

What it fixes, measured against the Gitea API on 2026-10-12: eight of the
ten images had an open PR at the same moment (#102, #103, #104, #105, #106,
#107, #108, #111). Each edits freesailarr/values.yaml and, through the
postUpgradeTasks hook, freesailarr/Chart.yaml - so the API reported
mergeable: false for all eight, since they rewrite the same version: line.
One can merge; the other seven then need a rebase each, and every rebase
restarts the most expensive install-test in this repo.

The second symptom is sharper: 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 it, which
is the point - nothing fails if the rebase does not happen first.

Two rules rather than one, and not for tidiness: renovate rejects the
combination outright with "packageRules cannot combine both
matchUpdateTypes and separateMinorPatch", a config-validation error. That
is consistent, since separateMinorPatch decides the split a rule would then
be selecting on. Without the second rule the group still arrives as two
PRs - verified by dry-run against a checkout holding those eight updates,
they split onto renovate/freesailarr-stack (the three minors) and
renovate/patch-freesailarr-stack (the five patches); note that the setting
prefixes the branch rather than suffixing it. With both rules, one branch
carries all eight.

Decisions, with their reasons in the rules themselves: matched by file name
so that no second list of image names has to be kept current (an image
added later would silently never join the group); mediathekarr included,
since its digest PR blocks the others identically - checked with a stale
digest, because a current pin makes a working and a broken rule look the
same; gluetun and busybox included, as neither's role has anything to do
with the blocking; and majors kept out via matchUpdateTypes, so the
stepwise app-major procedure and separateMultipleMajor keep working.

What it cannot show from here: Renovate runs as the shared instance outside
this repository, so the evidence available is a config that validates and a
local lookup that groups as intended - not the next real run.
jklein approved these changes 2026-10-13 10:46:42 +02:00
jklein merged commit d36112aaf6 into main 2026-10-13 10:46:57 +02:00
jklein deleted branch chore/freesailarr-renovate-group 2026-10-13 10:46:58 +02:00
Sign in to join this conversation.
No Reviewers
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: jlab-cluster/helm-charts#121