Group 3 of the #114 sweep. TWO HTTPRoutes - and the shape is not the one the
issue described.
#114 says this chart renders its Ingresses in a LOOP OVER SEVERAL APPS. It
does not; that is freesailarr. It renders ONE Ingress object with TWO HOST
rules: the homeserver on ingress.domain at "/", and the delegation nginx on
matrix.delegation.serverUrl at "/.well-known/matrix". An HTTPRoute's
spec.hostnames applies to the WHOLE object, so two hostnames that want
different paths and different backends have to be two objects. That is the
only faithful translation, not a stylistic choice.
httpRoute.delegation.hostnames deliberately does NOT fall back to
httpRoute.hostnames. The server name is what REMOTE homeservers resolve and is
a different thing from the homeserver's own hostname; the migration order -
homeserver host first, server name last - depends on telling them apart, and a
chart-global hostname list covering both would erase the distinction.
httpRoute.delegation.parentRefs falls back to httpRoute.parentRefs, which
matters because the server name is usually an apex domain behind a different
Gateway.
ONE THING THIS MODE IMPROVES, and it is the awkward part of Matrix delegation.
Under ingress-nginx two Ingress objects cannot both claim one hostname - the
admission webhook rejects the second - which is why the delegation rule has to
live inside this chart's single Ingress object even though the apex domain
usually belongs to a website. HTTPRoutes attached to the same listener for the
same hostname are merged by the implementation, longest match winning, so the
/.well-known/matrix route can sit beside whatever owns that apex.
WHAT THIS MODE DROPS, MEASURED ON THE LIVE OBJECT ON 2026-10-12 - and ONE OF
THE TWO IS NOT WHAT IT LOOKS LIKE. The live Ingress carried, all of it
operator-supplied through ingress.annotations (this chart sets none of it):
nginx.ingress.kubernetes.io/proxy-body-size: 50m
nginx.org/client-max-body-size: 50m
nginx.ingress.kubernetes.io/configuration-snippet: |
more_set_headers "X-Forwarded-For $remote_addr";
more_set_headers "X-Forwarded-Proto $scheme";
more_set_headers "Host $host:$server_port";
1. THE BODY SIZE IS THE REAL LOSS. Synapse's media repository takes uploads;
its own ceiling is max_upload_size in homeserver.yaml, which this chart
cannot see because the whole config arrives from 1Password. The effective
limit is the smaller of the two, and in this mode the proxy half becomes
the GATEWAY'S default - neither number anyone can point at is necessarily
the one that decides. No counterpart in the Gateway API, so per decision 2
of #114 it is documented, not rebuilt. nginx.org/client-max-body-size is
the same limit spelled for NGINX Inc's controller, a hedge across both
implementations, and it goes for the same reason. (#114's own measured
table did not list matrix-synapse under proxy-body-size at all; the live
object has it.)
2. THE SNIPPET IS NOT LOAD-BEARING, which took checking rather than assuming.
It looks like what Synapse's x_forwarded setting depends on - client IP for
rate limiting, and federation. It is not: more_set_headers is the
headers-more module's OUTPUT-header directive ("Replaces (if any) or adds
(if not any) the specified output headers"), the request-side directive
being more_set_input_headers. So those three headers are added to the
RESPONSE TO THE CLIENT; Synapse never sees them, and what it reads with
x_forwarded: true is the REQUEST's X-Forwarded-For, which ingress-nginx
supplies itself. The Gateway API's ResponseHeaderModifier could not rebuild
it anyway (it knows no nginx variables), and should not: NOTES.txt says in
as many words not to re-create it and not to read its absence as the cause
of a rate-limiting or federation problem.
Stated as what it is - a reading of the directive's documented semantics.
Nothing here observed a Synapse instance with and without the snippet.
NOTES.txt now asks $published (ingress.enabled OR httpRoute.enabled) wherever
it used to ask ingress.enabled - the server-name/serverUrl mismatch warning,
the "clients work fine" aside and the scaled-to-zero paragraph were all really
asking whether anything publishes the release.
Minor: additive, defaults unchanged. Empty helm template diff against
origin/main with ci/matrix-synapse/test-values.yaml.
Part of #114
Three changes, across both workloads (Synapse StatefulSet and delegation
nginx Deployment).
1. values.schema.json (#68): additionalProperties: false on every
chart-owned object level, including the whole matrix.delegation block
with its own contexts, probes and resources.
2. Ingress gate (#93): the Ingress template is wrapped in
{{- if .Values.ingress.enabled }} and the key is added to values.yaml
with default true, so behaviour is unchanged.
The domain checkpoint found a second reader: the delegation nginx
serves ingress.domain in its /.well-known/matrix/{server,client}
endpoints, and read it without a guard. Gating alone would therefore
have published '{"m.server": ":443"}' and base_url "https://" - a
federation-breaking answer that renders and starts cleanly. Both reads
are now required(), so that case fails loudly. Only clusterIssuer
actually becomes unnecessary when the Ingress is off.
3. Null-safety for hardening, probes and resources (#99) for both
workloads. The rebuilt probes keep each workload's own endpoint:
Synapse /health, delegation /.well-known/matrix/server - the endpoint
that workload exists to serve - including its shorter 3s timeout. The
delegation pod context keeps having no fsGroup, because it mounts no
volume that needs one.
The nginx checksum/config annotation from #82 is untouched.
storage stays deliberately not null-safe: an erased block yields an
invalid PVC, which is the loud class #99 excludes.
Rendering is byte-identical to 6.0.0, both with the CI values (which
enable delegation) and with the production bundle values.
The delegation nginx serves the .well-known documents from an nginx.conf that
is rendered from ingress.domain and mounted with subPath. Changing the domain
rewrote the ConfigMap but left the running pod untouched: the pod template was
unchanged, so nothing rolled, and the kubelet never refreshes a subPath mount.
The pod kept serving the old delegation while looking perfectly healthy - the
failure mode from #82.
The pod template now carries checksum/config over the rendered delegation
ConfigMap, so a changed nginx.conf rolls the deployment.
Also documents the limit that remains for homeserver.yaml: it comes from a
1Password-backed Secret, whose content Helm never sees, and the operator's own
auto-restart only ever covers Deployments - not this StatefulSet. That case
needs a manual rollout restart, which the values.yaml now says out loud.
Major bump: runtime behavior changes - a delegation config change now costs a
restart of the nginx pod (a few seconds, no persistent state).
- Values-driven securityContext for the Synapse pod/container with hardened
defaults: runAsNonRoot as uid/gid 1000 (matching the image's previous gosu
drop target), readOnlyRootFilesystem, allowPrivilegeEscalation false, all
capabilities dropped; /tmp mounted as emptyDir. The now-ineffective UID/GID
env vars are removed (the securityContext controls the user).
- Naming scheme: delegation Deployment renamed to ...-delegation-nginx-deployment,
ConfigMap to ...-delegation-nginx-configmap (references and service selector
updated).
- matrix.configSecretRef moved to secrets.configSecretRef (repo standard).
- Probes for Synapse and the delegation nginx are values-configurable;
defaults are the previous hardcoded settings.
- Flexible ingress standard: ingress.className (default nginx) and
ingress.annotations merged with the cert-manager annotation.
- Additive matrix.env block rendered through tpl (value/secretKeyRef/
configMapKeyRef forms).
- CI fixtures: secret ref moved, seed job image synced to appVersion.
- Chart 4.2.1+upv1.159.0 -> 5.0.0+upv1.159.0 (major; app version unchanged).
The delegation nginx image was hardcoded as nginx:1.28.3 in the
deployment template. It is now rendered from the new values
matrix.delegation.image.repository / matrix.delegation.image.tag,
defaulting to nginx:1.30.4 (current stable line, tag verified against
Docker Hub). README documents that auxiliary/sidecar images are pinned
via values and reviewed with every app-version update round.
Chart version: 4.1.1+upv1.159.0 -> 4.2.0+upv1.159.0 (minor: additive
values change).
Every Deployment/StatefulSet now takes its replica count from a replicas
value (default 1, explicit 0 allowed) via a new per-chart <chart>.replicas
helper; the helper uses a kindIs "invalid" nil check so an explicit 0 is
not swallowed by Helm's default function. A chart-global scaleDown flag
(default false) takes precedence: it renders every workload with
replicas: 0 and suspends the paperless-ngx consume CronJob, stopping the
app without uninstalling it (PVCs, Secrets, Services and Ingress remain).
Defaults leave the rendered manifests byte-identical to main, so all
published charts get a minor version bump; the template-chart carries the
reference helper implementation and stays unversioned.
Fixes#25
Every chart now renders its container resources through a duplicated
_helpers.tpl helper (reference implementation in template-chart): requests
ship as chart defaults, missing memory and ephemeral-storage limits are
computed as resources.limitFactor x the request (default 3, satisfactory
keeps 2 to preserve its previously published 8Gi limit), an explicitly set
limit always wins, and a CPU limit is only rendered when set explicitly.
Fixed limit defaults are removed from all values.yaml files; teamspeak-server
gains a values-driven resources block instead of hardcoded template values.
Major version bump for all published charts.
Add securityContext hardening (runAsNonRoot, readOnlyRootFilesystem,
capabilities drop ALL, allowPrivilegeEscalation false) to the 10 workloads
that lacked it: apache-tika, cyberchef, gotenberg, homeassistant,
matrix-delegation-nginx, paperless-server, paperless-consume cronjob
(plus automountServiceAccountToken false), patchmon-frontend,
urlaubsverwaltung and zipline. All contexts are configurable via
securityContext.pod / securityContext.container in values.yaml; writable
paths are provided as emptyDirs. Image-forced deviations (Home Assistant
root start for s6-overlay + NET_RAW for discovery, paperless root init
container for /run ownership) are documented in the charts' values.yaml.
Major version bump for every changed published chart.