2a21461e420f8d6e09ce0550e9454ddd1fbd2d33
100
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e3f6256244
|
feat(gotenberg): a named switch for remote assets, off by default
Validate Helm Charts / Resource quantities (pull_request) Successful in 11s
Validate Helm Charts / Trivy config apache-tika (pull_request) Successful in 13s
Validate Helm Charts / Build calibre-web-automated (pull_request) Successful in 5s
Validate Helm Charts / Bump level (pull_request) Successful in 4s
Validate Helm Charts / Build gotenberg (pull_request) Successful in 6s
Validate Helm Charts / Build keycloak (pull_request) Successful in 6s
Validate Helm Charts / Build minecraft-router (pull_request) Successful in 6s
Validate Helm Charts / Build paperless-ngx (pull_request) Successful in 5s
Validate Helm Charts / Build rallly (pull_request) Successful in 5s
Validate Helm Charts / Build samba (pull_request) Successful in 5s
Validate Helm Charts / Build teamspeak-server (pull_request) Successful in 5s
Validate Helm Charts / Build patchmon (pull_request) Successful in 5s
Validate Helm Charts / Build template-chart (pull_request) Successful in 5s
Validate Helm Charts / Build valkey (pull_request) Successful in 5s
Validate Helm Charts / Trivy config bulwark (pull_request) Successful in 19s
Validate Helm Charts / Trivy config cyberchef (pull_request) Successful in 13s
Install-test / Install-test apache-tika (pull_request) Successful in 1m31s
Validate Helm Charts / Trivy config matrix-synapse (pull_request) Successful in 18s
Validate Helm Charts / Trivy config samba (pull_request) Successful in 14s
Validate Helm Charts / Trivy config satisfactory (pull_request) Successful in 26s
Install-test / Install-test calibre-web-automated (pull_request) Successful in 1m52s
Validate Helm Charts / Trivy config sure (pull_request) Successful in 15s
Validate Helm Charts / Trivy config template-chart (pull_request) Successful in 14s
Validate Helm Charts / Trivy config urlaubsverwaltung (pull_request) Successful in 21s
Validate Helm Charts / Trivy config voucher-vault (pull_request) Successful in 13s
Validate Helm Charts / Trivy config ytdlp-web-player (pull_request) Successful in 13s
Install-test / Install-test garage (pull_request) Successful in 1m14s
Install-test / Install-test minecraft-server (pull_request) Successful in 1m52s
Install-test / Install-test paperless-ngx (pull_request) Successful in 2m56s
Install-test / Install-test patchmon (pull_request) Successful in 1m22s
Install-test / Install-test rss-bridge (pull_request) Successful in 42s
Install-test / Install-test valkey (pull_request) Successful in 1m14s
Install-test / Install-test voucher-vault (pull_request) Successful in 1m54s
Validate Helm Charts / Detect changed charts (pull_request) Successful in 51s
Validate Helm Charts / Selector match (pull_request) Successful in 10s
Validate Helm Charts / HTTPRoute match (pull_request) Successful in 10s
Validate Helm Charts / Build bentopdf (pull_request) Successful in 6s
Validate Helm Charts / Build garage (pull_request) Successful in 6s
Validate Helm Charts / YAML scalars (pull_request) Successful in 9s
Validate Helm Charts / NetworkPolicy (pull_request) Successful in 10s
Validate Helm Charts / Build cyberchef (pull_request) Successful in 6s
Validate Helm Charts / Build freesailarr (pull_request) Successful in 5s
Validate Helm Charts / Build homeassistant (pull_request) Successful in 5s
Validate Helm Charts / Build satisfactory (pull_request) Successful in 5s
Validate Helm Charts / Build searxng (pull_request) Successful in 6s
Validate Helm Charts / Build stalwart (pull_request) Successful in 5s
Validate Helm Charts / Build sure (pull_request) Successful in 6s
Validate Helm Charts / Build rss-bridge (pull_request) Successful in 6s
Validate Helm Charts / Build urlaubsverwaltung (pull_request) Successful in 5s
Validate Helm Charts / Build voucher-vault (pull_request) Successful in 6s
Validate Helm Charts / Build zipline (pull_request) Successful in 6s
Validate Helm Charts / Trivy config bentopdf (pull_request) Successful in 11s
Validate Helm Charts / Trivy config keycloak (pull_request) Successful in 13s
Install-test / Install-test bentopdf (pull_request) Successful in 55s
Validate Helm Charts / Trivy config minecraft-router (pull_request) Successful in 14s
Validate Helm Charts / Trivy config minecraft-server (pull_request) Successful in 13s
Validate Helm Charts / Trivy config outline (pull_request) Successful in 15s
Validate Helm Charts / Trivy config paperless-ngx (pull_request) Successful in 21s
Install-test / Install-test bulwark (pull_request) Successful in 1m20s
Validate Helm Charts / Trivy config rss-bridge (pull_request) Successful in 12s
Validate Helm Charts / Trivy config searxng (pull_request) Successful in 23s
Validate Helm Charts / Trivy config teamspeak-server (pull_request) Successful in 12s
Install-test / Install-test cyberchef (pull_request) Successful in 1m2s
Validate Helm Charts / Trivy config zipline (pull_request) Successful in 14s
Install-test / Install-test freesailarr (pull_request) Successful in 3m42s
Install-test / Install-test gotenberg (pull_request) Successful in 1m32s
Install-test / Install-test keycloak (pull_request) Successful in 2m34s
Install-test / Install-test minecraft-router (pull_request) Successful in 48s
Install-test / Install-test outline (pull_request) Successful in 2m7s
Install-test / Install-test samba (pull_request) Successful in 1m12s
Install-test / Install-test searxng (pull_request) Successful in 1m13s
Install-test / Install-test urlaubsverwaltung (pull_request) Successful in 1m33s
Validate Helm Charts / Install-test (pull_request) Successful in 45m59s
Install-test / Install-test ytdlp-web-player (pull_request) Successful in 54s
Install-test / Install-test zipline (pull_request) Successful in 1m57s
Validate Helm Charts / Build apache-tika (pull_request) Successful in 5s
Validate Helm Charts / Build bulwark (pull_request) Successful in 6s
Validate Helm Charts / Build matrix-synapse (pull_request) Successful in 5s
Validate Helm Charts / Build minecraft-server (pull_request) Successful in 5s
Validate Helm Charts / Build outline (pull_request) Successful in 5s
Validate Helm Charts / Build ytdlp-web-player (pull_request) Successful in 6s
Validate Helm Charts / Trivy config calibre-web-automated (pull_request) Successful in 16s
Validate Helm Charts / Trivy config freesailarr (pull_request) Successful in 20s
Validate Helm Charts / Trivy config garage (pull_request) Successful in 21s
Validate Helm Charts / Trivy config gotenberg (pull_request) Successful in 12s
Validate Helm Charts / Trivy config homeassistant (pull_request) Successful in 12s
Validate Helm Charts / Trivy config patchmon (pull_request) Successful in 15s
Validate Helm Charts / Trivy config rallly (pull_request) Successful in 11s
Validate Helm Charts / Trivy config stalwart (pull_request) Successful in 13s
Validate Helm Charts / Trivy config valkey (pull_request) Successful in 15s
Install-test / Install-test homeassistant (pull_request) Successful in 2m24s
Install-test / Install-test matrix-synapse (pull_request) Successful in 1m31s
Install-test / Install-test rallly (pull_request) Successful in 1m38s
Install-test / Install-test stalwart (pull_request) Successful in 1m18s
Install-test / Install-test sure (pull_request) Successful in 2m15s
Install-test / Install-test teamspeak-server (pull_request) Successful in 1m19s
The maintainer's decision, and it keeps restrictive-by-default intact: not an
opening but networkPolicy.egress.remoteAssets, whose purpose is in its name
instead of being an unplaceable [80, 443] in extraPorts. Additive inside the
major this chart already carries in this PR, so no second version step.
WHAT IT BUYS, measured rather than assumed. paperless-ngx splits a mail into
two steps with two separate cluster-internal endpoints: the .eml goes to TIKA
for parsing (PAPERLESS_TIKA_ENDPOINT) and the resulting HTML comes here for
rendering (PAPERLESS_TIKA_GOTENBERG_ENDPOINT). Of the images in that HTML,
EMBEDDED ones are MIME parts inside the file and need no network from either
service - which is why apache-tika stays DNS-only and is unchanged - while
REMOTE ones (<img src="https://...">) are fetched by Chromium here, at render
time. With the switch off they are silently dropped: the PDF is produced and is
simply missing them, nothing fails, nothing is logged, no probe notices. That
invisibility is the reason the switch exists.
NOT CHECKED, and written down as not checked rather than implied: whether
paperless-ngx re-embeds the images Tika extracted before handing the HTML over.
If it does not, embedded images would be lost too - but that would be
pre-existing paperless behaviour and not this policy, so the switch is not a
fix for it.
The SSRF argument is unchanged and is why the default is off: Gotenberg drives
a headless Chromium and 3000 is its probe port, so the service is reachable
from the whole cluster, and outbound web access turns anything that can POST
here into a way of fetching arbitrary URLs.
Two things the implementation needed, and one of them was a real defect. The
NOTES.txt branch read .Values.networkPolicy.egress.remoteAssets directly, which
fails the whole render with "nil pointer evaluating interface
{}.remoteAssets" when an operator erases the egress block - the schema permits
null there, and issue #99's rule is that an erased block means "the default
applies", not a broken render. It is null-safe now, and an erased block falls
back to DNS-only, which is the restrictive direction. And ci/networkpolicy/
gotenberg.yaml makes the switch's ON state permanent in CI rather than checked
once by hand: it renders both the switch's egress rule and the
source-restricted probe branch, so all five assertions run over the toggle in
both positions on every pull request. Worth having precisely because assertions
2 and 3 look at INGRESS rules - an egress mistake has no other mechanical
check.
Also, three things that were load-bearing and written nowhere:
- NO CHART SHIPS allowedNamespaces PRE-FILLED. A chart naming mc-router or
stalwart in its defaults would bake in an assumption about this cluster's
namespace layout, which a chart cannot keep true. Restrictive is the default
and filling it is the DEPLOYMENT adaptation - so the release-side one-liners
this sweep produces are the expected procedure, not a defect list, and the
major on each chart is what announces them. ingressController is the one
exception, because it names a cluster component rather than a tenant.
- A POLICY APPLIES TO TRAFFIC THAT CAME THROUGH A SERVICE, and that is why the
ingressController rule works at all. A Service is only destination NAT and
the policy acts at the pod's interface. What the Service changes is the
SOURCE ADDRESS: pod -> ClusterIP -> pod preserves it, so a podSelector can
still match Traefik; NodePort traffic with the default externalTrafficPolicy:
Cluster is masqueraded to a node, which is why those rules are left open.
stalwart's template already carried the second half; the first half is the
one everything rests on.
- AND THE ONE CLUSTER SETTING THIS CONVENTION DEPENDS ON AND CANNOT CHECK:
Traefik must not run with hostNetwork. Measured 2026-10-14 - the DaemonSet
has none and its pods carry real pod IPs (10.42.3.120, 10.42.4.94,
10.42.0.187 in the pod CIDR 10.42.0.0/16, nodes on entirely different
addresses), and only because of that does a podSelector match it. On host
networking those pods share the node's namespace, stop being selectable,
and EVERY WEB CHART GOES DARK AT ONCE while every pod stays Ready, because
the probe path does not run through the controller. Same class as
--enable-certificate-owner-ref in #126: undetectable from here. It is in
README and in the header of template-chart's policy, so whoever changes the
controller's networking meets the consequence on the thing they are changing.
Part of #5
|
||
|
|
8233617f57
|
docs: the NetworkPolicy convention and its render test
Validate Helm Charts / Build stalwart (pull_request) Successful in 5s
Validate Helm Charts / Build sure (pull_request) Successful in 7s
Validate Helm Charts / Build urlaubsverwaltung (pull_request) Successful in 6s
Validate Helm Charts / Build valkey (pull_request) Successful in 5s
Validate Helm Charts / Trivy config bentopdf (pull_request) Successful in 16s
Validate Helm Charts / Trivy config keycloak (pull_request) Successful in 14s
Validate Helm Charts / Trivy config matrix-synapse (pull_request) Successful in 26s
Validate Helm Charts / Trivy config minecraft-server (pull_request) Successful in 12s
Validate Helm Charts / Trivy config patchmon (pull_request) Successful in 12s
Validate Helm Charts / Trivy config sure (pull_request) Successful in 12s
Validate Helm Charts / Trivy config teamspeak-server (pull_request) Successful in 30s
Validate Helm Charts / Trivy config voucher-vault (pull_request) Successful in 12s
Validate Helm Charts / Trivy config template-chart (pull_request) Successful in 17s
Validate Helm Charts / Detect changed charts (pull_request) Successful in 4s
Validate Helm Charts / Trivy config zipline (pull_request) Successful in 13s
Validate Helm Charts / NetworkPolicy (pull_request) Successful in 13s
Validate Helm Charts / HTTPRoute match (pull_request) Successful in 9s
Validate Helm Charts / Build freesailarr (pull_request) Successful in 6s
Validate Helm Charts / Build keycloak (pull_request) Successful in 5s
Validate Helm Charts / Build matrix-synapse (pull_request) Successful in 4s
Validate Helm Charts / Build outline (pull_request) Successful in 5s
Validate Helm Charts / Build rallly (pull_request) Successful in 5s
Validate Helm Charts / Build paperless-ngx (pull_request) Successful in 6s
Validate Helm Charts / Build satisfactory (pull_request) Successful in 16s
Validate Helm Charts / Build samba (pull_request) Successful in 12s
Install-test / Install-test freesailarr (pull_request) Successful in 3m28s
Install-test / Install-test minecraft-router (pull_request) Successful in 40s
Install-test / Install-test rss-bridge (pull_request) Successful in 46s
Install-test / Install-test samba (pull_request) Successful in 1m9s
Install-test / Install-test searxng (pull_request) Successful in 1m5s
Install-test / Install-test stalwart (pull_request) Successful in 1m18s
Install-test / Install-test sure (pull_request) Successful in 2m26s
Install-test / Install-test teamspeak-server (pull_request) Successful in 1m14s
Install-test / Install-test valkey (pull_request) Successful in 1m14s
Install-test / Install-test voucher-vault (pull_request) Successful in 1m49s
Validate Helm Charts / Install-test (pull_request) Successful in 43m53s
Validate Helm Charts / Build teamspeak-server (pull_request) Successful in 5s
Validate Helm Charts / Build template-chart (pull_request) Successful in 5s
Validate Helm Charts / Build zipline (pull_request) Successful in 11s
Validate Helm Charts / Trivy config calibre-web-automated (pull_request) Successful in 12s
Validate Helm Charts / Trivy config freesailarr (pull_request) Successful in 14s
Validate Helm Charts / Trivy config garage (pull_request) Successful in 16s
Validate Helm Charts / Trivy config homeassistant (pull_request) Successful in 12s
Validate Helm Charts / Trivy config gotenberg (pull_request) Successful in 12s
Validate Helm Charts / Trivy config outline (pull_request) Successful in 12s
Validate Helm Charts / Trivy config paperless-ngx (pull_request) Successful in 16s
Validate Helm Charts / Trivy config searxng (pull_request) Successful in 12s
Validate Helm Charts / Trivy config ytdlp-web-player (pull_request) Successful in 16s
Validate Helm Charts / Trivy config stalwart (pull_request) Successful in 13s
Validate Helm Charts / Trivy config urlaubsverwaltung (pull_request) Successful in 13s
Validate Helm Charts / Selector match (pull_request) Successful in 9s
Validate Helm Charts / Resource quantities (pull_request) Successful in 13s
Validate Helm Charts / Bump level (pull_request) Successful in 4s
Validate Helm Charts / YAML scalars (pull_request) Successful in 19s
Validate Helm Charts / Build bentopdf (pull_request) Successful in 6s
Validate Helm Charts / Build homeassistant (pull_request) Successful in 9s
Validate Helm Charts / Build bulwark (pull_request) Successful in 9s
Validate Helm Charts / Build calibre-web-automated (pull_request) Successful in 11s
Validate Helm Charts / Build garage (pull_request) Successful in 9s
Validate Helm Charts / Build minecraft-router (pull_request) Successful in 6s
Validate Helm Charts / Build minecraft-server (pull_request) Successful in 7s
Validate Helm Charts / Build patchmon (pull_request) Successful in 5s
Validate Helm Charts / Build searxng (pull_request) Successful in 6s
Install-test / Install-test apache-tika (pull_request) Successful in 1m9s
Install-test / Install-test bentopdf (pull_request) Successful in 53s
Install-test / Install-test bulwark (pull_request) Successful in 1m18s
Install-test / Install-test calibre-web-automated (pull_request) Successful in 1m47s
Install-test / Install-test cyberchef (pull_request) Successful in 51s
Install-test / Install-test homeassistant (pull_request) Successful in 2m20s
Install-test / Install-test outline (pull_request) Successful in 2m15s
Install-test / Install-test matrix-synapse (pull_request) Successful in 1m12s
Install-test / Install-test paperless-ngx (pull_request) Successful in 3m15s
Validate Helm Charts / Build voucher-vault (pull_request) Successful in 5s
Validate Helm Charts / Build ytdlp-web-player (pull_request) Successful in 10s
Validate Helm Charts / Trivy config bulwark (pull_request) Successful in 12s
Validate Helm Charts / Trivy config cyberchef (pull_request) Successful in 20s
Validate Helm Charts / Trivy config minecraft-router (pull_request) Successful in 26s
Validate Helm Charts / Trivy config rallly (pull_request) Successful in 12s
Validate Helm Charts / Trivy config samba (pull_request) Successful in 20s
Validate Helm Charts / Trivy config satisfactory (pull_request) Successful in 15s
Validate Helm Charts / Trivy config rss-bridge (pull_request) Successful in 14s
Validate Helm Charts / Trivy config valkey (pull_request) Successful in 11s
Validate Helm Charts / Trivy config apache-tika (pull_request) Successful in 12s
Validate Helm Charts / Build apache-tika (pull_request) Successful in 5s
Validate Helm Charts / Build cyberchef (pull_request) Successful in 8s
Validate Helm Charts / Build gotenberg (pull_request) Successful in 10s
Validate Helm Charts / Build rss-bridge (pull_request) Successful in 5s
Install-test / Install-test garage (pull_request) Successful in 1m8s
Install-test / Install-test gotenberg (pull_request) Successful in 1m12s
Install-test / Install-test keycloak (pull_request) Successful in 1m54s
Install-test / Install-test minecraft-server (pull_request) Successful in 1m45s
Install-test / Install-test patchmon (pull_request) Successful in 1m23s
Install-test / Install-test rallly (pull_request) Successful in 1m44s
Install-test / Install-test urlaubsverwaltung (pull_request) Successful in 1m37s
Install-test / Install-test ytdlp-web-player (pull_request) Successful in 59s
Install-test / Install-test zipline (pull_request) Successful in 1m51s
README gains two sections and CLAUDE.md the binding compact summary. "NetworkPolicies (#5)" under Chart style guidelines carries the part that is actually hard to get right: the FOUR PORT CLASSES, and that the class decides the rule. 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 the ones whose probes use a different port: keycloak's 8080, garage's s3 and web, minecraft-server's RCON, teamspeak-server's query interface. And a cluster bus between a release's own replicas gets a bare podSelector, with the reminder that policyTypes Egress means the outbound side needs its own rule or the cluster never forms while every node looks healthy. The exec-probe distinction is written down as load-bearing rather than pedantic, because it is the whole reason valkey and minecraft-server are the tightest policies here and every HTTP-probed web app is looser. What the policies do NOT do has its own list, including the two that will surprise someone: most dependency ports are the STANDARD ones rather than the configured ones, because connection details live in 1Password items the charts never parse - with keycloak, stalwart and minecraft-router named as the exceptions that derive theirs from values; and a subchart's pods are not covered, with ytdlp-web-player's oauth2-proxy as the sharp case and paperless-ngx' valkey as the opposite. The three Ingress-only charts are listed with their individual arguments rather than a blanket excuse, and gotenberg is contrasted against them: it keeps a DNS-only cap on purpose, because it drives a headless Chromium on a port the whole cluster can reach. "NetworkPolicy render test" under CI/CD documents the gate, and ends with what the pipeline cannot prove - including the measurement from this PR that k3s exempts kubelet probe traffic from policy, so the install-test's net is an EGRESS net. That is stated there rather than implied, because it is the reason assertion 2 matters: a wrong ingress port is caught mechanically or not at all. Part of #5 |
||
|
|
39844fc92c
|
ci: hold every scalar that must be a string, and the required podAffinity
Validate Helm Charts / Bump level (pull_request) Successful in 7s
Publish Paperless-NGX Helm Chart / Publish Paperless-NGX Helm Chart (push) Successful in 16s
SonarQube Analysis / SonarQube Analysis (push) Successful in 47s
Validate Helm Charts / Detect changed charts (pull_request) Successful in 4s
Validate Helm Charts / HTTPRoute match (pull_request) Successful in 15s
Validate Helm Charts / Build paperless-ngx (pull_request) Successful in 9s
Validate Helm Charts / Resource quantities (pull_request) Successful in 15s
Validate Helm Charts / Trivy config paperless-ngx (pull_request) Successful in 29s
Install-test / Install-test paperless-ngx (pull_request) Successful in 5m11s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 16s
Validate Helm Charts / Selector match (pull_request) Successful in 14s
Validate Helm Charts / YAML scalars (pull_request) Successful in 15s
Validate Helm Charts / Install-test (pull_request) Successful in 5m11s
The test Gitea #11 asked for ("whoever fixes this ships a test case that would have caught it"), plus the guard for the fix #101 introduces. ci/run-yaml-scalar-render-test.sh renders every chart with the fixtures the install-test already uses and asserts three properties Kubernetes itself requires, so no per-chart expectations are needed: a CronJob schedule is a non-empty string, every env[].value of every container and initContainer is a string, and every label and annotation value is a string. The first catches the loud failure - the render aborting with a YAML error that names the template and not the value - and the other two catch the quiet one, where the render succeeds and the API server rejects the object at apply time. WHY A NEW JOB AND NOT THE INSTALL-TEST. The object that carried the defect is rendered by no fixture at all: paperless-ngx' consume CronJob sits behind smbConnection.enabled, false in the chart defaults and false in its install-test fixture, since the CI cluster has no SMB CSI driver. Nothing in this repository ever produced it. So the overlays under ci/yaml-scalar/ are the test, not the scaffolding: they switch such objects on AND feed them hostile values, and the script fails when an overlay stops reaching its object or when the run asserted nothing at all. The fail path was exercised in all three shapes against a throwaway copy of the chart as it stands on main, not reasoned about: the bare schedule aborted with "yaml: line 6: did not find expected alphabetic or numeric character"; with only the schedule quoted, the star-valued env entry aborted on the StatefulSet instead; and with only the bool-looking value left - a render that succeeds - the type assertion named the variable, the container and the API server's message. Green repo-wide otherwise: 30 charts, 408 scalars, 0 failed. ci/run-selector-match-test.sh additionally holds every requiredDuringSchedulingIgnoredDuringExecution podAffinity term against all pod templates of the same render (Gitea #101). That case is not the gap the script's header describes: a preference that cannot be met is dropped and an anti-affinity over zero pods is satisfied, but a REQUIRED term matching no pod can never be satisfied and the pod stays Pending for good. paperless-ngx' mover selects a label written in another template, which is exactly the copy this test holds - renaming it would leave both templates rendering perfectly while the mover silently stopped moving. Exercised by doing that rename in a throwaway copy: one FAIL naming the selector, the pod templates it missed and the labels actually present. Six charts still render their env block bare (matrix-synapse, minecraft-router, minecraft-server, rallly, satisfactory, teamspeak-server). They are green only because no fixture of theirs sets a hostile value; README now says so instead of repeating template-chart's claim that every chart quotes. Part of #11 Part of #101 |
||
|
|
59d2614384
|
feat(ci): the chart bump follows the app bump
Validate Helm Charts / Build ${{ matrix.chart }} (pull_request) Skipped
Validate Helm Charts / Install-test (pull_request) Skipped
Validate Helm Charts / HTTPRoute match (pull_request) Successful in 14s
Validate Helm Charts / Detect changed charts (pull_request) Successful in 5s
Validate Helm Charts / Bump level (pull_request) Successful in 7s
SonarQube Analysis / SonarQube Analysis (push) Successful in 48s
Validate Helm Charts / Resource quantities (pull_request) Successful in 18s
Validate Helm Charts / Selector match (pull_request) Successful in 14s
Validate Helm Charts / Trivy config cyberchef (pull_request) Successful in 20s
Validate Helm Charts / Trivy config matrix-synapse (pull_request) Successful in 22s
Validate Helm Charts / Trivy config samba (pull_request) Successful in 21s
The version-bump hook classified every app update that was not a major as a chart patch, so an app minor arrived as a patch - the mechanism behind the sonarr incident, where a security default was switched off inside a bump the conventions told the reviewer not to read (Gitea #128). ci/renovate-bump-chart.sh now takes the level from Renovate's update type, passed in as a second argument: major -> major, minor -> MINOR, patch/digest/pin -> patch. The level comes from Renovate rather than from comparing appVersion because an appVersion comparison is blind to the case that prompted the rule - freesailarr keeps a hand-maintained appVersion for ten upstreams, and prowlarr's 2.5.2 -> 2.6.5 moved an image tag in values.yaml while appVersion stood still. Three properties come with it: - Maximum, not replacement. The chart jump is the greater of the chart's own classification and the app jump, so a value that becomes required stays a major under an app patch. Mechanically it is also what makes a grouped branch deterministic: the hook runs once per update from the same base commit, so without it the last call would win. - Non-semver app versions stay at patch for a minor (seven charts). In CalVer and date tags the second position is a month; in a four-part or suffixed version the positions are not semver positions. An app major is not subject to it. - A missing or unknown update type falls back to the pre-#128 behaviour. postUpgradeTasks only runs where the command is allow-listed on the shared instance, which is not visible from here and fails silently, so the worst case is the old classification rather than every chart PR red on the version check. ci/run-bump-level-test.sh holds 49 cases against the version string the script writes, wired up as the bump-level job: a wrong level is a valid version string, so the PR would go green and the only symptom would be a number that understates what it publishes. The convention texts follow in CLAUDE.md and README, including the limit the decision deliberately keeps: the reading duty was rejected, so the incident stays repeatable and the honest number is the whole of the remedy. Fixes #128 |
||
|
|
71b757bf9f
|
docs(README): the MTA-STS delta on top of both landed rounds, measured at 64/22
Install-test / Install-test stalwart (pull_request) Successful in 1m36s
Validate Helm Charts / Install-test (pull_request) Successful in 1m36s
Validate Helm Charts / Selector match (pull_request) Successful in 15s
Validate Helm Charts / HTTPRoute match (pull_request) Successful in 16s
Validate Helm Charts / Build stalwart (pull_request) Successful in 9s
Validate Helm Charts / Detect changed charts (pull_request) Successful in 5s
Validate Helm Charts / Resource quantities (pull_request) Successful in 16s
Validate Helm Charts / Trivy config stalwart (pull_request) Successful in 24s
Second rebase of this branch, and the last one the parallel rounds force. Both have now merged - rss-bridge plus its documentation corrections, and the realm hostnames of #122 - so line 184 carries the chain 56/21 -> 58/22 -> 62/22 with the delta convention #124 introduced. That chain is kept WORD FOR WORD and this round's delta is appended to it; the only change to the line is an append, verified line by line against origin/main rather than asserted. THE NUMBER IS READ, NOT COMPUTED, and that distinction is this paragraph's own rule. 62 + 2 = 64 is arithmetic over a branch that had a different substructure when its 2 were measured; the figure committed here comes from ci/run-httproute-match-test.sh on THIS branch, rebased onto the main that already holds both other rounds: 64 assertions over 22 charts, 0 failed, with stalwart contributing 4. It is therefore the first figure in that sentence that is a genuine running total rather than a delta - the first run that contains all three rounds, which is exactly where the convention says to read it. The earlier 58/21 and 60/22 in this branch's history were each correct for the substructure they were measured on and are both superseded. The commit that set 60/22 was dropped in this rebase rather than carried, since its only content was a number that no longer holds. |
||
|
|
b4c9823e01
|
feat(stalwart): MTA-STS routes as a maintainable list, in both routing modes
The policy route for mta-sts.<domain> belonged to another repository
(jk-cluster-config) while pointing at this chart's own Service. It moves
here as mtaSts.hosts, a list, so several domains - subdomains included -
are maintainable in one place and in whichever routing mode is active.
THE LIST IS THE SWITCH. No mtaSts.enabled: "enabled with an empty list"
would be a state with no meaning that the chart would then have to catch.
The schema rejects the key outright, so the rule is enforced rather than
documented. Default [] renders nothing in either mode - the defaults and
the unchanged CI fixture both produce a byte-identical helm template
against origin/main.
Entry form {host, tlsSecretName, clusterIssuer?}. The two TLS fields are
read ONLY in Ingress mode; in Gateway mode TLS sits on the listener, so
requiring them there would demand a value that does nothing.
THE PATH IS HARDWIRED. RFC 8461 fixes /.well-known/mta-sts.txt, rendered
with pathType Exact (type: Exact in the route). A values key for a
protocol-mandated path can only ever be set wrong, and wrong is invisible
from this side. Exact also matters in itself: a prefix on these hostnames
would publish the mail server's whole management surface under them.
ONE INGRESS PER CLUSTER ISSUER, NOT ONE OBJECT - and the plan's own three
requirements forced that. cert-manager reads the two fields at different
scopes: cluster-issuer is an ANNOTATION (object-scoped, and absent means
nothing is issued for that object at all), while secretName is
tls-entry-scoped and yields one Certificate per entry. So an object cannot
both request a certificate and leave another host's secret alone, which is
what an empty per-entry issuer has to mean. Measured in jk-cluster:
ingress-shim names the Certificate AFTER THE SECRET and owns it from the
Ingress, so a shared secret must sit in exactly one object (hence grouping
the tls list by secret, reusing rss-bridge.tlsGroups' shape) and a secret
may not span two issuer groups (hence the abort). Objects are named by
issuer, not by index: an index is only free where the object holds no
certificate, which is the precondition homeassistant's route naming states
for itself.
Gateway mode renders ONE HTTPRoute with every hostname - nothing differs
per host there - attached to the same Gateway(s) as the WebUI route.
WHAT THE CHART CANNOT PROMISE, AND THEREFORE NAMES: it registers a route,
it does not create a domain. The domains live in Stalwart's data store, so
an entry whose domain is missing there answers 404 at a correctly routed
endpoint with every object green - and MTA-STS failures are silent on the
SENDER's side, which delivers in the clear and logs nothing here. Same for
the _mta-sts.<domain> TXT record and the policy's content. The subdomain
trap is documented too: mta-sts.sub.<domain> is one label below
*.<domain>, so a parent wildcard certificate does not cover it.
Not folded into certificate.extraDnsNames deliberately - that would hang
the MAIL certificate on this list, letting a bad policy entry take STARTTLS
and IMAPS with it.
Minor, additive, empty default: 1.2.0+upv0.16.25 -> 1.3.0+upv0.16.25.
CI: ci/httproute/stalwart.yaml enters all three shapes of the entry form
(with issuer, without, and a subdomain), taking the HTTPRoute render test
from 56 assertions over 21 charts to 58 over 21 - stalwart from 2 to 4.
The Ingress half cannot be covered there, since an overlay's whole job is
to switch the mode off, so ci/stalwart/test-values.yaml carries two
entries and the install-test has a real API server accept them.
Fixes #123
|
||
|
|
649417b428
|
Merge pull request 'feat(keycloak): one hostname per realm, in both routing modes' (#125) from feat/keycloak-realm-hosts into main | ||
|
|
5cdb2a6227
|
Merge origin/main into feat/keycloak-realm-hosts, and settle README line 184
Validate Helm Charts / HTTPRoute match (pull_request) Successful in 14s
Validate Helm Charts / Detect changed charts (pull_request) Successful in 4s
Validate Helm Charts / Resource quantities (pull_request) Successful in 17s
Validate Helm Charts / Build keycloak (pull_request) Successful in 8s
Validate Helm Charts / Trivy config keycloak (pull_request) Successful in 26s
Install-test / Install-test keycloak (pull_request) Successful in 3m43s
Validate Helm Charts / Install-test (pull_request) Successful in 3m43s
Validate Helm Charts / Selector match (pull_request) Successful in 21s
The rss-bridge Gateway-API round landed while this branch was open, and the two rounds shared exactly one line: the sentence in README.md that carries the whole history of the httproute-match assertion count. Merged rather than rebased on purpose: the branch is already pushed and its history is not rewritten here, so nothing of the other round's text can be lost to a force push. Their half is kept verbatim, including the delta rule they added - that 58/22 and 60/21 were measured on branches which did not contain each other, so the delta is what either number can be trusted for, and the running total belongs to the first branch that holds both. This branch IS that first branch, so the open question is now answered with a measurement instead of an addition: 62 assertions over 22 charts, 0 failed, read off ci/run-httproute-match-test.sh on this merge. keycloak contributes 8 of them, plus the two "not checked" lines for its realm routes' backend-less redirect rules. |
||
|
|
a411ec3c90
|
docs(rss-bridge): phrase the reproduced green without a run count
The count was pinned at "two consecutive runs" and a third green arrived with the next push, which is a regress: every commit that records the number invalidates it. Phrased as "every run of that branch since" instead, which is stable, still establishes that it is not a fluke, and cannot go stale in main. The caution it carries is unchanged and is the one that matters: it is a colour, not a cause. Nobody has identified what changed on the runner, and #51's diagnostic programme is still unexecuted. |
||
|
|
8896fc8d7d
|
docs(README): the 58/22 figure is a delta, not a running total
Validate Helm Charts / Detect changed charts (pull_request) Successful in 7s
Validate Helm Charts / Resource quantities (pull_request) Successful in 15s
Validate Helm Charts / Selector match (pull_request) Successful in 15s
Validate Helm Charts / Build rss-bridge (pull_request) Successful in 16s
SonarQube Analysis / SonarQube Analysis (push) Successful in 45s
Publish RSS-Bridge Helm Chart / Publish RSS-Bridge Helm Chart (push) Successful in 12s
Validate Helm Charts / HTTPRoute match (pull_request) Successful in 18s
Validate Helm Charts / Trivy config rss-bridge (pull_request) Successful in 18s
Install-test / Install-test rss-bridge (pull_request) Successful in 1m4s
Validate Helm Charts / Install-test (pull_request) Successful in 1m4s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 12s
Gitea #122 (one hostname per keycloak realm) is in flight concurrently and raises keycloak's share from 4 assertions to 8. Its round was measured at 60 over 21 on a branch that does not contain rss-bridge, while the 58 over 22 here was read on a branch that does not contain the realm hostnames. Neither total includes the other, so stating 58/22 as the figure after the sweep would be stale in main the moment the second of the two PRs merges. Says so now: the delta is what either round can claim, and the running total is whatever the first run on main prints once both have landed. Same rule, and the same wording, as the wiki's CI-CD page carries for the two rounds. |
||
|
|
23ac537e24
|
docs(rss-bridge): the green install-test reproduced, so say "twice" rather than "one run"
Validate Helm Charts / Resource quantities (pull_request) Successful in 19s
Validate Helm Charts / Selector match (pull_request) Successful in 16s
Validate Helm Charts / HTTPRoute match (pull_request) Successful in 17s
Validate Helm Charts / Build rss-bridge (pull_request) Successful in 9s
Install-test / Install-test rss-bridge (pull_request) Successful in 1m8s
Validate Helm Charts / Detect changed charts (pull_request) Successful in 7s
Validate Helm Charts / Trivy config rss-bridge (pull_request) Successful in 20s
Validate Helm Charts / Install-test (pull_request) Successful in 1m8s
The previous commit recorded the install-test going green and hedged it with "it is one run". Pushing that commit produced a second run, on a second commit, and it was green too - run 792 job 2623 (1m10s) and run 794 job 2638 (1m14s), both with the chart genuinely in the matrix. So the hedge was understated in one direction and the real caution lies elsewhere. Corrected to say what is actually true: it reproduced over two consecutive runs, so it is not a fluke - and nobody has identified what changed on the runner, which is the reason #51 stays open. A green test does not explain itself, and the diagnostic programme in that issue's body (docker info on the runner for userns and the dind configuration, the same image in a local k3d without remapping) remains unexecuted. "Close an issue on a cause, not on a colour" is unaffected by the count. |
||
|
|
7a2eb29e9e
|
feat(keycloak): one hostname per realm, in both routing modes
Validate Helm Charts / Detect changed charts (pull_request) Successful in 4s
Validate Helm Charts / Trivy config keycloak (pull_request) Successful in 24s
Validate Helm Charts / Install-test (pull_request) Successful in 3m29s
Install-test / Install-test keycloak (pull_request) Successful in 3m29s
Validate Helm Charts / Resource quantities (pull_request) Successful in 14s
Validate Helm Charts / Selector match (pull_request) Successful in 14s
Validate Helm Charts / HTTPRoute match (pull_request) Successful in 14s
Validate Helm Charts / Build keycloak (pull_request) Successful in 8s
keycloak.realmHosts gives a realm a hostname of its own: this release's Service published under a further name, with that name's bare / sent to /realms/<realm>/account/. It renders in BOTH modes - one Ingress per entry carrying nginx.ingress.kubernetes.io/app-root, or one HTTPRoute per entry whose Exact match on / carries a RequestRedirect filter - because a routing value in this repository is mode-independent by design - ingress.domain is read in Gateway mode too - so a feature rendering in one mode only would silently disappear when an installation switched. Default []: nothing renders, and the render is byte-identical to 1.2.0 for the fixture's values in both modes. Minor, additive, no value becomes required. ONE OBJECT PER ENTRY, not one with several hostnames. spec.hostnames applies to the whole object and a rule's matches are path-based, so inside a shared route every entry would match Exact: / and the realm could not be told apart. Names therefore carry the LIST INDEX, as homeassistant's per-host routes do and for the same reason - a realm name is not a dependable object-name fragment. The consequence is written down rather than left to be discovered: reordering the list renames and recreates the objects. THE WORK IS IN THE INGRESS HALF, NOT THE ROUTE. Two convention rules do not survive these objects, and both deviations are required rather than tolerated: tlsSecretName is a per-entry value with NO default, because these hostnames already have certificates and several entries typically share one, so a derived name would ask cert-manager for a second certificate for a name a wildcard already covers; and the cert-manager annotation is OPTIONAL per entry, because cert-manager keys a Certificate by the secret it writes and only one object per secret may request it - the same deliberate omission the qBittorrent object in freesailarr makes. Annotations are per entry too: of the four hostnames this was written for, three carry the proxy-buffer pair and one does not, which a chart-global block could not express. statusCode: 302 is written out at the field's own default. 301 and 302 are both Support: Core, so the portability trap of #119 - an implementation declining an Extended code sets Accepted: False on the WHOLE route - does not apply here; ingress-nginx emits 302 for app-root, and a value that is only accidentally right documents nothing. Five render aborts, each against a failure that reports nothing: an entry without a host (ingress-nginx reads an empty host as a catch-all for the whole controller), without a realm (/realms//account/, a 404 behind a healthy hostname and certificate), without a tlsSecretName in Ingress mode (the controller's default certificate, a browser-only failure), a repeated host or one the release already publishes (webhook rejection in Ingress mode, two merged routes decided by object age in Gateway mode), and a filled list with both modes off (no object at all, and nothing in the release that reveals the list was ignored). The entry-shape checks sit in a shared helper so they exist in both modes, and the Ingress template skips them while httpRoute.enabled is also true so that the specific message cannot hide the mutual-exclusion one. CI: the two entries live in ci/keycloak/test-values.yaml, which ci/httproute/keycloak.yaml is applied on top of, so one definition covers both modes - two Ingress objects in the install-test, two HTTPRoutes in the render test. They cover both Ingress forms, with and without a clusterIssuer, and the render test holds their inertness in Gateway mode. The httproute match test goes from 56 assertions over 21 charts to 60, keycloak's share from 4 to 8 plus two "not checked" lines for the backend-less redirect rules. The #118 prefix/redirect pairing correctly does not fire: it is scoped to rules that strip a prefix, and a realm route strips nothing. NOT IN THIS CHANGE: the migration. Nothing in jk-cluster-config is touched. With the default [] no running system changes, so the order a takeover has to run in is documented instead - remove the objects in the owning repository first, then install with the list filled, because the reverse or both at once is rejected by ingress-nginx's admission webhook and leaves a Fleet bundle in ErrApplied until forceSyncGeneration is raised. Whether anything there depends on those objects BY NAME is unchecked and named as unchecked. Fixes #122 |
||
|
|
7d365c912c
|
docs(rss-bridge): the install-test went green, and the chart is ruled out as the cause
Validate Helm Charts / Build rss-bridge (pull_request) Successful in 8s
Install-test / Install-test rss-bridge (pull_request) Successful in 1m14s
Validate Helm Charts / Resource quantities (pull_request) Successful in 15s
Validate Helm Charts / Detect changed charts (pull_request) Successful in 4s
Validate Helm Charts / Selector match (pull_request) Successful in 15s
Validate Helm Charts / HTTPRoute match (pull_request) Successful in 16s
Validate Helm Charts / Trivy config rss-bridge (pull_request) Successful in 23s
Validate Helm Charts / Install-test (pull_request) Successful in 1m14s
This chart's install-test had failed on the Gitea runner since the move (#51): php-fpm as uid 33 unable to read /app/index.php, the startup probe 403ing through its whole 150s budget, the pod never Ready. The chart was deliberately kept OUT of ci/excluded-charts.txt so that a pass would be noticed rather than silenced. It passed, in this PR. Measured from the run: k3d cluster up, helm install in Ingress mode, pod 1/1 Ready in 22s with 0 restarts, no "Permission denied" anywhere in the job, and all eight check contexts green. The chart is ruled out as the cause by a diff rather than by an argument: the rendered output for the fixture values is BYTE-IDENTICAL to origin/main - proven in the previous commit to justify the Minor bump - so the pod spec that succeeded is, line for line, the pod spec that had been failing. The implicated layer is the runner, which is where #51 had already placed the burden of proof. The control case arrived for free, from evidence collected for an unrelated reason. What this does NOT establish, and the docs say so: that it is repaired. It is one run, nobody has identified what changed on the runner, and an intermittent effect needs several runs per side. #51 therefore stays open and is to be closed on a cause, not on a colour - which was the standing instruction there before the green arrived. Corrected accordingly, because shipping documentation that says the test fails while it demonstrably passed is the half-finished state this repo tries not to leave behind: - README: the httproute-test paragraph, guideline 4's closing passage on the last chart of the sweep, and the chart table row. - rss-bridge/templates/NOTES.txt: the Gateway branch no longer claims the chart has no successful install-test. What remains true, and is what it now says, is that the install-test installs the INGRESS mode, so the Gateway mode is render-only - the same position as all 21 other charts of the sweep rather than a worse one. - ci/httproute/rss-bridge.yaml: same correction, plus why the overlay stays load-bearing either way. The wiki carries the same correction in the same round (rss-bridge, CI-CD) plus a Lessons-Learned entry on the decision that produced the signal: an excluded test is not a quiet test but a blind one, because you also lose the moment the fault stops existing. |
||
|
|
45b2d2ea4e
|
feat(rss-bridge): Gateway-API as an alternative to the Ingress (last chart)
Validate Helm Charts / Detect changed charts (pull_request) Successful in 4s
Validate Helm Charts / Build rss-bridge (pull_request) Successful in 8s
Install-test / Install-test rss-bridge (pull_request) Successful in 1m10s
Validate Helm Charts / Resource quantities (pull_request) Successful in 17s
Validate Helm Charts / Selector match (pull_request) Successful in 14s
Validate Helm Charts / HTTPRoute match (pull_request) Successful in 15s
Validate Helm Charts / Trivy config rss-bridge (pull_request) Successful in 24s
Validate Helm Charts / Install-test (pull_request) Successful in 1m10s
rss-bridge was the twenty-second and last chart of the Gateway-API sweep: 22 charts carried an Ingress template, 21 an HTTPRoute template, and the difference was exactly this one. It had been held back as frozen by #51 - a runner defect, not a property of the chart - and that freeze is lifted. One HTTPRoute carrying ALL THREE hostnames, because nothing differs per host: every name serves the same Service at "/", measured on the live Ingress. spec.hostnames is a list, so one object is the whole answer and a second would repeat one rule. (Unlike homeassistant, whose paths differ per host, or matrix-synapse, whose two routes want different backends.) httpRoute.hostnames is its own MANDATORY value, which is a third shape for this repository and follows from the values rather than from taste. Most charts fall back to ingress.domain; freesailarr and homeassistant reject a hostnames key outright, because a chart-global list could not say which app or host entry it belonged to. This chart has no ingress.domain either, but its names are unambiguous - and the ingress.hosts list it would otherwise reuse carries per-host tlsSecretName values that address nothing once TLS sits on the Gateway's listener. Reusing it would drag a structure whose other half is meaningless into the new mode. rss-bridge.tlsGroups is therefore INERT in Gateway mode, and that is named as a simplification rather than quietly translated: the helper exists only to group hosts by TLS secret name, and there is nothing left to group. The required() on ingress.hosts needed no change to become conditional - it already sits inside the Ingress template behind ingress.enabled - so what is new is that values.yaml and the schema now say so. The ingress.enabled-derived-value screen that caught sure, freesailarr and ytdlp-web-player was run here and came back CLEAN: outside the Ingress template the only readers of an ingress value are NOTES.txt, which knows its own mode, and tlsGroups, called only from behind the switch. The app takes no base URL in its environment, so no "published" helper is needed. First chart of the sweep whose Gateway branch has NO loss to explain, and it was measured rather than assumed (read-only kubectl against the live object): the running Ingress carries the cert-manager annotation plus Helm and Rancher/Fleet bookkeeping, and not one nginx.ingress.kubernetes.io annotation. The group-3 screen came back empty, so NOTES.txt says so positively instead of copying a loss paragraph from another chart. Also corrects the last occurrence in the repository of the outdated sentence "charts in this repo deliberately have no dependencies" (rss-bridge/templates/_helpers.tpl) - dependencies are admissible but expensive since #214, and ytdlp-web-player, stalwart and paperless-ngx use subcharts. The reason the helper is duplicated is unchanged, only its content: sharing it would mean a library chart, a second artefact to publish, version and pull for about one change a year. Minor, not major: additive, defaults unchanged, and relaxing a required is not a tightening. Proven with an EMPTY helm template diff against origin/main for the defaults (229 lines, byte-identical); the Gateway mode with its overlay renders the HTTPRoute in place of the Ingress and nothing else changes. appVersion untouched - 2026-09-30 is the newest date tag the registry has, everything newer is sha-* and latest. ci/run-httproute-match-test.sh goes from 56 assertions over 21 charts to 58 over 22. The overlay at ci/httproute/rss-bridge.yaml has to SUPPLY hostnames, the second in the repository that does (homeassistant's was the first, for a different reason), because this chart has no fallback. NOT PROVEN, and stated in the PR as well: that traffic flows. No Gateway controller runs in CI and no HTTPRoute reaches "Accepted: True" there. For this chart there is additionally NO successful install-test at all - it fails on the Gitea runner in either mode (#51, reproduced on two images while the same chart and image run healthy in the cluster). The chart is deliberately NOT added to ci/excluded-charts.txt: an exclusion would silence the signal, and the day the runner is fixed the test goes green by itself. So the only red contexts expected here are "Install-test / Install-test rss-bridge" and the collective "Validate Helm Charts / Install-test". Fixes #114 Fixes #40 |
||
|
|
6d79d8f270
|
docs(README): group 3 of the Gateway-API sweep, and four corrections it forced
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 15s
Publish Homeassistant Helm Chart / Publish Homeassistant Helm Chart (push) Successful in 13s
Publish Keycloak Helm Chart / Publish Keycloak Helm Chart (push) Successful in 12s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 13s
Publish Stalwart Helm Chart / Publish Stalwart Helm Chart (push) Successful in 13s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 14s
Publish YTDLP Web Player Helm Chart / Publish YTDLP Web Player Helm Chart (push) Successful in 14s
Publish Bulwark Helm Chart / Publish Bulwark Helm Chart (push) Successful in 15s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 12s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 13s
Publish Matrix Synapse Helm Chart / Publish Matrix Synapse Helm Chart (push) Successful in 13s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 13s
SonarQube Analysis / SonarQube Analysis (push) Successful in 53s
The figures: the HTTPRoute render test goes from 37 assertions over 15 charts to **56 over 21**, with the per-chart breakdown, and the sweep is finished except for the frozen rss-bridge - so #114 still cannot be closed while #51 is open. FOUR CORRECTIONS, because three of #114's descriptions of the group-3 shapes and one of this file's own forward-looking claims did not survive contact with the charts. The corrected list is a table in section 4, since that is what somebody will copy from: - bulwark has ONE Ingress, not two. keycloak has two. - bulwark has NO rewrite-target and cannot have one: NEXT_PUBLIC_BASE_PATH is compiled into its bundle. #114's table reached "bulwark, freesailarr" by grepping for the string, and every occurrence in that chart is an explanation of why the annotation does not help. freesailarr is therefore still the only chart the #118 prefix/redirect pairing applies to - the HTTPRoute-test section said bulwark would meet it "when group 3 lands", and now says why it does not. - matrix-synapse does NOT render in a loop over several apps (that is freesailarr): one Ingress object with two HOST rules, which has to become two HTTPRoute objects because spec.hostnames applies to the whole object. - stalwart does not render a routing object per node group - its Services and StatefulSets do; one route targets ingress.nodeGroup's own http Service. THREE NEW CONVENTIONS, written where the next sweep will read them: - SCREEN THE LIVE OBJECT, not only the chart. Four of group 3's six charts carried an annotation the chart never sets and the Gateway mode drops - keycloak's proxy buffers on BOTH its Ingresses, matrix-synapse's body size and snippet, stalwart's whole CORS block, bulwark's body size. None of them appears in a git grep of this repo. At #118 this was found after shipping; here it was measured beforehand with read-only kubectl, and that measurement is now part of the procedure. - READ THE DIRECTIVE before calling an annotation load-bearing. matrix-synapse's configuration-snippet uses more_set_headers, which is the headers-more module's OUTPUT-header directive - so those headers go on the response and Synapse never sees them. The scariest-looking loss in the group is not a loss, and its NOTES.txt says not to re-create it. - "NO COUNTERPART" IS A CLAIM TO CHECK AGAINST THE PINNED CRDs, and for CORS it is false. Grepped out of gateway-api v1.6.3 standard channel: HTTPRoute filters carry a full cors field (GEP-1767), Support: Extended, and responseHeaderModifier is Extended too rather than Core. stalwart still renders neither, and the three reasons are recorded - no CORS values to render from, Extended support is not support (Cilium accepts the filter and forwards the preflight anyway), and the preflight is the half a bare header filter cannot do while Authorization in the allowed headers makes every request preflighted. Also: pathType ImplementationSpecific has no counterpart and is a chart DEFAULT in homeassistant - PathPrefix on "/", render abort on any other path, and RegularExpression deliberately not offered. The ingress.enabled-derived-value screen is closed: ytdlp-web-player was the third and last find, and its guard is the one that is needed MORE in Gateway mode, because the ingress-nginx admission webhook never sees an HTTPRoute and the silent outcome is an authentication bypass that depends on DNS. A subchart does not come along: oauth2-proxy has no Gateway support, so in the recommended configuration the subchart's Ingress publishes the release in both of the chart's modes. And stalwart is the only chart in the sweep still rendering a Certificate in Gateway mode, for its mail listeners. The homeassistant documented-exception bullet now covers its Gateway mode: no httpRoute.hostnames key (the second chart without it), one route per ingress.hosts entry, index-based route names and what reordering the list costs. Part of #114 |
||
|
|
d36112aaf6
|
chore(renovate): group the freesailarr stack into one PR per cycle
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
Install-test / Install-test freesailarr (pull_request) Successful in 4m9s
Validate Helm Charts / Install-test (pull_request) Successful in 4m9s
Validate Helm Charts / Detect changed charts (pull_request) Successful in 6s
Validate Helm Charts / Build freesailarr (pull_request) Successful in 7s
Validate Helm Charts / Trivy config freesailarr (pull_request) Successful in 24s
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
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. |
||
|
|
f202fdc3fc
|
fix(freesailarr): redirect the bare /qbittorrent prefix in Gateway mode
Validate Helm Charts / Resource quantities (pull_request) Successful in 15s
Validate Helm Charts / Install-test (pull_request) Successful in 4m19s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 12s
Validate Helm Charts / Detect changed charts (pull_request) Successful in 5s
Validate Helm Charts / Selector match (pull_request) Successful in 14s
Validate Helm Charts / Trivy config freesailarr (pull_request) Successful in 22s
Install-test / Install-test freesailarr (pull_request) Successful in 4m19s
SonarQube Analysis / SonarQube Analysis (push) Successful in 48s
Validate Helm Charts / HTTPRoute match (pull_request) Successful in 14s
Validate Helm Charts / Build freesailarr (pull_request) Successful in 8s
Publish Freesailarr Helm Chart / Publish Freesailarr Helm Chart (push) Successful in 12s
qBittorrent has no base-path setting, so the chart serves it under a prefix and strips that prefix before the request reaches it. For the slashed form that is correct. For the BARE form - /qbittorrent, no trailing slash - the request is rewritten to "/" and qBittorrent answers 200 with its login page, while the browser's document URL keeps the prefixless form. Every relative link in that page therefore resolves against the host root, and on the shared host the root is seerr: the login form comes up unstyled and does nothing while a different application answers its assets and XHRs. Nothing reports an error at any level. The shared-domain HTTPRoute now carries a second rule for qBittorrent: an Exact match on qbittorrent.ingress.path with a RequestRedirect filter that replaces the full path with the slashed form, statusCode 301. The Gateway API ranks an Exact path match above every prefix match, so it wins over both qBittorrent's own PathPrefix rule and seerr's "/" wherever it is emitted - the rules' length-descending order stays a rendering convention. 301 rather than 308 because only 301 and 302 are Core in that API; an Extended value an implementation declines makes it set Accepted=False on the WHOLE route, which would take the four neighbouring apps' routing down with it, and that would also contradict decision 1 of Gitea #114. statusCode is written out because it defaults to 302. The rule is guarded on the app being qbittorrent and its path not being "/", and it reads the path from the value, never a literal. The INGRESS BRANCH IS UNCHANGED, deliberately. The remedy there is an nginx configuration-snippet annotation, and the chart cannot ship it: snippets are ignored unless the controller runs with allow-snippet-annotations=true, off by default since ingress-nginx 1.9 and only switched on by accepting annotation risk level "Critical". A shipped annotation would be a silent no-op on every cluster keeping the default. The working snippet, that price, and why a third Ingress object with pathType: Exact is not a substitute - buildLocation in ingress-nginx controller-v1.14.5 returns a regex location in its enforceRegex branch before reaching the PathTypeExact branch, and enforceRegex is computed once per nginx server over every location of the hostname, one of which carries rewrite-target - are all written out in values.yaml instead. ci/run-httproute-match-test.sh gains a third assertion, per HTTPRoute object: a rule stripping a prefix with ReplacePrefixMatch requires a rule in the same route matching that path Exactly and carrying a RequestRedirect filter. This is what stops the gap reappearing; bulwark is the second rewrite-target chart and meets the same check when group 3 of Gitea #114 lands. 37 assertions over 15 charts, up from 36. Both fail paths were exercised: the rule deleted, and the redirect pointed at a path other than the one stripped. Minor and not major: with the Ingress branch untouched and httpRoute.enabled defaulting to false, no existing release's render changes by a byte and no value becomes required. Shown by an empty helm template diff against origin/main in both Ingress shapes, shared-domain and per-app hosts. Fixes #118 |
||
|
|
471ea5ba5d
|
docs(README): the remaining-charts count, without double-counting rss-bridge
Validate Helm Charts / Build zipline (pull_request) Successful in 8s
Validate Helm Charts / Trivy config zipline (pull_request) Successful in 22s
Validate Helm Charts / Build outline (pull_request) Successful in 8s
Validate Helm Charts / Build paperless-ngx (pull_request) Successful in 8s
Validate Helm Charts / Trivy config outline (pull_request) Successful in 16s
Install-test / Install-test freesailarr (pull_request) Successful in 4m2s
Install-test / Install-test outline (pull_request) Successful in 2m50s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 14s
Publish Calibre Web Automated Helm Chart / Publish Calibre Web Automated Helm Chart (push) Successful in 14s
Publish Freesailarr Helm Chart / Publish Freesailarr Helm Chart (push) Successful in 14s
Publish Outline Helm Chart / Publish Outline Helm Chart (push) Successful in 12s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 11s
Validate Helm Charts / Detect changed charts (pull_request) Successful in 4s
Validate Helm Charts / Resource quantities (pull_request) Successful in 15s
Validate Helm Charts / Selector match (pull_request) Successful in 13s
Validate Helm Charts / HTTPRoute match (pull_request) Successful in 14s
Validate Helm Charts / Build calibre-web-automated (pull_request) Successful in 7s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 14s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 12s
Publish Paperless-NGX Helm Chart / Publish Paperless-NGX Helm Chart (push) Successful in 11s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 10s
Validate Helm Charts / Trivy config calibre-web-automated (pull_request) Successful in 20s
Validate Helm Charts / Build freesailarr (pull_request) Successful in 8s
Validate Helm Charts / Trivy config paperless-ngx (pull_request) Successful in 20s
Validate Helm Charts / Trivy config freesailarr (pull_request) Successful in 23s
Install-test / Install-test calibre-web-automated (pull_request) Successful in 2m22s
Install-test / Install-test paperless-ngx (pull_request) Successful in 3m45s
Install-test / Install-test zipline (pull_request) Successful in 2m23s
Validate Helm Charts / Install-test (pull_request) Successful in 15m22s
Publish Zipline Helm Chart / Publish Zipline Helm Chart (push) Successful in 10s
SonarQube Analysis / SonarQube Analysis (push) Successful in 44s
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 |
||
|
|
bd1fdc09a0
|
feat: Gateway-API as an alternative to the Ingress in five annotation charts
Validate Helm Charts / Detect changed charts (pull_request) Successful in 4s
Validate Helm Charts / Resource quantities (pull_request) Successful in 14s
Validate Helm Charts / Selector match (pull_request) Successful in 14s
Validate Helm Charts / Trivy config freesailarr (pull_request) Successful in 24s
Install-test / Install-test freesailarr (pull_request) Successful in 3m55s
Install-test / Install-test paperless-ngx (pull_request) Successful in 3m52s
Install-test / Install-test zipline (pull_request) Successful in 2m6s
Validate Helm Charts / Install-test (pull_request) Successful in 14m50s
Validate Helm Charts / HTTPRoute match (pull_request) Successful in 14s
Validate Helm Charts / Build freesailarr (pull_request) Successful in 7s
Validate Helm Charts / Build outline (pull_request) Successful in 7s
Validate Helm Charts / Trivy config outline (pull_request) Successful in 17s
Validate Helm Charts / Trivy config zipline (pull_request) Successful in 19s
Install-test / Install-test calibre-web-automated (pull_request) Successful in 2m11s
Install-test / Install-test outline (pull_request) Successful in 2m44s
Validate Helm Charts / Build paperless-ngx (pull_request) Successful in 8s
Validate Helm Charts / Build zipline (pull_request) Successful in 8s
Validate Helm Charts / Build calibre-web-automated (pull_request) Successful in 7s
Validate Helm Charts / Trivy config calibre-web-automated (pull_request) Successful in 19s
Validate Helm Charts / Trivy config paperless-ngx (pull_request) Successful in 19s
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 |
||
|
|
33d7cedbab
|
feat: Gateway-API as an alternative to the Ingress in eight charts
Validate Helm Charts / Selector match (pull_request) Successful in 14s
Validate Helm Charts / HTTPRoute match (pull_request) Successful in 17s
Validate Helm Charts / Trivy config bentopdf (pull_request) Successful in 19s
Validate Helm Charts / Build garage (pull_request) Successful in 8s
Validate Helm Charts / Build urlaubsverwaltung (pull_request) Successful in 6s
Validate Helm Charts / Trivy config garage (pull_request) Successful in 18s
Validate Helm Charts / Trivy config patchmon (pull_request) Successful in 16s
Validate Helm Charts / Trivy config rallly (pull_request) Successful in 18s
Install-test / Install-test bentopdf (pull_request) Successful in 1m10s
Validate Helm Charts / Trivy config sure (pull_request) Successful in 18s
Install-test / Install-test rallly (pull_request) Successful in 1m39s
Publish BentoPDF Helm Chart / Publish BentoPDF Helm Chart (push) Successful in 10s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 9s
Publish Rallly Helm Chart / Publish Rallly Helm Chart (push) Successful in 10s
Publish SearXNG Helm Chart / Publish SearXNG Helm Chart (push) Successful in 10s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 9s
Publish Sure Helm Chart / Publish Sure Helm Chart (push) Successful in 10s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 10s
SonarQube Analysis / SonarQube Analysis (push) Successful in 43s
Validate Helm Charts / Detect changed charts (pull_request) Successful in 5s
Validate Helm Charts / Build bentopdf (pull_request) Successful in 9s
Validate Helm Charts / Build rallly (pull_request) Successful in 7s
Validate Helm Charts / Build searxng (pull_request) Successful in 6s
Validate Helm Charts / Build voucher-vault (pull_request) Successful in 7s
Validate Helm Charts / Trivy config searxng (pull_request) Successful in 22s
Install-test / Install-test patchmon (pull_request) Successful in 1m24s
Install-test / Install-test sure (pull_request) Successful in 2m39s
Install-test / Install-test voucher-vault (pull_request) Successful in 2m9s
Validate Helm Charts / Install-test (pull_request) Successful in 13m15s
Install-test / Install-test urlaubsverwaltung (pull_request) Successful in 1m50s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 10s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 10s
Publish Patchmon Helm Chart / Publish Patchmon Helm Chart (push) Successful in 9s
Validate Helm Charts / Resource quantities (pull_request) Successful in 15s
Validate Helm Charts / Build patchmon (pull_request) Successful in 7s
Validate Helm Charts / Build sure (pull_request) Successful in 7s
Validate Helm Charts / Trivy config urlaubsverwaltung (pull_request) Successful in 17s
Validate Helm Charts / Trivy config voucher-vault (pull_request) Successful in 19s
Install-test / Install-test garage (pull_request) Successful in 1m17s
Install-test / Install-test searxng (pull_request) Successful in 1m7s
Publish Garage Helm Chart / Publish Garage Helm Chart (push) Successful in 9s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 9s
Publish Urlaubsverwaltung Helm Chart / Publish Urlaubsverwaltung Helm Chart (push) Successful in 10s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 10s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 10s
Publish VoucherVault Helm Chart / Publish VoucherVault Helm Chart (push) Successful in 10s
Group 1 of the #114 sweep: the charts that render exactly one Ingress object and set no behaviour-bearing nginx annotation - bentopdf, garage, patchmon, rallly, searxng, sure, urlaubsverwaltung, voucher-vault. Each gets the form the pilot (cyberchef, PR #115) fixed and template-chart carries: an httpRoute block (enabled/parentRefs/hostnames, 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 seven of the eight, and for sure identical in every rendered value (the diff is two YAML comment lines). Three findings that were not in the plan, each argued in the file it changes: sure derived RAILS_ASSUME_SSL/RAILS_FORCE_SSL from ingress.enabled alone. Gateway mode sets that false by construction while TLS is still terminated at the Gateway, so both flags would have gone false: insecure cookies, no HSTS, http:// links, with a Ready pod and a working UI as the only symptom. They now read sure.publishedOverTls (ingress.enabled OR httpRoute.enabled), which is ingress.enabled while httpRoute.enabled is false - hence still Minor. garage claimed, in its Ingress template and its schema, that nothing outside that template reads ingress.domain. The HTTPRoute reads it, so the claim now holds only while both modes are off. Corrected rather than left standing. garage also depends on an upload limit it does not set: its annotations comment tells the operator to set proxy-body-size: "0" because ingress-nginx truncates S3 uploads at 1 MB. proxy-body-size has no Gateway-API counterpart, so its Gateway-mode NOTES prints that the limit is the Gateway's. README's list of affected charts named five and now names garage as a sixth. clusterIssuer needed no change in any of the eight: its only required() sits inside the Ingress template behind ingress.enabled, the same no-op the pilot found. Annotation guidance that stops applying in Gateway mode moved into the Gateway branch of NOTES.txt for bentopdf (X-Robots-Tag) and searxng (forward-auth). CI: one render overlay per chart under ci/httproute/, so the assertion is not vacuum-green - ci/run-httproute-match-test.sh goes from 4 held assertions over 2 charts to 20 over 10. 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 garage the Ingress is the only place its Service port is referenced by NAME. Not proven, by construction: that traffic flows. No Gateway controller runs in CI. Not run locally either: k3d, Docker, Trivy. Part of #114 |
||
|
|
de92e59bb0
|
docs(README): the HTTPRoute test's figure from the CI run, not from the local subset
Validate Helm Charts / Detect changed charts (pull_request) Successful in 4s
Validate Helm Charts / Resource quantities (pull_request) Successful in 14s
Validate Helm Charts / Selector match (pull_request) Successful in 13s
Validate Helm Charts / Build cyberchef (pull_request) Successful in 7s
Validate Helm Charts / Trivy config cyberchef (pull_request) Successful in 15s
Validate Helm Charts / Trivy config template-chart (pull_request) Successful in 17s
Validate Helm Charts / HTTPRoute match (pull_request) Successful in 13s
Validate Helm Charts / Build template-chart (pull_request) Successful in 5s
Install-test / Install-test cyberchef (pull_request) Successful in 58s
Validate Helm Charts / Install-test (pull_request) Successful in 58s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 19s
Publish Cyberchef Helm Chart / Publish Cyberchef Helm Chart (push) Successful in 19s
SonarQube Analysis / SonarQube Analysis (push) Successful in 43s
The authoring environment cannot reach the private Helm registry, so the three charts with a dependency were missing from the local run and the number quoted 27 charts. CI run 719 renders all 30 and holds the same 4 assertions - that is the figure a later deviation should be compared against, so it is the one written down. |
||
|
|
50f683776e
|
feat(cyberchef): Gateway-API as an alternative to the Ingress (pilot)
Validate Helm Charts / Detect changed charts (pull_request) Successful in 4s
Validate Helm Charts / HTTPRoute match (pull_request) Successful in 13s
Validate Helm Charts / Build cyberchef (pull_request) Successful in 6s
Validate Helm Charts / Trivy config cyberchef (pull_request) Successful in 18s
Validate Helm Charts / Trivy config template-chart (pull_request) Successful in 17s
Install-test / Install-test cyberchef (pull_request) Successful in 1m0s
Validate Helm Charts / Install-test (pull_request) Successful in 1m0s
Validate Helm Charts / Resource quantities (pull_request) Successful in 14s
Validate Helm Charts / Build template-chart (pull_request) Successful in 7s
Validate Helm Charts / Selector match (pull_request) Successful in 12s
The Gateway-API pilot of Gitea #114: one chart, the convention, the CI that holds it. The remaining 20 charts follow in their own PRs. The form, additive and off by default: httpRoute: enabled: false # counterpart to ingress.enabled parentRefs: [] # the Gateway - a platform object hostnames: [] # empty = [ingress.domain] Only HTTPRoute with parentRefs is rendered: no Gateway, no gatewayClassName, no Certificate, and no implementation-specific policy object. That subset is identical on nginx-gateway-fabric, Cilium, Envoy Gateway and Istio, which is what lets the implementation be chosen later without touching the charts again. Class and certificate move to the platform. Four guards, all against silent failures: - mutually exclusive with ingress.enabled, with a render abort naming BOTH value paths. A migration runs over a second hostname, not in parallel. - parentRefs mandatory in Gateway mode, render abort on the empty list: a route without a parent is created, is schema-valid, and routes nothing. - hostnames empty means [ingress.domain], so the hostname has one source. - the mode is a VALUE, not auto-detection: .Capabilities.APIVersions is unreliable at template time and a missing CRD fails at install time. ingress.clusterIssuer was already only read inside the Ingress template, so the "conditionally required" half of #114 needed no change in this chart - checked rather than assumed, and it has to be re-checked per chart in the sweep. CI, both halves, because neither covers the other: - the install-test installs cyberchef in GATEWAY mode against the REAL Gateway-API CRDs (standard channel v1.6.3, pinned and Renovate-tracked, applied --server-side). A schema-less stub would accept a malformed route and prove only that something was created. The Ingress path of this one chart is no longer installed; it is a built-in type that never routed in k3d anyway, and helm lint renders this chart's defaults - Ingress mode - on every PR. - ci/run-httproute-match-test.sh, over every chart on every PR: every backendRefs entry must name a Service, on a port, that the SAME render creates, and parentRefs must not be empty. The CRDs accept both of those mistakes, so the schema cannot catch them, and the only report would be a route status no controller writes in CI. What stays unprovable, and is written down instead of implied: that traffic flows. No Gateway controller runs in k3d. Minor: additive with unchanged defaults, proven by an empty helm template diff against origin/main for the default values. Part of #114 |
||
|
|
6ade74db80
|
feat(stalwart): spread each node group over nodes and give each one a PDB
A two-replica node group was redundancy on paper: nothing kept both pods off one node, and no PodDisruptionBudget kept a drain from taking both at once. Both objects are now rendered per node group, gated on that group's COMPUTED replica count being >= 2. The distribution is a promise, not an effort: maxSkew 1 on kubernetes.io/hostname with whenUnsatisfiable DoNotSchedule, selecting only this group's own pods, so an "mx" and a "full" pod may share a node. The selector comes from the chart (stalwart.groupLabels), never from the operator - a spread constraint whose selector matches no pod is trivially satisfied, distributes nothing and reports nothing. nodeAffinityPolicy: Ignore is set explicitly, and that is the load-bearing detail. Its API default is Honor, under which a narrowing nodeSelector shrinks the set of counted domains until both replicas fit into one: skew 0, both pods co-located, no Pending, no event, and a selector that is correct - so no check can see it. With Ignore the same narrowing gives skew 2 and the second pod is Pending. A render abort was the alternative and would also have forbidden a nodeSelector spanning five nodes, which is legal. A user entry colliding with the chart's own on (topologyKey, whenUnsatisfiable) aborts the render naming both values, because the API rejects the duplicate with a message about a list index and an apply-based path merges the two over patchMergeKey: topologyKey silently, dropping one selector. Minor rather than major, proved with a diff rather than argued: with the shipped defaults (full: 1, mx: 0) and with ci/stalwart/test-values.yaml, helm template renders byte-for-byte what 1.0.1+upv0.16.24 rendered - both diffs empty. New in CI, over every chart on every PR: ci/run-selector-match-test.sh holds every rendered PodDisruptionBudget selector and every topologySpreadConstraints labelSelector against the labels of the pod templates in the same render. Helm itself is the YAML parser (ci/yaml2json), because the runners have neither pyyaml nor yq and a hand-written parser could read nothing and report nothing. podAffinity/podAntiAffinity are out of scope; matchExpressions-only selectors are reported as not checked rather than counted as passing. Two anti-vacuum guards: a chart with an overlay under ci/selector-match/ that renders no selector fails, and a run that checks no selector at all fails. Fixes #96 |
||
|
|
b39f36ff5c
|
feat(bulwark): new chart for Bulwark Webmail (1.0.0+up1.12.0)
A JMAP webmail client for the stalwart chart (Gitea #65): StatefulSet, one PVC at /app/data, Service plus headless Service, Ingress at a host root, two OnePasswordItems, three probes, computed limits, replicas plus scaleDown, strict values.schema.json. Everything below was measured against the source at tag 1.12.0 rather than read out of the documentation, which was wrong about three of them. THE DEFINING PROPERTY: THE VOLUME BEATS THE CHART, FOR EVERY VALUE, SILENTLY. configManager.get resolves "admin override > env var > default", the admin dashboard may write any key of CONFIG_ENV_MAP, and nothing reports that it just disconnected the chart - so a helm upgrade can succeed and change nothing. Upstream documents this for the server URL only; it is true of everything. Exactly three values escape it (SESSION_SECRET(_FILE), ADMIN_PASSWORD, BULWARK_TELEMETRY). The value that closes it, bulwark.adminConfigReadOnly, defaults to false because its price is threefold: it freezes the settings policy (and with it the only way to move Web Push off the vendor relay), it freezes an override that ALREADY exists instead of removing it, and on a fresh install it stops admin.json from being written at all - so the admin dashboard never comes up and its login answers HTTP 500 while the pod stays Ready. OIDC-only sign-in, with the real way back out of a lockout: not helm upgrade with OAUTH_ONLY=false (oauthOnly goes through the same precedence chain) but removing the override in the admin dashboard. That is why ADMIN_PASSWORD is set - the dashboard is the door. It generates no random password and logs nothing, so the #65 analogy does not apply. SESSION_SECRET_FILE and OAUTH_CLIENT_SECRET_FILE are both used - the better path garage (#81) and keycloak (#83) did not have - in one projected volume mounted WITHOUT subPath, because both are re-read on every use and a subPath mount is never refreshed, and at mode 0440 because secret files are owned by root and fsGroup only supplies the group. Two backup statements, both at the value: without the session secret the synchronized settings are unreadable out of an intact volume, and if the dashboard's OAuth setup was ever used the client secret sits in config.json on that volume in cleartext. Probes: all three a plain GET on /api/health, with the limit written at the readiness value - it carries process, HTTP acceptance, a free event loop and Next.js routing, and nothing about the reported state or about Stalwart. No ConfigMap, so the checksum/config rule (#82) has nothing to attach to, and values.yaml says so explicitly; the op:// paths are covered by checksum/secret-ref. Renovate: no versioningTemplate, measured rather than assumed. 315 tags in eight shapes (releases, a second v-prefixed family of the same images, an -always flavour, floating minors and names, commit SHAs, betas, attestations) and plain docker versioning excludes all six unwanted ones. Dry runs at 1.12.0, 1.11.0 and 1.10.0 are recorded in the block. Part of #89 - deliberately not "Fixes", because one binding decision of the issue was changed out of necessity and wants ratification rather than an automatic close: the plan specifies HEAD /api/health for all three probes, and a Kubernetes probe cannot send HEAD at all (HTTPGetAction has no method field; the kubelet always issues GET). The alternative, an exec probe, would mean starting a Node process every few seconds in an image that carries no curl and no wget. GET is used instead and is marginally more capable than HEAD, so every limit the issue argues is unchanged - but the decision is the maintainer's to confirm. |
||
|
|
320257c742 |
fix(keycloak): readiness could not fail on the database, and the measured budget
Two corrections found while verifying the first CI run, not from the plan. THE DEFAULT THAT MADE THE READINESS PROBE TOOTHLESS. Keycloak's DATABASE health check is the one row of upstream's "Available Checks" table whose "Requires Metrics" column says yes; Cluster, Graceful shutdown and Keycloak Initialized say no. So with metrics-enabled=false - this chart's default until now - /health/ready answers 200 while the database is gone, and the pod keeps taking logins it cannot serve. The probe path was right and the aggregate behind it contained nothing external, which is the urlaubsverwaltung defect (#218) in a new hiding place: not a health group this time, but an option named for observability, where nobody looks when reviewing a probe. keycloak.metrics.enabled therefore defaults to TRUE, and its comment argues about readiness rather than about scraping. The price is stated at the value: /metrics then answers on the management port, unauthenticated, carrying realm names, client ids and login counters - on a port that is ClusterIP-only and in no Ingress. Switching it off is now documented as a decision about whether readiness can fail at all. The CI fixture keeps it on and says why, so a green run really does touch the database. THE STARTUP BUDGET NOW CARRIES ITS MEASUREMENT. From the install-test of PR #87 on the Gitea docker runner (k3s v1.35.5, two replicas, empty database, image pulled during the run): 71.8 seconds from `helm install` to BOTH pods Ready - first at ~70.8s, second one second later, 0 restarts. The 600s budget stays, with the reason for the headroom written down: everything in that run was the cheap case, and a first install against a real database serializes the second replica behind the first's Liquibase migration lock instead of letting it arrive a second later. AND AN OVERCLAIM OF MY OWN, REMOVED. ci/keycloak/test-values.yaml said a green run proves jdbc-ping found the second replica. It does not: readiness reports one node's own health, so a node that never found its peer is Ready, healthy and alone - and two such nodes are two islands that each believe they are the cluster. What the test does rule out is the louder half, a replica that cannot start or cannot share the database. The Infinispan view has to be read out of the log, which is what step 2 of NOTES.txt tells the operator to do, now with the note that no probe and no install-test replaces that step. Measurement point 4 of the plan therefore stays open. Part of #83 |
||
|
|
4c1cff1744 |
feat(keycloak): new chart for Keycloak (1.0.0+up26.8.0)
Two replicas with cache=ispn, clustered through Keycloak's default jdbc-ping stack: discovery runs over the PostgreSQL that is there anyway, so no headless Service and no JGroups wiring. Deployment rather than StatefulSet - Keycloak 26 keeps everything in the database, user sessions included. The chart's defining property is that it has TWO INIT CONTAINERS and needs both. db, health-enabled and metrics-enabled are Keycloak BUILD options (verified in the 26.8.0 option table), the published image is augmented with their defaults, and supplying different ones at runtime makes Keycloak rewrite /opt/keycloak/lib/quarkus at start - which readOnlyRootFilesystem: true forbids and which upstream describes as a start that "creates an image that is mutable". Not setting db is no way out: the probes alone force it, because health-enabled is a build option too. An empty emptyDir over that path fails as well, because QuarkusEntryPoint loads quarkus/quarkus-application.dat before it dispatches any command, `build` included. So one init container seeds the directory into a volume, the next runs `kc.sh build` into it, and the serving container runs `start --optimized` - every container keeping the full hardening baseline, with no documented deviation to carry. Build options therefore exist in exactly one place, and the asymmetry is enforced rather than trusted: the serving container passes no build option at all, and a build option's variable name in keycloak.env fails the render with a message naming it. Hostname handling is the security-relevant part. KC_HOSTNAME is derived from ingress.domain as a full URL (which upstream requires alongside hostname-admin), hostname-strict stays at its default true, and proxy-headers is set rather than optional. The admin hostname is NOT presented as an access control: upstream states that hostname-admin "does not prevent accessing the Administration REST API endpoints via the frontend URL specified by the hostname option", so the chart renders no /admin deny rule and values.yaml, NOTES.txt and the wiki all say in the same words that restricting that API is reverse-proxy work. The management port 9000 is a containerPort and a ClusterIP Service for in-cluster scraping, and is in no Ingress: it serves /health/* and optionally /metrics without authentication. Liveness uses /health/live and deliberately not /health or /health/ready - those include the database, and a liveness probe on them would restart the whole cluster for as long as the database is away. Also in this change: - First PodDisruptionBudget in this repository, which makes it a convention (README section 11): minAvailable as a value, and rendered only while the computed replica count is >= 2, because minAvailable: 1 with one replica blocks every node drain forever with a message that names the PDB rather than the replica count. - First quay.io image, so quay.io joins the trusted registries in ci/trivy/data/ksv0125.yaml - configured rather than suppressed, so AVD-KSV-0125 keeps being able to say something. - Renovate block with NO version constraint, which is the counter-case to garage and stalwart and is measured rather than assumed: docker versioning already excludes the -N rebuild tags, the floating minors and latest/nightly. Three dry-run fixtures are recorded at the block, and against the committed config 26.7.4 offers exactly 26.7.5 (patch) and 26.8.0 (minor). - No automerge exception, by decision (Gitea #83): the global automerge applies, and the minor-upgrade procedure plus `kc.sh update-compatibility` is documented in NOTES.txt instead. - Two upstream defaults deviated from, each with its reason at the value: db-pool-max-size 20 instead of 100 (two replicas at 100 ask a default PostgreSQL for twice its max_connections), and uid 1000 / gid 0 instead of this repo's 20000/1000, because the image declares keycloak:x:1000:0 and chowns its install tree to that pair. Part of #83 |
||
|
|
79b7e44fdc
|
feat(garage): new chart for the Garage object store
Install-test / Install-test garage (pull_request) Successful in 1m15s
Validate Helm Charts / Install-test (pull_request) Successful in 1m15s
Validate Helm Charts / Resource quantities (pull_request) Successful in 11s
Validate Helm Charts / Detect changed charts (pull_request) Successful in 3s
Validate Helm Charts / Build garage (pull_request) Successful in 6s
Validate Helm Charts / Trivy config garage (pull_request) Successful in 16s
One Garage node with replication_factor = 1, built from template-chart to the style guide rather than copied from upstream's in-tree chart (Gitea #81). The single-node trade is deliberate and its price is named at the value: the factor applies to the metadata as well, where upstream calls a corrupted database "generally not recoverable" and a replicated volume replicates the corruption faithfully. The three countermeasures that work at factor 1 are therefore mandatory, not optional - metadata_fsync = true (off in Garage's defaults), the automatic metadata snapshots, and those snapshots on a second PVC. Storage: PVC 1 carries metadata_dir AND data_dir as two subdirectories, because volume snapshots are atomic per volume and that is what keeps index and blocks consistent with each other; PVC 2 carries metadata_snapshots_dir, because a recovery point must not die with the volume whose corruption it should repair. Garage creates all three directories itself, which matters because the image is FROM scratch and has no mkdir. Two corrections to the approved plan, both verified against the source at tag v2.4.1 and decided on the issue before implementation: - The RPC secret arrives as GARAGE_RPC_SECRET via secretKeyRef, not through rpc_secret_file. The file form cannot work in a non-root pod: read_secret_file rejects any mode with a group or other bit set (src/garage/secrets.rs:144, mask 0o077) while the kubelet OR-s the group-read bit onto every file of a secret volume once fsGroup is set (pkg/volume/volume_linux.go), and Kubernetes can set the group of such a file but never its owner. The mechanism is documented at the value so the next round does not "fix" it back. - The first start is done by the server, not by hand: garage server --single-node creates the layout in-process before any listener opens, is idempotent on the layout version and has no --version argument that could be reused, and --default-access-key/--default-bucket import key and bucket FROM the vault item. Both premises of the original "manual first start" decision fell with that. Probes follow from the same source reading: readiness is GET /health on the admin port, which answers 503 while no layout exists; liveness and startup are a TCP connect, because a probe that can only fail would restart a node waiting for its layout forever (same lesson as zipline, README 12.3). The S3 listener binds independently of the layout, so a TCP probe on 3900 would have been the lie the plan warned about. CI: fixtures under ci/garage/ rather than an exclusion - with --single-node the pod reaches Ready on its own, which makes the install-test an assertion about the layout, the one-PVC subdirectory layout and readOnlyRootFilesystem instead of a formality. The Renovate block carries a regex versioning anchored to ^v<major>.<minor>.<patch>$, because the registry receives 40-character commit-SHA tags almost daily (measured 2026-10-03: the newest 22 tags are all of that kind) and the four v1.99.x-internal builds sort above the v1.3.x line. Part of #81 |
||
|
|
a9ca5302de
|
feat(paperless-ngx): optional valkey subchart, password from the redis item
Validate Helm Charts / Detect changed charts (pull_request) Successful in 4s
Install-test / Install-test paperless-ngx (pull_request) Successful in 3m31s
Validate Helm Charts / Install-test (pull_request) Successful in 3m31s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 10s
Publish Paperless-NGX Helm Chart / Publish Paperless-NGX Helm Chart (push) Successful in 10s
SonarQube Analysis / SonarQube Analysis (push) Successful in 33s
Validate Helm Charts / Resource quantities (pull_request) Successful in 12s
Validate Helm Charts / Trivy config paperless-ngx (pull_request) Successful in 22s
Validate Helm Charts / Build paperless-ngx (pull_request) Successful in 6s
paperless-ngx cannot run without a Redis - PAPERLESS_REDIS is the Celery broker - so every installation needed a second Helm release whose address lived in a 1Password item and in neither chart. This adds our own published valkey chart as an optional dependency, off by default and gated on valkeySubchart.enabled (not valkey.enabled, which the subchart's strict schema rejects and which Helm would only have complained about for whoever first switched it on). Additive, default behaviour unchanged: minor, 12.1.3+up3.2.1 -> 12.2.0+up3.2.1. Configured as a QUEUE (PVC + noeviction, the subchart's defaults), unlike stalwart's cache. Read off the 3.2.1 source rather than assumed: one PAPERLESS_REDIS url serves five consumers - the Celery broker, the result backend (Redis in 3.x, the database before), the channels pub/sub layer and two Django caches. allkeys-lru would not be a trade here but actively wrong, because LRU evicts typeless across the whole keyspace and a backlog of waiting consume tasks is exactly what it reads as cold; a dropped task is a document silently never processed. The price, named in values.yaml rather than smoothed over: two of the five consumers are caches, so a full instance refuses writes - new tasks included - and the answer is memory sizing, not a policy change. All three cache-like consumers are TTL-bounded, which is what keeps that price small. The password comes from the SAME item as the Redis connection, in a third field auth-conf holding the complete line "requirepass <password>". It cannot DEFAULT to secrets.redisSecretRef, and that is a property of Helm, not an omission: a values.yaml is never templated and a parent cannot compute a subchart's value. Measured - a template expression there lands verbatim in the subchart's itemPath and fails as a YAML parse error naming neither the value nor the cause. Forgetting it is therefore caught in values.schema.json, by a conditional rule rather than a fail() in a template. Also measured, and the reason: Helm renders a subchart's templates BEFORE the parent's, so a guard in this chart loses the race to the subchart's own required(), which reports "secrets.authSecretRef" and cannot know the path belongs to the redis item. A check that cannot fire is the mistake found while testing #77, so the first draft of that guard was removed rather than shipped. The schema runs before any rendering and names /valkey/secrets/authSecretRef. One trap found while testing and closed here: "valkeySubchart: null" DELETES the key, Helm then cannot resolve the condition path and treats the dependency as ENABLED - so erasing the switch turned the subchart on with no password set. Unlike every other block in this chart (issue #99), that one is rejected by the schema instead of defaulting. The install-test now runs with the subchart ON and the throwaway redis Deployment removed: the url in the fixtures names ci-valkey-service, so the bundled Valkey is the broker the release actually uses. #65 covered this path by a render only - deliberately, since an unused Valkey would have made that test look like it covered clustering. This is the first real installation of the valkey-subchart path. Documented, not glossed: the password now sits in that item twice and a rotation has to touch both fields; with the subchart on the vault url is a mandatory step, not a hint; and a OnePasswordItem materializes every field of its item, so both Secrets carry all three keys - untidy, harmless, and written down so nobody "tidies it up". Fixes #78 |
||
|
|
07c75e80c1 |
feat(stalwart): take our own published valkey chart as the subchart
Replaces the dependency on upstream's valkey chart with this repo's own, resolved from the published index: version: "1.0.0+up9.1.2.alpine" repository: "https://git.jk-dev.de/api/packages/jlab-cluster/helm" The condition is "valkeySubchart.enabled", NOT "valkey.enabled", and that is forced rather than stylistic. Everything under "valkey:" is handed to the subchart as its values and validated against ITS schema, and our valkey chart ships additionalProperties: false with no "enabled" property - so "valkey.enabled" is rejected by the subchart: valkey: - at '': additional properties 'enabled' not allowed Measured. And because Helm skips a DISABLED subchart's schema validation, that error would have surfaced only for whoever first set it to true: every render, lint and install-test in CI would have stayed green. Adding "enabled" to valkey/values.schema.json was rejected as the repair - that chart does not read the key, and it would have needed its own PR and a publish first. The host helper was also wrong for our chart and is fixed: our Service is "<release>-valkey-service", not upstream's bare "<release>-valkey". The values block is rewritten for our chart's schema, with the pair chosen rather than inherited: CACHE (persistence off + allkeys-lru), because everything Stalwart keeps in the in-memory store - rate limiters, auth tokens, sessions, auto-ban entries, coordination pub/sub - is reconstructible, and the node-id lease lives in the data store. The cost is named where it is set: with allkeys-lru the rate limiters and the auto-ban FAIL OPEN under memory pressure, so the answer is to size memory, not to switch to noeviction (which would make writes error and take out authentication instead). secrets.authSecretRef wants an item whose single field "auth-conf" holds the complete "requirepass <pw>" line, not a bare password. Chart.yaml's comments are corrected where they became false: nothing is "consumed as it comes" any more and there is no shortfall of someone else's to document. The Renovate reasoning stays and is now backed by measurement. New trap, in Chart.yaml and in three places in the wiki: NEVER change valkey/ and a chart depending on it in the same PR. The dependency resolves from the published index, so a combined PR lints, packages, renders AND install-tests against the OLD subchart - green on an artifact that is not the one in the diff. Two consecutive PRs. Two things measured, not assumed, because either could have broken the update path with no symptom but "no updates ever": - helm dependency update AND build resolve the exact build-metadata version from the URL alone with an EMPTY HELM_REPOSITORY_CONFIG - a fresh runner's state - so the anonymous index needs no credential and ci/helm-dependencies.sh is untouched; - Renovate 44.132.2 dry run with the dependency pinned low: manager helmv3 matched the file, took registryUrls from Chart.yaml and offered newVersion "1.0.0+up9.1.2.alpine", versioning "helm". The self-hosted index and the "+up..." suffix are both handled. README: the claim "no registry address appears in this repository" is narrowed to what still holds - the publish target and its credentials come from 1Password and are not in the repo. The read path is now in Chart.yaml by necessity, carries no credential, and the host was always in CLAUDE.md. Done here rather than later so the paperless PR finds it already corrected. Re-verified after the change: helm lint --strict (0 failed), helm package (the subchart is embedded in the .tgz), helm template with the subchart OFF and ON (ON was previously impossible), against CI test-values, the rendered config.json, ci/run-quantity-render-test.sh (0 failed), NOTES.txt through the scratch-ConfigMap trick, and the fail paths including the reworded abort. The rendered subchart Service name matches what the helper emits, the cache pairing really renders (maxmemory-policy allkeys-lru, appendonly no, save "", emptyDir) and no PVC appears. Part of #65 |
||
|
|
780cb8eafd |
feat(stalwart): new chart for the Stalwart mail server
Cluster-capable from the start, with roles rather than symmetry: one StatefulSet per entry of the "nodes" map, the key passed in STALWART_ROLE. Since v0.16 Stalwart keeps its whole configuration inside the data store and manages it over JMAP, so the chart's scope is deliberately narrow: it renders config.json (exactly the DataStore object), the workloads, the Services, the NetworkPolicies, the Ingress and a cert-manager Certificate. Listeners, blob store, in-memory store, TLS object and cluster roles are operator work in the WebUI, which is why NOTES.txt carries an eight-step first install. config.json stays secret-free: authSecret uses the EnvironmentVariable variant (verified against the serde definition of PostgreSqlStore at v0.16.24), so the password arrives through secretKeyRef and checksum/config stays meaningful. The username cannot follow it - authUsername is a plain String with no secret form - so it is a public value and says so. Services are per PORT, not per group: a NodePort is cluster-unique, so "full" and "mx" can only accept SMTP behind one Service, selected through a per-port pod label. The http port gets a per-group ClusterIP Service instead, so the Ingress reaches exactly ingress.nodeGroup. Four traps, documented rather than explained away: - The role lookup FAILS OPEN. A node whose ClusterRole is missing from the database enables every listener and every task while staying Running with a green probe, and the log line arrives after the listeners are bound. Hence mx at replicas 0, per-port Services, and a NetworkPolicy that is a ceiling independent of what was bound. - NET_BIND_SERVICE is a documented deviation needed to EXEC, not to bind. The image sets setcap cap_net_bind_service=+ep, which makes the kernel fail execve when the bounding set is empty. The values comment says both why it is there and that it is never used, because the second half is what stops the next hardening pass from removing it. - externalTrafficPolicy must be Cluster - no pod runs on a control-plane node while the HA proxy delivers to them, so Local would drop every packet - and the source address that costs is restored only by the HA proxy's PROXY v2 header. - /healthz is rate-limited to 100 requests/60s per source IP, which is a node IP for kubelet probes, so all pods on a node share one bucket. The probe intervals are calculated (6 req/min/pod, ~16 pods per node) and the sum is a comment in values.yaml. A first install needs bootstrap.enabled: true: the setup wizard appears only when config.json is ABSENT, and this chart always renders one, so recovery mode is upstream's documented path for a templated config.json. Second chart in the repo with a dependency - the Valkey project's own chart, off by default behind condition:, because Stalwart's cluster coordinator rides on the in-memory store connection. The render aborts when more than one pod runs with no in-memory store address anywhere. Also wired up: publish-stalwart.yml through the shared block in workflow-util/pipelines, the change-app-version.yml choice list, a Renovate customManagers block anchored on the v prefix (which excludes the moving latest / v0.16 / -alpine tags), CI fixtures, and the README and CLAUDE.md lines that claimed ytdlp-web-player was the only chart with a dependency. Verified locally: ci/helm-dependencies.sh, helm lint --strict (0 failed), helm package, helm template against chart defaults and against ci/stalwart/test-values.yaml, the rendered config.json parsed as JSON with the field types the serde definition requires, ci/run-quantity-render-test.sh (0 failed, both nodes.*.resources paths exercised), NOTES.txt rendered through a scratch ConfigMap, and the null-safety and fail paths (securityContext=null keeps NET_BIND_SERVICE; a cluster without an in-memory store, an unknown port name, a group without http and an unknown ingress.nodeGroup all abort with a named message; the schema rejects timeout, a string timeoutMs and a list-shaped nodes). NO runtime verification was possible - there is no k3d or Docker daemon in this workspace. Part of #65 |
||
|
|
f284d209f7
|
sonar: correct the project key, and record what the first run measured
SonarQube Analysis / SonarQube Analysis (push) Successful in 42s
Validate Helm Charts / Detect changed charts (pull_request) Successful in 4s
Validate Helm Charts / Trivy config ${{ fromJSON(needs.detect-changed-charts.outputs.scan-charts) }} (pull_request) Skipped
Validate Helm Charts / Resource quantities (pull_request) Successful in 9s
Validate Helm Charts / Build ${{ fromJSON(needs.detect-changed-charts.outputs.charts) }} (pull_request) Skipped
Validate Helm Charts / Install-test (pull_request) Skipped
The project key was a guess. The project exists in SonarQube as
JLab-Helm-Charts; jlab-cluster_helm-charts was reasoned from the instance
serving two owners, and it produced the one error message that covers both a
non-existent project and a token without permission on it:
ERROR You're not authorized to analyze this project or the project doesn't
exist on SonarQube and you're not authorized to create it.
Because the server does not distinguish those two cases, the next occurrence of
that message now means the token lacks "Execute Analysis" - which is worth a
comment in the file rather than a second round of guessing.
sonar.projectName goes away entirely. Once the project exists on the server the
display name belongs to the server, and a name sent with every analysis would
overwrite a rename there on every run, silently. The reason is a comment in the
file so the line is not added back as an obvious omission.
The README analyser table was built from SonarQube's language list and promised
more than this instance delivers. Measured in run 375: two quality profiles
loaded, json and yaml; the IaC Kubernetes sensor (3.4 s) and the secrets
detection over all 391 indexed files did the work. Shell and GitHub Actions did
not run, and no configuration here can make them - analysers are server-side
plugins. The consequence is now stated: the five ci/ scripts and the thirty
workflow files, the files carrying the actual logic, are not analysed at all.
Whether the instance gains those plugins is a decision outside this repository.
Run 375 also answered the three questions this path carried, all positively:
node24 is in the runner image, the ~59 MiB scanner download and its signature
arrived in roughly three seconds (so the MTU failure that once killed a 17 MB
download does not repeat), and a repository variable does reach a workflow
called from another repository - which settles the identical question the
publish route carried for OP_CONNECT_HOST. The publish route's upload is still
unproven and stays listed as such.
Fixes #74
|
||
|
|
67d8c94fb3
|
ci: call the shared SonarQube block, and say what it does not do
Validate Helm Charts / Install-test (pull_request) Skipped
Validate Helm Charts / Detect changed charts (pull_request) Successful in 4s
Validate Helm Charts / Build ${{ fromJSON(needs.detect-changed-charts.outputs.charts) }} (pull_request) Skipped
Validate Helm Charts / Trivy config ${{ fromJSON(needs.detect-changed-charts.outputs.scan-charts) }} (pull_request) Skipped
Validate Helm Charts / Resource quantities (pull_request) Successful in 9s
SonarQube Analysis / SonarQube Analysis (push) Failing after 1m0s
Adds the caller side of the analysis: .github/workflows/sonar.yml as a hull of
the same kind as the publish ones, and sonar-project.properties as the
project-specific half. The workflow itself is the shared block in
workflow-util/pipelines, bound to @v4.
No paths filter, unlike the publish hulls: the analysis looks at the whole tree
- the five shell scripts under ci/, every workflow file, the YAML and Helm
templates, the values.schema.json files, and the secret detection over
everything. A filter would leave the previous result standing after a change it
did not cover while still presenting it as current.
workflow_dispatch is on the hull on purpose. Three things about this path are
unmeasured - node24 in the runner image, the ~59 MiB scanner download, and
whether a repository variable reaches a workflow called from another repository
- and any of them may need more than one attempt. Without a dispatch every
attempt would cost a dummy commit on main.
No exclusions, including the Helm templates, which are not valid YAML because
of {{ }}. What the Helm and YAML analysers make of them is only learnable by
letting them run over them once, and an exclusion written on a guess hides
whatever else matches it. If the first run produces noise worth suppressing,
the exclusion belongs in sonar-project.properties with the finding it answers
named next to it.
The project key carries the owner (jlab-cluster_helm-charts) because the
instance serves two, and a key cannot be changed afterwards without losing the
project's history.
The analysis is not a gate. The Community Build analyses the main branch only,
so the trigger is a push to main and a finding appears after the merge - at
which point there is nothing left to block. README and the CI-CD wiki page both
say so, with the two conditions under which a gate would become right, so that
its absence is not read as an oversight.
|
||
|
|
8878329f31
|
ci: publish charts through the shared block in workflow-util/pipelines
Validate Helm Charts / Detect changed charts (pull_request) Successful in 6s
Validate Helm Charts / Install-test (pull_request) Skipped
Validate Helm Charts / Trivy config ${{ fromJSON(needs.detect-changed-charts.outputs.scan-charts) }} (pull_request) Skipped
Validate Helm Charts / Build ${{ fromJSON(needs.detect-changed-charts.outputs.charts) }} (pull_request) Skipped
Validate Helm Charts / Resource quantities (pull_request) Successful in 13s
The reusable publish workflow now lives in workflow-util/pipelines and is
called pinned to @v3. All 25 thin hulls switch over and our copy is deleted in
the same commit: a hull pointing at a deleted file, or an orphaned file nobody
calls, would both be worse than the state before.
Each hull gains one line, registry-owner: ${{ github.repository_owner }}. The
owner used to be derived inside the workflow (#63); as a required input without
a default it is evaluated here, in the calling repository, where the value is
measured. That is the house rule of pipelines - a shared block never guesses an
owner segment - and it keeps the block free of any context semantics. The
workflow_dispatch and push triggers of every hull are untouched; after the move
the dispatch is the only way to trigger a publish by hand.
The Connect host is no longer wired into the block. It reads the repository
variable OP_CONNECT_HOST, which the maintainer has set, and fails in its first
step naming the variable when it is empty.
Documentation follows the same split as the code. The README entry no longer
describes a workflow of this repository; it names where the block lives, what
each caller owes it, and points at the publish-helm-chart page of that
repository's wiki for the reasons behind the upload step, which are no longer
ours to keep current. Nothing of them was lost in the move - the upload step's
shell body went across unchanged apart from two error messages that name the
owner as an input.
Also corrected, because the move made them wrong rather than merely stale:
- the helm-dependencies.sh entry still counted the local publish workflow among
the five places it runs; it is still five, one of them now the shared block,
which is why that block names the script as a prerequisite of its callers;
- two comments in install-test.yml pointed at publish-helm-chart.yml as a
sibling file, one of them for a trap it documented "at length" - that long
form is now on the pipelines wiki, and the comments say so;
- the hull count read 24, and reusable workflows with secrets: inherit are now
confirmed to work across repositories too;
- the chart-creation step in CLAUDE.md said the reusable workflow is ours.
install-test.yml stays here, with the reason written down (decision 4 on the
issue): it is reusable in form but welded to the ci/ fixtures, the exclusion
parser and this repository's chart matrix. A block only one repository can use
is not shared, only moved. The note is there so the question is not asked again.
Fixes #66
|
||
|
|
a12983fbea
|
ci(publish): compose the registry URL from a base and the repository owner
Validate Helm Charts / Detect changed charts (pull_request) Successful in 3s
Validate Helm Charts / Build ${{ fromJSON(needs.detect-changed-charts.outputs.charts) }} (pull_request) Skipped
Validate Helm Charts / Resource quantities (pull_request) Successful in 8s
Validate Helm Charts / Trivy config ${{ fromJSON(needs.detect-changed-charts.outputs.scan-charts) }} (pull_request) Successful in 15s
Validate Helm Charts / Install-test (pull_request) Skipped
charts-url in op://GitHub-Actions/jlab-registry-access held the full registry URL and publish-helm-chart.yml appended /api/charts to it. It now holds only the base up to /api/packages, and the owner segment comes from github.repository_owner, so the target is composed as <base>/<owner>/helm/api/charts. Nothing in this repository points at the registry any more, not even a path segment, and the second owner on the same instance needs no second setting. The 26 thin publish-<chart>.yml callers are unchanged. Two guards keep the switch from the old shape loud rather than a 404 against a masked URL: charts-url is rejected unless it ends in /api/packages, naming /helm, /api/charts or an included owner explicitly; and the derived owner is rejected when empty or when it is not a usable URL path segment. Everything the upload step already had is untouched - the per-field :? guards, the whitespace check on the credentials, the host masking, the built file name, --fail-with-body. The probe step in validate-charts.yml is temporary and measures whether this forge populates github.repository_owner at all; it is removed again once the value has been read off the run. |
||
|
|
6a3b05503e
|
Make the all-charts install-test repeatable, as one shared job
Validate Helm Charts / Resource quantities (pull_request) Successful in 10s
Validate Helm Charts / Trivy config ${{ fromJSON(needs.detect-changed-charts.outputs.scan-charts) }} (pull_request) Successful in 21s
Validate Helm Charts / Detect changed charts (pull_request) Successful in 3s
Validate Helm Charts / Install-test (pull_request) Skipped
Validate Helm Charts / Build ${{ fromJSON(needs.detect-changed-charts.outputs.charts) }} (pull_request) Skipped
The install-test matrix follows the charts a pull request changes, so a chart nobody has touched since the forge move carries a green history from a different runner - and that green says nothing about today. Gitea PR #48 showed what the answer is worth: correcting a comment in 21 charts made every one of them "changed" and turned the run into the first all-charts install-test after the move, 19 of 20 matrix charts Ready in 12m41s and exactly one not (rss-bridge, Gitea #51). By accident. The install-test job moves into the reusable .github/workflows/ install-test.yml and is called twice: by validate-charts.yml with the charts a pull request changed, and by the new install-test-all-charts.yml with all of them. Not copied for the second caller - a second copy is a second chance to test differently, unnoticed, which is the argument that gives ci/excluded-charts.txt exactly one parser. The parts that would drift are the ones that were paid for once already: the pinned kubectl with its skew check, envsubst, the containerised-job detection, the per-leg cluster name. Nothing about how a chart is installed changed. The moved job differs from its predecessor in the matrix source (inputs.charts instead of the detect job's output), an id on the install step, and a step that records the result; every other step is byte-identical. The all-charts run is workflow_dispatch only - a schedule would spend compute every day on a question that changes rarely - with an optional subset input, which is what lets a single chart be tested against the runner as Gitea #51 needs. One throwaway cluster per chart, no reuse: reuse saves time and changes what the test proves. An unknown or excluded chart name, and an empty matrix, are loud errors rather than a green run over nothing. Every leg writes its own outcome into the job summary next to the four facts that decide what the outcome means - date, runner label, K3S_IMAGE, kubectl version - and a final job records the verdict over the matrix. A green run nobody notes is unknown again next time. The excluded charts appear with their justification, from the one parser, as in the pull request pipeline. 20 charts in 12m41s is written down as the expectation, so a later deviation is visible as one. Fixes #52 |
||
|
|
813c796a7b
|
feat(valkey): one Valkey instance per application (1.0.0+up9.1.2.alpine)
Validate Helm Charts / Build ${{ fromJSON(needs.detect-changed-charts.outputs.charts) }} (pull_request) Successful in 4s
Validate Helm Charts / Detect changed charts (pull_request) Successful in 3s
Publish Valkey Helm Chart / Publish Valkey Helm Chart (push) Successful in 7s
Validate Helm Charts / Trivy config ${{ fromJSON(needs.detect-changed-charts.outputs.scan-charts) }} (pull_request) Successful in 11s
Validate Helm Charts / Install-test ${{ fromJSON(needs.detect-changed-charts.outputs.install-charts) }} (pull_request) Successful in 1m22s
Validate Helm Charts / Resource quantities (pull_request) Successful in 8s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 7s
A new chart for a single Valkey instance per application, replacing the
shared cluster-redis whose separation rests on one ACL file in one
1Password field - a construction in which every change touches six
applications and which cost a month of Paperless standstill.
Shape: StatefulSet with one replica, ClusterIP Service, no cluster mode,
no Ingress and no value to add one.
The password arrives as a finished configuration line ("requirepass
<secret>") in the 1Password field auth-conf, is mounted as a file and
included by the main configuration - never in argv, never in the
environment. The include is rendered last, so nothing in extraConfig can
override it. Because a misspelled or empty field would leave an open
server that looks perfectly healthy, an init container greps the mounted
file for the line and refuses to start the pod without it; it never
prints the file.
maxmemory is derived from the computed memory limit (maxmemoryFactor,
default 0.75) and a configured value above that limit aborts the render:
otherwise the kubelet's OOM kill decides what the eviction policy was
configured to decide. Persistence and eviction are documented as pairs -
queue (PVC + noeviction, the default) and cache (emptyDir +
allkeys-lru) - and the combination "no persistence + noeviction" carries
a warning in values.yaml and in NOTES.txt.
Probes: valkey-cli ping as an exec probe, authenticated through
VALKEYCLI_AUTH read out of the mounted file, with "-e" and an assertion
on the answer PONG. Without -e valkey-cli exits 0 on an error reply
(valkey 9.1.2, src/valkey-cli.c: the config.set_errcode branch in
cliReadReply), which would make the probe unable to fail.
Hardening: uid 999 / gid 1000 - the image's own valkey user - with
fsGroup matching, runAsNonRoot, readOnlyRootFilesystem, all capabilities
dropped and seccompProfile RuntimeDefault in the pod context so the init
container is covered too. /data is the only writable path.
NetworkPolicy on by default (first chart in this repo with one), with a
switch and a list of additional namespaces; values.yaml names that it is
only a boundary where the CNI enforces policies.
Wiring: publish-valkey.yml, the chart-name choice list, ci/valkey
fixtures for the persistent variant, a plain docker-versioning Renovate
block, README chart row.
|
||
|
|
8074ed14a5
|
Guard the four unguarded OnePasswordItem references with required()
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 6s
Publish Calibre Web Automated Helm Chart / Publish Calibre Web Automated Helm Chart (push) Successful in 7s
Publish Minecraft-Server Helm Chart / Publish Minecraft-Server Helm Chart (push) Successful in 6s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 6s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 6s
Validate Helm Charts / Detect changed charts (pull_request) Successful in 3s
Validate Helm Charts / Trivy config ${{ fromJSON(needs.detect-changed-charts.outputs.scan-charts) }} (pull_request) Successful in 19s
Validate Helm Charts / Install-test ${{ fromJSON(needs.detect-changed-charts.outputs.install-charts) }} (pull_request) Successful in 3m11s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 6s
Publish SearXNG Helm Chart / Publish SearXNG Helm Chart (push) Successful in 6s
Publish Urlaubsverwaltung Helm Chart / Publish Urlaubsverwaltung Helm Chart (push) Successful in 7s
Validate Helm Charts / Build ${{ fromJSON(needs.detect-changed-charts.outputs.charts) }} (pull_request) Successful in 6s
Validate Helm Charts / Resource quantities (pull_request) Successful in 8s
calibre-web-automated 7.0.0, minecraft-server 6.0.0 and urlaubsverwaltung
10.0.0 each majors: an installation that renders today with an empty itemPath
stops rendering. searxng 1.0.6 is documentation only - its valkey reference is
correctly optional and stays gated by {{- if }}.
Every message names the values path. values.yaml now says per key why it is
required, reusing the wording the charts' NOTES.txt already carry. The
hand-rolled "cwaSecretRef IS UNSET" block in calibre-web-automated's NOTES.txt
is gone: after the guard the render aborts first and that block is
unreachable. minecraft-server's note stays true and now points at the guard.
No "required" array in the schemas, and README guideline 12 now says why. The
plan called for one; measuring it showed it could never fire: the key ships as
null, so it is present at validation time, and JSON Schema's required tests
presence, not null-ness. Making it fire means either dropping the null default
or narrowing the type to "string", and both turn helm lint --strict red on the
chart's own defaults. The schema therefore describes the key as required and
required() enforces it. The README bullet on how null reaches the schema was
also wrong in the case it was being applied to - a null chart default is not
coalesced away - and now distinguishes the three cases.
searxng: the claim "charts in this repo have no dependencies (README,
guideline 13)" in the valkey template, in values.yaml and twice in _helpers.tpl
has been false since #214 and cited the wrong guideline; dependencies are
admissible and are guideline 14. The same sentence survives in other charts'
helpers and needs its own round.
|
||
|
|
93fdb3eaea |
sure: new chart for the we-promise Sure app (1.0.0+up0.7.4)
One StatefulSet with two containers - the Puma web process and a Sidekiq worker - sharing one ReadWriteOnce PVC at /rails/storage. That shared volume is the reason they live in one pod: splitting them would need an RWX volume. Only the web container migrates, because the image entrypoint runs db:prepare solely for the './bin/rails server' command; the worker is started through args: so the entrypoint (and its jemalloc preload) stays in place and the two can never race. Probes are chosen for what they prove: liveness and startup use Rails' /up, which is a boot check and touches no database, while readiness uses '/', which runs User.count through authenticate_user! in self-hosted mode and therefore fails while Postgres is unreachable. The worker speaks no HTTP, so all three of its probes ask sidekiqmon whether this pod still holds a Sidekiq heartbeat in Redis. Three OnePasswordItem CRs (app, database, redis), ingress.domain kept required outside the Ingress because APP_DOMAIN and the WebAuthn relying party read it, strict values.schema.json, CI fixtures with throwaway Postgres 16 and Redis, and a Renovate manager on semver so the -alpha/-rc/-hotfix channel and the moving tags are never selected. |
||
|
|
4715d85b99 |
satisfactory: server.storage stays the documented exception (#39)
Walks back the one part of the previous commit that changed rendered output: storage was pulled into the server fallback, so an erased size fell back to 20Gi instead of rendering a PVC without one. values.schema.json had documented the opposite decision - "an erased value yields an invalid PVC rather than a silently degraded workload" - and that is someone's reasoned trade, not collateral of the nil-pointer bug next to it. Overturning it inside a patch would have made this the only release where valid values render differently, and it would have settled an open design question in a PR nobody opened for that. It goes to #39 instead. So storage is out of the defaults again and the schema note is restored verbatim, extended only by where the exception is argued. values.yaml marks it as the one key of the block that is deliberately not null-safe. The README rule that had leaned on this example now says the thing that actually holds: an exception is fine, an unmarked one is not. Everything else stands - dev and server null-safe, the single-key findings on the ports, formatNumber against exponential notation, 7.0.1. Re-measured after the walk-back: rendered output against the chart defaults and against a full override set is byte-identical to 7.0.0, NOTES.txt included, and "--set server=null" now renders exactly the sizeless PVC that "--set server.storage=null" rendered on 7.0.0. |
||
|
|
5ff6e401c7 |
satisfactory: dev and server fall back instead of aborting (7.0.1)
--set dev=null aborted the render at .Values.dev.debug and --set server=null at .Values.server.replicas, while the third block, game, has fallen back to the chart defaults since 7.0.0. Half-safe is the worse state of the two: whoever sees game=null work assumes server=null does too. Both blocks now run through satisfactory.defaultedDict like game, via two new helpers next to it. server.resources is deliberately NOT re-defaulted there - it already runs through satisfactory.effectiveResources, which carries those defaults and is null-safe on its own, and a second copy of limitFactor: 2 / 4Gi / 1 / 5Gi would be a third place for them to drift. server is read in four files, not one, and the fourth is NOTES.txt, which helm template never renders - a fix confined to the manifests would have looked green and still aborted on a real helm install. All four go through the helpers. The single erased key was the quieter half of the bug and is fixed with it: a nulled server.gamePort rendered "port:" and "nodePort:" empty into the Service and an empty containerPort into the StatefulSet, and a nulled server.storage a PVC asking for no size - manifests rejected by nothing until the API server saw them. server.storage was documented in values.schema.json as deliberately not null-safe; that reasoning does not survive the block-level case, because it made "server: null" render a sizeless PVC instead of falling back like everything else. The schema note is replaced rather than left lying. Rendered output for valid values is unchanged: helm template against the chart defaults and against a full override set is byte-identical to 7.0.0, and so is NOTES.txt. Hence a patch bump, appVersion untouched. Fixes #16 |
||
|
|
02b3c3ce75
|
renovate: align the config with cluster-config
Validate Helm Charts / Detect changed charts (pull_request) Successful in 3s
Validate Helm Charts / Install-test ${{ fromJSON(needs.detect-changed-charts.outputs.install-charts) }} (pull_request) Skipped
Validate Helm Charts / Resource quantities (pull_request) Successful in 8s
Validate Helm Charts / Build ${{ fromJSON(needs.detect-changed-charts.outputs.charts) }} (pull_request) Skipped
Validate Helm Charts / Trivy config ${{ fromJSON(needs.detect-changed-charts.outputs.scan-charts) }} (pull_request) Skipped
The shared jlab-renovate-bot instance serves this repo and cluster-config,
and two repos behaving differently under one bot is a source of surprises
rather than of safety. This adopts cluster-config's configuration:
config:best-practices in extends, labels ["renovate"], no PR caps
(prHourlyLimit 0, prConcurrentLimit 100), separateMinorPatch, an explicit
dependencyDashboard, and global automerge with platformAutomerge.
Two of those choices were previously wrong here and are now right, because
the branch protection on main has caught up: a review requirement and
validate-charts as a required check are both active. platformAutomerge
therefore arms the PR on the forge and Gitea merges it when the check
passes AND the review exists - the review is the gate, which is what makes
automerging majors something other than unattended merging.
Comments that asserted the opposite are rewritten rather than left to rot:
the platformAutomerge: false comment ("not (yet) a required check on either
side"), the "Majors stay manual" rule, the README PR policy bullets and the
required-check paragraph, and the CLAUDE.md forge notes.
config:best-practices pulls in docker:pinDigests, which is switched off
again for custom.regex and helm-values: an appVersion: is a tag by
construction and has nowhere to put a digest. Kept deliberately:
minimumReleaseAge of three days on the routine updates (every merge here
publishes a chart), the ignorePaths and ci/** opt-outs, every per-chart
manager, the nginx and qbittorrent constraints, and postUpgradeTasks.
Fixes #34
|
||
|
|
03bb454eb7
|
docs: make the wiki the knowledge base and its upkeep a required step
The wiki now carries one page per chart (prerequisites, installation, the required 1Password fields, the complete values reference and the chart's traps) plus the workflow, the pipelines, versioning, Renovate, the style guide and the incidents behind each rule. README points at it and keeps its own role: the overview for someone reading the code, while anything an operator needs lives in the wiki. CLAUDE.md gains the wiki edit as step 7 of the mandatory workflow, with a table of what obliges an edit - and the one thing that does not, a plain app-version bump, since Renovate automerges those and never touches the wiki. Also corrects the patchmon row: the 2.x server serves frontend and /api/* from one process, so the chart renders one Deployment plus an optional guacd sidecar, not two Deployments. |
||
|
|
afa1efb598 | Merge pull request 'freesailarr: MediathekArr as the tenth container, off by default (3.1.0+up1.0.0)' (#25) from feat/freesailarr-mediathekarr into main | ||
|
|
60670182ff
|
docs: describe what is, now that the move is done
Validate Helm Charts / Build ${{ fromJSON(needs.detect-changed-charts.outputs.charts) }} (pull_request) Skipped
Validate Helm Charts / Trivy config ${{ fromJSON(needs.detect-changed-charts.outputs.scan-charts) }} (pull_request) Skipped
Validate Helm Charts / Resource quantities (pull_request) Successful in 7s
Validate Helm Charts / Detect changed charts (pull_request) Successful in 3s
Validate Helm Charts / Install-test ${{ fromJSON(needs.detect-changed-charts.outputs.install-charts) }} (pull_request) Skipped
The documentation still described a migration in progress, and the worst line claimed the install-test "cannot run on Gitea at all" - true two days ago, the opposite of true now, and exactly the sort of sentence someone builds a wrong assumption on. README and CLAUDE.md now state what holds: everything runs on Gitea, github.com is a mirror with Actions and Renovate off, branch protection with a signature requirement is active, and the tool is tea. The move itself is not retold - that is Gitea #14. "Where this repo lives" keeps its place but turns around: it is no longer a list of what blocks us, it is what a Gitea runner does differently from a GitHub one, with the measurements. Newly written down because it holds permanently and stood nowhere: a matrix runs concurrently in ONE job under ONE status context, so a required check per chart is impossible and the context name carries the unexpanded matrix expression; expected runtimes, so a later deviation is visible as one, with the note that the difference to GitHub is structural - one runner for both legs against a VM per leg - and not performance. The six assumptions a GitHub runner had been meeting quietly are split rather than listed. Five are environment (MTU, fs.inotify, missing kubectl and envsubst, kubeconfig on 0.0.0.0): they fail loudly, say nothing about our code, and are fixed on the node side. The sixth gets its own block because it is the one that does say something about our code - the hard-coded k3d cluster name was wrong on GitHub too and only never showed there, because a VM per matrix leg meant two legs never saw each other. A generous environment is no friend when you want to know whether something is correct. Two rules generalised out of this round, as rules rather than as a story: a check that a broken system passes is worse than no check (with the two vacuous forms that caused it), and green does not mean checked, pointing at Gitea #24 as an open case of the same kind - explicitly not claimed as solved. Also fixes a hazard the move created: most #n in these files name closed GitHub issues that were never migrated, while Gitea resolves a bare #n against its own counter - #22 there is an unrelated throwaway pull request, not the install-test issue the text means. Stated once at the top instead of rewriting sixteen references into numbers that do not exist. |
||
|
|
ae621746a1
|
Merge main into the MediathekArr branch
Validate Helm Charts / Detect changed charts (pull_request) Successful in 3s
Validate Helm Charts / Build ${{ fromJSON(needs.detect-changed-charts.outputs.charts) }} (pull_request) Successful in 4s
Validate Helm Charts / Trivy config ${{ fromJSON(needs.detect-changed-charts.outputs.scan-charts) }} (pull_request) Successful in 11s
Validate Helm Charts / Install-test ${{ fromJSON(needs.detect-changed-charts.outputs.install-charts) }} (pull_request) Successful in 3m45s
Validate Helm Charts / Resource quantities (pull_request) Successful in 7s
The branch predates the Gitea move (#253) by 37 commits and both sides touched the same two files. Merged rather than rebased, so the signed commit of the original work stays intact, and resolved by combining rather than choosing - both statements are true at once: the chart has ten images now, and the repo lives on Gitea. README.md, the one real conflict: this branch rewrote the freesailarr image bullet from nine upstreams to ten, while main had grown a minecraft-server bullet next to it that this branch never had. Kept both - our "ten" wording and main's new bullet. renovate.json5 merged cleanly, but "cleanly" is not "correctly", so the result was read rather than trusted: the freesailarr exception now says ten and gained the pointer to the mediathekarr digest block, while the two things main added right beside it survived unchanged - the note that the post-upgrade hook is allow-listed on the shared Gitea instance, and the forge-neutral wording of the automerge rule. Nothing from before the move brought "GitHub" back into a sentence that had been made neutral. |
||
|
|
c3889a66b6
|
Drop the Renovate workflow: the Gitea side already has a shared instance
A measurement overtook this part of the change. A shared `jlab-renovate-bot` instance already runs on the Gitea side and already has this repository enrolled - its first run there opened PRs for searxng and gotenberg plus the dependency dashboard. A second runner in this repo would produce duplicate PRs and two schedulers rewriting each other's branches, so .github/workflows/renovate.yml is removed again. Two things fall out of that run, and both were open risks: - The instance reads our renovate.json5. searxng and gotenberg are customManagers targets of ours - no built-in manager finds an appVersion in a Chart.yaml - so the config is demonstrably in effect. - allowedCommands is already granted there. The searxng PR's diff carries `version: 1.0.4+up2026.9.11.ffe96f8a6 -> 1.0.5+up2026.9.13.96479fff6` next to the appVersion, and nothing but ci/renovate-bump-chart.sh writes `version:`. Had that allow-list been missing, postUpgradeTasks would have been skipped SILENTLY and every chart PR on Gitea would have been red on the version check with nothing naming the cause. With the workflow gone there is no container image to pin either; verified by dry run that the ghcr.io/renovatebot/renovate dependency is no longer extracted, while TRIVY_VERSION still is - that one belongs to the Trivy replacement and stays. README and CLAUDE.md now say Renovate runs on Gitea as an existing shared instance and that no workflow belongs here, so the next reader who finds none knows why instead of building one. |
||
|
|
a6c27ebef3
|
Prepare the Gitea move: measured CI deviations, self-hosted Renovate, issue migration script
Groundwork for #225, built on a throwaway pull request against the Gitea instance rather than on assumptions. The move itself is not part of this change - no Actions are switched off, no mirror is enabled, no branch protection is touched. What the probe run measured, and what follows from it: - Which jobs need a Docker socket: only the install-test. Measured per job - detect-changed-charts, build-chart, the quantity render and the Trivy config scan all pass on `ubuntu-latest`, which on that instance has no socket. The install-test dies there at "Create k3d cluster" with "Cannot connect to the Docker daemon", an error naming k3d and never the runner label. Its `runs-on` is now an expression: the label `ubuntu-docker` on Gitea, `ubuntu-latest` on GitHub, because the former does not exist there and a chart PR would otherwise queue forever during the handover. Gitea evaluates it - the probe job was taken by the docker runner. - The install-test still cannot pass on Gitea. That daemon is rootless with "Cgroup Driver: none"; the k3s node restarts immediately with an empty log because k3s needs cgroup delegation. The label is necessary and not sufficient, and the fix is the maintainer's. - aquasecurity/setup-trivy cannot be used on Gitea: it checks out aquasecurity/trivy from github.com with `github.token`, which there is a Gitea token, and ends on "could not read Username for https://github.com". Replaced by the release tarball - one request, same behaviour on both forges, green on the probe run. Also in this change: - .github/workflows/renovate.yml, Renovate in self-operation: a daily scheduled run in the official container, inert on github.com via a `github.server_url` guard. Every rule proved by dry run instead of assumed - the pinned Renovate image is tracked by the built-in github-actions manager (44.80.0 -> 44.87.1, minor), TRIVY_VERSION is still tracked after setup-trivy is gone (v0.72.0 -> v0.74.0, minor), and `ubuntu-docker` is not extracted by the github-runners manager at all, so nothing will quietly "correct" it. - ci/migrate-issues-to-gitea.sh, runnable, with --dry-run, and NOT run. Its header names what it cannot do: comment authorship, the #<n> references between issues, and attachments. - CLAUDE.md and README.md on the new flow: which forge and which tool, auto-merge on green checks in place of the merge queue Gitea does not have, and the measured differences of the two CIs. Refs #225 - deliberately no "Fixes": the move is not finished with this. |
||
|
|
78d7020dab
|
freesailarr: MediathekArr as the tenth container, off by default (3.1.0+up1.0.0)
MediathekArr fakes a Usenet indexer towards Prowlarr and a SABnzbd downloader towards Sonarr/Radarr and fetches from the ARD and ZDF media libraries. Both consumers already share gluetun's network namespace, so they reach it over 127.0.0.1 - no service, no DNS, no ingress detour. Purely additive: the flag defaults to false and a default render is byte-identical to 3.0.5 (verified against the previous chart for both the default values and the CI values). The four things that fail silently without them: - A START GATE on gluetun. MediathekArr fetches its ruleset once at start and never retries; started before the tunnel it answers every Newznab search with 0 results while the container is up, Ready and quiet. Kubernetes cannot order two regular containers and an initContainer would deadlock, so the wait sits in front of the image entrypoint: poll gluetun's health server on 127.0.0.1:9999 and exec the entrypoint afterwards; on timeout the container exits rather than starting blind, because a crash loop is visible and an empty indexer is not. The startup probe budget (300s) deliberately outlasts the gate (180s). - incomplete/ AND complete/ ON THE SHARED DOWNLOADS CLAIM, not in an emptyDir. An emptyDir counts against the container's ephemeral-storage limit, and one large download would have the kubelet evict the whole pod with all ten containers - the trap ytdlp-web-player documents. - THE COMPLETE PATH SONARR SEES. The claim is mounted at /downloads exactly as sonarr/radarr mount it, and the chart sets DOWNLOAD_COMPLETE_PATH itself, so the SABnzbd complete_dir it reports is the same absolute path on both sides. - TWO PORTS: 5007 (SABnzbd role and UI, primary) and 5008 (Newznab role for Prowlarr), both declared to gluetun's firewall and both on one Service. The SABnzbd API is at /download/api - /api on 5007 answers with a misleading Newznab validation error. Further details: - Image pinned by digest (tag + digest): "latest" is a 2024 build, "beta" moves. Single-arch linux/amd64. The built-in helm-values manager cannot follow that pair, so renovate.json5 gets the chart's first customManagers block; verified by dry-run with two fixtures, since a matching and a broken rule look identical while the pin is current. - Hardened as measured: non-root, uid/gid from puid/pgid, readOnlyRootFilesystem: true, all capabilities dropped, no PUID/PGID. - Probes on measured 200 endpoints (there is no health endpoint): readiness and startup on /download/, liveness as an exec covering BOTH ports, because one container runs two processes and a wedged Newznab half is exactly what makes searches come back empty. - No ingress by default: the UI links its assets with root-absolute paths, so it can only be served at the root of a host; on the shared domain that root belongs to seerr. A sub-path fails the render instead of producing a broken UI. - values.yaml and NOTES.txt document the geo MARKER PROCEDURE (not a table of series), the escape route of a German exit and its price for the whole stack, the fail_message: null trap, and the HLS question as an open question. - CI: mediathekarr enabled in ci/freesailarr/test-values.yaml, install timeout raised to 1200s for the extra image and the gate. Fixes #245 |
||
|
|
5a4eeefd0e
|
docs: the excluded-chart signal, and minecraft-server's image-tag exception
Two documentation gaps, both about a reader drawing the wrong conclusion. The install-test section said nothing about what an exclusion looks like in a PR's check list. It now says that an excluded chart gets no job at all rather than a green tick, that the detect job names it with its documented reason in the summary, that the mandatory justification is what makes a line an exclusion in the first place, and why the per-chart "if" that would produce a visible Skipped is not available (#231). Section 3 of the style guidelines lists the charts that deviate from "the main image tag comes from appVersion" so that nobody mistakes a deviation for a mistake and tidies it away. minecraft-server has been the only chart with an overridable tag since #158 and was missing from that list: the line says that image.tagOverride exists, why (only the newest Java line is maintained, so the line becomes a decision at rollout time instead of a property of the chart), and that the chart checks the pairing of effective tag and minecraft.version itself and fails the render on a mismatch (#232). |
||
|
|
9c0039685b
|
minecraft-server: an overridable image tag and a Java-line guard (5.0.0+up2026.8.2.java25)
Only the newest Java line is maintained from now on. Publishing the chart once per line out of one directory is what made the lines drift apart (#158): a change reached whichever line happened to lie in the directory. java8 and java17 stay published as frozen artifacts at 4.0.0, java21 at 4.1.0, and none of them gets another chart change. Running an older JVM is now a decision at rollout time instead of a chart version: image.tagOverride replaces the tag that otherwise comes from appVersion. It is not called image.tag because it decouples the running image from the chart version, and it is deliberately outside Renovate's reach - renovate.json5 keeps tracking appVersion only. The pairing of tag and minecraft.version is now checked at render time and fails with both values named, the line each one belongs to, and where the tag came from. Until now nothing checked it: a mismatch installed silently and only the container failed afterwards. The boundaries are Mojang's, swept over all 102 releases of the piston-meta manifest (javaVersion.majorVersion): up to 1.16.5 Java 8, 1.17-1.17.1 Java 16, 1.18-1.20.4 Java 17, 1.20.5-1.21.11 Java 21, 26.1 and later Java 25. The 1.17 line was the one gap the chart's own notes did not cover. Two cases pass unchecked on purpose, documented in values.yaml and NOTES.txt: a tag without a recognizable java<N> part, and a minecraft.version that is not a plain numeric x[.y[.z]]. A guard that rejects what it does not understand gets worked around instead of respected. Major, because an installation that already runs a mismatched pair stops rendering. It does not run today either - it now fails earlier and visibly. |