Group 2 of the #114 sweep: the charts whose behaviour hangs off an nginx annotation. calibre-web-automated, outline, paperless-ngx and zipline carry proxy-body-size; freesailarr carries rewrite-target. Out of scope and untouched: rss-bridge (frozen, #51) and group 3.
Each chart gets the form the pilot fixed and group 1 repeated: httpRoute off by default, a template rendering only an HTTPRoute with parentRefs, render aborts on both modes at once (naming both value paths) and on an empty parentRefs, empty hostnames meaning [ingress.domain], a schema entry closed against unknown keys with an open parentRef definition, and a NOTES.txt that names the active mode and what it costs.
Minor, additive, defaults unchanged — helm template with each chart's CI values is byte-identical to origin/main for all five.
The upload limit is documented, not rebuilt
Decision 2 of the issue, and the Gateway branch of each NOTES.txt says it in that chart's own terms rather than in one copied sentence:
Chart
value
what the Gateway branch says
calibre-web-automated
cwa.maxUploadFileSize
this is an e-book library; a book above the Gateway's default is rejected before CWA sees a byte, and nothing says a chart value was ignored
outline
ingress.maxUploadFileSize
the ceiling is the smaller of outline.maxUploadSize and whatever the Gateway enforces, and the chart can only name the first
paperless-ngx
paperless.files.maxUploadFileSize
caps the web-UI half only; the consume directory passes no proxy and has no limit in any mode
zipline
config.maxUploadFileSize
this is an upload service, and the limit was the one that actually bit — far below the app's pinned UPLOADER_ADMIN_LIMIT=500mb
freesailarr sets no body-size annotation and needs none, but ingress.annotations is the only lever an operator has for one (qBittorrent takes torrent-file uploads), so its Gateway branch says an annotation set there is gone. That is the corrected sieve from group 1 — needs one, not sets one — applied to this group.
Interaction with #12, named and deliberately not resolved
outline ships outline.maxUploadSize: '262144000' (~250 MB) against ingress.maxUploadFileSize: 25m — a factor of ten, smaller wins, which is #12. In Gateway mode the ingress half disappears, so the effective ceiling becomes the Gateway's: neither of the two numbers the chart documents is necessarily the one that decides. That moves#12 rather than fixing it, and fixing it here would mean rendering a policy object — exactly the portability this sweep buys. Stated in the template header, in values.yaml, in the schema and in the Gateway branch of NOTES.txt. #12 is untouched by this PR.
Four findings that were not in the plan
1. freesailarr read ingress.enabled outside its Ingress template — the sure class, and this one was live.freesailarr.sharedIngressApps was gated on ingress.enabled, and that list is read by the <APP>__SERVER__URLBASE environment variables as well as by the routing objects. In Gateway mode ingress.enabled is false by construction, so the list would have come out empty while the route served radarr, sonarr and prowlarr under /radarr, /sonarr, /prowlarr: UrlBase unset, the apps answering at the sub-path with assets and API calls linked at the host root, a broken UI behind a Ready pod. The helper's two guards (seerr/mediathekarr only at /, no duplicate paths on the shared host) would have gone unenforced in that mode for the same reason.
Fixed with one helper, freesailarr.published = ingress.enabled OR httpRoute.enabled — the same shape as sure.publishedOverTls, and identical to ingress.enabled while httpRoute.enabled is false, which is why this stays Minor.
The other four charts were checked on the same pattern and on the related one (something derived from ingress.domain or ingress.className). Findings per chart are in the "what was checked" table below.
2. paperless-ngx was the last chart in the repository rendering its Ingress unconditionally — it needed an ingress.enabled switch, defaulting to true. Not uniformity: without a second value path there is nothing for the mutual-exclusion abort to name, and the Ingress together with its unconditionalrequired() on clusterIssuer would have kept rendering in Gateway mode — a value demanded for something the chart does not render, which is issue #114's Folge 1 and README guideline 12. The default keeps the render byte-identical. This is the one scope addition in the PR; say so if you would rather have had it separately.
3. paperless-ngx's PAPERLESS_URL had no required() of its own. It relied on the Ingress template's, which stops firing the moment the Ingress is switchable. With httpRoute.hostnames set and ingress.domain empty it would have rendered https:// and refused every login POST as a CSRF failure. It now carries its own, naming what the value is for — unchanged output, since the value was already mandatory in practice.
4. calibre-web-automated claimed, in values.yaml and in its schema, that ingress.domain is read nowhere but in the Ingress template. The HTTPRoute reads it, so the claim holds only while both modes are off. Corrected rather than left standing — the same correction garage needed in group 1.
freesailarr: the Gateway API is less machinery here, not a translation
rewrite-target applies per Ingress object. qBittorrent has no base path, so its prefix has to be stripped while its neighbours must not be — which forced a second Ingress object on the shared host, and that object then had to
repeat the first object's TLS secret name, so neither depends on an assumption about whose TLS configuration nginx applies to which paths,
omit the cert-manager annotation so only one Certificate is requested — conditionally, for the case where no other object renders at all, and
match an ImplementationSpecificregex path (<path>(/|$)(.*)) whose capture group the rewrite refers to.
A URLRewrite filter applies per rule. All four disappear: one route, one rule per app, the filter on qBittorrent's rule, a plain PathPrefix:
It is also the only chart withouthttpRoute.hostnames: it has no ingress.domain either (the documented exception), and a chart-global hostname list could not say which of the ten apps it belonged to — so the hostnames stay in ingress.host and <app>.ingress.host, which is where this chart's single source of truth already is. Both ingress shapes work unchanged: one HTTPRoute per app with its own host, plus exactly one shared-host route with one rule per app.
What was checked, per chart
Chart
reads ingress.enabled outside the Ingress template?
ingress.domain outside it?
result
calibre-web-automated
no — NOTES.txt only
no, and that claim was written down and is now false
claim corrected in values + schema; domain needed again in Gateway mode via the route's own required()
outline
no — NOTES.txt only
yes, URL, with its own required()
domain mandatory in every mode, already correct; outline.forceHttps is its own value and is not derived from ingress.enabled — checked, no sure-class defect
paperless-ngx
the value did not exist
yes, PAPERLESS_URL, without a required()
switch added; required() added — findings 2 and 3
zipline
no — NOTES.txt only
yes, CORE_DEFAULT_DOMAIN, with its own required()
domain mandatory in every mode, already correct
freesailarr
yes, in _helpers.tpl → sharedIngressApps → UrlBase env vars
no ingress.domain at all
finding 1
Verification
Run locally, all five charts:
ci/helm-dependencies.sh paperless-ngx first — the valkey subchart, or every helm command exits 1.
helm lint --strict at defaults and with CI values plus the Gateway overlay — clean (freesailarr's [INFO] icon is recommended is pre-existing on main).
helm template against both modes; helm package for all five.
An empty helm template diff against origin/main with unchanged values, five times. One was not empty at first: a # comment added next to PAPERLESS_URL is part of the rendered manifest. Moved into a non-rendered {{/* */}} block, with the reason in the comment — the same cause and the same fix as in group 1.
ci/run-httproute-match-test.sh over all charts: 36 assertions held over 15 charts, 0 failed (group 1 left it at 20 over 10). freesailarr contributes 8 — its own-host route plus five rules of the shared route. ci/run-quantity-render-test.sh (28 exercised, 0 failed) and ci/run-selector-match-test.sh (5 selectors, 0 failed) both clean.
Schema in both directions: the overlay is accepted; httpRoute.bogus is rejected (additional properties 'bogus' not allowed), a parentRef without name is rejected (missing property 'name'), and httpRoute.hostnames is rejected on freesailarr, which deliberately has no such key.
Fail paths exercised, not assumed: both modes at once on all five (paperless and freesailarr naming the true default explicitly), empty parentRefs, Gateway mode without a hostname (calibre aborts in the route, zipline in its StatefulSet first — which is the point), and httpRoute.hostnames set with ingress.domain empty on paperless, which the new required() catches.
The NOTES.txt branch where neither mode is on renders for all five — it is not covered by the template diff, because helm template does not emit notes.
What is not proven, by construction
That traffic flows. No Gateway controller runs in CI and none will, so no route ever reaches Accepted: True. What the pipeline can show: the object is schema-valid (install-test, real CRDs — paid by cyberchef) and it names a Service and a port the same render creates (the render test).
Nothing was run in a cluster for this PR: no k3d, no Docker, no Trivy, no kubectl apply.
The Gateway branch of NOTES.txt is executed but never read by any stage. Verified both halves: a deliberate error in NOTES.txt makes helm template exit 1, so the render test with the overlay does catch a broken Gateway branch — but helm template does not print notes, and the install-test installs in Ingress mode, so the text is asserted by nothing and displayed nowhere in CI. Reviewing it is reviewing it by reading.
Also in this PR
README's HTTPRoute-test section carried a stale figure: it still documented the pilot's 4 assertions over 2 charts although group 1 had taken it to 20 over 10. Corrected together with the new number.
Wiki: the five chart pages plus Chart-Style-Guide and CI-CD.
What remains of #114 after this: group 3 (homeassistant, keycloak, bulwark, matrix-synapse, stalwart, ytdlp-web-player) and rss-bridge, which is frozen — #114 cannot be closed while #51 is open.
Group 2 of the #114 sweep: the charts whose behaviour hangs off an nginx annotation. `calibre-web-automated`, `outline`, `paperless-ngx` and `zipline` carry `proxy-body-size`; `freesailarr` carries `rewrite-target`. Out of scope and untouched: `rss-bridge` (frozen, #51) and group 3.
Each chart gets the form the pilot fixed and group 1 repeated: `httpRoute` off by default, a template rendering **only** an `HTTPRoute` with `parentRefs`, render aborts on both modes at once (naming both value paths) and on an empty `parentRefs`, empty `hostnames` meaning `[ingress.domain]`, a schema entry closed against unknown keys with an open `parentRef` definition, and a `NOTES.txt` that names the active mode and what it costs.
**Minor, additive, defaults unchanged** — `helm template` with each chart's CI values is byte-identical to `origin/main` for all five.
## The upload limit is documented, not rebuilt
Decision 2 of the issue, and the Gateway branch of each `NOTES.txt` says it in that chart's own terms rather than in one copied sentence:
| Chart | value | what the Gateway branch says |
| --- | --- | --- |
| `calibre-web-automated` | `cwa.maxUploadFileSize` | this is an e-book library; a book above the Gateway's default is rejected before CWA sees a byte, and nothing says a chart value was ignored |
| `outline` | `ingress.maxUploadFileSize` | the ceiling is the smaller of `outline.maxUploadSize` and **whatever the Gateway enforces**, and the chart can only name the first |
| `paperless-ngx` | `paperless.files.maxUploadFileSize` | caps the **web-UI** half only; the consume directory passes no proxy and has no limit in any mode |
| `zipline` | `config.maxUploadFileSize` | this is an upload service, and the limit was the one that actually bit — far below the app's pinned `UPLOADER_ADMIN_LIMIT=500mb` |
`freesailarr` sets no body-size annotation and needs none, but `ingress.annotations` is the only lever an operator has for one (qBittorrent takes torrent-file uploads), so its Gateway branch says an annotation set there is gone. That is the corrected sieve from group 1 — *needs* one, not *sets* one — applied to this group.
## Interaction with #12, named and deliberately not resolved
`outline` ships `outline.maxUploadSize: '262144000'` (~250 MB) against `ingress.maxUploadFileSize: 25m` — a factor of ten, smaller wins, which is #12. **In Gateway mode the ingress half disappears**, so the effective ceiling becomes the Gateway's: neither of the two numbers the chart documents is necessarily the one that decides. That *moves* #12 rather than fixing it, and fixing it here would mean rendering a policy object — exactly the portability this sweep buys. Stated in the template header, in `values.yaml`, in the schema and in the Gateway branch of `NOTES.txt`. **#12 is untouched by this PR.**
## Four findings that were not in the plan
**1. `freesailarr` read `ingress.enabled` outside its Ingress template — the `sure` class, and this one was live.** `freesailarr.sharedIngressApps` was gated on `ingress.enabled`, and that list is read by the `<APP>__SERVER__URLBASE` environment variables as well as by the routing objects. In Gateway mode `ingress.enabled` is `false` by construction, so the list would have come out **empty** while the route served radarr, sonarr and prowlarr under `/radarr`, `/sonarr`, `/prowlarr`: UrlBase unset, the apps answering at the sub-path with assets and API calls linked at the host root, a broken UI behind a `Ready` pod. The helper's two guards (seerr/mediathekarr only at `/`, no duplicate paths on the shared host) would have gone unenforced in that mode for the same reason.
Measured rather than argued:
```
origin/main, ingress.enabled=false -> 0 URLBASE variables rendered
this branch, Gateway mode (overlay) -> PROWLARR__SERVER__URLBASE=/prowlarr
RADARR__SERVER__URLBASE=/radarr
SONARR__SERVER__URLBASE=/sonarr
```
Fixed with one helper, `freesailarr.published` = `ingress.enabled OR httpRoute.enabled` — the same shape as `sure.publishedOverTls`, and identical to `ingress.enabled` while `httpRoute.enabled` is `false`, which is why this stays Minor.
The other four charts were checked on the same pattern and on the related one (something derived from `ingress.domain` or `ingress.className`). Findings per chart are in the "what was checked" table below.
**2. `paperless-ngx` was the last chart in the repository rendering its Ingress unconditionally — it needed an `ingress.enabled` switch, defaulting to `true`.** Not uniformity: without a second value path there is nothing for the mutual-exclusion abort to name, and the Ingress together with its **unconditional** `required()` on `clusterIssuer` would have kept rendering in Gateway mode — a value demanded for something the chart does not render, which is issue #114's *Folge 1* and README guideline 12. The default keeps the render byte-identical. **This is the one scope addition in the PR; say so if you would rather have had it separately.**
**3. `paperless-ngx`'s `PAPERLESS_URL` had no `required()` of its own.** It relied on the Ingress template's, which stops firing the moment the Ingress is switchable. With `httpRoute.hostnames` set and `ingress.domain` empty it would have rendered `https://` and refused every login POST as a CSRF failure. It now carries its own, naming what the value is for — unchanged output, since the value was already mandatory in practice.
**4. `calibre-web-automated` claimed, in `values.yaml` and in its schema, that `ingress.domain` is read nowhere but in the Ingress template.** The HTTPRoute reads it, so the claim holds only while both modes are off. Corrected rather than left standing — the same correction `garage` needed in group 1.
## `freesailarr`: the Gateway API is less machinery here, not a translation
`rewrite-target` applies per Ingress **object**. qBittorrent has no base path, so its prefix has to be stripped while its neighbours must not be — which forced a **second Ingress object** on the shared host, and that object then had to
- repeat the first object's TLS secret name, so neither depends on an assumption about whose TLS configuration nginx applies to which paths,
- **omit** the cert-manager annotation so only one `Certificate` is requested — conditionally, for the case where no other object renders at all, and
- match an `ImplementationSpecific` **regex** path (`<path>(/|$)(.*)`) whose capture group the rewrite refers to.
A `URLRewrite` filter applies per **rule**. All four disappear: one route, one rule per app, the filter on qBittorrent's rule, a plain `PathPrefix`:
```yaml
- matches:
- path: { type: PathPrefix, value: "/qbittorrent" }
filters:
- type: URLRewrite
urlRewrite:
path: { type: ReplacePrefixMatch, replacePrefixMatch: / }
backendRefs:
- name: ci-freesailarr-qbittorrent-service
port: 8080
```
It is also the only chart **without** `httpRoute.hostnames`: it has no `ingress.domain` either (the documented exception), and a chart-global hostname list could not say which of the ten apps it belonged to — so the hostnames stay in `ingress.host` and `<app>.ingress.host`, which is where this chart's single source of truth already is. Both ingress shapes work unchanged: one `HTTPRoute` per app with its own host, plus exactly one shared-host route with one rule per app.
## What was checked, per chart
| Chart | reads `ingress.enabled` outside the Ingress template? | `ingress.domain` outside it? | result |
| --- | --- | --- | --- |
| `calibre-web-automated` | no — `NOTES.txt` only | **no**, and that claim was written down and is now false | claim corrected in values + schema; `domain` needed again in Gateway mode via the route's own `required()` |
| `outline` | no — `NOTES.txt` only | yes, `URL`, with its own `required()` | `domain` mandatory in every mode, already correct; `outline.forceHttps` is its own value and is **not** derived from `ingress.enabled` — checked, no `sure`-class defect |
| `paperless-ngx` | the value did not exist | yes, `PAPERLESS_URL`, **without** a `required()` | switch added; `required()` added — findings 2 and 3 |
| `zipline` | no — `NOTES.txt` only | yes, `CORE_DEFAULT_DOMAIN`, with its own `required()` | `domain` mandatory in every mode, already correct |
| `freesailarr` | **yes**, in `_helpers.tpl` → `sharedIngressApps` → UrlBase env vars | no `ingress.domain` at all | finding 1 |
## Verification
Run locally, all five charts:
- `ci/helm-dependencies.sh paperless-ngx` first — the valkey subchart, or every helm command exits 1.
- `helm lint --strict` at defaults **and** with CI values plus the Gateway overlay — clean (`freesailarr`'s `[INFO] icon is recommended` is pre-existing on `main`).
- `helm template` against both modes; `helm package` for all five.
- **An empty `helm template` diff against `origin/main` with unchanged values, five times.** One was not empty at first: a `#` comment added next to `PAPERLESS_URL` is part of the rendered manifest. Moved into a non-rendered `{{/* */}}` block, with the reason in the comment — the same cause and the same fix as in group 1.
- `ci/run-httproute-match-test.sh` over all charts: **36 assertions held over 15 charts, 0 failed** (group 1 left it at 20 over 10). `freesailarr` contributes 8 — its own-host route plus five rules of the shared route. `ci/run-quantity-render-test.sh` (28 exercised, 0 failed) and `ci/run-selector-match-test.sh` (5 selectors, 0 failed) both clean.
- Schema in both directions: the overlay is accepted; `httpRoute.bogus` is rejected (`additional properties 'bogus' not allowed`), a `parentRef` without `name` is rejected (`missing property 'name'`), and `httpRoute.hostnames` is rejected on `freesailarr`, which deliberately has no such key.
- Fail paths exercised, not assumed: both modes at once on all five (paperless and freesailarr naming the `true` default explicitly), empty `parentRefs`, Gateway mode without a hostname (`calibre` aborts in the route, `zipline` in its StatefulSet first — which is the point), and `httpRoute.hostnames` set with `ingress.domain` empty on paperless, which the new `required()` catches.
- The `NOTES.txt` branch where **neither** mode is on renders for all five — it is not covered by the template diff, because `helm template` does not emit notes.
## What is not proven, by construction
**That traffic flows.** No Gateway controller runs in CI and none will, so no route ever reaches `Accepted: True`. What the pipeline can show: the object is schema-valid (install-test, real CRDs — paid by `cyberchef`) and it names a Service and a port the same render creates (the render test).
**Nothing was run in a cluster for this PR**: no k3d, no Docker, no Trivy, no `kubectl apply`.
**The Gateway branch of `NOTES.txt` is executed but never read by any stage.** Verified both halves: a deliberate error in `NOTES.txt` makes `helm template` exit 1, so the render test with the overlay does catch a broken Gateway branch — but `helm template` does not print notes, and the install-test installs in Ingress mode, so the *text* is asserted by nothing and displayed nowhere in CI. Reviewing it is reviewing it by reading.
## Also in this PR
README's HTTPRoute-test section carried a **stale figure**: it still documented the pilot's 4 assertions over 2 charts although group 1 had taken it to 20 over 10. Corrected together with the new number.
Wiki: the five chart pages plus `Chart-Style-Guide` and `CI-CD`.
What remains of #114 after this: group 3 (`homeassistant`, `keycloak`, `bulwark`, `matrix-synapse`, `stalwart`, `ytdlp-web-player`) and `rss-bridge`, which is frozen — **#114 cannot be closed while #51 is open.**
Part of #114
Group 2 of the #114 sweep: the charts whose behaviour hangs off an nginx
annotation - calibre-web-automated, outline, paperless-ngx, zipline
(proxy-body-size) and freesailarr (rewrite-target).
Each gets the form the pilot (cyberchef, PR #115) fixed and group 1 (PR #116)
repeated: an httpRoute block off by default, a template rendering ONLY an
HTTPRoute with parentRefs - no Gateway, no gatewayClassName, no Certificate, no
implementation-specific policy object - render aborts on both modes at once
(naming both value paths), on an empty parentRefs and on a missing hostname,
empty hostnames meaning [ingress.domain], a schema entry closed against unknown
keys with an open parentRef definition, and a NOTES.txt that names the active
mode and what the mode costs.
Minor, additive, defaults unchanged: `helm template` with each chart's CI values
is byte-identical to origin/main for all five.
The upload limit is DOCUMENTED, not rebuilt (decision 2 of the issue). The
Gateway API knows no body size; a policy object would bind the chart to one
implementation. So the Gateway branch of NOTES.txt says, per chart and in the
chart's own terms, that the value is NOT ENFORCED and names where to set it: an
e-book library (calibre), an upload service (zipline), the web-UI half of the
document intake while the consume directory keeps no limit at all (paperless),
and the open #12 pair (outline).
Four findings that were not in the plan, each argued in the file it changes:
freesailarr gated freesailarr.sharedIngressApps on ingress.enabled, and that
list is read by the <APP>__SERVER__URLBASE variables as well as by the routing
objects. In Gateway mode it would have come out EMPTY while the route served
radarr, sonarr and prowlarr under their prefixes: UrlBase unset, assets and API
calls linked at the host root, broken UI behind a Ready pod. The helper's two
guards (seerr/mediathekarr only at "/", no duplicate paths) would have gone
unenforced there too. Fixed with freesailarr.published (ingress.enabled OR
httpRoute.enabled) - the same shape as sure.publishedOverTls in group 1, and
identical to ingress.enabled while httpRoute.enabled is false. Measured: on
origin/main with ingress.enabled: false the render emits ZERO URLBASE
variables; with this change in Gateway mode it emits three.
paperless-ngx was the last chart in the repository rendering its Ingress
UNCONDITIONALLY. Without an ingress.enabled switch there is no second value
path for the mutual-exclusion abort to name, and the Ingress plus its
unconditional required() on clusterIssuer would have kept rendering in Gateway
mode - a value demanded for nothing, which is issue #114's "Folge 1" and
README guideline 12. The switch was added, defaulting to true, so the render is
unchanged.
paperless-ngx PAPERLESS_URL had no required() of its own: it relied on the
Ingress template's, which stops firing the moment the Ingress is switchable.
With httpRoute.hostnames set and ingress.domain empty it would have rendered
"https://" and refused every login POST as a CSRF failure. It now carries its
own required(), naming what the value is for.
calibre-web-automated claimed, in values.yaml and in its schema, that
ingress.domain is read nowhere but in the Ingress template. The HTTPRoute reads
it, so the claim now holds only while both modes are off. Corrected rather than
left standing - the same correction garage needed in group 1.
freesailarr is where the Gateway API is LESS machinery than what it replaces,
not a translation of it. rewrite-target applies per Ingress OBJECT, so
qBittorrent (no base path, prefix must be stripped) needed a second Ingress on
the shared host, which then had to repeat the first object's TLS secret name,
omit the cert-manager annotation conditionally so only one Certificate is
requested, and match an ImplementationSpecific regex path for the rewrite's
capture group. A URLRewrite filter applies per RULE: one route, one rule per
app, the filter on qBittorrent's rule, a plain PathPrefix. All four crutches
gone. It is also the only chart without httpRoute.hostnames - it has no
ingress.domain either, and a chart-global hostname list could not say which of
the ten apps it belonged to, so the hostnames stay in ingress.host and
<app>.ingress.host.
CI: one render overlay per chart under ci/httproute/, so the assertion is not
vacuum-green - ci/run-httproute-match-test.sh goes from 20 held assertions over
10 charts to 36 over 15. freesailarr's overlay moves bazarr onto a host of its
own, so the shared-host route AND the per-app route are both rendered; without
that the per-app branch would be exercised by nothing. The install-test fixtures
stay in Ingress mode: the install runs one configuration per chart, cyberchef
already pays that price against the real Gateway-API CRDs, and for paperless-ngx
that fixture is the only install of the valkey-subchart path anywhere.
README also carries a stale figure fixed here: the HTTPRoute test was still
documented at the pilot's 4 assertions over 2 charts, although group 1 had taken
it to 20 over 10.
Not proven, by construction: that traffic flows. No Gateway controller runs in
CI. Not run locally either: k3d, Docker, Trivy. The Gateway branch of NOTES.txt
is EXECUTED by the render test (a template error in it fails the job, verified)
but its text is printed by no pipeline stage - helm template does not emit
notes, and the install-test installs in Ingress mode.
Part of #114
CI is green: all 20 jobs of run 742 succeeded — Detect changed charts, the four Build jobs with their version-bump checks, Resource quantities, Selector match, HTTPRoute match, the four Trivy config jobs, and all five install-tests (calibre-web-automated, freesailarr, outline, paperless-ngx, zipline).
The HTTPRoute match job's own tail (job 2402), which is the figure README and the wiki now carry:
That matches the local run exactly, which it should — the job is a pure render over every chart with no cluster involved. Group 1 left it at 20 assertions over 10 charts; the five new overlays add 16, eight of them from freesailarr alone (its own-host route plus the five rules of the shared route, qBittorrent's URLRewrite rule included).
not exercised is the honest half of that table. The fifteen charts listed there render no HTTPRoute at all: six of them are group 3, one is the frozen rss-bridge, and the rest have no Ingress to convert. Nothing in this PR claims coverage for them.
What the green install-tests do and do not say. They say the five charts install into a k3d cluster in Ingress mode and every workload becomes Ready — freesailarr including the real Wireguard tunnel and all ten containers, paperless-ngx including the bundled Valkey. They say nothing about the Gateway mode: no route is installed in any of them, because the install runs one configuration per chart and the Ingress was the better one to keep here. The route's schema validity is covered by cyberchef's fixture against the real CRDs; that it points somewhere by the render test above; that traffic flows by nothing, and by construction — no Gateway controller runs in CI.
CI is green: **all 20 jobs of run 742 succeeded** — `Detect changed charts`, the four `Build` jobs with their version-bump checks, `Resource quantities`, `Selector match`, `HTTPRoute match`, the four `Trivy config` jobs, and all five install-tests (`calibre-web-automated`, `freesailarr`, `outline`, `paperless-ngx`, `zipline`).
The `HTTPRoute match` job's own tail (job 2402), which is the figure README and the wiki now carry:
```
checked: 15 - bentopdf calibre-web-automated cyberchef freesailarr garage outline
paperless-ngx patchmon rallly searxng sure template-chart
urlaubsverwaltung voucher-vault zipline
not exercised: 15 - apache-tika bulwark gotenberg homeassistant keycloak matrix-synapse
minecraft-router minecraft-server rss-bridge samba satisfactory
stalwart teamspeak-server valkey ytdlp-web-player
failed: 0
assertions held: 36
```
That matches the local run exactly, which it should — the job is a pure render over every chart with no cluster involved. Group 1 left it at 20 assertions over 10 charts; the five new overlays add 16, eight of them from `freesailarr` alone (its own-host route plus the five rules of the shared route, qBittorrent's `URLRewrite` rule included).
**`not exercised` is the honest half of that table.** The fifteen charts listed there render no `HTTPRoute` at all: six of them are group 3, one is the frozen `rss-bridge`, and the rest have no Ingress to convert. Nothing in this PR claims coverage for them.
**What the green install-tests do and do not say.** They say the five charts install into a k3d cluster in **Ingress** mode and every workload becomes Ready — `freesailarr` including the real Wireguard tunnel and all ten containers, `paperless-ngx` including the bundled Valkey. They say nothing about the Gateway mode: no route is installed in any of them, because the install runs one configuration per chart and the Ingress was the better one to keep here. The route's schema validity is covered by `cyberchef`'s fixture against the real CRDs; that it *points somewhere* by the render test above; that traffic *flows* by nothing, and by construction — no Gateway controller runs in CI.
Group 2's sweep paragraph said "seven charts plus rss-bridge" while rss-bridge
is one of those seven. Six charts in group 3 plus the frozen one is seven, which
is also what the twelve before this PR was counting.
Part of #114
jklein
merged commit 471ea5ba5d into main2026-10-12 21:16:22 +02:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Group 2 of the #114 sweep: the charts whose behaviour hangs off an nginx annotation.
calibre-web-automated,outline,paperless-ngxandziplinecarryproxy-body-size;freesailarrcarriesrewrite-target. Out of scope and untouched:rss-bridge(frozen, #51) and group 3.Each chart gets the form the pilot fixed and group 1 repeated:
httpRouteoff by default, a template rendering only anHTTPRoutewithparentRefs, render aborts on both modes at once (naming both value paths) and on an emptyparentRefs, emptyhostnamesmeaning[ingress.domain], a schema entry closed against unknown keys with an openparentRefdefinition, and aNOTES.txtthat names the active mode and what it costs.Minor, additive, defaults unchanged —
helm templatewith each chart's CI values is byte-identical toorigin/mainfor all five.The upload limit is documented, not rebuilt
Decision 2 of the issue, and the Gateway branch of each
NOTES.txtsays it in that chart's own terms rather than in one copied sentence:calibre-web-automatedcwa.maxUploadFileSizeoutlineingress.maxUploadFileSizeoutline.maxUploadSizeand whatever the Gateway enforces, and the chart can only name the firstpaperless-ngxpaperless.files.maxUploadFileSizeziplineconfig.maxUploadFileSizeUPLOADER_ADMIN_LIMIT=500mbfreesailarrsets no body-size annotation and needs none, butingress.annotationsis the only lever an operator has for one (qBittorrent takes torrent-file uploads), so its Gateway branch says an annotation set there is gone. That is the corrected sieve from group 1 — needs one, not sets one — applied to this group.Interaction with #12, named and deliberately not resolved
outlineshipsoutline.maxUploadSize: '262144000'(~250 MB) againstingress.maxUploadFileSize: 25m— a factor of ten, smaller wins, which is #12. In Gateway mode the ingress half disappears, so the effective ceiling becomes the Gateway's: neither of the two numbers the chart documents is necessarily the one that decides. That moves #12 rather than fixing it, and fixing it here would mean rendering a policy object — exactly the portability this sweep buys. Stated in the template header, invalues.yaml, in the schema and in the Gateway branch ofNOTES.txt. #12 is untouched by this PR.Four findings that were not in the plan
1.
freesailarrreadingress.enabledoutside its Ingress template — thesureclass, and this one was live.freesailarr.sharedIngressAppswas gated oningress.enabled, and that list is read by the<APP>__SERVER__URLBASEenvironment variables as well as by the routing objects. In Gateway modeingress.enabledisfalseby construction, so the list would have come out empty while the route served radarr, sonarr and prowlarr under/radarr,/sonarr,/prowlarr: UrlBase unset, the apps answering at the sub-path with assets and API calls linked at the host root, a broken UI behind aReadypod. The helper's two guards (seerr/mediathekarr only at/, no duplicate paths on the shared host) would have gone unenforced in that mode for the same reason.Measured rather than argued:
Fixed with one helper,
freesailarr.published=ingress.enabled OR httpRoute.enabled— the same shape assure.publishedOverTls, and identical toingress.enabledwhilehttpRoute.enabledisfalse, which is why this stays Minor.The other four charts were checked on the same pattern and on the related one (something derived from
ingress.domainoringress.className). Findings per chart are in the "what was checked" table below.2.
paperless-ngxwas the last chart in the repository rendering its Ingress unconditionally — it needed aningress.enabledswitch, defaulting totrue. Not uniformity: without a second value path there is nothing for the mutual-exclusion abort to name, and the Ingress together with its unconditionalrequired()onclusterIssuerwould have kept rendering in Gateway mode — a value demanded for something the chart does not render, which is issue #114's Folge 1 and README guideline 12. The default keeps the render byte-identical. This is the one scope addition in the PR; say so if you would rather have had it separately.3.
paperless-ngx'sPAPERLESS_URLhad norequired()of its own. It relied on the Ingress template's, which stops firing the moment the Ingress is switchable. WithhttpRoute.hostnamesset andingress.domainempty it would have renderedhttps://and refused every login POST as a CSRF failure. It now carries its own, naming what the value is for — unchanged output, since the value was already mandatory in practice.4.
calibre-web-automatedclaimed, invalues.yamland in its schema, thatingress.domainis read nowhere but in the Ingress template. The HTTPRoute reads it, so the claim holds only while both modes are off. Corrected rather than left standing — the same correctiongarageneeded in group 1.freesailarr: the Gateway API is less machinery here, not a translationrewrite-targetapplies per Ingress object. qBittorrent has no base path, so its prefix has to be stripped while its neighbours must not be — which forced a second Ingress object on the shared host, and that object then had toCertificateis requested — conditionally, for the case where no other object renders at all, andImplementationSpecificregex path (<path>(/|$)(.*)) whose capture group the rewrite refers to.A
URLRewritefilter applies per rule. All four disappear: one route, one rule per app, the filter on qBittorrent's rule, a plainPathPrefix:It is also the only chart without
httpRoute.hostnames: it has noingress.domaineither (the documented exception), and a chart-global hostname list could not say which of the ten apps it belonged to — so the hostnames stay iningress.hostand<app>.ingress.host, which is where this chart's single source of truth already is. Both ingress shapes work unchanged: oneHTTPRouteper app with its own host, plus exactly one shared-host route with one rule per app.What was checked, per chart
ingress.enabledoutside the Ingress template?ingress.domainoutside it?calibre-web-automatedNOTES.txtonlydomainneeded again in Gateway mode via the route's ownrequired()outlineNOTES.txtonlyURL, with its ownrequired()domainmandatory in every mode, already correct;outline.forceHttpsis its own value and is not derived fromingress.enabled— checked, nosure-class defectpaperless-ngxPAPERLESS_URL, without arequired()required()added — findings 2 and 3ziplineNOTES.txtonlyCORE_DEFAULT_DOMAIN, with its ownrequired()domainmandatory in every mode, already correctfreesailarr_helpers.tpl→sharedIngressApps→ UrlBase env varsingress.domainat allVerification
Run locally, all five charts:
ci/helm-dependencies.sh paperless-ngxfirst — the valkey subchart, or every helm command exits 1.helm lint --strictat defaults and with CI values plus the Gateway overlay — clean (freesailarr's[INFO] icon is recommendedis pre-existing onmain).helm templateagainst both modes;helm packagefor all five.helm templatediff againstorigin/mainwith unchanged values, five times. One was not empty at first: a#comment added next toPAPERLESS_URLis part of the rendered manifest. Moved into a non-rendered{{/* */}}block, with the reason in the comment — the same cause and the same fix as in group 1.ci/run-httproute-match-test.shover all charts: 36 assertions held over 15 charts, 0 failed (group 1 left it at 20 over 10).freesailarrcontributes 8 — its own-host route plus five rules of the shared route.ci/run-quantity-render-test.sh(28 exercised, 0 failed) andci/run-selector-match-test.sh(5 selectors, 0 failed) both clean.httpRoute.bogusis rejected (additional properties 'bogus' not allowed), aparentRefwithoutnameis rejected (missing property 'name'), andhttpRoute.hostnamesis rejected onfreesailarr, which deliberately has no such key.truedefault explicitly), emptyparentRefs, Gateway mode without a hostname (calibreaborts in the route,ziplinein its StatefulSet first — which is the point), andhttpRoute.hostnamesset withingress.domainempty on paperless, which the newrequired()catches.NOTES.txtbranch where neither mode is on renders for all five — it is not covered by the template diff, becausehelm templatedoes not emit notes.What is not proven, by construction
That traffic flows. No Gateway controller runs in CI and none will, so no route ever reaches
Accepted: True. What the pipeline can show: the object is schema-valid (install-test, real CRDs — paid bycyberchef) and it names a Service and a port the same render creates (the render test).Nothing was run in a cluster for this PR: no k3d, no Docker, no Trivy, no
kubectl apply.The Gateway branch of
NOTES.txtis executed but never read by any stage. Verified both halves: a deliberate error inNOTES.txtmakeshelm templateexit 1, so the render test with the overlay does catch a broken Gateway branch — buthelm templatedoes not print notes, and the install-test installs in Ingress mode, so the text is asserted by nothing and displayed nowhere in CI. Reviewing it is reviewing it by reading.Also in this PR
README's HTTPRoute-test section carried a stale figure: it still documented the pilot's 4 assertions over 2 charts although group 1 had taken it to 20 over 10. Corrected together with the new number.
Wiki: the five chart pages plus
Chart-Style-GuideandCI-CD.What remains of #114 after this: group 3 (
homeassistant,keycloak,bulwark,matrix-synapse,stalwart,ytdlp-web-player) andrss-bridge, which is frozen — #114 cannot be closed while #51 is open.Part of #114
CI is green: all 20 jobs of run 742 succeeded —
Detect changed charts, the fourBuildjobs with their version-bump checks,Resource quantities,Selector match,HTTPRoute match, the fourTrivy configjobs, and all five install-tests (calibre-web-automated,freesailarr,outline,paperless-ngx,zipline).The
HTTPRoute matchjob's own tail (job 2402), which is the figure README and the wiki now carry:That matches the local run exactly, which it should — the job is a pure render over every chart with no cluster involved. Group 1 left it at 20 assertions over 10 charts; the five new overlays add 16, eight of them from
freesailarralone (its own-host route plus the five rules of the shared route, qBittorrent'sURLRewriterule included).not exercisedis the honest half of that table. The fifteen charts listed there render noHTTPRouteat all: six of them are group 3, one is the frozenrss-bridge, and the rest have no Ingress to convert. Nothing in this PR claims coverage for them.What the green install-tests do and do not say. They say the five charts install into a k3d cluster in Ingress mode and every workload becomes Ready —
freesailarrincluding the real Wireguard tunnel and all ten containers,paperless-ngxincluding the bundled Valkey. They say nothing about the Gateway mode: no route is installed in any of them, because the install runs one configuration per chart and the Ingress was the better one to keep here. The route's schema validity is covered bycyberchef's fixture against the real CRDs; that it points somewhere by the render test above; that traffic flows by nothing, and by construction — no Gateway controller runs in CI.