From f66593fa7791b88d35b544247336ea458a866097 Mon Sep 17 00:00:00 2001 From: sHa Date: Wed, 18 Mar 2026 12:04:22 +0200 Subject: [PATCH] feat: Add Home Assistant Helm chart with PostgreSQL backup support --- home-assistant/Chart.yaml | 9 +++ home-assistant/templates/_helpers.tpl | 49 ++++++++++++ home-assistant/templates/backup-cronjob.yaml | 71 +++++++++++++++++ home-assistant/templates/deployment.yaml | 84 ++++++++++++++++++++ home-assistant/templates/ingress.yaml | 32 ++++++++ home-assistant/templates/pvc.yaml | 24 ++++++ home-assistant/templates/service.yaml | 18 +++++ home-assistant/values.yaml | 82 +++++++++++++++++++ 8 files changed, 369 insertions(+) create mode 100644 home-assistant/Chart.yaml create mode 100644 home-assistant/templates/_helpers.tpl create mode 100644 home-assistant/templates/backup-cronjob.yaml create mode 100644 home-assistant/templates/deployment.yaml create mode 100644 home-assistant/templates/ingress.yaml create mode 100644 home-assistant/templates/pvc.yaml create mode 100644 home-assistant/templates/service.yaml create mode 100644 home-assistant/values.yaml diff --git a/home-assistant/Chart.yaml b/home-assistant/Chart.yaml new file mode 100644 index 0000000..0fc45f6 --- /dev/null +++ b/home-assistant/Chart.yaml @@ -0,0 +1,9 @@ +apiVersion: v2 +name: home-assistant +description: A Helm chart for Home Assistant with PostgreSQL backup support +type: application +annotations: + version-source: github-release:home-assistant/core + version-pattern: "s|^v||" +version: 0.1.0 +appVersion: "2026.3.1" diff --git a/home-assistant/templates/_helpers.tpl b/home-assistant/templates/_helpers.tpl new file mode 100644 index 0000000..34308db --- /dev/null +++ b/home-assistant/templates/_helpers.tpl @@ -0,0 +1,49 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "home-assistant.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "home-assistant.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 }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "home-assistant.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "home-assistant.labels" -}} +helm.sh/chart: {{ include "home-assistant.chart" . }} +{{ include "home-assistant.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "home-assistant.selectorLabels" -}} +app.kubernetes.io/name: {{ include "home-assistant.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} diff --git a/home-assistant/templates/backup-cronjob.yaml b/home-assistant/templates/backup-cronjob.yaml new file mode 100644 index 0000000..6e8fbba --- /dev/null +++ b/home-assistant/templates/backup-cronjob.yaml @@ -0,0 +1,71 @@ +{{- if .Values.backup.enabled -}} +apiVersion: batch/v1 +kind: CronJob +metadata: + name: {{ include "home-assistant.fullname" . }}-backup + {{- if .Values.namespaceOverride }} + namespace: {{ .Values.namespaceOverride }} + {{- end }} + labels: + {{- include "home-assistant.labels" . | nindent 4 }} +spec: + schedule: {{ .Values.backup.schedule | quote }} + jobTemplate: + spec: + template: + spec: + containers: + - name: postgres-backup + image: {{ .Values.backup.image }} + command: + - /bin/sh + - -c + - | + # Backup Home Assistant database + pg_dump -h {{ .Values.backup.postgres.host }} -U {{ .Values.backup.postgres.user }} -d $DB_NAME | gzip > /backup/$DB_NAME-$(date +%Y%m%d-%H%M%S).sql.gz + # Retention: Delete backups older than specified days + find /backup -name "$DB_NAME-*.sql.gz" -mtime +$RETENTION_DAYS -delete + env: + - name: PGPASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.backup.postgres.existingSecret }} + key: {{ .Values.backup.postgres.secretKey }} + - name: DB_NAME + value: {{ .Values.backup.postgres.db | quote }} + - name: RETENTION_DAYS + value: {{ .Values.backup.retentionDays | quote }} + volumeMounts: + - name: backup-storage + mountPath: /backup + volumes: + - name: backup-storage + hostPath: + path: {{ .Values.backup.storagePath }} + type: DirectoryOrCreate + restartPolicy: OnFailure + {{- if .Values.backup.node }} + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: kubernetes.io/hostname + operator: In + values: + - {{ .Values.backup.node }} + {{- else }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 10 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 10 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 10 }} + {{- end }} + {{- end }} +{{- end }} diff --git a/home-assistant/templates/deployment.yaml b/home-assistant/templates/deployment.yaml new file mode 100644 index 0000000..5dbd04d --- /dev/null +++ b/home-assistant/templates/deployment.yaml @@ -0,0 +1,84 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "home-assistant.fullname" . }} + {{- if .Values.namespaceOverride }} + namespace: {{ .Values.namespaceOverride }} + {{- end }} + annotations: + version-source: github-release:home-assistant/core + labels: + {{- include "home-assistant.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + strategy: + type: Recreate + selector: + matchLabels: + {{- include "home-assistant.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "home-assistant.selectorLabels" . | nindent 8 }} + spec: + {{- if .Values.hostNetwork }} + hostNetwork: true + dnsPolicy: ClusterFirstWithHostNet + {{- end }} + containers: + - name: {{ .Chart.Name }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: 8123 + protocol: TCP + {{- if .Values.homekit.enabled }} + - name: homekit + containerPort: {{ .Values.homekit.port }} + protocol: TCP + {{- end }} + env: + - name: TZ + value: {{ .Values.timezone | quote }} + volumeMounts: + {{- if .Values.persistence.config.enabled }} + - name: config + mountPath: {{ .Values.persistence.config.mountPath }} + {{- end }} + {{- if .Values.persistence.media.enabled }} + - name: media + mountPath: {{ .Values.persistence.media.mountPath }} + {{- end }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + volumes: + {{- if .Values.persistence.config.enabled }} + - name: config + {{- if eq .Values.persistence.config.type "hostPath" }} + hostPath: + path: {{ .Values.persistence.config.hostPath }} + type: DirectoryOrCreate + {{- else }} + persistentVolumeClaim: + claimName: {{ .Values.persistence.config.existingClaim | default (include "home-assistant.fullname" .) }} + {{- end }} + {{- end }} + {{- if .Values.persistence.media.enabled }} + - name: media + hostPath: + path: {{ .Values.persistence.media.hostPath }} + type: DirectoryOrCreate + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/home-assistant/templates/ingress.yaml b/home-assistant/templates/ingress.yaml new file mode 100644 index 0000000..30a11af --- /dev/null +++ b/home-assistant/templates/ingress.yaml @@ -0,0 +1,32 @@ +{{- range $name, $config := .Values.ingresses }} +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "home-assistant.fullname" $ }}-{{ $name }} + {{- if $.Values.namespaceOverride }} + namespace: {{ $.Values.namespaceOverride }} + {{- end }} + labels: + {{- include "home-assistant.labels" $ | nindent 4 }} + {{- with $config.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + rules: + - host: {{ $config.host }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ include "home-assistant.fullname" $ }} + port: + number: {{ $.Values.service.port }} + {{- with $config.tls }} + tls: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/home-assistant/templates/pvc.yaml b/home-assistant/templates/pvc.yaml new file mode 100644 index 0000000..cd1db4b --- /dev/null +++ b/home-assistant/templates/pvc.yaml @@ -0,0 +1,24 @@ +{{- if .Values.persistence.config.enabled }} +{{- if eq .Values.persistence.config.type "pvc" }} +{{- if not .Values.persistence.config.existingClaim }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "home-assistant.fullname" . }} + {{- if .Values.namespaceOverride }} + namespace: {{ .Values.namespaceOverride }} + {{- end }} + labels: + {{- include "home-assistant.labels" . | nindent 4 }} +spec: + accessModes: + {{- toYaml .Values.persistence.config.accessModes | nindent 4 }} + resources: + requests: + storage: {{ .Values.persistence.config.size | quote }} + {{- if .Values.persistence.config.storageClass }} + storageClassName: {{ .Values.persistence.config.storageClass | quote }} + {{- end }} +{{- end }} +{{- end }} +{{- end }} diff --git a/home-assistant/templates/service.yaml b/home-assistant/templates/service.yaml new file mode 100644 index 0000000..3a36dff --- /dev/null +++ b/home-assistant/templates/service.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "home-assistant.fullname" . }} + {{- if .Values.namespaceOverride }} + namespace: {{ .Values.namespaceOverride }} + {{- end }} + labels: + {{- include "home-assistant.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: 8123 + protocol: TCP + name: http + selector: + {{- include "home-assistant.selectorLabels" . | nindent 4 }} diff --git a/home-assistant/values.yaml b/home-assistant/values.yaml new file mode 100644 index 0000000..1d0e545 --- /dev/null +++ b/home-assistant/values.yaml @@ -0,0 +1,82 @@ +replicaCount: 1 + +image: + repository: ghcr.io/home-assistant/home-assistant + pullPolicy: Always + tag: "" + +nameOverride: "" +fullnameOverride: "" +namespaceOverride: "" + +hostNetwork: true + +timezone: "Europe/Kyiv" + +homekit: + enabled: true + port: 21063 + +service: + type: ClusterIP + port: 80 + +ingresses: {} + # https: + # host: ha.example.com + # annotations: + # traefik.ingress.kubernetes.io/router.entrypoints: web,websecure + # traefik.ingress.kubernetes.io/router.tls: "true" + # traefik.ingress.kubernetes.io/router.tls.certresolver: letsencrypt + # traefik.ingress.kubernetes.io/router.middlewares: traefik-redirect-to-https@kubernetescrd + # tls: + # - hosts: + # - ha.example.com + # lan: + # host: ha.local.lan + # annotations: + # traefik.ingress.kubernetes.io/router.entrypoints: web + +resources: + requests: + cpu: "100m" + memory: "512Mi" + limits: + cpu: "8" + memory: "4Gi" + +persistence: + config: + enabled: true + type: hostPath + hostPath: /srv/data/home-assistant/config + mountPath: /config + size: 10Gi + accessModes: + - ReadWriteOnce + storageClass: "" + media: + enabled: true + hostPath: /srv/data/home-assistant/media + mountPath: /media + +backup: + enabled: false + schedule: "0 2 * * *" + image: postgres:16 + retentionDays: 10 + storagePath: /storage/backups/postgres + # Node to run backup on (hostname). Overrides global affinity/tolerations. + node: "" + postgres: + host: postgres-tcp.postgres.svc.cluster.local + db: homeassistant + user: postgres + existingSecret: "" + secretKey: password + +nodeSelector: {} + +tolerations: [] + +affinity: {}