From e0c2043b08da6c70ae04577a6f19d8ece92057b8 Mon Sep 17 00:00:00 2001 From: sHa Date: Sun, 19 Apr 2026 03:11:23 +0300 Subject: [PATCH] feat: Add Pocket ID Helm chart with deployment, service, ingress, and backup configurations --- pocket-id/Chart.yaml | 9 +++ pocket-id/templates/_helpers.tpl | 8 ++ pocket-id/templates/backup-db-cronjob.yaml | 60 +++++++++++++++ pocket-id/templates/backup-pvc-cronjob.yaml | 59 +++++++++++++++ pocket-id/templates/db-init-job.yaml | 71 ++++++++++++++++++ pocket-id/templates/deployment.yaml | 71 ++++++++++++++++++ pocket-id/templates/ingress.yaml | 27 +++++++ pocket-id/templates/pvc.yaml | 13 ++++ pocket-id/templates/service.yaml | 16 ++++ pocket-id/values.yaml | 83 +++++++++++++++++++++ 10 files changed, 417 insertions(+) create mode 100644 pocket-id/Chart.yaml create mode 100644 pocket-id/templates/_helpers.tpl create mode 100644 pocket-id/templates/backup-db-cronjob.yaml create mode 100644 pocket-id/templates/backup-pvc-cronjob.yaml create mode 100644 pocket-id/templates/db-init-job.yaml create mode 100644 pocket-id/templates/deployment.yaml create mode 100644 pocket-id/templates/ingress.yaml create mode 100644 pocket-id/templates/pvc.yaml create mode 100644 pocket-id/templates/service.yaml create mode 100644 pocket-id/values.yaml diff --git a/pocket-id/Chart.yaml b/pocket-id/Chart.yaml new file mode 100644 index 0000000..0422217 --- /dev/null +++ b/pocket-id/Chart.yaml @@ -0,0 +1,9 @@ +apiVersion: v2 +name: pocket-id +description: Pocket ID Helm chart +type: application +version: 0.1.0 +appVersion: "2.4.0" +annotations: + version-source: github-release:pocket-id/pocket-id + version-pattern: "s|^v||" diff --git a/pocket-id/templates/_helpers.tpl b/pocket-id/templates/_helpers.tpl new file mode 100644 index 0000000..5469fbf --- /dev/null +++ b/pocket-id/templates/_helpers.tpl @@ -0,0 +1,8 @@ +{{/* PVC name to mount: existing claim if set, otherwise the chart-created one */}} +{{- define "pocket-id.pvcName" -}} +{{- if .Values.persistence.existingClaim -}} +{{ .Values.persistence.existingClaim }} +{{- else -}} +{{ .Release.Name }}-data +{{- end -}} +{{- end -}} diff --git a/pocket-id/templates/backup-db-cronjob.yaml b/pocket-id/templates/backup-db-cronjob.yaml new file mode 100644 index 0000000..d27a8d9 --- /dev/null +++ b/pocket-id/templates/backup-db-cronjob.yaml @@ -0,0 +1,60 @@ +{{- if .Values.backup.db.enabled }} +apiVersion: batch/v1 +kind: CronJob +metadata: + name: {{ .Release.Name }}-db-backup +spec: + schedule: {{ .Values.backup.db.schedule | quote }} + concurrencyPolicy: Forbid + successfulJobsHistoryLimit: 1 + failedJobsHistoryLimit: 1 + jobTemplate: + spec: + template: + spec: + restartPolicy: OnFailure + containers: + - name: pg-dump + image: {{ .Values.backup.db.image }} + command: + - /bin/sh + - -c + - | + set -e + mkdir -p /backup + TS=$(date +%Y%m%d-%H%M%S) + pg_dump -h $PGHOST -U $DB_USER -d $DB_NAME \ + | gzip > /backup/${FILE_PREFIX}-${TS}.sql.gz + find /backup -name "${FILE_PREFIX}-*.sql.gz" -mtime +$RETENTION_DAYS -delete + env: + - name: PGHOST + value: {{ .Values.backup.db.host | quote }} + - name: DB_USER + value: {{ .Values.backup.db.user | quote }} + - name: DB_NAME + value: {{ .Values.backup.db.database | quote }} + - name: FILE_PREFIX + value: {{ .Values.backup.db.filePrefix | quote }} + - name: RETENTION_DAYS + value: {{ .Values.backup.db.retentionDays | quote }} + - name: PGPASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.existingSecret }} + key: {{ .Values.backup.db.passwordSecretKey }} + volumeMounts: + - name: backup-storage + mountPath: /backup + resources: + requests: + memory: "64Mi" + cpu: "50m" + limits: + memory: "256Mi" + cpu: "500m" + volumes: + - name: backup-storage + hostPath: + path: {{ .Values.backup.db.hostPath }} + type: DirectoryOrCreate +{{- end }} diff --git a/pocket-id/templates/backup-pvc-cronjob.yaml b/pocket-id/templates/backup-pvc-cronjob.yaml new file mode 100644 index 0000000..7e4150c --- /dev/null +++ b/pocket-id/templates/backup-pvc-cronjob.yaml @@ -0,0 +1,59 @@ +{{- if .Values.backup.pvc.enabled }} +apiVersion: batch/v1 +kind: CronJob +metadata: + name: {{ .Release.Name }}-pvc-backup +spec: + schedule: {{ .Values.backup.pvc.schedule | quote }} + concurrencyPolicy: Forbid + successfulJobsHistoryLimit: 1 + failedJobsHistoryLimit: 1 + jobTemplate: + spec: + template: + spec: + restartPolicy: OnFailure + {{- with .Values.backup.pvc.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 12 }} + {{- end }} + containers: + - name: tar-backup + image: {{ .Values.backup.pvc.image }} + command: + - /bin/sh + - -c + - | + set -e + mkdir -p /backup + TS=$(date +%Y%m%d-%H%M%S) + tar czf /backup/${FILE_PREFIX}-${TS}.tar.gz -C /data . + find /backup -name "${FILE_PREFIX}-*.tar.gz" -mtime +$RETENTION_DAYS -delete + env: + - name: FILE_PREFIX + value: {{ .Values.backup.pvc.filePrefix | quote }} + - name: RETENTION_DAYS + value: {{ .Values.backup.pvc.retentionDays | quote }} + volumeMounts: + - name: data + mountPath: /data + readOnly: true + - name: backup-storage + mountPath: /backup + resources: + requests: + memory: "32Mi" + cpu: "50m" + limits: + memory: "128Mi" + cpu: "500m" + volumes: + - name: data + persistentVolumeClaim: + claimName: {{ include "pocket-id.pvcName" . }} + readOnly: true + - name: backup-storage + hostPath: + path: {{ .Values.backup.pvc.hostPath }} + type: DirectoryOrCreate +{{- end }} diff --git a/pocket-id/templates/db-init-job.yaml b/pocket-id/templates/db-init-job.yaml new file mode 100644 index 0000000..f23f9bd --- /dev/null +++ b/pocket-id/templates/db-init-job.yaml @@ -0,0 +1,71 @@ +{{- if .Values.dbInit.enabled }} +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ .Release.Name }}-db-init + annotations: + "helm.sh/hook": post-install + "helm.sh/hook-weight": "-5" + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded +spec: + template: + spec: + restartPolicy: OnFailure + containers: + - name: postgres-init + image: {{ .Values.dbInit.image }} + command: + - /bin/sh + - -c + - | + set -e + until pg_isready -h $PGHOST -p $PGPORT -U $ADMIN_USER; do + echo "Waiting for PostgreSQL to be ready..." + sleep 2 + done + + export PGPASSWORD=$POSTGRES_ADMIN_PASSWORD + + echo "Creating user $DB_USER if missing..." + psql -h $PGHOST -p $PGPORT -U $ADMIN_USER -d postgres -tc "SELECT 1 FROM pg_roles WHERE rolname='$DB_USER'" | grep -q 1 || \ + psql -h $PGHOST -p $PGPORT -U $ADMIN_USER -d postgres -c "CREATE USER $DB_USER WITH PASSWORD '${DB_USER_PASSWORD}';" + + echo "Creating database $DB_NAME if missing..." + psql -h $PGHOST -p $PGPORT -U $ADMIN_USER -d postgres -tc "SELECT 1 FROM pg_database WHERE datname='$DB_NAME'" | grep -q 1 || \ + psql -h $PGHOST -p $PGPORT -U $ADMIN_USER -d postgres -c "CREATE DATABASE $DB_NAME OWNER $DB_USER;" + + echo "Granting privileges..." + psql -h $PGHOST -p $PGPORT -U $ADMIN_USER -d $DB_NAME -c "GRANT ALL PRIVILEGES ON DATABASE $DB_NAME TO $DB_USER;" + psql -h $PGHOST -p $PGPORT -U $ADMIN_USER -d $DB_NAME -c "GRANT ALL PRIVILEGES ON SCHEMA public TO $DB_USER;" + psql -h $PGHOST -p $PGPORT -U $ADMIN_USER -d $DB_NAME -c "ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO $DB_USER;" + psql -h $PGHOST -p $PGPORT -U $ADMIN_USER -d $DB_NAME -c "ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO $DB_USER;" + echo "Database initialization complete." + env: + - name: PGHOST + value: {{ .Values.dbInit.host | quote }} + - name: PGPORT + value: {{ .Values.dbInit.port | quote }} + - name: ADMIN_USER + value: {{ .Values.dbInit.adminUser | quote }} + - name: DB_NAME + value: {{ .Values.dbInit.database | quote }} + - name: DB_USER + value: {{ .Values.dbInit.user | quote }} + - name: POSTGRES_ADMIN_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.existingSecret }} + key: {{ .Values.dbInit.adminPasswordSecretKey }} + - name: DB_USER_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.existingSecret }} + key: {{ .Values.dbInit.userPasswordSecretKey }} + resources: + requests: + memory: "64Mi" + cpu: "50m" + limits: + memory: "128Mi" + cpu: "100m" +{{- end }} diff --git a/pocket-id/templates/deployment.yaml b/pocket-id/templates/deployment.yaml new file mode 100644 index 0000000..79f17b8 --- /dev/null +++ b/pocket-id/templates/deployment.yaml @@ -0,0 +1,71 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }} + labels: + app.kubernetes.io/name: {{ .Chart.Name }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} + annotations: + version-source: {{ index .Chart.Annotations "version-source" }} + version-pattern: {{ index .Chart.Annotations "version-pattern" | quote }} +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app: {{ .Release.Name }} + template: + metadata: + labels: + app: {{ .Release.Name }} + spec: + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: {{ .Chart.Name }} + image: "{{ .Values.image.repository }}:v{{ .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - containerPort: {{ .Values.port }} + name: http + protocol: TCP + {{- if or .Values.env .Values.secretEnvKeys }} + env: + {{- range $key, $value := .Values.env }} + - name: {{ $key }} + value: {{ $value | quote }} + {{- end }} + {{- range .Values.secretEnvKeys }} + - name: {{ .envName }} + valueFrom: + secretKeyRef: + name: {{ $.Values.existingSecret }} + key: {{ .secretKey }} + {{- end }} + {{- end }} + {{- if .Values.existingSecret }} + envFrom: + - secretRef: + name: {{ .Values.existingSecret }} + {{- end }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + volumeMounts: + - name: data + mountPath: /app/data + volumes: + - name: data + persistentVolumeClaim: + claimName: {{ include "pocket-id.pvcName" . }} diff --git a/pocket-id/templates/ingress.yaml b/pocket-id/templates/ingress.yaml new file mode 100644 index 0000000..505e9a9 --- /dev/null +++ b/pocket-id/templates/ingress.yaml @@ -0,0 +1,27 @@ +{{- range $name, $config := .Values.ingresses }} +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ $.Release.Name }}-{{ $name }} + {{- with $config.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + rules: + - host: {{ $config.host }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ $.Release.Name }} + port: + number: {{ $.Values.servicePort }} + {{- with $config.tls }} + tls: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/pocket-id/templates/pvc.yaml b/pocket-id/templates/pvc.yaml new file mode 100644 index 0000000..8ccbbd3 --- /dev/null +++ b/pocket-id/templates/pvc.yaml @@ -0,0 +1,13 @@ +{{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ .Release.Name }}-data +spec: + accessModes: + - ReadWriteOnce + storageClassName: {{ .Values.persistence.storageClass }} + resources: + requests: + storage: {{ .Values.persistence.size }} +{{- end }} diff --git a/pocket-id/templates/service.yaml b/pocket-id/templates/service.yaml new file mode 100644 index 0000000..1916881 --- /dev/null +++ b/pocket-id/templates/service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }} + labels: + app.kubernetes.io/name: {{ .Chart.Name }} + app.kubernetes.io/instance: {{ .Release.Name }} +spec: + type: ClusterIP + selector: + app: {{ .Release.Name }} + ports: + - name: http + port: {{ .Values.servicePort }} + targetPort: http + protocol: TCP diff --git a/pocket-id/values.yaml b/pocket-id/values.yaml new file mode 100644 index 0000000..63a30b2 --- /dev/null +++ b/pocket-id/values.yaml @@ -0,0 +1,83 @@ +# Default values for Pocket ID Helm Chart +image: + repository: ghcr.io/pocket-id/pocket-id + pullPolicy: IfNotPresent + +port: 1411 + +# Service port exposed inside the cluster +servicePort: 80 + +env: {} + # APP_URL: "https://id.example.com" + # TRUST_PROXY: "true" + +# Reference an existing Kubernetes Secret for envFrom +existingSecret: "" + +secretEnvKeys: [] + # - envName: DB_CONNECTION_STRING + # secretKey: DB_CONNECTION_STRING + +resources: + requests: + cpu: "100m" + memory: "128Mi" + limits: + cpu: "500m" + memory: "512Mi" + +nodeSelector: {} +tolerations: [] +affinity: {} + +persistence: + enabled: true + # When set, chart uses an existing PVC and does NOT create one + existingClaim: "" + storageClass: local-path + size: 5Gi + +ingresses: {} + # https: + # host: id.example.com + # annotations: {} + # tls: + # - hosts: + # - id.example.com + +# One-shot Job that ensures DB user/database exist in an external Postgres. +# Runs as a Helm post-install hook. Idempotent. +dbInit: + enabled: false + host: postgres-tcp.postgres.svc.cluster.local + port: 5432 + adminUser: postgres + database: pocket_id + user: pocket_id + # References keys on existingSecret + adminPasswordSecretKey: POSTGRES_ADMIN_PASSWORD + userPasswordSecretKey: POCKET_ID_DB_PASSWORD + image: postgres:16-alpine + +backup: + db: + enabled: false + schedule: "0 4 * * *" + retentionDays: 7 + image: postgres:16-alpine + host: postgres-tcp.postgres.svc.cluster.local + user: pocket_id + database: pocket_id + passwordSecretKey: POCKET_ID_DB_PASSWORD + hostPath: /srv/backups/postgres + filePrefix: pocket_id + pvc: + enabled: false + schedule: "30 4 * * *" + retentionDays: 7 + image: busybox:latest + hostPath: /srv/backups/pvc + filePrefix: pocket-id-data + # Pin the backup pod to the same node as the PVC owner (required for RWO local-path) + nodeSelector: {}