freesailarr
Justin Klein edited this page 2026-10-14 14:57:00 +00:00
Clone

Wiki Page Revisions

8 Commits

Author SHA1 Message Date
jklein 81980279c4 NetworkPolicies in every chart (Gitea #5): the convention, the gate, and what it does not do
29 chart pages plus Chart-Style-Guide, CI-CD, Charts and Lessons-Learned.

Chart-Style-Guide gains section 15, and its core is the FOUR PORT CLASSES -
the part that is actually hard to get right. A port a network probe addresses
stays open until probeSources is set, because the kubelet probes from the
worker node's IP and no chart can know it. A NodePort-published port stays open
because the source is already masqueraded. Everything else can be and is
restricted by source, which is why the charts with a real inbound restriction
are exactly those whose probes use a different port. A replica-to-replica bus
gets a bare podSelector - and, since policyTypes lists Egress, its outbound
side needs a rule of its own or the cluster never forms while every node looks
healthy.

The exec-probe distinction is written down as load-bearing rather than
pedantic: it is the whole reason valkey and minecraft-server are the tightest
policies in the repository and every HTTP-probed web app is looser.

CI-CD gains the networkpolicy job as section 8 (the later two renumbered), and
it ends with the measurement rather than a claim. Two experiments ran on the PR
and were reverted: cyberchef's probe port restricted to an address no probe can
come from stayed GREEN at 1m9s, and outline with its DNS egress rule removed
went RED at 5m59s. Together those say k3s exempts the kubelet's probe traffic
while enforcing everything else - so the install-test's net is an EGRESS net,
and a wrong ingress port is caught by the render assertion or not at all.

Every chart page gets a networkPolicy values table and an operating note naming
that chart's own trap, not a boilerplate one:

- keycloak carries the blast radius: it is the cluster's identity provider, its
  http rule is the only source-restricted inbound rule in the repo, and nothing
  in CI can catch a wrong controller - so the page asks for a real login and a
  look at consumers' issuer URLs after the first upgrade.
- garage asks the reader to check their S3 consumers, because anything in
  another namespace is blocked until it is in allowedNamespaces.
- bulwark names the quietest failure in the sweep: its probes test its own port,
  so a jmapServerUrl on a blocked port yields a Ready pod serving an empty
  webmail.
- gotenberg explains what its DNS-only cap COSTS - HTML conversion silently
  loses remote assets - rather than only what it buys.
- satisfactory and homeassistant each argue their own Ingress-only policy;
  satisfactory's deciding half is that it is the one chart with no install-test
  at all, so an egress cap there would be the only one with no empirical check
  behind it.
- paperless-ngx names the two pods of its release that the object does NOT
  cover, including the consume CronJob whose pod template carries no labels.
- freesailarr explains why ten containers behind a VPN need only three egress
  rules, and that extraPorts cannot help with an alternative WireGuard port
  because it is TCP-only.

valkey and stalwart are corrected rather than extended. valkey's page now says
why having no probeSources is what makes it the tightest policy here and not an
incomplete one. stalwart's carries a dated correction: this page and the chart
both advised putting the ingress controller's namespace into allowedNamespaces
with ["ingress-nginx"] as the example, and both halves are wrong - ingress-nginx
is gone from the cluster entirely, and allowedNamespaces would have opened http
to every pod in that namespace anyway.

Charts drops the claim that valkey is "the first chart with a NetworkPolicy on
by default" and says instead that every chart has one, with the two shapes a
reader needs before opening any chart page.

Lessons-Learned gains four, all transferable:

- the net that existed for a different failure than the one it was built for,
  and why both experiments were needed before either could be read;
- a comment that could not describe its own syntax (a template comment must not
  spell out its own closing delimiter; the render then fails pointing at the
  template, not at the sentence);
- three values that reached no rule and would have read as controls - a
  convention about value NAMES is not a convention about which values exist;
- the subchart whose comments the parent could not fix, and the rule it
  produced: before making a check a gate, ask which failures the author of this
  diff can actually fix.
2026-10-14 14:57:00 +00:00
jklein 57cf169203 freesailarr, Lessons learned: the release runs in the jellyfin namespace - correcting a limit I invented
A correction to my own measurement round, and it runs in the direction of
understating the evidence rather than overstating it.

The freesailarr page claimed, as the second of three limits, that there is
no freesailarr namespace in jk-cluster and no pods of it visible, and
concluded that where the backend runs is not determinable. The first
clause is literally true; the conclusion does not follow.

THE RELEASE RUNS IN NAMESPACE jellyfin. Verified: pod
freesailarr-freesailarr-sfs-0, 10/10 Running, 0 restarts, on jk-worker-2;
the StatefulSet's Helm annotations name release freesailarr in namespace
jellyfin; all ten containers readable (gluetun, qbittorrent, prowlarr,
flaresolverr, radarr, recyclarr, sonarr, seerr, bazarr, mediathekarr).

How the error was produced: by searching over namespace NAMES - for
freesailarr, and for anything containing "sail" or "arr". None matched,
because the release lives beside its neighbouring service rather than in a
namespace named after its chart. An empty result that read like a fact.

What this strengthens, and it is the point of the correction: the
freesailarr.published helper is now proven AT THE SOURCE rather than
deduced from a redirect through a proxy. RADARR__SERVER__URLBASE=/radarr
is set in the running container (with PROWLARR__ and SONARR__ likewise)
while kubectl get ingress -n jellyfin returns NO resources at all - not
this chart's object and not jellyfin's own. That combination is exactly
what the helper exists for: read from ingress.enabled alone the variable
would be empty in Gateway mode, and the UI would be broken behind a Ready
pod.

The two genuine limits stay, and are now scoped: the implementation cannot
be named (httproutes and gatewayclasses are RBAC-forbidden to the
read-only identity), and every HTTP measurement passed through the front
proxy at proxy.jk-dev.de - which applies to the HTTP rows and not to the
rows read from the API server.

The lesson is recorded with it: a release does not have to live in a
namespace named after its chart, and a query over namespace names does not
answer "is this running". Same genus as the #118 finding one level over - a
search that answers a different question than the one asked - and the
mirror image of the rewrite-target grep that counted bulwark's refutations
as occurrences. Treat an empty result as a question about the query before
accepting it as a finding.

No chart change and no version step: this is a wiki-only correction.
2026-10-13 10:40:11 +00:00
jklein 3fd7e65d5e rss-bridge on the Gateway API, the production proof of the Gateway mode, and the red check that went green
Three rounds of knowledge in one commit, all of it measured rather than
relayed.

rss-bridge: the last chart of the Gateway-API sweep (Gitea #114). New
Routing row and prerequisites per mode, a Gateway-mode install example,
the httpRoute values table, and four operating-notes sections: the mode
guards, why httpRoute.hostnames is this chart's own mandatory value (the
third hostname shape in the repo - no ingress.domain to fall back to, and
ingress.hosts deliberately not reused because its tlsSecretName entries
address nothing once TLS sits on the listener), that rss-bridge.tlsGroups
is therefore inert in that mode, and that nothing is lost on the switch -
measured on the live Ingress, which carries no nginx annotation at all, so
this is the one chart of the sweep whose Gateway branch has no loss to
explain. Also records the ingress.enabled-derived-value screen coming back
CLEAN for this chart, because an unrecorded clean screen is
indistinguishable from one nobody ran.

The install-test section is new and was rewritten once while being
written: the chart's test had been red on the Gitea runner since the move
(#51) and went GREEN on 2026-10-13, pod Ready in 22s with 0 restarts. The
chart is ruled out as the cause by a diff, not an argument - the PR's
rendered defaults are byte-identical to origin/main, so the spec that
succeeded is the spec that had been failing. One run, no identified change
on the runner, so #51 stays open: close on a cause, not on a colour.

freesailarr: the Gateway mode is in production, which is the claim all
four PRs of the sweep refused to make. No freesailarr Ingress objects in
any namespace while the host answers; /radarr answers 302 from Kestrel
with returnUrl=%2Fradarr, so the freesailarr.published helper works in
production; /qbittorrent 301s to the slashed form from the chart's own
rule; and the query string survives the redirect. The query-string answer
is written as a measured data point and NOT as a promise - the
specification still has no query field, so a different implementation may
legitimately drop it. Three limits recorded with it: httproutes and
gatewayclasses are RBAC-forbidden so the implementation cannot be named,
there is no freesailarr namespace in the cluster at all, and every HTTP
measurement passed through a front proxy. The attribution of the 301 to
the chart's rule is inference from the snippet's carrier being gone plus
the changed response signature, and says so.

CI-CD: the httproute render test goes to 58 assertions over 22 charts, two
overlays now have to supply hostnames rather than one, and the all-charts
install-test note is corrected - rss-bridge's last green run is no longer
a GitHub one.

Lessons learned: three entries. The claim four PRs refused to make and
where the measurement that reached it actually stopped; a screen that
finds nothing is still a result worth writing down; and the red check that
was kept red so it could say something, and then said it - an excluded
test is not a quiet test but a blind one, because you also lose the moment
the fault stops existing.
2026-10-13 10:11:19 +00:00
jklein 1e684ca6ce freesailarr, Renovate, Lessons learned: the grouped stack PR and the security default a patch bump switched off
The chart's trustedNetworks value for prowlarr, radarr and sonarr, with the
consequence written as it is: the default is empty, so the chart does not
restore the forwarded-header trust that prowlarr 2.6.3 and radarr 6.4.3
removed, and sonarr has been without it since before 4.0.19.

Renovate: the first grouped chart, the two rules it takes because
matchUpdateTypes and separateMinorPatch are rejected in one rule, and the
measured branch split without the second rule.

Lessons learned: a plain app-version bump switched off a security default,
and that is the one update class the wiki obligation exempts.
2026-10-12 22:02:42 +00:00
jklein 52588f0d74 freesailarr: the trailing slash on qBittorrent's prefix (#118)
freesailarr: a new section on the bare, slashless prefix - why qBittorrent
answers it 200 while the page's relative links are served by seerr, that the
Gateway mode now renders the 301 itself (Exact match, RequestRedirect,
statusCode 301 written out because it defaults to 302, and 301 rather than an
Extended value that would fail the whole route), the query-string asymmetry,
and the Ingress-mode remedy with its price: a configuration-snippet annotation
is honoured only where allow-snippet-annotations is on, which has been off by
default since ingress-nginx 1.9 and means accepting risk level Critical. Also
why a third Ingress object with pathType: Exact is not a substitute, with the
controller-v1.14.5 source citation and the note that the ordering consequence
is inferred, not observed.

CI-CD: the third finding of the HTTPRoute render test, its per-object scope and
the precedence reasoning, the two new "not checked" entries, and the corrected
figures - 37 assertions over 15 charts, the first check added since the pilot
rather than a chart.

Lessons-Learned: both halves. The chart left a gap that an operator had closed
silently from another repository with an annotation of risk level Critical, so
the chart looked correct, the running system worked, and the migration appeared
to break something the chart had never provided. And the method half, which
generalises further: a search over the chart answers what the chart does, while
what the running system does is a separate measurement - at the live object or
in the cluster repository's values. Where a chart finding is meant to carry a
claim about behaviour, that second measurement is mandatory.
2026-10-12 20:11:38 +00:00
jklein 353fb7d6de Gateway-API mode for group 2 of #114: the five annotation charts
PR #117. calibre-web-automated, outline, paperless-ngx, zipline (proxy-body-size)
and freesailarr (rewrite-target) each gain an httpRoute block, off by default.

Per chart page: a Routing row instead of the Ingress row, a Gateway-mode install
snippet, an httpRoute values table, and a "Routing: Ingress or Gateway API"
operating note. The upload-limit disclosure is written in each chart's own terms
rather than copied: an e-book library (calibre), an upload service (zipline), the
web-UI half while the consume directory keeps no limit (paperless), and the open
#12 pair (outline). freesailarr sets no body-size annotation and needs none, but
ingress.annotations is its operator's only lever for one.

outline: #12 now says what Gateway mode does to it. The ingress half of the pair
disappears, so the effective ceiling is the Gateway's and neither documented
number necessarily decides. That moves the defect; it does not fix it.

paperless-ngx: it was the last chart rendering its Ingress unconditionally and
now has ingress.enabled, default true. Recorded with the reason, because the
page previously stated the absence of that switch as a property.

freesailarr: the page gains the finding and the improvement. Its shared-host app
list was gated on ingress.enabled and also feeds the <APP>__SERVER__URLBASE
variables, so Gateway mode would have served radarr/sonarr/prowlarr under their
prefixes with no base path set - broken UI, Ready pod. And its rewrite-target
crutch is gone: a URLRewrite filter is per rule, so qBittorrent needs no second
routing object, no repeated TLS secret, no conditional cert-manager annotation
and no regex path.

Chart-Style-Guide §4: the "needs one, not sets one" screen, the rewrite-target
asymmetry as the one place this mode is less machinery, the ingress.enabled-first
rule for a chart without that switch, the freesailarr hostnames exception, and
the group status. §12.1 gains the two addenda the sweep produced.

CI-CD: the HTTPRoute test figure (4 over 2 at the pilot, 20 over 10 after group 1,
36 over 15 now), the multi-app overlay rule, and the honest note that the Gateway
branch of NOTES.txt is executed by that test but printed by no stage.

Lessons-Learned: the freesailarr case in full, next to sure's. Same helper shape,
different symptom - which is the point: ingress.enabled is almost never asked
"does an Ingress exist", and the property it stands in for does not flip with the
object.
2026-10-12 18:28:32 +00:00
jklein 311293e164 chart pages carry no version numbers any more
Both version rows leave every chart page's at-a-glance table; the image
repository stays, because it does not rot. A plain app-version bump obliges
no wiki edit — that rule is deliberate and stays — so any number written
here is stale within a Renovate round, as urlaubsverwaltung's already was.
Chart.yaml is where the pair is read, exactly as the chart index has said
from the start.

Home and Workflow no longer promise version snapshots. Forge and tooling
described remotes named github and JLab-Git; the checkout has exactly one,
origin, pointing at Gitea. Workflow gains the rule the wiki never had:
fetch and rebase before every push, never force-push.
2026-09-26 21:42:15 +00:00
jklein f62f2e5871 Fill the wiki as the repository's knowledge base
One page per chart - prerequisites, installation walkthrough, the required
1Password fields, the complete values reference and the chart's operating
traps - plus the long form of the workflow, the forge and its measured
differences, the pipelines, versioning, Renovate, the chart style guide, the
procedure for a new chart, local development, and the incidents each rule was
bought with.

Chart pages quote versions as snapshots; Chart.yaml stays authoritative,
because Renovate moves app versions continuously and never edits the wiki.
2026-09-18 08:48:01 +00:00