Validate Helm Charts / Detect changed charts (pull_request) Successful in 3s
Validate Helm Charts / Resource quantities (pull_request) Successful in 13s
Validate Helm Charts / Build bulwark (pull_request) Successful in 6s
Validate Helm Charts / Trivy config bulwark (pull_request) Successful in 10s
Install-test / Install-test bulwark (pull_request) Successful in 8m50s
Validate Helm Charts / Install-test (pull_request) Successful in 8m50s
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.
68 lines
3.3 KiB
YAML
68 lines
3.3 KiB
YAML
{{- /*
|
|
ON BY DEFAULT (ingress.enabled: true): a webmail client nobody can reach is not
|
|
a webmail client.
|
|
|
|
AT THE ROOT OF THE HOST, AND THERE IS NO VALUE TO PUT IT UNDER A PATH.
|
|
NEXT_PUBLIC_BASE_PATH is compiled into the bundle - upstream: "These are
|
|
compiled into the bundle and cannot be changed at runtime" - so a sub-path
|
|
deployment needs a rebuilt image, which is outside what this repository does
|
|
(same limit as garage #81 and keycloak #83). A rewrite-target annotation is not a
|
|
way around it either: the bundle's own asset and API URLs are built for the root,
|
|
so stripping a prefix at the proxy produces a page that loads and then fetches
|
|
everything from the wrong place. The rule below is therefore "/" with pathType
|
|
Prefix, deliberately and permanently.
|
|
|
|
GUIDELINE 1, CHECKED IN BOTH DIRECTIONS (README, "Values that must not lie"),
|
|
AND FOR THIS CHART THE GATE FREES BOTH VALUES. Checked rather than assumed:
|
|
nothing outside this template FUNCTIONALLY reads ingress.domain. Bulwark derives
|
|
its own public URL from the request origin and takes no base-URL environment
|
|
variable - there is no KC_HOSTNAME equivalent here - so a release with
|
|
ingress.enabled: false is complete without a domain. That is the
|
|
calibre-web-automated case rather than the matrix-synapse one.
|
|
|
|
The one other READER is NOTES.txt, which prints the access URL and the OIDC
|
|
redirect URI. That read is deliberately NOT guarded with a required(): `helm
|
|
template` does not render NOTES.txt, so a guard there would fire for the first
|
|
time during a real `helm install` and never in lint, in the render test or in CI.
|
|
The bulwark.publicUrl helper renders a visible placeholder instead, and the
|
|
enforcement lives here - in the template that is reached by every render with the
|
|
Ingress on, which is the default.
|
|
*/ -}}
|
|
{{- if .Values.ingress.enabled }}
|
|
apiVersion: networking.k8s.io/v1
|
|
kind: Ingress
|
|
metadata:
|
|
name: {{ .Release.Name }}-{{ .Chart.Name }}-ingress
|
|
labels:
|
|
app: {{ .Release.Name }}-{{ .Chart.Name }}-sfs
|
|
annotations:
|
|
cert-manager.io/cluster-issuer: {{ required "A Cluster-Issuer must be provided (ingress.clusterIssuer)" .Values.ingress.clusterIssuer }}
|
|
{{- /* Merged, not replaced. The one annotation worth adding here for this
|
|
application is nginx.ingress.kubernetes.io/proxy-body-size: attachment
|
|
uploads and the Files view go through this Ingress, and nginx's
|
|
default of 1m rejects anything larger with a 413 that reads like an
|
|
application fault. The chart does not set it because the right number
|
|
is the mail server's limit, which this chart does not know. */}}
|
|
{{- with .Values.ingress.annotations }}
|
|
{{- toYaml . | nindent 4 }}
|
|
{{- end }}
|
|
spec:
|
|
ingressClassName: {{ .Values.ingress.className }}
|
|
rules:
|
|
- host: {{ required "A Domain/Host must be provided (ingress.domain)" .Values.ingress.domain }}
|
|
http:
|
|
paths:
|
|
- backend:
|
|
service:
|
|
name: {{ .Release.Name }}-{{ .Chart.Name }}-service
|
|
port:
|
|
# BY NAME, so bulwark.port and this file cannot drift apart.
|
|
name: http
|
|
path: /
|
|
pathType: Prefix
|
|
tls:
|
|
- hosts:
|
|
- {{ required "A Domain/Host must be provided (ingress.domain)" .Values.ingress.domain }}
|
|
secretName: tls-{{ .Release.Name }}-{{ .Chart.Name }}-ingress
|
|
{{- end }}
|