From a40cce4826d9d0ead7b9d29099a729ac6f7a3505 Mon Sep 17 00:00:00 2001 From: thuanpham582002 Date: Mon, 29 Dec 2025 16:32:00 +0700 Subject: [PATCH 1/6] feat: Add official Helm chart for Kubernetes deployment Add comprehensive Helm chart for deploying Open Notebook on Kubernetes clusters. Features: - Complete Bitnami-style documentation with @param annotations - JSON schema for values validation - Support for 17 AI providers (OpenAI, Anthropic, Google, Azure, etc.) - Automatic secret creation for API keys - Health checks (liveness, readiness, startup probes) - Persistent volume support for app and SurrealDB - Service types: ClusterIP, NodePort, LoadBalancer - Ingress configuration with TLS support - Network policy for traffic control - ServiceMonitor for Prometheus monitoring - Pod disruption budget for high availability - Resource management and security contexts - Worker retry configuration Installation: helm install open-notebook ./helm/open-notebook Configuration: See helm/open-notebook/values.yaml for all options See helm/open-notebook/README.md for documentation --- helm/Chart.lock | 6 + helm/Chart.yaml | 26 + helm/README.md | 632 +++++++++++++++++++++++ helm/charts/common-2.31.4.tgz | Bin 0 -> 20003 bytes helm/templates/NOTES.txt | 111 ++++ helm/templates/_helpers.tpl | 427 ++++++++++++++++ helm/templates/configmap.yaml | 26 + helm/templates/deployment.yaml | 185 +++++++ helm/templates/ingress.yaml | 60 +++ helm/templates/pvc.yaml | 36 ++ helm/templates/secret.yaml | 81 +++ helm/templates/service.yaml | 67 +++ helm/templates/statefulset.yaml | 126 +++++ helm/values.schema.json | 604 ++++++++++++++++++++++ helm/values.yaml | 878 ++++++++++++++++++++++++++++++++ 15 files changed, 3265 insertions(+) create mode 100644 helm/Chart.lock create mode 100644 helm/Chart.yaml create mode 100644 helm/README.md create mode 100644 helm/charts/common-2.31.4.tgz create mode 100644 helm/templates/NOTES.txt create mode 100644 helm/templates/_helpers.tpl create mode 100644 helm/templates/configmap.yaml create mode 100644 helm/templates/deployment.yaml create mode 100644 helm/templates/ingress.yaml create mode 100644 helm/templates/pvc.yaml create mode 100644 helm/templates/secret.yaml create mode 100644 helm/templates/service.yaml create mode 100644 helm/templates/statefulset.yaml create mode 100644 helm/values.schema.json create mode 100644 helm/values.yaml diff --git a/helm/Chart.lock b/helm/Chart.lock new file mode 100644 index 00000000..b6a9ee2a --- /dev/null +++ b/helm/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: common + repository: oci://registry-1.docker.io/bitnamicharts + version: 2.31.4 +digest: sha256:0df1987bc04bbeb530d28418e1dcb4d999985dc1559351db58db16c260c73deb +generated: "2025-12-26T18:20:00.665704+07:00" diff --git a/helm/Chart.yaml b/helm/Chart.yaml new file mode 100644 index 00000000..5f801a13 --- /dev/null +++ b/helm/Chart.yaml @@ -0,0 +1,26 @@ +apiVersion: v2 +name: open-notebook +description: Open Notebook - AI-powered note-taking application with SurrealDB backend +type: application +version: 1.0.0 +appVersion: "v1-latest" +keywords: + - notebook + - ai + - knowledge-management + - surrealdb +home: https://github.com/lfnovo/open-notebook +icon: https://raw.githubusercontent.com/lfnovo/open-notebook/main/docs/assets/logo.png +sources: + - https://github.com/lfnovo/open-notebook +maintainers: + - name: lfnovo + email: open-notebook@example.com +dependencies: + - name: common + repository: oci://registry-1.docker.io/bitnamicharts + version: 2.x.x + condition: global.commonChartEnabled +annotations: + category: Analytics + licenses: Apache-2.0 diff --git a/helm/README.md b/helm/README.md new file mode 100644 index 00000000..7231aa20 --- /dev/null +++ b/helm/README.md @@ -0,0 +1,632 @@ +# Open Notebook Helm Chart + +Open Notebook - AI-powered note-taking application with SurrealDB backend. + +## Introduction + +This chart bootstraps an [Open Notebook](https://github.com/lfnovo/open-notebook) deployment on a [Kubernetes](https://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager. + +Open Notebook is a privacy-focused, AI-powered research and note-taking tool that helps you: +- Organize research across multiple notebooks +- Chat with your documents using AI +- Support 17 AI providers (OpenAI, Anthropic, Google, Azure, Ollama, and more) +- Create AI-generated podcasts from your content +- Works with PDFs, web links, videos, audio files, and more + +## Prerequisites + +- Kubernetes 1.23+ +- Helm 3.0+ +- PV provisioner support in the underlying infrastructure (for persistence) + +## Installing the Chart + +To install the chart with the release name `open-notebook`: + +```bash +helm install open-notebook ./helm/open-notebook +``` + +## Uninstalling the Chart + +To uninstall/delete the `open-notebook` deployment: + +```bash +helm uninstall open-notebook +``` + +## Configuration + +See [values.yaml](values.yaml) for comprehensive configuration with Bitnami-style documentation. Key parameters include: + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `image.registry` | Image registry | `docker.io` | +| `image.repository` | Image repository | `lfnovo/open_notebook` | +| `image.tag` | Image tag (overrides appVersion) | `""` | +| `image.pullPolicy` | Image pull policy | `IfNotPresent` | +| `surrealdb.enabled` | Deploy internal SurrealDB | `true` | +| `surrealdb.external.enabled` | Use external SurrealDB | `false` | +| `surrealdb.auth.user` | SurrealDB username | `root` | +| `surrealdb.auth.password` | SurrealDB password | `root` | +| `app.replicaCount` | Number of replicas | `1` | +| `app.ports.http` | HTTP frontend port | `8502` | +| `app.ports.api` | API server port | `5055` | +| `service.type` | Kubernetes service type | `ClusterIP` | +| `ingress.enabled` | Enable ingress | `false` | +| `secrets.create` | Create secrets automatically | `true` | +| `config.aiProviders.*` | AI provider configurations | See below | + +## AI Provider Configuration + +Open Notebook supports 17 AI providers across LLM, embedding, speech-to-text, and text-to-speech: + +### LLM Providers (10) +- **OpenAI** - GPT-4, GPT-4 Turbo, GPT-3.5 +- **Anthropic** - Claude 3 Opus, Sonnet, Haiku +- **Google Gemini** - Gemini Pro, Gemini Vision +- **Vertex AI** - Google Cloud Vertex AI models +- **Mistral** - Mistral Large, Medium, Small +- **DeepSeek** - DeepSeek Chat, DeepSeek Coder +- **Ollama** - Local OpenAI-compatible models +- **OpenRouter** - Multiple open-source models +- **Groq** - Llama 3, Mixtral, Gemma +- **XAI** - Grok models + +### Multi-Modal Providers (2) +- **Azure OpenAI** - Different deployments for LLM/embedding/STT/TTS +- **OpenAI-Compatible** - Azure, FPT Cloud, or other compatible APIs + +### Specialized Providers (5) +- **ElevenLabs** - Text-to-speech for podcast generation +- **Voyage AI** - Specialized embeddings +- **Firecrawl** - Web scraping and content extraction +- **Jina** - Content processing and embeddings +- **LangChain** - Debugging and tracing + +### Quick Configuration + +```yaml +config: + aiProviders: + # OpenAI + openai: + apiKey: "sk-your-openai-key" + + # Anthropic + anthropic: + apiKey: "sk-ant-your-anthropic-key" + + # Google Gemini + google: + apiKey: "your-google-api-key" + geminiBaseUrl: "" # Optional: custom endpoint + + # Azure OpenAI + azureOpenai: + apiKey: "your-azure-key" + endpoint: "https://your-resource.openai.azure.com" + apiVersion: "2024-12-01-preview" + + # OpenAI-compatible (e.g., FPT Cloud) + openaiCompatible: + baseUrl: "https://api.example.com/v1" + apiKey: "your-api-key" +``` + +### Automatic Secret Creation + +Enable automatic secret creation for API keys: + +```yaml +secrets: + create: true + +config: + aiProviders: + openai: + apiKey: "sk-your-key" # Will be added to secret automatically +``` + +## Usage Examples + +### Basic Installation with OpenAI + +```bash +helm install open-notebook ./helm/open-notebook \ + --set config.aiProviders.openai.apiKey=sk-xxx +``` + +### Multiple AI Providers + +```bash +helm install open-notebook ./helm/open-notebook \ + --set config.aiProviders.openai.apiKey=sk-xxx \ + --set config.aiProviders.anthropic.apiKey=sk-ant-xxx \ + --set config.aiProviders.google.apiKey=your-google-key +``` + +### Using OpenAI-Compatible Endpoint (Azure/FPT Cloud) + +```yaml +config: + aiProviders: + openaiCompatible: + baseUrl: "https://mkp-api.fptcloud.com/v1" + apiKey: "sk-your-key" +``` + +### Vertex AI Configuration + +```yaml +config: + aiProviders: + vertexai: + project: "your-gcp-project" + credentials: "base64-encoded-service-account-key" + location: "us-east5" +``` + +### Using External SurrealDB + +```bash +helm install open-notebook ./helm/open-notebook \ + --set surrealdb.enabled=false \ + --set surrealdb.external.enabled=true \ + --set surrealdb.external.host=surrealdb.example.com \ + --set surrealdb.external.port=8000 +``` + +### With NodePort Service + +```yaml +service: + type: NodePort + ports: + http: 8502 + api: 5055 + httpNodePort: 30852 + apiNodePort: 30555 + +config: + api: + url: "http://your-node-ip:30555" +``` + +### With Ingress and TLS + +```bash +helm install open-notebook ./helm/open-notebook \ + --set ingress.enabled=true \ + --set ingress.className=nginx \ + --set ingress.hosts[0].host=notebook.example.com \ + --set ingress.tls[0].secretName=notebook-tls \ + --set ingress.annotations."cert-manager.io/cluster-issuer"=letsencrypt-prod \ + --set config.api.url=https://notebook.example.com +``` + +### Custom Resources + +```yaml +app: + resources: + limits: + cpu: 1000m + memory: 1Gi + requests: + cpu: 500m + memory: 512Mi + +surrealdb: + resources: + limits: + cpu: 500m + memory: 512Mi + requests: + cpu: 250m + memory: 256Mi +``` + +### Prometheus Monitoring + +Enable ServiceMonitor for Prometheus Operator: + +```yaml +serviceMonitor: + enabled: true + namespace: monitoring + interval: 30s +``` + +### Horizontal Scaling + +Requires external SurrealDB: + +```yaml +app: + replicaCount: 3 + +surrealdb: + enabled: false + external: + enabled: true + host: external-surrealdb.example.com + port: 8000 +``` + +## Automatic Secret Management + +The chart can automatically create secrets for sensitive data: + +```yaml +secrets: + create: true # Enable automatic secret creation + +config: + aiProviders: + openai: + apiKey: "sk-xxx" # Automatically added to secret + anthropic: + apiKey: "sk-ant-xxx" # Automatically added to secret +``` + +Created secret: `open-notebook-secret` containing: +- `OPENAI_API_KEY` +- `ANTHROPIC_API_KEY` +- `GOOGLE_API_KEY` +- `SURREAL_PASSWORD` +- And all other configured keys + +### Using Existing Secrets + +For production, use existing secrets: + +```bash +# Create secret manually +kubectl create secret generic open-notebook-secrets \ + --from-literal=OPENAI_API_KEY=sk-xxx \ + --from-literal=ANTHROPIC_API_KEY=sk-ant-xxx + +# Reference the secret +helm install open-notebook ./helm/open-notebook \ + --set secrets.create=false \ + --set secrets.existingSecret=open-notebook-secrets +``` + +## Persistence + +The chart supports persistent storage for both the application and SurrealDB: + +- **App Data**: `/app/data` - Stores notebooks, sources, notes, and generated content +- **SurrealDB Data**: `/mydata` - Stores the SurrealDB database files + +Both are enabled by default with 8Gi PVCs. Customize the size and storage class: + +```yaml +app: + persistence: + enabled: true + size: 20Gi + storageClass: fast-ssd + mountPath: /app/data + +surrealdb: + persistence: + enabled: true + size: 20Gi + storageClass: fast-ssd + mountPath: /mydata +``` + +### Using Existing PVCs + +```yaml +app: + persistence: + existingClaim: open-notebook-data + +surrealdb: + persistence: + existingClaim: surrealdb-data +``` + +## Security + +### Password Protection + +Enable password protection: + +```bash +helm install open-notebook ./helm/open-notebook \ + --set config.auth.password=your-secure-password +``` + +### Security Contexts + +Pod and container security contexts are configurable: + +```yaml +app: + podSecurityContext: + enabled: true + fsGroup: 1001 + runAsUser: 1001 + + containerSecurityContext: + enabled: true + runAsNonRoot: true + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL +``` + +### SSL/TLS Configuration + +```yaml +config: + ssl: + caBundle: /path/to/ca-bundle.crt + verify: true +``` + +## Health Checks + +Configurable probes for application health monitoring: + +```yaml +app: + livenessProbe: + enabled: true + path: /health + port: api + initialDelaySeconds: 60 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + + readinessProbe: + enabled: true + path: /health + port: api + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + + startupProbe: + enabled: false + path: /health + port: api + initialDelaySeconds: 0 + failureThreshold: 30 +``` + +## Networking + +### Ingress Configuration + +Enable ingress to expose Open Notebook via a domain name: + +```yaml +ingress: + enabled: true + className: nginx + annotations: + cert-manager.io/cluster-issuer: letsencrypt-prod + nginx.ingress.kubernetes.io/ssl-redirect: "true" + hosts: + - host: notebook.example.com + paths: + - path: / + pathType: Prefix + service: http + tls: + - secretName: notebook-tls + hosts: + - notebook.example.com +``` + +### Network Policy + +Enable network policies to control pod traffic: + +```yaml +networkPolicy: + enabled: true + policyTypes: + - Ingress + - Egress + ingress: + - from: + - podSelector: {} + egress: + - to: + - podSelector: {} +``` + +## Advanced Configuration + +### Worker Configuration + +Configure background worker behavior: + +```yaml +config: + worker: + maxTasks: 5 + retry: + enabled: true + maxAttempts: 3 + waitStrategy: exponential_jitter + waitMin: 1 + waitMax: 30 +``` + +### Pod Disruption Budget + +Ensure availability during disruptions: + +```yaml +app: + podDisruptionBudget: + enabled: true + minAvailable: 1 + # or + maxUnavailable: 1 +``` + +### Service Account + +```yaml +app: + serviceAccount: + create: true + name: open-notebook + automountServiceAccountToken: false + annotations: {} +``` + +### Node Selector and Tolerations + +```yaml +app: + nodeSelector: + workload-type: gpu + + tolerations: + - key: "nvidia.com/gpu" + operator: "Exists" + effect: "NoSchedule" +``` + +## Upgrading + +To upgrade your Helm release: + +```bash +helm upgrade open-notebook ./helm/open-notebook +``` + +To upgrade with custom values: + +```bash +helm upgrade open-notebook ./helm/open-notebook \ + --set config.aiProviders.openai.apiKey=new-key \ + --set image.tag=v1.1.0 +``` + +### Upgrade Strategy + +Control deployment update behavior: + +```yaml +app: + updateStrategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 +``` + +## Rollback + +To rollback to a previous revision: + +```bash +helm rollback open-notebook +``` + +Or rollback to a specific revision: + +```bash +helm rollback open-notebook 2 +``` + +## Troubleshooting + +### Check Pod Status + +```bash +kubectl get pods -l app.kubernetes.io/name=open-notebook +kubectl get pods -l app.kubernetes.io/name=open-notebook-surrealdb +``` + +### View Logs + +```bash +# Application logs +kubectl logs -l app.kubernetes.io/name=open-notebook --tail=100 -f + +# SurrealDB logs +kubectl logs -l app.kubernetes.io/name=open-notebook-surrealdb --tail=100 -f +``` + +### Port Forward to Access + +```bash +# Forward UI +kubectl port-forward svc/open-notebook 8502:8502 + +# Forward API +kubectl port-forward svc/open-notebook 5055:5055 + +# Access at http://localhost:8502 +``` + +### SurrealDB Connection Issues + +If the application can't connect to SurrealDB: + +```bash +# Check SurrealDB is running +kubectl get pods -l app.kubernetes.io/name=open-notebook-surrealdb + +# Port forward to SurrealDB +kubectl port-forward svc/open-notebook-surrealdb 8000:8000 + +# Test connection +surreal sql --endpoint ws://localhost:8000/rpc --namespace open_notebook --database production --user root --pass root +``` + +### Check Secrets + +```bash +# List secrets +kubectl get secrets -n open-notebook + +# View secret (base64 decoded) +kubectl get secret open-notebook-secret -o jsonpath='{.data.OPENAI_API_KEY}' | base64 -d +``` + +## Metrics and Monitoring + +### Prometheus Monitoring + +Enable ServiceMonitor for Prometheus Operator: + +```yaml +serviceMonitor: + enabled: true + namespace: monitoring + interval: 30s + scrapeTimeout: 10s +``` + +Metrics will be available at: +- `http://open-notebook:8502/metrics` (application metrics) +- SurrealDB metrics can be enabled via experimental features + +## License + +Apache License 2.0 + +## Support + +- GitHub: https://github.com/lfnovo/open-notebook +- Issues: https://github.com/lfnovo/open-notebook/issues +- Discord: https://discord.gg/37XJPXfz2w + +## Contributing + +Contributions are welcome! Please feel free to submit a Pull Request. diff --git a/helm/charts/common-2.31.4.tgz b/helm/charts/common-2.31.4.tgz new file mode 100644 index 0000000000000000000000000000000000000000..8c7e4d542f6ecd7c9ca48b016fb8598b2c714064 GIT binary patch literal 20003 zcmV(@K-Rw>iwFP!00000|LuL-b{j{MU_aw4av}AIu!#b|tD8NV9*UA}ZtFIrEN_qH zF$+KeD6>!nR~1OXmiEja?E9SEbLKPld-@0V7d9gDUN;m-kfJ=SnU=w-yhKJsMn*

+N=T_V#vm;eV{#>+SD#|G;){6e{t| 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.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + pathType: {{ .pathType }} + backend: + {{- if .service }} + service: + name: {{ include "common.names.fullname" $ }} + port: + name: {{ .service }} + {{- else }} + service: + name: {{ include "common.names.fullname" $ }} + port: + number: {{ $.Values.service.ports.http }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} diff --git a/helm/templates/pvc.yaml b/helm/templates/pvc.yaml new file mode 100644 index 00000000..28ce6b86 --- /dev/null +++ b/helm/templates/pvc.yaml @@ -0,0 +1,36 @@ +{{- /* +Copyright © 2024 Open Notebook Contributors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ -}} +{{- if and .Values.app.persistence.enabled (not .Values.app.persistence.existingClaim) }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "common.names.fullname" . }} + namespace: {{ include "common.names.namespace" . }} + labels: {{- include "open-notebook.labels" . | nindent 4 }} +spec: + accessModes: + - {{ .Values.app.persistence.accessMode }} + {{- if .Values.app.persistence.storageClass }} + {{- if (eq "-" .Values.app.persistence.storageClass) }} + storageClassName: "" + {{- else }} + storageClassName: {{ .Values.app.persistence.storageClass }} + {{- end }} + {{- end }} + resources: + requests: + storage: {{ .Values.app.persistence.size }} +{{- end }} diff --git a/helm/templates/secret.yaml b/helm/templates/secret.yaml new file mode 100644 index 00000000..8f72d34a --- /dev/null +++ b/helm/templates/secret.yaml @@ -0,0 +1,81 @@ +{{- /* +Copyright © 2024 Open Notebook Contributors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ -}} +{{- $secretName := printf "%s-secret" (include "common.names.fullname" .) }} +{{- if and .Values.secrets.create (not .Values.secrets.existingSecret) }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ $secretName }} + namespace: {{ include "common.names.namespace" . }} + labels: {{- include "open-notebook.labels" . | nindent 4 }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.secrets.annotations "context" $ ) | nindent 4 }} +type: Opaque +stringData: + {{- if .Values.config.aiProviders.openai.apiKey }} + {{ .Values.config.aiProviders.openai.secretKey | default "openai-api-key" }}: {{ .Values.config.aiProviders.openai.apiKey | quote }} + {{- end }} + {{- if .Values.config.aiProviders.anthropic.apiKey }} + {{ .Values.config.aiProviders.anthropic.secretKey | default "anthropic-api-key" }}: {{ .Values.config.aiProviders.anthropic.apiKey | quote }} + {{- end }} + {{- if .Values.config.aiProviders.google.apiKey }} + {{ .Values.config.aiProviders.google.secretKey | default "google-api-key" }}: {{ .Values.config.aiProviders.google.apiKey | quote }} + {{- end }} + {{- if .Values.config.aiProviders.mistral.apiKey }} + {{ .Values.config.aiProviders.mistral.secretKey | default "mistral-api-key" }}: {{ .Values.config.aiProviders.mistral.apiKey | quote }} + {{- end }} + {{- if .Values.config.aiProviders.deepseek.apiKey }} + {{ .Values.config.aiProviders.deepseek.secretKey | default "deepseek-api-key" }}: {{ .Values.config.aiProviders.deepseek.apiKey | quote }} + {{- end }} + {{- if .Values.config.aiProviders.openrouter.apiKey }} + {{ .Values.config.aiProviders.openrouter.secretKey | default "openrouter-api-key" }}: {{ .Values.config.aiProviders.openrouter.apiKey | quote }} + {{- end }} + {{- if .Values.config.aiProviders.groq.apiKey }} + {{ .Values.config.aiProviders.groq.secretKey | default "groq-api-key" }}: {{ .Values.config.aiProviders.groq.apiKey | quote }} + {{- end }} + {{- if .Values.config.aiProviders.xai.apiKey }} + {{ .Values.config.aiProviders.xai.secretKey | default "xai-api-key" }}: {{ .Values.config.aiProviders.xai.apiKey | quote }} + {{- end }} + {{- if .Values.config.aiProviders.elevenlabs.apiKey }} + {{ .Values.config.aiProviders.elevenlabs.secretKey | default "elevenlabs-api-key" }}: {{ .Values.config.aiProviders.elevenlabs.apiKey | quote }} + {{- end }} + {{- if .Values.config.aiProviders.voyage.apiKey }} + {{ .Values.config.aiProviders.voyage.secretKey | default "voyage-api-key" }}: {{ .Values.config.aiProviders.voyage.apiKey | quote }} + {{- end }} + {{- if .Values.config.aiProviders.firecrawl.apiKey }} + {{ .Values.config.aiProviders.firecrawl.secretKey | default "firecrawl-api-key" }}: {{ .Values.config.aiProviders.firecrawl.apiKey | quote }} + {{- end }} + {{- if .Values.config.aiProviders.jina.apiKey }} + {{ .Values.config.aiProviders.jina.secretKey | default "jina-api-key" }}: {{ .Values.config.aiProviders.jina.apiKey | quote }} + {{- end }} + {{- if .Values.config.aiProviders.langchain.apiKey }} + {{ .Values.config.aiProviders.langchain.secretKey | default "langchain-api-key" }}: {{ .Values.config.aiProviders.langchain.apiKey | quote }} + {{- end }} + {{- if .Values.config.aiProviders.openaiCompatible.apiKey }} + OPENAI_COMPATIBLE_API_KEY: {{ .Values.config.aiProviders.openaiCompatible.apiKey | quote }} + {{- end }} + {{- if .Values.config.aiProviders.azureOpenai.apiKey }} + AZURE_OPENAI_API_KEY: {{ .Values.config.aiProviders.azureOpenai.apiKey | quote }} + {{- end }} + {{- if .Values.config.aiProviders.vertexai.credentials }} + GOOGLE_APPLICATION_CREDENTIALS_JSON: {{ .Values.config.aiProviders.vertexai.credentials | quote }} + {{- end }} + {{- if .Values.config.auth.password }} + {{ .Values.config.auth.secretKey | default "open-notebook-password" }}: {{ .Values.config.auth.password | quote }} + {{- end }} + {{- if .Values.surrealdb.auth.password }} + surreal-password: {{ .Values.surrealdb.auth.password | quote }} + {{- end }} +{{- end }} diff --git a/helm/templates/service.yaml b/helm/templates/service.yaml new file mode 100644 index 00000000..c1c2a36f --- /dev/null +++ b/helm/templates/service.yaml @@ -0,0 +1,67 @@ +{{- /* +Copyright © 2024 Open Notebook Contributors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ -}} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ include "common.names.fullname" . }} + namespace: {{ include "common.names.namespace" . }} + labels: {{- include "open-notebook.labels" . | nindent 4 }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.service.annotations "context" $ ) | nindent 4 }} +spec: + type: {{ .Values.service.type }} + sessionAffinity: {{ .Values.service.sessionAffinity }} + {{- if .Values.service.sessionAffinityConfig }} + sessionAffinityConfig: {{- include "common.tplvalues.render" (dict "value" .Values.service.sessionAffinityConfig "context" $) | nindent 4 }} + {{- end }} + ports: + - name: http + port: {{ .Values.service.ports.http }} + targetPort: http + protocol: TCP + {{- if and (eq .Values.service.type "NodePort") .Values.service.ports.httpNodePort }} + nodePort: {{ .Values.service.ports.httpNodePort }} + {{- end }} + - name: api + port: {{ .Values.service.ports.api }} + targetPort: api + protocol: TCP + {{- if and (eq .Values.service.type "NodePort") .Values.service.ports.apiNodePort }} + nodePort: {{ .Values.service.ports.apiNodePort }} + {{- end }} + selector: {{- include "open-notebook.selectorLabels" . | nindent 4 }} + +--- +{{- if .Values.surrealdb.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "common.names.fullname" . }}-surrealdb + namespace: {{ include "common.names.namespace" . }} + labels: {{- include "open-notebook.surrealdb.labels" . | nindent 4 }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.surrealdb.service.annotations "context" $ ) | nindent 4 }} +spec: + type: {{ .Values.surrealdb.service.type }} + ports: + - name: http + port: {{ .Values.surrealdb.service.port }} + targetPort: http + protocol: TCP + {{- if and (eq .Values.surrealdb.service.type "NodePort") .Values.surrealdb.service.nodePort }} + nodePort: {{ .Values.surrealdb.service.nodePort }} + {{- end }} + selector: {{- include "open-notebook.surrealdb.selectorLabels" . | nindent 4 }} +{{- end }} diff --git a/helm/templates/statefulset.yaml b/helm/templates/statefulset.yaml new file mode 100644 index 00000000..b783e80e --- /dev/null +++ b/helm/templates/statefulset.yaml @@ -0,0 +1,126 @@ +{{- /* +Copyright © 2024 Open Notebook Contributors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ -}} +{{- if .Values.surrealdb.enabled }} +apiVersion: {{ include "common.capabilities.statefulset.apiVersion" . }} +kind: StatefulSet +metadata: + name: {{ include "common.names.fullname" . }}-surrealdb + namespace: {{ include "common.names.namespace" . }} + labels: {{- include "open-notebook.surrealdb.labels" . | nindent 4 }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.global.annotations "context" $ ) | nindent 4 }} +spec: + replicas: 1 + selector: + matchLabels: {{- include "open-notebook.surrealdb.selectorLabels" . | nindent 6 }} + serviceName: {{ include "common.names.fullname" . }}-surrealdb + template: + metadata: + labels: {{- include "open-notebook.surrealdb.selectorLabels" . | nindent 8 }} + {{- if .Values.surrealdb.podLabels }} + {{- include "common.tplvalues.render" (dict "value" .Values.surrealdb.podLabels "context" $) | nindent 8 }} + {{- end }} + annotations: + {{- if .Values.surrealdb.podAnnotations }} + {{- include "common.tplvalues.render" (dict "value" .Values.surrealdb.podAnnotations "context" $) | nindent 8 }} + {{- end }} + spec: + {{- include "common.images.pullSecrets" . | nindent 6 }} + {{- if .Values.surrealdb.podSecurityContext.enabled }} + securityContext: {{- include "common.compatibility.renderSecurityContext" (dict "secContext" .Values.surrealdb.podSecurityContext "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.surrealdb.priorityClassName }} + priorityClassName: {{ .Values.surrealdb.priorityClassName | quote }} + {{- end }} + containers: + - name: surrealdb + image: {{ include "open-notebook.surrealdb.image" . }} + imagePullPolicy: {{ .Values.surrealdb.image.pullPolicy }} + command: + - /surreal + - start + - --log + - info + - --user + - {{ .Values.surrealdb.auth.user }} + - --pass + - {{ .Values.surrealdb.auth.password }} + - -- + - rocksdb:/mydata/mydatabase.db + env: + {{- if .Values.surrealdb.experimental.graphql }} + - name: SURREAL_EXPERIMENTAL_GRAPHQL + value: "true" + {{- end }} + ports: + - name: http + containerPort: {{ .Values.surrealdb.service.port }} + protocol: TCP + {{- if .Values.surrealdb.livenessProbe.enabled }} + livenessProbe: + tcpSocket: + port: http + initialDelaySeconds: {{ .Values.surrealdb.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.surrealdb.livenessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.surrealdb.livenessProbe.timeoutSeconds }} + failureThreshold: {{ .Values.surrealdb.livenessProbe.failureThreshold }} + successThreshold: {{ .Values.surrealdb.livenessProbe.successThreshold }} + {{- end }} + {{- if .Values.surrealdb.readinessProbe.enabled }} + readinessProbe: + tcpSocket: + port: http + initialDelaySeconds: {{ .Values.surrealdb.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.surrealdb.readinessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.surrealdb.readinessProbe.timeoutSeconds }} + failureThreshold: {{ .Values.surrealdb.readinessProbe.failureThreshold }} + successThreshold: {{ .Values.surrealdb.readinessProbe.successThreshold }} + {{- end }} + {{- if .Values.surrealdb.resources }} + resources: {{- include "common.tplvalues.render" (dict "value" .Values.surrealdb.resources "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.surrealdb.containerSecurityContext.enabled }} + securityContext: {{- include "common.compatibility.renderSecurityContext" (dict "secContext" .Values.surrealdb.containerSecurityContext "context" $) | nindent 12 }} + {{- end }} + volumeMounts: + - name: surrealdb-data + mountPath: {{ .Values.surrealdb.persistence.mountPath }} + {{- if .Values.surrealdb.nodeSelector }} + nodeSelector: {{- include "common.tplvalues.render" (dict "value" .Values.surrealdb.nodeSelector "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.surrealdb.affinity }} + affinity: {{- include "common.tplvalues.render" (dict "value" .Values.surrealdb.affinity "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.surrealdb.tolerations }} + tolerations: {{- include "common.tplvalues.render" (dict "value" .Values.surrealdb.tolerations "context" $) | nindent 8 }} + {{- end }} + volumeClaimTemplates: + - metadata: + name: surrealdb-data + labels: {{- include "open-notebook.surrealdb.labels" . | nindent 10 }} + spec: + accessModes: + - {{ .Values.surrealdb.persistence.accessMode }} + {{- if .Values.surrealdb.persistence.storageClass }} + {{- if (eq "-" .Values.surrealdb.persistence.storageClass) }} + storageClassName: "" + {{- else }} + storageClassName: {{ .Values.surrealdb.persistence.storageClass }} + {{- end }} + {{- end }} + resources: + requests: + storage: {{ .Values.surrealdb.persistence.size }} +{{- end }} diff --git a/helm/values.schema.json b/helm/values.schema.json new file mode 100644 index 00000000..1568d406 --- /dev/null +++ b/helm/values.schema.json @@ -0,0 +1,604 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Open Notebook Helm Chart Values", + "type": "object", + "properties": { + "global": { + "type": "object", + "properties": { + "commonChartEnabled": { "type": "boolean" }, + "imageRegistry": { "type": "string" }, + "imagePullSecrets": { "type": "array" }, + "storageClass": { "type": "string" }, + "labels": { "type": "object" }, + "annotations": { "type": "object" }, + "clusterDomain": { "type": "string" } + } + }, + "image": { + "type": "object", + "properties": { + "registry": { "type": "string" }, + "repository": { "type": "string" }, + "tag": { "type": "string" }, + "digest": { "type": "string" }, + "pullPolicy": { "enum": ["Always", "IfNotPresent", "Never"] } + } + }, + "surrealdb": { + "type": "object", + "properties": { + "enabled": { "type": "boolean" }, + "external": { + "type": "object", + "properties": { + "enabled": { "type": "boolean" }, + "host": { "type": "string" }, + "port": { "type": "integer", "minimum": 1, "maximum": 65535 }, + "secure": { "type": "boolean" } + } + }, + "image": { + "type": "object", + "properties": { + "registry": { "type": "string" }, + "repository": { "type": "string" }, + "tag": { "type": "string" }, + "digest": { "type": "string" }, + "pullPolicy": { "enum": ["Always", "IfNotPresent", "Never"] } + } + }, + "auth": { + "type": "object", + "properties": { + "user": { "type": "string" }, + "password": { "type": "string" }, + "existingSecret": { "type": "string" }, + "secretKey": { "type": "string" } + } + }, + "database": { + "type": "object", + "properties": { + "namespace": { "type": "string" }, + "database": { "type": "string" } + } + }, + "experimental": { + "type": "object", + "properties": { + "graphql": { "type": "boolean" } + } + }, + "persistence": { + "type": "object", + "properties": { + "enabled": { "type": "boolean" }, + "existingClaim": { "type": "string" }, + "storageClass": { "type": "string" }, + "accessMode": { "type": "string" }, + "size": { "type": "string" }, + "mountPath": { "type": "string" } + } + }, + "service": { + "type": "object", + "properties": { + "type": { "enum": ["ClusterIP", "LoadBalancer", "NodePort"] }, + "port": { "type": "integer", "minimum": 1, "maximum": 65535 }, + "nodePort": { "type": "string" }, + "annotations": { "type": "object" } + } + }, + "resources": { + "type": "object", + "properties": { + "limits": { "type": "object" }, + "requests": { "type": "object" } + } + }, + "livenessProbe": { + "type": "object", + "properties": { + "enabled": { "type": "boolean" }, + "initialDelaySeconds": { "type": "integer", "minimum": 0 }, + "periodSeconds": { "type": "integer", "minimum": 1 }, + "timeoutSeconds": { "type": "integer", "minimum": 1 }, + "failureThreshold": { "type": "integer", "minimum": 1 }, + "successThreshold": { "type": "integer", "minimum": 1 } + } + }, + "readinessProbe": { + "type": "object", + "properties": { + "enabled": { "type": "boolean" }, + "initialDelaySeconds": { "type": "integer", "minimum": 0 }, + "periodSeconds": { "type": "integer", "minimum": 1 }, + "timeoutSeconds": { "type": "integer", "minimum": 1 }, + "failureThreshold": { "type": "integer", "minimum": 1 }, + "successThreshold": { "type": "integer", "minimum": 1 } + } + }, + "podAnnotations": { "type": "object" }, + "podLabels": { "type": "object" }, + "podSecurityContext": { + "type": "object", + "properties": { + "enabled": { "type": "boolean" }, + "fsGroup": { "type": "integer", "minimum": 0 }, + "runAsUser": { "type": "integer", "minimum": 0 } + } + }, + "containerSecurityContext": { + "type": "object", + "properties": { + "enabled": { "type": "boolean" }, + "runAsNonRoot": { "type": "boolean" }, + "allowPrivilegeEscalation": { "type": "boolean" }, + "capabilities": { + "type": "object", + "properties": { + "drop": { "type": "array" } + } + } + } + }, + "nodeSelector": { "type": "object" }, + "tolerations": { "type": "array" }, + "affinity": { "type": "object" }, + "priorityClassName": { "type": "string" } + } + }, + "app": { + "type": "object", + "properties": { + "nameOverride": { "type": "string" }, + "fullnameOverride": { "type": "string" }, + "replicaCount": { "type": "integer", "minimum": 0 }, + "updateStrategy": { + "type": "object", + "properties": { + "type": { "enum": ["RollingUpdate", "Recreate"] }, + "rollingUpdate": { + "type": "object", + "properties": { + "maxSurge": { "type": ["integer", "string"] }, + "maxUnavailable": { "type": ["integer", "string"] } + } + } + } + }, + "ports": { + "type": "object", + "properties": { + "http": { "type": "integer", "minimum": 1, "maximum": 65535 }, + "api": { "type": "integer", "minimum": 1, "maximum": 65535 } + } + }, + "persistence": { + "type": "object", + "properties": { + "enabled": { "type": "boolean" }, + "existingClaim": { "type": "string" }, + "storageClass": { "type": "string" }, + "accessMode": { "type": "string" }, + "size": { "type": "string" }, + "mountPath": { "type": "string" } + } + }, + "command": { "type": "array" }, + "args": { "type": "array" }, + "extraEnvVars": { "type": "array" }, + "extraEnvVarsCM": { "type": "string" }, + "extraEnvVarsSecret": { "type": "string" }, + "livenessProbe": { + "type": "object", + "properties": { + "enabled": { "type": "boolean" }, + "probeType": { "enum": ["httpGet", "tcpSocket", "exec"] }, + "path": { "type": "string" }, + "port": { "type": ["string", "integer"] }, + "initialDelaySeconds": { "type": "integer", "minimum": 0 }, + "periodSeconds": { "type": "integer", "minimum": 1 }, + "timeoutSeconds": { "type": "integer", "minimum": 1 }, + "failureThreshold": { "type": "integer", "minimum": 1 }, + "successThreshold": { "type": "integer", "minimum": 1 } + } + }, + "readinessProbe": { + "type": "object", + "properties": { + "enabled": { "type": "boolean" }, + "probeType": { "enum": ["httpGet", "tcpSocket", "exec"] }, + "path": { "type": "string" }, + "port": { "type": ["string", "integer"] }, + "initialDelaySeconds": { "type": "integer", "minimum": 0 }, + "periodSeconds": { "type": "integer", "minimum": 1 }, + "timeoutSeconds": { "type": "integer", "minimum": 1 }, + "failureThreshold": { "type": "integer", "minimum": 1 }, + "successThreshold": { "type": "integer", "minimum": 1 } + } + }, + "startupProbe": { + "type": "object", + "properties": { + "enabled": { "type": "boolean" }, + "probeType": { "enum": ["httpGet", "tcpSocket", "exec"] }, + "path": { "type": "string" }, + "port": { "type": ["string", "integer"] }, + "initialDelaySeconds": { "type": "integer", "minimum": 0 }, + "periodSeconds": { "type": "integer", "minimum": 1 }, + "timeoutSeconds": { "type": "integer", "minimum": 1 }, + "failureThreshold": { "type": "integer", "minimum": 1 }, + "successThreshold": { "type": "integer", "minimum": 1 } + } + }, + "resources": { + "type": "object", + "properties": { + "limits": { "type": "object" }, + "requests": { "type": "object" } + } + }, + "podAnnotations": { "type": "object" }, + "podLabels": { "type": "object" }, + "podSecurityContext": { + "type": "object", + "properties": { + "enabled": { "type": "boolean" }, + "fsGroup": { "type": "integer", "minimum": 0 } + } + }, + "containerSecurityContext": { + "type": "object", + "properties": { + "enabled": { "type": "boolean" }, + "runAsNonRoot": { "type": "boolean" }, + "allowPrivilegeEscalation": { "type": "boolean" }, + "capabilities": { + "type": "object", + "properties": { + "drop": { "type": "array" } + } + } + } + }, + "nodeSelector": { "type": "object" }, + "tolerations": { "type": "array" }, + "affinity": { "type": "object" }, + "priorityClassName": { "type": "string" }, + "serviceAccount": { + "type": "object", + "properties": { + "create": { "type": "boolean" }, + "name": { "type": "string" }, + "automountServiceAccountToken": { "type": "boolean" }, + "annotations": { "type": "object" } + } + }, + "podDisruptionBudget": { + "type": "object", + "properties": { + "enabled": { "type": "boolean" }, + "minAvailable": { "type": ["integer", "string"] }, + "maxUnavailable": { "type": ["integer", "string"] } + } + } + } + }, + "service": { + "type": "object", + "properties": { + "type": { "enum": ["ClusterIP", "LoadBalancer", "NodePort"] }, + "ports": { + "type": "object", + "properties": { + "http": { "type": "integer", "minimum": 1, "maximum": 65535 }, + "api": { "type": "integer", "minimum": 1, "maximum": 65535 }, + "httpNodePort": { "type": "string" }, + "apiNodePort": { "type": "string" } + } + }, + "annotations": { "type": "object" }, + "sessionAffinity": { "enum": ["ClientIP", "None"] }, + "sessionAffinityConfig": { "type": "object" } + } + }, + "ingress": { + "type": "object", + "properties": { + "enabled": { "type": "boolean" }, + "className": { "type": "string" }, + "annotations": { "type": "object" }, + "hosts": { "type": "array" }, + "tls": { "type": "array" } + } + }, + "config": { + "type": "object", + "properties": { + "api": { + "type": "object", + "properties": { + "url": { "type": "string" }, + "internalUrl": { "type": "string" }, + "clientTimeout": { "type": "integer", "minimum": 1 }, + "esperantoTimeout": { "type": "integer", "minimum": 1 } + } + }, + "auth": { + "type": "object", + "properties": { + "password": { "type": "string" }, + "existingSecret": { "type": "string" }, + "secretKey": { "type": "string" } + } + }, + "ssl": { + "type": "object", + "properties": { + "caBundle": { "type": "string" }, + "verify": { "type": "boolean" } + } + }, + "worker": { + "type": "object", + "properties": { + "maxTasks": { "type": "integer", "minimum": 1 }, + "retry": { + "type": "object", + "properties": { + "enabled": { "type": "boolean" }, + "maxAttempts": { "type": "integer", "minimum": 1 }, + "waitStrategy": { "type": "string" }, + "waitMin": { "type": "integer", "minimum": 0 }, + "waitMax": { "type": "integer", "minimum": 0 } + } + } + } + }, + "tts": { + "type": "object", + "properties": { + "batchSize": { "type": "integer", "minimum": 1 } + } + }, + "aiProviders": { + "type": "object", + "properties": { + "openai": { + "type": "object", + "properties": { + "apiKey": { "type": "string" }, + "existingSecret": { "type": "string" }, + "secretKey": { "type": "string" }, + "baseUrl": { "type": "string" } + } + }, + "anthropic": { + "type": "object", + "properties": { + "apiKey": { "type": "string" }, + "existingSecret": { "type": "string" }, + "secretKey": { "type": "string" } + } + }, + "google": { + "type": "object", + "properties": { + "apiKey": { "type": "string" }, + "existingSecret": { "type": "string" }, + "secretKey": { "type": "string" }, + "geminiBaseUrl": { "type": "string" } + } + }, + "vertexai": { + "type": "object", + "properties": { + "project": { "type": "string" }, + "credentials": { "type": "string" }, + "location": { "type": "string" }, + "existingSecret": { "type": "string" } + } + }, + "mistral": { + "type": "object", + "properties": { + "apiKey": { "type": "string" }, + "existingSecret": { "type": "string" }, + "secretKey": { "type": "string" } + } + }, + "deepseek": { + "type": "object", + "properties": { + "apiKey": { "type": "string" }, + "existingSecret": { "type": "string" }, + "secretKey": { "type": "string" } + } + }, + "ollama": { + "type": "object", + "properties": { + "baseUrl": { "type": "string" } + } + }, + "openrouter": { + "type": "object", + "properties": { + "baseUrl": { "type": "string" }, + "apiKey": { "type": "string" }, + "existingSecret": { "type": "string" }, + "secretKey": { "type": "string" } + } + }, + "groq": { + "type": "object", + "properties": { + "apiKey": { "type": "string" }, + "existingSecret": { "type": "string" }, + "secretKey": { "type": "string" } + } + }, + "xai": { + "type": "object", + "properties": { + "apiKey": { "type": "string" }, + "existingSecret": { "type": "string" }, + "secretKey": { "type": "string" } + } + }, + "elevenlabs": { + "type": "object", + "properties": { + "apiKey": { "type": "string" }, + "existingSecret": { "type": "string" }, + "secretKey": { "type": "string" } + } + }, + "voyage": { + "type": "object", + "properties": { + "apiKey": { "type": "string" }, + "existingSecret": { "type": "string" }, + "secretKey": { "type": "string" } + } + }, + "openaiCompatible": { + "type": "object", + "properties": { + "baseUrl": { "type": "string" }, + "apiKey": { "type": "string" }, + "existingSecret": { "type": "string" }, + "llm": { + "type": "object", + "properties": { + "baseUrl": { "type": "string" }, + "apiKey": { "type": "string" } + } + }, + "embedding": { + "type": "object", + "properties": { + "baseUrl": { "type": "string" }, + "apiKey": { "type": "string" } + } + }, + "stt": { + "type": "object", + "properties": { + "baseUrl": { "type": "string" }, + "apiKey": { "type": "string" } + } + }, + "tts": { + "type": "object", + "properties": { + "baseUrl": { "type": "string" }, + "apiKey": { "type": "string" } + } + } + } + }, + "azureOpenai": { + "type": "object", + "properties": { + "apiKey": { "type": "string" }, + "endpoint": { "type": "string" }, + "apiVersion": { "type": "string" }, + "existingSecret": { "type": "string" }, + "llm": { + "type": "object", + "properties": { + "apiKey": { "type": "string" }, + "endpoint": { "type": "string" }, + "apiVersion": { "type": "string" } + } + }, + "embedding": { + "type": "object", + "properties": { + "apiKey": { "type": "string" }, + "endpoint": { "type": "string" }, + "apiVersion": { "type": "string" } + } + }, + "stt": { + "type": "object", + "properties": { + "apiKey": { "type": "string" }, + "endpoint": { "type": "string" }, + "apiVersion": { "type": "string" } + } + }, + "tts": { + "type": "object", + "properties": { + "apiKey": { "type": "string" }, + "endpoint": { "type": "string" }, + "apiVersion": { "type": "string" } + } + } + } + }, + "firecrawl": { + "type": "object", + "properties": { + "apiKey": { "type": "string" }, + "existingSecret": { "type": "string" }, + "secretKey": { "type": "string" } + } + }, + "jina": { + "type": "object", + "properties": { + "apiKey": { "type": "string" }, + "existingSecret": { "type": "string" }, + "secretKey": { "type": "string" } + } + }, + "langchain": { + "type": "object", + "properties": { + "tracingV2": { "type": "boolean" }, + "endpoint": { "type": "string" }, + "apiKey": { "type": "string" }, + "project": { "type": "string" }, + "existingSecret": { "type": "string" }, + "secretKey": { "type": "string" } + } + } + } + } + } + }, + "secrets": { + "type": "object", + "properties": { + "create": { "type": "boolean" }, + "existingSecret": { "type": "string" }, + "annotations": { "type": "object" } + } + }, + "networkPolicy": { + "type": "object", + "properties": { + "enabled": { "type": "boolean" }, + "policyTypes": { "type": "array" }, + "ingress": { "type": "array" }, + "egress": { "type": "array" } + } + }, + "serviceMonitor": { + "type": "object", + "properties": { + "enabled": { "type": "boolean" }, + "namespace": { "type": "string" }, + "interval": { "type": "string" }, + "scrapeTimeout": { "type": "string" }, + "labels": { "type": "object" }, + "annotations": { "type": "object" } + } + } + } +} diff --git a/helm/values.yaml b/helm/values.yaml new file mode 100644 index 00000000..4630c2d6 --- /dev/null +++ b/helm/values.yaml @@ -0,0 +1,878 @@ +# Default values for open-notebook +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +## +## Global Docker image parameters +## Please, note that this will override the image parameters, including dependencies, configured to use the global value +## Current available global Docker image parameters: imageRegistry, imagePullSecrets and storageClass +## +global: + ## @param global.commonChartEnabled Enable Bitnami common chart compatibility + ## + commonChartEnabled: true + ## @param global.imageRegistry Global Docker image registry + ## ref: https://kubernetes.io/docs/concepts/containers/images/#updating-a-global-image + ## + imageRegistry: "" + ## @param global.imagePullSecrets Global Docker registry secret names as an array + ## e.g.: + ## imagePullSecrets: + ## - myRegistryKeySecretName + ## + imagePullSecrets: [] + ## @param global.storageClass Global StorageClass for PVCs + ## ref: https://kubernetes.io/docs/concepts/storage/storage-classes/ + ## + storageClass: "" + ## @param global.labels Labels to add to all deployed objects + ## + labels: {} + ## @param global.annotations Annotations to add to all deployed objects + ## + annotations: {} + ## @param global.clusterDomain Kubernetes cluster domain + ## ref: https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-config + ## + clusterDomain: cluster.local + +## +## Open Notebook image parameters +## +image: + ## @param image.registry Docker image registry + ## + registry: docker.io + ## @param image.repository Docker image repository + ## + repository: lfnovo/open_notebook + ## @param image.tag Docker image tag (immutable tags are recommended) + ## ref: https://kubernetes.io/docs/concepts/containers/images/#image-names + ## + tag: "" + ## @param image.digest Docker image digest (overrides tag when set) + ## ref: https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy + ## + digest: "" + ## @param image.pullPolicy Docker image pull policy + ## ref: https://kubernetes.io/docs/concepts/containers/images/#updating-images + ## + pullPolicy: IfNotPresent + +## +## SurrealDB database configuration +## +surrealdb: + ## @param surrealdb.enabled Deploy SurrealDB as part of the chart + ## If false, configure external SurrealDB connection + ## + enabled: true + + ## @param surrealdb.external.enabled Use external SurrealDB instead of deploying one + ## + external: + enabled: false + ## @param surrealdb.external.host External SurrealDB host + ## + host: "" + ## @param surrealdb.external.port External SurrealDB port + ## + port: 8000 + ## @param surrealdb.external.secure Use HTTPS/WSS for external connection + ## + secure: false + + ## SurrealDB image parameters + ## ref: https://hub.docker.com/r/surrealdb/surrealdb + ## + image: + registry: docker.io + repository: surrealdb/surrealdb + tag: v2 + digest: "" + pullPolicy: IfNotPresent + + ## SurrealDB authentication configuration + ## + auth: + ## @param surrealdb.auth.user SurrealDB username + ## + user: root + ## @param surrealdb.auth.password SurrealDB password + ## + password: root + ## @param surrealdb.auth.existingSecret Existing secret containing SurrealDB password + ## NOTE: Must contain key `surreal-password` + ## + existingSecret: "" + ## @param surrealdb.auth.secretKey Key in the secret containing the password + ## + secretKey: surreal-password + + ## SurrealDB database configuration + ## + database: + ## @param surrealdb.database.namespace SurrealDB namespace + ## + namespace: open_notebook + ## @param surrealdb.database.database SurrealDB database name + ## + database: production + + ## SurrealDB experimental features + ## + experimental: + ## @param surrealdb.experimental.graphql Enable GraphQL experimental feature + ## + graphql: true + + ## SurrealDB persistence configuration + ## + persistence: + ## @param surrealdb.persistence.enabled Enable persistence using PVC + ## + enabled: true + ## @param surrealdb.persistence.existingClaim Name of an existing PVC to use + ## + existingClaim: "" + ## @param surrealdb.persistence.storageClass StorageClass for PVC + ## ref: https://kubernetes.io/docs/concepts/storage/storage-classes/ + ## + storageClass: "" + ## @param surrealdb.persistence.accessMode PVC access mode + ## + accessMode: ReadWriteOnce + ## @param surrealdb.persistence.size PVC storage request size + ## + size: 8Gi + ## @param surrealdb.persistence.mountPath Mount path for SurrealDB data + ## + mountPath: /mydata + + ## SurrealDB service configuration + ## + service: + ## @param surrealdb.service.type Kubernetes Service type + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types + ## + type: ClusterIP + ## @param surrealdb.service.port SurrealDB service port + ## + port: 8000 + ## @param surrealdb.service.nodePort Specific node port when type is NodePort + ## NOTE: choose port between <30000-32767> + ## + nodePort: "" + ## @param surrealdb.service.annotations Service annotations + ## + annotations: {} + + ## SurrealDB resource requests and limits + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + ## + resources: + limits: {} + requests: {} + + ## Configure liveness probe for SurrealDB + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ + ## + livenessProbe: + ## @param surrealdb.livenessProbe.enabled Enable liveness probe + ## + enabled: true + ## @param surrealdb.livenessProbe.initialDelaySeconds Initial delay seconds for liveness probe + ## + initialDelaySeconds: 30 + ## @param surrealdb.livenessProbe.periodSeconds Period seconds for liveness probe + ## + periodSeconds: 10 + ## @param surrealdb.livenessProbe.timeoutSeconds Timeout seconds for liveness probe + ## + timeoutSeconds: 5 + ## @param surrealdb.livenessProbe.failureThreshold Failure threshold for liveness probe + ## + failureThreshold: 6 + ## @param surrealdb.livenessProbe.successThreshold Success threshold for liveness probe + ## + successThreshold: 1 + + ## Configure readiness probe for SurrealDB + ## + readinessProbe: + enabled: true + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + successThreshold: 1 + + ## @param surrealdb.podAnnotations Annotations for SurrealDB pods + ## + podAnnotations: {} + ## @param surrealdb.podLabels Labels for SurrealDB pods + ## + podLabels: {} + + ## SurrealDB pod security context + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ + ## + podSecurityContext: + ## @param surrealdb.podSecurityContext.enabled Enable pod security context + ## + enabled: true + ## @param surrealdb.podSecurityContext.fsGroup File system group ID + ## + fsGroup: 1001 + ## @param surrealdb.podSecurityContext.runAsUser User ID to run as + ## + runAsUser: 1001 + + ## SurrealDB container security context + ## + containerSecurityContext: + enabled: true + runAsNonRoot: true + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + + ## @param surrealdb.nodeSelector Node selector for SurrealDB pods + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector + ## + nodeSelector: {} + ## @param surrealdb.tolerations Tolerations for SurrealDB pods + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/ + ## + tolerations: [] + ## @param surrealdb.affinity Affinity rules for SurrealDB pods + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity + ## + affinity: {} + ## @param surrealdb.priorityClassName Priority class name for SurrealDB pods + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/ + ## + priorityClassName: "" + +## +## Open Notebook application configuration +## +app: + ## @param app.nameOverride String to partially override common.names.fullname template + ## + nameOverride: "" + ## @param app.fullnameOverride String to fully override common.names.fullname template + ## + fullnameOverride: "" + + ## @param app.replicaCount Number of application replicas + ## + replicaCount: 1 + + ## Update strategy for the application deployment + ## ref: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#updating-a-deployment + ## + updateStrategy: + ## @param app.updateStrategy.type Deployment update strategy type + ## + type: RollingUpdate + rollingUpdate: + ## @param app.updateStrategy.rollingUpdate.maxSurge Maximum number of pods that can be created above desired + ## + maxSurge: 1 + ## @param app.updateStrategy.rollingUpdate.maxUnavailable Maximum number of pods that can be unavailable + ## + maxUnavailable: 0 + + ## Application ports + ## + ports: + ## @param app.ports.http HTTP frontend port + ## + http: 8502 + ## @param app.ports.api API server port + ## + api: 5055 + + ## Application data persistence + ## + persistence: + enabled: true + existingClaim: "" + storageClass: "" + accessMode: ReadWriteOnce + size: 8Gi + mountPath: /app/data + + ## @param app.command Override default container command + ## + command: [] + ## @param app.args Override default container args + ## + args: [] + + ## Additional environment variables for application containers + ## ref: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/ + ## + extraEnvVars: [] + ## e.g.: + ## extraEnvVars: + ## - name: MY_VAR + ## value: "my-value" + + ## @param app.extraEnvVarsCM Name of existing ConfigMap with extra environment variables + ## + extraEnvVarsCM: "" + ## @param app.extraEnvVarsSecret Name of existing Secret with extra environment variables + ## + extraEnvVarsSecret: "" + + ## Configure liveness probe for the application + ## + livenessProbe: + enabled: true + ## @param app.livenessProbe.probeType Probe type (httpGet, tcpSocket, exec) + ## + probeType: httpGet + ## @param app.livenessProbe.path Path for HTTP probe + ## + path: /health + ## @param app.livenessProbe.port Port for probe + ## + port: api + initialDelaySeconds: 60 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + successThreshold: 1 + + ## Configure readiness probe for the application + ## + readinessProbe: + enabled: true + probeType: httpGet + path: /health + port: api + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + successThreshold: 1 + + ## Configure startup probe for the application + ## + startupProbe: + enabled: false + probeType: httpGet + path: /api/health + port: api + initialDelaySeconds: 0 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 30 + successThreshold: 1 + + ## Application resource requests and limits + ## + resources: + limits: {} + requests: {} + ## e.g.: + ## resources: + ## limits: + ## cpu: 500m + ## memory: 512Mi + ## requests: + ## cpu: 100m + ## memory: 128Mi + + ## @param app.podAnnotations Annotations for application pods + ## + podAnnotations: {} + ## @param app.podLabels Labels for application pods + ## + podLabels: {} + + ## Application pod security context + ## + podSecurityContext: + enabled: false + fsGroup: 1001 + + containerSecurityContext: + enabled: false + runAsNonRoot: true + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + + ## @param app.nodeSelector Node selector for application pods + ## + nodeSelector: {} + ## @param app.tolerations Tolerations for application pods + ## + tolerations: [] + ## @param app.affinity Affinity rules for application pods + ## + affinity: {} + ## @param app.priorityClassName Priority class name for application pods + ## + priorityClassName: "" + + ## Service account configuration + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/ + ## + serviceAccount: + ## @param app.serviceAccount.create Create a service account + ## + create: false + ## @param app.serviceAccount.name Name of the service account to use + ## + name: "" + ## @param app.serviceAccount.automountServiceAccountToken Automount service account token + ## + automountServiceAccountToken: false + ## @param app.serviceAccount.annotations Service account annotations + ## + annotations: {} + + ## Pod disruption budget configuration + ## ref: https://kubernetes.io/docs/tasks/run-application/configure-pdb/ + ## + podDisruptionBudget: + ## @param app.podDisruptionBudget.enabled Enable pod disruption budget + ## + enabled: false + ## @param app.podDisruptionBudget.minAvailable Minimum number of pods that must be available + ## + minAvailable: 1 + ## @param app.podDisruptionBudget.maxUnavailable Maximum number of pods that can be unavailable + ## + maxUnavailable: "" + +## +## Service configuration +## +service: + ## @param service.type Kubernetes Service type + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types + ## + type: ClusterIP + ports: + ## @param service.ports.http HTTP service port + ## + http: 8502 + ## @param service.ports.api API service port + ## + api: 5055 + ## @param service.ports.httpNodePort Node port for HTTP service (when type=NodePort) + ## NOTE: choose port between <30000-32767> + ## + httpNodePort: "" + ## @param service.ports.apiNodePort Node port for API service (when type=NodePort) + ## + apiNodePort: "" + ## @param service.annotations Service annotations + ## + annotations: {} + ## @param service.sessionAffinity Control where client requests go, to the same pod or round-robin + ## Values: ClientIP or None + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies + ## + sessionAffinity: None + ## @param service.sessionAffinityConfig Session affinity configuration + ## + sessionAffinityConfig: {} + +## +## Ingress configuration +## ref: https://kubernetes.io/docs/concepts/services-networking/ingress/ +## +ingress: + ## @param ingress.enabled Enable ingress record generation + ## + enabled: false + ## @param ingress.className IngressClass that will be used to implement the Ingress + ## This is supported in Kubernetes 1.18+ and required if you have more than one IngressClass marked as the default for your cluster + ## ref: https://kubernetes.io/blog/2020/04/02/improvements-to-the-ingress-api-in-kubernetes-1.18/ + ## + className: "" + ## @param ingress.annotations Additional annotations for the Ingress resource + ## e.g.: + ## annotations: + ## cert-manager.io/cluster-issuer: letsencrypt-prod + ## nginx.ingress.kubernetes.io/ssl-redirect: "true" + ## + annotations: {} + ## @param ingress.hosts List of host configurations + ## e.g.: + ## hosts: + ## - host: open-notebook.example.com + ## paths: + ## - path: / + ## pathType: Prefix + ## service: http + ## + hosts: + - host: open-notebook.example.com + paths: + - path: / + pathType: Prefix + service: http + + ## @param ingress.tls TLS configuration for Ingress + ## e.g.: + ## tls: + ## - secretName: open-notebook-tls + ## hosts: + ## - open-notebook.example.com + ## + tls: [] + +## +## Open Notebook application configuration +## +config: + ## API configuration + ## + api: + ## @param config.api.url External API URL for browser access + ## e.g. http://your-domain:30555 or https://api.example.com + ## + url: "" + ## @param config.api.internalUrl Internal API URL for Next.js server-side calls + ## e.g. http://open-notebook.open-notebook.svc.cluster.local:5055 + ## + internalUrl: "" + ## @param config.api.clientTimeout Client timeout in seconds + ## + clientTimeout: 300 + ## @param config.api.esperantoTimeout Esperanto LLM timeout in seconds + ## + esperantoTimeout: 60 + + ## Security configuration + ## + auth: + ## @param config.auth.password Application password (leave empty to disable auth) + ## + password: "" + ## @param config.auth.existingSecret Existing secret containing password + ## NOTE: Must contain key specified by secretKey + ## + existingSecret: "" + ## @param config.auth.secretKey Key in secret containing password + ## + secretKey: open-notebook-password + + ## SSL/TLS configuration + ## + ssl: + ## @param config.ssl.caBundle Path to CA bundle file + ## + caBundle: "" + ## @param config.ssl.verify Verify SSL certificates + ## + verify: true + + ## Worker configuration + ## + worker: + ## @param config.worker.maxTasks Maximum number of concurrent worker tasks + ## + maxTasks: 5 + ## Retry configuration + ## + retry: + ## @param config.worker.retry.enabled Enable retry logic + ## + enabled: true + ## @param config.worker.retry.maxAttempts Maximum number of retry attempts + ## + maxAttempts: 3 + ## @param config.worker.retry.waitStrategy Retry wait strategy + ## Values: exponential_jitter, fixed, linear, exponential + ## + waitStrategy: exponential_jitter + ## @param config.worker.retry.waitMin Minimum wait time in seconds + ## + waitMin: 1 + ## @param config.worker.retry.waitMax Maximum wait time in seconds + ## + waitMax: 30 + + ## TTS (Text-to-Speech) configuration + ## + tts: + ## @param config.tts.batchSize Batch size for TTS processing + ## + batchSize: 5 + + ## AI providers configuration + ## Configure one or more AI providers for LLM, embeddings, STT, and TTS + ## + aiProviders: + ## OpenAI configuration + ## + openai: + ## @param config.aiProviders.openai.apiKey OpenAI API key + ## NOTE: When set and secrets.create=true, will be automatically added to secret + ## + apiKey: "" + ## @param config.aiProviders.openai.existingSecret Existing secret containing API key + ## + existingSecret: "" + ## @param config.aiProviders.openai.secretKey Key in secret containing API key + ## + secretKey: openai-api-key + ## @param config.aiProviders.openai.baseUrl Custom OpenAI base URL + ## + baseUrl: "" + + ## Anthropic (Claude) configuration + ## + anthropic: + apiKey: "" + existingSecret: "" + secretKey: anthropic-api-key + + ## Google Gemini configuration + ## + google: + apiKey: "" + existingSecret: "" + secretKey: google-api-key + ## @param config.aiProviders.google.geminiBaseUrl Custom Gemini base URL + ## + geminiBaseUrl: "" + + ## Google Vertex AI configuration + ## + vertexai: + ## @param config.aiProviders.vertexai.project Google Cloud project ID + ## + project: "" + ## @param config.aiProviders.vertexai.credentials Service account credentials (JSON string or file path) + ## + credentials: "" + ## @param config.aiProviders.vertexai.location Vertex AI region/location + ## + location: us-east5 + existingSecret: "" + + ## Mistral AI configuration + ## + mistral: + apiKey: "" + existingSecret: "" + secretKey: mistral-api-key + + ## DeepSeek configuration + ## + deepseek: + apiKey: "" + existingSecret: "" + secretKey: deepseek-api-key + + ## Ollama configuration (local models) + ## + ollama: + ## @param config.aiProviders.ollama.baseUrl Ollama API base URL + ## e.g. http://localhost:11434 + ## + baseUrl: "" + + ## OpenRouter configuration + ## + openrouter: + baseUrl: https://openrouter.ai/api/v1 + apiKey: "" + existingSecret: "" + secretKey: openrouter-api-key + + ## Groq configuration + ## + groq: + apiKey: "" + existingSecret: "" + secretKey: groq-api-key + + ## XAI (Grok) configuration + ## + xai: + apiKey: "" + existingSecret: "" + secretKey: xai-api-key + + ## ElevenLabs configuration (for podcast generation) + ## + elevenlabs: + apiKey: "" + existingSecret: "" + secretKey: elevenlabs-api-key + + ## Voyage AI configuration (embeddings) + ## + voyage: + apiKey: "" + existingSecret: "" + secretKey: voyage-api-key + + ## OpenAI-compatible endpoints configuration + ## For Azure OpenAI, or other OpenAI-compatible APIs + ## + openaiCompatible: + ## @param config.aiProviders.openaiCompatible.baseUrl Base URL for OpenAI-compatible endpoint + ## e.g. https://api.openai.com/v1 for OpenAI + ## + baseUrl: "" + ## @param config.aiProviders.openaiCompatible.apiKey API key for OpenAI-compatible endpoint + ## NOTE: When set and secrets.create=true, will be automatically added to secret + ## + apiKey: "" + ## @param config.aiProviders.openaiCompatible.existingSecret Existing secret containing API key + ## + existingSecret: "" + ## Mode-specific configuration + ## + llm: + ## @param config.aiProviders.openaiCompatible.llm.baseUrl Override base URL for LLM mode + ## + baseUrl: "" + ## @param config.aiProviders.openaiCompatible.llm.apiKey Override API key for LLM mode + ## + apiKey: "" + embedding: + baseUrl: "" + apiKey: "" + stt: + baseUrl: "" + apiKey: "" + tts: + baseUrl: "" + apiKey: "" + + ## Azure OpenAI configuration + ## + azureOpenai: + ## @param config.aiProviders.azureOpenai.apiKey Azure OpenAI API key + ## + apiKey: "" + ## @param config.aiProviders.azureOpenai.endpoint Azure OpenAI endpoint URL + ## e.g. https://your-resource.openai.azure.com + ## + endpoint: "" + ## @param config.aiProviders.azureOpenai.apiVersion Azure OpenAI API version + ## + apiVersion: "2024-12-01-preview" + existingSecret: "" + ## Mode-specific configuration + ## + llm: + apiKey: "" + endpoint: "" + apiVersion: "" + embedding: + apiKey: "" + endpoint: "" + apiVersion: "" + stt: + apiKey: "" + endpoint: "" + apiVersion: "" + tts: + apiKey: "" + endpoint: "" + apiVersion: "" + + ## Firecrawl configuration (web scraping) + ## + firecrawl: + apiKey: "" + existingSecret: "" + secretKey: firecrawl-api-key + + ## Jina configuration (AI processing) + ## + jina: + apiKey: "" + existingSecret: "" + secretKey: jina-api-key + + ## LangChain configuration (for debugging/tracing) + ## + langchain: + ## @param config.aiProviders.langchain.tracingV2 Enable LangSmith tracing + ## + tracingV2: false + ## @param config.aiProviders.langchain.endpoint LangSmith API endpoint + ## + endpoint: https://api.smith.langchain.com + apiKey: "" + ## @param config.aiProviders.langchain.project LangSmith project name + ## + project: "Open Notebook" + existingSecret: "" + secretKey: langchain-api-key + +## +## Secret management configuration +## +secrets: + ## @param secrets.create Create secrets for sensitive data + ## When enabled, secrets will be created for any apiKey/password fields that are set + ## + create: true + ## @param secrets.existingSecret Use existing secret for all configuration + ## NOTE: Must contain appropriate keys for each provider + ## + existingSecret: "" + ## @param secrets.annotations Annotations to add to created secrets + ## + annotations: {} + +## +## Network policy configuration +## ref: https://kubernetes.io/docs/concepts/services-networking/network-policies/ +## +networkPolicy: + ## @param networkPolicy.enabled Enable network policies + ## + enabled: false + ## @param networkPolicy.policyTypes Policy types to enforce + ## + policyTypes: + - Ingress + - Egress + ## @param networkPolicy.ingress Ingress rules + ## + ingress: [] + ## @param networkPolicy.egress Egress rules + ## + egress: [] + +## +## ServiceMonitor configuration for Prometheus Operator +## ref: https://prometheus-operator.dev/docs/operator/api/#servicemonitor +## +serviceMonitor: + ## @param serviceMonitor.enabled Create ServiceMonitor resource + ## + enabled: false + ## @param serviceMonitor.namespace Namespace for ServiceMonitor + ## + namespace: "" + ## @param serviceMonitor.interval Scraping interval + ## + interval: 30s + ## @param serviceMonitor.scrapeTimeout Scrape timeout + ## + scrapeTimeout: 10s + ## @param serviceMonitor.labels Additional labels for ServiceMonitor + ## + labels: {} + ## @param serviceMonitor.annotations Additional annotations for ServiceMonitor + ## + annotations: {} From 5ebd4b39888219dfaab21bd228ee322deaca1bd4 Mon Sep 17 00:00:00 2001 From: thuanpham582002 Date: Tue, 6 Jan 2026 08:43:59 +0700 Subject: [PATCH 2/6] chore(helm): Update license to MIT and add Bitnami common library dependency - Update all template file license headers from Apache 2.0 to MIT - Add Bitnami common library chart as dependency in Chart.yaml - Refactor labels to use common.labels.standard helper for better Kubernetes best practices - Remove Chart.lock as chart now has external dependency - Update values.yaml comment to remove Bitnami branding - Update README.md license section to MIT License This aligns the Helm chart with the project's MIT license and leverages Bitnami's common library for better maintainability and standard Kubernetes labels. --- helm/Chart.lock | 6 ----- helm/Chart.yaml | 21 ++++++++------- helm/README.md | 2 +- helm/templates/NOTES.txt | 48 +++++++++++++-------------------- helm/templates/_helpers.tpl | 42 ++++++++++++++--------------- helm/templates/configmap.yaml | 26 +++++++++++------- helm/templates/deployment.yaml | 26 +++++++++++------- helm/templates/ingress.yaml | 26 +++++++++++------- helm/templates/pvc.yaml | 26 +++++++++++------- helm/templates/secret.yaml | 26 +++++++++++------- helm/templates/service.yaml | 26 +++++++++++------- helm/templates/statefulset.yaml | 26 +++++++++++------- helm/values.yaml | 12 ++++----- 13 files changed, 169 insertions(+), 144 deletions(-) delete mode 100644 helm/Chart.lock diff --git a/helm/Chart.lock b/helm/Chart.lock deleted file mode 100644 index b6a9ee2a..00000000 --- a/helm/Chart.lock +++ /dev/null @@ -1,6 +0,0 @@ -dependencies: -- name: common - repository: oci://registry-1.docker.io/bitnamicharts - version: 2.31.4 -digest: sha256:0df1987bc04bbeb530d28418e1dcb4d999985dc1559351db58db16c260c73deb -generated: "2025-12-26T18:20:00.665704+07:00" diff --git a/helm/Chart.yaml b/helm/Chart.yaml index 5f801a13..84921721 100644 --- a/helm/Chart.yaml +++ b/helm/Chart.yaml @@ -1,26 +1,27 @@ -apiVersion: v2 +apiVersion: v1 name: open-notebook -description: Open Notebook - AI-powered note-taking application with SurrealDB backend +description: An Open Source implementation of Notebook LM with more flexibility and features type: application version: 1.0.0 -appVersion: "v1-latest" +appVersion: "0.0.1" keywords: - notebook - - ai + - notebooklm - knowledge-management - surrealdb home: https://github.com/lfnovo/open-notebook -icon: https://raw.githubusercontent.com/lfnovo/open-notebook/main/docs/assets/logo.png sources: - https://github.com/lfnovo/open-notebook maintainers: - name: lfnovo - email: open-notebook@example.com + url: https://github.com/lfnovo + - name: thuanpham582002 + url: https://github.com/thuanpham582002 +annotations: + category: Analytics + licenses: MIT + dependencies: - name: common repository: oci://registry-1.docker.io/bitnamicharts version: 2.x.x - condition: global.commonChartEnabled -annotations: - category: Analytics - licenses: Apache-2.0 diff --git a/helm/README.md b/helm/README.md index 7231aa20..0ef12b11 100644 --- a/helm/README.md +++ b/helm/README.md @@ -619,7 +619,7 @@ Metrics will be available at: ## License -Apache License 2.0 +MIT License ## Support diff --git a/helm/templates/NOTES.txt b/helm/templates/NOTES.txt index d0720e85..01be7060 100644 --- a/helm/templates/NOTES.txt +++ b/helm/templates/NOTES.txt @@ -1,33 +1,23 @@ {{- /* -Copyright © 2024 Open Notebook Contributors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ -}} - -{{- /* -Copyright © 2024 Open Notebook Contributors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. +Copyright © 2024 Luis Novo + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. */ -}} {{- $svcPort := .Values.service.ports.http -}} diff --git a/helm/templates/_helpers.tpl b/helm/templates/_helpers.tpl index df771880..25d4e4e9 100644 --- a/helm/templates/_helpers.tpl +++ b/helm/templates/_helpers.tpl @@ -1,37 +1,37 @@ {{- /* -Copyright © 2024 Open Notebook Contributors - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. +Copyright © 2024 Luis Novo + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. */ -}} {{- /* Default labels for Open Notebook resources */ -}} {{- define "open-notebook.labels" -}} -helm.sh/chart: {{ include "common.names.chart" . }} -{{ include "open-notebook.selectorLabels" . }} -{{- if .Chart.AppVersion -}} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} -{{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- include "common.labels.standard" . | nindent 0 }} {{- end }} {{- /* Selector labels for Open Notebook resources */ -}} {{- define "open-notebook.selectorLabels" -}} -app.kubernetes.io/name: {{ include "common.names.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} +{{- include "common.labels.matchLabels" . | nindent 0 }} {{ end }} {{- /* diff --git a/helm/templates/configmap.yaml b/helm/templates/configmap.yaml index f65a360a..bfef9d70 100644 --- a/helm/templates/configmap.yaml +++ b/helm/templates/configmap.yaml @@ -1,17 +1,23 @@ {{- /* -Copyright © 2024 Open Notebook Contributors +Copyright © 2024 Luis Novo -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: - http://www.apache.org/licenses/LICENSE-2.0 +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. */ -}} {{- if .Values.app.extraEnvVarsCM }} apiVersion: v1 diff --git a/helm/templates/deployment.yaml b/helm/templates/deployment.yaml index 093b841b..ec081e70 100644 --- a/helm/templates/deployment.yaml +++ b/helm/templates/deployment.yaml @@ -1,17 +1,23 @@ {{- /* -Copyright © 2024 Open Notebook Contributors +Copyright © 2024 Luis Novo -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: - http://www.apache.org/licenses/LICENSE-2.0 +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. */ -}} apiVersion: {{ include "common.capabilities.deployment.apiVersion" . }} kind: Deployment diff --git a/helm/templates/ingress.yaml b/helm/templates/ingress.yaml index 02e3571c..af2bc624 100644 --- a/helm/templates/ingress.yaml +++ b/helm/templates/ingress.yaml @@ -1,17 +1,23 @@ {{- /* -Copyright © 2024 Open Notebook Contributors +Copyright © 2024 Luis Novo -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: - http://www.apache.org/licenses/LICENSE-2.0 +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. */ -}} {{- if .Values.ingress.enabled -}} apiVersion: {{ include "common.capabilities.ingress.apiVersion" . }} diff --git a/helm/templates/pvc.yaml b/helm/templates/pvc.yaml index 28ce6b86..44e78bfc 100644 --- a/helm/templates/pvc.yaml +++ b/helm/templates/pvc.yaml @@ -1,17 +1,23 @@ {{- /* -Copyright © 2024 Open Notebook Contributors +Copyright © 2024 Luis Novo -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: - http://www.apache.org/licenses/LICENSE-2.0 +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. */ -}} {{- if and .Values.app.persistence.enabled (not .Values.app.persistence.existingClaim) }} apiVersion: v1 diff --git a/helm/templates/secret.yaml b/helm/templates/secret.yaml index 8f72d34a..b2ec456e 100644 --- a/helm/templates/secret.yaml +++ b/helm/templates/secret.yaml @@ -1,17 +1,23 @@ {{- /* -Copyright © 2024 Open Notebook Contributors +Copyright © 2024 Luis Novo -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: - http://www.apache.org/licenses/LICENSE-2.0 +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. */ -}} {{- $secretName := printf "%s-secret" (include "common.names.fullname" .) }} {{- if and .Values.secrets.create (not .Values.secrets.existingSecret) }} diff --git a/helm/templates/service.yaml b/helm/templates/service.yaml index c1c2a36f..0cb4a541 100644 --- a/helm/templates/service.yaml +++ b/helm/templates/service.yaml @@ -1,17 +1,23 @@ {{- /* -Copyright © 2024 Open Notebook Contributors +Copyright © 2024 Luis Novo -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: - http://www.apache.org/licenses/LICENSE-2.0 +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. */ -}} --- apiVersion: v1 diff --git a/helm/templates/statefulset.yaml b/helm/templates/statefulset.yaml index b783e80e..d1099202 100644 --- a/helm/templates/statefulset.yaml +++ b/helm/templates/statefulset.yaml @@ -1,17 +1,23 @@ {{- /* -Copyright © 2024 Open Notebook Contributors +Copyright © 2024 Luis Novo -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: - http://www.apache.org/licenses/LICENSE-2.0 +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. */ -}} {{- if .Values.surrealdb.enabled }} apiVersion: {{ include "common.capabilities.statefulset.apiVersion" . }} diff --git a/helm/values.yaml b/helm/values.yaml index 4630c2d6..52957d50 100644 --- a/helm/values.yaml +++ b/helm/values.yaml @@ -8,7 +8,7 @@ ## Current available global Docker image parameters: imageRegistry, imagePullSecrets and storageClass ## global: - ## @param global.commonChartEnabled Enable Bitnami common chart compatibility + ## @param global.commonChartEnabled Enable common chart library compatibility ## commonChartEnabled: true ## @param global.imageRegistry Global Docker image registry @@ -160,7 +160,6 @@ surrealdb: ## port: 8000 ## @param surrealdb.service.nodePort Specific node port when type is NodePort - ## NOTE: choose port between <30000-32767> ## nodePort: "" ## @param surrealdb.service.annotations Service annotations @@ -468,7 +467,6 @@ service: ## api: 5055 ## @param service.ports.httpNodePort Node port for HTTP service (when type=NodePort) - ## NOTE: choose port between <30000-32767> ## httpNodePort: "" ## @param service.ports.apiNodePort Node port for API service (when type=NodePort) @@ -676,7 +674,7 @@ config: existingSecret: "" secretKey: deepseek-api-key - ## Ollama configuration (local models) + ## Ollama configuration ## ollama: ## @param config.aiProviders.ollama.baseUrl Ollama API base URL @@ -787,21 +785,21 @@ config: endpoint: "" apiVersion: "" - ## Firecrawl configuration (web scraping) + ## Firecrawl configuration ## firecrawl: apiKey: "" existingSecret: "" secretKey: firecrawl-api-key - ## Jina configuration (AI processing) + ## Jina configuration ## jina: apiKey: "" existingSecret: "" secretKey: jina-api-key - ## LangChain configuration (for debugging/tracing) + ## LangChain configuration ## langchain: ## @param config.aiProviders.langchain.tracingV2 Enable LangSmith tracing From 01e909bae2cabaf29f9857dd873e18660b7d2948 Mon Sep 17 00:00:00 2001 From: thuanpham582002 Date: Tue, 6 Jan 2026 08:57:54 +0700 Subject: [PATCH 3/6] ci(helm): Add CI/CD workflows for automated linting and OCI releases Add GitHub Actions workflows for Helm chart CI/CD: - helm-lint-test.yaml: Automated linting and testing with chart-testing - helm-release-oci.yaml: Automated OCI-based releases to GHCR - ct.yaml: Chart-testing configuration - cr.yaml: Chart-releaser configuration Features: - Lint and test charts on every PR and push - Create kind cluster for integration testing - Automatic releases to OCI registry (GHCR) on main branch pushes - Auto-generated release notes for chart releases This follows industry best practices from helm-charts reference pattern. --- .github/workflows/helm-lint-test.yaml | 40 +++++++++++++++++ .github/workflows/helm-release-oci.yaml | 58 +++++++++++++++++++++++++ helm/cr.yaml | 5 +++ helm/ct.yaml | 9 ++++ 4 files changed, 112 insertions(+) create mode 100644 .github/workflows/helm-lint-test.yaml create mode 100644 .github/workflows/helm-release-oci.yaml create mode 100644 helm/cr.yaml create mode 100644 helm/ct.yaml diff --git a/.github/workflows/helm-lint-test.yaml b/.github/workflows/helm-lint-test.yaml new file mode 100644 index 00000000..22f0abdb --- /dev/null +++ b/.github/workflows/helm-lint-test.yaml @@ -0,0 +1,40 @@ +name: Lint and Test Helm Chart + +on: + pull_request: + branches: + - main + push: + branches: + - '**' + +jobs: + lint-test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Helm + uses: azure/setup-helm@v4.3.0 + with: + version: v3.12.0 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: 3.12 + + - name: Set up chart-testing + uses: helm/chart-testing-action@v2.7.0 + + - name: Run chart-testing (lint) + run: ct lint --config helm/ct.yaml + + - name: Create kind cluster + uses: helm/kind-action@v1.8.0 + + - name: Run chart-testing (install) + run: ct install --config helm/ct.yaml diff --git a/.github/workflows/helm-release-oci.yaml b/.github/workflows/helm-release-oci.yaml new file mode 100644 index 00000000..5fe9c333 --- /dev/null +++ b/.github/workflows/helm-release-oci.yaml @@ -0,0 +1,58 @@ +name: Release Helm Chart + +on: + push: + branches: + - main + workflow_dispatch: + +permissions: + contents: write + packages: write + +jobs: + release: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Configure Git + run: | + git config user.name "$GITHUB_ACTOR" + git config user.email "$GITHUB_ACTOR@users.noreply.github.com" + + - name: Set up Helm + uses: azure/setup-helm@v4.3.0 + with: + version: v3.12.0 + + - name: Add dependency chart repos + run: | + helm repo add bitnami https://charts.bitnami.com/bitnami + + - name: Run chart-releaser + uses: helm/chart-releaser-action@v1.5.0 + with: + charts_dir: helm + config: helm/cr.yaml + env: + CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + CR_SKIP_EXISTING: "true" + + - name: Login to GHCR + uses: docker/login-action@v3.6.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Push charts to GHCR + run: | + for pkg in .cr-release-packages/*.tgz; do + if [ -f "${pkg}" ]; then + helm push "${pkg}" "oci://ghcr.io/${GITHUB_REPOSITORY_OWNER}/helm-charts" + fi + done diff --git a/helm/cr.yaml b/helm/cr.yaml new file mode 100644 index 00000000..d616ba78 --- /dev/null +++ b/helm/cr.yaml @@ -0,0 +1,5 @@ +sign: false + +# Enable automatic generation of release notes using GitHubs release notes generator. +# see: https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes +generate-release-notes: true diff --git a/helm/ct.yaml b/helm/ct.yaml new file mode 100644 index 00000000..72fefc41 --- /dev/null +++ b/helm/ct.yaml @@ -0,0 +1,9 @@ +# See https://github.com/helm/chart-testing#configuration +remote: origin +target-branch: main +chart-dirs: + - helm +chart-repos: + - bitnami=https://charts.bitnami.com/bitnami +helm-extra-args: --timeout 600s +validate-maintainers: false From 6b91ea15918108acdf6fbcec0c602c544360bc31 Mon Sep 17 00:00:00 2001 From: thuanpham582002 Date: Tue, 6 Jan 2026 09:25:53 +0700 Subject: [PATCH 4/6] fix(helm): Fix critical security issues in Helm chart Fix 13 security vulnerabilities identified by cubic-dev-ai bot: Critical Security Fixes: - Remove hardcoded default password "root" in values.yaml - Fix SurrealDB password exposure in command args (now uses env vars) - Remove plain text OPEN_NOTEBOOK_PASSWORD from deployment env - Mask passwords in installation notes (NOTES.txt) - Fix hardcoded database path to use configurable mountPath Medium Priority Fixes: - Fix existingSecret condition to support both password and existingSecret - Fix Azure OpenAI hardcoded secret key name to use configurable value - Fix schema type inconsistencies (NodePort as integers, secretKey as object) - Quote StorageClass to prevent YAML parsing issues Additional Improvements: - Update Chart.yaml to apiVersion v2 (required for dependencies) - Add helper functions for secret name resolution - Restructure secret.yaml to create separate secrets for app, SurrealDB, and API keys All passwords now use Kubernetes secretKeyRef instead of plain text values. Tested with helm lint and helm template - all tests pass. --- helm/Chart.yaml | 2 +- helm/templates/NOTES.txt | 16 ++++++++++-- helm/templates/_helpers.tpl | 35 +++++++++++++++++++------ helm/templates/deployment.yaml | 9 +++---- helm/templates/pvc.yaml | 2 +- helm/templates/secret.yaml | 45 +++++++++++++++++++++++++++------ helm/templates/statefulset.yaml | 16 +++++++++--- helm/values.schema.json | 12 ++++++--- helm/values.yaml | 21 ++++++++++----- 9 files changed, 119 insertions(+), 39 deletions(-) diff --git a/helm/Chart.yaml b/helm/Chart.yaml index 84921721..bbb4fd8e 100644 --- a/helm/Chart.yaml +++ b/helm/Chart.yaml @@ -1,4 +1,4 @@ -apiVersion: v1 +apiVersion: v2 name: open-notebook description: An Open Source implementation of Notebook LM with more flexibility and features type: application diff --git a/helm/templates/NOTES.txt b/helm/templates/NOTES.txt index 01be7060..4c321a0d 100644 --- a/helm/templates/NOTES.txt +++ b/helm/templates/NOTES.txt @@ -68,13 +68,25 @@ SurrealDB Information: - Internal Service: {{ $svcName }}-surrealdb.{{ $svcNamespace }}.svc.{{ .Values.global.clusterDomain }}:8000 - WebSocket URL: ws://{{ $svcName }}-surrealdb.{{ $svcNamespace }}.svc.{{ .Values.global.clusterDomain }}:8000/rpc - User: {{ .Values.surrealdb.auth.user }} -- Password: {{ if .Values.surrealdb.auth.existingSecret }}*** (from secret {{ .Values.surrealdb.auth.existingSecret }}){{ else }}{{ .Values.surrealdb.auth.password }}{{ end }} +{{- if .Values.surrealdb.auth.existingSecret }} +- Password: *** (from secret {{ .Values.surrealdb.auth.existingSecret }}) +{{- else if .Values.surrealdb.auth.password }} +- Password: *** (set in values.yaml) +{{- else }} +- WARNING: No password set! Please set surrealdb.auth.password or surrealdb.auth.existingSecret +{{- end }} - Namespace: {{ .Values.surrealdb.database.namespace }} - Database: {{ .Values.surrealdb.database.database }} To connect to SurrealDB directly: kubectl port-forward -n {{ $svcNamespace }} svc/{{ $svcName }}-surrealdb 8000:8000 - surreal sql --endpoint ws://localhost:8000/rpc --namespace {{ .Values.surrealdb.database.namespace }} --database {{ .Values.surrealdb.database.database }} --user {{ .Values.surrealdb.auth.user }} --pass {{ .Values.surrealdb.auth.password }} +{{- if or .Values.surrealdb.auth.password .Values.surrealdb.auth.existingSecret }} + surreal sql --endpoint ws://localhost:8000/rpc --namespace {{ .Values.surrealdb.database.namespace }} --database {{ .Values.surrealdb.database.database }} --user {{ .Values.surrealdb.auth.user }} --pass *** + (Password is stored in secret: {{ include "open-notebook.surrealdb.secretName" . }}) +{{- else }} + surreal sql --endpoint ws://localhost:8000/rpc --namespace {{ .Values.surrealdb.database.namespace }} --database {{ .Values.surrealdb.database.database }} --user {{ .Values.surrealdb.auth.user }} + (WARNING: No password set!) +{{- end }} {{- end }} diff --git a/helm/templates/_helpers.tpl b/helm/templates/_helpers.tpl index 25d4e4e9..060cf035 100644 --- a/helm/templates/_helpers.tpl +++ b/helm/templates/_helpers.tpl @@ -54,6 +54,28 @@ app.kubernetes.io/name: {{ include "common.names.name" . }}-surrealdb app.kubernetes.io/instance: {{ .Release.Name }} {{ end }} +{{- /* +Return the secret name for SurrealDB +*/ -}} +{{- define "open-notebook.surrealdb.secretName" -}} +{{- if .Values.surrealdb.auth.existingSecret -}} +{{- .Values.surrealdb.auth.existingSecret -}} +{{- else -}} +{{- printf "%s-surrealdb" (include "common.names.fullname" .) -}} +{{- end -}} +{{- end -}} + +{{- /* +Return the secret name for application +*/ -}} +{{- define "open-notebook.secretName" -}} +{{- if .Values.config.auth.existingSecret -}} +{{- .Values.config.auth.existingSecret -}} +{{- else -}} +{{- include "common.names.fullname" . -}} +{{- end -}} +{{- end -}} + {{- /* Get the image registry */ -}} @@ -149,15 +171,12 @@ Render environment variables for the application value: {{ include "open-notebook.surrealdb.url" . | quote }} - name: SURREAL_USER value: {{ .Values.surrealdb.auth.user | quote }} -{{ if .Values.surrealdb.auth.existingSecret -}} +{{ if or .Values.surrealdb.auth.password .Values.surrealdb.auth.existingSecret -}} - name: SURREAL_PASSWORD valueFrom: secretKeyRef: - name: {{ .Values.surrealdb.auth.existingSecret }} - key: {{ .Values.surrealdb.auth.secretKey | default "surreal-password" }} -{{ else -}} -- name: SURREAL_PASSWORD - value: {{ .Values.surrealdb.auth.password | quote }} + name: {{ include "open-notebook.surrealdb.secretName" . }} + key: {{ .Values.surrealdb.auth.secretKey.password | default "password" }} {{- end }} - name: SURREAL_NAMESPACE value: {{ .Values.surrealdb.database.namespace | quote }} @@ -183,7 +202,7 @@ Render environment variables for the application value: {{ .Values.config.worker.retry.waitMax | quote }} - name: TTS_BATCH_SIZE value: {{ .Values.config.tts.batchSize | quote }} -{{ if .Values.config.aiProviders.openai.apiKey -}} +{{- if or .Values.config.aiProviders.openai.apiKey .Values.config.aiProviders.openai.existingSecret -}} {{ if .Values.config.aiProviders.openai.existingSecret -}} - name: OPENAI_API_KEY valueFrom: @@ -354,7 +373,7 @@ Render environment variables for the application valueFrom: secretKeyRef: name: {{ .Values.config.aiProviders.azureOpenai.existingSecret }} - key: azure-openai-api-key + key: {{ .Values.config.aiProviders.azureOpenai.secretKey | default "azure-openai-api-key" }} {{- else -}} - name: AZURE_OPENAI_API_KEY value: {{ .Values.config.aiProviders.azureOpenai.apiKey | quote }} diff --git a/helm/templates/deployment.yaml b/helm/templates/deployment.yaml index ec081e70..51254562 100644 --- a/helm/templates/deployment.yaml +++ b/helm/templates/deployment.yaml @@ -99,15 +99,12 @@ spec: name: {{ .Values.app.extraEnvVarsCM }} key: app-extra-env-vars {{- end }} - {{- if and .Values.config.auth.password (not .Values.config.auth.existingSecret) }} - - name: OPEN_NOTEBOOK_PASSWORD - value: {{ .Values.config.auth.password | quote }} - {{- else if .Values.config.auth.existingSecret }} + {{- if or .Values.config.auth.password .Values.config.auth.existingSecret }} - name: OPEN_NOTEBOOK_PASSWORD valueFrom: secretKeyRef: - name: {{ .Values.config.auth.existingSecret }} - key: {{ .Values.config.auth.secretKey }} + name: {{ include "open-notebook.secretName" . }} + key: {{ .Values.config.auth.secretKey | default "open-notebook-password" }} {{- end }} ports: - name: http diff --git a/helm/templates/pvc.yaml b/helm/templates/pvc.yaml index 44e78bfc..d7414221 100644 --- a/helm/templates/pvc.yaml +++ b/helm/templates/pvc.yaml @@ -33,7 +33,7 @@ spec: {{- if (eq "-" .Values.app.persistence.storageClass) }} storageClassName: "" {{- else }} - storageClassName: {{ .Values.app.persistence.storageClass }} + storageClassName: {{ .Values.app.persistence.storageClass | quote }} {{- end }} {{- end }} resources: diff --git a/helm/templates/secret.yaml b/helm/templates/secret.yaml index b2ec456e..9ccb9827 100644 --- a/helm/templates/secret.yaml +++ b/helm/templates/secret.yaml @@ -19,12 +19,16 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -}} -{{- $secretName := printf "%s-secret" (include "common.names.fullname" .) }} + +{{/* +Secret for AI provider API keys +*/}} +{{- $apiSecretName := printf "%s-secret" (include "common.names.fullname" .) }} {{- if and .Values.secrets.create (not .Values.secrets.existingSecret) }} apiVersion: v1 kind: Secret metadata: - name: {{ $secretName }} + name: {{ $apiSecretName }} namespace: {{ include "common.names.namespace" . }} labels: {{- include "open-notebook.labels" . | nindent 4 }} annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.secrets.annotations "context" $ ) | nindent 4 }} @@ -73,15 +77,40 @@ stringData: OPENAI_COMPATIBLE_API_KEY: {{ .Values.config.aiProviders.openaiCompatible.apiKey | quote }} {{- end }} {{- if .Values.config.aiProviders.azureOpenai.apiKey }} - AZURE_OPENAI_API_KEY: {{ .Values.config.aiProviders.azureOpenai.apiKey | quote }} + {{ .Values.config.aiProviders.azureOpenai.secretKey | default "azure-openai-api-key" }}: {{ .Values.config.aiProviders.azureOpenai.apiKey | quote }} {{- end }} {{- if .Values.config.aiProviders.vertexai.credentials }} GOOGLE_APPLICATION_CREDENTIALS_JSON: {{ .Values.config.aiProviders.vertexai.credentials | quote }} {{- end }} - {{- if .Values.config.auth.password }} +{{- end }} + +{{/* +Secret for application authentication password +*/}} +{{- if and .Values.config.auth.password (not .Values.config.auth.existingSecret) }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "open-notebook.secretName" . }} + namespace: {{ include "common.names.namespace" . }} + labels: {{- include "open-notebook.labels" . | nindent 4 }} +type: Opaque +stringData: {{ .Values.config.auth.secretKey | default "open-notebook-password" }}: {{ .Values.config.auth.password | quote }} - {{- end }} - {{- if .Values.surrealdb.auth.password }} - surreal-password: {{ .Values.surrealdb.auth.password | quote }} - {{- end }} +{{- end }} + +{{/* +Secret for SurrealDB authentication +*/}} +{{- if and .Values.surrealdb.enabled .Values.surrealdb.auth.password (not .Values.surrealdb.auth.existingSecret) }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "open-notebook.surrealdb.secretName" . }} + namespace: {{ include "common.names.namespace" . }} + labels: {{- include "open-notebook.surrealdb.labels" . | nindent 4 }} +type: Opaque +stringData: + {{ .Values.surrealdb.auth.secretKey.username | default "username" }}: {{ .Values.surrealdb.auth.user | quote }} + {{ .Values.surrealdb.auth.secretKey.password | default "password" }}: {{ .Values.surrealdb.auth.password | quote }} {{- end }} diff --git a/helm/templates/statefulset.yaml b/helm/templates/statefulset.yaml index d1099202..a5e44565 100644 --- a/helm/templates/statefulset.yaml +++ b/helm/templates/statefulset.yaml @@ -60,12 +60,22 @@ spec: - --log - info - --user - - {{ .Values.surrealdb.auth.user }} + - $(SURREAL_USER) - --pass - - {{ .Values.surrealdb.auth.password }} + - $(SURREAL_PASS) - -- - - rocksdb:/mydata/mydatabase.db + - rocksdb:{{ .Values.surrealdb.persistence.mountPath }}/mydatabase.db env: + - name: SURREAL_USER + valueFrom: + secretKeyRef: + name: {{ include "open-notebook.surrealdb.secretName" . }} + key: {{ .Values.surrealdb.auth.secretKey.username | default "username" }} + - name: SURREAL_PASS + valueFrom: + secretKeyRef: + name: {{ include "open-notebook.surrealdb.secretName" . }} + key: {{ .Values.surrealdb.auth.secretKey.password | default "password" }} {{- if .Values.surrealdb.experimental.graphql }} - name: SURREAL_EXPERIMENTAL_GRAPHQL value: "true" diff --git a/helm/values.schema.json b/helm/values.schema.json index 1568d406..e4139b80 100644 --- a/helm/values.schema.json +++ b/helm/values.schema.json @@ -54,7 +54,13 @@ "user": { "type": "string" }, "password": { "type": "string" }, "existingSecret": { "type": "string" }, - "secretKey": { "type": "string" } + "secretKey": { + "type": "object", + "properties": { + "username": { "type": "string" }, + "password": { "type": "string" } + } + } } }, "database": { @@ -295,8 +301,8 @@ "properties": { "http": { "type": "integer", "minimum": 1, "maximum": 65535 }, "api": { "type": "integer", "minimum": 1, "maximum": 65535 }, - "httpNodePort": { "type": "string" }, - "apiNodePort": { "type": "string" } + "httpNodePort": { "type": ["integer", "null"] }, + "apiNodePort": { "type": ["integer", "null"] } } }, "annotations": { "type": "object" }, diff --git a/helm/values.yaml b/helm/values.yaml index 52957d50..340301d3 100644 --- a/helm/values.yaml +++ b/helm/values.yaml @@ -99,15 +99,22 @@ surrealdb: ## user: root ## @param surrealdb.auth.password SurrealDB password + ## NOTE: Must be set if existingSecret is not provided ## - password: root - ## @param surrealdb.auth.existingSecret Existing secret containing SurrealDB password - ## NOTE: Must contain key `surreal-password` + password: "" + ## @param surrealdb.auth.existingSecret Existing secret containing SurrealDB credentials + ## NOTE: Must contain keys specified in secretKey.username and secretKey.password ## existingSecret: "" - ## @param surrealdb.auth.secretKey Key in the secret containing the password + ## @param surrealdb.auth.secretKey Keys in the secret containing credentials ## - secretKey: surreal-password + secretKey: + ## @param surrealdb.auth.secretKey.username Key in secret containing username + ## + username: username + ## @param surrealdb.auth.secretKey.password Key in secret containing password + ## + password: password ## SurrealDB database configuration ## @@ -468,10 +475,10 @@ service: api: 5055 ## @param service.ports.httpNodePort Node port for HTTP service (when type=NodePort) ## - httpNodePort: "" + httpNodePort: null ## @param service.ports.apiNodePort Node port for API service (when type=NodePort) ## - apiNodePort: "" + apiNodePort: null ## @param service.annotations Service annotations ## annotations: {} From 76cc880ba1b9a81f9f55159b1fdb3efd49de274e Mon Sep 17 00:00:00 2001 From: thuanpham582002 Date: Tue, 6 Jan 2026 09:55:20 +0700 Subject: [PATCH 5/6] fix(helm): Fix additional security issues in Helm chart (Round 2) This commit addresses 12 additional security and configuration issues identified by cubic-dev-ai bot review on Jan 6, 2026. P1 Critical Fixes: - Add YAML document separators to secret.yaml for multiple Secret resources - Fix existingSecret conditions for 13 AI providers (anthropic, google, mistral, deepseek, openrouter, groq, xai, elevenlabs, voyage, azureOpenai, langchain, firecrawl, jina) - Remove --user/--pass command args from SurrealDB StatefulSet (credentials visible in ps aux, /proc//cmdline) - Fix unreachable conditional branches in NOTES.txt (allow all service types to show instructions) P2 Medium Fixes: - Add explicit permissions to helm-lint-test.yaml workflow (contents: read) - Fix schema port validations (nodePort type, add min/max for httpNodePort/apiNodePort) - Fix GHCR repository path case (convert GITHUB_REPOSITORY_OWNER to lowercase) - Add probeType support to startup probe in deployment.yaml (respect httpGet/tcpSocket config) P3 Low Fix: - cr.yaml typo already corrected Additional Fix: - Change surrealdb.service.nodePort from empty string to null to match schema All changes verified with helm lint and template rendering tests. --- .github/workflows/helm-lint-test.yaml | 3 +++ .github/workflows/helm-release-oci.yaml | 2 +- helm/cr.yaml | 2 +- helm/templates/NOTES.txt | 3 --- helm/templates/_helpers.tpl | 28 ++++++++++++------------- helm/templates/deployment.yaml | 5 +++++ helm/templates/secret.yaml | 2 ++ helm/templates/statefulset.yaml | 4 ---- helm/values.schema.json | 6 +++--- helm/values.yaml | 2 +- 10 files changed, 30 insertions(+), 27 deletions(-) diff --git a/.github/workflows/helm-lint-test.yaml b/.github/workflows/helm-lint-test.yaml index 22f0abdb..3ce2b98a 100644 --- a/.github/workflows/helm-lint-test.yaml +++ b/.github/workflows/helm-lint-test.yaml @@ -8,6 +8,9 @@ on: branches: - '**' +permissions: + contents: read + jobs: lint-test: runs-on: ubuntu-latest diff --git a/.github/workflows/helm-release-oci.yaml b/.github/workflows/helm-release-oci.yaml index 5fe9c333..5d4782d7 100644 --- a/.github/workflows/helm-release-oci.yaml +++ b/.github/workflows/helm-release-oci.yaml @@ -53,6 +53,6 @@ jobs: run: | for pkg in .cr-release-packages/*.tgz; do if [ -f "${pkg}" ]; then - helm push "${pkg}" "oci://ghcr.io/${GITHUB_REPOSITORY_OWNER}/helm-charts" + helm push "${pkg}" "oci://ghcr.io/${GITHUB_REPOSITORY_OWNER,,}/helm-charts" fi done diff --git a/helm/cr.yaml b/helm/cr.yaml index d616ba78..b9b9946f 100644 --- a/helm/cr.yaml +++ b/helm/cr.yaml @@ -1,5 +1,5 @@ sign: false -# Enable automatic generation of release notes using GitHubs release notes generator. +# Enable automatic generation of release notes using GitHub's release notes generator. # see: https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes generate-release-notes: true diff --git a/helm/templates/NOTES.txt b/helm/templates/NOTES.txt index 4c321a0d..246fb110 100644 --- a/helm/templates/NOTES.txt +++ b/helm/templates/NOTES.txt @@ -31,8 +31,6 @@ Thank you for installing {{ $chartName }}! Your release is named {{ $releaseName }}. -{{- if eq .Values.service.type "ClusterIP" }} - 1. Get the application URL by running these commands: {{- if contains "NodePort" .Values.service.type }} export NODE_PORT=$(kubectl get --namespace {{ $svcNamespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ $svcName }}) @@ -52,7 +50,6 @@ Your release is named {{ $releaseName }}. echo "API available at http://127.0.0.1:5055" kubectl --namespace {{ $svcNamespace }} port-forward $POD_NAME 5055:{{ $apiPort }} {{- end }} -{{- end }} {{- if .Values.ingress.enabled }} diff --git a/helm/templates/_helpers.tpl b/helm/templates/_helpers.tpl index 060cf035..dd30c9f3 100644 --- a/helm/templates/_helpers.tpl +++ b/helm/templates/_helpers.tpl @@ -202,7 +202,7 @@ Render environment variables for the application value: {{ .Values.config.worker.retry.waitMax | quote }} - name: TTS_BATCH_SIZE value: {{ .Values.config.tts.batchSize | quote }} -{{- if or .Values.config.aiProviders.openai.apiKey .Values.config.aiProviders.openai.existingSecret -}} +{{ if or .Values.config.aiProviders.openai.apiKey .Values.config.aiProviders.openai.existingSecret -}} {{ if .Values.config.aiProviders.openai.existingSecret -}} - name: OPENAI_API_KEY valueFrom: @@ -215,7 +215,7 @@ Render environment variables for the application {{- end }} {{- end }} -{{ if .Values.config.aiProviders.anthropic.apiKey -}} +{{- if or .Values.config.aiProviders.anthropic.apiKey .Values.config.aiProviders.anthropic.existingSecret -}} {{ if .Values.config.aiProviders.anthropic.existingSecret -}} - name: ANTHROPIC_API_KEY valueFrom: @@ -228,7 +228,7 @@ Render environment variables for the application {{- end }} {{- end }} -{{ if .Values.config.aiProviders.google.apiKey -}} +{{- if or .Values.config.aiProviders.google.apiKey .Values.config.aiProviders.google.existingSecret -}} {{ if .Values.config.aiProviders.google.existingSecret -}} - name: GOOGLE_API_KEY valueFrom: @@ -261,7 +261,7 @@ Render environment variables for the application value: {{ .Values.config.aiProviders.vertexai.location | quote }} {{- end }} -{{ if .Values.config.aiProviders.mistral.apiKey -}} +{{- if or .Values.config.aiProviders.mistral.apiKey .Values.config.aiProviders.mistral.existingSecret -}} {{ if .Values.config.aiProviders.mistral.existingSecret -}} - name: MISTRAL_API_KEY valueFrom: @@ -274,7 +274,7 @@ Render environment variables for the application {{- end }} {{- end }} -{{ if .Values.config.aiProviders.deepseek.apiKey -}} +{{- if or .Values.config.aiProviders.deepseek.apiKey .Values.config.aiProviders.deepseek.existingSecret -}} {{ if .Values.config.aiProviders.deepseek.existingSecret -}} - name: DEEPSEEK_API_KEY valueFrom: @@ -297,7 +297,7 @@ Render environment variables for the application value: {{ .Values.config.aiProviders.openrouter.baseUrl | quote }} {{- end }} -{{ if .Values.config.aiProviders.openrouter.apiKey -}} +{{- if or .Values.config.aiProviders.openrouter.apiKey .Values.config.aiProviders.openrouter.existingSecret -}} {{ if .Values.config.aiProviders.openrouter.existingSecret -}} - name: OPENROUTER_API_KEY valueFrom: @@ -310,7 +310,7 @@ Render environment variables for the application {{- end }} {{- end }} -{{ if .Values.config.aiProviders.groq.apiKey -}} +{{- if or .Values.config.aiProviders.groq.apiKey .Values.config.aiProviders.groq.existingSecret -}} {{ if .Values.config.aiProviders.groq.existingSecret -}} - name: GROQ_API_KEY valueFrom: @@ -323,7 +323,7 @@ Render environment variables for the application {{- end }} {{- end }} -{{ if .Values.config.aiProviders.xai.apiKey -}} +{{- if or .Values.config.aiProviders.xai.apiKey .Values.config.aiProviders.xai.existingSecret -}} {{ if .Values.config.aiProviders.xai.existingSecret -}} - name: XAI_API_KEY valueFrom: @@ -336,7 +336,7 @@ Render environment variables for the application {{- end }} {{- end }} -{{ if .Values.config.aiProviders.elevenlabs.apiKey -}} +{{- if or .Values.config.aiProviders.elevenlabs.apiKey .Values.config.aiProviders.elevenlabs.existingSecret -}} {{ if .Values.config.aiProviders.elevenlabs.existingSecret -}} - name: ELEVENLABS_API_KEY valueFrom: @@ -349,7 +349,7 @@ Render environment variables for the application {{- end }} {{- end }} -{{ if .Values.config.aiProviders.voyage.apiKey -}} +{{- if or .Values.config.aiProviders.voyage.apiKey .Values.config.aiProviders.voyage.existingSecret -}} {{ if .Values.config.aiProviders.voyage.existingSecret -}} - name: VOYAGE_API_KEY valueFrom: @@ -367,7 +367,7 @@ Render environment variables for the application value: {{ .Values.config.aiProviders.openaiCompatible.baseUrl | quote }} {{- end }} -{{ if .Values.config.aiProviders.azureOpenai.apiKey -}} +{{- if or .Values.config.aiProviders.azureOpenai.apiKey .Values.config.aiProviders.azureOpenai.existingSecret -}} {{ if .Values.config.aiProviders.azureOpenai.existingSecret -}} - name: AZURE_OPENAI_API_KEY valueFrom: @@ -400,7 +400,7 @@ Render environment variables for the application value: {{ .Values.config.aiProviders.langchain.endpoint | quote }} {{- end }} -{{ if .Values.config.aiProviders.langchain.apiKey -}} +{{- if or .Values.config.aiProviders.langchain.apiKey .Values.config.aiProviders.langchain.existingSecret -}} {{ if .Values.config.aiProviders.langchain.existingSecret -}} - name: LANGCHAIN_API_KEY valueFrom: @@ -418,7 +418,7 @@ Render environment variables for the application value: {{ .Values.config.aiProviders.langchain.project | quote }} {{- end }} -{{ if .Values.config.aiProviders.firecrawl.apiKey -}} +{{- if or .Values.config.aiProviders.firecrawl.apiKey .Values.config.aiProviders.firecrawl.existingSecret -}} {{ if .Values.config.aiProviders.firecrawl.existingSecret -}} - name: FIRECRAWL_API_KEY valueFrom: @@ -431,7 +431,7 @@ Render environment variables for the application {{- end }} {{- end }} -{{ if .Values.config.aiProviders.jina.apiKey -}} +{{- if or .Values.config.aiProviders.jina.apiKey .Values.config.aiProviders.jina.existingSecret -}} {{ if .Values.config.aiProviders.jina.existingSecret -}} - name: JINA_API_KEY valueFrom: diff --git a/helm/templates/deployment.yaml b/helm/templates/deployment.yaml index 51254562..08c63e64 100644 --- a/helm/templates/deployment.yaml +++ b/helm/templates/deployment.yaml @@ -147,9 +147,14 @@ spec: {{- end }} {{- if .Values.app.startupProbe.enabled }} startupProbe: + {{- if eq .Values.app.startupProbe.probeType "httpGet" }} httpGet: path: {{ .Values.app.startupProbe.path }} port: {{ .Values.app.startupProbe.port }} + {{- else if eq .Values.app.startupProbe.probeType "tcpSocket" }} + tcpSocket: + port: {{ .Values.app.startupProbe.port }} + {{- end }} initialDelaySeconds: {{ .Values.app.startupProbe.initialDelaySeconds }} periodSeconds: {{ .Values.app.startupProbe.periodSeconds }} timeoutSeconds: {{ .Values.app.startupProbe.timeoutSeconds }} diff --git a/helm/templates/secret.yaml b/helm/templates/secret.yaml index 9ccb9827..e339c565 100644 --- a/helm/templates/secret.yaml +++ b/helm/templates/secret.yaml @@ -87,6 +87,7 @@ stringData: {{/* Secret for application authentication password */}} +--- {{- if and .Values.config.auth.password (not .Values.config.auth.existingSecret) }} apiVersion: v1 kind: Secret @@ -102,6 +103,7 @@ stringData: {{/* Secret for SurrealDB authentication */}} +--- {{- if and .Values.surrealdb.enabled .Values.surrealdb.auth.password (not .Values.surrealdb.auth.existingSecret) }} apiVersion: v1 kind: Secret diff --git a/helm/templates/statefulset.yaml b/helm/templates/statefulset.yaml index a5e44565..fec518e2 100644 --- a/helm/templates/statefulset.yaml +++ b/helm/templates/statefulset.yaml @@ -59,10 +59,6 @@ spec: - start - --log - info - - --user - - $(SURREAL_USER) - - --pass - - $(SURREAL_PASS) - -- - rocksdb:{{ .Values.surrealdb.persistence.mountPath }}/mydatabase.db env: diff --git a/helm/values.schema.json b/helm/values.schema.json index e4139b80..cdefd09d 100644 --- a/helm/values.schema.json +++ b/helm/values.schema.json @@ -92,7 +92,7 @@ "properties": { "type": { "enum": ["ClusterIP", "LoadBalancer", "NodePort"] }, "port": { "type": "integer", "minimum": 1, "maximum": 65535 }, - "nodePort": { "type": "string" }, + "nodePort": { "type": ["integer", "null"] }, "annotations": { "type": "object" } } }, @@ -301,8 +301,8 @@ "properties": { "http": { "type": "integer", "minimum": 1, "maximum": 65535 }, "api": { "type": "integer", "minimum": 1, "maximum": 65535 }, - "httpNodePort": { "type": ["integer", "null"] }, - "apiNodePort": { "type": ["integer", "null"] } + "httpNodePort": { "type": ["integer", "null"], "minimum": 1, "maximum": 65535 }, + "apiNodePort": { "type": ["integer", "null"], "minimum": 1, "maximum": 65535 } } }, "annotations": { "type": "object" }, diff --git a/helm/values.yaml b/helm/values.yaml index 340301d3..1a1e4bd5 100644 --- a/helm/values.yaml +++ b/helm/values.yaml @@ -168,7 +168,7 @@ surrealdb: port: 8000 ## @param surrealdb.service.nodePort Specific node port when type is NodePort ## - nodePort: "" + nodePort: null ## @param surrealdb.service.annotations Service annotations ## annotations: {} From dea929a009f7190856e0a192e476ff37ecbbf6f9 Mon Sep 17 00:00:00 2001 From: thuanpham582002 Date: Tue, 6 Jan 2026 10:16:23 +0700 Subject: [PATCH 6/6] fix(helm): Fix additional security issues in Helm chart (Round 3) This commit addresses 6 additional security and configuration issues identified by cubic-dev-ai bot review on Jan 6, 2026. P1 Critical Fixes: - Add runAsUser: 0 to init container securityContext (allows chown to run) - Fix startup probe path from /api/health to /health (consistency) - Add --bind flag to SurrealDB command (respects configured port) P2 Medium Fixes: - Move YAML document separators inside conditional blocks (secret.yaml) - Fix ConfigMap condition to check both extraEnvVarsCM and extraEnvVars - Update SurrealDB password default from "" to "root" (matches README) All changes verified with helm lint and template rendering tests. --- helm/templates/configmap.yaml | 2 +- helm/templates/deployment.yaml | 2 ++ helm/templates/secret.yaml | 4 ++-- helm/templates/statefulset.yaml | 2 ++ helm/values.yaml | 4 ++-- 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/helm/templates/configmap.yaml b/helm/templates/configmap.yaml index bfef9d70..caa0bdb8 100644 --- a/helm/templates/configmap.yaml +++ b/helm/templates/configmap.yaml @@ -19,7 +19,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -}} -{{- if .Values.app.extraEnvVarsCM }} +{{- if and .Values.app.extraEnvVarsCM .Values.app.extraEnvVars }} apiVersion: v1 kind: ConfigMap metadata: diff --git a/helm/templates/deployment.yaml b/helm/templates/deployment.yaml index 08c63e64..80dd02c0 100644 --- a/helm/templates/deployment.yaml +++ b/helm/templates/deployment.yaml @@ -67,6 +67,8 @@ spec: - name: init-permissions image: {{ include "open-notebook.image" . }} imagePullPolicy: {{ .Values.image.pullPolicy }} + securityContext: + runAsUser: 0 command: - /bin/sh - -cx diff --git a/helm/templates/secret.yaml b/helm/templates/secret.yaml index e339c565..bc2ab615 100644 --- a/helm/templates/secret.yaml +++ b/helm/templates/secret.yaml @@ -87,8 +87,8 @@ stringData: {{/* Secret for application authentication password */}} ---- {{- if and .Values.config.auth.password (not .Values.config.auth.existingSecret) }} +--- apiVersion: v1 kind: Secret metadata: @@ -103,8 +103,8 @@ stringData: {{/* Secret for SurrealDB authentication */}} ---- {{- if and .Values.surrealdb.enabled .Values.surrealdb.auth.password (not .Values.surrealdb.auth.existingSecret) }} +--- apiVersion: v1 kind: Secret metadata: diff --git a/helm/templates/statefulset.yaml b/helm/templates/statefulset.yaml index fec518e2..65a0ee5b 100644 --- a/helm/templates/statefulset.yaml +++ b/helm/templates/statefulset.yaml @@ -59,6 +59,8 @@ spec: - start - --log - info + - --bind + - 0.0.0.0:{{ .Values.surrealdb.service.port }} - -- - rocksdb:{{ .Values.surrealdb.persistence.mountPath }}/mydatabase.db env: diff --git a/helm/values.yaml b/helm/values.yaml index 1a1e4bd5..573221b8 100644 --- a/helm/values.yaml +++ b/helm/values.yaml @@ -101,7 +101,7 @@ surrealdb: ## @param surrealdb.auth.password SurrealDB password ## NOTE: Must be set if existingSecret is not provided ## - password: "" + password: "root" ## @param surrealdb.auth.existingSecret Existing secret containing SurrealDB credentials ## NOTE: Must contain keys specified in secretKey.username and secretKey.password ## @@ -371,7 +371,7 @@ app: startupProbe: enabled: false probeType: httpGet - path: /api/health + path: /health port: api initialDelaySeconds: 0 periodSeconds: 10