{{- $replicas := include "patchmon.replicas" (dict "scaleDown" .Values.scaleDown "replicas" .Values.patchmon.server.replicas) -}} {{- /* The effective (null-safe) probe helper, so the budget below matches the container even when the startupProbe block has been erased (#99). */ -}} {{- $startup := fromYaml (include "patchmon.server.startupProbe" (dict "given" .Values.patchmon.server.startupProbe "root" $)) -}} {{- $budget := mul (int $startup.periodSeconds) (int $startup.failureThreshold) -}} {{- $name := printf "%s-%s" .Release.Name .Chart.Name -}} {{- $httpRoute := .Values.httpRoute | default dict -}} {{- $hostnames := $httpRoute.hostnames | default list -}} {{- if not $hostnames -}} {{- $hostnames = list .Values.ingress.domain -}} {{- end -}} PatchMon {{ .Chart.AppVersion }} — release {{ .Release.Name }} in namespace {{ .Release.Namespace }}. ACCESS {{- if .Values.ingress.enabled }} INGRESS MODE (ingress.enabled: true, httpRoute.enabled: false) https://{{ .Values.ingress.domain }} While cert-manager is still issuing the certificate the browser warns: kubectl -n {{ .Release.Namespace }} get certificate tls-{{ $name }}-ingress {{- else if $httpRoute.enabled }} GATEWAY MODE (httpRoute.enabled: true, ingress.enabled: false) {{- range $hostnames }} https://{{ . }} {{- end }} This release renders an HTTPRoute ({{ $name }}-httproute) and NOTHING else of the routing path — no Gateway, no class, no Certificate. Three things therefore belong to the cluster and not to this chart: 1. The Gateway-API CRDs. Without them this install would have failed. 2. The Gateway named in httpRoute.parentRefs {{- range $httpRoute.parentRefs }} {{ .name }}{{ if .namespace }} in namespace {{ .namespace }}{{ end }}{{ if .sectionName }}, listener {{ .sectionName }}{{ end }} {{- end }} must exist AND its listener must allow routes from this namespace ({{ .Release.Namespace }}) in allowedRoutes. A rejected attachment shows up only in the route's status: kubectl -n {{ .Release.Namespace }} get httproute {{ $name }}-httproute -o yaml Read status.parents[].conditions — "Accepted: True" and "ResolvedRefs: True" is a working attachment. An empty status means no controller has looked at the route at all. 3. The TLS certificate for the hostname, on the Gateway's listener. ingress.clusterIssuer is NOT used in this mode and is not required: the chart renders no Certificate, so there is no tls-{{ $name }}-ingress secret and nothing for cert-manager to do. ingress.domain is still required in this mode and is {{ .Values.ingress.domain }}: it builds CORS_ORIGIN and the probes' Host header, neither of which the route replaces. If you set httpRoute.hostnames to a name ingress.domain does not carry, the server answers that name with a rejection — set both together. ingress.annotations is inert in this mode: the Gateway API has no annotation channel. Nothing this chart needs lives there, though — the WebSocket upgrade works on a plain route, and TRUST_PROXY stays correct behind a Gateway. {{- else }} NEITHER INGRESS NOR HTTPROUTE — ingress.enabled and httpRoute.enabled are both false, so this release is not published. Reach it directly with kubectl -n {{ .Release.Namespace }} port-forward svc/{{ $name }}-service 3000:80 ingress.domain stays required even without either — the server's CORS_ORIGIN is built from it — and it is still {{ .Values.ingress.domain }} here. The server answers 403 to any Host header it does not know, so address it under that name rather than as plain localhost: curl -H "Host: {{ .Values.ingress.domain }}" http://localhost:3000/health {{- end }} THE FOUR 1PASSWORD ITEMS AND THE FIELDS THEY MUST CONTAIN The OnePasswordItem CRs only carry the item paths; which *fields* each item needs is decided in the deployment, which is a tedious place to look it up. A missing field does not fail the install — the container starts with the variable empty and misbehaves later. secrets.postgresSecretRef -> {{ .Values.secrets.postgresSecretRef | default "(not set)" }} database-url secrets.redisSecretRef -> {{ .Values.secrets.redisSecretRef | default "(not set)" }} host, port, username, password, db secrets.patchmonSecretRef -> {{ .Values.secrets.patchmonSecretRef | default "(not set)" }} jwt-secret, encryption-key, session-secret secrets.oidcSecretRef -> {{ .Values.secrets.oidcSecretRef | default "(not set)" }} issuer-url, client-id, client-secret, redirect-uri, requested-scopes, button-text, user-group-name, admin-group-name Watch them materialize: kubectl -n {{ .Release.Namespace }} get onepassworditem SIGN-IN IS OIDC-ONLY — THERE IS NO LOCAL ACCOUNT TO FALL BACK ON The chart sets OIDC_DISABLE_LOCAL_AUTH=true, so the login form is gone and there is no built-in admin. If the OIDC item is wrong, nobody gets in — including you. Users are created on first login with the "readonly" role; admin rights follow the group named in the item's admin-group-name field. The item's redirect-uri has to point back at this installation{{ if or .Values.ingress.enabled $httpRoute.enabled }} under https://{{ first $hostnames }}{{ end }}, and it must match what is registered at the identity provider exactly. {{- if or .Values.ingress.enabled $httpRoute.enabled }} A WRONG ingress.domain KEEPS THE POD OUT OF READY FOREVER The 2.x server answers 403 to any request whose Host header is neither loopback nor a CORS_ORIGIN host, and CORS_ORIGIN is built from ingress.domain. The probes therefore send "Host: {{ .Values.ingress.domain }}" explicitly. If that value is not the hostname the server is actually reached under, the probes get 403, the pod never turns Ready and nothing in the log looks like a configuration error — just repeated 403s. {{- if $httpRoute.enabled }} In GATEWAY MODE that applies to httpRoute.hostnames as well: the route publishes {{ join ", " $hostnames }}, while CORS_ORIGIN and the probes still follow ingress.domain ({{ .Values.ingress.domain }}). The two have to agree. {{- end }} {{- end }} GIVE THE FIRST START TIME The server waits for the database and runs its migrations at boot, and the first 2.x start rebuilds the database indexes, which takes minutes on slow storage. The startup probe budget is {{ $budget }}s ({{ div $budget 60 }} minutes) before Kubernetes starts killing the container — a slow first boot is expected, not a hang: kubectl -n {{ .Release.Namespace }} logs deploy/{{ $name }}-deployment -f ONE REPLICA ONLY The server registers itself in the Redis presence registry under its pod hostname, which the chart pins to {{ $name }}-server. A second replica would register the same identity, so this chart supports exactly one — raising patchmon.server.replicas does not scale it out. {{- if .Values.patchmon.guacd.enabled }} GUACD SIDECAR IS ON patchmon.guacd.enabled is true, so {{ .Values.patchmon.guacd.image }} runs beside the server and it is reached at 127.0.0.1:4822 — in-browser RDP for Windows hosts, which upstream still labels beta. The sidecar is pinned separately from the chart's appVersion and needs its own review at upgrade time. It listens on the pod only; nothing exposes 4822 outside. {{- end }} {{- if eq $replicas "0" }} SCALED TO ZERO {{ if .Values.scaleDown }}scaleDown: true{{ else }}patchmon.server.replicas: 0{{ end }} — the deployment runs with replicas: 0. Secrets, Service{{ if .Values.ingress.enabled }} and Ingress{{ else if $httpRoute.enabled }} and HTTPRoute{{ end }} stay in place, and the agents on your hosts keep retrying, so nothing is lost; they simply report nothing until you set it back and upgrade again. {{- end }}