2 Commits

Author SHA1 Message Date
3f78f2d4c5 Readding ServiceAccount (#212)
* Readding ServiceAccount

* Bumping version
2026-06-10 03:32:09 +00:00
b5eb61a43d Adding health probes, resource requests and fixing container permissions (#210)
* Adding health probes and fixing container permissions

* Bump version

* Cleanup

* No ServiceAccount anymore

* No ServiceAccount anymore

* Update pull secrets

* Fixing yaml issue

* Fixed resource requests and limits

* Bumping version
2026-06-09 23:41:50 +00:00
12 changed files with 192 additions and 81 deletions

View File

@ -49,7 +49,6 @@ jobs:
grep -q 'kind: Deployment' /tmp/helm-sqlite.yaml grep -q 'kind: Deployment' /tmp/helm-sqlite.yaml
grep -q 'kind: Service' /tmp/helm-sqlite.yaml grep -q 'kind: Service' /tmp/helm-sqlite.yaml
grep -q 'kind: Ingress' /tmp/helm-sqlite.yaml grep -q 'kind: Ingress' /tmp/helm-sqlite.yaml
grep -q 'kind: ServiceAccount' /tmp/helm-sqlite.yaml
# Verify SQLite-specific rendering # Verify SQLite-specific rendering
grep -q 'emptyDir' /tmp/helm-sqlite.yaml grep -q 'emptyDir' /tmp/helm-sqlite.yaml
@ -82,7 +81,6 @@ jobs:
grep -q 'kind: Deployment' /tmp/helm-postgres.yaml grep -q 'kind: Deployment' /tmp/helm-postgres.yaml
grep -q 'kind: Service' /tmp/helm-postgres.yaml grep -q 'kind: Service' /tmp/helm-postgres.yaml
grep -q 'kind: Secret' /tmp/helm-postgres.yaml grep -q 'kind: Secret' /tmp/helm-postgres.yaml
grep -q 'kind: ServiceAccount' /tmp/helm-postgres.yaml
# Verify PostgreSQL-specific rendering # Verify PostgreSQL-specific rendering
grep -q 'kind: HTTPRoute' /tmp/helm-postgres.yaml grep -q 'kind: HTTPRoute' /tmp/helm-postgres.yaml

View File

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

View File

@ -2,7 +2,7 @@ apiVersion: v2
name: taskchampion-sync-server name: taskchampion-sync-server
description: A Helm chart for deploying TaskChampion Sync Server on Kubernetes description: A Helm chart for deploying TaskChampion Sync Server on Kubernetes
type: application type: application
version: 0.1.2 version: 0.2.1
appVersion: "0.7.0" appVersion: "0.7.0"
keywords: keywords:
- taskchampion - taskchampion

View File

@ -10,9 +10,12 @@ postgres:
clientIdSecret: "taskchampion-client-ids" clientIdSecret: "taskchampion-client-ids"
env: env:
RUST_LOG: debug - name: RUST_LOG
LISTEN: "0.0.0.0:8080" value: debug
CREATE_CLIENTS: "false" - name: LISTEN
value: "0.0.0.0:8080"
- name: CREATE_CLIENTS
value: "false"
replicas: replicas:
enabled: true enabled: true
@ -20,7 +23,7 @@ replicas:
image: image:
pullSecrets: pullSecrets:
- my-registry-secret - name: my-registry-secret
httpRoute: httpRoute:
enabled: true enabled: true

View File

@ -12,9 +12,12 @@ postgres:
clientIdSecret: "taskchampion-client-ids" clientIdSecret: "taskchampion-client-ids"
env: env:
RUST_LOG: info - name: RUST_LOG
LISTEN: "0.0.0.0:8080" value: info
CREATE_CLIENTS: "false" - name: LISTEN
value: "0.0.0.0:8080"
- name: CREATE_CLIENTS
value: "false"
ingress: ingress:
enabled: true enabled: true

View File

@ -0,0 +1,46 @@
Thank you for installing {{ .Chart.Name }} — version {{ .Chart.Version }} (app: {{ .Chart.AppVersion }}).
## Storage Backend
{{- if eq .Values.sqlite.enabled true }}
**SQLite** — Data is stored at `{{ .Values.sqlite.dataDir }}`.
{{- if .Values.sqlite.persistence.enabled }}
PersistentVolumeClaim: `{{ include "taskchampion-sync-server.fullname" . }}-pvc`
{{- else if .Values.sqlite.existingPV }}
Using existing PVC: `{{ .Values.sqlite.existingPV }}`
{{- else }}
Using an emptyDir volume (ephemeral — data is lost on pod restart).
Set `sqlite.persistence.enabled=true` for persistent storage.
{{- end }}
{{- end }}
{{- if eq .Values.postgres.enabled true }}
**PostgreSQL** — Connection URI stored in Secret `{{ include "taskchampion-sync-server.postgres-secret-name" . }}`.
Host: {{ .Values.postgres.host }}:{{ .Values.postgres.port }}
Database: {{ .Values.postgres.database }}
{{- end }}
## Verify the Deployment
kubectl get pods -l {{ include "taskchampion-sync-server.selectorLabels" . | replace ": " "=" | replace "\n" "," | trimSuffix "," }}
## Check Pod Logs
kubectl logs -l {{ include "taskchampion-sync-server.selectorLabels" . | replace ": " "=" | replace "\n" "," | trimSuffix "," }}
## Test the API
kubectl port-forward svc/{{ include "taskchampion-sync-server.fullname" . }} {{ .Values.service.port }}:{{ .Values.service.port }}
curl http://localhost:{{ .Values.service.port }}/
## View Connection Secret (PostgreSQL only)
{{- if eq .Values.postgres.enabled true }}
kubectl get secret {{ include "taskchampion-sync-server.postgres-secret-name" . }} -o jsonpath="{.data.connection}" | base64 -d
{{- end }}
## Configuration
For full configuration options, see:
https://github.com/GothenburgBitFactory/taskchampion-sync-server

View File

@ -23,6 +23,7 @@ taskchampion-sync-server helpers
helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 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/name: {{ include "taskchampion-sync-server.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: server
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service }} app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }} {{- end }}
@ -32,13 +33,6 @@ app.kubernetes.io/name: {{ include "taskchampion-sync-server.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }} {{- 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 }}
{{- define "taskchampion-sync-server.postgres-connection" -}} {{- define "taskchampion-sync-server.postgres-connection" -}}
{{- $host := .Values.postgres.host -}} {{- $host := .Values.postgres.host -}}
@ -100,3 +94,7 @@ app.kubernetes.io/instance: {{ .Release.Name }}
{{- define "taskchampion-sync-server.postgres-secret-name" -}} {{- define "taskchampion-sync-server.postgres-secret-name" -}}
{{- printf "%s-connection" .Release.Name -}} {{- printf "%s-connection" .Release.Name -}}
{{- end -}} {{- end -}}
{{- define "taskchampion-sync-server.serviceAccountName" -}}
{{- default (include "taskchampion-sync-server.fullname" .) .Values.serviceAccount.name -}}
{{- end -}}

View File

@ -18,10 +18,13 @@ spec:
labels: labels:
{{- include "taskchampion-sync-server.selectorLabels" . | nindent 8 }} {{- include "taskchampion-sync-server.selectorLabels" . | nindent 8 }}
spec: spec:
{{- with .Values.image.pullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- if .Values.serviceAccount.create }} {{- if .Values.serviceAccount.create }}
serviceAccountName: {{ include "taskchampion-sync-server.fullname" . }} serviceAccountName: {{ include "taskchampion-sync-server.serviceAccountName" . }}
{{- else if .Values.serviceAccount.name }}
serviceAccountName: {{ .Values.serviceAccount.name }}
{{- end }} {{- end }}
securityContext: securityContext:
{{- toYaml .Values.securityContext | nindent 8 }} {{- toYaml .Values.securityContext | nindent 8 }}
@ -32,6 +35,8 @@ spec:
imagePullPolicy: {{ .Values.postgres.initContainer.imagePullPolicy }} imagePullPolicy: {{ .Values.postgres.initContainer.imagePullPolicy }}
securityContext: securityContext:
allowPrivilegeEscalation: false allowPrivilegeEscalation: false
runAsNonRoot: true
runAsUser: 999
env: env:
- name: PGURI - name: PGURI
valueFrom: valueFrom:
@ -90,11 +95,18 @@ spec:
imagePullPolicy: {{ .Values.image.pullPolicy }} imagePullPolicy: {{ .Values.image.pullPolicy }}
securityContext: securityContext:
allowPrivilegeEscalation: false allowPrivilegeEscalation: false
env: runAsNonRoot: true
{{- range $name, $value := .Values.env }} runAsUser: 1092
- name: {{ $name }} readOnlyRootFilesystem: true
value: {{ $value | quote }} {{- if eq .Values.postgres.enabled true }}
command:
- /bin/taskchampion-sync-server-postgres
{{- else }}
command:
- /bin/taskchampion-sync-server
{{- end }} {{- end }}
env:
{{- toYaml .Values.env | nindent 12 }}
{{- if .Values.clientIdSecret }} {{- if .Values.clientIdSecret }}
- name: CLIENT_ID - name: CLIENT_ID
valueFrom: valueFrom:
@ -117,9 +129,24 @@ spec:
- name: http - name: http
containerPort: {{ .Values.service.targetPort }} containerPort: {{ .Values.service.targetPort }}
protocol: TCP protocol: TCP
{{- with .Values.resources }} livenessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 5
periodSeconds: 10
{{- if eq .Values.postgres.enabled true }}
resources: resources:
{{- toYaml . | nindent 12 }} {{- toYaml .Values.postgres.resources | nindent 12 }}
{{- else }}
resources:
{{- toYaml .Values.sqlite.resources | nindent 12 }}
{{- end }} {{- end }}
{{- if eq .Values.sqlite.enabled true }} {{- if eq .Values.sqlite.enabled true }}
volumeMounts: volumeMounts:

View File

@ -18,7 +18,10 @@ spec:
{{- toYaml .Values.ingress.tls | nindent 4 }} {{- toYaml .Values.ingress.tls | nindent 4 }}
{{- end }} {{- end }}
rules: rules:
{{- $svcName := include "taskchampion-sync-server.fullname" . -}}
{{- $svcPort := $.Values.service.port -}}
{{- range .Values.ingress.hosts }} {{- range .Values.ingress.hosts }}
{{- if kindIs "string" . }}
- host: {{ . | quote }} - host: {{ . | quote }}
http: http:
paths: paths:
@ -26,8 +29,30 @@ spec:
pathType: Prefix pathType: Prefix
backend: backend:
service: service:
name: {{ include "taskchampion-sync-server.fullname" $ }} name: {{ $svcName }}
port: port:
number: {{ $.Values.service.port }} number: {{ $svcPort }}
{{- else }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path | default "/" | quote }}
pathType: {{ .pathType | default "Prefix" }}
backend:
service:
name: {{ $svcName }}
port:
number: {{ $svcPort }}
{{- else }}
- path: /
pathType: Prefix
backend:
service:
name: {{ $svcName }}
port:
number: {{ $svcPort }}
{{- end }}
{{- end }}
{{- end }} {{- end }}
{{- end }} {{- end }}

View File

@ -2,37 +2,8 @@
apiVersion: v1 apiVersion: v1
kind: ServiceAccount kind: ServiceAccount
metadata: metadata:
name: {{ include "taskchampion-sync-server.fullname" . }} name: {{ include "taskchampion-sync-server.serviceAccountName" . }}
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 }} namespace: {{ .Release.Namespace }}
{{- end -}} labels:
{{- include "taskchampion-sync-server.labels" . | nindent 4 }}
{{- end }}

View File

@ -1,3 +1,10 @@
{{- /* Validate imagePullSecrets entries are objects (not plain strings) */ -}}
{{- range $i, $secret := .Values.image.pullSecrets -}}
{{- if not (kindIs "map" $secret) -}}
{{- fail (printf "ERROR: image.pullSecrets[%d] is a string (%q). Each entry must be an object with a 'name' key, e.g.\n image:\n pullSecrets:\n - name: %s" $i $secret $secret) -}}
{{- end -}}
{{- end -}}
{{- if and (eq .Values.sqlite.enabled false) (eq .Values.postgres.enabled false) -}} {{- 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." -}} {{- fail "ERROR: Exactly one storage backend must be enabled. Either sqlite.enabled or postgres.enabled must be true." -}}
{{- end -}} {{- end -}}

View File

@ -9,6 +9,9 @@ image:
tag: "0.7.0" tag: "0.7.0"
pullPolicy: IfNotPresent pullPolicy: IfNotPresent
pullSecrets: [] pullSecrets: []
# pullSecrets expects a list of objects with a "name" key:
# pullSecrets:
# - name: my-registry-cred
# Existing secret containing client IDs (comma-separated UUIDs) # Existing secret containing client IDs (comma-separated UUIDs)
# Expected key: client-ids # Expected key: client-ids
@ -16,10 +19,19 @@ clientIdSecret: ""
# Environment variables passed directly to the container # Environment variables passed directly to the container
# NOTE: DATA_DIR and CONNECTION are set automatically based on backend # NOTE: DATA_DIR and CONNECTION are set automatically based on backend
# To add secret/configMap references, use valueFrom:
# - name: MY_SECRET_VAR
# valueFrom:
# secretKeyRef:
# name: my-secret
# key: my-key
env: env:
RUST_LOG: info - name: RUST_LOG
LISTEN: "0.0.0.0:8080" value: info
CREATE_CLIENTS: "true" - name: LISTEN
value: "0.0.0.0:8080"
- name: CREATE_CLIENTS
value: "true"
# Service configuration # Service configuration
service: service:
@ -28,6 +40,14 @@ service:
targetPort: 8080 targetPort: 8080
# Ingress configuration # Ingress configuration
# Each host can be a string (simple) or a map with path rules:
# hosts:
# - host: app.example.com
# paths:
# - path: /
# pathType: Prefix
# - path: /api
# pathType: Exact
ingress: ingress:
enabled: false enabled: false
className: "" className: ""
@ -71,20 +91,21 @@ httpRoute:
path: "/" path: "/"
port: 8080 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) # Replica configuration (only applies when postgres is enabled)
replicas: replicas:
enabled: false enabled: false
count: 1 count: 1
# ServiceAccount configuration
# The app does not access the Kubernetes API, so no RBAC permissions are needed.
# A dedicated ServiceAccount is created to give the pod a stable identity
# for network policies, pod security, or future RBAC.
serviceAccount:
# create specifies whether a ServiceAccount should be created
create: true
# name sets the ServiceAccount name
name: taskchampion-sync-server
# Security context for the pod # Security context for the pod
# NOTE: runAsUser and runAsGroup are intentionally unset. # NOTE: runAsUser and runAsGroup are intentionally unset.
# The Docker entrypoint requires root to chown the data directory and then # The Docker entrypoint requires root to chown the data directory and then
@ -96,6 +117,15 @@ securityContext:
sqlite: sqlite:
enabled: false enabled: false
dataDir: /var/lib/taskchampion-sync-server/data dataDir: /var/lib/taskchampion-sync-server/data
# Resource limits and requests
resources:
limits:
memory: 25Mi
cpu: 100m
requests:
# 5Mi is sufficient for the SQLite image
memory: 5Mi
cpu: 10m
existingPV: "" existingPV: ""
emptyDir: emptyDir:
sizeLimit: "" sizeLimit: ""
@ -107,12 +137,6 @@ sqlite:
storageClass: "" storageClass: ""
existingClaim: "" existingClaim: ""
# Service account configuration
serviceAccount:
create: true # Default: automatically create service account
name: "" # Optional: use existing service account
annotations: {}
# PostgreSQL configuration # PostgreSQL configuration
postgres: postgres:
enabled: false enabled: false
@ -134,3 +158,12 @@ postgres:
# Override the schema URL. Defaults to the official schema for this chart's appVersion. # 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 # e.g. https://raw.githubusercontent.com/GothenburgBitFactory/taskchampion-sync-server/v0.7.0/postgres/schema.sql
schemaUrl: "" schemaUrl: ""
# Resource limits and requests
resources:
limits:
memory: 100Mi
cpu: 100m
requests:
# 20Mi is the minimum for the Postgres image
memory: 20Mi
cpu: 10m