diff --git a/.agents/architecture.md b/.agents/architecture.md index d825eab..be8b291 100644 --- a/.agents/architecture.md +++ b/.agents/architecture.md @@ -74,9 +74,9 @@ editing templates or values. 15. **URL derivation is the chart's core UX.** `PUBLIC_URL`, `NUXT_PUBLIC_API_URL`, `NUXT_PUBLIC_PUBLIC_URL` derive from the two ingress blocks; the UI origin is auto-appended to `TRUSTED_ORIGINS` - (`server.trustedOriginsAppendUI`). A missing trusted origin is the #1 + (`server.trustedOriginsAppendAdminConsole`). A missing trusted origin is the #1 dead-login misconfiguration. The chart never sets - `NUXT_PUBLIC_COOKIE_DOMAIN`: sharing a cookie domain between client-web + `NUXT_PUBLIC_COOKIE_DOMAIN`: sharing a cookie domain between client-admin-console and the hosted auth pages is unsupported by authup. 16. **Every list/map passthrough is tpl-rendered** via `authup.tplvalues.render`, so umbrella charts can inject template diff --git a/.agents/references/authup.md b/.agents/references/authup.md index 47bcfc3..f9d9708 100644 --- a/.agents/references/authup.md +++ b/.agents/references/authup.md @@ -9,7 +9,7 @@ v1.0.0-beta.58 line (chart `appVersion`). | Fact | authup source | Chart counterpart | |---|---|---| -| One image `authup/authup`, arg-dispatched entrypoint (`server/core start`, `client/web start`, `server/core migration run`, `server/core healthcheck`) | `Dockerfile`, `entrypoint.sh` (repo root) | `args` in `templates/{server,ui}/deployment.yaml`, `server/migration-job.yaml` | +| One image `authup/authup`, arg-dispatched entrypoint (`server/core start`, `client/admin-console start`, `server/core migration run`, `server/core healthcheck`) | `Dockerfile`, `entrypoint.sh` (repo root) | `args` in `templates/{server,admin-console}/deployment.yaml`, `server/migration-job.yaml` | | Entrypoint force-exports `PORT=3000` / `NUXT_PORT=3000` (chart-set PORT is dead) | `entrypoint.sh` | containerPort pinned 3000 everywhere | | Image runs as root; writable paths `/usr/src/app/writable` + npm cache | `Dockerfile` (`WRITABLE_DIRECTORY_PATH`, no `USER`) | emptyDir mounts + `npm_config_cache=/tmp/.npm-cache`; root securityContext default | | `latest`/``/`beta`/`next` tags | `.github/workflows/release.yml`, `docker-nightly.yml` | `image.tag` defaults to `Chart.AppVersion` | @@ -39,22 +39,22 @@ Config file: `authup.server.core.conf` in the process cwd `/provisioning/*` scanned at boot, fail-closed (`app/modules/provisioning/module.ts`) -> `server.provisioning.*` mount. -## client-web env surface +## client-admin-console env surface Runtime config only (prebuilt Nitro bundle; bare `API_URL` etc. are build-time -and dead): `apps/client-web/nuxt.config.ts`, -`docs/src/guide/deployment/configuration-client-web.md`. +and dead): `apps/client-admin-console/nuxt.config.ts`, +`docs/src/guide/deployment/configuration-client-admin-console.md`. `NUXT_PUBLIC_API_URL` (browser-reachable server URL), `NUXT_PUBLIC_PUBLIC_URL`, `NUXT_API_URL` (SSR-side override), `NUXT_PUBLIC_COOKIE_DOMAIN` (deliberately never set by the chart: sharing a cookie domain with the server origin is unsupported per `.agents/architecture.md` in the monorepo). Chart counterpart: -`_ui-env.tpl`. +`_admin-console-env.tpl`. ## Operational contract - `GET /` = anonymous status endpoint `{version, date, features}` (`adapters/http/controllers/workflows/status/`) -> liveness/readiness for - server-core; client-web uses its SSR `/`. + server-core; client-admin-console uses its SSR `/`. - server-core auto-runs migrations + provisioning at boot (`app/modules/database/module.ts`; no off-switch) -> generous startupProbe; optional pre-upgrade migration Job for multi-replica DDL serialization. diff --git a/.agents/references/goauthentik-helm.md b/.agents/references/goauthentik-helm.md index c496519..7eba838 100644 --- a/.agents/references/goauthentik-helm.md +++ b/.agents/references/goauthentik-helm.md @@ -9,7 +9,7 @@ auth server). | Their piece | This repo | |---|---| -| Per-role template directories, duplication accepted (they built and REVERTED the DRY role-loop in #163: "takes DRY maybe a bit too far") | `templates/server/` + `templates/ui/` | +| Per-role template directories, duplication accepted (they built and REVERTED the DRY role-loop in #163: "takes DRY maybe a bit too far") | `templates/server/` + `templates/admin-console/` | | Self-contained chart, no library dependency (their k8s-at-home common era died upstream) | zero `dependencies:` in Chart.yaml | | ct lint + kind `ct install` gated by `ct list-changed`, `ci/*-values.yaml` scenario matrix, `ci/manifests/` fixtures | `.github/workflows/lint-test.yaml` | | helm-docs drift gate (`git diff` fail step) | hardened variant in CI | diff --git a/.agents/structure.md b/.agents/structure.md index b74ee89..76a7dfa 100644 --- a/.agents/structure.md +++ b/.agents/structure.md @@ -26,7 +26,7 @@ authup/helm │ ├── mysql-values.yaml │ ├── external-db-values.yaml # externalDatabase + existingSecret, against ci/manifests fixture │ ├── valkey-values.yaml # cache + 2 replicas + migration hook - │ ├── server-only-values.yaml # headless IdP (ui.enabled=false) + │ ├── server-only-values.yaml # headless IdP (adminConsole.enabled=false) │ └── manifests/postgres.yaml # fixtures pre-applied before ct install └── templates/ ├── _helpers.tpl # names, labels, images, tplvalues, affinity, securityContext @@ -35,7 +35,7 @@ authup/helm ├── _urls.tpl # publicUrl/apiUrl/origin derivation + post-render scheme asserts ├── _ingress.tpl # shared Ingress + HTTPRoute renderers (server and ui call them) ├── _server-env.tpl # configEnv map, secretEnv list, shared volumes (deployment + job) - ├── _ui-env.tpl # ui configEnv map + ├── _admin-console-env.tpl # ui configEnv map ├── validations.yaml # render-nothing fail-fast guards (cross-field rules) ├── secret.yaml # chart-managed auth secret (admin password, system client, KEK) ├── secret-db.yaml # external-db password secret (no generation fallback) @@ -47,7 +47,7 @@ authup/helm ├── server/ # server-core: deployment, service, ingress, httproute, │ # configmap-env, configmap-configuration, configmap-provisioning, │ # migration-job, hpa, pdb, networkpolicy, servicemonitor - ├── ui/ # client-web: deployment, service, ingress, httproute, + ├── ui/ # client-admin-console: deployment, service, ingress, httproute, │ # configmap-env, hpa, pdb, networkpolicy ├── postgresql/ # built-in instance: statefulset, service, secret ├── mysql/ # built-in instance: statefulset, service, secret @@ -57,14 +57,14 @@ authup/helm ## Two components, per-role template directories `server/` (server-core, the IdP: OAuth2/OIDC surface + SSR auth pages) and -`ui/` (client-web admin console) are separate template directories with ~85% +`ui/` (client-admin-console admin console) are separate template directories with ~85% similar deployment templates. This duplication is DELIBERATE: authentik built the DRY role-loop and reverted it ("takes DRY maybe a bit too far", their PR #163). Do not introduce a role loop. A future authup server/worker split becomes a third directory with the same skeleton. Both services run the SAME image (`authup/authup`) with different args -(`server/core start` vs `client/web start`). The image entrypoint force-exports +(`server/core start` vs `client/admin-console start`). The image entrypoint force-exports `PORT=3000` for both, so `containerPort` is pinned to 3000 everywhere and only Service ports are values. diff --git a/.agents/testing.md b/.agents/testing.md index 598e2ba..cbe742b 100644 --- a/.agents/testing.md +++ b/.agents/testing.md @@ -23,7 +23,7 @@ helm template test charts/authup # defaults (b helm template test charts/authup -f charts/authup/ci/valkey-values.yaml helm template test charts/authup --set server.ingress.enabled=true \ --set server.ingress.hostname=auth.example.com --set server.ingress.tls=true \ - --set ui.ingress.enabled=true --set ui.ingress.hostname=app.example.com --set ui.ingress.tls=true + --set adminConsole.ingress.enabled=true --set adminConsole.ingress.hostname=app.example.com --set adminConsole.ingress.tls=true ``` When verifying env wiring, grep the rendered ConfigMaps/Deployments for diff --git a/AGENTS.md b/AGENTS.md index 506eaa9..016ba63 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,7 +5,7 @@ Helm charts for [Authup](https://authup.org), an authentication & authorization system. One application chart today: `charts/authup` deploys the two runtime services of the [authup monorepo](https://github.com/authup/authup) (server-core -IdP/API and the client-web admin UI) plus optional built-in PostgreSQL, MySQL +IdP/API and the client-admin-console admin UI) plus optional built-in PostgreSQL, MySQL and Valkey instances. `DESIGN.md` at the repo root is the authoritative design record: every major diff --git a/DESIGN.md b/DESIGN.md index bfa30cf..f9dfbe7 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -23,7 +23,7 @@ repo; every decision below cites its evidence. vendored locally (Apache-2.0-clean, bitnami-compatible key names). 2. **Two workloads, one chart, one image.** `authup/authup` is a single image whose entrypoint dispatches on args: `server/core start` (server-core, the IdP/API) and - `client/web start` (client-web, the Nuxt admin UI). The `authup` CLI supervisor is + `client/admin-console start` (client-admin-console, the Nuxt admin UI). The `authup` CLI supervisor is **not routable through the container entrypoint** and the monorepo docs pin "containers with one service each" as the production topology — so the chart ships two Deployments and never a combined pod. @@ -58,7 +58,7 @@ authup/helm │ │ ├── mysql-values.yaml # bundled mysql │ │ ├── external-db-values.yaml # externalDatabase + existingSecret fixture │ │ ├── redis-values.yaml # bundled valkey + replicas 2 -│ │ └── server-only-values.yaml # ui.enabled=false (headless deployment) +│ │ └── server-only-values.yaml # adminConsole.enabled=false (headless deployment) │ ├── ci/manifests/ # fixtures pre-applied before ct install │ └── templates/ │ ├── _helpers.tpl # names, labels, images (vendored bitnami-compatible) @@ -76,7 +76,7 @@ authup/helm │ ├── server/ # server-core: deployment, service, ingress, │ │ # httproute, configmap-env, migration-job, hpa, │ │ # pdb, networkpolicy, servicemonitor -│ ├── ui/ # client-web: deployment, service, ingress, +│ ├── ui/ # client-admin-console: deployment, service, ingress, │ │ # httproute, configmap-env, hpa, pdb, networkpolicy │ ├── postgresql/ # optional built-in dev instance (statefulset, │ │ # service, secret) — docker-official image @@ -100,15 +100,15 @@ charts (e.g. an `authup-remote` RBAC chart, authentik-style). ### 3.1 Workloads -| | `server` (server-core) | `ui` (client-web) | +| | `server` (server-core) | `ui` (client-admin-console) | |---|---|---| -| args | `["server/core", "start"]` | `["client/web", "start"]` | +| args | `["server/core", "start"]` | `["client/admin-console", "start"]` | | containerPort | 3000 (pinned) | 3000 (pinned) | | role | OAuth2/OIDC IdP origin + SSR auth pages | admin console, ordinary OAuth2 RP | | state | stateless w/ external DB+redis | fully stateless | | probes | httpGet `/` (status endpoint); generous startupProbe (boot = migrate + provision) | httpGet `/` | | scaling | replicas > 1 **requires redis** (hard template fail) | free | -| default | enabled | enabled (`ui.enabled: false` = headless IdP) | +| default | enabled | enabled (`adminConsole.enabled: false` = headless IdP) | Per-role template directories, ~85% duplication between the two deployment templates **accepted deliberately** — authentik tried the DRY role-loop and @@ -124,7 +124,7 @@ Vendored helpers (bitnami-compatible semantics, local implementation): - `authup.fullname` — release-scoped, honors `fullnameOverride` (bitnami key name, not Authelia's confusing nameOverride-acts-as-fullname variant), 63-char safe. -- `authup.server.fullname` / `authup.ui.fullname` — `-server` / `-ui`. +- `authup.server.fullname` / `authup.adminConsole.fullname` — `-server` / `-ui`. - `authup.labels.standard` / `authup.labels.matchLabels` — the five `app.kubernetes.io/*` labels; selectors carry ONLY name+instance+component (user `commonLabels` never leak into immutable selectors — the bitnami `pick` @@ -274,7 +274,7 @@ externalRedis: ### 3.7 Ingress and topology Two-host model as the default (server-core is the IdP origin serving the SSR auth -pages; client-web is an ordinary RP; **cookie-domain sharing between the two is +pages; client-admin-console is an ordinary RP; **cookie-domain sharing between the two is unsupported by authup** — the chart never sets `NUXT_PUBLIC_COOKIE_DOMAIN` and validates against foot-guns): @@ -296,7 +296,7 @@ publicUrl drift): - `PUBLIC_URL` ← `server.publicUrl` | derived `http(s)://` - `NUXT_PUBLIC_API_URL` ← the same value (browser-reachable, never the cluster Service DNS; the optional private `NUXT_API_URL` may point in-cluster for SSR) -- `NUXT_PUBLIC_PUBLIC_URL` ← `ui.publicUrl` | derived from `ui.ingress` +- `NUXT_PUBLIC_PUBLIC_URL` ← `adminConsole.publicUrl` | derived from `adminConsole.ingress` - `TRUSTED_ORIGINS` ← user list ∪ the UI origin (auto-appended unless disabled) - `TRUST_PROXY` defaults to `"1"` (one ingress hop), not authup's spoofable `true`-every-hop default. @@ -386,7 +386,7 @@ Cross-field rules the JSON schema cannot express, one render-nothing template: 3. `mfa.required` without `mfa.enabled`; `loginThrottle` without event log (mirrors authup's boot validations — fail at render, not at CrashLoopBackOff). 4. `auth.existingSecret` combined with inline passwords. -5. `ui.enabled` with neither ingress nor explicit `ui.publicUrl` when server +5. `adminConsole.enabled` with neither ingress nor explicit `adminConsole.publicUrl` when server ingress is on (dead-login trap), and any config that would point the UI cookie domain at the server host. 6. Tombstones for renamed values (grows over time). diff --git a/README.md b/README.md index 5b979a9..899bb2a 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ on Kubernetes. ## Highlights - 🔐 **Complete deployment** - the server-core IdP/API (OAuth2 / OpenID Connect, - hosted login & consent pages) and the client-web admin UI, from one chart + hosted login & consent pages) and the client-admin-console admin UI, from one chart - 🗄️ **Hybrid database model** - built-in PostgreSQL **or** MySQL for a one-command start, or bring your own external database - ⚡ **Optional Valkey cache** - built-in instance or external Redis; required @@ -100,7 +100,7 @@ key). | Chart | Description | |---|---| -| [authup](./charts/authup) | server-core (IdP/API) + client-web (admin UI), optional built-in PostgreSQL / MySQL / Valkey | +| [authup](./charts/authup) | server-core (IdP/API) + client-admin-console (admin UI), optional built-in PostgreSQL / MySQL / Valkey | ## Documentation diff --git a/charts/authup/BREAKING.md b/charts/authup/BREAKING.md index b8b9952..0f8fe35 100644 --- a/charts/authup/BREAKING.md +++ b/charts/authup/BREAKING.md @@ -3,4 +3,21 @@ This chart uses `0.major.minor` versioning while below 1.0.0: breaking changes land on the middle digit. Every entry lists the value migrations required. -_No breaking changes yet._ +## 0.2.0 (unreleased) + +Follows the upstream rename of the admin UI app (authup/authup#3370) and its +dedicated OAuth2 client (authup/authup#3371). + +- Values section `ui.*` -> `adminConsole.*`. Every key moves unchanged, e.g. + `ui.enabled` -> `adminConsole.enabled`, `ui.ingress.hostname` -> + `adminConsole.ingress.hostname`. +- `server.trustedOriginsAppendUI` -> `server.trustedOriginsAppendAdminConsole`. +- Rendered resource names change suffix `-ui` -> `-admin-console` + (Deployment, Service, Ingress, ...). Helm re-creates them on upgrade; + expect a brief admin-UI rollout and update anything referencing the old + Service name directly. +- The admin UI container now starts with `client/admin-console start` and + logs in against the per-realm `admin-console` OAuth2 client. Requires an + authup image containing authup/authup#3370 + #3371; older images only know + `client/web` and would crash-loop. Ship this chart version together with + the `appVersion` bump to that release. diff --git a/charts/authup/Chart.yaml b/charts/authup/Chart.yaml index c6d1db3..ce034ba 100644 --- a/charts/authup/Chart.yaml +++ b/charts/authup/Chart.yaml @@ -1,6 +1,6 @@ apiVersion: v2 name: authup -description: Authup is an authentication & authorization system. This chart deploys the server-core IdP/API service and the client-web admin UI, with optional built-in PostgreSQL, MySQL and Valkey instances. +description: Authup is an authentication & authorization system. This chart deploys the server-core IdP/API service and the client-admin-console admin UI, with optional built-in PostgreSQL, MySQL and Valkey instances. type: application version: 0.1.0 appVersion: "1.0.0-beta.58" diff --git a/charts/authup/README.md b/charts/authup/README.md index 6e7a5be..570f2b9 100644 --- a/charts/authup/README.md +++ b/charts/authup/README.md @@ -8,13 +8,13 @@ ![Version: 0.1.0](https://img.shields.io/badge/Version-0.1.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 1.0.0-beta.58](https://img.shields.io/badge/AppVersion-1.0.0--beta.58-informational?style=flat-square) -Authup is an authentication & authorization system. This chart deploys the server-core IdP/API service and the client-web admin UI, with optional built-in PostgreSQL, MySQL and Valkey instances. It deploys: +Authup is an authentication & authorization system. This chart deploys the server-core IdP/API service and the client-admin-console admin UI, with optional built-in PostgreSQL, MySQL and Valkey instances. It deploys: - **server-core** — the Authup IdP/API service: the OAuth2/OIDC protocol surface plus the server-rendered auth pages (login, consent, registration, password recovery). This is the identity origin. -- **client-web** — the Nuxt-based admin UI, an ordinary OAuth2 relying party - (optional; disable with `ui.enabled=false` for a headless IdP). +- **client-admin-console** — the Nuxt-based admin UI, an ordinary OAuth2 relying party + (optional; disable with `adminConsole.enabled=false` for a headless IdP). - optionally, single-instance **PostgreSQL**, **MySQL** or **Valkey** built-in instances on docker-official images — a convenience for dev and small deployments, not the production database story. @@ -111,6 +111,102 @@ Kubernetes: `>=1.25.0-0` | Key | Type | Default | Description | |-----|------|---------|-------------| +| adminConsole.affinity | object | `{}` | Affinity (overrides the anti-affinity preset when set) | +| adminConsole.apiUrl | string | `""` | Browser-facing server-core URL (NUXT_PUBLIC_API_URL). "" = the server public URL. Must be reachable from the user's browser, never a cluster-internal DNS name | +| adminConsole.args | list | `[]` | Override the container args | +| adminConsole.autoscaling.hpa.enabled | bool | `false` | Enable HPA for the UI | +| adminConsole.autoscaling.hpa.maxReplicas | int | `5` | Maximum replicas | +| adminConsole.autoscaling.hpa.minReplicas | int | `2` | Minimum replicas | +| adminConsole.autoscaling.hpa.targetCPU | int | `75` | Target CPU utilization percentage | +| adminConsole.autoscaling.hpa.targetMemory | string | `""` | Target memory utilization percentage | +| adminConsole.command | list | `[]` | Override the container command | +| adminConsole.config | object | `{}` | Extra environment variables rendered literally into the env ConfigMap | +| adminConsole.containerSecurityContext | object | `{"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]},"enabled":true,"readOnlyRootFilesystem":false,"runAsNonRoot":false,"runAsUser":0,"seccompProfile":{"type":"RuntimeDefault"}}` | Container security context (same root-image caveat as the server) | +| adminConsole.customLivenessProbe | object | `{}` | Custom liveness probe | +| adminConsole.customReadinessProbe | object | `{}` | Custom readiness probe | +| adminConsole.customStartupProbe | object | `{}` | Custom startup probe | +| adminConsole.disableRestartOnChanges | bool | `false` | Disable the checksum annotations that roll pods on config changes | +| adminConsole.enabled | bool | `true` | Deploy the client-admin-console admin UI (false = headless IdP) | +| adminConsole.extraEnvVars | list | `[]` | Extra environment variables for the UI container | +| adminConsole.extraEnvVarsCM | string | `""` | Extra ConfigMap with environment variables (tpl-rendered name) | +| adminConsole.extraEnvVarsSecret | string | `""` | Extra Secret with environment variables (tpl-rendered name) | +| adminConsole.extraVolumeMounts | list | `[]` | Extra volume mounts (tpl-rendered) | +| adminConsole.extraVolumes | list | `[]` | Extra volumes (tpl-rendered) | +| adminConsole.hostAliases | list | `[]` | Pod host aliases | +| adminConsole.ingress.annotations | object | `{}` | Ingress annotations (tpl-rendered) | +| adminConsole.ingress.certManager | bool | `false` | Request a cert-manager certificate (adds kubernetes.io/tls-acme) | +| adminConsole.ingress.enabled | bool | `false` | Enable ingress for the UI | +| adminConsole.ingress.extraHosts | list | `[]` | Extra hosts | +| adminConsole.ingress.extraPaths | list | `[]` | Extra paths for the primary host | +| adminConsole.ingress.extraRules | list | `[]` | Full custom rules (tpl-rendered; overrides the generated rule) | +| adminConsole.ingress.extraTls | list | `[]` | Extra TLS entries | +| adminConsole.ingress.hostname | string | `""` | Ingress hostname (tpl-rendered); also drives the derived UI public URL | +| adminConsole.ingress.ingressClassName | string | `""` | Ingress class name | +| adminConsole.ingress.path | string | `"/"` | Ingress path | +| adminConsole.ingress.pathType | string | `"Prefix"` | Ingress path type | +| adminConsole.ingress.tls | bool | `false` | Enable TLS for the hostname | +| adminConsole.initContainers | list | `[]` | Init containers (tpl-rendered) | +| adminConsole.internalApiUrl | string | `""` | Server-side (SSR) API URL override (NUXT_API_URL), e.g. the in-cluster service URL to keep SSR traffic off the ingress | +| adminConsole.lifecycleHooks | object | `{}` | Container lifecycle hooks | +| adminConsole.livenessProbe.enabled | bool | `true` | Enable the liveness probe | +| adminConsole.livenessProbe.failureThreshold | int | `3` | | +| adminConsole.livenessProbe.initialDelaySeconds | int | `0` | | +| adminConsole.livenessProbe.periodSeconds | int | `30` | | +| adminConsole.livenessProbe.successThreshold | int | `1` | | +| adminConsole.livenessProbe.timeoutSeconds | int | `5` | | +| adminConsole.networkPolicy.allowExternal | bool | `true` | Allow ingress from anywhere | +| adminConsole.networkPolicy.allowExternalEgress | bool | `true` | Allow all egress | +| adminConsole.networkPolicy.enabled | bool | `false` | Create a NetworkPolicy for the UI | +| adminConsole.networkPolicy.extraEgress | list | `[]` | Extra egress rules | +| adminConsole.networkPolicy.extraIngress | list | `[]` | Extra ingress rules | +| adminConsole.networkPolicy.ingressNSMatchLabels | object | `{}` | Namespace labels allowed to connect when allowExternal is false | +| adminConsole.networkPolicy.ingressPodMatchLabels | object | `{}` | Pod labels allowed to connect when allowExternal is false | +| adminConsole.nodeSelector | object | `{}` | Node selector | +| adminConsole.pdb.create | bool | `false` | Create a PodDisruptionBudget for the UI | +| adminConsole.pdb.maxUnavailable | string | `""` | Maximum unavailable pods (defaults to 1 when both are empty) | +| adminConsole.pdb.minAvailable | string | `""` | Minimum available pods | +| adminConsole.podAnnotations | object | `{}` | Pod annotations (tpl-rendered) | +| adminConsole.podAntiAffinityPreset | string | `"soft"` | Pod anti-affinity preset: soft, hard or "" | +| adminConsole.podLabels | object | `{}` | Pod labels (tpl-rendered) | +| adminConsole.podSecurityContext | object | `{"enabled":true,"fsGroup":1000}` | Pod security context | +| adminConsole.priorityClassName | string | `""` | Priority class name | +| adminConsole.publicUrl | string | `""` | Public URL of the UI (NUXT_PUBLIC_PUBLIC_URL). "" = derived from adminConsole.ingress | +| adminConsole.readinessProbe.enabled | bool | `true` | Enable the readiness probe | +| adminConsole.readinessProbe.failureThreshold | int | `3` | | +| adminConsole.readinessProbe.initialDelaySeconds | int | `0` | | +| adminConsole.readinessProbe.periodSeconds | int | `10` | | +| adminConsole.readinessProbe.successThreshold | int | `1` | | +| adminConsole.readinessProbe.timeoutSeconds | int | `5` | | +| adminConsole.replicaCount | int | `1` | Number of UI replicas (fully stateless, scale freely) | +| adminConsole.resources | object | `{"limits":{"memory":"512Mi"},"requests":{"cpu":"100m","memory":"256Mi"}}` | UI container resources | +| adminConsole.revisionHistoryLimit | int | `3` | Deployment revision history limit | +| adminConsole.route.annotations | object | `{}` | HTTPRoute annotations | +| adminConsole.route.enabled | bool | `false` | Create a Gateway API HTTPRoute for the UI | +| adminConsole.route.hostnames | list | `[]` | Route hostnames ([] = derived from adminConsole.publicUrl / ingress hostname) | +| adminConsole.route.parentRefs | list | `[]` | Gateway parentRefs | +| adminConsole.schedulerName | string | `""` | Scheduler name | +| adminConsole.service.annotations | object | `{}` | Service annotations (tpl-rendered) | +| adminConsole.service.clusterIP | string | `""` | Static cluster IP | +| adminConsole.service.externalTrafficPolicy | string | `"Cluster"` | External traffic policy | +| adminConsole.service.extraPorts | list | `[]` | Extra service ports | +| adminConsole.service.loadBalancerIP | string | `""` | LoadBalancer IP | +| adminConsole.service.loadBalancerSourceRanges | list | `[]` | LoadBalancer source ranges | +| adminConsole.service.nodePorts.http | string | `""` | Node port ("" = auto-assign) | +| adminConsole.service.ports.http | int | `3000` | Service HTTP port (the container port is fixed at 3000) | +| adminConsole.service.sessionAffinity | string | `"None"` | Session affinity | +| adminConsole.service.sessionAffinityConfig | object | `{}` | Session affinity config | +| adminConsole.service.type | string | `"ClusterIP"` | Service type | +| adminConsole.sidecars | list | `[]` | Sidecar containers (tpl-rendered) | +| adminConsole.startupProbe.enabled | bool | `true` | Enable the startup probe | +| adminConsole.startupProbe.failureThreshold | int | `24` | | +| adminConsole.startupProbe.initialDelaySeconds | int | `5` | | +| adminConsole.startupProbe.periodSeconds | int | `5` | | +| adminConsole.startupProbe.successThreshold | int | `1` | | +| adminConsole.startupProbe.timeoutSeconds | int | `5` | | +| adminConsole.terminationGracePeriodSeconds | int | `30` | Pod termination grace period | +| adminConsole.tolerations | list | `[]` | Tolerations | +| adminConsole.topologySpreadConstraints | list | `[]` | Topology spread constraints | +| adminConsole.updateStrategy | object | `{"type":"RollingUpdate"}` | Deployment update strategy | | auth.adminPassword | string | `""` | Initial admin user password ("" = generate once, keep across upgrades). Changing it after the first install only takes effect with adminPasswordReset=true for one upgrade cycle | | auth.adminPasswordReset | bool | `false` | Re-assert the admin password on every boot (USER_ADMIN_PASSWORD_RESET) | | auth.existingSecret | string | `""` | Existing secret holding the keys below instead of the chart-managed secret (tpl-rendered) | @@ -313,7 +409,7 @@ Kubernetes: `>=1.25.0-0` | server.topologySpreadConstraints | list | `[]` | Topology spread constraints (a missing labelSelector is filled with the pod's selector labels) | | server.trustProxy | string | `"1"` | TRUST_PROXY setting. The chart defaults to one trusted hop (the ingress), not authup's spoofable trust-everything default | | server.trustedOrigins | list | `[]` | Additional trusted first-party app origins (TRUSTED_ORIGINS). Each listed origin can obtain full-permission tokens via the per-realm web client. List or comma-separated string; tpl-rendered. | -| server.trustedOriginsAppendUI | bool | `true` | Automatically append the client-web UI origin to TRUSTED_ORIGINS (removes the most common dead-login misconfiguration) | +| server.trustedOriginsAppendAdminConsole | bool | `true` | Automatically append the client-admin-console UI origin to TRUSTED_ORIGINS (removes the most common dead-login misconfiguration) | | server.updateStrategy | object | `{"type":"RollingUpdate"}` | Deployment update strategy | | serviceAccount.annotations | object | `{}` | ServiceAccount annotations (tpl-rendered) | | serviceAccount.automountServiceAccountToken | bool | `false` | Automount the service account token | @@ -322,102 +418,6 @@ Kubernetes: `>=1.25.0-0` | smtp.connectionString | string | `""` | SMTP connection string (smtp(s)://user:pass@host:port); stored in a chart-managed secret | | smtp.existingSecret | string | `""` | Existing secret holding the SMTP connection string (tpl-rendered) | | smtp.existingSecretKey | string | `"smtp-connection-string"` | Key inside smtp.existingSecret holding the connection string | -| ui.affinity | object | `{}` | Affinity (overrides the anti-affinity preset when set) | -| ui.apiUrl | string | `""` | Browser-facing server-core URL (NUXT_PUBLIC_API_URL). "" = the server public URL. Must be reachable from the user's browser, never a cluster-internal DNS name | -| ui.args | list | `[]` | Override the container args | -| ui.autoscaling.hpa.enabled | bool | `false` | Enable HPA for the UI | -| ui.autoscaling.hpa.maxReplicas | int | `5` | Maximum replicas | -| ui.autoscaling.hpa.minReplicas | int | `2` | Minimum replicas | -| ui.autoscaling.hpa.targetCPU | int | `75` | Target CPU utilization percentage | -| ui.autoscaling.hpa.targetMemory | string | `""` | Target memory utilization percentage | -| ui.command | list | `[]` | Override the container command | -| ui.config | object | `{}` | Extra environment variables rendered literally into the env ConfigMap | -| ui.containerSecurityContext | object | `{"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]},"enabled":true,"readOnlyRootFilesystem":false,"runAsNonRoot":false,"runAsUser":0,"seccompProfile":{"type":"RuntimeDefault"}}` | Container security context (same root-image caveat as the server) | -| ui.customLivenessProbe | object | `{}` | Custom liveness probe | -| ui.customReadinessProbe | object | `{}` | Custom readiness probe | -| ui.customStartupProbe | object | `{}` | Custom startup probe | -| ui.disableRestartOnChanges | bool | `false` | Disable the checksum annotations that roll pods on config changes | -| ui.enabled | bool | `true` | Deploy the client-web admin UI (false = headless IdP) | -| ui.extraEnvVars | list | `[]` | Extra environment variables for the UI container | -| ui.extraEnvVarsCM | string | `""` | Extra ConfigMap with environment variables (tpl-rendered name) | -| ui.extraEnvVarsSecret | string | `""` | Extra Secret with environment variables (tpl-rendered name) | -| ui.extraVolumeMounts | list | `[]` | Extra volume mounts (tpl-rendered) | -| ui.extraVolumes | list | `[]` | Extra volumes (tpl-rendered) | -| ui.hostAliases | list | `[]` | Pod host aliases | -| ui.ingress.annotations | object | `{}` | Ingress annotations (tpl-rendered) | -| ui.ingress.certManager | bool | `false` | Request a cert-manager certificate (adds kubernetes.io/tls-acme) | -| ui.ingress.enabled | bool | `false` | Enable ingress for the UI | -| ui.ingress.extraHosts | list | `[]` | Extra hosts | -| ui.ingress.extraPaths | list | `[]` | Extra paths for the primary host | -| ui.ingress.extraRules | list | `[]` | Full custom rules (tpl-rendered; overrides the generated rule) | -| ui.ingress.extraTls | list | `[]` | Extra TLS entries | -| ui.ingress.hostname | string | `""` | Ingress hostname (tpl-rendered); also drives the derived UI public URL | -| ui.ingress.ingressClassName | string | `""` | Ingress class name | -| ui.ingress.path | string | `"/"` | Ingress path | -| ui.ingress.pathType | string | `"Prefix"` | Ingress path type | -| ui.ingress.tls | bool | `false` | Enable TLS for the hostname | -| ui.initContainers | list | `[]` | Init containers (tpl-rendered) | -| ui.internalApiUrl | string | `""` | Server-side (SSR) API URL override (NUXT_API_URL), e.g. the in-cluster service URL to keep SSR traffic off the ingress | -| ui.lifecycleHooks | object | `{}` | Container lifecycle hooks | -| ui.livenessProbe.enabled | bool | `true` | Enable the liveness probe | -| ui.livenessProbe.failureThreshold | int | `3` | | -| ui.livenessProbe.initialDelaySeconds | int | `0` | | -| ui.livenessProbe.periodSeconds | int | `30` | | -| ui.livenessProbe.successThreshold | int | `1` | | -| ui.livenessProbe.timeoutSeconds | int | `5` | | -| ui.networkPolicy.allowExternal | bool | `true` | Allow ingress from anywhere | -| ui.networkPolicy.allowExternalEgress | bool | `true` | Allow all egress | -| ui.networkPolicy.enabled | bool | `false` | Create a NetworkPolicy for the UI | -| ui.networkPolicy.extraEgress | list | `[]` | Extra egress rules | -| ui.networkPolicy.extraIngress | list | `[]` | Extra ingress rules | -| ui.networkPolicy.ingressNSMatchLabels | object | `{}` | Namespace labels allowed to connect when allowExternal is false | -| ui.networkPolicy.ingressPodMatchLabels | object | `{}` | Pod labels allowed to connect when allowExternal is false | -| ui.nodeSelector | object | `{}` | Node selector | -| ui.pdb.create | bool | `false` | Create a PodDisruptionBudget for the UI | -| ui.pdb.maxUnavailable | string | `""` | Maximum unavailable pods (defaults to 1 when both are empty) | -| ui.pdb.minAvailable | string | `""` | Minimum available pods | -| ui.podAnnotations | object | `{}` | Pod annotations (tpl-rendered) | -| ui.podAntiAffinityPreset | string | `"soft"` | Pod anti-affinity preset: soft, hard or "" | -| ui.podLabels | object | `{}` | Pod labels (tpl-rendered) | -| ui.podSecurityContext | object | `{"enabled":true,"fsGroup":1000}` | Pod security context | -| ui.priorityClassName | string | `""` | Priority class name | -| ui.publicUrl | string | `""` | Public URL of the UI (NUXT_PUBLIC_PUBLIC_URL). "" = derived from ui.ingress | -| ui.readinessProbe.enabled | bool | `true` | Enable the readiness probe | -| ui.readinessProbe.failureThreshold | int | `3` | | -| ui.readinessProbe.initialDelaySeconds | int | `0` | | -| ui.readinessProbe.periodSeconds | int | `10` | | -| ui.readinessProbe.successThreshold | int | `1` | | -| ui.readinessProbe.timeoutSeconds | int | `5` | | -| ui.replicaCount | int | `1` | Number of UI replicas (fully stateless, scale freely) | -| ui.resources | object | `{"limits":{"memory":"512Mi"},"requests":{"cpu":"100m","memory":"256Mi"}}` | UI container resources | -| ui.revisionHistoryLimit | int | `3` | Deployment revision history limit | -| ui.route.annotations | object | `{}` | HTTPRoute annotations | -| ui.route.enabled | bool | `false` | Create a Gateway API HTTPRoute for the UI | -| ui.route.hostnames | list | `[]` | Route hostnames ([] = derived from ui.publicUrl / ingress hostname) | -| ui.route.parentRefs | list | `[]` | Gateway parentRefs | -| ui.schedulerName | string | `""` | Scheduler name | -| ui.service.annotations | object | `{}` | Service annotations (tpl-rendered) | -| ui.service.clusterIP | string | `""` | Static cluster IP | -| ui.service.externalTrafficPolicy | string | `"Cluster"` | External traffic policy | -| ui.service.extraPorts | list | `[]` | Extra service ports | -| ui.service.loadBalancerIP | string | `""` | LoadBalancer IP | -| ui.service.loadBalancerSourceRanges | list | `[]` | LoadBalancer source ranges | -| ui.service.nodePorts.http | string | `""` | Node port ("" = auto-assign) | -| ui.service.ports.http | int | `3000` | Service HTTP port (the container port is fixed at 3000) | -| ui.service.sessionAffinity | string | `"None"` | Session affinity | -| ui.service.sessionAffinityConfig | object | `{}` | Session affinity config | -| ui.service.type | string | `"ClusterIP"` | Service type | -| ui.sidecars | list | `[]` | Sidecar containers (tpl-rendered) | -| ui.startupProbe.enabled | bool | `true` | Enable the startup probe | -| ui.startupProbe.failureThreshold | int | `24` | | -| ui.startupProbe.initialDelaySeconds | int | `5` | | -| ui.startupProbe.periodSeconds | int | `5` | | -| ui.startupProbe.successThreshold | int | `1` | | -| ui.startupProbe.timeoutSeconds | int | `5` | | -| ui.terminationGracePeriodSeconds | int | `30` | Pod termination grace period | -| ui.tolerations | list | `[]` | Tolerations | -| ui.topologySpreadConstraints | list | `[]` | Topology spread constraints | -| ui.updateStrategy | object | `{"type":"RollingUpdate"}` | Deployment update strategy | | useHelmHooks | bool | `true` | Render Job hook annotations (set false for ArgoCD / Flux) | | valkey.affinity | object | `{}` | Valkey affinity | | valkey.auth.password | string | `""` | Valkey password ("" = generate once, keep across upgrades) | diff --git a/charts/authup/README.md.gotmpl b/charts/authup/README.md.gotmpl index cc02c22..ee9178a 100644 --- a/charts/authup/README.md.gotmpl +++ b/charts/authup/README.md.gotmpl @@ -15,8 +15,8 @@ - **server-core** — the Authup IdP/API service: the OAuth2/OIDC protocol surface plus the server-rendered auth pages (login, consent, registration, password recovery). This is the identity origin. -- **client-web** — the Nuxt-based admin UI, an ordinary OAuth2 relying party - (optional; disable with `ui.enabled=false` for a headless IdP). +- **client-admin-console** — the Nuxt-based admin UI, an ordinary OAuth2 relying party + (optional; disable with `adminConsole.enabled=false` for a headless IdP). - optionally, single-instance **PostgreSQL**, **MySQL** or **Valkey** built-in instances on docker-official images — a convenience for dev and small deployments, not the production database story. diff --git a/charts/authup/ci/default-values.yaml b/charts/authup/ci/default-values.yaml index 5311aff..8a84938 100644 --- a/charts/authup/ci/default-values.yaml +++ b/charts/authup/ci/default-values.yaml @@ -7,7 +7,7 @@ server: requests: cpu: 50m memory: 256Mi -ui: +adminConsole: resources: requests: cpu: 25m diff --git a/charts/authup/ci/external-db-values.yaml b/charts/authup/ci/external-db-values.yaml index 1a95b50..1a9b7da 100644 --- a/charts/authup/ci/external-db-values.yaml +++ b/charts/authup/ci/external-db-values.yaml @@ -16,5 +16,5 @@ server: requests: cpu: 50m memory: 256Mi -ui: +adminConsole: enabled: false diff --git a/charts/authup/ci/mysql-values.yaml b/charts/authup/ci/mysql-values.yaml index d6c614f..4bde7a3 100644 --- a/charts/authup/ci/mysql-values.yaml +++ b/charts/authup/ci/mysql-values.yaml @@ -10,7 +10,7 @@ server: requests: cpu: 50m memory: 256Mi -ui: +adminConsole: resources: requests: cpu: 25m diff --git a/charts/authup/ci/server-only-values.yaml b/charts/authup/ci/server-only-values.yaml index 512d0f5..60eeaba 100644 --- a/charts/authup/ci/server-only-values.yaml +++ b/charts/authup/ci/server-only-values.yaml @@ -2,7 +2,7 @@ postgresql: persistence: enabled: false -ui: +adminConsole: enabled: false server: resources: diff --git a/charts/authup/ci/valkey-values.yaml b/charts/authup/ci/valkey-values.yaml index eea0654..ccfa0a0 100644 --- a/charts/authup/ci/valkey-values.yaml +++ b/charts/authup/ci/valkey-values.yaml @@ -12,7 +12,7 @@ server: requests: cpu: 50m memory: 256Mi -ui: +adminConsole: resources: requests: cpu: 25m diff --git a/charts/authup/templates/NOTES.txt b/charts/authup/templates/NOTES.txt index b2ef951..6e4a8e3 100644 --- a/charts/authup/templates/NOTES.txt +++ b/charts/authup/templates/NOTES.txt @@ -1,7 +1,7 @@ ** Authup {{ .Chart.AppVersion }} has been installed as release {{ .Release.Name }}. ** {{- $serverUrl := include "authup.server.publicUrl" . }} -{{- $uiUrl := include "authup.ui.publicUrl" . }} +{{- $uiUrl := include "authup.adminConsole.publicUrl" . }} {{- if .Values.server.enabled }} @@ -25,14 +25,14 @@ server-core (IdP / API): {{- end }} {{- end }} -{{- if .Values.ui.enabled }} +{{- if .Values.adminConsole.enabled }} -client-web (admin UI): +client-admin-console (admin UI): {{- if $uiUrl }} URL: {{ $uiUrl }} {{- else }} - In-cluster URL: http://{{ include "authup.ui.fullname" . }}.{{ include "authup.namespace" . }}.svc:{{ .Values.ui.service.ports.http }} - (port-forward: kubectl -n {{ include "authup.namespace" . }} port-forward svc/{{ include "authup.ui.fullname" . }} 3000:{{ .Values.ui.service.ports.http }}) + In-cluster URL: http://{{ include "authup.adminConsole.fullname" . }}.{{ include "authup.namespace" . }}.svc:{{ .Values.adminConsole.service.ports.http }} + (port-forward: kubectl -n {{ include "authup.namespace" . }} port-forward svc/{{ include "authup.adminConsole.fullname" . }} 3000:{{ .Values.adminConsole.service.ports.http }}) {{- end }} {{- end }} @@ -63,11 +63,11 @@ value. In GitOps setups, set explicit auth values or auth.existingSecret. {{- end }} {{- end }} -{{- if and .Values.ui.enabled (not $uiUrl) $serverUrl }} +{{- if and .Values.adminConsole.enabled (not $uiUrl) $serverUrl }} -WARNING: the UI has no resolvable public URL (ui.publicUrl / ui.ingress). Its +WARNING: the UI has no resolvable public URL (adminConsole.publicUrl / adminConsole.ingress). Its origin cannot be appended to TRUSTED_ORIGINS, and logins from the UI will fail -after the OAuth2 redirect. Set ui.ingress.hostname or ui.publicUrl. +after the OAuth2 redirect. Set adminConsole.ingress.hostname or adminConsole.publicUrl. {{- end }} {{- if and (gt (int .Values.server.replicaCount) 1) .Values.server.migration.enabled }} diff --git a/charts/authup/templates/_ui-env.tpl b/charts/authup/templates/_admin-console-env.tpl similarity index 53% rename from charts/authup/templates/_ui-env.tpl rename to charts/authup/templates/_admin-console-env.tpl index 397ecb0..c5d101d 100644 --- a/charts/authup/templates/_ui-env.tpl +++ b/charts/authup/templates/_admin-console-env.tpl @@ -1,23 +1,23 @@ {{/* -client-web environment as a YAML map. The published UI bundle only honors Nuxt +client-admin-console environment as a YAML map. The published UI bundle only honors Nuxt runtime-config names (NUXT_*); the API URL must be the BROWSER-reachable server-core URL, never a cluster-internal service name. The chart deliberately never sets NUXT_PUBLIC_COOKIE_DOMAIN: sharing a cookie -domain between client-web and the hosted auth pages is unsupported by authup. +domain between client-admin-console and the hosted auth pages is unsupported by authup. */}} -{{- define "authup.ui.configEnv" -}} -{{- $apiUrl := include "authup.ui.apiUrl" . }} +{{- define "authup.adminConsole.configEnv" -}} +{{- $apiUrl := include "authup.adminConsole.apiUrl" . }} {{- if $apiUrl }} NUXT_PUBLIC_API_URL: {{ $apiUrl | quote }} {{- end }} -{{- $publicUrl := include "authup.ui.publicUrl" . }} +{{- $publicUrl := include "authup.adminConsole.publicUrl" . }} {{- if $publicUrl }} NUXT_PUBLIC_PUBLIC_URL: {{ $publicUrl | quote }} {{- end }} -{{- if .Values.ui.internalApiUrl }} -NUXT_API_URL: {{ include "authup.tplvalues.render" (dict "value" .Values.ui.internalApiUrl "context" $) | quote }} +{{- if .Values.adminConsole.internalApiUrl }} +NUXT_API_URL: {{ include "authup.tplvalues.render" (dict "value" .Values.adminConsole.internalApiUrl "context" $) | quote }} {{- end }} -{{- range $key, $value := .Values.ui.config }} +{{- range $key, $value := .Values.adminConsole.config }} {{ $key }}: {{ include "authup.tplvalues.render" (dict "value" ($value | toString) "context" $) | quote }} {{- end }} {{- end -}} diff --git a/charts/authup/templates/_helpers.tpl b/charts/authup/templates/_helpers.tpl index 154ce04..9b362b4 100644 --- a/charts/authup/templates/_helpers.tpl +++ b/charts/authup/templates/_helpers.tpl @@ -30,8 +30,8 @@ component onto one identical name. {{- printf "%s-server" (include "authup.fullname" . | trunc 52 | trimSuffix "-") -}} {{- end -}} -{{- define "authup.ui.fullname" -}} -{{- printf "%s-ui" (include "authup.fullname" . | trunc 52 | trimSuffix "-") -}} +{{- define "authup.adminConsole.fullname" -}} +{{- printf "%s-admin-console" (include "authup.fullname" . | trunc 52 | trimSuffix "-") -}} {{- end -}} {{- define "authup.postgresql.fullname" -}} diff --git a/charts/authup/templates/_urls.tpl b/charts/authup/templates/_urls.tpl index f2b3d39..633ad92 100644 --- a/charts/authup/templates/_urls.tpl +++ b/charts/authup/templates/_urls.tpl @@ -1,5 +1,5 @@ {{/* -URL derivation. server.publicUrl / ui.publicUrl always win; otherwise the URL is +URL derivation. server.publicUrl / adminConsole.publicUrl always win; otherwise the URL is derived from the component's ingress (scheme from tls/certManager, host, path). Returns "" when nothing can be derived. */}} @@ -33,20 +33,20 @@ scheme-less result can never reach an env var or origin derivation. {{- end -}} {{- end -}} -{{- define "authup.ui.publicUrl" -}} -{{- if .Values.ui.publicUrl -}} -{{- include "authup.assertUrlScheme" (dict "key" "ui.publicUrl" "url" (include "authup.tplvalues.render" (dict "value" .Values.ui.publicUrl "context" $) | trimSuffix "/")) -}} +{{- define "authup.adminConsole.publicUrl" -}} +{{- if .Values.adminConsole.publicUrl -}} +{{- include "authup.assertUrlScheme" (dict "key" "adminConsole.publicUrl" "url" (include "authup.tplvalues.render" (dict "value" .Values.adminConsole.publicUrl "context" $) | trimSuffix "/")) -}} {{- else -}} -{{- include "authup.ingress.derivedUrl" (dict "ingress" .Values.ui.ingress "context" $) -}} +{{- include "authup.ingress.derivedUrl" (dict "ingress" .Values.adminConsole.ingress "context" $) -}} {{- end -}} {{- end -}} {{/* Browser-facing server-core URL for the UI (NUXT_PUBLIC_API_URL). */}} -{{- define "authup.ui.apiUrl" -}} -{{- if .Values.ui.apiUrl -}} -{{- include "authup.assertUrlScheme" (dict "key" "ui.apiUrl" "url" (include "authup.tplvalues.render" (dict "value" .Values.ui.apiUrl "context" $) | trimSuffix "/")) -}} +{{- define "authup.adminConsole.apiUrl" -}} +{{- if .Values.adminConsole.apiUrl -}} +{{- include "authup.assertUrlScheme" (dict "key" "adminConsole.apiUrl" "url" (include "authup.tplvalues.render" (dict "value" .Values.adminConsole.apiUrl "context" $) | trimSuffix "/")) -}} {{- else -}} {{- include "authup.server.publicUrl" . -}} {{- end -}} @@ -82,8 +82,8 @@ disabled or already covered by the server public URL's origin. {{- $origins = append $origins (trim (include "authup.tplvalues.render" (dict "value" . "context" $))) -}} {{- end -}} {{- end -}} -{{- if and .Values.ui.enabled .Values.server.trustedOriginsAppendUI -}} -{{- $uiOrigin := include "authup.urlOrigin" (include "authup.ui.publicUrl" .) -}} +{{- if and .Values.adminConsole.enabled .Values.server.trustedOriginsAppendAdminConsole -}} +{{- $uiOrigin := include "authup.urlOrigin" (include "authup.adminConsole.publicUrl" .) -}} {{- $serverOrigin := include "authup.urlOrigin" (include "authup.server.publicUrl" .) -}} {{- if and $uiOrigin (ne $uiOrigin $serverOrigin) -}} {{- $origins = append $origins $uiOrigin -}} diff --git a/charts/authup/templates/ui/configmap-env.yaml b/charts/authup/templates/admin-console/configmap-env.yaml similarity index 54% rename from charts/authup/templates/ui/configmap-env.yaml rename to charts/authup/templates/admin-console/configmap-env.yaml index a479327..1799903 100644 --- a/charts/authup/templates/ui/configmap-env.yaml +++ b/charts/authup/templates/admin-console/configmap-env.yaml @@ -1,10 +1,10 @@ -{{- if .Values.ui.enabled }} +{{- if .Values.adminConsole.enabled }} apiVersion: v1 kind: ConfigMap metadata: - name: {{ printf "%s-env" (include "authup.ui.fullname" .) }} + name: {{ printf "%s-env" (include "authup.adminConsole.fullname" .) }} namespace: {{ include "authup.namespace" . | quote }} - labels: {{- include "authup.labels" (dict "context" $ "component" "ui") | nindent 4 }} + labels: {{- include "authup.labels" (dict "context" $ "component" "admin-console") | nindent 4 }} annotations: {{- include "authup.annotations" (dict "context" $) | nindent 4 }} -data: {{- include "authup.ui.configEnv" . | nindent 2 }} +data: {{- include "authup.adminConsole.configEnv" . | nindent 2 }} {{- end }} diff --git a/charts/authup/templates/admin-console/deployment.yaml b/charts/authup/templates/admin-console/deployment.yaml new file mode 100644 index 0000000..b1cd407 --- /dev/null +++ b/charts/authup/templates/admin-console/deployment.yaml @@ -0,0 +1,159 @@ +{{- if .Values.adminConsole.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "authup.adminConsole.fullname" . }} + namespace: {{ include "authup.namespace" . | quote }} + labels: {{- include "authup.labels" (dict "context" $ "component" "admin-console") | nindent 4 }} + annotations: {{- include "authup.annotations" (dict "context" $) | nindent 4 }} +spec: + {{- if not .Values.adminConsole.autoscaling.hpa.enabled }} + replicas: {{ .Values.adminConsole.replicaCount }} + {{- end }} + revisionHistoryLimit: {{ .Values.adminConsole.revisionHistoryLimit }} + {{- if .Values.adminConsole.updateStrategy }} + strategy: {{- include "authup.tplvalues.render" (dict "value" .Values.adminConsole.updateStrategy "context" $) | nindent 4 }} + {{- end }} + selector: + matchLabels: {{- include "authup.matchLabels" (dict "context" $ "component" "admin-console") | nindent 6 }} + template: + metadata: + labels: {{- include "authup.labels" (dict "context" $ "component" "admin-console") | nindent 8 }} + {{- if .Values.adminConsole.podLabels }} + {{- include "authup.tplvalues.render" (dict "value" .Values.adminConsole.podLabels "context" $) | nindent 8 }} + {{- end }} + annotations: + {{- if not .Values.adminConsole.disableRestartOnChanges }} + checksum/env: {{ include "authup.adminConsole.configEnv" . | sha256sum }} + {{- end }} + {{- if .Values.adminConsole.podAnnotations }} + {{- include "authup.tplvalues.render" (dict "value" .Values.adminConsole.podAnnotations "context" $) | nindent 8 }} + {{- end }} + spec: + serviceAccountName: {{ include "authup.serviceAccountName" . }} + automountServiceAccountToken: {{ .Values.serviceAccount.automountServiceAccountToken }} + {{- include "authup.imagePullSecrets" . | nindent 6 }} + {{- if .Values.adminConsole.podSecurityContext.enabled }} + securityContext: {{- include "authup.securityContext" .Values.adminConsole.podSecurityContext | nindent 8 }} + {{- end }} + {{- if .Values.adminConsole.priorityClassName }} + priorityClassName: {{ .Values.adminConsole.priorityClassName }} + {{- end }} + {{- if .Values.adminConsole.schedulerName }} + schedulerName: {{ .Values.adminConsole.schedulerName }} + {{- end }} + {{- if .Values.adminConsole.hostAliases }} + hostAliases: {{- include "authup.tplvalues.render" (dict "value" .Values.adminConsole.hostAliases "context" $) | nindent 8 }} + {{- end }} + terminationGracePeriodSeconds: {{ .Values.adminConsole.terminationGracePeriodSeconds }} + {{- if .Values.adminConsole.affinity }} + affinity: {{- include "authup.tplvalues.render" (dict "value" .Values.adminConsole.affinity "context" $) | nindent 8 }} + {{- else if .Values.adminConsole.podAntiAffinityPreset }} + affinity: {{- include "authup.podAntiAffinity" (dict "context" $ "component" "admin-console" "preset" .Values.adminConsole.podAntiAffinityPreset) | nindent 8 }} + {{- end }} + {{- if .Values.adminConsole.nodeSelector }} + nodeSelector: {{- include "authup.tplvalues.render" (dict "value" .Values.adminConsole.nodeSelector "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.adminConsole.tolerations }} + tolerations: {{- include "authup.tplvalues.render" (dict "value" .Values.adminConsole.tolerations "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.adminConsole.topologySpreadConstraints }} + topologySpreadConstraints: {{- include "authup.topologySpreadConstraints" (dict "context" $ "component" "admin-console" "constraints" .Values.adminConsole.topologySpreadConstraints) | nindent 8 }} + {{- end }} + {{- if .Values.adminConsole.initContainers }} + initContainers: {{- include "authup.tplvalues.render" (dict "value" .Values.adminConsole.initContainers "context" $) | nindent 8 }} + {{- end }} + containers: + - name: ui + image: {{ include "authup.appImage" . }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + {{- if .Values.adminConsole.containerSecurityContext.enabled }} + securityContext: {{- include "authup.securityContext" .Values.adminConsole.containerSecurityContext | nindent 12 }} + {{- end }} + {{- if .Values.diagnosticMode.enabled }} + command: {{- toYaml .Values.diagnosticMode.command | nindent 12 }} + args: {{- toYaml .Values.diagnosticMode.args | nindent 12 }} + {{- else }} + {{- if .Values.adminConsole.command }} + command: {{- include "authup.tplvalues.render" (dict "value" .Values.adminConsole.command "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.adminConsole.args }} + args: {{- include "authup.tplvalues.render" (dict "value" .Values.adminConsole.args "context" $) | nindent 12 }} + {{- else }} + args: + - client/admin-console + - start + {{- end }} + {{- end }} + envFrom: + - configMapRef: + name: {{ printf "%s-env" (include "authup.adminConsole.fullname" .) }} + {{- if .Values.adminConsole.extraEnvVarsCM }} + - configMapRef: + name: {{ include "authup.tplvalues.render" (dict "value" .Values.adminConsole.extraEnvVarsCM "context" $) }} + {{- end }} + {{- if .Values.adminConsole.extraEnvVarsSecret }} + - secretRef: + name: {{ include "authup.tplvalues.render" (dict "value" .Values.adminConsole.extraEnvVarsSecret "context" $) }} + {{- end }} + env: + - name: npm_config_cache + value: /tmp/.npm-cache + {{- if .Values.adminConsole.extraEnvVars }} + {{- include "authup.tplvalues.render" (dict "value" .Values.adminConsole.extraEnvVars "context" $) | nindent 12 }} + {{- end }} + ports: + - name: http + containerPort: 3000 + protocol: TCP + {{- if not .Values.diagnosticMode.enabled }} + {{- if .Values.adminConsole.customStartupProbe }} + startupProbe: {{- include "authup.tplvalues.render" (dict "value" .Values.adminConsole.customStartupProbe "context" $) | nindent 12 }} + {{- else if .Values.adminConsole.startupProbe.enabled }} + startupProbe: + httpGet: + path: / + port: http + {{- omit .Values.adminConsole.startupProbe "enabled" | toYaml | nindent 12 }} + {{- end }} + {{- if .Values.adminConsole.customLivenessProbe }} + livenessProbe: {{- include "authup.tplvalues.render" (dict "value" .Values.adminConsole.customLivenessProbe "context" $) | nindent 12 }} + {{- else if .Values.adminConsole.livenessProbe.enabled }} + livenessProbe: + httpGet: + path: / + port: http + {{- omit .Values.adminConsole.livenessProbe "enabled" | toYaml | nindent 12 }} + {{- end }} + {{- if .Values.adminConsole.customReadinessProbe }} + readinessProbe: {{- include "authup.tplvalues.render" (dict "value" .Values.adminConsole.customReadinessProbe "context" $) | nindent 12 }} + {{- else if .Values.adminConsole.readinessProbe.enabled }} + readinessProbe: + httpGet: + path: / + port: http + {{- omit .Values.adminConsole.readinessProbe "enabled" | toYaml | nindent 12 }} + {{- end }} + {{- end }} + {{- if .Values.adminConsole.resources }} + resources: {{- toYaml .Values.adminConsole.resources | nindent 12 }} + {{- end }} + {{- if .Values.adminConsole.lifecycleHooks }} + lifecycle: {{- include "authup.tplvalues.render" (dict "value" .Values.adminConsole.lifecycleHooks "context" $) | nindent 12 }} + {{- end }} + volumeMounts: + - name: tmp + mountPath: /tmp + {{- if .Values.adminConsole.extraVolumeMounts }} + {{- include "authup.tplvalues.render" (dict "value" .Values.adminConsole.extraVolumeMounts "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.adminConsole.sidecars }} + {{- include "authup.tplvalues.render" (dict "value" .Values.adminConsole.sidecars "context" $) | nindent 8 }} + {{- end }} + volumes: + - name: tmp + emptyDir: {} + {{- if .Values.adminConsole.extraVolumes }} + {{- include "authup.tplvalues.render" (dict "value" .Values.adminConsole.extraVolumes "context" $) | nindent 8 }} + {{- end }} +{{- end }} diff --git a/charts/authup/templates/admin-console/hpa.yaml b/charts/authup/templates/admin-console/hpa.yaml new file mode 100644 index 0000000..f8d1322 --- /dev/null +++ b/charts/authup/templates/admin-console/hpa.yaml @@ -0,0 +1,33 @@ +{{- if and .Values.adminConsole.enabled .Values.adminConsole.autoscaling.hpa.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "authup.adminConsole.fullname" . }} + namespace: {{ include "authup.namespace" . | quote }} + labels: {{- include "authup.labels" (dict "context" $ "component" "admin-console") | nindent 4 }} + annotations: {{- include "authup.annotations" (dict "context" $) | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "authup.adminConsole.fullname" . }} + minReplicas: {{ .Values.adminConsole.autoscaling.hpa.minReplicas }} + maxReplicas: {{ .Values.adminConsole.autoscaling.hpa.maxReplicas }} + metrics: + {{- if .Values.adminConsole.autoscaling.hpa.targetCPU }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.adminConsole.autoscaling.hpa.targetCPU }} + {{- end }} + {{- if .Values.adminConsole.autoscaling.hpa.targetMemory }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.adminConsole.autoscaling.hpa.targetMemory }} + {{- end }} +{{- end }} diff --git a/charts/authup/templates/admin-console/httproute.yaml b/charts/authup/templates/admin-console/httproute.yaml new file mode 100644 index 0000000..f3a4916 --- /dev/null +++ b/charts/authup/templates/admin-console/httproute.yaml @@ -0,0 +1,11 @@ +{{- if and .Values.adminConsole.enabled .Values.adminConsole.route.enabled }} +{{- include "authup.httproute" (dict + "context" $ + "component" "admin-console" + "name" (include "authup.adminConsole.fullname" .) + "route" .Values.adminConsole.route + "serviceName" (include "authup.adminConsole.fullname" .) + "servicePort" .Values.adminConsole.service.ports.http + "publicUrl" (include "authup.adminConsole.publicUrl" .) +) }} +{{- end }} diff --git a/charts/authup/templates/admin-console/ingress.yaml b/charts/authup/templates/admin-console/ingress.yaml new file mode 100644 index 0000000..3133b86 --- /dev/null +++ b/charts/authup/templates/admin-console/ingress.yaml @@ -0,0 +1,9 @@ +{{- if and .Values.adminConsole.enabled .Values.adminConsole.ingress.enabled }} +{{- include "authup.ingress" (dict + "context" $ + "component" "admin-console" + "name" (include "authup.adminConsole.fullname" .) + "ingress" .Values.adminConsole.ingress + "serviceName" (include "authup.adminConsole.fullname" .) +) }} +{{- end }} diff --git a/charts/authup/templates/ui/networkpolicy.yaml b/charts/authup/templates/admin-console/networkpolicy.yaml similarity index 58% rename from charts/authup/templates/ui/networkpolicy.yaml rename to charts/authup/templates/admin-console/networkpolicy.yaml index 64dca78..a0ded6a 100644 --- a/charts/authup/templates/ui/networkpolicy.yaml +++ b/charts/authup/templates/admin-console/networkpolicy.yaml @@ -1,14 +1,14 @@ -{{- if and .Values.ui.enabled .Values.ui.networkPolicy.enabled }} +{{- if and .Values.adminConsole.enabled .Values.adminConsole.networkPolicy.enabled }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: - name: {{ include "authup.ui.fullname" . }} + name: {{ include "authup.adminConsole.fullname" . }} namespace: {{ include "authup.namespace" . | quote }} - labels: {{- include "authup.labels" (dict "context" $ "component" "ui") | nindent 4 }} + labels: {{- include "authup.labels" (dict "context" $ "component" "admin-console") | nindent 4 }} annotations: {{- include "authup.annotations" (dict "context" $) | nindent 4 }} spec: podSelector: - matchLabels: {{- include "authup.matchLabels" (dict "context" $ "component" "ui") | nindent 6 }} + matchLabels: {{- include "authup.matchLabels" (dict "context" $ "component" "admin-console") | nindent 6 }} policyTypes: - Ingress - Egress @@ -16,25 +16,25 @@ spec: - ports: - port: 3000 protocol: TCP - {{- if not .Values.ui.networkPolicy.allowExternal }} + {{- if not .Values.adminConsole.networkPolicy.allowExternal }} {{- /* An empty `from:` would mean allow-all — always emit at least the same-namespace baseline peer. */}} from: - podSelector: {} - {{- if .Values.ui.networkPolicy.ingressPodMatchLabels }} + {{- if .Values.adminConsole.networkPolicy.ingressPodMatchLabels }} - podSelector: - matchLabels: {{- include "authup.tplvalues.render" (dict "value" .Values.ui.networkPolicy.ingressPodMatchLabels "context" $) | nindent 14 }} + matchLabels: {{- include "authup.tplvalues.render" (dict "value" .Values.adminConsole.networkPolicy.ingressPodMatchLabels "context" $) | nindent 14 }} {{- end }} - {{- if .Values.ui.networkPolicy.ingressNSMatchLabels }} + {{- if .Values.adminConsole.networkPolicy.ingressNSMatchLabels }} - namespaceSelector: - matchLabels: {{- include "authup.tplvalues.render" (dict "value" .Values.ui.networkPolicy.ingressNSMatchLabels "context" $) | nindent 14 }} + matchLabels: {{- include "authup.tplvalues.render" (dict "value" .Values.adminConsole.networkPolicy.ingressNSMatchLabels "context" $) | nindent 14 }} {{- end }} {{- end }} - {{- if .Values.ui.networkPolicy.extraIngress }} - {{- include "authup.tplvalues.render" (dict "value" .Values.ui.networkPolicy.extraIngress "context" $) | nindent 4 }} + {{- if .Values.adminConsole.networkPolicy.extraIngress }} + {{- include "authup.tplvalues.render" (dict "value" .Values.adminConsole.networkPolicy.extraIngress "context" $) | nindent 4 }} {{- end }} egress: - {{- if .Values.ui.networkPolicy.allowExternalEgress }} + {{- if .Values.adminConsole.networkPolicy.allowExternalEgress }} - {} {{- else }} # DNS @@ -48,7 +48,7 @@ spec: - podSelector: matchLabels: {{- include "authup.matchLabels" (dict "context" $ "component" "server") | nindent 14 }} {{- end }} - {{- if .Values.ui.networkPolicy.extraEgress }} - {{- include "authup.tplvalues.render" (dict "value" .Values.ui.networkPolicy.extraEgress "context" $) | nindent 4 }} + {{- if .Values.adminConsole.networkPolicy.extraEgress }} + {{- include "authup.tplvalues.render" (dict "value" .Values.adminConsole.networkPolicy.extraEgress "context" $) | nindent 4 }} {{- end }} {{- end }} diff --git a/charts/authup/templates/admin-console/pdb.yaml b/charts/authup/templates/admin-console/pdb.yaml new file mode 100644 index 0000000..aa48ff6 --- /dev/null +++ b/charts/authup/templates/admin-console/pdb.yaml @@ -0,0 +1,20 @@ +{{- if and .Values.adminConsole.enabled .Values.adminConsole.pdb.create }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ include "authup.adminConsole.fullname" . }} + namespace: {{ include "authup.namespace" . | quote }} + labels: {{- include "authup.labels" (dict "context" $ "component" "admin-console") | nindent 4 }} + annotations: {{- include "authup.annotations" (dict "context" $) | nindent 4 }} +spec: + {{- if .Values.adminConsole.pdb.minAvailable }} + minAvailable: {{ .Values.adminConsole.pdb.minAvailable }} + {{- end }} + {{- if .Values.adminConsole.pdb.maxUnavailable }} + maxUnavailable: {{ .Values.adminConsole.pdb.maxUnavailable }} + {{- else if not .Values.adminConsole.pdb.minAvailable }} + maxUnavailable: 1 + {{- end }} + selector: + matchLabels: {{- include "authup.matchLabels" (dict "context" $ "component" "admin-console") | nindent 6 }} +{{- end }} diff --git a/charts/authup/templates/admin-console/service.yaml b/charts/authup/templates/admin-console/service.yaml new file mode 100644 index 0000000..88d176f --- /dev/null +++ b/charts/authup/templates/admin-console/service.yaml @@ -0,0 +1,47 @@ +{{- if .Values.adminConsole.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "authup.adminConsole.fullname" . }} + namespace: {{ include "authup.namespace" . | quote }} + labels: {{- include "authup.labels" (dict "context" $ "component" "admin-console") | nindent 4 }} + annotations: + {{- include "authup.annotations" (dict "context" $) | nindent 4 }} + {{- if .Values.adminConsole.service.annotations }} + {{- include "authup.tplvalues.render" (dict "value" .Values.adminConsole.service.annotations "context" $) | nindent 4 }} + {{- end }} +spec: + type: {{ .Values.adminConsole.service.type }} + {{- if and .Values.adminConsole.service.clusterIP (eq .Values.adminConsole.service.type "ClusterIP") }} + clusterIP: {{ .Values.adminConsole.service.clusterIP }} + {{- end }} + {{- if eq .Values.adminConsole.service.type "LoadBalancer" }} + {{- if .Values.adminConsole.service.loadBalancerIP }} + loadBalancerIP: {{ .Values.adminConsole.service.loadBalancerIP }} + {{- end }} + {{- if .Values.adminConsole.service.loadBalancerSourceRanges }} + loadBalancerSourceRanges: {{- toYaml .Values.adminConsole.service.loadBalancerSourceRanges | nindent 4 }} + {{- end }} + {{- end }} + {{- if or (eq .Values.adminConsole.service.type "LoadBalancer") (eq .Values.adminConsole.service.type "NodePort") }} + externalTrafficPolicy: {{ .Values.adminConsole.service.externalTrafficPolicy | quote }} + {{- end }} + {{- if .Values.adminConsole.service.sessionAffinity }} + sessionAffinity: {{ .Values.adminConsole.service.sessionAffinity }} + {{- end }} + {{- if .Values.adminConsole.service.sessionAffinityConfig }} + sessionAffinityConfig: {{- toYaml .Values.adminConsole.service.sessionAffinityConfig | nindent 4 }} + {{- end }} + ports: + - name: http + port: {{ .Values.adminConsole.service.ports.http }} + targetPort: http + protocol: TCP + {{- if and (or (eq .Values.adminConsole.service.type "NodePort") (eq .Values.adminConsole.service.type "LoadBalancer")) .Values.adminConsole.service.nodePorts.http }} + nodePort: {{ .Values.adminConsole.service.nodePorts.http }} + {{- end }} + {{- if .Values.adminConsole.service.extraPorts }} + {{- include "authup.tplvalues.render" (dict "value" .Values.adminConsole.service.extraPorts "context" $) | nindent 4 }} + {{- end }} + selector: {{- include "authup.matchLabels" (dict "context" $ "component" "admin-console") | nindent 4 }} +{{- end }} diff --git a/charts/authup/templates/server/networkpolicy.yaml b/charts/authup/templates/server/networkpolicy.yaml index 63452b9..5c05752 100644 --- a/charts/authup/templates/server/networkpolicy.yaml +++ b/charts/authup/templates/server/networkpolicy.yaml @@ -19,7 +19,7 @@ spec: {{- if not .Values.server.networkPolicy.allowExternal }} from: - podSelector: - matchLabels: {{- include "authup.matchLabels" (dict "context" $ "component" "ui") | nindent 14 }} + matchLabels: {{- include "authup.matchLabels" (dict "context" $ "component" "admin-console") | nindent 14 }} {{- if .Values.server.networkPolicy.ingressPodMatchLabels }} - podSelector: matchLabels: {{- include "authup.tplvalues.render" (dict "value" .Values.server.networkPolicy.ingressPodMatchLabels "context" $) | nindent 14 }} diff --git a/charts/authup/templates/ui/deployment.yaml b/charts/authup/templates/ui/deployment.yaml deleted file mode 100644 index 0cdc297..0000000 --- a/charts/authup/templates/ui/deployment.yaml +++ /dev/null @@ -1,159 +0,0 @@ -{{- if .Values.ui.enabled }} -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ include "authup.ui.fullname" . }} - namespace: {{ include "authup.namespace" . | quote }} - labels: {{- include "authup.labels" (dict "context" $ "component" "ui") | nindent 4 }} - annotations: {{- include "authup.annotations" (dict "context" $) | nindent 4 }} -spec: - {{- if not .Values.ui.autoscaling.hpa.enabled }} - replicas: {{ .Values.ui.replicaCount }} - {{- end }} - revisionHistoryLimit: {{ .Values.ui.revisionHistoryLimit }} - {{- if .Values.ui.updateStrategy }} - strategy: {{- include "authup.tplvalues.render" (dict "value" .Values.ui.updateStrategy "context" $) | nindent 4 }} - {{- end }} - selector: - matchLabels: {{- include "authup.matchLabels" (dict "context" $ "component" "ui") | nindent 6 }} - template: - metadata: - labels: {{- include "authup.labels" (dict "context" $ "component" "ui") | nindent 8 }} - {{- if .Values.ui.podLabels }} - {{- include "authup.tplvalues.render" (dict "value" .Values.ui.podLabels "context" $) | nindent 8 }} - {{- end }} - annotations: - {{- if not .Values.ui.disableRestartOnChanges }} - checksum/env: {{ include "authup.ui.configEnv" . | sha256sum }} - {{- end }} - {{- if .Values.ui.podAnnotations }} - {{- include "authup.tplvalues.render" (dict "value" .Values.ui.podAnnotations "context" $) | nindent 8 }} - {{- end }} - spec: - serviceAccountName: {{ include "authup.serviceAccountName" . }} - automountServiceAccountToken: {{ .Values.serviceAccount.automountServiceAccountToken }} - {{- include "authup.imagePullSecrets" . | nindent 6 }} - {{- if .Values.ui.podSecurityContext.enabled }} - securityContext: {{- include "authup.securityContext" .Values.ui.podSecurityContext | nindent 8 }} - {{- end }} - {{- if .Values.ui.priorityClassName }} - priorityClassName: {{ .Values.ui.priorityClassName }} - {{- end }} - {{- if .Values.ui.schedulerName }} - schedulerName: {{ .Values.ui.schedulerName }} - {{- end }} - {{- if .Values.ui.hostAliases }} - hostAliases: {{- include "authup.tplvalues.render" (dict "value" .Values.ui.hostAliases "context" $) | nindent 8 }} - {{- end }} - terminationGracePeriodSeconds: {{ .Values.ui.terminationGracePeriodSeconds }} - {{- if .Values.ui.affinity }} - affinity: {{- include "authup.tplvalues.render" (dict "value" .Values.ui.affinity "context" $) | nindent 8 }} - {{- else if .Values.ui.podAntiAffinityPreset }} - affinity: {{- include "authup.podAntiAffinity" (dict "context" $ "component" "ui" "preset" .Values.ui.podAntiAffinityPreset) | nindent 8 }} - {{- end }} - {{- if .Values.ui.nodeSelector }} - nodeSelector: {{- include "authup.tplvalues.render" (dict "value" .Values.ui.nodeSelector "context" $) | nindent 8 }} - {{- end }} - {{- if .Values.ui.tolerations }} - tolerations: {{- include "authup.tplvalues.render" (dict "value" .Values.ui.tolerations "context" $) | nindent 8 }} - {{- end }} - {{- if .Values.ui.topologySpreadConstraints }} - topologySpreadConstraints: {{- include "authup.topologySpreadConstraints" (dict "context" $ "component" "ui" "constraints" .Values.ui.topologySpreadConstraints) | nindent 8 }} - {{- end }} - {{- if .Values.ui.initContainers }} - initContainers: {{- include "authup.tplvalues.render" (dict "value" .Values.ui.initContainers "context" $) | nindent 8 }} - {{- end }} - containers: - - name: ui - image: {{ include "authup.appImage" . }} - imagePullPolicy: {{ .Values.image.pullPolicy }} - {{- if .Values.ui.containerSecurityContext.enabled }} - securityContext: {{- include "authup.securityContext" .Values.ui.containerSecurityContext | nindent 12 }} - {{- end }} - {{- if .Values.diagnosticMode.enabled }} - command: {{- toYaml .Values.diagnosticMode.command | nindent 12 }} - args: {{- toYaml .Values.diagnosticMode.args | nindent 12 }} - {{- else }} - {{- if .Values.ui.command }} - command: {{- include "authup.tplvalues.render" (dict "value" .Values.ui.command "context" $) | nindent 12 }} - {{- end }} - {{- if .Values.ui.args }} - args: {{- include "authup.tplvalues.render" (dict "value" .Values.ui.args "context" $) | nindent 12 }} - {{- else }} - args: - - client/web - - start - {{- end }} - {{- end }} - envFrom: - - configMapRef: - name: {{ printf "%s-env" (include "authup.ui.fullname" .) }} - {{- if .Values.ui.extraEnvVarsCM }} - - configMapRef: - name: {{ include "authup.tplvalues.render" (dict "value" .Values.ui.extraEnvVarsCM "context" $) }} - {{- end }} - {{- if .Values.ui.extraEnvVarsSecret }} - - secretRef: - name: {{ include "authup.tplvalues.render" (dict "value" .Values.ui.extraEnvVarsSecret "context" $) }} - {{- end }} - env: - - name: npm_config_cache - value: /tmp/.npm-cache - {{- if .Values.ui.extraEnvVars }} - {{- include "authup.tplvalues.render" (dict "value" .Values.ui.extraEnvVars "context" $) | nindent 12 }} - {{- end }} - ports: - - name: http - containerPort: 3000 - protocol: TCP - {{- if not .Values.diagnosticMode.enabled }} - {{- if .Values.ui.customStartupProbe }} - startupProbe: {{- include "authup.tplvalues.render" (dict "value" .Values.ui.customStartupProbe "context" $) | nindent 12 }} - {{- else if .Values.ui.startupProbe.enabled }} - startupProbe: - httpGet: - path: / - port: http - {{- omit .Values.ui.startupProbe "enabled" | toYaml | nindent 12 }} - {{- end }} - {{- if .Values.ui.customLivenessProbe }} - livenessProbe: {{- include "authup.tplvalues.render" (dict "value" .Values.ui.customLivenessProbe "context" $) | nindent 12 }} - {{- else if .Values.ui.livenessProbe.enabled }} - livenessProbe: - httpGet: - path: / - port: http - {{- omit .Values.ui.livenessProbe "enabled" | toYaml | nindent 12 }} - {{- end }} - {{- if .Values.ui.customReadinessProbe }} - readinessProbe: {{- include "authup.tplvalues.render" (dict "value" .Values.ui.customReadinessProbe "context" $) | nindent 12 }} - {{- else if .Values.ui.readinessProbe.enabled }} - readinessProbe: - httpGet: - path: / - port: http - {{- omit .Values.ui.readinessProbe "enabled" | toYaml | nindent 12 }} - {{- end }} - {{- end }} - {{- if .Values.ui.resources }} - resources: {{- toYaml .Values.ui.resources | nindent 12 }} - {{- end }} - {{- if .Values.ui.lifecycleHooks }} - lifecycle: {{- include "authup.tplvalues.render" (dict "value" .Values.ui.lifecycleHooks "context" $) | nindent 12 }} - {{- end }} - volumeMounts: - - name: tmp - mountPath: /tmp - {{- if .Values.ui.extraVolumeMounts }} - {{- include "authup.tplvalues.render" (dict "value" .Values.ui.extraVolumeMounts "context" $) | nindent 12 }} - {{- end }} - {{- if .Values.ui.sidecars }} - {{- include "authup.tplvalues.render" (dict "value" .Values.ui.sidecars "context" $) | nindent 8 }} - {{- end }} - volumes: - - name: tmp - emptyDir: {} - {{- if .Values.ui.extraVolumes }} - {{- include "authup.tplvalues.render" (dict "value" .Values.ui.extraVolumes "context" $) | nindent 8 }} - {{- end }} -{{- end }} diff --git a/charts/authup/templates/ui/hpa.yaml b/charts/authup/templates/ui/hpa.yaml deleted file mode 100644 index c6d400f..0000000 --- a/charts/authup/templates/ui/hpa.yaml +++ /dev/null @@ -1,33 +0,0 @@ -{{- if and .Values.ui.enabled .Values.ui.autoscaling.hpa.enabled }} -apiVersion: autoscaling/v2 -kind: HorizontalPodAutoscaler -metadata: - name: {{ include "authup.ui.fullname" . }} - namespace: {{ include "authup.namespace" . | quote }} - labels: {{- include "authup.labels" (dict "context" $ "component" "ui") | nindent 4 }} - annotations: {{- include "authup.annotations" (dict "context" $) | nindent 4 }} -spec: - scaleTargetRef: - apiVersion: apps/v1 - kind: Deployment - name: {{ include "authup.ui.fullname" . }} - minReplicas: {{ .Values.ui.autoscaling.hpa.minReplicas }} - maxReplicas: {{ .Values.ui.autoscaling.hpa.maxReplicas }} - metrics: - {{- if .Values.ui.autoscaling.hpa.targetCPU }} - - type: Resource - resource: - name: cpu - target: - type: Utilization - averageUtilization: {{ .Values.ui.autoscaling.hpa.targetCPU }} - {{- end }} - {{- if .Values.ui.autoscaling.hpa.targetMemory }} - - type: Resource - resource: - name: memory - target: - type: Utilization - averageUtilization: {{ .Values.ui.autoscaling.hpa.targetMemory }} - {{- end }} -{{- end }} diff --git a/charts/authup/templates/ui/httproute.yaml b/charts/authup/templates/ui/httproute.yaml deleted file mode 100644 index 3a2aecd..0000000 --- a/charts/authup/templates/ui/httproute.yaml +++ /dev/null @@ -1,11 +0,0 @@ -{{- if and .Values.ui.enabled .Values.ui.route.enabled }} -{{- include "authup.httproute" (dict - "context" $ - "component" "ui" - "name" (include "authup.ui.fullname" .) - "route" .Values.ui.route - "serviceName" (include "authup.ui.fullname" .) - "servicePort" .Values.ui.service.ports.http - "publicUrl" (include "authup.ui.publicUrl" .) -) }} -{{- end }} diff --git a/charts/authup/templates/ui/ingress.yaml b/charts/authup/templates/ui/ingress.yaml deleted file mode 100644 index c9429e2..0000000 --- a/charts/authup/templates/ui/ingress.yaml +++ /dev/null @@ -1,9 +0,0 @@ -{{- if and .Values.ui.enabled .Values.ui.ingress.enabled }} -{{- include "authup.ingress" (dict - "context" $ - "component" "ui" - "name" (include "authup.ui.fullname" .) - "ingress" .Values.ui.ingress - "serviceName" (include "authup.ui.fullname" .) -) }} -{{- end }} diff --git a/charts/authup/templates/ui/pdb.yaml b/charts/authup/templates/ui/pdb.yaml deleted file mode 100644 index 66165cc..0000000 --- a/charts/authup/templates/ui/pdb.yaml +++ /dev/null @@ -1,20 +0,0 @@ -{{- if and .Values.ui.enabled .Values.ui.pdb.create }} -apiVersion: policy/v1 -kind: PodDisruptionBudget -metadata: - name: {{ include "authup.ui.fullname" . }} - namespace: {{ include "authup.namespace" . | quote }} - labels: {{- include "authup.labels" (dict "context" $ "component" "ui") | nindent 4 }} - annotations: {{- include "authup.annotations" (dict "context" $) | nindent 4 }} -spec: - {{- if .Values.ui.pdb.minAvailable }} - minAvailable: {{ .Values.ui.pdb.minAvailable }} - {{- end }} - {{- if .Values.ui.pdb.maxUnavailable }} - maxUnavailable: {{ .Values.ui.pdb.maxUnavailable }} - {{- else if not .Values.ui.pdb.minAvailable }} - maxUnavailable: 1 - {{- end }} - selector: - matchLabels: {{- include "authup.matchLabels" (dict "context" $ "component" "ui") | nindent 6 }} -{{- end }} diff --git a/charts/authup/templates/ui/service.yaml b/charts/authup/templates/ui/service.yaml deleted file mode 100644 index ee03fd1..0000000 --- a/charts/authup/templates/ui/service.yaml +++ /dev/null @@ -1,47 +0,0 @@ -{{- if .Values.ui.enabled }} -apiVersion: v1 -kind: Service -metadata: - name: {{ include "authup.ui.fullname" . }} - namespace: {{ include "authup.namespace" . | quote }} - labels: {{- include "authup.labels" (dict "context" $ "component" "ui") | nindent 4 }} - annotations: - {{- include "authup.annotations" (dict "context" $) | nindent 4 }} - {{- if .Values.ui.service.annotations }} - {{- include "authup.tplvalues.render" (dict "value" .Values.ui.service.annotations "context" $) | nindent 4 }} - {{- end }} -spec: - type: {{ .Values.ui.service.type }} - {{- if and .Values.ui.service.clusterIP (eq .Values.ui.service.type "ClusterIP") }} - clusterIP: {{ .Values.ui.service.clusterIP }} - {{- end }} - {{- if eq .Values.ui.service.type "LoadBalancer" }} - {{- if .Values.ui.service.loadBalancerIP }} - loadBalancerIP: {{ .Values.ui.service.loadBalancerIP }} - {{- end }} - {{- if .Values.ui.service.loadBalancerSourceRanges }} - loadBalancerSourceRanges: {{- toYaml .Values.ui.service.loadBalancerSourceRanges | nindent 4 }} - {{- end }} - {{- end }} - {{- if or (eq .Values.ui.service.type "LoadBalancer") (eq .Values.ui.service.type "NodePort") }} - externalTrafficPolicy: {{ .Values.ui.service.externalTrafficPolicy | quote }} - {{- end }} - {{- if .Values.ui.service.sessionAffinity }} - sessionAffinity: {{ .Values.ui.service.sessionAffinity }} - {{- end }} - {{- if .Values.ui.service.sessionAffinityConfig }} - sessionAffinityConfig: {{- toYaml .Values.ui.service.sessionAffinityConfig | nindent 4 }} - {{- end }} - ports: - - name: http - port: {{ .Values.ui.service.ports.http }} - targetPort: http - protocol: TCP - {{- if and (or (eq .Values.ui.service.type "NodePort") (eq .Values.ui.service.type "LoadBalancer")) .Values.ui.service.nodePorts.http }} - nodePort: {{ .Values.ui.service.nodePorts.http }} - {{- end }} - {{- if .Values.ui.service.extraPorts }} - {{- include "authup.tplvalues.render" (dict "value" .Values.ui.service.extraPorts "context" $) | nindent 4 }} - {{- end }} - selector: {{- include "authup.matchLabels" (dict "context" $ "component" "ui") | nindent 4 }} -{{- end }} diff --git a/charts/authup/templates/validations.yaml b/charts/authup/templates/validations.yaml index a12b5f1..dbbbed6 100644 --- a/charts/authup/templates/validations.yaml +++ b/charts/authup/templates/validations.yaml @@ -34,8 +34,8 @@ store, token blocklist and MFA challenges fall back to a per-process memory cach {{- fail "authup: auth.existingSecret and inline auth values are mutually exclusive — when an existing secret is referenced, the inline adminPassword / systemClientSecret / secretsEncryptionKey values are unused; remove them to avoid a false sense of configuration." }} {{- end }} -{{- if and .Values.ui.enabled (not .Values.server.enabled) (not .Values.ui.apiUrl) }} -{{- fail "authup: ui.enabled without server.enabled requires ui.apiUrl (the browser-reachable URL of an external authup server-core)." }} +{{- if and .Values.adminConsole.enabled (not .Values.server.enabled) (not .Values.adminConsole.apiUrl) }} +{{- fail "authup: adminConsole.enabled without server.enabled requires adminConsole.apiUrl (the browser-reachable URL of an external authup server-core)." }} {{- end }} {{/* An external database password must be deliberate — never silently generated for a database the chart does not own. */}} @@ -46,12 +46,12 @@ store, token blocklist and MFA challenges fall back to a per-process memory cach {{- if and .Values.server.ingress.enabled (not .Values.server.ingress.hostname) (not .Values.server.ingress.extraHosts) (not .Values.server.ingress.extraRules) }} {{- fail "authup: server.ingress.enabled requires server.ingress.hostname (or extraHosts / extraRules)." }} {{- end }} -{{- if and .Values.ui.enabled .Values.ui.ingress.enabled (not .Values.ui.ingress.hostname) (not .Values.ui.ingress.extraHosts) (not .Values.ui.ingress.extraRules) }} -{{- fail "authup: ui.ingress.enabled requires ui.ingress.hostname (or extraHosts / extraRules)." }} +{{- if and .Values.adminConsole.enabled .Values.adminConsole.ingress.enabled (not .Values.adminConsole.ingress.hostname) (not .Values.adminConsole.ingress.extraHosts) (not .Values.adminConsole.ingress.extraRules) }} +{{- fail "authup: adminConsole.ingress.enabled requires adminConsole.ingress.hostname (or extraHosts / extraRules)." }} {{- end }} {{/* URL values must carry a scheme — a scheme-less value would derive a broken "://" origin into TRUSTED_ORIGINS and crash-loop server-core. */}} -{{- range $key, $value := dict "server.publicUrl" .Values.server.publicUrl "ui.publicUrl" .Values.ui.publicUrl "ui.apiUrl" .Values.ui.apiUrl "ui.internalApiUrl" .Values.ui.internalApiUrl }} +{{- range $key, $value := dict "server.publicUrl" .Values.server.publicUrl "adminConsole.publicUrl" .Values.adminConsole.publicUrl "adminConsole.apiUrl" .Values.adminConsole.apiUrl "adminConsole.internalApiUrl" .Values.adminConsole.internalApiUrl }} {{- if and $value (not (contains "{{" $value)) (not (regexMatch "^https?://" $value)) }} {{- fail (printf "authup: %s must be a full URL including the http(s):// scheme (got %q)." $key $value) }} {{- end }} diff --git a/charts/authup/values.schema.json b/charts/authup/values.schema.json index 9ea4d20..1d15b12 100644 --- a/charts/authup/values.schema.json +++ b/charts/authup/values.schema.json @@ -2,2001 +2,1722 @@ "$schema": "http://json-schema.org/draft-07/schema#", "additionalProperties": false, "properties": { - "auth": { + "adminConsole": { "additionalProperties": false, - "description": "#\n# Credentials seeded into authup at boot. Empty passwords are generated once and\n# kept stable across upgrades via lookup.\n# GITOPS WARNING: lookup is inert under `helm template` and ArgoCD-style\n# renders — every sync would then apply a NEW random value, roll the pods via\n# the checksum annotation, and desync the Secret from the live credential\n# (authup only applies the admin password at first provisioning unless the\n# reset flag is set). GitOps users MUST set explicit values or auth.existingSecret.", + "description": "#", "properties": { - "adminPassword": { + "affinity": { + "additionalProperties": true, + "description": "Affinity (overrides the anti-affinity preset when set)", + "required": [], + "title": "affinity" + }, + "apiUrl": { "default": "", - "description": "Initial admin user password (\"\" = generate once, keep across upgrades).\nChanging it after the first install only takes effect with\nadminPasswordReset=true for one upgrade cycle", + "description": "Browser-facing server-core URL (NUXT_PUBLIC_API_URL). \"\" = the server public URL.\nMust be reachable from the user's browser, never a cluster-internal DNS name", "required": [], - "title": "adminPassword", + "title": "apiUrl", "type": "string" }, - "adminPasswordReset": { - "default": false, - "description": "Re-assert the admin password on every boot (USER_ADMIN_PASSWORD_RESET)", + "args": { + "description": "Override the container args", + "items": { + "required": [] + }, "required": [], - "title": "adminPasswordReset", - "type": "boolean" + "title": "args", + "type": "array" }, - "existingSecret": { - "default": "", - "description": "Existing secret holding the keys below instead of the chart-managed secret (tpl-rendered)", + "autoscaling": { + "additionalProperties": false, + "properties": { + "hpa": { + "additionalProperties": false, + "properties": { + "enabled": { + "default": false, + "description": "Enable HPA for the UI", + "required": [], + "title": "enabled", + "type": "boolean" + }, + "maxReplicas": { + "default": 5, + "description": "Maximum replicas", + "required": [], + "title": "maxReplicas", + "type": "integer" + }, + "minReplicas": { + "default": 2, + "description": "Minimum replicas", + "required": [], + "title": "minReplicas", + "type": "integer" + }, + "targetCPU": { + "default": 75, + "description": "Target CPU utilization percentage", + "required": [], + "title": "targetCPU", + "type": "integer" + }, + "targetMemory": { + "default": "", + "description": "Target memory utilization percentage", + "required": [], + "title": "targetMemory", + "type": "string" + } + }, + "required": [ + "enabled", + "minReplicas", + "maxReplicas", + "targetCPU", + "targetMemory" + ], + "title": "hpa", + "type": "object" + } + }, + "required": [ + "hpa" + ], + "title": "autoscaling", + "type": "object" + }, + "command": { + "description": "Override the container command", + "items": { + "required": [] + }, "required": [], - "title": "existingSecret", - "type": "string" + "title": "command", + "type": "array" }, - "secretKeys": { - "additionalProperties": false, + "config": { + "additionalProperties": true, + "description": "Extra environment variables rendered literally into the env ConfigMap", + "required": [], + "title": "config" + }, + "containerSecurityContext": { + "additionalProperties": true, + "description": "Container security context (same root-image caveat as the server)", "properties": { - "adminPasswordKey": { - "default": "admin-password", - "description": "Key inside the (existing) secret holding the admin password", + "allowPrivilegeEscalation": { + "default": false, "required": [], - "title": "adminPasswordKey", - "type": "string" + "title": "allowPrivilegeEscalation", + "type": "boolean" }, - "secretsEncryptionKeyKey": { - "default": "secrets-encryption-key", - "description": "Key inside the (existing) secret holding the secrets encryption key", + "capabilities": { + "additionalProperties": false, + "properties": { + "drop": { + "items": { + "anyOf": [ + { + "required": [], + "type": "string" + } + ], + "required": [] + }, + "required": [], + "title": "drop", + "type": "array" + } + }, + "required": [ + "drop" + ], + "title": "capabilities", + "type": "object" + }, + "enabled": { + "default": true, "required": [], - "title": "secretsEncryptionKeyKey", - "type": "string" + "title": "enabled", + "type": "boolean" }, - "systemClientSecretKey": { - "default": "system-client-secret", - "description": "Key inside the (existing) secret holding the system client secret", + "readOnlyRootFilesystem": { + "default": false, "required": [], - "title": "systemClientSecretKey", - "type": "string" + "title": "readOnlyRootFilesystem", + "type": "boolean" + }, + "runAsNonRoot": { + "default": false, + "required": [], + "title": "runAsNonRoot", + "type": "boolean" + }, + "runAsUser": { + "default": 0, + "required": [], + "title": "runAsUser", + "type": "integer" + }, + "seccompProfile": { + "additionalProperties": false, + "properties": { + "type": { + "default": "RuntimeDefault", + "required": [], + "title": "type", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "seccompProfile", + "type": "object" } }, "required": [ - "adminPasswordKey", - "systemClientSecretKey", - "secretsEncryptionKeyKey" + "enabled", + "runAsUser", + "runAsNonRoot", + "allowPrivilegeEscalation", + "capabilities", + "seccompProfile", + "readOnlyRootFilesystem" ], - "title": "secretKeys", - "type": "object" + "title": "containerSecurityContext" }, - "secretsEncryptionKey": { - "default": "", - "description": "Optional AES-256 key-encryption-key wrapping the realm key store\n(SECRETS_ENCRYPTION_KEY, base64-encoded 32 bytes). Deliberately never\ngenerated by the chart: this key is effectively write-once — losing or\nrotating it bricks wrapped MFA seeds and signing keys. Set it explicitly\nhere, or reference it via existingSecret plus\nsecretsEncryptionKeyEnabled=true, and back it up.", + "customLivenessProbe": { + "additionalProperties": true, + "description": "Custom liveness probe", "required": [], - "title": "secretsEncryptionKey", - "type": "string" + "title": "customLivenessProbe" }, - "secretsEncryptionKeyEnabled": { + "customReadinessProbe": { + "additionalProperties": true, + "description": "Custom readiness probe", + "required": [], + "title": "customReadinessProbe" + }, + "customStartupProbe": { + "additionalProperties": true, + "description": "Custom startup probe", + "required": [], + "title": "customStartupProbe" + }, + "disableRestartOnChanges": { "default": false, - "description": "Declare that auth.existingSecret carries the secrets encryption key\n(under secretKeys.secretsEncryptionKeyKey). Required for the key to be\nwired from an existing secret — it is never inferred, so a missing key can\nnot silently fail open into plaintext-at-rest", + "description": "Disable the checksum annotations that roll pods on config changes", "required": [], - "title": "secretsEncryptionKeyEnabled", + "title": "disableRestartOnChanges", "type": "boolean" }, - "systemClientEnabled": { - "default": false, - "description": "Provision the built-in system client (CLIENT_SYSTEM_ENABLED); required for machine-to-machine consumers", + "enabled": { + "default": true, + "description": "Deploy the client-admin-console admin UI (false = headless IdP)", "required": [], - "title": "systemClientEnabled", + "title": "enabled", "type": "boolean" }, - "systemClientSecret": { + "extraEnvVars": { + "description": "Extra environment variables for the UI container", + "items": { + "required": [] + }, + "required": [], + "title": "extraEnvVars", + "type": "array" + }, + "extraEnvVarsCM": { "default": "", - "description": "System client secret (\"\" = generate once when systemClientEnabled)", + "description": "Extra ConfigMap with environment variables (tpl-rendered name)", "required": [], - "title": "systemClientSecret", + "title": "extraEnvVarsCM", "type": "string" }, - "systemClientSecretReset": { - "default": false, - "description": "Re-assert the system client secret on every boot (CLIENT_SYSTEM_SECRET_RESET)", + "extraEnvVarsSecret": { + "default": "", + "description": "Extra Secret with environment variables (tpl-rendered name)", "required": [], - "title": "systemClientSecretReset", - "type": "boolean" - } - }, - "required": [ - "adminPassword", - "adminPasswordReset", - "systemClientEnabled", - "systemClientSecret", - "systemClientSecretReset", - "secretsEncryptionKey", - "secretsEncryptionKeyEnabled", - "existingSecret", - "secretKeys" - ], - "title": "auth", - "type": "object" - }, - "commonAnnotations": { - "additionalProperties": true, - "description": "Annotations added to every object", - "required": [], - "title": "commonAnnotations" - }, - "commonLabels": { - "additionalProperties": true, - "description": "Labels added to every object", - "required": [], - "title": "commonLabels" - }, - "database": { - "additionalProperties": false, - "description": "#\n# Exactly one of: built-in postgresql, built-in mysql, or externalDatabase.", - "properties": { - "type": { - "default": "postgres", - "description": "Database engine when using externalDatabase: postgres or mysql", - "required": [], - "title": "type", + "title": "extraEnvVarsSecret", "type": "string" - } - }, - "required": [ - "type" - ], - "title": "database", - "type": "object" - }, - "diagnosticMode": { - "additionalProperties": false, - "properties": { - "args": { - "description": "Args overriding the containers' args in diagnostic mode", + }, + "extraVolumeMounts": { + "description": "Extra volume mounts (tpl-rendered)", "items": { - "anyOf": [ - { - "required": [], - "type": "string" - } - ], "required": [] }, "required": [], - "title": "args", + "title": "extraVolumeMounts", "type": "array" }, - "command": { - "description": "Command overriding the containers' command in diagnostic mode", + "extraVolumes": { + "description": "Extra volumes (tpl-rendered)", "items": { - "anyOf": [ - { - "required": [], - "type": "string" - } - ], "required": [] }, "required": [], - "title": "command", + "title": "extraVolumes", "type": "array" }, - "enabled": { - "default": false, - "description": "Start every container with a sleep command and disable probes (debugging)", - "required": [], - "title": "enabled", - "type": "boolean" - } - }, - "required": [ - "enabled", - "command", - "args" - ], - "title": "diagnosticMode", - "type": "object" - }, - "externalDatabase": { - "additionalProperties": false, - "properties": { - "database": { - "default": "authup", - "description": "External database name (must exist, or the user needs CREATE privilege)", + "hostAliases": { + "description": "Pod host aliases", + "items": { + "required": [] + }, "required": [], - "title": "database", - "type": "string" + "title": "hostAliases", + "type": "array" }, - "existingSecret": { - "default": "", - "description": "Existing secret with the database password (tpl-rendered)", - "required": [], - "title": "existingSecret", - "type": "string" + "ingress": { + "additionalProperties": false, + "properties": { + "annotations": { + "additionalProperties": true, + "description": "Ingress annotations (tpl-rendered)", + "required": [], + "title": "annotations" + }, + "certManager": { + "default": false, + "description": "Request a cert-manager certificate (adds kubernetes.io/tls-acme)", + "required": [], + "title": "certManager", + "type": "boolean" + }, + "enabled": { + "default": false, + "description": "Enable ingress for the UI", + "required": [], + "title": "enabled", + "type": "boolean" + }, + "extraHosts": { + "description": "Extra hosts", + "items": { + "required": [] + }, + "required": [], + "title": "extraHosts", + "type": "array" + }, + "extraPaths": { + "description": "Extra paths for the primary host", + "items": { + "required": [] + }, + "required": [], + "title": "extraPaths", + "type": "array" + }, + "extraRules": { + "description": "Full custom rules (tpl-rendered; overrides the generated rule)", + "items": { + "required": [] + }, + "required": [], + "title": "extraRules", + "type": "array" + }, + "extraTls": { + "description": "Extra TLS entries", + "items": { + "required": [] + }, + "required": [], + "title": "extraTls", + "type": "array" + }, + "hostname": { + "default": "", + "description": "Ingress hostname (tpl-rendered); also drives the derived UI public URL", + "required": [], + "title": "hostname", + "type": "string" + }, + "ingressClassName": { + "default": "", + "description": "Ingress class name", + "required": [], + "title": "ingressClassName", + "type": "string" + }, + "path": { + "default": "/", + "description": "Ingress path", + "required": [], + "title": "path", + "type": "string" + }, + "pathType": { + "default": "Prefix", + "description": "Ingress path type", + "required": [], + "title": "pathType", + "type": "string" + }, + "tls": { + "default": false, + "description": "Enable TLS for the hostname", + "required": [], + "title": "tls", + "type": "boolean" + } + }, + "required": [ + "enabled", + "hostname", + "path", + "pathType", + "ingressClassName", + "tls", + "certManager", + "extraHosts", + "extraPaths", + "extraTls", + "extraRules" + ], + "title": "ingress", + "type": "object" }, - "existingSecretPasswordKey": { - "default": "password", - "description": "Key inside externalDatabase.existingSecret holding the password", + "initContainers": { + "description": "Init containers (tpl-rendered)", + "items": { + "required": [] + }, "required": [], - "title": "existingSecretPasswordKey", - "type": "string" + "title": "initContainers", + "type": "array" }, - "host": { + "internalApiUrl": { "default": "", - "description": "External database host (tpl-rendered). Setting this selects the external database", + "description": "Server-side (SSR) API URL override (NUXT_API_URL), e.g. the in-cluster\nservice URL to keep SSR traffic off the ingress", "required": [], - "title": "host", + "title": "internalApiUrl", "type": "string" }, - "password": { - "default": "", - "description": "External database password (stored in a chart-managed secret, never inline env)", + "lifecycleHooks": { + "additionalProperties": true, + "description": "Container lifecycle hooks", "required": [], - "title": "password", - "type": "string" + "title": "lifecycleHooks" }, - "port": { - "default": "", - "description": "External database port (\"\" = engine default: 5432 / 3306)", - "required": [], - "title": "port", - "type": "string" + "livenessProbe": { + "additionalProperties": false, + "properties": { + "enabled": { + "default": true, + "description": "Enable the liveness probe", + "required": [], + "title": "enabled", + "type": "boolean" + }, + "failureThreshold": { + "default": 3, + "required": [], + "title": "failureThreshold", + "type": "integer" + }, + "initialDelaySeconds": { + "default": 0, + "required": [], + "title": "initialDelaySeconds", + "type": "integer" + }, + "periodSeconds": { + "default": 30, + "required": [], + "title": "periodSeconds", + "type": "integer" + }, + "successThreshold": { + "default": 1, + "required": [], + "title": "successThreshold", + "type": "integer" + }, + "timeoutSeconds": { + "default": 5, + "required": [], + "title": "timeoutSeconds", + "type": "integer" + } + }, + "required": [ + "enabled", + "initialDelaySeconds", + "periodSeconds", + "timeoutSeconds", + "failureThreshold", + "successThreshold" + ], + "title": "livenessProbe", + "type": "object" }, - "user": { - "default": "authup", - "description": "External database user", + "networkPolicy": { + "additionalProperties": false, + "properties": { + "allowExternal": { + "default": true, + "description": "Allow ingress from anywhere", + "required": [], + "title": "allowExternal", + "type": "boolean" + }, + "allowExternalEgress": { + "default": true, + "description": "Allow all egress", + "required": [], + "title": "allowExternalEgress", + "type": "boolean" + }, + "enabled": { + "default": false, + "description": "Create a NetworkPolicy for the UI", + "required": [], + "title": "enabled", + "type": "boolean" + }, + "extraEgress": { + "description": "Extra egress rules", + "items": { + "required": [] + }, + "required": [], + "title": "extraEgress", + "type": "array" + }, + "extraIngress": { + "description": "Extra ingress rules", + "items": { + "required": [] + }, + "required": [], + "title": "extraIngress", + "type": "array" + }, + "ingressNSMatchLabels": { + "additionalProperties": true, + "description": "Namespace labels allowed to connect when allowExternal is false", + "required": [], + "title": "ingressNSMatchLabels" + }, + "ingressPodMatchLabels": { + "additionalProperties": true, + "description": "Pod labels allowed to connect when allowExternal is false", + "required": [], + "title": "ingressPodMatchLabels" + } + }, + "required": [ + "enabled", + "allowExternal", + "allowExternalEgress", + "extraIngress", + "extraEgress" + ], + "title": "networkPolicy", + "type": "object" + }, + "nodeSelector": { + "additionalProperties": true, + "description": "Node selector", "required": [], - "title": "user", - "type": "string" - } - }, - "required": [ - "host", - "port", - "user", - "database", - "password", - "existingSecret", - "existingSecretPasswordKey" - ], - "title": "externalDatabase", - "type": "object" - }, - "externalRedis": { - "additionalProperties": false, - "properties": { - "existingSecret": { - "default": "", - "description": "Existing secret holding a FULL connection URL (tpl-rendered)", + "title": "nodeSelector" + }, + "pdb": { + "additionalProperties": false, + "properties": { + "create": { + "default": false, + "description": "Create a PodDisruptionBudget for the UI", + "required": [], + "title": "create", + "type": "boolean" + }, + "maxUnavailable": { + "default": "", + "description": "Maximum unavailable pods (defaults to 1 when both are empty)", + "required": [], + "title": "maxUnavailable", + "type": "string" + }, + "minAvailable": { + "default": "", + "description": "Minimum available pods", + "required": [], + "title": "minAvailable", + "type": "string" + } + }, + "required": [ + "create", + "minAvailable", + "maxUnavailable" + ], + "title": "pdb", + "type": "object" + }, + "podAnnotations": { + "additionalProperties": true, + "description": "Pod annotations (tpl-rendered)", "required": [], - "title": "existingSecret", - "type": "string" + "title": "podAnnotations" }, - "existingSecretKey": { - "default": "redis-connection-string", - "description": "Key inside externalRedis.existingSecret holding the connection URL", + "podAntiAffinityPreset": { + "default": "soft", + "description": "Pod anti-affinity preset: soft, hard or \"\"", "required": [], - "title": "existingSecretKey", + "title": "podAntiAffinityPreset", "type": "string" }, - "host": { - "default": "", - "description": "External Redis host (alternative to url; the chart composes the URL)", + "podLabels": { + "additionalProperties": true, + "description": "Pod labels (tpl-rendered)", "required": [], - "title": "host", - "type": "string" + "title": "podLabels" }, - "password": { + "podSecurityContext": { + "additionalProperties": true, + "description": "Pod security context", + "properties": { + "enabled": { + "default": true, + "required": [], + "title": "enabled", + "type": "boolean" + }, + "fsGroup": { + "default": 1000, + "required": [], + "title": "fsGroup", + "type": "integer" + } + }, + "required": [ + "enabled", + "fsGroup" + ], + "title": "podSecurityContext" + }, + "priorityClassName": { "default": "", - "description": "External Redis password", + "description": "Priority class name", "required": [], - "title": "password", + "title": "priorityClassName", "type": "string" }, - "port": { - "default": 6379, - "description": "External Redis port", - "required": [], - "title": "port", - "type": "integer" - }, - "url": { + "publicUrl": { "default": "", - "description": "Full external Redis/Valkey connection URL (redis://[user:pass@]host:port); stored in a chart-managed secret", + "description": "Public URL of the UI (NUXT_PUBLIC_PUBLIC_URL). \"\" = derived from adminConsole.ingress", "required": [], - "title": "url", + "title": "publicUrl", "type": "string" - } - }, - "required": [ - "url", - "host", - "port", - "password", - "existingSecret", - "existingSecretKey" - ], - "title": "externalRedis", - "type": "object" - }, - "extraDeploy": { - "description": "Extra objects to deploy (rendered through tpl; list of manifests or strings)", - "items": { - "required": [] - }, - "required": [], - "title": "extraDeploy", - "type": "array" - }, - "fullnameOverride": { - "default": "", - "description": "Override the fully qualified release name", - "required": [], - "title": "fullnameOverride", - "type": "string" - }, - "global": { - "additionalProperties": false, - "properties": { - "defaultStorageClass": { - "default": "", - "description": "Global default storage class for dynamic provisioning", - "required": [], - "title": "defaultStorageClass", - "type": "string" - }, - "imagePullSecrets": { - "description": "Global image pull secrets (list of names or objects)", - "items": { - "required": [] - }, - "required": [], - "title": "imagePullSecrets", - "type": "array" - }, - "imageRegistry": { - "default": "", - "description": "Global container image registry override (takes precedence over image.registry)", - "required": [], - "title": "imageRegistry", - "type": "string" - } - }, - "required": [ - "imageRegistry", - "imagePullSecrets", - "defaultStorageClass" - ], - "title": "global", - "type": "object" - }, - "image": { - "additionalProperties": false, - "description": "#", - "properties": { - "digest": { - "default": "", - "description": "Authup image digest (takes precedence over tag)", - "required": [], - "title": "digest", - "type": "string" - }, - "pullPolicy": { - "default": "IfNotPresent", - "description": "Authup image pull policy", - "required": [], - "title": "pullPolicy", - "type": "string" - }, - "pullSecrets": { - "description": "Authup image pull secrets", - "items": { - "required": [] - }, - "required": [], - "title": "pullSecrets", - "type": "array" - }, - "registry": { - "default": "docker.io", - "description": "Authup image registry", - "required": [], - "title": "registry", - "type": "string" - }, - "repository": { - "default": "authup/authup", - "description": "Authup image repository (one image serves both services)", - "required": [], - "title": "repository", - "type": "string" - }, - "tag": { - "default": "", - "description": "Authup image tag (defaults to the chart appVersion)", - "required": [], - "title": "tag", - "type": "string" - } - }, - "required": [ - "registry", - "repository", - "tag", - "digest", - "pullPolicy", - "pullSecrets" - ], - "title": "image", - "type": "object" - }, - "mysql": { - "additionalProperties": false, - "description": "# Built-in single-instance MySQL (docker-official image). Same caveats as postgresql.", - "properties": { - "affinity": { - "additionalProperties": true, - "description": "MySQL affinity", - "required": [], - "title": "affinity" }, - "auth": { + "readinessProbe": { "additionalProperties": false, "properties": { - "database": { - "default": "authup", - "description": "MySQL database name (created on first boot)", + "enabled": { + "default": true, + "description": "Enable the readiness probe", "required": [], - "title": "database", - "type": "string" + "title": "enabled", + "type": "boolean" }, - "password": { - "default": "", - "description": "MySQL password (\"\" = generate once, keep across upgrades)", + "failureThreshold": { + "default": 3, "required": [], - "title": "password", - "type": "string" + "title": "failureThreshold", + "type": "integer" }, - "username": { - "default": "authup", - "description": "MySQL application username", + "initialDelaySeconds": { + "default": 0, "required": [], - "title": "username", - "type": "string" + "title": "initialDelaySeconds", + "type": "integer" + }, + "periodSeconds": { + "default": 10, + "required": [], + "title": "periodSeconds", + "type": "integer" + }, + "successThreshold": { + "default": 1, + "required": [], + "title": "successThreshold", + "type": "integer" + }, + "timeoutSeconds": { + "default": 5, + "required": [], + "title": "timeoutSeconds", + "type": "integer" } }, "required": [ - "username", - "password", - "database" + "enabled", + "initialDelaySeconds", + "periodSeconds", + "timeoutSeconds", + "failureThreshold", + "successThreshold" ], - "title": "auth", + "title": "readinessProbe", "type": "object" }, - "containerSecurityContext": { + "replicaCount": { + "default": 1, + "description": "Number of UI replicas (fully stateless, scale freely)", + "required": [], + "title": "replicaCount", + "type": "integer" + }, + "resources": { "additionalProperties": true, - "description": "MySQL container security context", + "description": "UI container resources", "properties": { - "allowPrivilegeEscalation": { - "default": false, - "required": [], - "title": "allowPrivilegeEscalation", - "type": "boolean" - }, - "capabilities": { - "additionalProperties": false, + "limits": { + "additionalProperties": true, "properties": { - "drop": { - "items": { - "anyOf": [ - { - "required": [], - "type": "string" - } - ], - "required": [] - }, + "memory": { + "default": "512Mi", "required": [], - "title": "drop", - "type": "array" + "title": "memory", + "type": "string" } }, "required": [ - "drop" + "memory" ], - "title": "capabilities", - "type": "object" - }, - "enabled": { - "default": true, - "required": [], - "title": "enabled", - "type": "boolean" - }, - "runAsGroup": { - "default": 999, - "required": [], - "title": "runAsGroup", - "type": "integer" - }, - "runAsNonRoot": { - "default": true, - "required": [], - "title": "runAsNonRoot", - "type": "boolean" - }, - "runAsUser": { - "default": 999, - "required": [], - "title": "runAsUser", - "type": "integer" + "title": "limits" }, - "seccompProfile": { - "additionalProperties": false, + "requests": { + "additionalProperties": true, "properties": { - "type": { - "default": "RuntimeDefault", + "cpu": { + "default": "100m", "required": [], - "title": "type", + "title": "cpu", "type": "string" - } + }, + "memory": { + "default": "256Mi", + "required": [], + "title": "memory", + "type": "string" + } }, "required": [ - "type" + "cpu", + "memory" ], - "title": "seccompProfile", - "type": "object" + "title": "requests" } }, - "required": [ - "enabled", - "runAsUser", - "runAsGroup", - "runAsNonRoot", - "allowPrivilegeEscalation", - "capabilities", - "seccompProfile" - ], - "title": "containerSecurityContext" - }, - "enabled": { - "default": false, - "description": "Deploy the built-in MySQL instance", "required": [], - "title": "enabled", - "type": "boolean" + "title": "resources" }, - "extraEnvVars": { - "description": "Extra environment variables for the MySQL container", - "items": { - "required": [] - }, + "revisionHistoryLimit": { + "default": 3, + "description": "Deployment revision history limit", "required": [], - "title": "extraEnvVars", - "type": "array" + "title": "revisionHistoryLimit", + "type": "integer" }, - "image": { + "route": { "additionalProperties": false, "properties": { - "pullPolicy": { - "default": "IfNotPresent", - "description": "MySQL image pull policy", + "annotations": { + "additionalProperties": true, + "description": "HTTPRoute annotations", "required": [], - "title": "pullPolicy", - "type": "string" + "title": "annotations" }, - "registry": { - "default": "docker.io", - "description": "MySQL image registry", + "enabled": { + "default": false, + "description": "Create a Gateway API HTTPRoute for the UI", "required": [], - "title": "registry", - "type": "string" + "title": "enabled", + "type": "boolean" }, - "repository": { - "default": "mysql", - "description": "MySQL image repository (docker official image)", + "hostnames": { + "description": "Route hostnames ([] = derived from adminConsole.publicUrl / ingress hostname)", + "items": { + "required": [] + }, "required": [], - "title": "repository", - "type": "string" + "title": "hostnames", + "type": "array" }, - "tag": { - "default": "8.4", - "description": "MySQL image tag", + "parentRefs": { + "description": "Gateway parentRefs", + "items": { + "required": [] + }, "required": [], - "title": "tag", - "type": "string" + "title": "parentRefs", + "type": "array" } }, "required": [ - "registry", - "repository", - "tag", - "pullPolicy" + "enabled", + "hostnames", + "parentRefs" ], - "title": "image", + "title": "route", "type": "object" }, - "nodeSelector": { - "additionalProperties": true, - "description": "MySQL node selector", + "schedulerName": { + "default": "", + "description": "Scheduler name", "required": [], - "title": "nodeSelector" + "title": "schedulerName", + "type": "string" }, - "persistence": { + "service": { "additionalProperties": false, "properties": { - "enabled": { - "default": true, - "description": "Persist MySQL data", + "annotations": { + "additionalProperties": true, + "description": "Service annotations (tpl-rendered)", "required": [], - "title": "enabled", - "type": "boolean" + "title": "annotations" }, - "existingClaim": { + "clusterIP": { "default": "", - "description": "Use an existing PVC instead of creating one", + "description": "Static cluster IP", "required": [], - "title": "existingClaim", + "title": "clusterIP", "type": "string" }, - "size": { - "default": "8Gi", - "description": "PVC size", + "externalTrafficPolicy": { + "default": "Cluster", + "description": "External traffic policy", "required": [], - "title": "size", + "title": "externalTrafficPolicy", "type": "string" }, - "storageClass": { + "extraPorts": { + "description": "Extra service ports", + "items": { + "required": [] + }, + "required": [], + "title": "extraPorts", + "type": "array" + }, + "loadBalancerIP": { "default": "", - "description": "PVC storage class (\"\" = cluster default; global.defaultStorageClass wins)", + "description": "LoadBalancer IP", "required": [], - "title": "storageClass", + "title": "loadBalancerIP", "type": "string" - } - }, - "required": [ - "enabled", - "size", - "storageClass", - "existingClaim" - ], - "title": "persistence", - "type": "object" - }, - "podSecurityContext": { - "additionalProperties": true, - "description": "MySQL pod security context", - "properties": { - "enabled": { - "default": true, - "required": [], - "title": "enabled", - "type": "boolean" }, - "fsGroup": { - "default": 999, + "loadBalancerSourceRanges": { + "description": "LoadBalancer source ranges", + "items": { + "required": [] + }, "required": [], - "title": "fsGroup", - "type": "integer" - } - }, - "required": [ - "enabled", - "fsGroup" - ], - "title": "podSecurityContext" - }, - "resources": { - "additionalProperties": true, - "description": "MySQL container resources", - "properties": { - "limits": { - "additionalProperties": true, + "title": "loadBalancerSourceRanges", + "type": "array" + }, + "nodePorts": { + "additionalProperties": false, "properties": { - "memory": { - "default": "1Gi", + "http": { + "default": "", + "description": "Node port (\"\" = auto-assign)", "required": [], - "title": "memory", + "title": "http", "type": "string" } }, "required": [ - "memory" + "http" ], - "title": "limits" + "title": "nodePorts", + "type": "object" }, - "requests": { - "additionalProperties": true, + "ports": { + "additionalProperties": false, "properties": { - "cpu": { - "default": "100m", - "required": [], - "title": "cpu", - "type": "string" - }, - "memory": { - "default": "512Mi", + "http": { + "default": 3000, + "description": "Service HTTP port (the container port is fixed at 3000)", "required": [], - "title": "memory", - "type": "string" + "title": "http", + "type": "integer" } }, "required": [ - "cpu", - "memory" + "http" ], - "title": "requests" + "title": "ports", + "type": "object" + }, + "sessionAffinity": { + "default": "None", + "description": "Session affinity", + "required": [], + "title": "sessionAffinity", + "type": "string" + }, + "sessionAffinityConfig": { + "additionalProperties": true, + "description": "Session affinity config", + "required": [], + "title": "sessionAffinityConfig" + }, + "type": { + "default": "ClusterIP", + "description": "Service type", + "required": [], + "title": "type", + "type": "string" } }, - "required": [], - "title": "resources" - }, - "tolerations": { - "description": "MySQL tolerations", - "items": { - "required": [] + "required": [ + "type", + "ports", + "nodePorts", + "clusterIP", + "loadBalancerIP", + "loadBalancerSourceRanges", + "externalTrafficPolicy", + "sessionAffinity", + "extraPorts" + ], + "title": "service", + "type": "object" + }, + "sidecars": { + "description": "Sidecar containers (tpl-rendered)", + "items": { + "required": [] }, "required": [], - "title": "tolerations", + "title": "sidecars", "type": "array" - } - }, - "required": [ - "enabled", - "image", - "auth", - "persistence", - "tolerations", - "extraEnvVars" - ], - "title": "mysql", - "type": "object" - }, - "nameOverride": { - "default": "", - "description": "Override the chart name", - "required": [], - "title": "nameOverride", - "type": "string" - }, - "namespaceOverride": { - "default": "", - "description": "Override the release namespace", - "required": [], - "title": "namespaceOverride", - "type": "string" - }, - "postgresql": { - "additionalProperties": false, - "description": "# Built-in single-instance PostgreSQL (docker-official image).\n# Convenience for dev / small production; for serious deployments bring an\n# external database or an operator such as CloudNativePG.", - "properties": { - "affinity": { - "additionalProperties": true, - "description": "PostgreSQL affinity", - "required": [], - "title": "affinity" }, - "auth": { + "startupProbe": { "additionalProperties": false, "properties": { - "database": { - "default": "authup", - "description": "PostgreSQL database name (created on first boot)", - "required": [], - "title": "database", - "type": "string" - }, - "password": { - "default": "", - "description": "PostgreSQL password (\"\" = generate once, keep across upgrades)", - "required": [], - "title": "password", - "type": "string" - }, - "username": { - "default": "authup", - "description": "PostgreSQL application username", - "required": [], - "title": "username", - "type": "string" - } - }, - "required": [ - "username", - "password", - "database" - ], - "title": "auth", - "type": "object" - }, - "containerSecurityContext": { - "additionalProperties": true, - "description": "PostgreSQL container security context", - "properties": { - "allowPrivilegeEscalation": { - "default": false, - "required": [], - "title": "allowPrivilegeEscalation", - "type": "boolean" - }, - "capabilities": { - "additionalProperties": false, - "properties": { - "drop": { - "items": { - "anyOf": [ - { - "required": [], - "type": "string" - } - ], - "required": [] - }, - "required": [], - "title": "drop", - "type": "array" - } - }, - "required": [ - "drop" - ], - "title": "capabilities", - "type": "object" - }, "enabled": { "default": true, + "description": "Enable the startup probe", "required": [], "title": "enabled", "type": "boolean" }, - "runAsGroup": { - "default": 999, + "failureThreshold": { + "default": 24, "required": [], - "title": "runAsGroup", + "title": "failureThreshold", "type": "integer" }, - "runAsNonRoot": { - "default": true, + "initialDelaySeconds": { + "default": 5, "required": [], - "title": "runAsNonRoot", - "type": "boolean" + "title": "initialDelaySeconds", + "type": "integer" }, - "runAsUser": { - "default": 999, + "periodSeconds": { + "default": 5, "required": [], - "title": "runAsUser", + "title": "periodSeconds", "type": "integer" }, - "seccompProfile": { - "additionalProperties": false, - "properties": { - "type": { - "default": "RuntimeDefault", - "required": [], - "title": "type", - "type": "string" - } - }, - "required": [ - "type" - ], - "title": "seccompProfile", - "type": "object" + "successThreshold": { + "default": 1, + "required": [], + "title": "successThreshold", + "type": "integer" + }, + "timeoutSeconds": { + "default": 5, + "required": [], + "title": "timeoutSeconds", + "type": "integer" } }, "required": [ "enabled", - "runAsUser", - "runAsGroup", - "runAsNonRoot", - "allowPrivilegeEscalation", - "capabilities", - "seccompProfile" + "initialDelaySeconds", + "periodSeconds", + "timeoutSeconds", + "failureThreshold", + "successThreshold" ], - "title": "containerSecurityContext" + "title": "startupProbe", + "type": "object" }, - "enabled": { - "default": true, - "description": "Deploy the built-in PostgreSQL instance", + "terminationGracePeriodSeconds": { + "default": 30, + "description": "Pod termination grace period", "required": [], - "title": "enabled", - "type": "boolean" + "title": "terminationGracePeriodSeconds", + "type": "integer" }, - "extraEnvVars": { - "description": "Extra environment variables for the PostgreSQL container", + "tolerations": { + "description": "Tolerations", "items": { "required": [] }, "required": [], - "title": "extraEnvVars", + "title": "tolerations", "type": "array" }, - "image": { - "additionalProperties": false, + "topologySpreadConstraints": { + "description": "Topology spread constraints", + "items": { + "required": [] + }, + "required": [], + "title": "topologySpreadConstraints", + "type": "array" + }, + "updateStrategy": { + "additionalProperties": true, + "description": "Deployment update strategy", "properties": { - "pullPolicy": { - "default": "IfNotPresent", - "description": "PostgreSQL image pull policy", - "required": [], - "title": "pullPolicy", - "type": "string" - }, - "registry": { - "default": "docker.io", - "description": "PostgreSQL image registry", - "required": [], - "title": "registry", - "type": "string" - }, - "repository": { - "default": "postgres", - "description": "PostgreSQL image repository (docker official image)", - "required": [], - "title": "repository", - "type": "string" - }, - "tag": { - "default": "17", - "description": "PostgreSQL image tag", + "type": { + "default": "RollingUpdate", "required": [], - "title": "tag", + "title": "type", "type": "string" } }, "required": [ - "registry", - "repository", - "tag", - "pullPolicy" + "type" ], - "title": "image", - "type": "object" - }, - "nodeSelector": { - "additionalProperties": true, - "description": "PostgreSQL node selector", - "required": [], - "title": "nodeSelector" - }, - "persistence": { + "title": "updateStrategy" + } + }, + "required": [ + "enabled", + "replicaCount", + "publicUrl", + "apiUrl", + "internalApiUrl", + "command", + "args", + "extraEnvVars", + "extraEnvVarsCM", + "extraEnvVarsSecret", + "extraVolumes", + "extraVolumeMounts", + "initContainers", + "sidecars", + "hostAliases", + "terminationGracePeriodSeconds", + "revisionHistoryLimit", + "disableRestartOnChanges", + "startupProbe", + "livenessProbe", + "readinessProbe", + "tolerations", + "podAntiAffinityPreset", + "topologySpreadConstraints", + "priorityClassName", + "schedulerName", + "service", + "ingress", + "route", + "pdb", + "autoscaling", + "networkPolicy" + ], + "title": "adminConsole", + "type": "object" + }, + "auth": { + "additionalProperties": false, + "description": "#\n# Credentials seeded into authup at boot. Empty passwords are generated once and\n# kept stable across upgrades via lookup.\n# GITOPS WARNING: lookup is inert under `helm template` and ArgoCD-style\n# renders — every sync would then apply a NEW random value, roll the pods via\n# the checksum annotation, and desync the Secret from the live credential\n# (authup only applies the admin password at first provisioning unless the\n# reset flag is set). GitOps users MUST set explicit values or auth.existingSecret.", + "properties": { + "adminPassword": { + "default": "", + "description": "Initial admin user password (\"\" = generate once, keep across upgrades).\nChanging it after the first install only takes effect with\nadminPasswordReset=true for one upgrade cycle", + "required": [], + "title": "adminPassword", + "type": "string" + }, + "adminPasswordReset": { + "default": false, + "description": "Re-assert the admin password on every boot (USER_ADMIN_PASSWORD_RESET)", + "required": [], + "title": "adminPasswordReset", + "type": "boolean" + }, + "existingSecret": { + "default": "", + "description": "Existing secret holding the keys below instead of the chart-managed secret (tpl-rendered)", + "required": [], + "title": "existingSecret", + "type": "string" + }, + "secretKeys": { "additionalProperties": false, "properties": { - "enabled": { - "default": true, - "description": "Persist PostgreSQL data", - "required": [], - "title": "enabled", - "type": "boolean" - }, - "existingClaim": { - "default": "", - "description": "Use an existing PVC instead of creating one", + "adminPasswordKey": { + "default": "admin-password", + "description": "Key inside the (existing) secret holding the admin password", "required": [], - "title": "existingClaim", + "title": "adminPasswordKey", "type": "string" }, - "size": { - "default": "8Gi", - "description": "PVC size", + "secretsEncryptionKeyKey": { + "default": "secrets-encryption-key", + "description": "Key inside the (existing) secret holding the secrets encryption key", "required": [], - "title": "size", + "title": "secretsEncryptionKeyKey", "type": "string" }, - "storageClass": { - "default": "", - "description": "PVC storage class (\"\" = cluster default; global.defaultStorageClass wins)", + "systemClientSecretKey": { + "default": "system-client-secret", + "description": "Key inside the (existing) secret holding the system client secret", "required": [], - "title": "storageClass", + "title": "systemClientSecretKey", "type": "string" } }, "required": [ - "enabled", - "size", - "storageClass", - "existingClaim" + "adminPasswordKey", + "systemClientSecretKey", + "secretsEncryptionKeyKey" ], - "title": "persistence", + "title": "secretKeys", "type": "object" }, - "podSecurityContext": { - "additionalProperties": true, - "description": "PostgreSQL pod security context", - "properties": { - "enabled": { - "default": true, - "required": [], - "title": "enabled", - "type": "boolean" - }, - "fsGroup": { - "default": 999, - "required": [], - "title": "fsGroup", - "type": "integer" - } - }, - "required": [ - "enabled", - "fsGroup" - ], - "title": "podSecurityContext" + "secretsEncryptionKey": { + "default": "", + "description": "Optional AES-256 key-encryption-key wrapping the realm key store\n(SECRETS_ENCRYPTION_KEY, base64-encoded 32 bytes). Deliberately never\ngenerated by the chart: this key is effectively write-once — losing or\nrotating it bricks wrapped MFA seeds and signing keys. Set it explicitly\nhere, or reference it via existingSecret plus\nsecretsEncryptionKeyEnabled=true, and back it up.", + "required": [], + "title": "secretsEncryptionKey", + "type": "string" }, - "resources": { - "additionalProperties": true, - "description": "PostgreSQL container resources", - "properties": { - "limits": { - "additionalProperties": true, - "properties": { - "memory": { - "default": "1Gi", - "required": [], - "title": "memory", - "type": "string" - } - }, - "required": [ - "memory" - ], - "title": "limits" - }, - "requests": { - "additionalProperties": true, - "properties": { - "cpu": { - "default": "100m", - "required": [], - "title": "cpu", - "type": "string" - }, - "memory": { - "default": "256Mi", - "required": [], - "title": "memory", - "type": "string" - } - }, - "required": [ - "cpu", - "memory" - ], - "title": "requests" - } - }, + "secretsEncryptionKeyEnabled": { + "default": false, + "description": "Declare that auth.existingSecret carries the secrets encryption key\n(under secretKeys.secretsEncryptionKeyKey). Required for the key to be\nwired from an existing secret — it is never inferred, so a missing key can\nnot silently fail open into plaintext-at-rest", "required": [], - "title": "resources" + "title": "secretsEncryptionKeyEnabled", + "type": "boolean" }, - "tolerations": { - "description": "PostgreSQL tolerations", - "items": { - "required": [] - }, + "systemClientEnabled": { + "default": false, + "description": "Provision the built-in system client (CLIENT_SYSTEM_ENABLED); required for machine-to-machine consumers", "required": [], - "title": "tolerations", - "type": "array" + "title": "systemClientEnabled", + "type": "boolean" + }, + "systemClientSecret": { + "default": "", + "description": "System client secret (\"\" = generate once when systemClientEnabled)", + "required": [], + "title": "systemClientSecret", + "type": "string" + }, + "systemClientSecretReset": { + "default": false, + "description": "Re-assert the system client secret on every boot (CLIENT_SYSTEM_SECRET_RESET)", + "required": [], + "title": "systemClientSecretReset", + "type": "boolean" } }, "required": [ - "enabled", - "image", - "auth", - "persistence", - "tolerations", - "extraEnvVars" + "adminPassword", + "adminPasswordReset", + "systemClientEnabled", + "systemClientSecret", + "systemClientSecretReset", + "secretsEncryptionKey", + "secretsEncryptionKeyEnabled", + "existingSecret", + "secretKeys" ], - "title": "postgresql", + "title": "auth", "type": "object" }, - "server": { + "commonAnnotations": { + "additionalProperties": true, + "description": "Annotations added to every object", + "required": [], + "title": "commonAnnotations" + }, + "commonLabels": { + "additionalProperties": true, + "description": "Labels added to every object", + "required": [], + "title": "commonLabels" + }, + "database": { "additionalProperties": false, - "description": "#", + "description": "#\n# Exactly one of: built-in postgresql, built-in mysql, or externalDatabase.", "properties": { - "affinity": { - "additionalProperties": true, - "description": "Affinity (overrides the anti-affinity preset when set)", - "required": [], - "title": "affinity" - }, - "args": { - "description": "Override the container args", - "items": { - "required": [] - }, + "type": { + "default": "postgres", + "description": "Database engine when using externalDatabase: postgres or mysql", "required": [], - "title": "args", - "type": "array" - }, - "autoscaling": { - "additionalProperties": false, - "properties": { - "hpa": { - "additionalProperties": false, - "properties": { - "enabled": { - "default": false, - "description": "Enable HPA for server-core (requires a configured cache)", - "required": [], - "title": "enabled", - "type": "boolean" - }, - "maxReplicas": { - "default": 5, - "description": "Maximum replicas", - "required": [], - "title": "maxReplicas", - "type": "integer" - }, - "minReplicas": { - "default": 2, - "description": "Minimum replicas", - "required": [], - "title": "minReplicas", - "type": "integer" - }, - "targetCPU": { - "default": 75, - "description": "Target CPU utilization percentage", - "required": [], - "title": "targetCPU", - "type": "integer" - }, - "targetMemory": { - "default": "", - "description": "Target memory utilization percentage", - "required": [], - "title": "targetMemory", - "type": "string" - } - }, - "required": [ - "enabled", - "minReplicas", - "maxReplicas", - "targetCPU", - "targetMemory" - ], - "title": "hpa", - "type": "object" - } + "title": "type", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "database", + "type": "object" + }, + "diagnosticMode": { + "additionalProperties": false, + "properties": { + "args": { + "description": "Args overriding the containers' args in diagnostic mode", + "items": { + "anyOf": [ + { + "required": [], + "type": "string" + } + ], + "required": [] }, - "required": [ - "hpa" - ], - "title": "autoscaling", - "type": "object" + "required": [], + "title": "args", + "type": "array" }, "command": { - "description": "Override the container command", + "description": "Command overriding the containers' command in diagnostic mode", "items": { + "anyOf": [ + { + "required": [], + "type": "string" + } + ], "required": [] }, "required": [], "title": "command", "type": "array" }, - "config": { - "additionalProperties": true, - "description": "Extra environment variables rendered literally into the env ConfigMap\n(map of NAME: value) for options without first-class values", + "enabled": { + "default": false, + "description": "Start every container with a sleep command and disable probes (debugging)", "required": [], - "title": "config" + "title": "enabled", + "type": "boolean" + } + }, + "required": [ + "enabled", + "command", + "args" + ], + "title": "diagnosticMode", + "type": "object" + }, + "externalDatabase": { + "additionalProperties": false, + "properties": { + "database": { + "default": "authup", + "description": "External database name (must exist, or the user needs CREATE privilege)", + "required": [], + "title": "database", + "type": "string" }, - "configuration": { + "existingSecret": { "default": "", - "description": "Content of an authup.server.core.conf mounted into the working directory\nfor file-only options (middleware objects, per-field SMTP, CORS allowlist).\nEnvironment variables always win over file values.", + "description": "Existing secret with the database password (tpl-rendered)", "required": [], - "title": "configuration", + "title": "existingSecret", "type": "string" }, - "containerSecurityContext": { - "additionalProperties": true, - "description": "Container security context. The upstream image runs as root and needs a\nwritable npm cache; the chart mounts emptyDirs at /usr/src/app/writable and\n/tmp to keep readOnlyRootFilesystem viable.", - "properties": { - "allowPrivilegeEscalation": { - "default": false, - "required": [], - "title": "allowPrivilegeEscalation", - "type": "boolean" - }, - "capabilities": { - "additionalProperties": false, - "properties": { - "drop": { - "items": { - "anyOf": [ - { - "required": [], - "type": "string" - } - ], - "required": [] - }, - "required": [], - "title": "drop", - "type": "array" - } - }, - "required": [ - "drop" - ], - "title": "capabilities", - "type": "object" - }, - "enabled": { - "default": true, - "required": [], - "title": "enabled", - "type": "boolean" - }, - "readOnlyRootFilesystem": { - "default": false, - "required": [], - "title": "readOnlyRootFilesystem", - "type": "boolean" - }, - "runAsNonRoot": { - "default": false, - "required": [], - "title": "runAsNonRoot", - "type": "boolean" - }, - "runAsUser": { - "default": 0, - "required": [], - "title": "runAsUser", - "type": "integer" - }, - "seccompProfile": { - "additionalProperties": false, - "properties": { - "type": { - "default": "RuntimeDefault", - "required": [], - "title": "type", - "type": "string" - } - }, - "required": [ - "type" - ], - "title": "seccompProfile", - "type": "object" - } - }, - "required": [ - "enabled", - "runAsUser", - "runAsNonRoot", - "allowPrivilegeEscalation", - "capabilities", - "seccompProfile", - "readOnlyRootFilesystem" - ], - "title": "containerSecurityContext" - }, - "customLivenessProbe": { - "additionalProperties": true, - "description": "Custom liveness probe", + "existingSecretPasswordKey": { + "default": "password", + "description": "Key inside externalDatabase.existingSecret holding the password", "required": [], - "title": "customLivenessProbe" + "title": "existingSecretPasswordKey", + "type": "string" }, - "customReadinessProbe": { - "additionalProperties": true, - "description": "Custom readiness probe", + "host": { + "default": "", + "description": "External database host (tpl-rendered). Setting this selects the external database", "required": [], - "title": "customReadinessProbe" + "title": "host", + "type": "string" }, - "customStartupProbe": { - "additionalProperties": true, - "description": "Custom startup probe overriding the structured one", + "password": { + "default": "", + "description": "External database password (stored in a chart-managed secret, never inline env)", "required": [], - "title": "customStartupProbe" + "title": "password", + "type": "string" }, - "disableRestartOnChanges": { - "default": false, - "description": "Disable the checksum annotations that roll pods on config/secret changes", + "port": { + "default": "", + "description": "External database port (\"\" = engine default: 5432 / 3306)", "required": [], - "title": "disableRestartOnChanges", - "type": "boolean" + "title": "port", + "type": "string" }, - "enabled": { - "default": true, - "description": "Deploy the server-core service", + "user": { + "default": "authup", + "description": "External database user", "required": [], - "title": "enabled", - "type": "boolean" - }, - "existingConfigmap": { + "title": "user", + "type": "string" + } + }, + "required": [ + "host", + "port", + "user", + "database", + "password", + "existingSecret", + "existingSecretPasswordKey" + ], + "title": "externalDatabase", + "type": "object" + }, + "externalRedis": { + "additionalProperties": false, + "properties": { + "existingSecret": { "default": "", - "description": "Existing ConfigMap holding authup.server.core.conf (tpl-rendered)", + "description": "Existing secret holding a FULL connection URL (tpl-rendered)", "required": [], - "title": "existingConfigmap", + "title": "existingSecret", "type": "string" }, - "extraEnvVars": { - "description": "Extra environment variables for the server container", - "items": { - "required": [] - }, + "existingSecretKey": { + "default": "redis-connection-string", + "description": "Key inside externalRedis.existingSecret holding the connection URL", "required": [], - "title": "extraEnvVars", - "type": "array" + "title": "existingSecretKey", + "type": "string" }, - "extraEnvVarsCM": { + "host": { "default": "", - "description": "Extra ConfigMap with environment variables (tpl-rendered name)", + "description": "External Redis host (alternative to url; the chart composes the URL)", "required": [], - "title": "extraEnvVarsCM", + "title": "host", "type": "string" }, - "extraEnvVarsSecret": { + "password": { "default": "", - "description": "Extra Secret with environment variables (tpl-rendered name)", + "description": "External Redis password", "required": [], - "title": "extraEnvVarsSecret", + "title": "password", "type": "string" }, - "extraVolumeMounts": { - "description": "Extra volume mounts (tpl-rendered)", - "items": { - "required": [] - }, + "port": { + "default": 6379, + "description": "External Redis port", "required": [], - "title": "extraVolumeMounts", - "type": "array" + "title": "port", + "type": "integer" }, - "extraVolumes": { - "description": "Extra volumes (tpl-rendered)", - "items": { - "required": [] - }, + "url": { + "default": "", + "description": "Full external Redis/Valkey connection URL (redis://[user:pass@]host:port); stored in a chart-managed secret", "required": [], - "title": "extraVolumes", - "type": "array" - }, - "features": { - "additionalProperties": false, - "properties": { - "emailVerification": { - "default": false, - "description": "Enable email verification (EMAIL_VERIFICATION_ENABLED; requires SMTP)", - "required": [], - "title": "emailVerification", - "type": "boolean" - }, - "passwordRecovery": { - "default": false, - "description": "Enable password recovery (PASSWORD_RECOVERY_ENABLED; requires SMTP)", - "required": [], - "title": "passwordRecovery", - "type": "boolean" - }, - "registration": { - "default": false, - "description": "Enable self-service user registration (REGISTRATION_ENABLED)", - "required": [], - "title": "registration", - "type": "boolean" - } - }, - "required": [ - "registration", - "passwordRecovery", - "emailVerification" - ], - "title": "features", - "type": "object" - }, - "hostAliases": { - "description": "Pod host aliases", - "items": { - "required": [] - }, + "title": "url", + "type": "string" + } + }, + "required": [ + "url", + "host", + "port", + "password", + "existingSecret", + "existingSecretKey" + ], + "title": "externalRedis", + "type": "object" + }, + "extraDeploy": { + "description": "Extra objects to deploy (rendered through tpl; list of manifests or strings)", + "items": { + "required": [] + }, + "required": [], + "title": "extraDeploy", + "type": "array" + }, + "fullnameOverride": { + "default": "", + "description": "Override the fully qualified release name", + "required": [], + "title": "fullnameOverride", + "type": "string" + }, + "global": { + "additionalProperties": false, + "properties": { + "defaultStorageClass": { + "default": "", + "description": "Global default storage class for dynamic provisioning", "required": [], - "title": "hostAliases", - "type": "array" - }, - "ingress": { - "additionalProperties": false, - "properties": { - "annotations": { - "additionalProperties": true, - "description": "Ingress annotations (tpl-rendered). Token responses are large; with\ningress-nginx consider proxy-buffer-size 16k+.", - "required": [], - "title": "annotations" - }, - "certManager": { - "default": false, - "description": "Request a cert-manager certificate (adds kubernetes.io/tls-acme)", - "required": [], - "title": "certManager", - "type": "boolean" - }, - "enabled": { - "default": false, - "description": "Enable ingress for server-core. NOTE: this also exposes the\nUNAUTHENTICATED /metrics endpoint publicly — block it at the ingress\ncontroller or disable it via server.configuration\n(\"middlewarePrometheus: false\") when it is not scraped", - "required": [], - "title": "enabled", - "type": "boolean" - }, - "extraHosts": { - "description": "Extra hosts", - "items": { - "required": [] - }, - "required": [], - "title": "extraHosts", - "type": "array" - }, - "extraPaths": { - "description": "Extra paths for the primary host", - "items": { - "required": [] - }, - "required": [], - "title": "extraPaths", - "type": "array" - }, - "extraRules": { - "description": "Full custom rules (tpl-rendered; overrides the generated rule)", - "items": { - "required": [] - }, - "required": [], - "title": "extraRules", - "type": "array" - }, - "extraTls": { - "description": "Extra TLS entries", - "items": { - "required": [] - }, - "required": [], - "title": "extraTls", - "type": "array" - }, - "hostname": { - "default": "", - "description": "Ingress hostname (tpl-rendered); also drives the derived PUBLIC_URL", - "required": [], - "title": "hostname", - "type": "string" - }, - "ingressClassName": { - "default": "", - "description": "Ingress class name", - "required": [], - "title": "ingressClassName", - "type": "string" - }, - "path": { - "default": "/", - "description": "Ingress path", - "required": [], - "title": "path", - "type": "string" - }, - "pathType": { - "default": "Prefix", - "description": "Ingress path type", - "required": [], - "title": "pathType", - "type": "string" - }, - "tls": { - "default": false, - "description": "Enable TLS for the hostname (secret \u003chostname\u003e-tls unless extraTls overrides)", - "required": [], - "title": "tls", - "type": "boolean" - } - }, - "required": [ - "enabled", - "hostname", - "path", - "pathType", - "ingressClassName", - "tls", - "certManager", - "extraHosts", - "extraPaths", - "extraTls", - "extraRules" - ], - "title": "ingress", - "type": "object" + "title": "defaultStorageClass", + "type": "string" }, - "initContainers": { - "description": "Init containers (tpl-rendered)", + "imagePullSecrets": { + "description": "Global image pull secrets (list of names or objects)", "items": { "required": [] }, "required": [], - "title": "initContainers", + "title": "imagePullSecrets", "type": "array" }, - "lifecycleHooks": { - "additionalProperties": true, - "description": "Container lifecycle hooks", + "imageRegistry": { + "default": "", + "description": "Global container image registry override (takes precedence over image.registry)", "required": [], - "title": "lifecycleHooks" - }, - "livenessProbe": { - "additionalProperties": false, - "properties": { - "enabled": { - "default": true, - "description": "Enable the liveness probe (GET / status endpoint)", - "required": [], - "title": "enabled", - "type": "boolean" - }, - "failureThreshold": { - "default": 3, - "required": [], - "title": "failureThreshold", - "type": "integer" - }, - "initialDelaySeconds": { - "default": 0, - "required": [], - "title": "initialDelaySeconds", - "type": "integer" - }, - "periodSeconds": { - "default": 30, - "required": [], - "title": "periodSeconds", - "type": "integer" - }, - "successThreshold": { - "default": 1, - "required": [], - "title": "successThreshold", - "type": "integer" - }, - "timeoutSeconds": { - "default": 5, - "required": [], - "title": "timeoutSeconds", - "type": "integer" - } - }, - "required": [ - "enabled", - "initialDelaySeconds", - "periodSeconds", - "timeoutSeconds", - "failureThreshold", - "successThreshold" - ], - "title": "livenessProbe", - "type": "object" + "title": "imageRegistry", + "type": "string" + } + }, + "required": [ + "imageRegistry", + "imagePullSecrets", + "defaultStorageClass" + ], + "title": "global", + "type": "object" + }, + "image": { + "additionalProperties": false, + "description": "#", + "properties": { + "digest": { + "default": "", + "description": "Authup image digest (takes precedence over tag)", + "required": [], + "title": "digest", + "type": "string" }, - "metrics": { - "additionalProperties": false, - "properties": { - "serviceMonitor": { - "additionalProperties": false, - "description": "The /metrics endpoint is UNAUTHENTICATED; keep it off the public ingress", - "properties": { - "enabled": { - "default": false, - "description": "Create a prometheus-operator ServiceMonitor scraping /metrics", - "required": [], - "title": "enabled", - "type": "boolean" - }, - "honorLabels": { - "default": false, - "description": "Honor labels", - "required": [], - "title": "honorLabels", - "type": "boolean" - }, - "interval": { - "default": "30s", - "description": "Scrape interval", - "required": [], - "title": "interval", - "type": "string" - }, - "jobLabel": { - "default": "", - "description": "Job label", - "required": [], - "title": "jobLabel", - "type": "string" - }, - "labels": { - "additionalProperties": true, - "description": "ServiceMonitor labels", - "required": [], - "title": "labels" - }, - "metricRelabelings": { - "description": "Metric relabelings", - "items": { - "required": [] - }, - "required": [], - "title": "metricRelabelings", - "type": "array" - }, - "namespace": { - "default": "", - "description": "ServiceMonitor namespace (\"\" = release namespace)", - "required": [], - "title": "namespace", - "type": "string" - }, - "relabelings": { - "description": "Relabelings", - "items": { - "required": [] - }, - "required": [], - "title": "relabelings", - "type": "array" - }, - "scrapeTimeout": { - "default": "", - "description": "Scrape timeout", - "required": [], - "title": "scrapeTimeout", - "type": "string" - } - }, - "required": [ - "enabled", - "namespace", - "interval", - "scrapeTimeout", - "honorLabels", - "metricRelabelings", - "relabelings", - "jobLabel" - ], - "title": "serviceMonitor", - "type": "object" - } - }, - "required": [ - "serviceMonitor" - ], - "title": "metrics", - "type": "object" + "pullPolicy": { + "default": "IfNotPresent", + "description": "Authup image pull policy", + "required": [], + "title": "pullPolicy", + "type": "string" }, - "mfa": { - "additionalProperties": false, - "properties": { - "enabled": { - "default": false, - "description": "Enable multi-factor authentication (MFA_ENABLED)", - "required": [], - "title": "enabled", - "type": "boolean" - }, - "required": { - "default": false, - "description": "Require MFA for every user (MFA_REQUIRED; needs mfa.enabled)", - "required": [], - "title": "required", - "type": "boolean" - } + "pullSecrets": { + "description": "Authup image pull secrets", + "items": { + "required": [] }, - "required": [ - "enabled", - "required" - ], - "title": "mfa", - "type": "object" + "required": [], + "title": "pullSecrets", + "type": "array" }, - "migration": { - "additionalProperties": false, - "properties": { - "backoffLimit": { - "default": 3, - "description": "Job backoff limit", - "required": [], - "title": "backoffLimit", - "type": "integer" - }, - "enabled": { - "default": false, - "description": "Run `server/core migration run` as a pre-upgrade hook Job. Recommended\nfor multi-replica deployments (serializes DDL before pods roll). Fresh\ninstalls migrate at boot regardless.", - "required": [], - "title": "enabled", - "type": "boolean" - }, - "podAnnotations": { - "additionalProperties": true, - "description": "Job pod annotations", - "required": [], - "title": "podAnnotations" - }, - "resources": { - "additionalProperties": true, - "description": "Job resources ({} = server resources defaults)", - "required": [], - "title": "resources" - }, - "ttlSecondsAfterFinished": { - "default": 300, - "description": "Delete the Job this many seconds after it finishes (\"\" = keep)", - "required": [], - "title": "ttlSecondsAfterFinished", - "type": "integer" - } - }, - "required": [ - "enabled", - "backoffLimit", - "ttlSecondsAfterFinished" - ], - "title": "migration", - "type": "object" + "registry": { + "default": "docker.io", + "description": "Authup image registry", + "required": [], + "title": "registry", + "type": "string" }, - "networkPolicy": { - "additionalProperties": false, - "properties": { - "allowExternal": { - "default": true, - "description": "Allow ingress from anywhere. When false, only same-namespace pods,\nthe release's UI pods and the configured selectors may connect — add\nyour ingress controller via ingressNSMatchLabels/ingressPodMatchLabels", - "required": [], - "title": "allowExternal", - "type": "boolean" - }, - "allowExternalEgress": { - "default": true, - "description": "Allow all egress", - "required": [], - "title": "allowExternalEgress", - "type": "boolean" - }, - "enabled": { - "default": false, - "description": "Create a NetworkPolicy for server-core", - "required": [], - "title": "enabled", - "type": "boolean" - }, - "extraEgress": { - "description": "Extra egress rules", - "items": { - "required": [] - }, - "required": [], - "title": "extraEgress", - "type": "array" - }, - "extraIngress": { - "description": "Extra ingress rules", - "items": { - "required": [] - }, - "required": [], - "title": "extraIngress", - "type": "array" - }, - "ingressNSMatchLabels": { - "additionalProperties": true, - "description": "Namespace labels allowed to connect when allowExternal is false", - "required": [], - "title": "ingressNSMatchLabels" - }, - "ingressPodMatchLabels": { - "additionalProperties": true, - "description": "Pod labels allowed to connect when allowExternal is false", - "required": [], - "title": "ingressPodMatchLabels" - } - }, - "required": [ - "enabled", - "allowExternal", - "allowExternalEgress", - "extraIngress", - "extraEgress" - ], - "title": "networkPolicy", - "type": "object" + "repository": { + "default": "authup/authup", + "description": "Authup image repository (one image serves both services)", + "required": [], + "title": "repository", + "type": "string" }, - "nodeSelector": { + "tag": { + "default": "", + "description": "Authup image tag (defaults to the chart appVersion)", + "required": [], + "title": "tag", + "type": "string" + } + }, + "required": [ + "registry", + "repository", + "tag", + "digest", + "pullPolicy", + "pullSecrets" + ], + "title": "image", + "type": "object" + }, + "mysql": { + "additionalProperties": false, + "description": "# Built-in single-instance MySQL (docker-official image). Same caveats as postgresql.", + "properties": { + "affinity": { "additionalProperties": true, - "description": "Node selector", + "description": "MySQL affinity", "required": [], - "title": "nodeSelector" + "title": "affinity" }, - "pdb": { + "auth": { "additionalProperties": false, "properties": { - "create": { - "default": false, - "description": "Create a PodDisruptionBudget for server-core", + "database": { + "default": "authup", + "description": "MySQL database name (created on first boot)", "required": [], - "title": "create", - "type": "boolean" + "title": "database", + "type": "string" }, - "maxUnavailable": { + "password": { "default": "", - "description": "Maximum unavailable pods (defaults to 1 when both are empty)", + "description": "MySQL password (\"\" = generate once, keep across upgrades)", "required": [], - "title": "maxUnavailable", + "title": "password", "type": "string" }, - "minAvailable": { - "default": "", - "description": "Minimum available pods", + "username": { + "default": "authup", + "description": "MySQL application username", "required": [], - "title": "minAvailable", + "title": "username", "type": "string" } }, "required": [ - "create", - "minAvailable", - "maxUnavailable" + "username", + "password", + "database" ], - "title": "pdb", + "title": "auth", "type": "object" }, - "podAnnotations": { - "additionalProperties": true, - "description": "Pod annotations (tpl-rendered)", - "required": [], - "title": "podAnnotations" - }, - "podAntiAffinityPreset": { - "default": "soft", - "description": "Pod anti-affinity preset: soft, hard or \"\"", - "required": [], - "title": "podAntiAffinityPreset", - "type": "string" - }, - "podLabels": { - "additionalProperties": true, - "description": "Pod labels (tpl-rendered)", - "required": [], - "title": "podLabels" - }, - "podSecurityContext": { + "containerSecurityContext": { "additionalProperties": true, - "description": "Pod security context", + "description": "MySQL container security context", "properties": { + "allowPrivilegeEscalation": { + "default": false, + "required": [], + "title": "allowPrivilegeEscalation", + "type": "boolean" + }, + "capabilities": { + "additionalProperties": false, + "properties": { + "drop": { + "items": { + "anyOf": [ + { + "required": [], + "type": "string" + } + ], + "required": [] + }, + "required": [], + "title": "drop", + "type": "array" + } + }, + "required": [ + "drop" + ], + "title": "capabilities", + "type": "object" + }, "enabled": { "default": true, "required": [], "title": "enabled", "type": "boolean" }, - "fsGroup": { - "default": 1000, + "runAsGroup": { + "default": 999, "required": [], - "title": "fsGroup", + "title": "runAsGroup", + "type": "integer" + }, + "runAsNonRoot": { + "default": true, + "required": [], + "title": "runAsNonRoot", + "type": "boolean" + }, + "runAsUser": { + "default": 999, + "required": [], + "title": "runAsUser", "type": "integer" + }, + "seccompProfile": { + "additionalProperties": false, + "properties": { + "type": { + "default": "RuntimeDefault", + "required": [], + "title": "type", + "type": "string" + } + }, + "required": [ + "type" + ], + "title": "seccompProfile", + "type": "object" } }, "required": [ "enabled", - "fsGroup" + "runAsUser", + "runAsGroup", + "runAsNonRoot", + "allowPrivilegeEscalation", + "capabilities", + "seccompProfile" ], - "title": "podSecurityContext" + "title": "containerSecurityContext" }, - "priorityClassName": { - "default": "", - "description": "Priority class name", + "enabled": { + "default": false, + "description": "Deploy the built-in MySQL instance", "required": [], - "title": "priorityClassName", - "type": "string" + "title": "enabled", + "type": "boolean" }, - "provisioning": { + "extraEnvVars": { + "description": "Extra environment variables for the MySQL container", + "items": { + "required": [] + }, + "required": [], + "title": "extraEnvVars", + "type": "array" + }, + "image": { "additionalProperties": false, "properties": { - "enabled": { - "default": false, - "description": "Mount provisioning files consumed at boot (fail-closed: an invalid file aborts startup)", + "pullPolicy": { + "default": "IfNotPresent", + "description": "MySQL image pull policy", "required": [], - "title": "enabled", - "type": "boolean" + "title": "pullPolicy", + "type": "string" }, - "existingConfigMap": { - "default": "", - "description": "Existing ConfigMap with provisioning files (tpl-rendered)", + "registry": { + "default": "docker.io", + "description": "MySQL image registry", "required": [], - "title": "existingConfigMap", + "title": "registry", "type": "string" }, - "existingSecret": { - "default": "", - "description": "Existing Secret with provisioning files (tpl-rendered; takes\nprecedence — use for provisioning content that carries credentials)", + "repository": { + "default": "mysql", + "description": "MySQL image repository (docker official image)", "required": [], - "title": "existingSecret", + "title": "repository", "type": "string" }, - "files": { - "additionalProperties": true, - "description": "Map of filename -\u003e file content (tpl-rendered; the extension selects\nthe reader). Lands in a ConfigMap — credential-bearing provisioning\ncontent belongs in existingSecret instead", + "tag": { + "default": "8.4", + "description": "MySQL image tag", "required": [], - "title": "files" + "title": "tag", + "type": "string" } }, "required": [ - "enabled", - "existingConfigMap", - "existingSecret" + "registry", + "repository", + "tag", + "pullPolicy" ], - "title": "provisioning", + "title": "image", "type": "object" }, - "publicUrl": { - "default": "", - "description": "Public URL of server-core (PUBLIC_URL) — the OIDC issuer origin.\n\"\" = derived from server.ingress when enabled. Changing it later breaks\nenrolled WebAuthn credentials and the OIDC issuer.", + "nodeSelector": { + "additionalProperties": true, + "description": "MySQL node selector", "required": [], - "title": "publicUrl", - "type": "string" + "title": "nodeSelector" }, - "readinessProbe": { + "persistence": { "additionalProperties": false, "properties": { "enabled": { "default": true, - "description": "Enable the readiness probe (GET / status endpoint)", + "description": "Persist MySQL data", "required": [], "title": "enabled", "type": "boolean" }, - "failureThreshold": { - "default": 3, + "existingClaim": { + "default": "", + "description": "Use an existing PVC instead of creating one", "required": [], - "title": "failureThreshold", - "type": "integer" + "title": "existingClaim", + "type": "string" }, - "initialDelaySeconds": { - "default": 0, + "size": { + "default": "8Gi", + "description": "PVC size", "required": [], - "title": "initialDelaySeconds", - "type": "integer" + "title": "size", + "type": "string" }, - "periodSeconds": { - "default": 10, + "storageClass": { + "default": "", + "description": "PVC storage class (\"\" = cluster default; global.defaultStorageClass wins)", "required": [], - "title": "periodSeconds", - "type": "integer" - }, - "successThreshold": { - "default": 1, + "title": "storageClass", + "type": "string" + } + }, + "required": [ + "enabled", + "size", + "storageClass", + "existingClaim" + ], + "title": "persistence", + "type": "object" + }, + "podSecurityContext": { + "additionalProperties": true, + "description": "MySQL pod security context", + "properties": { + "enabled": { + "default": true, "required": [], - "title": "successThreshold", - "type": "integer" + "title": "enabled", + "type": "boolean" }, - "timeoutSeconds": { - "default": 5, + "fsGroup": { + "default": 999, "required": [], - "title": "timeoutSeconds", + "title": "fsGroup", "type": "integer" } }, "required": [ "enabled", - "initialDelaySeconds", - "periodSeconds", - "timeoutSeconds", - "failureThreshold", - "successThreshold" + "fsGroup" ], - "title": "readinessProbe", - "type": "object" - }, - "replicaCount": { - "default": 1, - "description": "Number of server-core replicas (values \u003e 1 REQUIRE a configured cache)", - "required": [], - "title": "replicaCount", - "type": "integer" + "title": "podSecurityContext" }, "resources": { "additionalProperties": true, - "description": "Server container resources", + "description": "MySQL container resources", "properties": { "limits": { "additionalProperties": true, "properties": { "memory": { - "default": "2Gi", + "default": "1Gi", "required": [], "title": "memory", "type": "string" @@ -2011,7 +1732,7 @@ "additionalProperties": true, "properties": { "cpu": { - "default": "250m", + "default": "100m", "required": [], "title": "cpu", "type": "string" @@ -2033,426 +1754,364 @@ "required": [], "title": "resources" }, - "revisionHistoryLimit": { - "default": 3, - "description": "Deployment revision history limit", + "tolerations": { + "description": "MySQL tolerations", + "items": { + "required": [] + }, + "required": [], + "title": "tolerations", + "type": "array" + } + }, + "required": [ + "enabled", + "image", + "auth", + "persistence", + "tolerations", + "extraEnvVars" + ], + "title": "mysql", + "type": "object" + }, + "nameOverride": { + "default": "", + "description": "Override the chart name", + "required": [], + "title": "nameOverride", + "type": "string" + }, + "namespaceOverride": { + "default": "", + "description": "Override the release namespace", + "required": [], + "title": "namespaceOverride", + "type": "string" + }, + "postgresql": { + "additionalProperties": false, + "description": "# Built-in single-instance PostgreSQL (docker-official image).\n# Convenience for dev / small production; for serious deployments bring an\n# external database or an operator such as CloudNativePG.", + "properties": { + "affinity": { + "additionalProperties": true, + "description": "PostgreSQL affinity", "required": [], - "title": "revisionHistoryLimit", - "type": "integer" + "title": "affinity" }, - "route": { + "auth": { "additionalProperties": false, "properties": { - "annotations": { - "additionalProperties": true, - "description": "HTTPRoute annotations", - "required": [], - "title": "annotations" - }, - "enabled": { - "default": false, - "description": "Create a Gateway API HTTPRoute for server-core", + "database": { + "default": "authup", + "description": "PostgreSQL database name (created on first boot)", "required": [], - "title": "enabled", - "type": "boolean" + "title": "database", + "type": "string" }, - "hostnames": { - "description": "Route hostnames ([] = derived from server.publicUrl / ingress hostname)", - "items": { - "required": [] - }, + "password": { + "default": "", + "description": "PostgreSQL password (\"\" = generate once, keep across upgrades)", "required": [], - "title": "hostnames", - "type": "array" + "title": "password", + "type": "string" }, - "parentRefs": { - "description": "Gateway parentRefs", - "items": { - "required": [] - }, + "username": { + "default": "authup", + "description": "PostgreSQL application username", "required": [], - "title": "parentRefs", - "type": "array" + "title": "username", + "type": "string" } }, "required": [ - "enabled", - "hostnames", - "parentRefs" + "username", + "password", + "database" ], - "title": "route", + "title": "auth", "type": "object" }, - "schedulerName": { - "default": "", - "description": "Scheduler name", - "required": [], - "title": "schedulerName", - "type": "string" - }, - "service": { - "additionalProperties": false, + "containerSecurityContext": { + "additionalProperties": true, + "description": "PostgreSQL container security context", "properties": { - "annotations": { - "additionalProperties": true, - "description": "Service annotations (tpl-rendered)", + "allowPrivilegeEscalation": { + "default": false, "required": [], - "title": "annotations" + "title": "allowPrivilegeEscalation", + "type": "boolean" }, - "clusterIP": { - "default": "", - "description": "Static cluster IP", - "required": [], - "title": "clusterIP", - "type": "string" + "capabilities": { + "additionalProperties": false, + "properties": { + "drop": { + "items": { + "anyOf": [ + { + "required": [], + "type": "string" + } + ], + "required": [] + }, + "required": [], + "title": "drop", + "type": "array" + } + }, + "required": [ + "drop" + ], + "title": "capabilities", + "type": "object" }, - "externalTrafficPolicy": { - "default": "Cluster", - "description": "External traffic policy", + "enabled": { + "default": true, "required": [], - "title": "externalTrafficPolicy", - "type": "string" + "title": "enabled", + "type": "boolean" }, - "extraPorts": { - "description": "Extra service ports", - "items": { - "required": [] - }, + "runAsGroup": { + "default": 999, "required": [], - "title": "extraPorts", - "type": "array" + "title": "runAsGroup", + "type": "integer" }, - "loadBalancerIP": { - "default": "", - "description": "LoadBalancer IP", + "runAsNonRoot": { + "default": true, "required": [], - "title": "loadBalancerIP", - "type": "string" + "title": "runAsNonRoot", + "type": "boolean" }, - "loadBalancerSourceRanges": { - "description": "LoadBalancer source ranges", - "items": { - "required": [] - }, + "runAsUser": { + "default": 999, "required": [], - "title": "loadBalancerSourceRanges", - "type": "array" + "title": "runAsUser", + "type": "integer" }, - "nodePorts": { + "seccompProfile": { "additionalProperties": false, "properties": { - "http": { - "default": "", - "description": "Node port (\"\" = auto-assign)", + "type": { + "default": "RuntimeDefault", "required": [], - "title": "http", + "title": "type", "type": "string" } }, "required": [ - "http" - ], - "title": "nodePorts", - "type": "object" - }, - "ports": { - "additionalProperties": false, - "properties": { - "http": { - "default": 3000, - "description": "Service HTTP port (the container port is fixed at 3000)", - "required": [], - "title": "http", - "type": "integer" - } - }, - "required": [ - "http" + "type" ], - "title": "ports", + "title": "seccompProfile", "type": "object" + } + }, + "required": [ + "enabled", + "runAsUser", + "runAsGroup", + "runAsNonRoot", + "allowPrivilegeEscalation", + "capabilities", + "seccompProfile" + ], + "title": "containerSecurityContext" + }, + "enabled": { + "default": true, + "description": "Deploy the built-in PostgreSQL instance", + "required": [], + "title": "enabled", + "type": "boolean" + }, + "extraEnvVars": { + "description": "Extra environment variables for the PostgreSQL container", + "items": { + "required": [] + }, + "required": [], + "title": "extraEnvVars", + "type": "array" + }, + "image": { + "additionalProperties": false, + "properties": { + "pullPolicy": { + "default": "IfNotPresent", + "description": "PostgreSQL image pull policy", + "required": [], + "title": "pullPolicy", + "type": "string" }, - "sessionAffinity": { - "default": "None", - "description": "Session affinity", + "registry": { + "default": "docker.io", + "description": "PostgreSQL image registry", "required": [], - "title": "sessionAffinity", + "title": "registry", "type": "string" }, - "sessionAffinityConfig": { - "additionalProperties": true, - "description": "Session affinity config", + "repository": { + "default": "postgres", + "description": "PostgreSQL image repository (docker official image)", "required": [], - "title": "sessionAffinityConfig" + "title": "repository", + "type": "string" }, - "type": { - "default": "ClusterIP", - "description": "Service type", + "tag": { + "default": "17", + "description": "PostgreSQL image tag", "required": [], - "title": "type", + "title": "tag", "type": "string" } }, "required": [ - "type", - "ports", - "nodePorts", - "clusterIP", - "loadBalancerIP", - "loadBalancerSourceRanges", - "externalTrafficPolicy", - "sessionAffinity", - "extraPorts" + "registry", + "repository", + "tag", + "pullPolicy" ], - "title": "service", + "title": "image", "type": "object" }, - "sidecars": { - "description": "Sidecar containers (tpl-rendered)", - "items": { - "required": [] - }, + "nodeSelector": { + "additionalProperties": true, + "description": "PostgreSQL node selector", "required": [], - "title": "sidecars", - "type": "array" + "title": "nodeSelector" }, - "startupProbe": { + "persistence": { "additionalProperties": false, "properties": { "enabled": { "default": true, - "description": "Enable the startup probe (first boot runs database creation, migrations and provisioning)", + "description": "Persist PostgreSQL data", "required": [], "title": "enabled", "type": "boolean" }, - "failureThreshold": { - "default": 60, - "required": [], - "title": "failureThreshold", - "type": "integer" - }, - "initialDelaySeconds": { - "default": 5, - "required": [], - "title": "initialDelaySeconds", - "type": "integer" - }, - "periodSeconds": { - "default": 5, + "existingClaim": { + "default": "", + "description": "Use an existing PVC instead of creating one", "required": [], - "title": "periodSeconds", - "type": "integer" + "title": "existingClaim", + "type": "string" }, - "successThreshold": { - "default": 1, + "size": { + "default": "8Gi", + "description": "PVC size", "required": [], - "title": "successThreshold", - "type": "integer" + "title": "size", + "type": "string" }, - "timeoutSeconds": { - "default": 5, + "storageClass": { + "default": "", + "description": "PVC storage class (\"\" = cluster default; global.defaultStorageClass wins)", "required": [], - "title": "timeoutSeconds", - "type": "integer" + "title": "storageClass", + "type": "string" } }, "required": [ "enabled", - "initialDelaySeconds", - "periodSeconds", - "timeoutSeconds", - "failureThreshold", - "successThreshold" + "size", + "storageClass", + "existingClaim" ], - "title": "startupProbe", + "title": "persistence", "type": "object" }, - "terminationGracePeriodSeconds": { - "default": 30, - "description": "Pod termination grace period (server-core tears down within ~10s after signal)", - "required": [], - "title": "terminationGracePeriodSeconds", - "type": "integer" - }, - "tolerations": { - "description": "Tolerations", - "items": { - "required": [] - }, - "required": [], - "title": "tolerations", - "type": "array" - }, - "topologySpreadConstraints": { - "description": "Topology spread constraints (a missing labelSelector is filled with the pod's selector labels)", - "items": { - "required": [] - }, - "required": [], - "title": "topologySpreadConstraints", - "type": "array" - }, - "trustProxy": { - "default": "1", - "description": "TRUST_PROXY setting. The chart defaults to one trusted hop (the ingress),\nnot authup's spoofable trust-everything default", - "required": [], - "title": "trustProxy", - "type": "string" - }, - "trustedOrigins": { - "description": "Additional trusted first-party app origins (TRUSTED_ORIGINS). Each listed\norigin can obtain full-permission tokens via the per-realm web client.\nList or comma-separated string; tpl-rendered.", - "items": { - "required": [] - }, - "required": [], - "title": "trustedOrigins", - "type": "array" - }, - "trustedOriginsAppendUI": { - "default": true, - "description": "Automatically append the client-web UI origin to TRUSTED_ORIGINS (removes the\nmost common dead-login misconfiguration)", - "required": [], - "title": "trustedOriginsAppendUI", - "type": "boolean" - }, - "updateStrategy": { + "podSecurityContext": { "additionalProperties": true, - "description": "Deployment update strategy", + "description": "PostgreSQL pod security context", "properties": { - "type": { - "default": "RollingUpdate", + "enabled": { + "default": true, "required": [], - "title": "type", - "type": "string" + "title": "enabled", + "type": "boolean" + }, + "fsGroup": { + "default": 999, + "required": [], + "title": "fsGroup", + "type": "integer" } }, "required": [ - "type" + "enabled", + "fsGroup" ], - "title": "updateStrategy" - } - }, - "required": [ - "enabled", - "replicaCount", - "publicUrl", - "trustedOrigins", - "trustedOriginsAppendUI", - "trustProxy", - "features", - "mfa", - "configuration", - "existingConfigmap", - "provisioning", - "migration", - "command", - "args", - "extraEnvVars", - "extraEnvVarsCM", - "extraEnvVarsSecret", - "extraVolumes", - "extraVolumeMounts", - "initContainers", - "sidecars", - "hostAliases", - "terminationGracePeriodSeconds", - "revisionHistoryLimit", - "disableRestartOnChanges", - "startupProbe", - "livenessProbe", - "readinessProbe", - "tolerations", - "podAntiAffinityPreset", - "topologySpreadConstraints", - "priorityClassName", - "schedulerName", - "service", - "ingress", - "route", - "metrics", - "pdb", - "autoscaling", - "networkPolicy" - ], - "title": "server", - "type": "object" - }, - "serviceAccount": { - "additionalProperties": false, - "description": "#", - "properties": { - "annotations": { - "additionalProperties": true, - "description": "ServiceAccount annotations (tpl-rendered)", - "required": [], - "title": "annotations" - }, - "automountServiceAccountToken": { - "default": false, - "description": "Automount the service account token", - "required": [], - "title": "automountServiceAccountToken", - "type": "boolean" - }, - "create": { - "default": true, - "description": "Create a ServiceAccount (shared by both services)", - "required": [], - "title": "create", - "type": "boolean" - }, - "name": { - "default": "", - "description": "ServiceAccount name (\"\" = generated from the fullname)", - "required": [], - "title": "name", - "type": "string" - } - }, - "required": [ - "create", - "name", - "automountServiceAccountToken" - ], - "title": "serviceAccount", - "type": "object" - }, - "smtp": { - "additionalProperties": false, - "description": "#", - "properties": { - "connectionString": { - "default": "", - "description": "SMTP connection string (smtp(s)://user:pass", - "required": [], - "title": "connectionString", - "type": "string" + "title": "podSecurityContext" }, - "existingSecret": { - "default": "", - "description": "Existing secret holding the SMTP connection string (tpl-rendered)", + "resources": { + "additionalProperties": true, + "description": "PostgreSQL container resources", + "properties": { + "limits": { + "additionalProperties": true, + "properties": { + "memory": { + "default": "1Gi", + "required": [], + "title": "memory", + "type": "string" + } + }, + "required": [ + "memory" + ], + "title": "limits" + }, + "requests": { + "additionalProperties": true, + "properties": { + "cpu": { + "default": "100m", + "required": [], + "title": "cpu", + "type": "string" + }, + "memory": { + "default": "256Mi", + "required": [], + "title": "memory", + "type": "string" + } + }, + "required": [ + "cpu", + "memory" + ], + "title": "requests" + } + }, "required": [], - "title": "existingSecret", - "type": "string" + "title": "resources" }, - "existingSecretKey": { - "default": "smtp-connection-string", - "description": "Key inside smtp.existingSecret holding the connection string", + "tolerations": { + "description": "PostgreSQL tolerations", + "items": { + "required": [] + }, "required": [], - "title": "existingSecretKey", - "type": "string" + "title": "tolerations", + "type": "array" } }, "required": [ - "connectionString", - "existingSecret", - "existingSecretKey" + "enabled", + "image", + "auth", + "persistence", + "tolerations", + "extraEnvVars" ], - "title": "smtp", + "title": "postgresql", "type": "object" }, - "ui": { + "server": { "additionalProperties": false, "description": "#", "properties": { @@ -2462,13 +2121,6 @@ "required": [], "title": "affinity" }, - "apiUrl": { - "default": "", - "description": "Browser-facing server-core URL (NUXT_PUBLIC_API_URL). \"\" = the server public URL.\nMust be reachable from the user's browser, never a cluster-internal DNS name", - "required": [], - "title": "apiUrl", - "type": "string" - }, "args": { "description": "Override the container args", "items": { @@ -2486,7 +2138,7 @@ "properties": { "enabled": { "default": false, - "description": "Enable HPA for the UI", + "description": "Enable HPA for server-core (requires a configured cache)", "required": [], "title": "enabled", "type": "boolean" @@ -2548,13 +2200,20 @@ }, "config": { "additionalProperties": true, - "description": "Extra environment variables rendered literally into the env ConfigMap", + "description": "Extra environment variables rendered literally into the env ConfigMap\n(map of NAME: value) for options without first-class values", "required": [], "title": "config" }, + "configuration": { + "default": "", + "description": "Content of an authup.server.core.conf mounted into the working directory\nfor file-only options (middleware objects, per-field SMTP, CORS allowlist).\nEnvironment variables always win over file values.", + "required": [], + "title": "configuration", + "type": "string" + }, "containerSecurityContext": { "additionalProperties": true, - "description": "Container security context (same root-image caveat as the server)", + "description": "Container security context. The upstream image runs as root and needs a\nwritable npm cache; the chart mounts emptyDirs at /usr/src/app/writable and\n/tmp to keep readOnlyRootFilesystem viable.", "properties": { "allowPrivilegeEscalation": { "default": false, @@ -2652,26 +2311,33 @@ }, "customStartupProbe": { "additionalProperties": true, - "description": "Custom startup probe", + "description": "Custom startup probe overriding the structured one", "required": [], "title": "customStartupProbe" }, "disableRestartOnChanges": { "default": false, - "description": "Disable the checksum annotations that roll pods on config changes", + "description": "Disable the checksum annotations that roll pods on config/secret changes", "required": [], "title": "disableRestartOnChanges", "type": "boolean" }, "enabled": { "default": true, - "description": "Deploy the client-web admin UI (false = headless IdP)", + "description": "Deploy the server-core service", "required": [], "title": "enabled", "type": "boolean" }, + "existingConfigmap": { + "default": "", + "description": "Existing ConfigMap holding authup.server.core.conf (tpl-rendered)", + "required": [], + "title": "existingConfigmap", + "type": "string" + }, "extraEnvVars": { - "description": "Extra environment variables for the UI container", + "description": "Extra environment variables for the server container", "items": { "required": [] }, @@ -2711,6 +2377,39 @@ "title": "extraVolumes", "type": "array" }, + "features": { + "additionalProperties": false, + "properties": { + "emailVerification": { + "default": false, + "description": "Enable email verification (EMAIL_VERIFICATION_ENABLED; requires SMTP)", + "required": [], + "title": "emailVerification", + "type": "boolean" + }, + "passwordRecovery": { + "default": false, + "description": "Enable password recovery (PASSWORD_RECOVERY_ENABLED; requires SMTP)", + "required": [], + "title": "passwordRecovery", + "type": "boolean" + }, + "registration": { + "default": false, + "description": "Enable self-service user registration (REGISTRATION_ENABLED)", + "required": [], + "title": "registration", + "type": "boolean" + } + }, + "required": [ + "registration", + "passwordRecovery", + "emailVerification" + ], + "title": "features", + "type": "object" + }, "hostAliases": { "description": "Pod host aliases", "items": { @@ -2725,7 +2424,7 @@ "properties": { "annotations": { "additionalProperties": true, - "description": "Ingress annotations (tpl-rendered)", + "description": "Ingress annotations (tpl-rendered). Token responses are large; with\ningress-nginx consider proxy-buffer-size 16k+.", "required": [], "title": "annotations" }, @@ -2738,7 +2437,7 @@ }, "enabled": { "default": false, - "description": "Enable ingress for the UI", + "description": "Enable ingress for server-core. NOTE: this also exposes the\nUNAUTHENTICATED /metrics endpoint publicly — block it at the ingress\ncontroller or disable it via server.configuration\n(\"middlewarePrometheus: false\") when it is not scraped", "required": [], "title": "enabled", "type": "boolean" @@ -2781,7 +2480,7 @@ }, "hostname": { "default": "", - "description": "Ingress hostname (tpl-rendered); also drives the derived UI public URL", + "description": "Ingress hostname (tpl-rendered); also drives the derived PUBLIC_URL", "required": [], "title": "hostname", "type": "string" @@ -2809,7 +2508,7 @@ }, "tls": { "default": false, - "description": "Enable TLS for the hostname", + "description": "Enable TLS for the hostname (secret \u003chostname\u003e-tls unless extraTls overrides)", "required": [], "title": "tls", "type": "boolean" @@ -2840,69 +2539,226 @@ "title": "initContainers", "type": "array" }, - "internalApiUrl": { - "default": "", - "description": "Server-side (SSR) API URL override (NUXT_API_URL), e.g. the in-cluster\nservice URL to keep SSR traffic off the ingress", - "required": [], - "title": "internalApiUrl", - "type": "string" - }, "lifecycleHooks": { "additionalProperties": true, "description": "Container lifecycle hooks", "required": [], "title": "lifecycleHooks" }, - "livenessProbe": { + "livenessProbe": { + "additionalProperties": false, + "properties": { + "enabled": { + "default": true, + "description": "Enable the liveness probe (GET / status endpoint)", + "required": [], + "title": "enabled", + "type": "boolean" + }, + "failureThreshold": { + "default": 3, + "required": [], + "title": "failureThreshold", + "type": "integer" + }, + "initialDelaySeconds": { + "default": 0, + "required": [], + "title": "initialDelaySeconds", + "type": "integer" + }, + "periodSeconds": { + "default": 30, + "required": [], + "title": "periodSeconds", + "type": "integer" + }, + "successThreshold": { + "default": 1, + "required": [], + "title": "successThreshold", + "type": "integer" + }, + "timeoutSeconds": { + "default": 5, + "required": [], + "title": "timeoutSeconds", + "type": "integer" + } + }, + "required": [ + "enabled", + "initialDelaySeconds", + "periodSeconds", + "timeoutSeconds", + "failureThreshold", + "successThreshold" + ], + "title": "livenessProbe", + "type": "object" + }, + "metrics": { + "additionalProperties": false, + "properties": { + "serviceMonitor": { + "additionalProperties": false, + "description": "The /metrics endpoint is UNAUTHENTICATED; keep it off the public ingress", + "properties": { + "enabled": { + "default": false, + "description": "Create a prometheus-operator ServiceMonitor scraping /metrics", + "required": [], + "title": "enabled", + "type": "boolean" + }, + "honorLabels": { + "default": false, + "description": "Honor labels", + "required": [], + "title": "honorLabels", + "type": "boolean" + }, + "interval": { + "default": "30s", + "description": "Scrape interval", + "required": [], + "title": "interval", + "type": "string" + }, + "jobLabel": { + "default": "", + "description": "Job label", + "required": [], + "title": "jobLabel", + "type": "string" + }, + "labels": { + "additionalProperties": true, + "description": "ServiceMonitor labels", + "required": [], + "title": "labels" + }, + "metricRelabelings": { + "description": "Metric relabelings", + "items": { + "required": [] + }, + "required": [], + "title": "metricRelabelings", + "type": "array" + }, + "namespace": { + "default": "", + "description": "ServiceMonitor namespace (\"\" = release namespace)", + "required": [], + "title": "namespace", + "type": "string" + }, + "relabelings": { + "description": "Relabelings", + "items": { + "required": [] + }, + "required": [], + "title": "relabelings", + "type": "array" + }, + "scrapeTimeout": { + "default": "", + "description": "Scrape timeout", + "required": [], + "title": "scrapeTimeout", + "type": "string" + } + }, + "required": [ + "enabled", + "namespace", + "interval", + "scrapeTimeout", + "honorLabels", + "metricRelabelings", + "relabelings", + "jobLabel" + ], + "title": "serviceMonitor", + "type": "object" + } + }, + "required": [ + "serviceMonitor" + ], + "title": "metrics", + "type": "object" + }, + "mfa": { "additionalProperties": false, "properties": { "enabled": { - "default": true, - "description": "Enable the liveness probe", + "default": false, + "description": "Enable multi-factor authentication (MFA_ENABLED)", "required": [], "title": "enabled", "type": "boolean" }, - "failureThreshold": { + "required": { + "default": false, + "description": "Require MFA for every user (MFA_REQUIRED; needs mfa.enabled)", + "required": [], + "title": "required", + "type": "boolean" + } + }, + "required": [ + "enabled", + "required" + ], + "title": "mfa", + "type": "object" + }, + "migration": { + "additionalProperties": false, + "properties": { + "backoffLimit": { "default": 3, + "description": "Job backoff limit", "required": [], - "title": "failureThreshold", + "title": "backoffLimit", "type": "integer" }, - "initialDelaySeconds": { - "default": 0, + "enabled": { + "default": false, + "description": "Run `server/core migration run` as a pre-upgrade hook Job. Recommended\nfor multi-replica deployments (serializes DDL before pods roll). Fresh\ninstalls migrate at boot regardless.", "required": [], - "title": "initialDelaySeconds", - "type": "integer" + "title": "enabled", + "type": "boolean" }, - "periodSeconds": { - "default": 30, + "podAnnotations": { + "additionalProperties": true, + "description": "Job pod annotations", "required": [], - "title": "periodSeconds", - "type": "integer" + "title": "podAnnotations" }, - "successThreshold": { - "default": 1, + "resources": { + "additionalProperties": true, + "description": "Job resources ({} = server resources defaults)", "required": [], - "title": "successThreshold", - "type": "integer" + "title": "resources" }, - "timeoutSeconds": { - "default": 5, + "ttlSecondsAfterFinished": { + "default": 300, + "description": "Delete the Job this many seconds after it finishes (\"\" = keep)", "required": [], - "title": "timeoutSeconds", + "title": "ttlSecondsAfterFinished", "type": "integer" } }, "required": [ "enabled", - "initialDelaySeconds", - "periodSeconds", - "timeoutSeconds", - "failureThreshold", - "successThreshold" + "backoffLimit", + "ttlSecondsAfterFinished" ], - "title": "livenessProbe", + "title": "migration", "type": "object" }, "networkPolicy": { @@ -2910,7 +2766,7 @@ "properties": { "allowExternal": { "default": true, - "description": "Allow ingress from anywhere", + "description": "Allow ingress from anywhere. When false, only same-namespace pods,\nthe release's UI pods and the configured selectors may connect — add\nyour ingress controller via ingressNSMatchLabels/ingressPodMatchLabels", "required": [], "title": "allowExternal", "type": "boolean" @@ -2924,7 +2780,7 @@ }, "enabled": { "default": false, - "description": "Create a NetworkPolicy for the UI", + "description": "Create a NetworkPolicy for server-core", "required": [], "title": "enabled", "type": "boolean" @@ -2981,7 +2837,7 @@ "properties": { "create": { "default": false, - "description": "Create a PodDisruptionBudget for the UI", + "description": "Create a PodDisruptionBudget for server-core", "required": [], "title": "create", "type": "boolean" @@ -3058,9 +2914,48 @@ "title": "priorityClassName", "type": "string" }, + "provisioning": { + "additionalProperties": false, + "properties": { + "enabled": { + "default": false, + "description": "Mount provisioning files consumed at boot (fail-closed: an invalid file aborts startup)", + "required": [], + "title": "enabled", + "type": "boolean" + }, + "existingConfigMap": { + "default": "", + "description": "Existing ConfigMap with provisioning files (tpl-rendered)", + "required": [], + "title": "existingConfigMap", + "type": "string" + }, + "existingSecret": { + "default": "", + "description": "Existing Secret with provisioning files (tpl-rendered; takes\nprecedence — use for provisioning content that carries credentials)", + "required": [], + "title": "existingSecret", + "type": "string" + }, + "files": { + "additionalProperties": true, + "description": "Map of filename -\u003e file content (tpl-rendered; the extension selects\nthe reader). Lands in a ConfigMap — credential-bearing provisioning\ncontent belongs in existingSecret instead", + "required": [], + "title": "files" + } + }, + "required": [ + "enabled", + "existingConfigMap", + "existingSecret" + ], + "title": "provisioning", + "type": "object" + }, "publicUrl": { "default": "", - "description": "Public URL of the UI (NUXT_PUBLIC_PUBLIC_URL). \"\" = derived from ui.ingress", + "description": "Public URL of server-core (PUBLIC_URL) — the OIDC issuer origin.\n\"\" = derived from server.ingress when enabled. Changing it later breaks\nenrolled WebAuthn credentials and the OIDC issuer.", "required": [], "title": "publicUrl", "type": "string" @@ -3070,7 +2965,7 @@ "properties": { "enabled": { "default": true, - "description": "Enable the readiness probe", + "description": "Enable the readiness probe (GET / status endpoint)", "required": [], "title": "enabled", "type": "boolean" @@ -3119,20 +3014,20 @@ }, "replicaCount": { "default": 1, - "description": "Number of UI replicas (fully stateless, scale freely)", + "description": "Number of server-core replicas (values \u003e 1 REQUIRE a configured cache)", "required": [], "title": "replicaCount", "type": "integer" }, "resources": { "additionalProperties": true, - "description": "UI container resources", + "description": "Server container resources", "properties": { "limits": { "additionalProperties": true, "properties": { "memory": { - "default": "512Mi", + "default": "2Gi", "required": [], "title": "memory", "type": "string" @@ -3147,13 +3042,13 @@ "additionalProperties": true, "properties": { "cpu": { - "default": "100m", + "default": "250m", "required": [], "title": "cpu", "type": "string" }, "memory": { - "default": "256Mi", + "default": "512Mi", "required": [], "title": "memory", "type": "string" @@ -3187,13 +3082,13 @@ }, "enabled": { "default": false, - "description": "Create a Gateway API HTTPRoute for the UI", + "description": "Create a Gateway API HTTPRoute for server-core", "required": [], "title": "enabled", "type": "boolean" }, "hostnames": { - "description": "Route hostnames ([] = derived from ui.publicUrl / ingress hostname)", + "description": "Route hostnames ([] = derived from server.publicUrl / ingress hostname)", "items": { "required": [] }, @@ -3357,13 +3252,13 @@ "properties": { "enabled": { "default": true, - "description": "Enable the startup probe", + "description": "Enable the startup probe (first boot runs database creation, migrations and provisioning)", "required": [], "title": "enabled", "type": "boolean" }, "failureThreshold": { - "default": 24, + "default": 60, "required": [], "title": "failureThreshold", "type": "integer" @@ -3406,7 +3301,7 @@ }, "terminationGracePeriodSeconds": { "default": 30, - "description": "Pod termination grace period", + "description": "Pod termination grace period (server-core tears down within ~10s after signal)", "required": [], "title": "terminationGracePeriodSeconds", "type": "integer" @@ -3421,7 +3316,7 @@ "type": "array" }, "topologySpreadConstraints": { - "description": "Topology spread constraints", + "description": "Topology spread constraints (a missing labelSelector is filled with the pod's selector labels)", "items": { "required": [] }, @@ -3429,6 +3324,29 @@ "title": "topologySpreadConstraints", "type": "array" }, + "trustProxy": { + "default": "1", + "description": "TRUST_PROXY setting. The chart defaults to one trusted hop (the ingress),\nnot authup's spoofable trust-everything default", + "required": [], + "title": "trustProxy", + "type": "string" + }, + "trustedOrigins": { + "description": "Additional trusted first-party app origins (TRUSTED_ORIGINS). Each listed\norigin can obtain full-permission tokens via the per-realm web client.\nList or comma-separated string; tpl-rendered.", + "items": { + "required": [] + }, + "required": [], + "title": "trustedOrigins", + "type": "array" + }, + "trustedOriginsAppendAdminConsole": { + "default": true, + "description": "Automatically append the client-admin-console UI origin to TRUSTED_ORIGINS (removes the\nmost common dead-login misconfiguration)", + "required": [], + "title": "trustedOriginsAppendAdminConsole", + "type": "boolean" + }, "updateStrategy": { "additionalProperties": true, "description": "Deployment update strategy", @@ -3450,8 +3368,15 @@ "enabled", "replicaCount", "publicUrl", - "apiUrl", - "internalApiUrl", + "trustedOrigins", + "trustedOriginsAppendAdminConsole", + "trustProxy", + "features", + "mfa", + "configuration", + "existingConfigmap", + "provisioning", + "migration", "command", "args", "extraEnvVars", @@ -3476,11 +3401,86 @@ "service", "ingress", "route", + "metrics", "pdb", "autoscaling", "networkPolicy" ], - "title": "ui", + "title": "server", + "type": "object" + }, + "serviceAccount": { + "additionalProperties": false, + "description": "#", + "properties": { + "annotations": { + "additionalProperties": true, + "description": "ServiceAccount annotations (tpl-rendered)", + "required": [], + "title": "annotations" + }, + "automountServiceAccountToken": { + "default": false, + "description": "Automount the service account token", + "required": [], + "title": "automountServiceAccountToken", + "type": "boolean" + }, + "create": { + "default": true, + "description": "Create a ServiceAccount (shared by both services)", + "required": [], + "title": "create", + "type": "boolean" + }, + "name": { + "default": "", + "description": "ServiceAccount name (\"\" = generated from the fullname)", + "required": [], + "title": "name", + "type": "string" + } + }, + "required": [ + "create", + "name", + "automountServiceAccountToken" + ], + "title": "serviceAccount", + "type": "object" + }, + "smtp": { + "additionalProperties": false, + "description": "#", + "properties": { + "connectionString": { + "default": "", + "description": "SMTP connection string (smtp(s)://user:pass", + "required": [], + "title": "connectionString", + "type": "string" + }, + "existingSecret": { + "default": "", + "description": "Existing secret holding the SMTP connection string (tpl-rendered)", + "required": [], + "title": "existingSecret", + "type": "string" + }, + "existingSecretKey": { + "default": "smtp-connection-string", + "description": "Key inside smtp.existingSecret holding the connection string", + "required": [], + "title": "existingSecretKey", + "type": "string" + } + }, + "required": [ + "connectionString", + "existingSecret", + "existingSecretKey" + ], + "title": "smtp", "type": "object" }, "useHelmHooks": { @@ -3797,7 +3797,7 @@ "externalRedis", "smtp", "server", - "ui", + "adminConsole", "serviceAccount" ], "type": "object" diff --git a/charts/authup/values.yaml b/charts/authup/values.yaml index c40d510..2cab0e8 100644 --- a/charts/authup/values.yaml +++ b/charts/authup/values.yaml @@ -406,9 +406,9 @@ server: # origin can obtain full-permission tokens via the per-realm web client. # List or comma-separated string; tpl-rendered. trustedOrigins: [] - # -- Automatically append the client-web UI origin to TRUSTED_ORIGINS (removes the + # -- Automatically append the client-admin-console UI origin to TRUSTED_ORIGINS (removes the # most common dead-login misconfiguration) - trustedOriginsAppendUI: true + trustedOriginsAppendAdminConsole: true # -- TRUST_PROXY setting. The chart defaults to one trusted hop (the ingress), # not authup's spoofable trust-everything default trustProxy: "1" @@ -757,14 +757,14 @@ server: # -- Extra egress rules extraEgress: [] -## @section client-web (admin UI) +## @section client-admin-console (admin UI) -ui: - # -- Deploy the client-web admin UI (false = headless IdP) +adminConsole: + # -- Deploy the client-admin-console admin UI (false = headless IdP) enabled: true # -- Number of UI replicas (fully stateless, scale freely) replicaCount: 1 - # -- Public URL of the UI (NUXT_PUBLIC_PUBLIC_URL). "" = derived from ui.ingress + # -- Public URL of the UI (NUXT_PUBLIC_PUBLIC_URL). "" = derived from adminConsole.ingress publicUrl: "" # -- Browser-facing server-core URL (NUXT_PUBLIC_API_URL). "" = the server public URL. # Must be reachable from the user's browser, never a cluster-internal DNS name @@ -982,7 +982,7 @@ ui: route: # -- Create a Gateway API HTTPRoute for the UI enabled: false - # -- Route hostnames ([] = derived from ui.publicUrl / ingress hostname) + # -- Route hostnames ([] = derived from adminConsole.publicUrl / ingress hostname) hostnames: [] # -- Gateway parentRefs parentRefs: []