{{- $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 }}
{{- if .Values.networkPolicy.enabled }}

NETWORKPOLICY IS ACTIVE — WHAT IT CAPS, AND WHAT IT DOES NOT
  {{ .Release.Name }}-{{ .Chart.Name }}-networkpolicy selects this workload's pods.
  Inbound: TCP 3000 only. Outbound: DNS, TCP 5432 (Postgres), 6379 (Redis) and
  443 (OIDC){{ with .Values.networkPolicy.egress }}{{ with .extraPorts }}, plus TCP {{ join ", " . }}{{ end }}{{ end }}.

  THOSE ARE THE STANDARD PORTS, NOT YOUR CONFIGURED ONES — all three
  dependencies arrive as connection details inside 1Password items this chart
  never parses. A dependency elsewhere is blocked; add its port to
  networkPolicy.egress.extraPorts.

  Three limits belong in the same breath, because none of them is visible in
  the object itself:

  - EGRESS IS BY PORT, NEVER BY DESTINATION. A NetworkPolicy matches CIDRs and
    selectors and cannot express a hostname, so every egress rule permits its
    port to ANY address. That is a limit of the object, not a shortcut — this
    cluster's CNI (Canal: Flannel plus Calico) has no FQDN rule either. An
    outbound port the chart does not know about is BLOCKED: add it to
    networkPolicy.egress.extraPorts, and expect to find out through an
    application-level connection error rather than through this object.
{{- if .Values.networkPolicy.probeSources }}
  - THE PROBE PATH IS RESTRICTED in this release, because
    networkPolicy.probeSources is set to
      {{ join ", " .Values.networkPolicy.probeSources }}
    Probes come from the IP of the WORKER NODE running the pod. If none of those
    CIDRs covers the node this pod lands on, the kubelet's probes are dropped
    and THE POD NEVER BECOMES READY — the rollout then times out and the symptom
    looks like a broken application. Compare

      kubectl -n {{ .Release.Namespace }} describe pod -l app={{ .Release.Name }}-{{ .Chart.Name }}

    against `kubectl get node -o wide`.
{{- else }}
  - THE PROBED PORT IS OPEN INSIDE THE CLUSTER. networkPolicy.probeSources is
    empty, so that rule carries no source selector at all and any pod the CNI
    routes to can reach it. Not from the internet — but this is NOT pod-level
    isolation. Narrowing it means listing the worker node IPs in
    networkPolicy.probeSources, because the kubelet probes from the node's own
    address and the chart cannot know it; a wrong guess there leaves the pod
    permanently un-Ready.
{{- end }}
  - IT IS ONLY A BOUNDARY WHERE THE CNI ENFORCES POLICIES. Where it does not,
    this object is accepted and filters nothing, so everything reads as
    configured while nothing is filtered. `kubectl -n {{ .Release.Namespace }} get networkpolicy`
    shows the object exists; only a connection attempt from an unrelated
    namespace shows that it bites.

  Switch it off with networkPolicy.enabled: false.
{{- end }}
