diff --git a/charts/norish/Chart.yaml b/charts/norish/Chart.yaml new file mode 100644 index 0000000..3f2a30a --- /dev/null +++ b/charts/norish/Chart.yaml @@ -0,0 +1,17 @@ +apiVersion: v2 +name: norish +description: Norish helm chart for Kubernetes - A recipe management and meal planning application +type: application +version: 0.0.1 +appVersion: "v0.13.6-beta" +maintainers: + - name: Richard Tomik + email: no@m.com +keywords: + - recipe + - meal-planning + - food + - norish +home: https://github.com/rtomik/helm-charts +sources: + - https://github.com/norishapp/norish diff --git a/charts/norish/readme.md b/charts/norish/readme.md new file mode 100644 index 0000000..16d93fb --- /dev/null +++ b/charts/norish/readme.md @@ -0,0 +1,544 @@ +cl# Norish Helm Chart + +A Helm chart for deploying [Norish](https://github.com/norishapp/norish), a recipe management and meal planning application, on Kubernetes. + +## Introduction + +This chart bootstraps a Norish deployment on a Kubernetes cluster using the Helm package manager. + +**IMPORTANT: This chart requires a central PostgreSQL database.** You must have a PostgreSQL server available before deploying this chart. The chart does not include a PostgreSQL deployment. + +**Note:** This chart includes a Chrome headless sidecar container that is required for recipe parsing and scraping functionality. Chrome requires elevated security privileges (`SYS_ADMIN` capability) and additional resources (recommend 256Mi-512Mi memory). + +## Prerequisites + +- Kubernetes 1.19+ +- Helm 3.0+ +- **PostgreSQL database server** (required) +- PV provisioner support in the underlying infrastructure (if persistence is enabled) + +## Installing the Chart + +To install the chart with the release name `norish`: + +```bash +$ helm repo add helm-charts https://rtomik.github.io/helm-charts +$ helm install norish helm-charts/norish +``` + +The command deploys Norish on the Kubernetes cluster with default configuration. The [Parameters](#parameters) section lists the parameters that can be configured during installation. + +## Uninstalling the Chart + +To uninstall/delete the `norish` deployment: + +```bash +helm uninstall norish +``` + +This command removes all the Kubernetes components associated with the chart and deletes the release. + +## Configuration + +### Required Configuration + +Before deploying, you must configure: + +1. **PostgreSQL Database** (REQUIRED): A central PostgreSQL database must be available + - Configure `database.host` to point to your PostgreSQL server + - Ensure the database exists before deployment + - Set appropriate credentials + +2. **Master Key**: A 32-byte base64-encoded encryption key + ```bash + # Generate a master key + openssl rand -base64 32 + ``` + +3. **Application URL**: Set `config.authUrl` to match your ingress hostname + +### Authentication Configuration + +**Authentication providers are now optional!** You can deploy Norish in two ways: + +**Option 1: Password Authentication (Simple Setup)** +- No external authentication provider required +- Users can register and log in with email/password +- Perfect for self-hosted, single-tenant deployments +- Enabled automatically when no OAuth/OIDC provider is configured + +**Option 2: OAuth/OIDC Provider (Enterprise Setup)** +- Configure ONE of the following: + - OIDC/OAuth2 + - GitHub OAuth + - Google OAuth +- Recommended for multi-user environments +- Can be combined with password authentication via `config.passwordAuthEnabled` + +### Example: Minimal Installation (Password Authentication) + +This is the simplest setup using built-in password authentication: + +```yaml +# values.yaml +database: + host: "postgresql.default.svc.cluster.local" + port: 5432 + name: norish + username: norish + password: "secure-password" + +config: + authUrl: "https://norish.example.com" + masterKey: + value: "" + # passwordAuthEnabled defaults to true when no OAuth/OIDC is configured + +ingress: + enabled: true + hosts: + - host: norish.example.com + paths: + - path: / + pathType: Prefix + tls: + - hosts: + - norish.example.com +``` + +Install with: +```bash +$ helm repo add helm-charts https://rtomik.github.io/helm-charts +$ helm install norish helm-charts/norish -f values.yaml +``` + +### Example: Installation with OIDC + +For enterprise deployments with an external identity provider: + +```yaml +# values.yaml +database: + host: "postgresql.default.svc.cluster.local" + port: 5432 + name: norish + username: norish + password: "secure-password" + +config: + authUrl: "https://norish.example.com" + masterKey: + value: "" + # Optional: Allow both OIDC and password authentication + passwordAuthEnabled: "true" + auth: + oidc: + enabled: true + name: "MyAuth" + issuer: "https://auth.example.com" + clientId: "" + clientSecret: "" + +ingress: + enabled: true + hosts: + - host: norish.example.com + paths: + - path: / + pathType: Prefix + tls: + - hosts: + - norish.example.com +``` + +Install with: +```bash +$ helm repo add helm-charts https://rtomik.github.io/helm-charts +$ helm install norish helm-charts/norish -f values.yaml +``` + +### Example: Using Existing Secrets + +For production deployments, store sensitive data in Kubernetes secrets: + +```yaml +# values.yaml +database: + host: "postgresql.default.svc.cluster.local" + existingSecret: "norish-db-secret" + usernameKey: "username" + passwordKey: "password" + +config: + masterKey: + existingSecret: "norish-master-key" + secretKey: "master-key" + auth: + oidc: + enabled: true + name: "MyAuth" + issuer: "https://auth.example.com" + existingSecret: "norish-oidc-secret" + clientIdKey: "client-id" + clientSecretKey: "client-secret" +``` + +Create the secrets: +```bash +# Database credentials +kubectl create secret generic norish-db-secret \ + --from-literal=username="norish" \ + --from-literal=password="secure-db-password" + +# Master encryption key +kubectl create secret generic norish-master-key \ + --from-literal=master-key="$(openssl rand -base64 32)" + +# OIDC credentials +kubectl create secret generic norish-oidc-secret \ + --from-literal=client-id="" \ + --from-literal=client-secret="" +``` + +### Example: Using Existing PVC + +If you want to use an existing PersistentVolumeClaim for uploads storage: + +```yaml +# values.yaml +persistence: + enabled: true + existingClaim: "my-existing-pvc" +``` + +This is useful when: +- You want to reuse storage from a previous installation +- You have pre-provisioned PVCs with specific configurations +- You're managing PVCs separately from the Helm chart + +### Optional Configuration + +Version v0.13.6-beta introduces additional optional configuration options: + +```yaml +config: + # Log level configuration + logLevel: "info" # Options: trace, debug, info, warn, error, fatal + + # Additional trusted origins (useful when behind a proxy or using multiple domains) + trustedOrigins: "http://192.168.1.100:3000,https://norish.example.com" + + # Enable/disable password authentication + # Defaults to true when no OAuth/OIDC is configured, false otherwise + # Set to "true" to enable password auth alongside OAuth/OIDC + passwordAuthEnabled: "true" + + auth: + oidc: + enabled: true + name: "MyAuth" + issuer: "https://auth.example.com" + # Optional: Custom well-known configuration URL + # By default derived from issuer + wellKnown: "https://auth.example.com/.well-known/openid-configuration" + clientId: "" + clientSecret: "" +``` + +### Customizing Chrome Headless Resources + +Chrome headless is required but you can customize its resource limits: + +```yaml +chrome: + enabled: true # Must be true for v0.13.6+ + resources: + limits: + cpu: 500m + memory: 512Mi + requests: + cpu: 100m + memory: 256Mi +``` + +### Setting Up PostgreSQL Database + +You need to create the database before deploying this chart: + +```sql +-- Connect to your PostgreSQL server +CREATE DATABASE norish; +CREATE USER norish WITH ENCRYPTED PASSWORD 'secure-password'; +GRANT ALL PRIVILEGES ON DATABASE norish TO norish; +``` + +Or if using a centralized PostgreSQL Helm chart or service, ensure the database is created and accessible from your Kubernetes cluster. + +## Parameters + +### Global Parameters + +| Name | Description | Default | +|------|-------------|---------| +| `nameOverride` | Override the chart name | `""` | +| `fullnameOverride` | Override the full resource names | `""` | + +### Image Parameters + +| Name | Description | Default | +|------|-------------|---------| +| `image.repository` | Norish image repository | `norishapp/norish` | +| `image.tag` | Norish image tag | `v0.13.6-beta` | +| `image.pullPolicy` | Image pull policy | `IfNotPresent` | +| `imagePullSecrets` | Image pull secrets | `[]` | + +### Deployment Parameters + +| Name | Description | Default | +|------|-------------|---------| +| `replicaCount` | Number of replicas | `1` | +| `revisionHistoryLimit` | Number of old ReplicaSets to retain | `3` | + +### Service Parameters + +| Name | Description | Default | +|------|-------------|---------| +| `service.type` | Kubernetes service type | `ClusterIP` | +| `service.port` | Service port | `3000` | +| `service.annotations` | Service annotations | `{}` | + +### Ingress Parameters + +| Name | Description | Default | +|------|-------------|---------| +| `ingress.enabled` | Enable ingress | `false` | +| `ingress.className` | Ingress class name | `""` | +| `ingress.annotations` | Ingress annotations | `{"traefik.ingress.kubernetes.io/router.entrypoints": "websecure"}` | +| `ingress.hosts` | Ingress hosts configuration | See values.yaml | +| `ingress.tls` | Ingress TLS configuration | See values.yaml | + +### Persistence Parameters + +| Name | Description | Default | +|------|-------------|---------| +| `persistence.enabled` | Enable persistent storage | `true` | +| `persistence.existingClaim` | Use an existing PVC instead of creating a new one | `""` | +| `persistence.storageClass` | Storage class name | `""` | +| `persistence.accessMode` | Access mode | `ReadWriteOnce` | +| `persistence.size` | Storage size | `5Gi` | +| `persistence.annotations` | PVC annotations | `{}` | + +### Application Configuration + +| Name | Description | Default | +|------|-------------|---------| +| `config.authUrl` | Application URL (required) | `"http://norish.domain.com"` | +| `config.masterKey.value` | Master encryption key | `""` | +| `config.masterKey.existingSecret` | Use existing secret for master key | `""` | +| `config.logLevel` | Log level: trace, debug, info, warn, error, fatal | `""` | +| `config.trustedOrigins` | Additional trusted origins (comma-separated) | `""` | +| `config.passwordAuthEnabled` | Enable/disable password authentication (defaults to true when no OAuth/OIDC configured) | `""` | +| `config.auth.oidc.enabled` | Enable OIDC authentication | `false` | +| `config.auth.oidc.name` | OIDC provider name | `"MyAuth"` | +| `config.auth.oidc.issuer` | OIDC issuer URL | `""` | +| `config.auth.oidc.wellKnown` | OIDC well-known configuration URL (optional) | `""` | +| `config.auth.oidc.clientId` | OIDC client ID | `""` | +| `config.auth.oidc.clientSecret` | OIDC client secret | `""` | +| `config.auth.github.enabled` | Enable GitHub OAuth | `false` | +| `config.auth.github.clientId` | GitHub client ID | `""` | +| `config.auth.github.clientSecret` | GitHub client secret | `""` | +| `config.auth.google.enabled` | Enable Google OAuth | `false` | +| `config.auth.google.clientId` | Google client ID | `""` | +| `config.auth.google.clientSecret` | Google client secret | `""` | + +### Database Parameters (REQUIRED) + +| Name | Description | Default | +|------|-------------|---------| +| `database.host` | PostgreSQL database host (required) | `""` | +| `database.port` | PostgreSQL database port | `5432` | +| `database.name` | PostgreSQL database name | `norish` | +| `database.username` | PostgreSQL username | `postgres` | +| `database.password` | PostgreSQL password | `""` | +| `database.existingSecret` | Use existing secret for database credentials | `""` | +| `database.usernameKey` | Key in secret for username | `"username"` | +| `database.passwordKey` | Key in secret for password | `"password"` | +| `database.databaseKey` | Key in secret for database name | `"database"` | +| `database.hostKey` | Key in secret for host | `"host"` | + +### Chrome Headless Parameters (REQUIRED) + +| Name | Description | Default | +|------|-------------|---------| +| `chrome.enabled` | Enable Chrome headless sidecar (required for v0.13.6+) | `true` | +| `chrome.image.repository` | Chrome headless image repository | `zenika/alpine-chrome` | +| `chrome.image.tag` | Chrome headless image tag | `latest` | +| `chrome.image.pullPolicy` | Chrome image pull policy | `IfNotPresent` | +| `chrome.port` | Chrome remote debugging port | `3000` | +| `chrome.resources` | Chrome container resource limits/requests | `{}` | + +### Security Parameters + +| Name | Description | Default | +|------|-------------|---------| +| `podSecurityContext.runAsNonRoot` | Run as non-root user | `true` | +| `podSecurityContext.runAsUser` | User ID to run as | `1000` | +| `podSecurityContext.fsGroup` | Group ID for filesystem | `1000` | + +### Resource Parameters + +| Name | Description | Default | +|------|-------------|---------| +| `resources` | CPU/Memory resource requests/limits | `{}` | + +### Health Check Parameters + +| Name | Description | Default | +|------|-------------|---------| +| `probes.startup.enabled` | Enable startup probe | `true` | +| `probes.liveness.enabled` | Enable liveness probe | `true` | +| `probes.readiness.enabled` | Enable readiness probe | `true` | + +## What's New in v0.13.6-beta + +This version introduces several improvements and new features: + +**UI/UX Improvements:** +- Ability to change prompts used in Settings → Admin +- Improved transcriber logic +- Double tapping/clicking planned recipes now opens the recipe page +- Small icon that opens the original recipe page +- Add recipes button now opens a dropdown instead of instantly redirecting to manual creation + +**New Features:** +- Support for trusting additional origins using `TRUSTED_ORIGINS` environment variable (comma-separated) +- Customizable password authentication via `PASSWORD_AUTH_ENABLED` flag +- Configurable log level via `NEXT_PUBLIC_LOG_LEVEL` + +**Bug Fixes:** +- User menu remaining open when clicking import +- Text truncation no longer uses the tailwind truncate class in the calendar +- Comma decimals being parsed as nothing (e.g., 2,5 ended up as 25) +- Unicode character handling + +**Breaking Changes:** +- Chrome headless is now mandatory for improved parsing functionality + +## Authentication Setup + +Norish v0.13.6-beta and later support multiple authentication methods: + +### Password Authentication (Default) + +When no external authentication provider is configured, Norish automatically enables password-based authentication. Users can: +- Register new accounts with email and password +- Log in using their credentials +- Manage their account through the web interface + +This is the simplest setup and perfect for: +- Self-hosted, single-user or family deployments +- Testing and development environments +- Scenarios where external OAuth providers are not needed + +### External Authentication Providers (Optional) + +For enterprise or multi-tenant deployments, you can configure external authentication providers. After configuring a provider, you can manage additional authentication methods through the Settings → Admin interface. + +### OIDC/OAuth2 + +```yaml +config: + auth: + oidc: + enabled: true + name: "Authentik" # Display name + issuer: "https://auth.example.com/application/o/norish/" + clientId: "" + clientSecret: "" +``` + +### GitHub OAuth + +1. Create a GitHub OAuth App at https://github.com/settings/developers +2. Set Authorization callback URL to: `https://norish.example.com/api/auth/callback/github` + +```yaml +config: + auth: + github: + enabled: true + clientId: "" + clientSecret: "" +``` + +### Google OAuth + +1. Create OAuth credentials at https://console.cloud.google.com/apis/credentials +2. Set Authorized redirect URI to: `https://norish.example.com/api/auth/callback/google` + +```yaml +config: + auth: + google: + enabled: true + clientId: "" + clientSecret: "" +``` + +## Troubleshooting + +### Check Pod Status + +```bash +kubectl get pods -l app.kubernetes.io/name=norish +kubectl logs -l app.kubernetes.io/name=norish +``` + +### Check Database Connection + +```bash +# Test connection from app pod +kubectl exec -it deployment/norish -- sh +nc -zv 5432 +``` + +### Common Issues + +1. **Master Key Not Set**: Ensure you've generated and configured a master key +2. **Cannot Log In**: + - Password authentication is enabled by default when no OAuth/OIDC is configured + - If you configured an external provider, ensure the client ID/secret are correct + - Check the callback URL matches your ingress hostname +3. **Database Connection Failed**: + - Verify database host is correct and accessible from the cluster + - Check database credentials + - Ensure the database exists + - Verify network policies allow connections to the database +4. **Application Not Accessible**: Verify ingress configuration and DNS records +5. **Chrome Headless Issues**: + - Chrome requires `SYS_ADMIN` capability for proper operation + - If pod fails to start, check if your cluster's security policies allow the required capabilities + - Chrome container may require additional memory (256Mi-512Mi recommended) + - Check Chrome container logs: `kubectl logs -l app.kubernetes.io/name=norish -c chrome-headless` +6. **Recipe Parsing Failures**: + - Ensure Chrome headless is running: `kubectl get pods -l app.kubernetes.io/name=norish` + - Verify `CHROME_WS_ENDPOINT` is set correctly (automatically configured by the chart) + - Check if Chrome is accessible from the Norish container + +## Upgrading + +To upgrade the chart: + +```bash +$ helm upgrade norish helm-charts/norish -f values.yaml +``` + +## Support + +- Norish Repository: https://github.com/norishapp/norish +- Chart Repository: https://github.com/rtomik/helm-charts +- Issue Tracker: https://github.com/rtomik/helm-charts/issues + +## License + +This Helm chart is provided as-is under the same license as the Norish application. diff --git a/charts/norish/templates/NOTES.txt b/charts/norish/templates/NOTES.txt new file mode 100644 index 0000000..19d09b1 --- /dev/null +++ b/charts/norish/templates/NOTES.txt @@ -0,0 +1,74 @@ +Thank you for installing {{ .Chart.Name }}! + +Your release is named {{ .Release.Name }}. + +To learn more about the release, try: + + $ helm status {{ .Release.Name }} -n {{ .Release.Namespace }} + $ helm get all {{ .Release.Name }} -n {{ .Release.Namespace }} + +{{- if .Values.ingress.enabled }} + +Application URL: +{{- range .Values.ingress.hosts }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ .host }}{{ range .paths }}{{ .path }}{{ end }} +{{- end }} +{{- else }} + +Get the application URL by running these commands: +{{- if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "norish.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "norish.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "norish.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "norish.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT +{{- end }} +{{- end }} + +IMPORTANT CONFIGURATION NOTES: + +1. Database Configuration: + {{- if .Values.database.host }} + Using external PostgreSQL at: {{ .Values.database.host }}:{{ .Values.database.port }} + {{- else }} + ⚠️ WARNING: Database host is not configured! + Configure database.host to point to your PostgreSQL server. + {{- end }} + +2. Master Key: + {{- if .Values.config.masterKey.existingSecret }} + Using existing secret: {{ .Values.config.masterKey.existingSecret }} + {{- else }} + {{- if not .Values.config.masterKey.value }} + ⚠️ WARNING: Master key is not set! Generate one with: openssl rand -base64 32 + {{- else }} + Master key configured from values.yaml + {{- end }} + {{- end }} + +3. Authentication: + {{- if or .Values.config.auth.oidc.enabled .Values.config.auth.github.enabled .Values.config.auth.google.enabled }} + {{- if .Values.config.auth.oidc.enabled }} + - OIDC provider: {{ .Values.config.auth.oidc.name }} + {{- end }} + {{- if .Values.config.auth.github.enabled }} + - GitHub OAuth enabled + {{- end }} + {{- if .Values.config.auth.google.enabled }} + - Google OAuth enabled + {{- end }} + After first login, configure additional providers in Settings → Admin + {{- else }} + ⚠️ WARNING: No authentication provider configured! + Configure ONE provider (OIDC, GitHub, or Google) to create your admin account. + {{- end }} + +For more information, visit: https://github.com/norishapp/norish diff --git a/charts/norish/templates/_helpers.tpl b/charts/norish/templates/_helpers.tpl new file mode 100644 index 0000000..1bc0d94 --- /dev/null +++ b/charts/norish/templates/_helpers.tpl @@ -0,0 +1,57 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "norish.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "norish.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- printf "%s" $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "norish.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "norish.labels" -}} +helm.sh/chart: {{ include "norish.chart" . }} +{{ include "norish.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "norish.selectorLabels" -}} +app.kubernetes.io/name: {{ include "norish.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Database connection URL +*/}} +{{- define "norish.databaseUrl" -}} +{{- $username := .Values.database.username }} +{{- $password := .Values.database.password }} +{{- $host := .Values.database.host }} +{{- $port := .Values.database.port }} +{{- $database := .Values.database.name }} +{{- printf "postgres://%s:%s@%s:%d/%s" $username $password $host (int $port) $database }} +{{- end }} diff --git a/charts/norish/templates/deployment-app.yaml b/charts/norish/templates/deployment-app.yaml new file mode 100644 index 0000000..9e8dd29 --- /dev/null +++ b/charts/norish/templates/deployment-app.yaml @@ -0,0 +1,279 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "norish.fullname" . }} + labels: + {{- include "norish.labels" . | nindent 4 }} + app.kubernetes.io/component: app + annotations: + checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} +spec: + replicas: {{ .Values.replicaCount }} + revisionHistoryLimit: {{ .Values.revisionHistoryLimit }} + selector: + matchLabels: + {{- include "norish.selectorLabels" . | nindent 6 }} + strategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: 1 + maxSurge: 1 + template: + metadata: + labels: + {{- include "norish.selectorLabels" . | nindent 8 }} + annotations: + checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} + {{- with .Values.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.containerSecurityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.service.port }} + protocol: TCP + {{- if .Values.probes.startup.enabled }} + startupProbe: + httpGet: + path: {{ .Values.probes.startup.path }} + port: http + initialDelaySeconds: {{ .Values.probes.startup.initialDelaySeconds }} + periodSeconds: {{ .Values.probes.startup.periodSeconds }} + timeoutSeconds: {{ .Values.probes.startup.timeoutSeconds }} + failureThreshold: {{ .Values.probes.startup.failureThreshold }} + successThreshold: {{ .Values.probes.startup.successThreshold }} + {{- end }} + {{- if .Values.probes.liveness.enabled }} + livenessProbe: + httpGet: + path: {{ .Values.probes.liveness.path }} + port: http + initialDelaySeconds: {{ .Values.probes.liveness.initialDelaySeconds }} + periodSeconds: {{ .Values.probes.liveness.periodSeconds }} + timeoutSeconds: {{ .Values.probes.liveness.timeoutSeconds }} + failureThreshold: {{ .Values.probes.liveness.failureThreshold }} + successThreshold: {{ .Values.probes.liveness.successThreshold }} + {{- end }} + {{- if .Values.probes.readiness.enabled }} + readinessProbe: + httpGet: + path: {{ .Values.probes.readiness.path }} + port: http + initialDelaySeconds: {{ .Values.probes.readiness.initialDelaySeconds }} + periodSeconds: {{ .Values.probes.readiness.periodSeconds }} + timeoutSeconds: {{ .Values.probes.readiness.timeoutSeconds }} + failureThreshold: {{ .Values.probes.readiness.failureThreshold }} + successThreshold: {{ .Values.probes.readiness.successThreshold }} + {{- end }} + env: + - name: AUTH_URL + value: {{ .Values.config.authUrl | quote }} + {{- if .Values.chrome.enabled }} + - name: CHROME_WS_ENDPOINT + value: "ws://localhost:{{ .Values.chrome.port }}" + {{- end }} + {{- if .Values.config.logLevel }} + - name: NEXT_PUBLIC_LOG_LEVEL + value: {{ .Values.config.logLevel | quote }} + {{- end }} + {{- if .Values.config.trustedOrigins }} + - name: TRUSTED_ORIGINS + value: {{ .Values.config.trustedOrigins | quote }} + {{- end }} + {{- if .Values.config.passwordAuthEnabled }} + - name: PASSWORD_AUTH_ENABLED + value: {{ .Values.config.passwordAuthEnabled | quote }} + {{- end }} + {{- if .Values.database.existingSecret }} + - name: DB_USERNAME + valueFrom: + secretKeyRef: + name: {{ .Values.database.existingSecret }} + key: {{ .Values.database.usernameKey }} + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.database.existingSecret }} + key: {{ .Values.database.passwordKey }} + {{- if .Values.database.databaseKey }} + - name: DB_NAME + valueFrom: + secretKeyRef: + name: {{ .Values.database.existingSecret }} + key: {{ .Values.database.databaseKey }} + {{- else }} + - name: DB_NAME + value: {{ .Values.database.name | quote }} + {{- end }} + {{- if .Values.database.hostKey }} + - name: DB_HOST + valueFrom: + secretKeyRef: + name: {{ .Values.database.existingSecret }} + key: {{ .Values.database.hostKey }} + {{- else }} + - name: DB_HOST + value: {{ .Values.database.host | quote }} + {{- end }} + - name: DB_PORT + value: {{ .Values.database.port | quote }} + - name: DATABASE_URL + value: "postgres://$(DB_USERNAME):$(DB_PASSWORD)@$(DB_HOST):$(DB_PORT)/$(DB_NAME)" + {{- else }} + - name: DATABASE_URL + value: {{ include "norish.databaseUrl" . | quote }} + {{- end }} + - name: MASTER_KEY + valueFrom: + secretKeyRef: + {{- if .Values.config.masterKey.existingSecret }} + name: {{ .Values.config.masterKey.existingSecret }} + key: {{ .Values.config.masterKey.secretKey }} + {{- else }} + name: {{ include "norish.fullname" . }}-secret + key: master-key + {{- end }} + {{- if .Values.config.auth.oidc.enabled }} + - name: OIDC_NAME + value: {{ .Values.config.auth.oidc.name | quote }} + - name: OIDC_ISSUER + value: {{ .Values.config.auth.oidc.issuer | quote }} + {{- if .Values.config.auth.oidc.wellKnown }} + - name: OIDC_WELLKNOWN + value: {{ .Values.config.auth.oidc.wellKnown | quote }} + {{- end }} + - name: OIDC_CLIENT_ID + valueFrom: + secretKeyRef: + {{- if .Values.config.auth.oidc.existingSecret }} + name: {{ .Values.config.auth.oidc.existingSecret }} + key: {{ .Values.config.auth.oidc.clientIdKey }} + {{- else }} + name: {{ include "norish.fullname" . }}-secret + key: oidc-client-id + {{- end }} + - name: OIDC_CLIENT_SECRET + valueFrom: + secretKeyRef: + {{- if .Values.config.auth.oidc.existingSecret }} + name: {{ .Values.config.auth.oidc.existingSecret }} + key: {{ .Values.config.auth.oidc.clientSecretKey }} + {{- else }} + name: {{ include "norish.fullname" . }}-secret + key: oidc-client-secret + {{- end }} + {{- end }} + {{- if .Values.config.auth.github.enabled }} + - name: GITHUB_CLIENT_ID + valueFrom: + secretKeyRef: + {{- if .Values.config.auth.github.existingSecret }} + name: {{ .Values.config.auth.github.existingSecret }} + key: {{ .Values.config.auth.github.clientIdKey }} + {{- else }} + name: {{ include "norish.fullname" . }}-secret + key: github-client-id + {{- end }} + - name: GITHUB_CLIENT_SECRET + valueFrom: + secretKeyRef: + {{- if .Values.config.auth.github.existingSecret }} + name: {{ .Values.config.auth.github.existingSecret }} + key: {{ .Values.config.auth.github.clientSecretKey }} + {{- else }} + name: {{ include "norish.fullname" . }}-secret + key: github-client-secret + {{- end }} + {{- end }} + {{- if .Values.config.auth.google.enabled }} + - name: GOOGLE_CLIENT_ID + valueFrom: + secretKeyRef: + {{- if .Values.config.auth.google.existingSecret }} + name: {{ .Values.config.auth.google.existingSecret }} + key: {{ .Values.config.auth.google.clientIdKey }} + {{- else }} + name: {{ include "norish.fullname" . }}-secret + key: google-client-id + {{- end }} + - name: GOOGLE_CLIENT_SECRET + valueFrom: + secretKeyRef: + {{- if .Values.config.auth.google.existingSecret }} + name: {{ .Values.config.auth.google.existingSecret }} + key: {{ .Values.config.auth.google.clientSecretKey }} + {{- else }} + name: {{ include "norish.fullname" . }}-secret + key: google-client-secret + {{- end }} + {{- end }} + volumeMounts: + - name: uploads + mountPath: /app/uploads + {{- with .Values.extraVolumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- if .Values.chrome.enabled }} + - name: chrome-headless + image: "{{ .Values.chrome.image.repository }}:{{ .Values.chrome.image.tag }}" + imagePullPolicy: {{ .Values.chrome.image.pullPolicy }} + securityContext: + {{- toYaml .Values.chrome.securityContext | nindent 12 }} + ports: + - name: chrome + containerPort: {{ .Values.chrome.port }} + protocol: TCP + command: + - chromium-browser + args: + - "--no-sandbox" + - "--disable-gpu" + - "--disable-dev-shm-usage" + - "--remote-debugging-address=0.0.0.0" + - "--remote-debugging-port={{ .Values.chrome.port }}" + - "--headless" + resources: + {{- toYaml .Values.chrome.resources | nindent 12 }} + {{- end }} + volumes: + {{- if .Values.persistence.enabled }} + - name: uploads + persistentVolumeClaim: + {{- if .Values.persistence.existingClaim }} + claimName: {{ .Values.persistence.existingClaim }} + {{- else }} + claimName: {{ include "norish.fullname" . }}-uploads + {{- end }} + {{- else }} + - name: uploads + emptyDir: {} + {{- end }} + {{- with .Values.extraVolumes }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/charts/norish/templates/ingress.yaml b/charts/norish/templates/ingress.yaml new file mode 100644 index 0000000..c2ae2c9 --- /dev/null +++ b/charts/norish/templates/ingress.yaml @@ -0,0 +1,43 @@ +{{- if .Values.ingress.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "norish.fullname" . }} + labels: + {{- include "norish.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.ingress.className }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + {{- if .secretName }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + pathType: {{ .pathType }} + backend: + service: + name: {{ include "norish.fullname" $ }} + port: + number: {{ $.Values.service.port }} + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/norish/templates/pvc.yaml b/charts/norish/templates/pvc.yaml new file mode 100644 index 0000000..84e7c09 --- /dev/null +++ b/charts/norish/templates/pvc.yaml @@ -0,0 +1,22 @@ +{{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "norish.fullname" . }}-uploads + labels: + {{- include "norish.labels" . | nindent 4 }} + app.kubernetes.io/component: app + {{- with .Values.persistence.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + accessModes: + - {{ .Values.persistence.accessMode }} + {{- if .Values.persistence.storageClass }} + storageClassName: {{ .Values.persistence.storageClass }} + {{- end }} + resources: + requests: + storage: {{ .Values.persistence.size }} +{{- end }} diff --git a/charts/norish/templates/secret.yaml b/charts/norish/templates/secret.yaml new file mode 100644 index 0000000..0ed62cf --- /dev/null +++ b/charts/norish/templates/secret.yaml @@ -0,0 +1,32 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "norish.fullname" . }}-secret + labels: + {{- include "norish.labels" . | nindent 4 }} +type: Opaque +stringData: + {{- if not .Values.config.masterKey.existingSecret }} + master-key: {{ .Values.config.masterKey.value | required "config.masterKey.value is required when config.masterKey.existingSecret is not set" | quote }} + {{- end }} + {{- if not .Values.database.existingSecret }} + database-url: {{ include "norish.databaseUrl" . | quote }} + {{- end }} + {{- if .Values.config.auth.oidc.enabled }} + {{- if not .Values.config.auth.oidc.existingSecret }} + oidc-client-id: {{ .Values.config.auth.oidc.clientId | quote }} + oidc-client-secret: {{ .Values.config.auth.oidc.clientSecret | quote }} + {{- end }} + {{- end }} + {{- if .Values.config.auth.github.enabled }} + {{- if not .Values.config.auth.github.existingSecret }} + github-client-id: {{ .Values.config.auth.github.clientId | quote }} + github-client-secret: {{ .Values.config.auth.github.clientSecret | quote }} + {{- end }} + {{- end }} + {{- if .Values.config.auth.google.enabled }} + {{- if not .Values.config.auth.google.existingSecret }} + google-client-id: {{ .Values.config.auth.google.clientId | quote }} + google-client-secret: {{ .Values.config.auth.google.clientSecret | quote }} + {{- end }} + {{- end }} diff --git a/charts/norish/templates/service.yaml b/charts/norish/templates/service.yaml new file mode 100644 index 0000000..f85ce02 --- /dev/null +++ b/charts/norish/templates/service.yaml @@ -0,0 +1,20 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "norish.fullname" . }} + labels: + {{- include "norish.labels" . | nindent 4 }} + app.kubernetes.io/component: app + {{- with .Values.service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "norish.selectorLabels" . | nindent 4 }} diff --git a/charts/norish/values.yaml b/charts/norish/values.yaml new file mode 100644 index 0000000..887e941 --- /dev/null +++ b/charts/norish/values.yaml @@ -0,0 +1,229 @@ +## Global settings +nameOverride: "" +fullnameOverride: "" + +## Image settings +image: + repository: norishapp/norish + tag: "v0.13.6-beta" + pullPolicy: IfNotPresent + +imagePullSecrets: [] + +## Deployment settings +replicaCount: 1 +revisionHistoryLimit: 3 + +# Pod security settings +podSecurityContext: + runAsNonRoot: true + runAsUser: 1000 + fsGroup: 1000 + +containerSecurityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: false + capabilities: + drop: + - ALL + +## Pod scheduling +nodeSelector: {} +tolerations: [] +affinity: {} + +## Pod annotations +podAnnotations: {} + +## Service settings +service: + type: ClusterIP + port: 3000 + annotations: {} + +## Ingress settings +ingress: + enabled: false + className: "" + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: websecure + hosts: + - host: norish.domain.com + paths: + - path: / + pathType: Prefix + tls: + - hosts: + - norish.domain.com + # Optional: specify the name of an existing TLS secret + # secretName: "existing-tls-secret" + +## Persistence settings +persistence: + enabled: true + # Use an existing PVC instead of creating a new one + existingClaim: "" + storageClass: "" + accessMode: ReadWriteOnce + size: 5Gi + annotations: {} + +# Extra volume mounts +extraVolumeMounts: [] + +# Extra volumes +extraVolumes: [] + +## Resource limits and requests +resources: {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 500m + # memory: 512Mi + # requests: + # cpu: 100m + # memory: 128Mi + +## Application health checks +probes: + startup: + enabled: true + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 30 + successThreshold: 1 + path: / + liveness: + enabled: true + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + successThreshold: 1 + path: / + readiness: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 3 + successThreshold: 1 + path: / + +## Application configuration +config: + # Application URL (required) + # This should match your ingress hostname + authUrl: "http://norish.domain.com" + + # Master encryption key (required) + # Generate with: openssl rand -base64 32 + # For production, use an existing Kubernetes Secret + masterKey: + existingSecret: "" # Name of existing Kubernetes secret + secretKey: "master-key" # Key in the secret where master key is stored + value: "" # Only used if existingSecret is not set (must be 32-byte base64) + + # Optional configuration + # Log level: trace, debug, info, warn, error, fatal + # Defaults to info in production, debug in development + logLevel: "" + + # Additional trusted origins (comma-separated) + # Useful when behind a proxy or using multiple domains + # Example: "http://192.168.1.100:3000,https://norish.example.com" + trustedOrigins: "" + + # Enable/disable password authentication + # Defaults to false if OIDC or OAuth is configured, true otherwise + passwordAuthEnabled: "" + + # Authentication provider configuration + # Configure ONE provider for initial admin account creation + # After first login, manage additional providers via Settings → Admin + auth: + # OIDC/OAuth2 provider + oidc: + enabled: false + name: "MyAuth" + issuer: "" + clientId: "" + clientSecret: "" + # Optional: OIDC well-known configuration URL + # By default derived from issuer by appending /.well-known/openid-configuration + wellKnown: "" + # Use existing secret for OIDC credentials + existingSecret: "" + clientIdKey: "oidc-client-id" + clientSecretKey: "oidc-client-secret" + + # GitHub OAuth + github: + enabled: false + clientId: "" + clientSecret: "" + # Use existing secret for GitHub credentials + existingSecret: "" + clientIdKey: "github-client-id" + clientSecretKey: "github-client-secret" + + # Google OAuth + google: + enabled: false + clientId: "" + clientSecret: "" + # Use existing secret for Google credentials + existingSecret: "" + clientIdKey: "google-client-id" + clientSecretKey: "google-client-secret" + +## External PostgreSQL database configuration (REQUIRED) +## Norish requires a central PostgreSQL database +## You must have a PostgreSQL server available before deploying this chart +database: + # Database connection details + host: "" # Required: PostgreSQL server hostname + port: 5432 + name: norish + username: postgres + password: "" + + # Use existing secret for database credentials (recommended for production) + existingSecret: "" # Name of existing Kubernetes secret + usernameKey: "username" # Key in the secret for database username + passwordKey: "password" # Key in the secret for database password + databaseKey: "database" # Key in the secret for database name (optional) + hostKey: "" # Key in the secret for database host (optional) + +## Chrome Headless configuration (REQUIRED) +## Required for improved recipe parsing and scraping +chrome: + enabled: true + image: + repository: zenika/alpine-chrome + tag: "latest" + pullPolicy: IfNotPresent + + # Chrome port for remote debugging + port: 3000 + + # Chrome security context - requires specific capabilities + securityContext: + runAsNonRoot: false + runAsUser: 0 + capabilities: + add: + - SYS_ADMIN + + # Chrome resource limits + resources: {} + # limits: + # cpu: 500m + # memory: 512Mi + # requests: + # cpu: 100m + # memory: 256Mi diff --git a/charts/tandoor/Chart.yaml b/charts/tandoor/Chart.yaml new file mode 100644 index 0000000..8da5b66 --- /dev/null +++ b/charts/tandoor/Chart.yaml @@ -0,0 +1,18 @@ +apiVersion: v2 +name: tandoor +description: Tandoor Recipes - A recipe management application for Kubernetes +type: application +version: 0.0.1 +appVersion: "2.3.5" +maintainers: + - name: Richard Tomik + email: no@m.com +keywords: + - recipes + - cooking + - meal-planning + - tandoor + - food +home: https://github.com/rtomik/helm-charts +sources: + - https://github.com/TandoorRecipes/recipes diff --git a/charts/tandoor/readme.md b/charts/tandoor/readme.md new file mode 100644 index 0000000..cc139c4 --- /dev/null +++ b/charts/tandoor/readme.md @@ -0,0 +1,427 @@ +# Tandoor Recipes Helm Chart + +A Helm chart for deploying [Tandoor Recipes](https://github.com/TandoorRecipes/recipes) on Kubernetes. + +Tandoor is a recipe management application that allows you to manage your recipes, plan meals, and create shopping lists. + +Source code can be found here: +- https://github.com/rtomik/helm-charts/tree/main/charts/tandoor + +## Prerequisites + +- Kubernetes 1.19+ +- Helm 3.0+ +- PV provisioner support in the underlying infrastructure +- **External PostgreSQL database** (required - this chart does NOT include PostgreSQL) + +## Installing the Chart + +```bash +helm repo add rtomik https://rtomik.github.io/helm-charts +helm repo update +helm install tandoor rtomik/tandoor -f values.yaml +``` + +## Usage Examples + +### Minimal Configuration + +```yaml +postgresql: + host: "postgresql.database.svc.cluster.local" + database: "tandoor" + username: "tandoor" + password: "your-secure-password" + +config: + secretKey: + value: "your-secret-key-at-least-50-characters-long-for-security-purposes" +``` + +### Production Configuration + +```yaml +postgresql: + host: "postgresql.database.svc.cluster.local" + database: "tandoor" + username: "tandoor" + existingSecret: "tandoor-db-secret" + passwordKey: "password" + +config: + secretKey: + existingSecret: "tandoor-app-secret" + secretKey: "secret-key" + + allowedHosts: "tandoor.example.com" + csrfTrustedOrigins: "https://tandoor.example.com" + timezone: "Europe/Berlin" + + # Optional: OpenID Connect with Authentik + # oidc: + # enabled: true + # providerId: "authentik" + # providerName: "Authentik" + # clientId: "your-client-id" + # clientSecret: "your-client-secret" + # serverUrl: "https://authentik.company/application/o/tandoor/.well-known/openid-configuration" + +ingress: + enabled: true + className: "nginx" + annotations: + cert-manager.io/cluster-issuer: "letsencrypt-prod" + hosts: + - host: tandoor.example.com + paths: + - path: / + pathType: Prefix + tls: + - hosts: + - tandoor.example.com + secretName: tandoor-tls + +persistence: + staticfiles: + enabled: true + # existingClaim: "my-existing-pvc" + storageClass: "longhorn" + size: 2Gi + mediafiles: + enabled: true + storageClass: "longhorn" + size: 10Gi + +resources: + limits: + cpu: 1000m + memory: 512Mi + requests: + cpu: 100m + memory: 256Mi +``` + + +## Configuration + +All configuration options are based on the official Tandoor documentation: +https://docs.tandoor.dev/system/configuration/ + +The following table lists the configurable parameters and their default values. + +### Global Parameters + +| Name | Description | Value | +|------|-------------|-------| +| `nameOverride` | String to partially override the release name | `""` | +| `fullnameOverride` | String to fully override the release name | `""` | + +### Image Parameters + +| Name | Description | Value | +|------|-------------|-------| +| `image.repository` | Tandoor image repository | `vabene1111/recipes` | +| `image.tag` | Tandoor image tag | `2.3.5` | +| `image.pullPolicy` | Tandoor image pull policy | `IfNotPresent` | + +### Deployment Parameters + +| Name | Description | Value | +|------|-------------|-------| +| `replicaCount` | Number of Tandoor replicas | `1` | +| `revisionHistoryLimit` | Number of old ReplicaSets to retain | `3` | + +### PostgreSQL Parameters + +| Name | Description | Value | +|------|-------------|-------| +| `postgresql.host` | PostgreSQL host | `postgresql.default.svc.cluster.local` | +| `postgresql.port` | PostgreSQL port | `5432` | +| `postgresql.database` | PostgreSQL database name | `tandoor` | +| `postgresql.username` | PostgreSQL username | `tandoor` | +| `postgresql.password` | PostgreSQL password (not recommended for production) | `""` | +| `postgresql.existingSecret` | Existing secret with PostgreSQL credentials | `""` | +| `postgresql.passwordKey` | Key in existing secret for PostgreSQL password | `postgresql-password` | + +### Security Configuration + +| Name | Description | Value | +|------|-------------|-------| +| `config.secretKey.value` | Django secret key (at least 50 characters) | `""` | +| `config.secretKey.existingSecret` | Existing secret for Django secret key | `""` | +| `config.secretKey.secretKey` | Key in existing secret for Django secret key | `secret-key` | +| `config.allowedHosts` | Allowed hosts for HTTP Host Header validation | `*` | +| `config.csrfTrustedOrigins` | CSRF trusted origins | `""` | +| `config.corsAllowOrigins` | Enable CORS allow all origins | `false` | + +### Server Configuration + +| Name | Description | Value | +|------|-------------|-------| +| `config.tandoorPort` | Port where Tandoor exposes its web server | `8080` | +| `config.gunicornWorkers` | Number of Gunicorn worker processes | `3` | +| `config.gunicornThreads` | Number of Gunicorn threads per worker | `2` | +| `config.gunicornTimeout` | Gunicorn request timeout in seconds | `30` | +| `config.gunicornMedia` | Enable media serving via Gunicorn | `0` | +| `config.timezone` | Application timezone | `UTC` | +| `config.scriptName` | URL path base for subfolder deployments | `""` | +| `config.sessionCookieDomain` | Session cookie domain | `""` | +| `config.sessionCookieName` | Session cookie identifier | `sessionid` | + +### Feature Configuration + +| Name | Description | Value | +|------|-------------|-------| +| `config.enableSignup` | Allow user registration | `false` | +| `config.enableMetrics` | Enable Prometheus metrics at /metrics | `false` | +| `config.enablePdfExport` | Enable recipe PDF export | `false` | +| `config.sortTreeByName` | Sort keywords/foods alphabetically | `false` | + +### Social Authentication + +| Name | Description | Value | +|------|-------------|-------| +| `config.socialDefaultAccess` | Space ID for auto-joining new social auth users | `0` | +| `config.socialDefaultGroup` | Default group for new users (guest/user/admin) | `guest` | +| `config.socialProviders` | Comma-separated OAuth provider list | `""` | +| `config.socialAccountProviders` | SOCIALACCOUNT_PROVIDERS JSON (for complex setups) | `""` | + +### OpenID Connect (OIDC) Configuration + +| Name | Description | Value | +|------|-------------|-------| +| `config.oidc.enabled` | Enable OpenID Connect authentication | `false` | +| `config.oidc.providerId` | Provider ID (e.g., authentik, keycloak) | `authentik` | +| `config.oidc.providerName` | Display name on login page | `Authentik` | +| `config.oidc.clientId` | Client ID from OIDC provider | `""` | +| `config.oidc.clientSecret` | Client Secret from OIDC provider | `""` | +| `config.oidc.serverUrl` | OpenID Connect well-known configuration URL | `""` | + +### LDAP Configuration + +| Name | Description | Value | +|------|-------------|-------| +| `config.ldap.enabled` | Enable LDAP authentication | `false` | +| `config.ldap.serverUri` | LDAP server URI | `""` | +| `config.ldap.bindDn` | LDAP bind distinguished name | `""` | +| `config.ldap.bindPassword` | LDAP bind password | `""` | +| `config.ldap.userSearchBaseDn` | LDAP user search base | `""` | +| `config.ldap.tlsCacertFile` | LDAP TLS CA certificate file | `""` | +| `config.ldap.startTls` | Enable LDAP StartTLS | `false` | +| `config.ldap.existingSecret` | Existing secret for LDAP credentials | `""` | +| `config.ldap.bindPasswordKey` | Key in existing secret for LDAP password | `ldap-bind-password` | + +### Remote User Authentication + +| Name | Description | Value | +|------|-------------|-------| +| `config.remoteUserAuth` | Enable REMOTE-USER header authentication | `false` | + +### Email Configuration + +| Name | Description | Value | +|------|-------------|-------| +| `config.email.host` | SMTP server hostname | `""` | +| `config.email.port` | SMTP server port | `25` | +| `config.email.user` | SMTP authentication username | `""` | +| `config.email.password` | SMTP authentication password | `""` | +| `config.email.useTls` | Enable TLS for email | `false` | +| `config.email.useSsl` | Enable SSL for email | `false` | +| `config.email.defaultFrom` | Default from email address | `webmaster@localhost` | +| `config.email.accountEmailSubjectPrefix` | Email subject prefix | `[Tandoor Recipes]` | +| `config.email.existingSecret` | Existing secret for email credentials | `""` | +| `config.email.passwordKey` | Key in existing secret for email password | `email-password` | + +### S3/Object Storage Configuration + +| Name | Description | Value | +|------|-------------|-------| +| `config.s3.enabled` | Enable S3 storage for media files | `false` | +| `config.s3.accessKey` | S3 access key | `""` | +| `config.s3.secretAccessKey` | S3 secret access key | `""` | +| `config.s3.bucketName` | S3 bucket name | `""` | +| `config.s3.regionName` | S3 region name | `""` | +| `config.s3.endpointUrl` | Custom S3 endpoint URL (for MinIO) | `""` | +| `config.s3.customDomain` | CDN/proxy domain for S3 | `""` | +| `config.s3.querystringAuth` | Use signed URLs for S3 objects | `true` | +| `config.s3.querystringExpire` | Signed URL expiration (seconds) | `3600` | +| `config.s3.existingSecret` | Existing secret for S3 credentials | `""` | + +### AI Features + +| Name | Description | Value | +|------|-------------|-------| +| `config.ai.enabled` | Enable AI features | `false` | +| `config.ai.creditsMonthly` | Monthly AI credits per space | `100` | +| `config.ai.rateLimit` | AI API rate limit | `60/hour` | + +### External Services + +| Name | Description | Value | +|------|-------------|-------| +| `config.fdcApiKey` | Food Data Central API key | `DEMO_KEY` | +| `config.disableExternalConnectors` | Disable all external connectors | `false` | +| `config.externalConnectorsQueueSize` | External connectors queue size | `100` | + +### Rate Limiting + +| Name | Description | Value | +|------|-------------|-------| +| `config.ratelimitUrlImportRequests` | Rate limit for URL imports | `""` | +| `config.drfThrottleRecipeUrlImport` | DRF throttle for recipe URL import | `60/hour` | + +### Space Defaults + +| Name | Description | Value | +|------|-------------|-------| +| `config.spaceDefaultMaxRecipes` | Max recipes per space (0=unlimited) | `0` | +| `config.spaceDefaultMaxUsers` | Max users per space (0=unlimited) | `0` | +| `config.spaceDefaultMaxFiles` | Max file storage in MB (0=unlimited) | `0` | +| `config.spaceDefaultAllowSharing` | Allow public recipe sharing | `true` | + +### User Preference Defaults + +| Name | Description | Value | +|------|-------------|-------| +| `config.fractionPrefDefault` | Default fraction display | `false` | +| `config.commentPrefDefault` | Enable comments by default | `true` | +| `config.stickyNavPrefDefault` | Sticky navbar by default | `true` | +| `config.maxOwnedSpacesPrefDefault` | Max spaces per user | `100` | + +### Cosmetic Configuration + +| Name | Description | Value | +|------|-------------|-------| +| `config.unauthenticatedThemeFromSpace` | Space ID for unauthenticated theme | `0` | +| `config.forceThemeFromSpace` | Space ID to enforce theme globally | `0` | + +### Performance Configuration + +| Name | Description | Value | +|------|-------------|-------| +| `config.shoppingMinAutosyncInterval` | Min auto-sync interval (minutes) | `5` | +| `config.exportFileCacheDuration` | Export cache duration (seconds) | `600` | + +### Legal URLs + +| Name | Description | Value | +|------|-------------|-------| +| `config.termsUrl` | Terms of service URL | `""` | +| `config.privacyUrl` | Privacy policy URL | `""` | +| `config.imprintUrl` | Legal imprint URL | `""` | + +### hCaptcha Configuration + +| Name | Description | Value | +|------|-------------|-------| +| `config.hcaptcha.siteKey` | hCaptcha site key | `""` | +| `config.hcaptcha.secret` | hCaptcha secret key | `""` | +| `config.hcaptcha.existingSecret` | Existing secret for hCaptcha | `""` | + +### Debugging + +| Name | Description | Value | +|------|-------------|-------| +| `config.debug` | Enable Django debug mode | `false` | +| `config.debugToolbar` | Enable Django Debug Toolbar | `false` | +| `config.sqlDebug` | Enable SQL debug output | `false` | +| `config.logLevel` | Application log level | `WARNING` | +| `config.gunicornLogLevel` | Gunicorn log level | `info` | + +### Service Parameters + +| Name | Description | Value | +|------|-------------|-------| +| `service.type` | Kubernetes Service type | `ClusterIP` | +| `service.port` | Service HTTP port | `8080` | + +### Ingress Parameters + +| Name | Description | Value | +|------|-------------|-------| +| `ingress.enabled` | Enable ingress | `false` | +| `ingress.className` | Ingress class name | `""` | +| `ingress.annotations` | Ingress annotations | See values.yaml | +| `ingress.hosts` | Ingress hosts configuration | See values.yaml | +| `ingress.tls` | Ingress TLS configuration | See values.yaml | + +### Persistence Parameters + +| Name | Description | Value | +|------|-------------|-------| +| `persistence.staticfiles.enabled` | Enable static files persistence | `true` | +| `persistence.staticfiles.existingClaim` | Use existing PVC for static files | `""` | +| `persistence.staticfiles.storageClass` | Storage class for static files | `""` | +| `persistence.staticfiles.accessMode` | Access mode for static files PVC | `ReadWriteOnce` | +| `persistence.staticfiles.size` | Size of static files PVC | `1Gi` | +| `persistence.mediafiles.enabled` | Enable media files persistence | `true` | +| `persistence.mediafiles.existingClaim` | Use existing PVC for media files | `""` | +| `persistence.mediafiles.storageClass` | Storage class for media files | `""` | +| `persistence.mediafiles.accessMode` | Access mode for media files PVC | `ReadWriteOnce` | +| `persistence.mediafiles.size` | Size of media files PVC | `5Gi` | + +### Pod Security Context + +| Name | Description | Value | +|------|-------------|-------| +| `podSecurityContext.runAsNonRoot` | Run as non-root user | `true` | +| `podSecurityContext.runAsUser` | User ID to run as | `1000` | +| `podSecurityContext.fsGroup` | Group ID for filesystem | `1000` | + +### Container Security Context + +| Name | Description | Value | +|------|-------------|-------| +| `containerSecurityContext.allowPrivilegeEscalation` | Allow privilege escalation | `false` | +| `containerSecurityContext.readOnlyRootFilesystem` | Read-only root filesystem | `false` | + +### Autoscaling Parameters + +| Name | Description | Value | +|------|-------------|-------| +| `autoscaling.enabled` | Enable autoscaling | `false` | +| `autoscaling.minReplicas` | Minimum replicas | `1` | +| `autoscaling.maxReplicas` | Maximum replicas | `3` | +| `autoscaling.targetCPUUtilizationPercentage` | Target CPU utilization | `80` | +| `autoscaling.targetMemoryUtilizationPercentage` | Target memory utilization | `80` | + +### Probes Configuration + +| Name | Description | Value | +|------|-------------|-------| +| `probes.liveness.enabled` | Enable liveness probe | `true` | +| `probes.liveness.initialDelaySeconds` | Initial delay for liveness probe | `30` | +| `probes.liveness.periodSeconds` | Period for liveness probe | `10` | +| `probes.readiness.enabled` | Enable readiness probe | `true` | +| `probes.readiness.initialDelaySeconds` | Initial delay for readiness probe | `15` | +| `probes.readiness.periodSeconds` | Period for readiness probe | `5` | + +### Additional Configuration + +| Name | Description | Value | +|------|-------------|-------| +| `env` | Additional environment variables | `[]` | +| `extraEnvFrom` | Additional environment variables from secrets | `[]` | +| `extraVolumes` | Additional volumes | `[]` | +| `extraVolumeMounts` | Additional volume mounts | `[]` | +| `nodeSelector` | Node selector | `{}` | +| `tolerations` | Tolerations | `[]` | +| `affinity` | Affinity rules | `{}` | + +## Uninstalling the Chart + +```bash +helm uninstall tandoor +``` + +**Note:** PVCs are not automatically deleted. To remove them: + +```bash +kubectl delete pvc -l app.kubernetes.io/name=tandoor +``` + +## Links + +- [Tandoor Recipes GitHub](https://github.com/TandoorRecipes/recipes) +- [Tandoor Documentation](https://docs.tandoor.dev/) +- [Configuration Reference](https://docs.tandoor.dev/system/configuration/) diff --git a/charts/tandoor/templates/NOTES.txt b/charts/tandoor/templates/NOTES.txt new file mode 100644 index 0000000..03445fd --- /dev/null +++ b/charts/tandoor/templates/NOTES.txt @@ -0,0 +1,43 @@ +Tandoor Recipes has been deployed successfully! + +{{- if .Values.ingress.enabled }} + +Access Tandoor at: +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} + +{{- else if contains "NodePort" .Values.service.type }} + +Get the application URL by running: + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "tandoor.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT + +{{- else if contains "LoadBalancer" .Values.service.type }} + +Get the application URL by running: + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "tandoor.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} + +{{- else if contains "ClusterIP" .Values.service.type }} + +Access Tandoor by port-forwarding: + kubectl --namespace {{ .Release.Namespace }} port-forward service/{{ include "tandoor.fullname" . }} 8080:{{ .Values.service.port }} + +Then visit: http://localhost:8080 + +{{- end }} + +IMPORTANT: This chart requires an external PostgreSQL database. +Make sure your PostgreSQL database is configured and accessible at: + Host: {{ .Values.postgresql.host }} + Port: {{ .Values.postgresql.port }} + Database: {{ .Values.postgresql.database }} + +For more information, visit: + - Tandoor Documentation: https://docs.tandoor.dev/ + - Configuration Reference: https://docs.tandoor.dev/system/configuration/ diff --git a/charts/tandoor/templates/_helpers.tpl b/charts/tandoor/templates/_helpers.tpl new file mode 100644 index 0000000..bc33063 --- /dev/null +++ b/charts/tandoor/templates/_helpers.tpl @@ -0,0 +1,59 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "tandoor.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "tandoor.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- printf "%s" $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "tandoor.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "tandoor.labels" -}} +helm.sh/chart: {{ include "tandoor.chart" . }} +{{ include "tandoor.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "tandoor.selectorLabels" -}} +app.kubernetes.io/name: {{ include "tandoor.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +PostgreSQL host +*/}} +{{- define "tandoor.postgresql.host" -}} +{{- .Values.postgresql.host }} +{{- end }} + +{{/* +PostgreSQL port +*/}} +{{- define "tandoor.postgresql.port" -}} +{{- .Values.postgresql.port | toString }} +{{- end }} diff --git a/charts/tandoor/templates/deployment.yaml b/charts/tandoor/templates/deployment.yaml new file mode 100644 index 0000000..4f14d64 --- /dev/null +++ b/charts/tandoor/templates/deployment.yaml @@ -0,0 +1,402 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "tandoor.fullname" . }} + labels: + {{- include "tandoor.labels" . | nindent 4 }} + annotations: + checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} +spec: + replicas: {{ .Values.replicaCount }} + revisionHistoryLimit: {{ .Values.revisionHistoryLimit }} + selector: + matchLabels: + {{- include "tandoor.selectorLabels" . | nindent 6 }} + strategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: 1 + maxSurge: 1 + template: + metadata: + labels: + {{- include "tandoor.selectorLabels" . | nindent 8 }} + annotations: + {{- with .Values.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.containerSecurityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.config.tandoorPort }} + protocol: TCP + {{- if .Values.probes.liveness.enabled }} + livenessProbe: + httpGet: + path: {{ .Values.probes.liveness.path }} + port: http + initialDelaySeconds: {{ .Values.probes.liveness.initialDelaySeconds }} + periodSeconds: {{ .Values.probes.liveness.periodSeconds }} + timeoutSeconds: {{ .Values.probes.liveness.timeoutSeconds }} + failureThreshold: {{ .Values.probes.liveness.failureThreshold }} + successThreshold: {{ .Values.probes.liveness.successThreshold }} + {{- end }} + {{- if .Values.probes.readiness.enabled }} + readinessProbe: + httpGet: + path: {{ .Values.probes.readiness.path }} + port: http + initialDelaySeconds: {{ .Values.probes.readiness.initialDelaySeconds }} + periodSeconds: {{ .Values.probes.readiness.periodSeconds }} + timeoutSeconds: {{ .Values.probes.readiness.timeoutSeconds }} + failureThreshold: {{ .Values.probes.readiness.failureThreshold }} + successThreshold: {{ .Values.probes.readiness.successThreshold }} + {{- end }} + env: + # Database configuration + - name: DB_ENGINE + value: "django.db.backends.postgresql" + - name: POSTGRES_HOST + value: {{ include "tandoor.postgresql.host" . | quote }} + - name: POSTGRES_PORT + value: {{ include "tandoor.postgresql.port" . | quote }} + - name: POSTGRES_DB + value: {{ .Values.postgresql.database | quote }} + - name: POSTGRES_USER + value: {{ .Values.postgresql.username | quote }} + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.postgresql.existingSecret | default (printf "%s-secrets" (include "tandoor.fullname" .)) }} + key: {{ .Values.postgresql.passwordKey | default "postgresql-password" }} + + # Security + - name: SECRET_KEY + valueFrom: + secretKeyRef: + name: {{ .Values.config.secretKey.existingSecret | default (printf "%s-secrets" (include "tandoor.fullname" .)) }} + key: {{ .Values.config.secretKey.secretKey | default "secret-key" }} + - name: ALLOWED_HOSTS + value: {{ .Values.config.allowedHosts | quote }} + {{- if .Values.config.csrfTrustedOrigins }} + - name: CSRF_TRUSTED_ORIGINS + value: {{ .Values.config.csrfTrustedOrigins | quote }} + {{- end }} + - name: CORS_ALLOW_ALL_ORIGINS + value: {{ ternary "1" "0" .Values.config.corsAllowOrigins | quote }} + + # Server configuration + - name: TANDOOR_PORT + value: {{ .Values.config.tandoorPort | quote }} + - name: GUNICORN_WORKERS + value: {{ .Values.config.gunicornWorkers | quote }} + - name: GUNICORN_THREADS + value: {{ .Values.config.gunicornThreads | quote }} + - name: GUNICORN_TIMEOUT + value: {{ .Values.config.gunicornTimeout | quote }} + - name: GUNICORN_MEDIA + value: {{ .Values.config.gunicornMedia | quote }} + + # URL configuration + {{- if .Values.config.scriptName }} + - name: SCRIPT_NAME + value: {{ .Values.config.scriptName | quote }} + {{- end }} + + # Session cookie configuration + {{- if .Values.config.sessionCookieDomain }} + - name: SESSION_COOKIE_DOMAIN + value: {{ .Values.config.sessionCookieDomain | quote }} + {{- end }} + - name: SESSION_COOKIE_NAME + value: {{ .Values.config.sessionCookieName | quote }} + + # Time and locale + - name: TZ + value: {{ .Values.config.timezone | quote }} + + # Feature toggles + - name: ENABLE_SIGNUP + value: {{ ternary "1" "0" .Values.config.enableSignup | quote }} + - name: ENABLE_METRICS + value: {{ ternary "1" "0" .Values.config.enableMetrics | quote }} + - name: ENABLE_PDF_EXPORT + value: {{ ternary "1" "0" .Values.config.enablePdfExport | quote }} + - name: SORT_TREE_BY_NAME + value: {{ ternary "1" "0" .Values.config.sortTreeByName | quote }} + + # Social authentication + - name: SOCIAL_DEFAULT_ACCESS + value: {{ .Values.config.socialDefaultAccess | quote }} + - name: SOCIAL_DEFAULT_GROUP + value: {{ .Values.config.socialDefaultGroup | quote }} + {{- if or .Values.config.socialProviders .Values.config.oidc.enabled }} + - name: SOCIAL_PROVIDERS + value: {{ .Values.config.socialProviders | default "allauth.socialaccount.providers.openid_connect" | quote }} + {{- end }} + {{- if .Values.config.socialAccountProviders }} + - name: SOCIALACCOUNT_PROVIDERS + value: {{ .Values.config.socialAccountProviders | quote }} + {{- end }} + + # OpenID Connect / OAuth configuration (e.g., Authentik, Keycloak) + # Note: For production, consider using extraEnvFrom with a secret containing SOCIALACCOUNT_PROVIDERS + {{- if .Values.config.oidc.enabled }} + {{- $clientId := .Values.config.oidc.clientId }} + {{- $clientSecret := .Values.config.oidc.clientSecret }} + - name: SOCIALACCOUNT_PROVIDERS + value: '{"openid_connect":{"APPS":[{"provider_id":"{{ .Values.config.oidc.providerId }}","name":"{{ .Values.config.oidc.providerName }}","client_id":"{{ $clientId }}","secret":"{{ $clientSecret }}","settings":{"server_url":"{{ .Values.config.oidc.serverUrl }}"}}]}}' + {{- end }} + + # Remote user authentication + - name: REMOTE_USER_AUTH + value: {{ ternary "1" "0" .Values.config.remoteUserAuth | quote }} + + # LDAP configuration + {{- if .Values.config.ldap.enabled }} + - name: LDAP_AUTH + value: "1" + - name: AUTH_LDAP_SERVER_URI + value: {{ .Values.config.ldap.serverUri | quote }} + {{- if .Values.config.ldap.bindDn }} + - name: AUTH_LDAP_BIND_DN + value: {{ .Values.config.ldap.bindDn | quote }} + {{- end }} + {{- if or .Values.config.ldap.bindPassword .Values.config.ldap.existingSecret }} + - name: AUTH_LDAP_BIND_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.config.ldap.existingSecret | default (printf "%s-secrets" (include "tandoor.fullname" .)) }} + key: {{ .Values.config.ldap.bindPasswordKey | default "ldap-bind-password" }} + {{- end }} + {{- if .Values.config.ldap.userSearchBaseDn }} + - name: AUTH_LDAP_USER_SEARCH_BASE_DN + value: {{ .Values.config.ldap.userSearchBaseDn | quote }} + {{- end }} + {{- if .Values.config.ldap.tlsCacertFile }} + - name: AUTH_LDAP_TLS_CACERTFILE + value: {{ .Values.config.ldap.tlsCacertFile | quote }} + {{- end }} + {{- if .Values.config.ldap.startTls }} + - name: AUTH_LDAP_START_TLS + value: "True" + {{- end }} + {{- end }} + + # Email configuration + {{- if .Values.config.email.host }} + - name: EMAIL_HOST + value: {{ .Values.config.email.host | quote }} + - name: EMAIL_PORT + value: {{ .Values.config.email.port | quote }} + {{- if .Values.config.email.user }} + - name: EMAIL_HOST_USER + value: {{ .Values.config.email.user | quote }} + - name: EMAIL_HOST_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.config.email.existingSecret | default (printf "%s-secrets" (include "tandoor.fullname" .)) }} + key: {{ .Values.config.email.passwordKey | default "email-password" }} + {{- end }} + - name: EMAIL_USE_TLS + value: {{ ternary "1" "0" .Values.config.email.useTls | quote }} + - name: EMAIL_USE_SSL + value: {{ ternary "1" "0" .Values.config.email.useSsl | quote }} + - name: DEFAULT_FROM_EMAIL + value: {{ .Values.config.email.defaultFrom | quote }} + - name: ACCOUNT_EMAIL_SUBJECT_PREFIX + value: {{ .Values.config.email.accountEmailSubjectPrefix | quote }} + {{- end }} + + # S3/Object storage configuration + {{- if .Values.config.s3.enabled }} + - name: S3_ACCESS_KEY + valueFrom: + secretKeyRef: + name: {{ .Values.config.s3.existingSecret | default (printf "%s-secrets" (include "tandoor.fullname" .)) }} + key: {{ .Values.config.s3.accessKeyKey | default "s3-access-key" }} + - name: S3_SECRET_ACCESS_KEY + valueFrom: + secretKeyRef: + name: {{ .Values.config.s3.existingSecret | default (printf "%s-secrets" (include "tandoor.fullname" .)) }} + key: {{ .Values.config.s3.secretAccessKeyKey | default "s3-secret-access-key" }} + - name: S3_BUCKET_NAME + value: {{ .Values.config.s3.bucketName | quote }} + {{- if .Values.config.s3.regionName }} + - name: S3_REGION_NAME + value: {{ .Values.config.s3.regionName | quote }} + {{- end }} + {{- if .Values.config.s3.endpointUrl }} + - name: S3_ENDPOINT_URL + value: {{ .Values.config.s3.endpointUrl | quote }} + {{- end }} + {{- if .Values.config.s3.customDomain }} + - name: S3_CUSTOM_DOMAIN + value: {{ .Values.config.s3.customDomain | quote }} + {{- end }} + - name: S3_QUERYSTRING_AUTH + value: {{ ternary "1" "0" .Values.config.s3.querystringAuth | quote }} + - name: S3_QUERYSTRING_EXPIRE + value: {{ .Values.config.s3.querystringExpire | quote }} + {{- end }} + + # AI features + {{- if .Values.config.ai.enabled }} + - name: SPACE_AI_ENABLED + value: "1" + - name: SPACE_AI_CREDITS_MONTHLY + value: {{ .Values.config.ai.creditsMonthly | quote }} + - name: AI_RATELIMIT + value: {{ .Values.config.ai.rateLimit | quote }} + {{- end }} + + # Food Data Central API + - name: FDC_API_KEY + value: {{ .Values.config.fdcApiKey | quote }} + + # External connectors + - name: DISABLE_EXTERNAL_CONNECTORS + value: {{ ternary "1" "0" .Values.config.disableExternalConnectors | quote }} + - name: EXTERNAL_CONNECTORS_QUEUE_SIZE + value: {{ .Values.config.externalConnectorsQueueSize | quote }} + + # Rate limiting + {{- if .Values.config.ratelimitUrlImportRequests }} + - name: RATELIMIT_URL_IMPORT_REQUESTS + value: {{ .Values.config.ratelimitUrlImportRequests | quote }} + {{- end }} + - name: DRF_THROTTLE_RECIPE_URL_IMPORT + value: {{ .Values.config.drfThrottleRecipeUrlImport | quote }} + + # Space defaults + - name: SPACE_DEFAULT_MAX_RECIPES + value: {{ .Values.config.spaceDefaultMaxRecipes | quote }} + - name: SPACE_DEFAULT_MAX_USERS + value: {{ .Values.config.spaceDefaultMaxUsers | quote }} + - name: SPACE_DEFAULT_MAX_FILES + value: {{ .Values.config.spaceDefaultMaxFiles | quote }} + - name: SPACE_DEFAULT_ALLOW_SHARING + value: {{ ternary "1" "0" .Values.config.spaceDefaultAllowSharing | quote }} + + # User preference defaults + - name: FRACTION_PREF_DEFAULT + value: {{ ternary "1" "0" .Values.config.fractionPrefDefault | quote }} + - name: COMMENT_PREF_DEFAULT + value: {{ ternary "1" "0" .Values.config.commentPrefDefault | quote }} + - name: STICKY_NAV_PREF_DEFAULT + value: {{ ternary "1" "0" .Values.config.stickyNavPrefDefault | quote }} + - name: MAX_OWNED_SPACES_PREF_DEFAULT + value: {{ .Values.config.maxOwnedSpacesPrefDefault | quote }} + + # Cosmetic + - name: UNAUTHENTICATED_THEME_FROM_SPACE + value: {{ .Values.config.unauthenticatedThemeFromSpace | quote }} + - name: FORCE_THEME_FROM_SPACE + value: {{ .Values.config.forceThemeFromSpace | quote }} + + # Performance + - name: SHOPPING_MIN_AUTOSYNC_INTERVAL + value: {{ .Values.config.shoppingMinAutosyncInterval | quote }} + - name: EXPORT_FILE_CACHE_DURATION + value: {{ .Values.config.exportFileCacheDuration | quote }} + + # Legal URLs + {{- if .Values.config.termsUrl }} + - name: TERMS_URL + value: {{ .Values.config.termsUrl | quote }} + {{- end }} + {{- if .Values.config.privacyUrl }} + - name: PRIVACY_URL + value: {{ .Values.config.privacyUrl | quote }} + {{- end }} + {{- if .Values.config.imprintUrl }} + - name: IMPRINT_URL + value: {{ .Values.config.imprintUrl | quote }} + {{- end }} + + # hCaptcha + {{- if .Values.config.hcaptcha.siteKey }} + - name: HCAPTCHA_SITEKEY + value: {{ .Values.config.hcaptcha.siteKey | quote }} + - name: HCAPTCHA_SECRET + valueFrom: + secretKeyRef: + name: {{ .Values.config.hcaptcha.existingSecret | default (printf "%s-secrets" (include "tandoor.fullname" .)) }} + key: {{ .Values.config.hcaptcha.secretKeyKey | default "hcaptcha-secret" }} + {{- end }} + + # Debugging + - name: DEBUG + value: {{ ternary "1" "0" .Values.config.debug | quote }} + - name: DEBUG_TOOLBAR + value: {{ ternary "1" "0" .Values.config.debugToolbar | quote }} + - name: SQL_DEBUG + value: {{ ternary "1" "0" .Values.config.sqlDebug | quote }} + - name: LOG_LEVEL + value: {{ .Values.config.logLevel | quote }} + - name: GUNICORN_LOG_LEVEL + value: {{ .Values.config.gunicornLogLevel | quote }} + + # Custom environment variables + {{- range .Values.env }} + - name: {{ .name }} + value: {{ .value | quote }} + {{- end }} + {{- with .Values.extraEnvFrom }} + envFrom: + {{- toYaml . | nindent 12 }} + {{- end }} + volumeMounts: + - name: staticfiles + mountPath: /opt/recipes/staticfiles + - name: mediafiles + mountPath: /opt/recipes/mediafiles + {{- with .Values.extraVolumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + volumes: + {{- if .Values.persistence.staticfiles.enabled }} + - name: staticfiles + persistentVolumeClaim: + claimName: {{ .Values.persistence.staticfiles.existingClaim | default (printf "%s-staticfiles" (include "tandoor.fullname" .)) }} + {{- else }} + - name: staticfiles + emptyDir: {} + {{- end }} + {{- if .Values.persistence.mediafiles.enabled }} + - name: mediafiles + persistentVolumeClaim: + claimName: {{ .Values.persistence.mediafiles.existingClaim | default (printf "%s-mediafiles" (include "tandoor.fullname" .)) }} + {{- else }} + - name: mediafiles + emptyDir: {} + {{- end }} + {{- with .Values.extraVolumes }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/charts/tandoor/templates/ingress.yaml b/charts/tandoor/templates/ingress.yaml new file mode 100644 index 0000000..6f107af --- /dev/null +++ b/charts/tandoor/templates/ingress.yaml @@ -0,0 +1,43 @@ +{{- if .Values.ingress.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "tandoor.fullname" . }} + labels: + {{- include "tandoor.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.ingress.className }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + {{- if .secretName }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + pathType: {{ .pathType }} + backend: + service: + name: {{ include "tandoor.fullname" $ }} + port: + number: {{ $.Values.service.port }} + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/tandoor/templates/pvc.yaml b/charts/tandoor/templates/pvc.yaml new file mode 100644 index 0000000..91b22dc --- /dev/null +++ b/charts/tandoor/templates/pvc.yaml @@ -0,0 +1,44 @@ +{{- if and .Values.persistence.staticfiles.enabled (not .Values.persistence.staticfiles.existingClaim) }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "tandoor.fullname" . }}-staticfiles + labels: + {{- include "tandoor.labels" . | nindent 4 }} + {{- with .Values.persistence.staticfiles.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + accessModes: + - {{ .Values.persistence.staticfiles.accessMode | quote }} + {{- if .Values.persistence.staticfiles.storageClass }} + storageClassName: {{ .Values.persistence.staticfiles.storageClass | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.persistence.staticfiles.size | quote }} +--- +{{- end }} + +{{- if and .Values.persistence.mediafiles.enabled (not .Values.persistence.mediafiles.existingClaim) }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "tandoor.fullname" . }}-mediafiles + labels: + {{- include "tandoor.labels" . | nindent 4 }} + {{- with .Values.persistence.mediafiles.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + accessModes: + - {{ .Values.persistence.mediafiles.accessMode | quote }} + {{- if .Values.persistence.mediafiles.storageClass }} + storageClassName: {{ .Values.persistence.mediafiles.storageClass | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.persistence.mediafiles.size | quote }} +{{- end }} diff --git a/charts/tandoor/templates/secret.yaml b/charts/tandoor/templates/secret.yaml new file mode 100644 index 0000000..2450da3 --- /dev/null +++ b/charts/tandoor/templates/secret.yaml @@ -0,0 +1,49 @@ +{{- $needsSecret := false -}} +{{- if not .Values.config.secretKey.existingSecret -}} + {{- $needsSecret = true -}} +{{- end -}} +{{- if not .Values.postgresql.existingSecret -}} + {{- $needsSecret = true -}} +{{- end -}} +{{- if and .Values.config.ldap.enabled .Values.config.ldap.bindPassword (not .Values.config.ldap.existingSecret) -}} + {{- $needsSecret = true -}} +{{- end -}} +{{- if and .Values.config.email.host .Values.config.email.user (not .Values.config.email.existingSecret) -}} + {{- $needsSecret = true -}} +{{- end -}} +{{- if and .Values.config.s3.enabled (not .Values.config.s3.existingSecret) -}} + {{- $needsSecret = true -}} +{{- end -}} +{{- if and .Values.config.hcaptcha.siteKey .Values.config.hcaptcha.secret (not .Values.config.hcaptcha.existingSecret) -}} + {{- $needsSecret = true -}} +{{- end -}} + +{{- if $needsSecret }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "tandoor.fullname" . }}-secrets + labels: + {{- include "tandoor.labels" . | nindent 4 }} +type: Opaque +data: + {{- if not .Values.config.secretKey.existingSecret }} + {{ .Values.config.secretKey.secretKey | default "secret-key" }}: {{ .Values.config.secretKey.value | default "change-me-tandoor-secret-key-at-least-50-characters-long-for-security" | b64enc }} + {{- end }} + {{- if not .Values.postgresql.existingSecret }} + {{ .Values.postgresql.passwordKey | default "postgresql-password" }}: {{ .Values.postgresql.password | default "tandoor" | b64enc }} + {{- end }} + {{- if and .Values.config.ldap.enabled .Values.config.ldap.bindPassword (not .Values.config.ldap.existingSecret) }} + {{ .Values.config.ldap.bindPasswordKey | default "ldap-bind-password" }}: {{ .Values.config.ldap.bindPassword | b64enc }} + {{- end }} + {{- if and .Values.config.email.host .Values.config.email.user (not .Values.config.email.existingSecret) }} + {{ .Values.config.email.passwordKey | default "email-password" }}: {{ .Values.config.email.password | default "" | b64enc }} + {{- end }} + {{- if and .Values.config.s3.enabled (not .Values.config.s3.existingSecret) }} + {{ .Values.config.s3.accessKeyKey | default "s3-access-key" }}: {{ .Values.config.s3.accessKey | default "" | b64enc }} + {{ .Values.config.s3.secretAccessKeyKey | default "s3-secret-access-key" }}: {{ .Values.config.s3.secretAccessKey | default "" | b64enc }} + {{- end }} + {{- if and .Values.config.hcaptcha.siteKey .Values.config.hcaptcha.secret (not .Values.config.hcaptcha.existingSecret) }} + {{ .Values.config.hcaptcha.secretKeyKey | default "hcaptcha-secret" }}: {{ .Values.config.hcaptcha.secret | b64enc }} + {{- end }} +{{- end }} diff --git a/charts/tandoor/templates/service.yaml b/charts/tandoor/templates/service.yaml new file mode 100644 index 0000000..e66fc8d --- /dev/null +++ b/charts/tandoor/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "tandoor.fullname" . }} + labels: + {{- include "tandoor.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "tandoor.selectorLabels" . | nindent 4 }} diff --git a/charts/tandoor/values.yaml b/charts/tandoor/values.yaml new file mode 100644 index 0000000..5ea0247 --- /dev/null +++ b/charts/tandoor/values.yaml @@ -0,0 +1,317 @@ +## Global settings +nameOverride: "" +fullnameOverride: "" + +## Image settings +image: + repository: vabene1111/recipes + tag: "2.3.5" + pullPolicy: IfNotPresent + +## Deployment settings +replicaCount: 1 +revisionHistoryLimit: 3 + +# Pod security settings +# Note: Tandoor runs nginx internally which requires root privileges +# to write to /var/lib/nginx, /run/nginx, and /opt/recipes/http.d +podSecurityContext: + fsGroup: 0 + +containerSecurityContext: + runAsUser: 0 + runAsGroup: 0 + allowPrivilegeEscalation: false + readOnlyRootFilesystem: false + +## Pod scheduling +nodeSelector: {} +tolerations: [] +affinity: {} + +## Service settings +service: + type: ClusterIP + port: 8080 + +## Ingress settings +ingress: + enabled: false + className: "" + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: websecure + # Enable these for proper HTTP to HTTPS redirect (prevents Origin: null issues) + # traefik.ingress.kubernetes.io/router.middlewares: default-redirect-https@kubernetescrd + hosts: + - host: tandoor.domain.com + paths: + - path: / + pathType: Prefix + tls: + - hosts: + - tandoor.domain.com + # Optional: specify the name of an existing TLS secret + # secretName: "existing-tls-secret" + +## Persistence settings +persistence: + # Tandoor static files directory + staticfiles: + enabled: true + # Use an existing PVC instead of creating a new one + existingClaim: "" + storageClass: "" + accessMode: ReadWriteOnce + size: 1Gi + annotations: {} + # Tandoor media files directory (recipe images, etc.) + mediafiles: + enabled: true + # Use an existing PVC instead of creating a new one + existingClaim: "" + storageClass: "" + accessMode: ReadWriteOnce + size: 5Gi + annotations: {} + +# Extra volume mounts +extraVolumeMounts: [] + +# Extra volumes +extraVolumes: [] + +## Resource limits and requests +# resources: +# limits: +# cpu: 1000m +# memory: 512Mi +# requests: +# cpu: 100m +# memory: 256Mi + +## Application health checks +probes: + liveness: + enabled: true + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + successThreshold: 1 + path: / + readiness: + enabled: true + initialDelaySeconds: 15 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 3 + successThreshold: 1 + path: / + +## Autoscaling configuration +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 3 + targetCPUUtilizationPercentage: 80 + targetMemoryUtilizationPercentage: 80 + +## External PostgreSQL database configuration +## This chart does NOT include PostgreSQL - you must provide an external database +postgresql: + host: "postgresql.default.svc.cluster.local" + port: 5432 + database: "tandoor" + username: "tandoor" + # Use existingSecret for credentials (recommended for production) + existingSecret: "" + passwordKey: "postgresql-password" + # Or set password directly (not recommended for production) + password: "" + +## Tandoor Configuration +## All settings based on official documentation: https://docs.tandoor.dev/system/configuration/ +config: + # Required: Secret key for Django cryptographic operations (at least 50 characters) + secretKey: + # Use existingSecret for production + existingSecret: "" + secretKey: "secret-key" + # Or set directly (not recommended for production) + value: "" + + # Security setting to prevent HTTP Host Header Attacks + allowedHosts: "*" + + # Allows setting origins to allow for unsafe requests (CSRF) + csrfTrustedOrigins: "" + + # Enable cross-origin resource sharing + corsAllowOrigins: false + + # Time and locale settings + timezone: "UTC" + + # Server configuration + tandoorPort: 8080 + gunicornWorkers: 3 + gunicornThreads: 2 + gunicornTimeout: 30 + gunicornMedia: 0 + + # URL configuration (for reverse proxy setups) + # URL path base for subfolder deployments + scriptName: "" + + # Session cookie configuration + sessionCookieDomain: "" + sessionCookieName: "sessionid" + + # Feature toggles + enableSignup: false + enableMetrics: false + enablePdfExport: false + sortTreeByName: false + + # Social authentication + socialDefaultAccess: 0 + socialDefaultGroup: "guest" + socialProviders: "" + # For OpenID Connect providers (like Authentik), use the socialAccountProviders field + # or set via env for complex JSON configurations + socialAccountProviders: "" + + # OpenID Connect / OAuth configuration (e.g., Authentik, Keycloak, etc.) + # For simple single-provider OIDC setup, configure here. + # For complex multi-provider setups or production with secrets, use env + extraEnvFrom. + oidc: + enabled: false + # Provider ID (e.g., "authentik", "keycloak") + providerId: "authentik" + # Display name shown on login page + providerName: "Authentik" + # Client ID from your OIDC provider + clientId: "" + # Client Secret from your OIDC provider (for production, use extraEnvFrom with a secret) + clientSecret: "" + # OpenID Connect well-known configuration URL + # e.g., https://authentik.company/application/o//.well-known/openid-configuration + serverUrl: "" + + # Remote user authentication + remoteUserAuth: false + + # LDAP authentication (optional) + ldap: + enabled: false + serverUri: "" + bindDn: "" + bindPassword: "" + bindPasswordFile: "" + userSearchBaseDn: "" + tlsCacertFile: "" + startTls: false + existingSecret: "" + bindPasswordKey: "ldap-bind-password" + + # Email configuration (optional) + email: + host: "" + port: 25 + user: "" + password: "" + useTls: false + useSsl: false + defaultFrom: "webmaster@localhost" + accountEmailSubjectPrefix: "[Tandoor Recipes]" + existingSecret: "" + passwordKey: "email-password" + + # S3/Object storage configuration (optional) + s3: + enabled: false + accessKey: "" + secretAccessKey: "" + bucketName: "" + regionName: "" + endpointUrl: "" + customDomain: "" + querystringAuth: true + querystringExpire: 3600 + existingSecret: "" + accessKeyKey: "s3-access-key" + secretAccessKeyKey: "s3-secret-access-key" + + # AI features (optional) + ai: + enabled: false + creditsMonthly: 100 + rateLimit: "60/hour" + + # Food Data Central API key for nutrition data + fdcApiKey: "DEMO_KEY" + + # External connectors + disableExternalConnectors: false + externalConnectorsQueueSize: 100 + + # Rate limiting + ratelimitUrlImportRequests: "" + drfThrottleRecipeUrlImport: "60/hour" + + # Space defaults + spaceDefaultMaxRecipes: 0 + spaceDefaultMaxUsers: 0 + spaceDefaultMaxFiles: 0 + spaceDefaultAllowSharing: true + + # User preference defaults + fractionPrefDefault: false + commentPrefDefault: true + stickyNavPrefDefault: true + maxOwnedSpacesPrefDefault: 100 + + # Cosmetic + unauthenticatedThemeFromSpace: 0 + forceThemeFromSpace: 0 + + # Performance + shoppingMinAutosyncInterval: 5 + exportFileCacheDuration: 600 + + # Legal URLs (optional) + termsUrl: "" + privacyUrl: "" + imprintUrl: "" + + # hCaptcha (optional) + hcaptcha: + siteKey: "" + secret: "" + existingSecret: "" + secretKeyKey: "hcaptcha-secret" + + # Debugging (not recommended for production) + debug: false + debugToolbar: false + sqlDebug: false + logLevel: "WARNING" + gunicornLogLevel: "info" + +# Environment variables (for additional configuration not covered above) +# Use this for advanced configurations or settings not exposed in config section +env: [] + # Example: Custom environment variable + # - name: CUSTOM_VAR + # value: "custom-value" + # + # Example: Complex SOCIALACCOUNT_PROVIDERS for multiple OIDC providers + # - name: SOCIAL_PROVIDERS + # value: "allauth.socialaccount.providers.openid_connect" + # - name: SOCIALACCOUNT_PROVIDERS + # value: '{"openid_connect":{"APPS":[{"provider_id":"authentik","name":"Authentik","client_id":"your-client-id","secret":"your-client-secret","settings":{"server_url":"https://authentik.company/application/o/tandoor/.well-known/openid-configuration"}}]}}' + +# Extra environment variables from secrets (recommended for sensitive data) +extraEnvFrom: [] + # - secretRef: + # name: tandoor-extra-secrets diff --git a/charts/values.yaml b/charts/values.yaml new file mode 100644 index 0000000..53547da --- /dev/null +++ b/charts/values.yaml @@ -0,0 +1,84 @@ +## Ingress settings +image: + repository: norishapp/norish + tag: "v0.13.6-beta" + pullPolicy: IfNotPresent + +ingress: + enabled: true + className: "traefik" + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: websecure + hosts: + - host: norish.tomik.lat + paths: + - path: / + pathType: Prefix + tls: + - hosts: + - norish.tomik.lat + +## Persistence settings +persistence: + enabled: true + storageClass: "longhorn" + accessMode: ReadWriteOnce + size: 5Gi + +config: + # Application URL (required) + # This should match your ingress hostname + authUrl: "https://norish.tomik.lat" + + # Master encryption key (required) + # Generate with: openssl rand -base64 32 + # For production, use an existing Kubernetes Secret + masterKey: + existingSecret: "" # Name of existing Kubernetes secret + secretKey: "master-key" # Key in the secret where master key is stored + value: "cp6eVbe4ddmJxlJCJyux5Nlk39gbJR3M9mWjAqEon1c=" # Only used if existingSecret is not set (must be 32-byte base64) + + # Authentication provider configuration + # Configure ONE provider for initial admin account creation + # After first login, manage additional providers via Settings → Admin + auth: + # OIDC/OAuth2 provider + oidc: + enabled: true + name: "Authentik" + issuer: "https://authentik.tomik.lat/application/o/norish/" + clientId: "tSQZSJDBs479OVLyEzwDYAVaVYJhQuaFouIRWHyg" + clientSecret: "SpCQGIhXXF9iVT6qc37ApPC8epy1ZhukDtPp6Ipy8XqI7HK4LQUJmsbNTGhLaz25rNgM3GUUDo0vqoGe4INiEjiPeQ4tpiokrvnjPQ2tXf8AFCiu79eyFttB7TCEdtfI" + + # GitHub OAuth + github: + enabled: false + clientId: "" + clientSecret: "" + # Use existing secret for GitHub credentials + existingSecret: "" + clientIdKey: "github-client-id" + clientSecretKey: "github-client-secret" + + # Google OAuth + google: + enabled: false + clientId: "" + clientSecret: "" + # Use existing secret for Google credentials + existingSecret: "" + clientIdKey: "google-client-id" + clientSecretKey: "google-client-secret" + +## External PostgreSQL database configuration (REQUIRED) +## Norish requires a central PostgreSQL database +## You must have a PostgreSQL server available before deploying this chart +database: + # Database connection details + host: "postgres-cluster-pooler.dbs.svc.cluster.local" # Required: PostgreSQL server hostname + port: 5432 + # Use existing secret for database credentials (recommended for production) + existingSecret: "norish3-db-credentials" # Name of existing Kubernetes secret + usernameKey: "username" # Key in the secret for database username + passwordKey: "password" # Key in the secret for database password + databaseKey: "database" # Key in the secret for database name (optional)