diff --git a/charts/norish/Chart.yaml b/charts/norish/Chart.yaml index e6eab60..85dca7e 100644 --- a/charts/norish/Chart.yaml +++ b/charts/norish/Chart.yaml @@ -2,8 +2,8 @@ apiVersion: v2 name: norish description: Norish helm chart for Kubernetes - A recipe management and meal planning application type: application -version: 0.0.5 -appVersion: "v0.15.4-beta" +version: 0.0.6 +appVersion: "v0.20.0-beta" maintainers: - name: Richard Tomik email: no@m.com @@ -14,4 +14,4 @@ keywords: - norish home: https://github.com/rtomik/helm-charts sources: - - https://github.com/norishapp/norish + - https://github.com/norish-recipes/norish diff --git a/charts/norish/readme.md b/charts/norish/readme.md index 334e65f..3a00cf0 100644 --- a/charts/norish/readme.md +++ b/charts/norish/readme.md @@ -1,6 +1,6 @@ # Norish Helm Chart -A Helm chart for deploying [Norish](https://github.com/norishapp/norish), a recipe management and meal planning application, on Kubernetes. +A Helm chart for deploying [Norish](https://github.com/norish-recipes/norish), a recipe management and meal planning application, on Kubernetes. ## Introduction @@ -112,6 +112,24 @@ config: passwordAuthEnabled: "true" ``` +Set the redirect URI in your provider to: +`https://norish.example.com/api/auth/oauth2/callback/oidc` + +Optionally map OIDC group claims to the admin role and to households: + +```yaml +config: + auth: + oidc: + enabled: true + claimMapping: + enabled: true + scopes: "groups" + groupsClaim: "groups" + adminGroup: "norish_admin" + householdGroupPrefix: "norish_household_" +``` + ### GitHub OAuth 1. Create a GitHub OAuth App at https://github.com/settings/developers @@ -148,6 +166,30 @@ persistence: existingClaim: "my-existing-pvc" ``` +### Adopting an Existing (non-Helm) Norish Install + +Resource names are derived from the chart name, not the release name, so this chart always +creates `norish-secret` and `norish-uploads`. If you already run Norish from hand-written +manifests using those same names, point the chart at the existing objects instead of +letting it template new ones: + +```yaml +config: + masterKey: + existingSecret: "norish-secret" # reuse the existing key + secretKey: "master-key" +persistence: + existingClaim: "norish-uploads" +redis: + existingSecret: "norish-secret" + urlKey: "redis-url" +``` + +⚠️ **Never let a new `MASTER_KEY` be generated for an existing database.** The key derives +the encryption keys, so replacing it makes every previously encrypted value unreadable. +Helm refuses to adopt resources it does not own, but a GitOps tool configured to replace or +prune resources will not stop you — set `existingSecret` before the first sync. + ## Parameters ### Global Parameters @@ -162,7 +204,7 @@ persistence: | Name | Description | Default | |------|-------------|---------| | `image.repository` | Norish image repository | `norishapp/norish` | -| `image.tag` | Image tag | `v0.15.4-beta` | +| `image.tag` | Image tag | `v0.20.0-beta` | | `image.pullPolicy` | Image pull policy | `IfNotPresent` | | `imagePullSecrets` | Image pull secrets | `[]` | @@ -245,6 +287,11 @@ persistence: | `config.logLevel` | Log level (`trace`, `debug`, `info`, `warn`, `error`, `fatal`) | `""` | | `config.trustedOrigins` | Additional trusted origins (comma-separated) | `""` | | `config.passwordAuthEnabled` | Enable/disable password auth | `""` | +| `config.enableRegistration` | Allow self-registration of new users | `""` | +| `config.uploadsDir` | Uploads directory / volume mount path (`UPLOADS_DIR`) | `/app/uploads` | +| `config.parserApiTimeoutMs` | Recipe parser API timeout in ms | `""` | +| `config.defaultLocale` | Instance default locale | `""` | +| `config.enabledLocales` | Comma-separated list of enabled locales (empty = all) | `""` | | `config.extraEnv` | Extra environment variables | `[]` | ### Master Key Configuration (Required) @@ -270,6 +317,11 @@ Generate with: `openssl rand -base64 32` | `config.auth.oidc.existingSecret` | Existing secret name | `""` | | `config.auth.oidc.clientIdKey` | Key for client ID in secret | `oidc-client-id` | | `config.auth.oidc.clientSecretKey` | Key for client secret in secret | `oidc-client-secret` | +| `config.auth.oidc.claimMapping.enabled` | Assign admin role / households from OIDC claims | `false` | +| `config.auth.oidc.claimMapping.scopes` | Extra scopes to request (comma-separated) | `""` | +| `config.auth.oidc.claimMapping.groupsClaim` | Claim containing user groups | `groups` | +| `config.auth.oidc.claimMapping.adminGroup` | Group granting the server admin role | `norish_admin` | +| `config.auth.oidc.claimMapping.householdGroupPrefix` | Prefix for household groups | `norish_household_` | ### GitHub OAuth @@ -316,13 +368,16 @@ Generate with: `openssl rand -base64 32` | Name | Description | Default | |------|-------------|---------| | `probes.startup.enabled` | Enable startup probe | `true` | +| `probes.startup.path` | Startup probe path | `/api/v1/health` | | `probes.startup.initialDelaySeconds` | Startup initial delay | `10` | | `probes.startup.periodSeconds` | Startup period | `10` | | `probes.startup.failureThreshold` | Startup failure threshold | `30` | | `probes.liveness.enabled` | Enable liveness probe | `true` | +| `probes.liveness.path` | Liveness probe path (see [Upgrading](#upgrading) for why this is not the health endpoint) | `/` | | `probes.liveness.initialDelaySeconds` | Liveness initial delay | `30` | | `probes.liveness.periodSeconds` | Liveness period | `10` | | `probes.readiness.enabled` | Enable readiness probe | `true` | +| `probes.readiness.path` | Readiness probe path | `/api/v1/health` | | `probes.readiness.initialDelaySeconds` | Readiness initial delay | `5` | | `probes.readiness.periodSeconds` | Readiness period | `5` | @@ -336,13 +391,102 @@ Generate with: `openssl rand -base64 32` No configuration changes required. Redis, PostgreSQL, and Chrome headless are already configured. Back up your database before upgrading as a precaution. +### From chart 0.0.5 to chart 0.0.6 (app v0.20.0-beta) + +⚠️ **Back up your database and your uploads volume before upgrading.** + +**Which app version were you on?** Chart 0.0.5 declared `appVersion: v0.15.4-beta` but +shipped `image.tag: v0.16.2-beta` in values.yaml, and the tag always wins. So unless you +pinned `image.tag` yourself, you were already running **v0.16.2-beta** and the two +v0.16.x data-loss items below have already happened to you — skip them. Chart 0.0.6 fixes +that mismatch: both `appVersion` and `image.tag` are now `v0.20.0-beta`. + +**v0.16.0-beta — data loss (calendar) — only if you pinned `image.tag` to v0.15.x or older** +All calendar data is permanently deleted on upgrade. The calendar was rebuilt on a new +database schema and upstream provides no migration path. Recipes, groceries and planning +data outside the calendar are unaffected. + +**v0.16.1-beta — data loss (custom units) — only if you pinned `image.tag` to v0.16.0 or older** +Custom UOM (unit of measure) data is wiped as part of the move to a locale-aware schema. +Custom units have to be re-created after the upgrade. + +The remaining items apply to everyone upgrading from chart 0.0.5. + +**v0.17.0-beta — image restructure** +Upstream migrated to a pnpm/Turborepo monorepo. The Docker image, its internal paths and +the package layout all changed. The image name is unchanged (`norishapp/norish`), and this +chart needs no value changes for it, but the release is explicitly flagged as +"back up your data before upgrading" by upstream. + +**v0.18.0-beta — breaking: health endpoint moved** +The previous `/api/health` endpoint was removed; the endpoint is now `/api/v1/health`. +Verified on both versions: on v0.17.3-beta `/api/health` returns `{"status":"ok"}`, on +v0.20.0-beta it falls through to the auth redirect. Note that a removed API path returns a +**307 redirect**, not a 404 — so an HTTP probe or uptime check still pointing at +`/api/health` reports *success* while checking nothing at all. Repoint it explicitly. + +This chart's probe defaults changed accordingly: + +| Value | Old default | New default | +|-------|-------------|-------------| +| `probes.startup.path` | `/` | `/api/v1/health` | +| `probes.readiness.path` | `/` | `/api/v1/health` | +| `probes.liveness.path` | `/` | `/` (unchanged — see below) | + +If you pinned these paths in your own values, update them. Any external uptime monitor or +ingress health check pointing at the old endpoint must be updated as well. + +**Why liveness deliberately does not use the health endpoint.** Since v0.18.1-beta the +endpoint also reports database health, and it returns **503** when PostgreSQL is +unreachable — verified on a test cluster by scaling the database to zero. With liveness +pointed at it, the sequence is: + +1. Six consecutive 503s fail the liveness probe and the kubelet restarts the container. +2. On restart the app runs its migrations, cannot reach the database, and exits 1. +3. The pod enters `CrashLoopBackOff`, so it stays down for the backoff interval even + after the database comes back. + +A short database blip therefore becomes a multi-minute outage. With liveness on `/` (which +returns a 307 redirect — a probe success) a running pod rides out a DB blip: readiness +still fails, so the pod is removed from the Service endpoints, and it serves again as soon +as the database returns, with no restart. + +Note that the app cannot start at all without a reachable database, by design — it runs +migrations at boot and exits on failure. Liveness on the app root does not hide that; it +only avoids restarting a process that is alive and would otherwise recover on its own. + +**v0.18.0-beta — recipe import pipeline** +Imports moved to the `recipe-scrapers` Python package. The Chrome headless sidecar is +still required (upstream still ships it and still lists `CHROME_WS_ENDPOINT` as a core +required setting), so leave `chrome.enabled: true`. Import timeouts can be tuned with the +new `config.parserApiTimeoutMs`. + +**Migration path — tested** +The v0.17.3-beta → v0.20.0-beta upgrade was verified on a Kubernetes test cluster against +a schema created by v0.17.3-beta: migrations applied automatically at boot (31 → 40 +applied migrations, 29 → 34 tables), with no manual steps and no errors. The app applies +migrations itself on startup; there is nothing to run by hand. + +**v0.19.0-beta / v0.20.0-beta — no configuration changes** +Web app refresh (HeroUI v3, home screen, cooking mode), offline support, recipe +provenance and AI workflow improvements. Nothing to change in this chart. + +**New chart values in this release** (all optional, all default to the previous behaviour): +`config.enableRegistration`, `config.uploadsDir`, `config.parserApiTimeoutMs`, +`config.defaultLocale`, `config.enabledLocales` and `config.auth.oidc.claimMapping.*`. + +The Chrome sidecar also now passes `--disable-features=dbus`, matching the upstream Docker +Compose example. + ## Troubleshooting - **Master Key Not Set**: Generate with `openssl rand -base64 32` - **Login Failures**: Password auth is enabled by default when no OAuth/OIDC is configured. Verify callback URLs match your ingress hostname. - **Database Connection Failed**: Verify host, credentials, and that the database exists. - **Chrome Headless Issues**: Chrome requires `SYS_ADMIN` capability and 256Mi-512Mi memory. Check logs with `kubectl logs -l app.kubernetes.io/name=norish -c chrome-headless` -- **Recipe Parsing Failures**: Ensure Chrome is running. `CHROME_WS_ENDPOINT` is automatically configured by the chart. +- **Recipe Parsing Failures**: Ensure Chrome is running. `CHROME_WS_ENDPOINT` is automatically configured by the chart. Raise `config.parserApiTimeoutMs` if imports time out. +- **Pod Never Becomes Ready After Upgrade**: On v0.18.0+ the health endpoint is `/api/v1/health`. Probes still pointing at the old endpoint will fail. Check with `kubectl exec deploy/norish -c norish -- wget -qO- http://127.0.0.1:3000/api/v1/health` (use `127.0.0.1`, not `localhost` — that resolves to `::1` in the container and is refused). A healthy response reports `status`, `db.status`, the app version and the `recipe-scrapers` version. +- **CrashLoopBackOff With "Migration failed" / "Server startup failed"**: The app runs migrations at boot and exits if PostgreSQL is unreachable. Verify `database.host`, credentials and that the database exists; the pod recovers on its own once the database is reachable. ```bash kubectl get pods -l app.kubernetes.io/name=norish @@ -351,5 +495,7 @@ kubectl logs -l app.kubernetes.io/name=norish ## Links -- [Norish GitHub](https://github.com/norishapp/norish) +- [Norish GitHub](https://github.com/norish-recipes/norish) +- [Norish Documentation](https://docs.norish.dev) +- [Norish Releases](https://github.com/norish-recipes/norish/releases) - [Chart Source](https://github.com/rtomik/helm-charts/tree/main/charts/norish) diff --git a/charts/norish/templates/NOTES.txt b/charts/norish/templates/NOTES.txt index 19d09b1..56f1a24 100644 --- a/charts/norish/templates/NOTES.txt +++ b/charts/norish/templates/NOTES.txt @@ -71,4 +71,4 @@ IMPORTANT CONFIGURATION NOTES: Configure ONE provider (OIDC, GitHub, or Google) to create your admin account. {{- end }} -For more information, visit: https://github.com/norishapp/norish +For more information, visit: https://github.com/norish-recipes/norish diff --git a/charts/norish/templates/deployment-app.yaml b/charts/norish/templates/deployment-app.yaml index 62b2cfc..bc75e5f 100644 --- a/charts/norish/templates/deployment-app.yaml +++ b/charts/norish/templates/deployment-app.yaml @@ -96,6 +96,24 @@ spec: - name: PASSWORD_AUTH_ENABLED value: {{ .Values.config.passwordAuthEnabled | quote }} {{- end }} + {{- if .Values.config.enableRegistration }} + - name: ENABLE_REGISTRATION + value: {{ .Values.config.enableRegistration | quote }} + {{- end }} + - name: UPLOADS_DIR + value: {{ .Values.config.uploadsDir | quote }} + {{- if .Values.config.parserApiTimeoutMs }} + - name: PARSER_API_TIMEOUT_MS + value: {{ .Values.config.parserApiTimeoutMs | quote }} + {{- end }} + {{- if .Values.config.defaultLocale }} + - name: DEFAULT_LOCALE + value: {{ .Values.config.defaultLocale | quote }} + {{- end }} + {{- if .Values.config.enabledLocales }} + - name: ENABLED_LOCALES + value: {{ .Values.config.enabledLocales | quote }} + {{- end }} {{- if .Values.database.existingSecret }} - name: DB_USERNAME valueFrom: @@ -164,6 +182,20 @@ spec: - name: OIDC_WELLKNOWN value: {{ .Values.config.auth.oidc.wellKnown | quote }} {{- end }} + {{- if .Values.config.auth.oidc.claimMapping.enabled }} + - name: OIDC_CLAIM_MAPPING_ENABLED + value: "true" + {{- with .Values.config.auth.oidc.claimMapping.scopes }} + - name: OIDC_SCOPES + value: {{ . | quote }} + {{- end }} + - name: OIDC_GROUPS_CLAIM + value: {{ .Values.config.auth.oidc.claimMapping.groupsClaim | quote }} + - name: OIDC_ADMIN_GROUP + value: {{ .Values.config.auth.oidc.claimMapping.adminGroup | quote }} + - name: OIDC_HOUSEHOLD_GROUP_PREFIX + value: {{ .Values.config.auth.oidc.claimMapping.householdGroupPrefix | quote }} + {{- end }} - name: OIDC_CLIENT_ID valueFrom: secretKeyRef: @@ -234,7 +266,7 @@ spec: {{- end }} volumeMounts: - name: uploads - mountPath: /app/uploads + mountPath: {{ .Values.config.uploadsDir }} {{- with .Values.extraVolumeMounts }} {{- toYaml . | nindent 12 }} {{- end }} @@ -256,6 +288,7 @@ spec: - "--no-sandbox" - "--disable-gpu" - "--disable-dev-shm-usage" + - "--disable-features=dbus" - "--remote-debugging-address=0.0.0.0" - "--remote-debugging-port={{ .Values.chrome.port }}" - "--headless" diff --git a/charts/norish/values.yaml b/charts/norish/values.yaml index 2a938e1..790c98e 100644 --- a/charts/norish/values.yaml +++ b/charts/norish/values.yaml @@ -5,7 +5,7 @@ fullnameOverride: "" ## Image settings image: repository: norishapp/norish - tag: "v0.16.2-beta" + tag: "v0.20.0-beta" pullPolicy: IfNotPresent imagePullSecrets: [] @@ -96,7 +96,7 @@ probes: timeoutSeconds: 5 failureThreshold: 30 successThreshold: 1 - path: / + path: /api/v1/health liveness: enabled: true initialDelaySeconds: 30 @@ -104,6 +104,11 @@ probes: timeoutSeconds: 5 failureThreshold: 6 successThreshold: 1 + # Deliberately NOT /api/v1/health: since v0.18.1 that endpoint returns 503 when the + # database is unreachable, so a transient DB outage would restart the pod. The app + # root confirms the process is alive without coupling liveness to the database. + # Readiness still uses the health endpoint, which is what pulls the pod out of the + # Service during a DB outage. path: / readiness: enabled: true @@ -112,7 +117,7 @@ probes: timeoutSeconds: 3 failureThreshold: 3 successThreshold: 1 - path: / + path: /api/v1/health ## Application configuration config: @@ -154,6 +159,26 @@ config: # Defaults to false if OIDC or OAuth is configured, true otherwise passwordAuthEnabled: "" + # Allow new users to register themselves (ENABLE_REGISTRATION) + # Leave empty to use the application default (false). + # The first account created always becomes the server owner/admin. + enableRegistration: "" + + # Uploads directory inside the container (UPLOADS_DIR) + # Also used as the mount path for the uploads volume + uploadsDir: "/app/uploads" + + # Timeout for recipe parser API calls in milliseconds (PARSER_API_TIMEOUT_MS) + # Leave empty to use the application default (15000) + parserApiTimeoutMs: "" + + # Instance default locale, must match a supported locale (DEFAULT_LOCALE) + defaultLocale: "" + + # Comma-separated list of enabled locales (ENABLED_LOCALES) + # Leave empty to enable all locales. Example: "en,de,nl" + enabledLocales: "" + # Authentication provider configuration # Configure ONE provider for initial admin account creation # After first login, manage additional providers via Settings → Admin @@ -173,6 +198,16 @@ config: clientIdKey: "oidc-client-id" clientSecretKey: "oidc-client-secret" + # Claim mapping: auto-assign the admin role and households from OIDC claims + # Disabled by default for security + claimMapping: + enabled: false + # Additional scopes to request, comma-separated (e.g. "groups" for Keycloak) + scopes: "" + groupsClaim: "groups" # Claim containing the user groups + adminGroup: "norish_admin" # Group that grants the server admin role + householdGroupPrefix: "norish_household_" # Prefix for household groups + # GitHub OAuth github: enabled: false