From 3c21d77641c8e734784ea1b4cde8596469cf50c0 Mon Sep 17 00:00:00 2001 From: Richard Tomik Date: Sun, 23 Mar 2025 17:05:04 +0100 Subject: [PATCH] improved helm chart --- charts/donetick/Chart.yaml | 12 +- charts/donetick/NOTES.txt | 50 +++++++ charts/donetick/readme.md | 110 +++++++++++++++ charts/donetick/templates/configmap.yaml | 76 ++++++---- charts/donetick/templates/deployment.yaml | 134 +++++++++++++++++- charts/donetick/templates/ingress.yaml | 3 + charts/donetick/templates/pvc.yaml | 4 + charts/donetick/templates/secret.yaml | 22 +++ charts/donetick/values.yaml | 162 ++++++++++++++++++---- 9 files changed, 515 insertions(+), 58 deletions(-) create mode 100644 charts/donetick/NOTES.txt create mode 100644 charts/donetick/readme.md create mode 100644 charts/donetick/templates/secret.yaml diff --git a/charts/donetick/Chart.yaml b/charts/donetick/Chart.yaml index 36d74f5..7cedae1 100644 --- a/charts/donetick/Chart.yaml +++ b/charts/donetick/Chart.yaml @@ -3,4 +3,14 @@ name: donetick description: A Helm chart for Donetick application type: application version: 0.1.0 -appVersion: "latest" \ No newline at end of file +appVersion: "latest" +maintainers: + - name: Richard Tomik + email: no@m.com +keywords: + - productivity + - task-management + - donetick +home: https://github.com/rtomik/helm-charts +sources: + - https://github.com/donetick/donetick \ No newline at end of file diff --git a/charts/donetick/NOTES.txt b/charts/donetick/NOTES.txt new file mode 100644 index 0000000..0a40784 --- /dev/null +++ b/charts/donetick/NOTES.txt @@ -0,0 +1,50 @@ +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "donetick.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "donetick.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "donetick.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "donetick.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT +{{- end }} + +2. Application is accessible at port {{ .Values.service.port }} + +3. Donetick application is configured with: + - Database: {{ .Values.config.database.type }} + - User creation: {{ if .Values.config.is_user_creation_disabled }}disabled{{ else }}enabled{{ end }} + +{{- if .Values.persistence.enabled }} +4. Data is persisted using PVC: {{ include "donetick.fullname" . }}-data +{{- else }} +4. WARNING: No persistence enabled. Data will be lost when pods are restarted. +{{- end }} + +{{- if or .Values.config.jwt.existingSecret .Values.config.oauth2.existingSecret }} +5. Using external secrets for sensitive information: +{{- if .Values.config.jwt.existingSecret }} + - JWT secret from: {{ .Values.config.jwt.existingSecret }} +{{- end }} +{{- if .Values.config.oauth2.existingSecret }} + - OAuth2 credentials from: {{ .Values.config.oauth2.existingSecret }} +{{- end }} +{{- else }} +5. SECURITY NOTE: For production use, it's recommended to store sensitive data in Kubernetes Secrets. + - Set config.jwt.existingSecret to use an external secret for JWT + - Set config.oauth2.existingSecret to use an external secret for OAuth2 credentials +{{- end }} + +For more information about using this Helm chart, please refer to the README.md file. \ No newline at end of file diff --git a/charts/donetick/readme.md b/charts/donetick/readme.md new file mode 100644 index 0000000..92c6074 --- /dev/null +++ b/charts/donetick/readme.md @@ -0,0 +1,110 @@ +# Donetick Helm Chart + +A Helm chart for deploying the Donetick task management application on Kubernetes. + +## Introduction + +This chart deploys [Donetick](https://github.com/donetick/donetick) on a Kubernetes cluster using the Helm package manager. + +## Prerequisites + +- Kubernetes 1.19+ +- Helm 3.0+ +- PV provisioner support in the underlying infrastructure (if persistence is needed) + +## Installing the Chart + +To install the chart with the release name `my-donetick`: + +```bash +$ helm repo add donetick-chart https://rtomik.github.io/helm-charts +$ helm install my-donetick donetick-chart/donetick +``` + +> **Tip**: List all releases using `helm list` + +## Uninstalling the Chart + +To uninstall/delete the `my-donetick` deployment: + +```bash +$ helm delete my-donetick +``` + +## Parameters + +### Global parameters + +| Name | Description | Value | +|------------------------|-------------------------------------------------------------------------------------|-------| +| `nameOverride` | String to partially override the release name | `""` | +| `fullnameOverride` | String to fully override the release name | `""` | + +### Image parameters + +| Name | Description | Value | +|-------------------------|--------------------------------------------------------------------------------------|--------------------| +| `image.repository` | Donetick image repository | `donetick/donetick` | +| `image.tag` | Donetick image tag | `latest` | +| `image.pullPolicy` | Donetick image pull policy | `IfNotPresent` | +| `imagePullSecrets` | Global Docker registry secret names as an array | `[]` | + +### Secret Management + +| Name | Description | Value | +|----------------------------------------|--------------------------------------------------------------------|---------------------| +| `config.jwt.existingSecret` | Name of existing secret for JWT token | `""` | +| `config.jwt.secretKey` | Key in the existing secret for JWT token | `"jwtSecret"` | +| `config.oauth2.existingSecret` | Name of existing secret for OAuth2 credentials | `""` | +| `config.oauth2.clientIdKey` | Key in the existing secret for OAuth2 client ID | `"client-id"` | +| `config.oauth2.clientSecretKey` | Key in the existing secret for OAuth2 client secret | `"client-secret"` | +| `config.database.existingSecret` | Name of existing secret for database credentials | `""` | +| `config.database.hostKey` | Key in the existing secret for database host | `"db-host"` | +| `config.database.portKey` | Key in the existing secret for database port | `"db-port"` | +| `config.database.userKey` | Key in the existing secret for database user | `"db-user"` | +| `config.database.passwordKey` | Key in the existing secret for database password | `"db-password"` | +| `config.database.nameKey` | Key in the existing secret for database name | `"db-name"` | + +### Deployment parameters + +| Name | Description | Value | +|--------------------------------------|--------------------------------------------------------------------------|-----------| +| `replicaCount` | Number of Donetick replicas | `1` | +| `revisionHistoryLimit` | Number of revisions to retain for rollback | `3` | +| `podSecurityContext.runAsNonRoot` | Run containers as non-root user | `true` | +| `podSecurityContext.runAsUser` | User ID for the container | `1000` | +| `podSecurityContext.fsGroup` | Group ID for the container filesystem | `1000` | +| `containerSecurityContext` | Security context for the container | See values.yaml | +| `nodeSelector` | Node labels for pod assignment | `{}` | +| `tolerations` | Tolerations for pod assignment | `[]` | +| `affinity` | Affinity for pod assignment | `{}` | + +### Service parameters + +| Name | Description | Value | +|----------------------------|------------------------------------------------------|-------------| +| `service.type` | Kubernetes Service type | `ClusterIP` | +| `service.port` | Service HTTP port | `2021` | +| `service.annotations` | Additional annotations for Service | `{}` | +| `service.nodePort` | Service HTTP node port (when applicable) | `""` | + +### Ingress parameters + +| Name | Description | Value | +|----------------------------|------------------------------------------------------|----------------------| +| `ingress.enabled` | Enable ingress record generation | `true` | +| `ingress.className` | IngressClass name | `"traefik"` | +| `ingress.annotations` | Additional annotations for the Ingress resource | See values.yaml | +| `ingress.hosts` | Array of host and path objects | See values.yaml | +| `ingress.tlsSecretName` | Global TLS secret name for all hosts | `""` | +| `ingress.tls` | TLS configuration | See values.yaml | +| `ingress.tls[].secretName` | Host-specific TLS secret name (overrides global) | `""` | + +### Persistence parameters + +| Name | Description | Value | +|-------------------------------|------------------------------------------------------|---------------| +| `persistence.enabled` | Enable persistence using PVC | `true` | +| `persistence.storageClass` | PVC Storage Class | `"longhorn"` | +| `persistence.accessMode` | PVC Access Mode | `ReadWriteOnce` | +| `persistence.size` | \ No newline at end of file diff --git a/charts/donetick/templates/configmap.yaml b/charts/donetick/templates/configmap.yaml index ca92424..1a6f250 100644 --- a/charts/donetick/templates/configmap.yaml +++ b/charts/donetick/templates/configmap.yaml @@ -16,41 +16,61 @@ data: database: type: {{ .Values.config.database.type | default "sqlite" | quote }} migration: {{ .Values.config.database.migration }} - host: {{ .Values.config.database.host | default "secret" | quote }} - port: {{ .Values.config.database.port | default 5432 }} - user: {{ .Values.config.database.user | default "secret" | quote }} - password: {{ .Values.config.database.password | default "secret" | quote }} - name: {{ .Values.config.database.name | default "secret" | quote }} + {{- if .Values.config.database.migration_skip }} + migration_skip: {{ .Values.config.database.migration_skip }} + {{- end }} + {{- if .Values.config.database.migration_retry }} + migration_retry: {{ .Values.config.database.migration_retry }} + {{- end }} + {{- if eq .Values.config.database.type "postgres" }} + {{- if not .Values.config.database.existingSecret }} + host: {{ .Values.config.database.host | quote }} + port: {{ .Values.config.database.port }} + user: {{ .Values.config.database.user | quote }} + password: {{ .Values.config.database.password | quote }} + name: {{ .Values.config.database.name | quote }} + {{- else }} + # Database credentials will be injected via environment variables from Secret + {{- end }} + {{- end }} jwt: + {{- if .Values.config.jwt.existingSecret }} + # Secret will be injected from Secret + {{- else }} secret: {{ .Values.config.jwt.secret | quote }} - session_time: {{ .Values.config.jwt.session_time | default "168h" | quote }} - max_refresh: {{ .Values.config.jwt.max_refresh | default "168h" | quote }} + {{- end }} + session_time: {{ .Values.config.jwt.session_time | quote }} + max_refresh: {{ .Values.config.jwt.max_refresh | quote }} server: - port: {{ .Values.config.server.port | default 2021 }} - read_timeout: {{ .Values.config.server.read_timeout | default "10s" | quote }} - write_timeout: {{ .Values.config.server.write_timeout | default "10s" | quote }} - rate_period: {{ .Values.config.server.rate_period | default "60s" | quote }} - rate_limit: {{ .Values.config.server.rate_limit | default 300 }} + port: {{ .Values.config.server.port }} + read_timeout: {{ .Values.config.server.read_timeout | quote }} + write_timeout: {{ .Values.config.server.write_timeout | quote }} + rate_period: {{ .Values.config.server.rate_period | quote }} + rate_limit: {{ .Values.config.server.rate_limit }} cors_allow_origins: {{- range .Values.config.server.cors_allow_origins }} - {{ . | quote }} {{- end }} - serve_frontend: {{ .Values.config.server.serve_frontend | default true }} + serve_frontend: {{ .Values.config.server.serve_frontend }} scheduler_jobs: - due_job: {{ .Values.config.scheduler_jobs.due_job | default "30m" | quote }} - overdue_job: {{ .Values.config.scheduler_jobs.overdue_job | default "3h" | quote }} - pre_due_job: {{ .Values.config.scheduler_jobs.pre_due_job | default "3h" | quote }} + due_job: {{ .Values.config.scheduler_jobs.due_job | quote }} + overdue_job: {{ .Values.config.scheduler_jobs.overdue_job | quote }} + pre_due_job: {{ .Values.config.scheduler_jobs.pre_due_job | quote }} email: - host: {{ .Values.config.email.host | quote }} - port: {{ .Values.config.email.port | quote }} - key: {{ .Values.config.email.key | quote }} - email: {{ .Values.config.email.email | quote }} - appHost: {{ .Values.config.email.appHost | quote }} + host: {{ .Values.config.email.host | default "" | quote }} + port: {{ .Values.config.email.port | default "" | quote }} + key: {{ .Values.config.email.key | default "" | quote }} + email: {{ .Values.config.email.email | default "" | quote }} + appHost: {{ .Values.config.email.appHost | default "" | quote }} oauth2: - client_id: {{ .Values.config.oauth2.client_id | quote }} - client_secret: {{ .Values.config.oauth2.client_secret | quote }} - auth_url: {{ .Values.config.oauth2.auth_url | quote }} - token_url: {{ .Values.config.oauth2.token_url | quote }} - user_info_url: {{ .Values.config.oauth2.user_info_url | quote }} - redirect_url: {{ .Values.config.oauth2.redirect_url | quote }} - name: {{ .Values.config.oauth2.name | quote }} \ No newline at end of file + {{- if .Values.config.oauth2.existingSecret }} + # Client ID and Secret will be injected from Secret + {{- else }} + client_id: {{ .Values.config.oauth2.client_id | default "" | quote }} + client_secret: {{ .Values.config.oauth2.client_secret | default "" | quote }} + {{- end }} + auth_url: {{ .Values.config.oauth2.auth_url | default "" | quote }} + token_url: {{ .Values.config.oauth2.token_url | default "" | quote }} + user_info_url: {{ .Values.config.oauth2.user_info_url | default "" | quote }} + redirect_url: {{ .Values.config.oauth2.redirect_url | default "" | quote }} + name: {{ .Values.config.oauth2.name | default "" | quote }} \ No newline at end of file diff --git a/charts/donetick/templates/deployment.yaml b/charts/donetick/templates/deployment.yaml index 2894b2b..7268b92 100644 --- a/charts/donetick/templates/deployment.yaml +++ b/charts/donetick/templates/deployment.yaml @@ -4,35 +4,144 @@ metadata: name: {{ include "donetick.fullname" . }} labels: {{- include "donetick.labels" . | nindent 4 }} + annotations: + checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} spec: replicas: {{ .Values.replicaCount }} + revisionHistoryLimit: {{ .Values.revisionHistoryLimit }} selector: matchLabels: {{- include "donetick.selectorLabels" . | nindent 6 }} + strategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: 1 + maxSurge: 1 template: metadata: labels: {{- include "donetick.selectorLabels" . | nindent 8 }} + annotations: + {{- with .Values.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} containers: - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.containerSecurityContext | nindent 12 }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} command: ["/donetick"] + {{- if .Values.startupArgs }} + args: + {{- range .Values.startupArgs }} + - {{ . | quote }} + {{- end }} + {{- end }} ports: - name: http - containerPort: 2021 + containerPort: {{ .Values.config.server.port }} protocol: TCP + {{- if .Values.probes.liveness.enabled }} + livenessProbe: + httpGet: + path: {{ .Values.probes.liveness.path }} + port: http + initialDelaySeconds: {{ .Values.probes.liveness.initialDelaySeconds }} + periodSeconds: {{ .Values.probes.liveness.periodSeconds }} + timeoutSeconds: {{ .Values.probes.liveness.timeoutSeconds }} + failureThreshold: {{ .Values.probes.liveness.failureThreshold }} + successThreshold: {{ .Values.probes.liveness.successThreshold }} + {{- end }} + {{- if .Values.probes.readiness.enabled }} + readinessProbe: + httpGet: + path: {{ .Values.probes.readiness.path }} + port: http + initialDelaySeconds: {{ .Values.probes.readiness.initialDelaySeconds }} + periodSeconds: {{ .Values.probes.readiness.periodSeconds }} + timeoutSeconds: {{ .Values.probes.readiness.timeoutSeconds }} + failureThreshold: {{ .Values.probes.readiness.failureThreshold }} + successThreshold: {{ .Values.probes.readiness.successThreshold }} + {{- end }} env: {{- range .Values.env }} - name: {{ .name }} value: {{ .value | quote }} {{- end }} + {{- if or .Values.config.jwt.existingSecret .Values.config.oauth2.existingSecret .Values.config.database.existingSecret }} + # Secret-based environment variables + {{- if .Values.config.jwt.existingSecret }} + - name: DT_JWT_SECRET + valueFrom: + secretKeyRef: + name: {{ .Values.config.jwt.existingSecret }} + key: {{ .Values.config.jwt.secretKey }} + {{- end }} + {{- if .Values.config.oauth2.existingSecret }} + - name: DT_OAUTH2_CLIENT_ID + valueFrom: + secretKeyRef: + name: {{ .Values.config.oauth2.existingSecret }} + key: {{ .Values.config.oauth2.clientIdKey }} + - name: DT_OAUTH2_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: {{ .Values.config.oauth2.existingSecret }} + key: {{ .Values.config.oauth2.clientSecretKey }} + {{- end }} + {{- if and .Values.config.database.existingSecret (eq .Values.config.database.type "postgres") }} + - name: DT_DB_HOST + valueFrom: + secretKeyRef: + name: {{ .Values.config.database.existingSecret }} + key: {{ .Values.config.database.hostKey }} + - name: DT_DB_PORT + valueFrom: + secretKeyRef: + name: {{ .Values.config.database.existingSecret }} + key: {{ .Values.config.database.portKey }} + - name: DT_DB_USER + valueFrom: + secretKeyRef: + name: {{ .Values.config.database.existingSecret }} + key: {{ .Values.config.database.userKey }} + - name: DT_DB_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.config.database.existingSecret }} + key: {{ .Values.config.database.passwordKey }} + - name: DT_DB_NAME + valueFrom: + secretKeyRef: + name: {{ .Values.config.database.existingSecret }} + key: {{ .Values.config.database.nameKey }} + {{- end }} + {{- end }} + {{- with .Values.extraEnv }} + {{- toYaml . | nindent 12 }} + {{- end }} volumeMounts: - name: config - mountPath: /config + mountPath: /config + readOnly: true - name: data mountPath: /donetick-data + {{- if not .Values.containerSecurityContext.readOnlyRootFilesystem }} + - name: tmp + mountPath: /tmp + {{- end }} + {{- with .Values.extraVolumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} resources: {{- toYaml .Values.resources | nindent 12 }} volumes: @@ -41,4 +150,23 @@ spec: name: {{ include "donetick.fullname" . }}-configmap - name: data persistentVolumeClaim: - claimName: {{ include "donetick.fullname" . }}-data \ No newline at end of file + claimName: {{ include "donetick.fullname" . }}-data + {{- if not .Values.containerSecurityContext.readOnlyRootFilesystem }} + - name: tmp + emptyDir: {} + {{- end }} + {{- with .Values.extraVolumes }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} \ No newline at end of file diff --git a/charts/donetick/templates/ingress.yaml b/charts/donetick/templates/ingress.yaml index 247b365..65d0761 100644 --- a/charts/donetick/templates/ingress.yaml +++ b/charts/donetick/templates/ingress.yaml @@ -20,6 +20,9 @@ spec: {{- range .hosts }} - {{ . | quote }} {{- end }} + {{- if .secretName }} + secretName: {{ .secretName }} + {{- end }} {{- end }} {{- end }} rules: diff --git a/charts/donetick/templates/pvc.yaml b/charts/donetick/templates/pvc.yaml index d7fddd0..0e824e4 100644 --- a/charts/donetick/templates/pvc.yaml +++ b/charts/donetick/templates/pvc.yaml @@ -5,6 +5,10 @@ metadata: name: {{ include "donetick.fullname" . }}-data labels: {{- include "donetick.labels" . | nindent 4 }} + {{- with .Values.persistence.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} spec: accessModes: - {{ .Values.persistence.accessMode | quote }} diff --git a/charts/donetick/templates/secret.yaml b/charts/donetick/templates/secret.yaml new file mode 100644 index 0000000..3b225f7 --- /dev/null +++ b/charts/donetick/templates/secret.yaml @@ -0,0 +1,22 @@ +{{- if or (not .Values.config.jwt.existingSecret) (and (not .Values.config.oauth2.existingSecret) (or .Values.config.oauth2.client_id .Values.config.oauth2.client_secret)) (and (eq .Values.config.database.type "postgres") (not .Values.config.database.existingSecret)) }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "donetick.fullname" . }}-secrets + labels: + {{- include "donetick.labels" . | nindent 4 }} +type: Opaque +data: + {{- if not .Values.config.jwt.existingSecret }} + {{ .Values.config.jwt.secretKey }}: {{ .Values.config.jwt.secret | b64enc }} + {{- end }} + {{- if and (eq .Values.config.database.type "postgres") (not .Values.config.database.existingSecret) }} + {{ .Values.config.database.passwordKey }}: {{ .Values.config.database.password | b64enc }} + {{- end }} + {{- if and (not .Values.config.oauth2.existingSecret) .Values.config.oauth2.client_id }} + {{ .Values.config.oauth2.clientIdKey }}: {{ .Values.config.oauth2.client_id | b64enc }} + {{- end }} + {{- if and (not .Values.config.oauth2.existingSecret) .Values.config.oauth2.client_secret }} + {{ .Values.config.oauth2.clientSecretKey }}: {{ .Values.config.oauth2.client_secret | b64enc }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/charts/donetick/values.yaml b/charts/donetick/values.yaml index 8938f7a..18cc7f0 100644 --- a/charts/donetick/values.yaml +++ b/charts/donetick/values.yaml @@ -1,43 +1,90 @@ +## Global settings +nameOverride: "" +fullnameOverride: "" + +## Image settings image: repository: donetick/donetick tag: latest pullPolicy: IfNotPresent - -nameOverride: "" -fullnameOverride: "" - + +## Deployment settings replicaCount: 1 +revisionHistoryLimit: 3 +# Optional startup arguments +startupArgs: [] + # - "--skip-migrations" # Uncomment to skip database migrations on startup + +# Pod security settings +podSecurityContext: + runAsNonRoot: true + runAsUser: 1000 + fsGroup: 1000 + +containerSecurityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + +## Pod scheduling +nodeSelector: {} +tolerations: [] +affinity: {} + +## Service settings service: type: ClusterIP port: 2021 +## Ingress settings ingress: enabled: true className: "traefik" annotations: traefik.ingress.kubernetes.io/router.entrypoints: websecure hosts: - - host: donetick.example.com + - host: donetick.tomik.lat paths: - path: / pathType: Prefix tls: - hosts: - - donetick.example.com + - donetick.tomik.lat + # Optional: specify the name of an existing TLS secret + # secretName: "existing-tls-secret" +## Persistence settings persistence: enabled: true storageClass: "longhorn" accessMode: ReadWriteOnce size: 1Gi + annotations: {} +## Environment variables env: - name: DT_ENV value: selfhosted - name: DT_SQLITE_PATH - value: /donetick-data/donetick.db + value: /donetick-data/donetick.db +# Extra environment variables (for advanced use cases) +extraEnv: [] + # - name: DT_LOG_LEVEL + # value: "debug" + # - name: DT_SKIP_MIGRATIONS + # value: "true" + +# Extra volume mounts +extraVolumeMounts: [] + +# Extra volumes +extraVolumes: [] + +## Resource limits and requests resources: limits: cpu: 500m @@ -46,27 +93,78 @@ resources: cpu: 100m memory: 128Mi +## Application health checks +probes: + liveness: + enabled: true + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + successThreshold: 1 + path: /health + readiness: + enabled: true + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 3 + successThreshold: 1 + path: /health + +## Autoscaling configuration +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 5 + targetCPUUtilizationPercentage: 80 + targetMemoryUtilizationPercentage: 80 + +## Application configuration config: name: "selfhosted" is_done_tick_dot_com: false is_user_creation_disabled: false + + # Notification settings telegram: token: "" pushover: token: "" + + # Database configuration database: type: "sqlite" migration: true - # these are only required for postgres - host: "secret" + # Migration options + migration_skip: false # Set to true to skip database migrations + migration_retry: 3 # Number of retries for failed migrations + + # These are only required for postgres - direct configuration + host: "" port: 5432 - user: "secret" - password: "secret" - name: "secret" + user: "" + password: "" + name: "" + + # Secret configuration for database credentials + existingSecret: "" # Name of existing Kubernetes secret + hostKey: "db-host" # Key in the secret for database host + portKey: "db-port" # Key in the secret for database port + userKey: "db-user" # Key in the secret for database user + passwordKey: "db-password" # Key in the secret for database password + nameKey: "db-name" # Key in the secret for database name + + # Security settings + # For production, use a generated secret and store in a Kubernetes Secret jwt: - secret: "secret" + existingSecret: "" # Set this to use an existing secret + secretKey: "jwtSecret" # The key in the secret where JWT secret is stored + secret: "changeme-this-secret-should-be-at-least-32-characters-long" # Only used if existingSecret is not set session_time: 168h max_refresh: 168h + + # Server configuration server: port: 2021 read_timeout: 10s @@ -76,25 +174,37 @@ config: cors_allow_origins: - "http://localhost:5173" - "http://localhost:7926" - # the below are required for the android app to work + # The below are required for the android app to work - "https://localhost" - "capacitor://localhost" serve_frontend: true + + # Scheduler configuration scheduler_jobs: due_job: 30m overdue_job: 3h pre_due_job: 3h + + # Email settings email: - host: - port: - key: - email: - appHost: + host: "" + port: "" + key: "" + email: "" + appHost: "" + + # OAuth2 configuration oauth2: - client_id: - client_secret: - auth_url: - token_url: - user_info_url: - redirect_url: - name: \ No newline at end of file + # Direct configuration + client_id: "" + client_secret: "" + # Secret configuration - alternative to direct configuration + existingSecret: "" # Name of existing Kubernetes secret + clientIdKey: "client-id" # Key in the secret for client ID + clientSecretKey: "client-secret" # Key in the secret for client secret + # Other OAuth2 settings + auth_url: "" + token_url: "" + user_info_url: "" + redirect_url: "" + name: "" \ No newline at end of file