From 9fba509e624f943a540fc01803a3c4e3807eda04 Mon Sep 17 00:00:00 2001 From: Jansen Fuller Date: Thu, 30 Apr 2026 22:54:31 -0600 Subject: [PATCH 01/19] Adding Helm chart --- helm/taskchampion-sync-server/.helmignore | 5 + helm/taskchampion-sync-server/Chart.yaml | 15 +++ helm/taskchampion-sync-server/README.md | 61 +++++++++++ .../examples/postgres-values.yaml | 31 ++++++ .../examples/sqlite-values.yaml | 22 ++++ .../templates/_helpers.tpl | 41 +++++++ .../templates/deployment.yaml | 100 ++++++++++++++++++ .../templates/httproute.yaml | 23 ++++ .../templates/ingress.yaml | 33 ++++++ .../templates/pvc.yaml | 17 +++ .../templates/service.yaml | 15 +++ .../templates/validation-config.yaml | 7 ++ helm/taskchampion-sync-server/values.yaml | 84 +++++++++++++++ 13 files changed, 454 insertions(+) create mode 100644 helm/taskchampion-sync-server/.helmignore create mode 100644 helm/taskchampion-sync-server/Chart.yaml create mode 100644 helm/taskchampion-sync-server/README.md create mode 100644 helm/taskchampion-sync-server/examples/postgres-values.yaml create mode 100644 helm/taskchampion-sync-server/examples/sqlite-values.yaml create mode 100644 helm/taskchampion-sync-server/templates/_helpers.tpl create mode 100644 helm/taskchampion-sync-server/templates/deployment.yaml create mode 100644 helm/taskchampion-sync-server/templates/httproute.yaml create mode 100644 helm/taskchampion-sync-server/templates/ingress.yaml create mode 100644 helm/taskchampion-sync-server/templates/pvc.yaml create mode 100644 helm/taskchampion-sync-server/templates/service.yaml create mode 100644 helm/taskchampion-sync-server/templates/validation-config.yaml create mode 100644 helm/taskchampion-sync-server/values.yaml diff --git a/helm/taskchampion-sync-server/.helmignore b/helm/taskchampion-sync-server/.helmignore new file mode 100644 index 0000000..6c3a50f --- /dev/null +++ b/helm/taskchampion-sync-server/.helmignore @@ -0,0 +1,5 @@ +# Patterns to ignore when building the Helm chart +.git +.gitignore +*.md +examples/ diff --git a/helm/taskchampion-sync-server/Chart.yaml b/helm/taskchampion-sync-server/Chart.yaml new file mode 100644 index 0000000..1d431c1 --- /dev/null +++ b/helm/taskchampion-sync-server/Chart.yaml @@ -0,0 +1,15 @@ +apiVersion: v2 +name: taskchampion-sync-server +description: A Helm chart for deploying TaskChampion Sync Server on Kubernetes +type: application +version: 0.1.0 +appVersion: "0.7.0" +keywords: + - taskchampion + - taskwarrior + - sync + - task-management +home: https://github.com/GothenburgBitFactory/taskchampion-sync-server +sources: + - https://github.com/GothenburgBitFactory/taskchampion-sync-server +maintainers: [] diff --git a/helm/taskchampion-sync-server/README.md b/helm/taskchampion-sync-server/README.md new file mode 100644 index 0000000..5631a76 --- /dev/null +++ b/helm/taskchampion-sync-server/README.md @@ -0,0 +1,61 @@ +# TaskChampion Sync Server Helm Chart + +Deploy the [TaskChampion Sync Server](https://github.com/GothenburgBitFactory/taskchampion-sync-server) on Kubernetes. + +## Prerequisites + +- Kubernetes 1.23+ +- Helm 3+ + +## Storage Backends + +Exactly one storage backend must be enabled. The chart will fail validation if both or neither are enabled. + +### SQLite + +```console +helm install my-release ./helm/taskchampion-sync-server -f helm/taskchampion-sync-server/examples/sqlite-values.yaml +``` + +### PostgreSQL + +```console +helm install my-release ./helm/taskchampion-sync-server -f helm/taskchampion-sync-server/examples/postgres-values.yaml +``` + +## Secrets + +The chart expects pre-created secrets referenced by name: + +### Client ID Secret + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: my-client-ids +type: Opaque +data: + client-ids: +``` + +Reference it via `clientIdSecret: "my-client-ids"`. + +### PostgreSQL Secret + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: my-postgres-creds +type: Opaque +data: + connection: + password: +``` + +Reference it via `postgres.existingSecret: "my-postgres-creds"`. + +## Configuration + +See [values.yaml](values.yaml) for all configurable options. diff --git a/helm/taskchampion-sync-server/examples/postgres-values.yaml b/helm/taskchampion-sync-server/examples/postgres-values.yaml new file mode 100644 index 0000000..6741d71 --- /dev/null +++ b/helm/taskchampion-sync-server/examples/postgres-values.yaml @@ -0,0 +1,31 @@ +sqlite: + enabled: false + +postgres: + enabled: true + existingSecret: "taskchampion-postgres-creds" + database: taskchampion + host: postgres-primary + username: taskchampion-user + +clientIdSecret: "taskchampion-client-ids" + +env: + RUST_LOG: debug + LISTEN: "0.0.0.0:8080" + CREATE_CLIENTS: "false" + +replicas: + enabled: true + count: 3 + +image: + pullSecrets: + - my-registry-secret + +httpRoute: + enabled: true + gateway: "my-gateway" + host: "taskchampion.example.com" + path: "/" + port: 8080 diff --git a/helm/taskchampion-sync-server/examples/sqlite-values.yaml b/helm/taskchampion-sync-server/examples/sqlite-values.yaml new file mode 100644 index 0000000..9159979 --- /dev/null +++ b/helm/taskchampion-sync-server/examples/sqlite-values.yaml @@ -0,0 +1,22 @@ +sqlite: + enabled: true + existingPV: "" + emptyDir: + sizeLimit: 1Gi + persistence: + enabled: false + +postgres: + enabled: false + +clientIdSecret: "taskchampion-client-ids" + +env: + RUST_LOG: info + LISTEN: "0.0.0.0:8080" + CREATE_CLIENTS: "false" + +ingress: + enabled: true + hosts: + - taskchampion.example.com diff --git a/helm/taskchampion-sync-server/templates/_helpers.tpl b/helm/taskchampion-sync-server/templates/_helpers.tpl new file mode 100644 index 0000000..be73dc9 --- /dev/null +++ b/helm/taskchampion-sync-server/templates/_helpers.tpl @@ -0,0 +1,41 @@ +{{- /* +taskchampion-sync-server helpers +*/ -}} + +{{- define "taskchampion-sync-server.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{- define "taskchampion-sync-server.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{- define "taskchampion-sync-server.labels" -}} +helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +app.kubernetes.io/name: {{ include "taskchampion-sync-server.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{- define "taskchampion-sync-server.selectorLabels" -}} +app.kubernetes.io/name: {{ include "taskchampion-sync-server.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{- define "taskchampion-sync-server.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "taskchampion-sync-server.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/helm/taskchampion-sync-server/templates/deployment.yaml b/helm/taskchampion-sync-server/templates/deployment.yaml new file mode 100644 index 0000000..aef6395 --- /dev/null +++ b/helm/taskchampion-sync-server/templates/deployment.yaml @@ -0,0 +1,100 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "taskchampion-sync-server.fullname" . }} + labels: + {{- include "taskchampion-sync-server.labels" . | nindent 4 }} +spec: + {{- if and (eq .Values.postgres.enabled true) .Values.replicas.enabled }} + replicas: {{ .Values.replicas.count }} + {{- else }} + replicas: 1 + {{- end }} + selector: + matchLabels: + {{- include "taskchampion-sync-server.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "taskchampion-sync-server.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.image.pullSecrets }} + imagePullSecrets: + {{- range . }} + - name: {{ . }} + {{- end }} + {{- end }} + securityContext: + {{- toYaml .Values.securityContext | nindent 8 }} + containers: + - name: taskchampion-sync-server + {{- if eq .Values.postgres.enabled true }} + image: "{{ .Values.image.repository }}-postgres:{{ .Values.image.tag }}" + {{- else }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + {{- end }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + env: + {{- range $name, $value := .Values.env }} + - name: {{ $name }} + value: {{ $value | quote }} + {{- end }} + {{- if .Values.clientIdSecret }} + - name: CLIENT_ID + valueFrom: + secretKeyRef: + name: {{ .Values.clientIdSecret }} + key: client-ids + {{- end }} + {{- if eq .Values.sqlite.enabled true }} + - name: DATA_DIR + value: {{ .Values.sqlite.dataDir }} + {{- end }} + {{- if eq .Values.postgres.enabled true }} + - name: CONNECTION + valueFrom: + secretKeyRef: + name: {{ required "postgres.existingSecret is required when postgres is enabled" .Values.postgres.existingSecret }} + key: connection + optional: true + - name: PGPASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.postgres.existingSecret }} + key: password + optional: true + {{- end }} + ports: + - name: http + containerPort: {{ .Values.service.targetPort }} + protocol: TCP + {{- with .Values.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- if eq .Values.sqlite.enabled true }} + volumeMounts: + - name: data + mountPath: {{ .Values.sqlite.dataDir }} + {{- end }} + {{- if eq .Values.sqlite.enabled true }} + volumes: + {{- if .Values.sqlite.existingPV }} + - name: data + persistentVolumeClaim: + claimName: {{ .Values.sqlite.existingPV }} + {{- else if .Values.sqlite.persistence.enabled }} + - name: data + persistentVolumeClaim: + claimName: {{ include "taskchampion-sync-server.fullname" . }}-pvc + {{- else }} + - name: data + emptyDir: + {{- if .Values.sqlite.emptyDir.sizeLimit }} + sizeLimit: {{ .Values.sqlite.emptyDir.sizeLimit }} + {{- end }} + {{- if .Values.sqlite.emptyDir.medium }} + medium: {{ .Values.sqlite.emptyDir.medium }} + {{- end }} + {{- end }} + {{- end }} diff --git a/helm/taskchampion-sync-server/templates/httproute.yaml b/helm/taskchampion-sync-server/templates/httproute.yaml new file mode 100644 index 0000000..8ca5245 --- /dev/null +++ b/helm/taskchampion-sync-server/templates/httproute.yaml @@ -0,0 +1,23 @@ +{{- if .Values.httpRoute.enabled }} +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ include "taskchampion-sync-server.fullname" . }} + labels: + {{- include "taskchampion-sync-server.labels" . | nindent 4 }} +spec: + parentRefs: + - name: {{ .Values.httpRoute.gateway }} + {{- if .Values.httpRoute.host }} + hostnames: + - {{ .Values.httpRoute.host }} + {{- end }} + rules: + - matches: + - path: + type: PathPrefix + value: {{ .Values.httpRoute.path }} + backendRefs: + - name: {{ include "taskchampion-sync-server.fullname" . }} + port: {{ .Values.httpRoute.port }} +{{- end }} diff --git a/helm/taskchampion-sync-server/templates/ingress.yaml b/helm/taskchampion-sync-server/templates/ingress.yaml new file mode 100644 index 0000000..8565711 --- /dev/null +++ b/helm/taskchampion-sync-server/templates/ingress.yaml @@ -0,0 +1,33 @@ +{{- if .Values.ingress.enabled }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "taskchampion-sync-server.fullname" . }} + labels: + {{- include "taskchampion-sync-server.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.ingress.className }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- toYaml .Values.ingress.tls | nindent 4 }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ . | quote }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ include "taskchampion-sync-server.fullname" $ }} + port: + number: {{ $.Values.service.port }} + {{- end }} +{{- end }} diff --git a/helm/taskchampion-sync-server/templates/pvc.yaml b/helm/taskchampion-sync-server/templates/pvc.yaml new file mode 100644 index 0000000..42b701a --- /dev/null +++ b/helm/taskchampion-sync-server/templates/pvc.yaml @@ -0,0 +1,17 @@ +{{- if and (eq .Values.sqlite.enabled true) .Values.sqlite.persistence.enabled (not .Values.sqlite.existingPV) }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "taskchampion-sync-server.fullname" . }}-pvc + labels: + {{- include "taskchampion-sync-server.labels" . | nindent 4 }} +spec: + accessModes: + - {{ .Values.sqlite.persistence.accessMode }} + resources: + requests: + storage: {{ .Values.sqlite.persistence.size }} + {{- if .Values.sqlite.persistence.storageClass }} + storageClassName: {{ .Values.sqlite.persistence.storageClass }} + {{- end }} +{{- end }} diff --git a/helm/taskchampion-sync-server/templates/service.yaml b/helm/taskchampion-sync-server/templates/service.yaml new file mode 100644 index 0000000..9083f04 --- /dev/null +++ b/helm/taskchampion-sync-server/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "taskchampion-sync-server.fullname" . }} + labels: + {{- include "taskchampion-sync-server.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: {{ .Values.service.targetPort }} + protocol: TCP + name: http + selector: + {{- include "taskchampion-sync-server.selectorLabels" . | nindent 4 }} diff --git a/helm/taskchampion-sync-server/templates/validation-config.yaml b/helm/taskchampion-sync-server/templates/validation-config.yaml new file mode 100644 index 0000000..9add1b5 --- /dev/null +++ b/helm/taskchampion-sync-server/templates/validation-config.yaml @@ -0,0 +1,7 @@ +{{- if and (eq .Values.sqlite.enabled false) (eq .Values.postgres.enabled false) -}} +{{- fail "ERROR: Exactly one storage backend must be enabled. Either sqlite.enabled or postgres.enabled must be true." -}} +{{- end -}} + +{{- if and (eq .Values.sqlite.enabled true) (eq .Values.postgres.enabled true) -}} +{{- fail "ERROR: Only one storage backend can be enabled. Both sqlite.enabled and postgres.enabled cannot be true at the same time." -}} +{{- end -}} diff --git a/helm/taskchampion-sync-server/values.yaml b/helm/taskchampion-sync-server/values.yaml new file mode 100644 index 0000000..46091d4 --- /dev/null +++ b/helm/taskchampion-sync-server/values.yaml @@ -0,0 +1,84 @@ +# Image configuration +image: + repository: ghcr.io/gothenburgbitfactory/taskchampion-sync-server + tag: "0.7.0" + pullPolicy: IfNotPresent + pullSecrets: [] + +# Existing secret containing client IDs (comma-separated UUIDs) +# Expected key: client-ids +clientIdSecret: "" + +# Environment variables passed directly to the container +# NOTE: DATA_DIR and CONNECTION are set automatically based on backend +env: + RUST_LOG: info + LISTEN: "0.0.0.0:8080" + CREATE_CLIENTS: "false" + +# Service configuration +service: + type: ClusterIP + port: 8080 + targetPort: 8080 + +# Ingress configuration +ingress: + enabled: false + className: "" + annotations: {} + hosts: [] + tls: [] + +# HTTPRoute configuration (Kubernetes Gateway API) +httpRoute: + enabled: false + gateway: "" + host: "" + path: "/" + port: 8080 + +# Resource limits and requests +resources: {} + # limits: + # memory: 128Mi + # cpu: 100m + # requests: + # memory: 64Mi + # cpu: 50m + +# Replica configuration (only applies when postgres is enabled) +replicas: + enabled: false + count: 1 + +# Security context for the pod +securityContext: + runAsUser: 1092 + runAsGroup: 100 + fsGroup: 100 + +# SQLite backend configuration (mutually exclusive with postgres) +sqlite: + enabled: false + dataDir: /var/lib/taskchampion-sync-server/data + existingPV: "" + emptyDir: + sizeLimit: "" + medium: "" + persistence: + enabled: true + size: 1Gi + accessMode: ReadWriteOnce + storageClass: "" + existingClaim: "" + +# PostgreSQL backend configuration (mutually exclusive with sqlite) +postgres: + enabled: false + existingSecret: "" + database: taskchampion + host: postgres + port: 5432 + username: user + password: "" From 5e40eb53dac9d34f8383867725e9633f7fdadd73 Mon Sep 17 00:00:00 2001 From: Jansen Fuller Date: Thu, 30 Apr 2026 23:00:10 -0600 Subject: [PATCH 02/19] Update maintainer --- helm/taskchampion-sync-server/Chart.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/helm/taskchampion-sync-server/Chart.yaml b/helm/taskchampion-sync-server/Chart.yaml index 1d431c1..ab6b79a 100644 --- a/helm/taskchampion-sync-server/Chart.yaml +++ b/helm/taskchampion-sync-server/Chart.yaml @@ -12,4 +12,6 @@ keywords: home: https://github.com/GothenburgBitFactory/taskchampion-sync-server sources: - https://github.com/GothenburgBitFactory/taskchampion-sync-server -maintainers: [] +maintainers: + - name: Jansen Fuller + email: jansendfuller@mailfence.com From 6a7dd21bba55b06a4c40b32a2ae4585a80a4ef32 Mon Sep 17 00:00:00 2001 From: Jansen Fuller Date: Thu, 30 Apr 2026 23:52:55 -0600 Subject: [PATCH 03/19] Updating filesystem settings to prevent issues --- helm/taskchampion-sync-server/values.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/helm/taskchampion-sync-server/values.yaml b/helm/taskchampion-sync-server/values.yaml index 46091d4..efd214f 100644 --- a/helm/taskchampion-sync-server/values.yaml +++ b/helm/taskchampion-sync-server/values.yaml @@ -53,9 +53,10 @@ replicas: count: 1 # Security context for the pod +# NOTE: runAsUser and runAsGroup are intentionally unset. +# The Docker entrypoint requires root to chown the data directory and then +# drops privileges via su-exec to the taskchampion user (uid 1092). securityContext: - runAsUser: 1092 - runAsGroup: 100 fsGroup: 100 # SQLite backend configuration (mutually exclusive with postgres) From 5981d674453bceec1c5da8cdbb4f3d352997e82f Mon Sep 17 00:00:00 2001 From: Jansen Fuller Date: Fri, 1 May 2026 09:25:06 -0600 Subject: [PATCH 04/19] CREATE_CLIENTS is now true by default --- helm/taskchampion-sync-server/values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helm/taskchampion-sync-server/values.yaml b/helm/taskchampion-sync-server/values.yaml index efd214f..789d2dd 100644 --- a/helm/taskchampion-sync-server/values.yaml +++ b/helm/taskchampion-sync-server/values.yaml @@ -14,7 +14,7 @@ clientIdSecret: "" env: RUST_LOG: info LISTEN: "0.0.0.0:8080" - CREATE_CLIENTS: "false" + CREATE_CLIENTS: "true" # Service configuration service: From af1201a6f22f876dd6503dcb57a336ee701c751e Mon Sep 17 00:00:00 2001 From: Jansen Fuller Date: Fri, 1 May 2026 09:42:22 -0600 Subject: [PATCH 05/19] Add name override options --- helm/taskchampion-sync-server/values.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/helm/taskchampion-sync-server/values.yaml b/helm/taskchampion-sync-server/values.yaml index 789d2dd..b305ba7 100644 --- a/helm/taskchampion-sync-server/values.yaml +++ b/helm/taskchampion-sync-server/values.yaml @@ -1,3 +1,8 @@ +# Override the chart name used in resource names +nameOverride: "" +# Override the full resource name (takes precedence over nameOverride) +fullnameOverride: "" + # Image configuration image: repository: ghcr.io/gothenburgbitfactory/taskchampion-sync-server From 05d8a4cd7d1911fcd963a7b8f87099960add544e Mon Sep 17 00:00:00 2001 From: Jansen Fuller Date: Fri, 1 May 2026 10:15:27 -0600 Subject: [PATCH 06/19] Validation for postges --- .../templates/_helpers.tpl | 65 +++++++++++++++++++ .../templates/deployment.yaml | 12 +--- 2 files changed, 66 insertions(+), 11 deletions(-) diff --git a/helm/taskchampion-sync-server/templates/_helpers.tpl b/helm/taskchampion-sync-server/templates/_helpers.tpl index be73dc9..b436ee4 100644 --- a/helm/taskchampion-sync-server/templates/_helpers.tpl +++ b/helm/taskchampion-sync-server/templates/_helpers.tpl @@ -39,3 +39,68 @@ app.kubernetes.io/instance: {{ .Release.Name }} {{- default "default" .Values.serviceAccount.name }} {{- end }} {{- end }} + +{{- define "taskchampion-sync-server.postgres-connection" -}} + {{- $secret := .Values.postgres.existingSecret -}} + {{- $secretData := "" -}} + {{- if $secret -}} + {{- $secretData = (lookup "v1" "Secret" .Release.Namespace $secret).data -}} + {{- end -}} + + {{- $host := "" -}} + {{- $port := "5432" -}} + {{- $username := "" -}} + {{- $password := "" -}} + {{- $database := "taskchampion" -}} + + {{- /* Get values from secret (higher priority) */ -}} + {{- if $secretData -}} + {{- if hasKey $secretData "host" -}} + {{- $host = (b64dec $secretData.host) -}} + {{- end -}} + {{- if hasKey $secretData "port" -}} + {{- $port = (b64dec $secretData.port) -}} + {{- end -}} + {{- if hasKey $secretData "username" -}} + {{- $username = (b64dec $secretData.username) -}} + {{- end -}} + {{- if hasKey $secretData "password" -}} + {{- $password = (b64dec $secretData.password) -}} + {{- end -}} + {{- if hasKey $secretData "database" -}} + {{- $database = (b64dec $secretData.database) -}} + {{- end -}} + {{- end -}} + + {{- /* Fallback to values.yaml */ -}} + {{- if eq $host "" -}} + {{- $host = .Values.postgres.host -}} + {{- end -}} + {{- if eq $username "" -}} + {{- $username = .Values.postgres.username -}} + {{- end -}} + {{- if eq $password "" -}} + {{- $password = .Values.postgres.password -}} + {{- end -}} + {{- if eq $database "" -}} + {{- $database = .Values.postgres.database -}} + {{- end -}} + + {{- /* Build URI */ -}} + {{- $uri := printf "postgresql://" -}} + {{- if ne $username "" -}} + {{- $uri = printf "%s%s" $uri $username -}} + {{- if ne $password "" -}} + {{- $uri = printf "%s:%s" $uri $password -}} + {{- end -}} + {{- $uri = printf "%s@" $uri -}} + {{- end -}} + {{- $uri = printf "%s%s" $uri $host -}} + {{- if ne $port "5432" -}} + {{- $uri = printf "%s:%s" $uri $port -}} + {{- end -}} + {{- if ne $database "taskchampion" -}} + {{- $uri = printf "%s/%s" $uri $database -}} + {{- end -}} + {{- $uri -}} +{{- end -}} diff --git a/helm/taskchampion-sync-server/templates/deployment.yaml b/helm/taskchampion-sync-server/templates/deployment.yaml index aef6395..ccc44b1 100644 --- a/helm/taskchampion-sync-server/templates/deployment.yaml +++ b/helm/taskchampion-sync-server/templates/deployment.yaml @@ -52,17 +52,7 @@ spec: {{- end }} {{- if eq .Values.postgres.enabled true }} - name: CONNECTION - valueFrom: - secretKeyRef: - name: {{ required "postgres.existingSecret is required when postgres is enabled" .Values.postgres.existingSecret }} - key: connection - optional: true - - name: PGPASSWORD - valueFrom: - secretKeyRef: - name: {{ .Values.postgres.existingSecret }} - key: password - optional: true + value: {{ include "taskchampion-sync-server.postgres-connection" . | quote }} {{- end }} ports: - name: http From ffbd3d5ae5cbe754d58cae27b06a13396ce9b500 Mon Sep 17 00:00:00 2001 From: Jansen Fuller Date: Fri, 1 May 2026 11:07:42 -0600 Subject: [PATCH 07/19] Fix plain connection string in pod env --- .../templates/_helpers.tpl | 57 +++++-------------- .../templates/deployment.yaml | 14 +++-- .../templates/secrets/postgres-secret.yaml | 11 ++++ .../templates/serviceaccount.yaml | 38 +++++++++++++ helm/taskchampion-sync-server/values.yaml | 11 +++- 5 files changed, 79 insertions(+), 52 deletions(-) create mode 100644 helm/taskchampion-sync-server/templates/secrets/postgres-secret.yaml create mode 100644 helm/taskchampion-sync-server/templates/serviceaccount.yaml diff --git a/helm/taskchampion-sync-server/templates/_helpers.tpl b/helm/taskchampion-sync-server/templates/_helpers.tpl index b436ee4..da1a417 100644 --- a/helm/taskchampion-sync-server/templates/_helpers.tpl +++ b/helm/taskchampion-sync-server/templates/_helpers.tpl @@ -41,50 +41,11 @@ app.kubernetes.io/instance: {{ .Release.Name }} {{- end }} {{- define "taskchampion-sync-server.postgres-connection" -}} - {{- $secret := .Values.postgres.existingSecret -}} - {{- $secretData := "" -}} - {{- if $secret -}} - {{- $secretData = (lookup "v1" "Secret" .Release.Namespace $secret).data -}} - {{- end -}} - - {{- $host := "" -}} - {{- $port := "5432" -}} - {{- $username := "" -}} - {{- $password := "" -}} - {{- $database := "taskchampion" -}} - - {{- /* Get values from secret (higher priority) */ -}} - {{- if $secretData -}} - {{- if hasKey $secretData "host" -}} - {{- $host = (b64dec $secretData.host) -}} - {{- end -}} - {{- if hasKey $secretData "port" -}} - {{- $port = (b64dec $secretData.port) -}} - {{- end -}} - {{- if hasKey $secretData "username" -}} - {{- $username = (b64dec $secretData.username) -}} - {{- end -}} - {{- if hasKey $secretData "password" -}} - {{- $password = (b64dec $secretData.password) -}} - {{- end -}} - {{- if hasKey $secretData "database" -}} - {{- $database = (b64dec $secretData.database) -}} - {{- end -}} - {{- end -}} - - {{- /* Fallback to values.yaml */ -}} - {{- if eq $host "" -}} - {{- $host = .Values.postgres.host -}} - {{- end -}} - {{- if eq $username "" -}} - {{- $username = .Values.postgres.username -}} - {{- end -}} - {{- if eq $password "" -}} - {{- $password = .Values.postgres.password -}} - {{- end -}} - {{- if eq $database "" -}} - {{- $database = .Values.postgres.database -}} - {{- end -}} + {{- $host := .Values.postgres.host -}} + {{- $port := .Values.postgres.port | quote -}} + {{- $username := .Values.postgres.username -}} + {{- $password := .Values.postgres.password -}} + {{- $database := .Values.postgres.database -}} {{- /* Build URI */ -}} {{- $uri := printf "postgresql://" -}} @@ -104,3 +65,11 @@ app.kubernetes.io/instance: {{ .Release.Name }} {{- end -}} {{- $uri -}} {{- end -}} + +{{- define "taskchampion-sync-server.postgres-secret-name" -}} + {{- if .Values.postgres.existingSecret -}} + {{- .Values.postgres.existingSecret -}} + {{- else -}} + {{- include "taskchampion-sync-server.fullname" . -}} + {{- end }} +{{- end -}} \ No newline at end of file diff --git a/helm/taskchampion-sync-server/templates/deployment.yaml b/helm/taskchampion-sync-server/templates/deployment.yaml index ccc44b1..f059489 100644 --- a/helm/taskchampion-sync-server/templates/deployment.yaml +++ b/helm/taskchampion-sync-server/templates/deployment.yaml @@ -18,11 +18,10 @@ spec: labels: {{- include "taskchampion-sync-server.selectorLabels" . | nindent 8 }} spec: - {{- with .Values.image.pullSecrets }} - imagePullSecrets: - {{- range . }} - - name: {{ . }} - {{- end }} + {{- if .Values.serviceAccount.create }} + serviceAccountName: {{ include "taskchampion-sync-server.fullname" . }} + {{- else if .Values.serviceAccount.name }} + serviceAccountName: {{ .Values.serviceAccount.name }} {{- end }} securityContext: {{- toYaml .Values.securityContext | nindent 8 }} @@ -52,7 +51,10 @@ spec: {{- end }} {{- if eq .Values.postgres.enabled true }} - name: CONNECTION - value: {{ include "taskchampion-sync-server.postgres-connection" . | quote }} + valueFrom: + secretKeyRef: + name: {{ include "taskchampion-sync-server.postgres-secret-name" . }} + key: connection {{- end }} ports: - name: http diff --git a/helm/taskchampion-sync-server/templates/secrets/postgres-secret.yaml b/helm/taskchampion-sync-server/templates/secrets/postgres-secret.yaml new file mode 100644 index 0000000..30fd22c --- /dev/null +++ b/helm/taskchampion-sync-server/templates/secrets/postgres-secret.yaml @@ -0,0 +1,11 @@ +{{- if and .Values.postgres.enabled (eq .Values.postgres.existingSecret "") -}} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "taskchampion-sync-server.fullname" . }} + labels: + {{- include "taskchampion-sync-server.labels" . | nindent 4 }} +type: Opaque +data: + connection: {{ include "taskchampion-sync-server.postgres-connection" . | b64enc }} +{{- end -}} \ No newline at end of file diff --git a/helm/taskchampion-sync-server/templates/serviceaccount.yaml b/helm/taskchampion-sync-server/templates/serviceaccount.yaml new file mode 100644 index 0000000..d4742a8 --- /dev/null +++ b/helm/taskchampion-sync-server/templates/serviceaccount.yaml @@ -0,0 +1,38 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "taskchampion-sync-server.fullname" . }} + labels: + {{- include "taskchampion-sync-server.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ include "taskchampion-sync-server.fullname" . }}-secret-manager + labels: + {{- include "taskchampion-sync-server.labels" . | nindent 4 }} +rules: +- apiGroups: [""] + resources: ["secrets"] + verbs: ["create", "get", "update", "patch"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ include "taskchampion-sync-server.fullname" . }}-secret-binding + labels: + {{- include "taskchampion-sync-server.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ include "taskchampion-sync-server.fullname" . }}-secret-manager +subjects: +- kind: ServiceAccount + name: {{ include "taskchampion-sync-server.fullname" . }} + namespace: {{ .Release.Namespace }} +{{- end -}} \ No newline at end of file diff --git a/helm/taskchampion-sync-server/values.yaml b/helm/taskchampion-sync-server/values.yaml index b305ba7..9ffac05 100644 --- a/helm/taskchampion-sync-server/values.yaml +++ b/helm/taskchampion-sync-server/values.yaml @@ -79,10 +79,17 @@ sqlite: storageClass: "" existingClaim: "" -# PostgreSQL backend configuration (mutually exclusive with sqlite) +# Service account configuration +serviceAccount: + create: true # Default: automatically create service account + name: "" # Optional: use existing service account + annotations: {} + +# PostgreSQL configuration postgres: enabled: false - existingSecret: "" + existingSecret: "" # If empty, auto-create secret; if provided, use existing + # Individual connection components for building connection string database: taskchampion host: postgres port: 5432 From 54c14a466cc509278cac4f9d3a4b5b3f3733101a Mon Sep 17 00:00:00 2001 From: Jansen Fuller Date: Fri, 1 May 2026 11:22:55 -0600 Subject: [PATCH 08/19] Simplifying secret management --- helm/taskchampion-sync-server/README.md | 21 +++++++++---------- .../examples/postgres-values.yaml | 1 - .../templates/_helpers.tpl | 6 +----- .../templates/secrets/postgres-secret.yaml | 6 +++--- 4 files changed, 14 insertions(+), 20 deletions(-) diff --git a/helm/taskchampion-sync-server/README.md b/helm/taskchampion-sync-server/README.md index 5631a76..4ec516a 100644 --- a/helm/taskchampion-sync-server/README.md +++ b/helm/taskchampion-sync-server/README.md @@ -43,18 +43,17 @@ Reference it via `clientIdSecret: "my-client-ids"`. ### PostgreSQL Secret -```yaml -apiVersion: v1 -kind: Secret -metadata: - name: my-postgres-creds -type: Opaque -data: - connection: - password: -``` +For PostgreSQL, the chart can automatically create a secret with the connection string, or use an existing secret. + +**Automatic Secret Creation**: +- When `postgres.existingSecret` is empty (default), the chart automatically creates a secret +- Secret is named using Helm naming convention: `release-name-taskchampion-sync-server` +- Secret contains only a `connection` key with the built connection string -Reference it via `postgres.existingSecret: "my-postgres-creds"`. +**Existing Secret Usage**: +- When `postgres.existingSecret` is provided, the chart uses that secret +- The secret **must** contain a `connection` key with the PostgreSQL connection string +- If the secret doesn't have a `connection` key, the deployment will fail with a clear error ## Configuration diff --git a/helm/taskchampion-sync-server/examples/postgres-values.yaml b/helm/taskchampion-sync-server/examples/postgres-values.yaml index 6741d71..ed2ae0b 100644 --- a/helm/taskchampion-sync-server/examples/postgres-values.yaml +++ b/helm/taskchampion-sync-server/examples/postgres-values.yaml @@ -3,7 +3,6 @@ sqlite: postgres: enabled: true - existingSecret: "taskchampion-postgres-creds" database: taskchampion host: postgres-primary username: taskchampion-user diff --git a/helm/taskchampion-sync-server/templates/_helpers.tpl b/helm/taskchampion-sync-server/templates/_helpers.tpl index da1a417..d3dbb35 100644 --- a/helm/taskchampion-sync-server/templates/_helpers.tpl +++ b/helm/taskchampion-sync-server/templates/_helpers.tpl @@ -67,9 +67,5 @@ app.kubernetes.io/instance: {{ .Release.Name }} {{- end -}} {{- define "taskchampion-sync-server.postgres-secret-name" -}} - {{- if .Values.postgres.existingSecret -}} - {{- .Values.postgres.existingSecret -}} - {{- else -}} - {{- include "taskchampion-sync-server.fullname" . -}} - {{- end }} +{{- printf "%s-connection" .Release.Name -}} {{- end -}} \ No newline at end of file diff --git a/helm/taskchampion-sync-server/templates/secrets/postgres-secret.yaml b/helm/taskchampion-sync-server/templates/secrets/postgres-secret.yaml index 30fd22c..8364a23 100644 --- a/helm/taskchampion-sync-server/templates/secrets/postgres-secret.yaml +++ b/helm/taskchampion-sync-server/templates/secrets/postgres-secret.yaml @@ -1,11 +1,11 @@ -{{- if and .Values.postgres.enabled (eq .Values.postgres.existingSecret "") -}} +{{- if .Values.postgres.enabled }} apiVersion: v1 kind: Secret metadata: - name: {{ include "taskchampion-sync-server.fullname" . }} + name: {{ include "taskchampion-sync-server.postgres-secret-name" . }} labels: {{- include "taskchampion-sync-server.labels" . | nindent 4 }} type: Opaque data: connection: {{ include "taskchampion-sync-server.postgres-connection" . | b64enc }} -{{- end -}} \ No newline at end of file +{{- end }} \ No newline at end of file From 071cef994099e6f173e23b60718955c5cbe72099 Mon Sep 17 00:00:00 2001 From: Jansen Fuller Date: Fri, 1 May 2026 11:31:26 -0600 Subject: [PATCH 09/19] Fix port quoting --- helm/taskchampion-sync-server/templates/_helpers.tpl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/helm/taskchampion-sync-server/templates/_helpers.tpl b/helm/taskchampion-sync-server/templates/_helpers.tpl index d3dbb35..9e97bf3 100644 --- a/helm/taskchampion-sync-server/templates/_helpers.tpl +++ b/helm/taskchampion-sync-server/templates/_helpers.tpl @@ -42,7 +42,7 @@ app.kubernetes.io/instance: {{ .Release.Name }} {{- define "taskchampion-sync-server.postgres-connection" -}} {{- $host := .Values.postgres.host -}} - {{- $port := .Values.postgres.port | quote -}} + {{- $port := .Values.postgres.port -}} {{- $username := .Values.postgres.username -}} {{- $password := .Values.postgres.password -}} {{- $database := .Values.postgres.database -}} @@ -57,8 +57,8 @@ app.kubernetes.io/instance: {{ .Release.Name }} {{- $uri = printf "%s@" $uri -}} {{- end -}} {{- $uri = printf "%s%s" $uri $host -}} - {{- if ne $port "5432" -}} - {{- $uri = printf "%s:%s" $uri $port -}} + {{- if ne (printf "%v" $port) "5432" -}} + {{- $uri = printf "%s:%v" $uri $port -}} {{- end -}} {{- if ne $database "taskchampion" -}} {{- $uri = printf "%s/%s" $uri $database -}} From e39c7177ee6028cdcbef06aba1d051b5d4f19d39 Mon Sep 17 00:00:00 2001 From: Jansen Fuller Date: Fri, 1 May 2026 16:40:27 -0600 Subject: [PATCH 10/19] Init container to run sql file --- .../templates/_helpers.tpl | 8 +++ .../templates/deployment.yaml | 50 +++++++++++++++++++ .../templates/secrets/postgres-secret.yaml | 5 ++ helm/taskchampion-sync-server/values.yaml | 7 +++ 4 files changed, 70 insertions(+) diff --git a/helm/taskchampion-sync-server/templates/_helpers.tpl b/helm/taskchampion-sync-server/templates/_helpers.tpl index 9e97bf3..c0f0792 100644 --- a/helm/taskchampion-sync-server/templates/_helpers.tpl +++ b/helm/taskchampion-sync-server/templates/_helpers.tpl @@ -66,6 +66,14 @@ app.kubernetes.io/instance: {{ .Release.Name }} {{- $uri -}} {{- end -}} +{{- define "taskchampion-sync-server.schema-url" -}} +{{- if .Values.postgres.initContainer.schemaUrl -}} +{{- .Values.postgres.initContainer.schemaUrl -}} +{{- else -}} +{{- printf "https://raw.githubusercontent.com/GothenburgBitFactory/taskchampion-sync-server/v%s/postgres/schema.sql" .Chart.AppVersion -}} +{{- end -}} +{{- end -}} + {{- define "taskchampion-sync-server.postgres-secret-name" -}} {{- printf "%s-connection" .Release.Name -}} {{- end -}} \ No newline at end of file diff --git a/helm/taskchampion-sync-server/templates/deployment.yaml b/helm/taskchampion-sync-server/templates/deployment.yaml index f059489..8002152 100644 --- a/helm/taskchampion-sync-server/templates/deployment.yaml +++ b/helm/taskchampion-sync-server/templates/deployment.yaml @@ -25,6 +25,56 @@ spec: {{- end }} securityContext: {{- toYaml .Values.securityContext | nindent 8 }} + {{- if and .Values.postgres.enabled .Values.postgres.initContainer.enabled }} + initContainers: + - name: postgres-init + image: "{{ .Values.postgres.initContainer.image }}" + imagePullPolicy: {{ .Values.postgres.initContainer.imagePullPolicy }} + env: + - name: PGHOST + valueFrom: + secretKeyRef: + name: {{ include "taskchampion-sync-server.postgres-secret-name" . }} + key: host + - name: PGPORT + value: "{{ .Values.postgres.port }}" + - name: PGUSER + valueFrom: + secretKeyRef: + name: {{ include "taskchampion-sync-server.postgres-secret-name" . }} + key: username + - name: PGPASSWORD + valueFrom: + secretKeyRef: + name: {{ include "taskchampion-sync-server.postgres-secret-name" . }} + key: password + - name: PGDATABASE + valueFrom: + secretKeyRef: + name: {{ include "taskchampion-sync-server.postgres-secret-name" . }} + key: database + - name: SCHEMA_URL + value: {{ include "taskchampion-sync-server.schema-url" . | quote }} + command: + - sh + - -c + - | + set -e + until pg_isready -h "$PGHOST" -p "$PGPORT" -U "$PGUSER"; do + echo 'Waiting for PostgreSQL...' + sleep 2 + done + echo "Downloading schema from ${SCHEMA_URL}..." + wget -qO /tmp/schema.sql "$SCHEMA_URL" || { + echo 'Failed to download schema - continuing with main container' + exit 0 + } + psql -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -d "$PGDATABASE" -f /tmp/schema.sql || { + echo 'Schema execution failed (SQL error) - continuing with main container' + exit 0 + } + echo 'Schema executed successfully' + {{- end }} containers: - name: taskchampion-sync-server {{- if eq .Values.postgres.enabled true }} diff --git a/helm/taskchampion-sync-server/templates/secrets/postgres-secret.yaml b/helm/taskchampion-sync-server/templates/secrets/postgres-secret.yaml index 8364a23..07b13d9 100644 --- a/helm/taskchampion-sync-server/templates/secrets/postgres-secret.yaml +++ b/helm/taskchampion-sync-server/templates/secrets/postgres-secret.yaml @@ -7,5 +7,10 @@ metadata: {{- include "taskchampion-sync-server.labels" . | nindent 4 }} type: Opaque data: + host: {{ .Values.postgres.host | b64enc }} + port: {{ .Values.postgres.port | b64enc }} + username: {{ .Values.postgres.username | b64enc }} + password: {{ .Values.postgres.password | b64enc }} + database: {{ .Values.postgres.database | b64enc }} connection: {{ include "taskchampion-sync-server.postgres-connection" . | b64enc }} {{- end }} \ No newline at end of file diff --git a/helm/taskchampion-sync-server/values.yaml b/helm/taskchampion-sync-server/values.yaml index 9ffac05..c7afab1 100644 --- a/helm/taskchampion-sync-server/values.yaml +++ b/helm/taskchampion-sync-server/values.yaml @@ -95,3 +95,10 @@ postgres: port: 5432 username: user password: "" + initContainer: + enabled: true + image: postgres:15-alpine + imagePullPolicy: IfNotPresent + # Override the schema URL. Defaults to the official schema for this chart's appVersion. + # e.g. https://raw.githubusercontent.com/GothenburgBitFactory/taskchampion-sync-server/v0.7.0/postgres/schema.sql + schemaUrl: "" From 82ea05596aa39151017ed999d43e8400d0eccb33 Mon Sep 17 00:00:00 2001 From: Jansen Fuller Date: Fri, 1 May 2026 16:45:20 -0600 Subject: [PATCH 11/19] Fix port type --- .../templates/secrets/postgres-secret.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helm/taskchampion-sync-server/templates/secrets/postgres-secret.yaml b/helm/taskchampion-sync-server/templates/secrets/postgres-secret.yaml index 07b13d9..c1a0405 100644 --- a/helm/taskchampion-sync-server/templates/secrets/postgres-secret.yaml +++ b/helm/taskchampion-sync-server/templates/secrets/postgres-secret.yaml @@ -8,7 +8,7 @@ metadata: type: Opaque data: host: {{ .Values.postgres.host | b64enc }} - port: {{ .Values.postgres.port | b64enc }} + port: {{ .Values.postgres.port | toString | b64enc }} username: {{ .Values.postgres.username | b64enc }} password: {{ .Values.postgres.password | b64enc }} database: {{ .Values.postgres.database | b64enc }} From 24e61971b9e7c3988c0c9d1aa54283afb99565ef Mon Sep 17 00:00:00 2001 From: Jansen Fuller Date: Sat, 2 May 2026 15:42:41 -0600 Subject: [PATCH 12/19] Fix secret management --- .../templates/_helpers.tpl | 4 +++ .../templates/deployment.yaml | 27 ++++--------------- .../templates/secrets/postgres-secret.yaml | 5 ---- 3 files changed, 9 insertions(+), 27 deletions(-) diff --git a/helm/taskchampion-sync-server/templates/_helpers.tpl b/helm/taskchampion-sync-server/templates/_helpers.tpl index c0f0792..d85c1ec 100644 --- a/helm/taskchampion-sync-server/templates/_helpers.tpl +++ b/helm/taskchampion-sync-server/templates/_helpers.tpl @@ -75,5 +75,9 @@ app.kubernetes.io/instance: {{ .Release.Name }} {{- end -}} {{- define "taskchampion-sync-server.postgres-secret-name" -}} +{{- if .Values.postgres.existingSecret -}} +{{- .Values.postgres.existingSecret -}} +{{- else -}} {{- printf "%s-connection" .Release.Name -}} +{{- end -}} {{- end -}} \ No newline at end of file diff --git a/helm/taskchampion-sync-server/templates/deployment.yaml b/helm/taskchampion-sync-server/templates/deployment.yaml index 8002152..7daadef 100644 --- a/helm/taskchampion-sync-server/templates/deployment.yaml +++ b/helm/taskchampion-sync-server/templates/deployment.yaml @@ -31,28 +31,11 @@ spec: image: "{{ .Values.postgres.initContainer.image }}" imagePullPolicy: {{ .Values.postgres.initContainer.imagePullPolicy }} env: - - name: PGHOST + - name: PGURI valueFrom: secretKeyRef: - name: {{ include "taskchampion-sync-server.postgres-secret-name" . }} - key: host - - name: PGPORT - value: "{{ .Values.postgres.port }}" - - name: PGUSER - valueFrom: - secretKeyRef: - name: {{ include "taskchampion-sync-server.postgres-secret-name" . }} - key: username - - name: PGPASSWORD - valueFrom: - secretKeyRef: - name: {{ include "taskchampion-sync-server.postgres-secret-name" . }} - key: password - - name: PGDATABASE - valueFrom: - secretKeyRef: - name: {{ include "taskchampion-sync-server.postgres-secret-name" . }} - key: database + name: {{ printf "%s-connection" .Release.Name }} + key: connection - name: SCHEMA_URL value: {{ include "taskchampion-sync-server.schema-url" . | quote }} command: @@ -60,7 +43,7 @@ spec: - -c - | set -e - until pg_isready -h "$PGHOST" -p "$PGPORT" -U "$PGUSER"; do + until pg_isready -d "$PGURI"; do echo 'Waiting for PostgreSQL...' sleep 2 done @@ -69,7 +52,7 @@ spec: echo 'Failed to download schema - continuing with main container' exit 0 } - psql -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -d "$PGDATABASE" -f /tmp/schema.sql || { + psql "$PGURI" -f /tmp/schema.sql || { echo 'Schema execution failed (SQL error) - continuing with main container' exit 0 } diff --git a/helm/taskchampion-sync-server/templates/secrets/postgres-secret.yaml b/helm/taskchampion-sync-server/templates/secrets/postgres-secret.yaml index c1a0405..8364a23 100644 --- a/helm/taskchampion-sync-server/templates/secrets/postgres-secret.yaml +++ b/helm/taskchampion-sync-server/templates/secrets/postgres-secret.yaml @@ -7,10 +7,5 @@ metadata: {{- include "taskchampion-sync-server.labels" . | nindent 4 }} type: Opaque data: - host: {{ .Values.postgres.host | b64enc }} - port: {{ .Values.postgres.port | toString | b64enc }} - username: {{ .Values.postgres.username | b64enc }} - password: {{ .Values.postgres.password | b64enc }} - database: {{ .Values.postgres.database | b64enc }} connection: {{ include "taskchampion-sync-server.postgres-connection" . | b64enc }} {{- end }} \ No newline at end of file From d765ac6ed078fc9612ba509f7b8dff87ade00314 Mon Sep 17 00:00:00 2001 From: Jansen Fuller Date: Sat, 2 May 2026 16:14:40 -0600 Subject: [PATCH 13/19] Working now --- .../templates/_helpers.tpl | 36 ++++++++++++++----- .../templates/deployment.yaml | 2 +- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/helm/taskchampion-sync-server/templates/_helpers.tpl b/helm/taskchampion-sync-server/templates/_helpers.tpl index d85c1ec..5473f22 100644 --- a/helm/taskchampion-sync-server/templates/_helpers.tpl +++ b/helm/taskchampion-sync-server/templates/_helpers.tpl @@ -42,13 +42,35 @@ app.kubernetes.io/instance: {{ .Release.Name }} {{- define "taskchampion-sync-server.postgres-connection" -}} {{- $host := .Values.postgres.host -}} - {{- $port := .Values.postgres.port -}} + {{- $port := .Values.postgres.port | toString -}} {{- $username := .Values.postgres.username -}} {{- $password := .Values.postgres.password -}} {{- $database := .Values.postgres.database -}} - + + {{- /* Override individual fields from existingSecret where present */ -}} + {{- if .Values.postgres.existingSecret -}} + {{- $secret := lookup "v1" "Secret" .Release.Namespace .Values.postgres.existingSecret -}} + {{- if $secret -}} + {{- if index $secret.data "host" -}} + {{- $host = index $secret.data "host" | b64dec -}} + {{- end -}} + {{- if index $secret.data "port" -}} + {{- $port = index $secret.data "port" | b64dec -}} + {{- end -}} + {{- if index $secret.data "username" -}} + {{- $username = index $secret.data "username" | b64dec -}} + {{- end -}} + {{- if index $secret.data "password" -}} + {{- $password = index $secret.data "password" | b64dec -}} + {{- end -}} + {{- if index $secret.data "database" -}} + {{- $database = index $secret.data "database" | b64dec -}} + {{- end -}} + {{- end -}} + {{- end -}} + {{- /* Build URI */ -}} - {{- $uri := printf "postgresql://" -}} + {{- $uri := "postgresql://" -}} {{- if ne $username "" -}} {{- $uri = printf "%s%s" $uri $username -}} {{- if ne $password "" -}} @@ -57,8 +79,8 @@ app.kubernetes.io/instance: {{ .Release.Name }} {{- $uri = printf "%s@" $uri -}} {{- end -}} {{- $uri = printf "%s%s" $uri $host -}} - {{- if ne (printf "%v" $port) "5432" -}} - {{- $uri = printf "%s:%v" $uri $port -}} + {{- if ne $port "5432" -}} + {{- $uri = printf "%s:%s" $uri $port -}} {{- end -}} {{- if ne $database "taskchampion" -}} {{- $uri = printf "%s/%s" $uri $database -}} @@ -75,9 +97,5 @@ app.kubernetes.io/instance: {{ .Release.Name }} {{- end -}} {{- define "taskchampion-sync-server.postgres-secret-name" -}} -{{- if .Values.postgres.existingSecret -}} -{{- .Values.postgres.existingSecret -}} -{{- else -}} {{- printf "%s-connection" .Release.Name -}} -{{- end -}} {{- end -}} \ No newline at end of file diff --git a/helm/taskchampion-sync-server/templates/deployment.yaml b/helm/taskchampion-sync-server/templates/deployment.yaml index 7daadef..2f4d1b2 100644 --- a/helm/taskchampion-sync-server/templates/deployment.yaml +++ b/helm/taskchampion-sync-server/templates/deployment.yaml @@ -34,7 +34,7 @@ spec: - name: PGURI valueFrom: secretKeyRef: - name: {{ printf "%s-connection" .Release.Name }} + name: {{ include "taskchampion-sync-server.postgres-secret-name" . }} key: connection - name: SCHEMA_URL value: {{ include "taskchampion-sync-server.schema-url" . | quote }} From 2429d93ce8f72298580d973b736d24032b9fbf36 Mon Sep 17 00:00:00 2001 From: Jansen Fuller Date: Sat, 2 May 2026 23:34:13 -0600 Subject: [PATCH 14/19] Fixing Postgres connection issue --- helm/taskchampion-sync-server/templates/_helpers.tpl | 5 +++-- helm/taskchampion-sync-server/values.yaml | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/helm/taskchampion-sync-server/templates/_helpers.tpl b/helm/taskchampion-sync-server/templates/_helpers.tpl index 5473f22..1b04f91 100644 --- a/helm/taskchampion-sync-server/templates/_helpers.tpl +++ b/helm/taskchampion-sync-server/templates/_helpers.tpl @@ -82,8 +82,9 @@ app.kubernetes.io/instance: {{ .Release.Name }} {{- if ne $port "5432" -}} {{- $uri = printf "%s:%s" $uri $port -}} {{- end -}} - {{- if ne $database "taskchampion" -}} - {{- $uri = printf "%s/%s" $uri $database -}} + {{- $uri = printf "%s/%s" $uri $database -}} + {{- if .Values.postgres.sslMode -}} + {{- $uri = printf "%s?sslmode=%s" $uri .Values.postgres.sslMode -}} {{- end -}} {{- $uri -}} {{- end -}} diff --git a/helm/taskchampion-sync-server/values.yaml b/helm/taskchampion-sync-server/values.yaml index c7afab1..cae77a8 100644 --- a/helm/taskchampion-sync-server/values.yaml +++ b/helm/taskchampion-sync-server/values.yaml @@ -95,6 +95,10 @@ postgres: port: 5432 username: user password: "" + # SSL mode for the PostgreSQL connection. + # Use 'disable' for internal cluster connections (no TLS). + # Options: disable, allow, prefer, require, verify-ca, verify-full + sslMode: disable initContainer: enabled: true image: postgres:15-alpine From 3a8ec365b11bd84f9642992d0834a8088d59966e Mon Sep 17 00:00:00 2001 From: Jansen Fuller Date: Sun, 3 May 2026 10:17:54 -0600 Subject: [PATCH 15/19] Upgrade postgres init container --- helm/taskchampion-sync-server/values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helm/taskchampion-sync-server/values.yaml b/helm/taskchampion-sync-server/values.yaml index cae77a8..103c45e 100644 --- a/helm/taskchampion-sync-server/values.yaml +++ b/helm/taskchampion-sync-server/values.yaml @@ -101,7 +101,7 @@ postgres: sslMode: disable initContainer: enabled: true - image: postgres:15-alpine + image: postgres:17-alpine imagePullPolicy: IfNotPresent # Override the schema URL. Defaults to the official schema for this chart's appVersion. # e.g. https://raw.githubusercontent.com/GothenburgBitFactory/taskchampion-sync-server/v0.7.0/postgres/schema.sql From 9098be67649812dcb28672d128b480fe0ed27dbf Mon Sep 17 00:00:00 2001 From: Jansen Fuller Date: Sun, 3 May 2026 13:14:10 -0600 Subject: [PATCH 16/19] Auto add UUIDs when starting postgres --- .../templates/deployment.yaml | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/helm/taskchampion-sync-server/templates/deployment.yaml b/helm/taskchampion-sync-server/templates/deployment.yaml index 2f4d1b2..ccc3c8f 100644 --- a/helm/taskchampion-sync-server/templates/deployment.yaml +++ b/helm/taskchampion-sync-server/templates/deployment.yaml @@ -38,6 +38,13 @@ spec: key: connection - name: SCHEMA_URL value: {{ include "taskchampion-sync-server.schema-url" . | quote }} + {{- if .Values.clientIdSecret }} + - name: CLIENT_IDS + valueFrom: + secretKeyRef: + name: {{ .Values.clientIdSecret }} + key: client-ids + {{- end }} command: - sh - -c @@ -57,6 +64,19 @@ spec: exit 0 } echo 'Schema executed successfully' + if [ -n "$CLIENT_IDS" ]; then + echo "Inserting client IDs..." + IFS=',' + for client_id in $CLIENT_IDS; do + client_id=$(echo "$client_id" | tr -d ' ') + [ -z "$client_id" ] && continue + psql "$PGURI" -c "INSERT INTO clients (client_id) VALUES ('$client_id') ON CONFLICT (client_id) DO NOTHING;" || { + echo "Failed to insert client $client_id - continuing" + } + done + unset IFS + echo "Client ID insertion complete" + fi {{- end }} containers: - name: taskchampion-sync-server From 934757343c42b93eb118ed6ef819e2d9bfcc4c40 Mon Sep 17 00:00:00 2001 From: Jansen Fuller Date: Thu, 21 May 2026 15:22:25 -0600 Subject: [PATCH 17/19] feat: Use standard value names for adding HTTPReoute --- .../templates/httproute.yaml | 42 +++++++++++++++++-- helm/taskchampion-sync-server/values.yaml | 28 +++++++++++++ 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/helm/taskchampion-sync-server/templates/httproute.yaml b/helm/taskchampion-sync-server/templates/httproute.yaml index 8ca5245..a3b30c7 100644 --- a/helm/taskchampion-sync-server/templates/httproute.yaml +++ b/helm/taskchampion-sync-server/templates/httproute.yaml @@ -5,19 +5,53 @@ metadata: name: {{ include "taskchampion-sync-server.fullname" . }} labels: {{- include "taskchampion-sync-server.labels" . | nindent 4 }} + {{- with .Values.httpRoute.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} spec: parentRefs: + {{- if .Values.httpRoute.parentRefs }} + {{- range .Values.httpRoute.parentRefs }} + - name: {{ .name }} + {{- if .namespace }} + namespace: {{ .namespace }} + {{- end }} + {{- if .sectionName }} + sectionName: {{ .sectionName }} + {{- end }} + {{- end }} + {{- else if .Values.httpRoute.gateway }} - name: {{ .Values.httpRoute.gateway }} - {{- if .Values.httpRoute.host }} + {{- end }} + {{- $hostnames := .Values.httpRoute.hostnames }} + {{- if and (not $hostnames) .Values.httpRoute.host }} + {{- $hostnames = list .Values.httpRoute.host }} + {{- end }} + {{- if $hostnames }} hostnames: - - {{ .Values.httpRoute.host }} + {{- range $hostnames }} + - {{ . | quote }} + {{- end }} {{- end }} rules: + {{- if .Values.httpRoute.rules }} + {{- range .Values.httpRoute.rules }} + - matches: + - path: + type: {{ .path.type | default "PathPrefix" }} + value: {{ .path.value | default "/" }} + backendRefs: + - name: {{ include "taskchampion-sync-server.fullname" $ }} + port: {{ .backendPort | default 8080 }} + {{- end }} + {{- else }} - matches: - path: type: PathPrefix - value: {{ .Values.httpRoute.path }} + value: {{ .Values.httpRoute.path | default "/" }} backendRefs: - name: {{ include "taskchampion-sync-server.fullname" . }} - port: {{ .Values.httpRoute.port }} + port: {{ .Values.httpRoute.port | default 8080 }} + {{- end }} {{- end }} diff --git a/helm/taskchampion-sync-server/values.yaml b/helm/taskchampion-sync-server/values.yaml index 103c45e..1c07ab3 100644 --- a/helm/taskchampion-sync-server/values.yaml +++ b/helm/taskchampion-sync-server/values.yaml @@ -38,8 +38,36 @@ ingress: # HTTPRoute configuration (Kubernetes Gateway API) httpRoute: enabled: false + annotations: {} + + # List of parent gateway references. + # name is required; namespace and sectionName are optional. + parentRefs: [] + # parentRefs: + # - name: my-gateway + # namespace: gateway-system # optional — cross-namespace gateway reference + # sectionName: https # optional — targets a specific listener on the gateway + + # List of hostnames the route applies to. + hostnames: [] + # hostnames: + # - tasks.example.com + # - tasks.internal.example.com + + # List of routing rules. Each rule matches a path and forwards to this chart's Service. + # When empty, falls back to the deprecated path/port fields below. + rules: [] + # rules: + # - path: + # type: PathPrefix # PathPrefix or Exact + # value: / + # backendPort: 8080 + + # Deprecated: use parentRefs instead gateway: "" + # Deprecated: use hostnames instead host: "" + # Deprecated: use rules instead path: "/" port: 8080 From bc984296707c4c1c6ca2dcbbdd401ffdec8a65d1 Mon Sep 17 00:00:00 2001 From: Jansen Fuller Date: Fri, 29 May 2026 10:57:21 -0600 Subject: [PATCH 18/19] fix: Remove merge issues --- helm/taskchampion-sync-server/values.yaml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/helm/taskchampion-sync-server/values.yaml b/helm/taskchampion-sync-server/values.yaml index 473f274..1c07ab3 100644 --- a/helm/taskchampion-sync-server/values.yaml +++ b/helm/taskchampion-sync-server/values.yaml @@ -38,7 +38,6 @@ ingress: # HTTPRoute configuration (Kubernetes Gateway API) httpRoute: enabled: false -<<<<<<< HEAD annotations: {} # List of parent gateway references. @@ -69,10 +68,6 @@ httpRoute: # Deprecated: use hostnames instead host: "" # Deprecated: use rules instead -======= - gateway: "" - host: "" ->>>>>>> main path: "/" port: 8080 From 15a4434a63e80401050cf5c8922581c722fad340 Mon Sep 17 00:00:00 2001 From: Jansen Fuller Date: Fri, 29 May 2026 10:57:44 -0600 Subject: [PATCH 19/19] chore: bump version --- helm/taskchampion-sync-server/Chart.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helm/taskchampion-sync-server/Chart.yaml b/helm/taskchampion-sync-server/Chart.yaml index ab6b79a..6f46da7 100644 --- a/helm/taskchampion-sync-server/Chart.yaml +++ b/helm/taskchampion-sync-server/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: taskchampion-sync-server description: A Helm chart for deploying TaskChampion Sync Server on Kubernetes type: application -version: 0.1.0 +version: 0.1.1 appVersion: "0.7.0" keywords: - taskchampion