Adding Helm chart

This commit is contained in:
Jansen Fuller
2026-04-30 22:54:31 -06:00
committed by Dustin J. Mitchell
parent dd1b87dad5
commit b98c87798c
13 changed files with 454 additions and 0 deletions

View File

@ -0,0 +1,5 @@
# Patterns to ignore when building the Helm chart
.git
.gitignore
*.md
examples/

View File

@ -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: []

View File

@ -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: <base64-encoded comma-separated UUIDs>
```
Reference it via `clientIdSecret: "my-client-ids"`.
### PostgreSQL Secret
```yaml
apiVersion: v1
kind: Secret
metadata:
name: my-postgres-creds
type: Opaque
data:
connection: <base64-encoded LibPQ connection URI>
password: <base64-encoded password>
```
Reference it via `postgres.existingSecret: "my-postgres-creds"`.
## Configuration
See [values.yaml](values.yaml) for all configurable options.

View File

@ -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

View File

@ -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

View File

@ -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 }}

View File

@ -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 }}

View File

@ -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 }}

View File

@ -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 }}

View File

@ -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 }}

View File

@ -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 }}

View File

@ -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 -}}

View File

@ -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: ""