# Supabase Helm chart values (supabase-community/supabase-kubernetes).
#
# Chart schema reference:
#   helm show values supabase-community/supabase
#
# Top-level keys recognised by this chart: secret, deployment, image,
# environment, persistence, service, autoscaling, ingress, migration,
# bigQuery. Component toggles live under `deployment.<comp>.enabled` —
# top-level keys like `functions:` or `analytics:` are silently ignored.
#
# Placeholders like {{DOMAIN}} are substituted at deploy time in
# installSupabase — do not hand-edit after deploy.
#
# We intentionally do NOT set fullnameOverride/nameOverride: the chart's
# generated resources ignore the top-level override, so attempting to
# rename creates a mismatch (ConfigMap URLs stop resolving). Deploy code
# and the production overlay reference the chart-generated names directly
# (`supabase-supabase-<component>`).

# Shared secret wiring — every component reads from the vibecarbon-secrets
# Secret created by applyVibecarbonSecrets before helm install runs. The
# chart's `secretRef` + `secretRefKey` map the chart's internal field
# names to keys in our secret; plain `secretName: vibecarbon-secrets`
# does NOT work — the chart silently falls back to generating its own
# supabase-jwt/supabase-dashboard secrets with random values (hit on
# 2026-04-19 run #3 — Kong rejected our app's SERVICE_ROLE_KEY because
# it was signed with our JWT_SECRET while the chart-generated
# supabase-jwt.secret was a different string).
secret:
  jwt:
    secretRef: vibecarbon-secrets
    secretRefKey:
      anonKey: ANON_KEY
      serviceKey: SERVICE_ROLE_KEY
      secret: JWT_SECRET
  dashboard:
    # Inline values — dashboard.openAiApiKey is chart-required and we don't
    # ship one in vibecarbon-secrets. Using secretRef here would force the
    # chart to read EVERY dashboard field from the secret (including
    # openAiApiKey), and kubelet would fail the studio StatefulSet with
    # "couldn't find key openAiApiKey in Secret vibecarbon/vibecarbon-secrets".
    # Inline values are rendered into the chart's own supabase-dashboard
    # secret at install time — still secure, just not deduped into ours.
    username: "{{ADMIN_EMAIL}}"
    password: "{{ADMIN_PASSWORD}}"
    openAiApiKey: ""
  db:
    secretRef: vibecarbon-secrets
    # When secretRef is set, the chart uses secret keys for every db.* value
    # (inline values are ignored). Maps the chart's lowercase field names to
    # the uppercase keys vibecarbon-secrets ships (DB_NAME defaulted to
    # "postgres" by applyVibecarbonSecrets).
    secretRefKey:
      password: DB_PASSWORD
      database: DB_NAME

# Override the db image with vibecarbon's wal-g-equipped build (carbon/db/
# Dockerfile: FROM supabase/postgres + wal-g v3.0.5). The stock chart image
# has no wal-g, which is why k8s historically had no WAL archiving / PITR.
# Built + sideloaded to the supabase node by deployK3s (k3s-db-build /
# k3s-db-sideload); IfNotPresent so the sideloaded image is used, not pulled.
# {{DB_IMAGE}}/{{DB_IMAGE_TAG}} substituted at deploy time in installSupabase.
image:
  db:
    repository: "{{DB_IMAGE}}"
    tag: "{{DB_IMAGE_TAG}}"
    pullPolicy: IfNotPresent

# Component enable toggles — chart's path is deployment.<comp>.enabled.
# Top-level `functions:`/`analytics:`/`vector:` are silently ignored
# (was the bug behind the 2026-04-26 e2e helm-wait timeout).
deployment:
  functions:
    # Chart's edge-runtime expects a function source at /home/deno/functions.
    # The template doesn't ship a default `main/index.ts`, so the chart's
    # default deployment fetches `https://deno.land/x/jose@v4.14.4/index.ts`
    # at boot — and the network policy + missing function source produce
    # a CrashLoopBackOff that holds up `helm --wait`. Re-enable once a
    # project actually defines a function.
    enabled: false
  analytics:
    # Logflare — disable until we need it. Adds ClickHouse + BigQuery deps.
    enabled: false
  vector:
    # Log-shipper — disable; conflicts with the optional observability stack.
    enabled: false
  db:
    # Pin postgres to the dedicated supabase node (taint applied by Pulumi).
    nodeSelector:
      dedicated: supabase
    tolerations:
      - key: dedicated
        operator: Equal
        value: supabase
        effect: NoSchedule
    # Headroom for the daily wal-g base backup, which runs INSIDE this
    # container's cgroup (via `kubectl exec`, see k8s/base/backup/cronjob.yaml).
    # backup-push streams PGDATA → lz4 → S3; size the limit for the backup
    # window, not steady state, or it throttles postgres too. WALG_UPLOAD_
    # CONCURRENCY is kept at 2 (vs compose's 4) to bound the burst.
    resources:
      requests:
        cpu: 250m
        memory: 512Mi
      limits:
        cpu: "2"
        memory: 2Gi
    # Mount the fault-tolerant WAL-archive wrapper that postgres'
    # archive_command calls (enabled at runtime via ALTER SYSTEM in
    # applyK3sManifests). Script body lives in the vibecarbon-wal-archive
    # ConfigMap (k8s/base/backup/configmap-walg.yaml) — verbatim copy of
    # carbon/volumes/db/wal-archive.sh (retry + exit-0 prevents disk-fill).
    volumeMounts:
      - name: wal-archive-script
        mountPath: /etc/postgresql/wal-archive.sh
        subPath: wal-archive.sh
        readOnly: true
      - name: walg-s3-creds
        mountPath: /etc/walg
        readOnly: true
    volumes:
      - name: wal-archive-script
        configMap:
          # Invoked as `bash /etc/postgresql/wal-archive.sh` (archive_command),
          # so no exec bit is required on the mounted file.
          name: vibecarbon-wal-archive
      - name: walg-s3-creds
        secret:
          secretName: vibecarbon-secrets
          # Optional so a no-S3 (dev) deploy still schedules the db pod; wal-g
          # is simply unconfigured then. items maps the INI key → credentials.
          optional: true
          items:
            - key: S3_CREDENTIALS_INI
              path: credentials
    # Restore bootstrap: a marker-gated init container that, ONLY when a
    # restore is requested (RESTORE_TARGET set in vibecarbon-secrets), fetches
    # the latest wal-g base backup into PGDATA before postgres starts. On a
    # normal boot RESTORE_TARGET is empty and the init container is a no-op.
    # Uses the same wal-g-equipped db image. RESTORE_TARGET is an OPTIONAL
    # secretKeyRef so deploys without the key still schedule.
    extraInitContainers:
      # ROBUSTNESS FIX (RCA 2026-06-23): the chart's init-pgsodium copies the
      # /etc/postgresql-custom confs into the pgsodium PVC ONLY when it sees the
      # volume as empty (`[ -z "$(ls -A /mnt/pgsodium)" ]`). A freshly-formatted
      # Hetzner CSI ext4 volume contains a `lost+found` dir, so init-pgsodium
      # treats it as "already initialized" and SKIPS the copy — postgres then
      # FATALs on the missing wal-g.conf / read-replica.conf / supautils.conf
      # `include` directives and CrashLoopBackOffs (intermittent, gated on the
      # CSI mke2fs format race; failed k8s + k8s-ha in the 2026-06-22 matrix).
      # This runs after init-pgsodium and idempotently restores the image's
      # default confs whenever any required conf is absent — checking the confs
      # themselves, not dir-emptiness. Same db image (its /etc/postgresql-custom
      # holds the wal-g-equipped defaults); cp -a only overlays, never deletes.
      - name: ensure-postgresql-custom
        image: "{{DB_IMAGE}}:{{DB_IMAGE_TAG}}"
        imagePullPolicy: IfNotPresent
        command:
          - /bin/sh
          - -c
          - |
            for f in supautils.conf wal-g.conf read-replica.conf; do
              if [ ! -f "/mnt/pgsodium/$f" ]; then
                echo "postgresql-custom: $f missing (init-pgsodium skipped a non-empty volume, e.g. lost+found) — restoring image defaults"
                cp -a /etc/postgresql-custom/. /mnt/pgsodium/
                break
              fi
            done
            echo "postgresql-custom verified: $(ls -A /mnt/pgsodium 2>/dev/null | tr '\n' ' ')"
        volumeMounts:
          - name: pgsodium
            mountPath: /mnt/pgsodium
      - name: walg-restore
        image: "{{DB_IMAGE}}:{{DB_IMAGE_TAG}}"
        imagePullPolicy: IfNotPresent
        command:
          - /bin/bash
          - -c
          - |
            set -euo pipefail
            if [ -z "${RESTORE_TARGET:-}" ]; then
              echo "walg-restore: RESTORE_TARGET empty — normal boot, skipping."
              exit 0
            fi
            echo "walg-restore: RESTORE_TARGET=${RESTORE_TARGET} — restoring from S3."
            if [ -f "${PGDATA}/PG_VERSION" ]; then
              echo "walg-restore: existing PGDATA — clearing for backup-fetch."
              rm -rf "${PGDATA:?}/"* "${PGDATA:?}/".* 2>/dev/null || true
            fi
            # Fetch the latest base backup into PGDATA. (For PITR the base that
            # precedes the target is also the latest base — base backups are
            # frequent, so LATEST is the right starting point either way.)
            wal-g backup-fetch "${PGDATA}" LATEST
            # Configure archive recovery. A fetched base backup is only
            # consistent AFTER replaying the WAL from its redo point to its
            # stop point — and that WAL lives in S3, not in the fetched PGDATA.
            # Without restore_command + recovery.signal postgres aborts startup
            # with "could not locate required checkpoint record" (the bug this
            # fixes — the `latest` branch previously did neither). restore_command
            # pulls each segment back from S3 via `wal-g wal-fetch`; recovery.signal
            # puts postgres into archive recovery; on reaching the end of WAL (or
            # the PITR target) it promotes to read-write. (RCA 2026-05-31: e2e
            # k8s/k8s-ha restore — postgres crash-looped post-fetch.)
            {
              echo "restore_command = 'wal-g wal-fetch \"%f\" \"%p\"'"
              echo "recovery_target_action = 'promote'"
              # Pin recovery to the fetched base backup's OWN timeline. Default
              # 'latest' makes postgres chase the newest timeline with a .history
              # file; in HA, repeated restore→promote cycles leave DIVERGENT
              # timelines in the shared wal-g S3 prefix, and 'latest' can pick one
              # that forked before this base backup → crash-loop "requested
              # timeline N is not a child of this server's history". 'current'
              # recovers along the base backup's timeline, then promotes fresh.
              echo "recovery_target_timeline = 'current'"
              if [ "${RESTORE_TARGET}" != "latest" ]; then
                # PITR: RESTORE_TARGET is an ISO-8601 timestamp — replay only up
                # to that point instead of to the end of the WAL stream.
                echo "recovery_target_time = '${RESTORE_TARGET}'"
              fi
            } >> "${PGDATA}/postgresql.auto.conf"
            touch "${PGDATA}/recovery.signal"
            echo "walg-restore: backup-fetch complete; archive recovery configured."
        env:
          - name: PGDATA
            value: /var/lib/postgresql/data
          - name: RESTORE_TARGET
            valueFrom:
              secretKeyRef:
                name: vibecarbon-secrets
                key: RESTORE_TARGET
                optional: true
          - name: WALG_S3_PREFIX
            value: "{{WALG_S3_PREFIX}}"
          - name: WALG_COMPRESSION_METHOD
            value: lz4
          - name: AWS_ENDPOINT
            value: "{{S3_ENDPOINT}}"
          - name: AWS_REGION
            value: "{{S3_REGION}}"
          - name: AWS_SHARED_CREDENTIALS_FILE
            value: /etc/walg/credentials
        volumeMounts:
          - name: postgres-volume
            mountPath: /var/lib/postgresql/data
            subPath: postgres-data
          - name: walg-s3-creds
            mountPath: /etc/walg
            readOnly: true

# Per-component environment — chart's path is environment.<comp>.
environment:
  auth:
    API_EXTERNAL_URL: https://{{DOMAIN}}
    GOTRUE_SITE_URL: https://{{DOMAIN}}
    GOTRUE_EXTERNAL_EMAIL_ENABLED: "true"
    # Auto-confirm signups: the chart doesn't ship an SMTP provider, so
    # leaving AUTOCONFIRM=false makes every signup return 500 "Error
    # sending confirmation email" (observed 2026-04-26 batch run #3 —
    # k8s verify-deploy failed at the auth_signup check). Real
    # production deployments override this to false once they wire up
    # SMTP (configure SMTP_* env in the auth section). Default-true is
    # the right call for an out-of-the-box deploy that doesn't have a
    # mail provider.
    GOTRUE_MAILER_AUTOCONFIRM: "true"
  realtime:
    DB_NAME: postgres
  # Non-secret WAL-G env for the db container (continuous WAL archiving +
  # the exec'd base-backup CronJob both read these). The chart renders
  # environment.<comp> as plain `value:` strings (no valueFrom), so the
  # SECRET keys (AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY) are injected
  # separately via a StatefulSet env patch in applyK3sManifests.
  # {{WALG_S3_PREFIX}} resolves to s3://<backup-bucket|storage-bucket>/
  # backups/<project>/walg (a SINGLE canonical prefix; fallback handled in
  # installSupabase). {{WALG_ROLE}} is 'primary' or 'standby' — it is NOT part
  # of the prefix; it's the WRITE-GUARD signal that wal-archive.sh + the backup
  # CronJob read to no-op on the standby (so a standby never writes into the
  # canonical prefix), while restore/reseed still READ the one canonical prefix.
  db:
    WALG_S3_PREFIX: "{{WALG_S3_PREFIX}}"
    WALG_ROLE: "{{WALG_ROLE}}"
    WALG_COMPRESSION_METHOD: lz4
    WALG_UPLOAD_CONCURRENCY: "2"
    AWS_ENDPOINT: "{{S3_ENDPOINT}}"
    AWS_REGION: "{{S3_REGION}}"
    # wal-g (AWS SDK) reads S3 creds from this INI file, mounted from
    # vibecarbon-secrets (deployment.db.volumes below). File-mount, not env,
    # so it's helm-owned and survives every upgrade with no db restart.
    AWS_SHARED_CREDENTIALS_FILE: /etc/walg/credentials
  storage:
    STORAGE_BACKEND: s3
  imgproxy:
    IMGPROXY_USE_S3: "true"
  studio:
    SUPABASE_PUBLIC_URL: https://{{DOMAIN}}
    STUDIO_DEFAULT_ORGANIZATION: "{{PROJECT_NAME}}"
  # Kong reads its declarative config from /usr/local/kong/kong.yml — written
  # by the chart's kong-entrypoint.sh after env substitution. The chart's
  # default `KONG_DECLARATIVE_CONFIG` already points at this path, so leave
  # it un-overridden. (Setting it to `/usr/local/kong/declarative/kong.yml`
  # — which doesn't exist — was the cause of the 2026-04-26 e2e kong
  # CrashLoopBackOff.)

# We front Kong with our own Traefik IngressRoute (k8s/base/traefik) — the
# chart's ingress object is off so we don't end up with two ingresses
# pointing at the same Service.
ingress:
  enabled: false

# Persistence:
# - db: bump default 5Gi → 10Gi.
# - functions: disable PVC creation. The chart's persistence.functions PVC
#   is independent of deployment.functions.enabled — without this, studio
#   stays Pending forever waiting for a `supabase-functions` PVC that
#   persistence.yaml gates on `deployment.functions.enabled` (off here).
persistence:
  db:
    enabled: true
    size: 10Gi
  functions:
    enabled: false
