From 250a126357361c208a0eab864fae2fa6b15b17d5 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Wed, 5 Nov 2025 22:56:01 +0300 Subject: [PATCH 01/10] fix(ipv6): use httpGet probes by default for IPv4 and IPv6 compatibility Change default health probes from tcpSocket to httpGet to fix issue #68 where pods fail to start in IPv6-only clusters due to incompatible health checks. Changes: - Change default livenessProbe and readinessProbe to use httpGet with /health endpoint - Add comprehensive IPv6 and dual-stack configuration documentation - Document ipFamilyPolicy options (SingleStack, PreferDualStack, RequireDualStack) - Document ipFamilies configuration for IPv4-only, IPv6-only, and dual-stack setups - Add IPv6 Configuration section to README with examples - Update Chart version from 2.5.0 to 2.5.1 The httpGet probe works with both IPv4 and IPv6 out of the box, while tcpSocket may fail in IPv6-only environments. Jellyfin's /health endpoint is specifically designed for health checks and verifies both HTTP and database connectivity. Fixes: #68 Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- charts/jellyfin/Chart.yaml | 8 ++++++- charts/jellyfin/README.md | 39 ++++++++++++++++++++++++++++---- charts/jellyfin/README.md.gotmpl | 29 ++++++++++++++++++++++++ charts/jellyfin/values.yaml | 21 +++++++++++++---- 4 files changed, 87 insertions(+), 10 deletions(-) diff --git a/charts/jellyfin/Chart.yaml b/charts/jellyfin/Chart.yaml index 96a351b..2960dbf 100644 --- a/charts/jellyfin/Chart.yaml +++ b/charts/jellyfin/Chart.yaml @@ -8,5 +8,11 @@ keywords: - jellyfin - media - self-hosted -version: 2.5.0 +version: 2.5.1 appVersion: 10.11.2 +annotations: + artifacthub.io/changes: | + - kind: added + description: Add comprehensive IPv6 and dual-stack networking documentation + - kind: changed + description: Improve health probe configuration examples for IPv6 compatibility diff --git a/charts/jellyfin/README.md b/charts/jellyfin/README.md index 10952b6..d330a84 100644 --- a/charts/jellyfin/README.md +++ b/charts/jellyfin/README.md @@ -1,6 +1,6 @@ # jellyfin -![Version: 2.5.0](https://img.shields.io/badge/Version-2.5.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 10.11.2](https://img.shields.io/badge/AppVersion-10.11.2-informational?style=flat-square) +![Version: 2.5.1](https://img.shields.io/badge/Version-2.5.1-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 10.11.2](https://img.shields.io/badge/AppVersion-10.11.2-informational?style=flat-square) A Helm chart for Jellyfin Media Server @@ -69,7 +69,7 @@ helm install my-jellyfin jellyfin/jellyfin -f values.yaml | jellyfin.command | list | `[]` | Custom command to use as container entrypoint. | | jellyfin.enableDLNA | bool | `false` | Enable DLNA. Requires host network. See: https://jellyfin.org/docs/general/networking/dlna.html | | jellyfin.env | list | `[]` | Additional environment variables for the container. | -| livenessProbe | object | `{"initialDelaySeconds":10,"tcpSocket":{"port":"http"}}` | Configure liveness probe for Jellyfin. | +| livenessProbe | object | `{"httpGet":{"path":"/health","port":"http"},"initialDelaySeconds":10}` | Configure liveness probe for Jellyfin. Uses httpGet for compatibility with both IPv4 and IPv6. | | metrics | object | `{"enabled":false,"serviceMonitor":{"enabled":false,"interval":"30s","labels":{},"metricRelabelings":[],"namespace":"","path":"/metrics","port":8096,"relabelings":[],"scheme":"http","scrapeTimeout":"30s","targetLabels":[],"tlsConfig":{}}}` | Configuration for metrics collection and monitoring | | metrics.enabled | bool | `false` | Enable or disable metrics collection | | metrics.serviceMonitor | object | `{"enabled":false,"interval":"30s","labels":{},"metricRelabelings":[],"namespace":"","path":"/metrics","port":8096,"relabelings":[],"scheme":"http","scrapeTimeout":"30s","targetLabels":[],"tlsConfig":{}}` | Configuration for the Prometheus ServiceMonitor | @@ -107,14 +107,14 @@ helm install my-jellyfin jellyfin/jellyfin -f values.yaml | podPrivileges.hostPID | bool | `false` | Enable hostPID namespace. Allows pod to see processes on the host. | | podSecurityContext | object | `{}` | Security context for the pod. | | priorityClassName | string | `""` | Define a priorityClassName for the pod. | -| readinessProbe | object | `{"initialDelaySeconds":10,"tcpSocket":{"port":"http"}}` | Configure readiness probe for Jellyfin. | +| readinessProbe | object | `{"httpGet":{"path":"/health","port":"http"},"initialDelaySeconds":10}` | Configure readiness probe for Jellyfin. Uses httpGet for compatibility with both IPv4 and IPv6. | | replicaCount | int | `1` | Number of Jellyfin replicas to start. Should be left at 1. | | resources | object | `{}` | Resource requests and limits for the Jellyfin container. | | runtimeClassName | string | `""` | Define a custom runtimeClassName for the pod. | | securityContext | object | `{}` | Security context for the container. | | service.annotations | object | `{}` | Annotations for the service. | -| service.ipFamilies | list | `[]` | Supported IP families (IPv4, IPv6). | -| service.ipFamilyPolicy | string | `""` | Configure dual-stack IP family policy. See: https://kubernetes.io/docs/concepts/services-networking/dual-stack/ | +| service.ipFamilies | list | `[]` | Supported IP families (IPv4, IPv6). Examples: IPv4 only: ["IPv4"] IPv6 only: ["IPv6"] Dual-stack (IPv4 primary): ["IPv4", "IPv6"] Dual-stack (IPv6 primary): ["IPv6", "IPv4"] Note: When using IPv6, ensure your health checks are compatible (consider using httpGet instead of tcpSocket) | +| service.ipFamilyPolicy | string | `""` | Configure dual-stack IP family policy. See: https://kubernetes.io/docs/concepts/services-networking/dual-stack/ Options: SingleStack, PreferDualStack, RequireDualStack For IPv6-only clusters, use "SingleStack" with ipFamilies: ["IPv6"] For dual-stack, use "PreferDualStack" or "RequireDualStack" with ipFamilies: ["IPv4", "IPv6"] or ["IPv6", "IPv4"] | | service.labels | object | `{}` | Labels for the service. | | service.loadBalancerClass | string | `""` | Class of the LoadBalancer. | | service.loadBalancerIP | string | `""` | Specific IP address for the LoadBalancer. | @@ -160,3 +160,32 @@ extraVolumeMounts: - name: hwa mountPath: /dev/dri ``` + +## IPv6 Configuration + +This chart supports IPv6 and dual-stack networking configurations out of the box. Health probes use httpGet by default for compatibility with both IPv4 and IPv6. + +### IPv6-only Configuration + +For IPv6-only clusters: + +```yaml +service: + ipFamilyPolicy: SingleStack + ipFamilies: + - IPv6 +``` + +### Dual-stack Configuration + +For dual-stack clusters (both IPv4 and IPv6): + +```yaml +service: + ipFamilyPolicy: PreferDualStack # or RequireDualStack + ipFamilies: + - IPv4 + - IPv6 # First family in the list is the primary +``` + +For more information about Kubernetes dual-stack networking, see: diff --git a/charts/jellyfin/README.md.gotmpl b/charts/jellyfin/README.md.gotmpl index ee08a35..90546e6 100644 --- a/charts/jellyfin/README.md.gotmpl +++ b/charts/jellyfin/README.md.gotmpl @@ -85,3 +85,32 @@ extraVolumeMounts: - name: hwa mountPath: /dev/dri ``` + +## IPv6 Configuration + +This chart supports IPv6 and dual-stack networking configurations out of the box. Health probes use httpGet by default for compatibility with both IPv4 and IPv6. + +### IPv6-only Configuration + +For IPv6-only clusters: + +```yaml +service: + ipFamilyPolicy: SingleStack + ipFamilies: + - IPv6 +``` + +### Dual-stack Configuration + +For dual-stack clusters (both IPv4 and IPv6): + +```yaml +service: + ipFamilyPolicy: PreferDualStack # or RequireDualStack + ipFamilies: + - IPv4 + - IPv6 # First family in the list is the primary +``` + +For more information about Kubernetes dual-stack networking, see: diff --git a/charts/jellyfin/values.yaml b/charts/jellyfin/values.yaml index 068c0ef..69170b3 100644 --- a/charts/jellyfin/values.yaml +++ b/charts/jellyfin/values.yaml @@ -85,8 +85,17 @@ service: # -- Service type (ClusterIP, NodePort, or LoadBalancer). type: ClusterIP # -- Configure dual-stack IP family policy. See: https://kubernetes.io/docs/concepts/services-networking/dual-stack/ + # Options: SingleStack, PreferDualStack, RequireDualStack + # For IPv6-only clusters, use "SingleStack" with ipFamilies: ["IPv6"] + # For dual-stack, use "PreferDualStack" or "RequireDualStack" with ipFamilies: ["IPv4", "IPv6"] or ["IPv6", "IPv4"] ipFamilyPolicy: "" # -- Supported IP families (IPv4, IPv6). + # Examples: + # IPv4 only: ["IPv4"] + # IPv6 only: ["IPv6"] + # Dual-stack (IPv4 primary): ["IPv4", "IPv6"] + # Dual-stack (IPv6 primary): ["IPv6", "IPv4"] + # Note: When using IPv6, ensure your health checks are compatible (consider using httpGet instead of tcpSocket) ipFamilies: [] # -- Specific IP address for the LoadBalancer. loadBalancerIP: "" @@ -134,20 +143,24 @@ resources: {} # memory: 128Mi # -- Configure liveness probe for Jellyfin. +# Uses httpGet for compatibility with both IPv4 and IPv6. livenessProbe: - initialDelaySeconds: 10 - tcpSocket: + httpGet: + path: /health port: http + initialDelaySeconds: 10 # successThreshold: 1 # failureThreshold: 3 # timeoutSeconds: 1 # periodSeconds: 10 # -- Configure readiness probe for Jellyfin. +# Uses httpGet for compatibility with both IPv4 and IPv6. readinessProbe: - initialDelaySeconds: 10 - tcpSocket: + httpGet: + path: /health port: http + initialDelaySeconds: 10 # successThreshold: 1 # failureThreshold: 3 # timeoutSeconds: 1 From 35f39bf92c1e158a379622fb8d6d27e7eaad58ef Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Wed, 5 Nov 2025 23:26:58 +0300 Subject: [PATCH 02/10] feat(persistence): add cache volume persistence support Add dedicated cache volume configuration to reduce wear on SD cards and improve performance by allowing cache to be stored on separate storage. Changes: - Add persistence.cache configuration in values.yaml with support for: - PVC (default type when enabled) - hostPath for direct host mount - emptyDir (default when disabled) - Add cache volume mount at /cache in deployment - Add cache PersistentVolumeClaim template - Support for existing PVC via existingClaim - Configurable size (default 10Gi), accessMode, storageClass, and annotations - Update Chart version from 2.5.0 to 2.5.1 - Add Artifact Hub changelog annotation The cache volume is disabled by default to maintain backward compatibility. Users can enable it via: ```yaml persistence: cache: enabled: true type: pvc size: 10Gi ``` This addresses the use case mentioned in #62 where users want to avoid SD card wear by storing cache on separate persistent storage. Fixes: #62 Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- charts/jellyfin/Chart.yaml | 6 ++++- charts/jellyfin/README.md | 9 +++++++- charts/jellyfin/templates/deployment.yaml | 17 ++++++++++++++ .../templates/persistentVolumeClaim.yaml | 23 +++++++++++++++++++ charts/jellyfin/values.yaml | 16 +++++++++++++ 5 files changed, 69 insertions(+), 2 deletions(-) diff --git a/charts/jellyfin/Chart.yaml b/charts/jellyfin/Chart.yaml index 96a351b..6f24db1 100644 --- a/charts/jellyfin/Chart.yaml +++ b/charts/jellyfin/Chart.yaml @@ -8,5 +8,9 @@ keywords: - jellyfin - media - self-hosted -version: 2.5.0 +version: 2.5.1 appVersion: 10.11.2 +annotations: + artifacthub.io/changes: | + - kind: added + description: Add persistence.cache configuration for dedicated cache volume support diff --git a/charts/jellyfin/README.md b/charts/jellyfin/README.md index 10952b6..8b18a7a 100644 --- a/charts/jellyfin/README.md +++ b/charts/jellyfin/README.md @@ -1,6 +1,6 @@ # jellyfin -![Version: 2.5.0](https://img.shields.io/badge/Version-2.5.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 10.11.2](https://img.shields.io/badge/AppVersion-10.11.2-informational?style=flat-square) +![Version: 2.5.1](https://img.shields.io/badge/Version-2.5.1-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 10.11.2](https://img.shields.io/badge/AppVersion-10.11.2-informational?style=flat-square) A Helm chart for Jellyfin Media Server @@ -87,6 +87,13 @@ helm install my-jellyfin jellyfin/jellyfin -f values.yaml | metrics.serviceMonitor.tlsConfig | object | `{}` | TLS configuration for scraping metrics | | nameOverride | string | `""` | Override the default name of the chart. | | nodeSelector | object | `{}` | Node selector for pod scheduling. | +| persistence.cache.accessMode | string | `"ReadWriteOnce"` | PVC specific settings, only used if type is 'pvc'. | +| persistence.cache.annotations | object | `{}` | Custom annotations to be added to the PVC | +| persistence.cache.enabled | bool | `false` | set to false to use emptyDir | +| persistence.cache.hostPath | string | `""` | Path on the host node for cache storage, only used if type is 'hostPath'. | +| persistence.cache.size | string | `"10Gi"` | | +| persistence.cache.storageClass | string | `""` | If undefined (the default) or set to null, no storageClassName spec is set, choosing the default provisioner. | +| persistence.cache.type | string | `"pvc"` | Type of volume for cache storage (pvc, hostPath, emptyDir). If 'enabled' is false, 'emptyDir' is used regardless of this setting. | | persistence.config.accessMode | string | `"ReadWriteOnce"` | | | persistence.config.annotations | object | `{}` | Custom annotations to be added to the PVC | | persistence.config.enabled | bool | `true` | set to false to use emptyDir | diff --git a/charts/jellyfin/templates/deployment.yaml b/charts/jellyfin/templates/deployment.yaml index c235001..3e1c0ec 100644 --- a/charts/jellyfin/templates/deployment.yaml +++ b/charts/jellyfin/templates/deployment.yaml @@ -119,6 +119,8 @@ spec: name: config - mountPath: /media name: media + - mountPath: /cache + name: cache {{- with .Values.volumeMounts }} {{- toYaml . | nindent 12 }} {{- end }} @@ -152,6 +154,21 @@ spec: {{- else }} emptyDir: {} {{- end }} + - name: cache + {{- if .Values.persistence.cache.enabled }} + {{- if eq .Values.persistence.cache.type "pvc" }} + persistentVolumeClaim: + claimName: {{ if .Values.persistence.cache.existingClaim }}{{ .Values.persistence.cache.existingClaim }}{{- else }}{{ template "jellyfin.fullname" . }}-cache{{- end }} + {{- else if eq .Values.persistence.cache.type "hostPath" }} + hostPath: + path: {{ .Values.persistence.cache.hostPath }} + type: Directory + {{- else }} + emptyDir: {} + {{- end }} + {{- else }} + emptyDir: {} + {{- end }} {{- with .Values.volumes }} {{- toYaml . | nindent 8 }} {{- end }} diff --git a/charts/jellyfin/templates/persistentVolumeClaim.yaml b/charts/jellyfin/templates/persistentVolumeClaim.yaml index 709436d..64ad819 100644 --- a/charts/jellyfin/templates/persistentVolumeClaim.yaml +++ b/charts/jellyfin/templates/persistentVolumeClaim.yaml @@ -43,3 +43,26 @@ spec: storageClassName: {{ . | quote }} {{- end }} {{- end }} + +{{- if and .Values.persistence.cache.enabled (eq .Values.persistence.cache.type "pvc") (not .Values.persistence.cache.existingClaim) }} +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "jellyfin.fullname" . }}-cache + labels: + {{- include "jellyfin.labels" . | nindent 4 }} + {{- with .Values.persistence.cache.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + accessModes: + - {{ .Values.persistence.cache.accessMode | quote }} + resources: + requests: + storage: {{ .Values.persistence.cache.size | quote }} + {{- with .Values.persistence.cache.storageClass }} + storageClassName: {{ . | quote }} + {{- end }} +{{- end }} diff --git a/charts/jellyfin/values.yaml b/charts/jellyfin/values.yaml index 068c0ef..d4fdf8a 100644 --- a/charts/jellyfin/values.yaml +++ b/charts/jellyfin/values.yaml @@ -222,6 +222,22 @@ persistence: storageClass: '' ## -- Use an existing PVC for this mount # existingClaim: '' + cache: + # -- set to false to use emptyDir + enabled: false + # -- Type of volume for cache storage (pvc, hostPath, emptyDir). If 'enabled' is false, 'emptyDir' is used regardless of this setting. + type: pvc + # -- Path on the host node for cache storage, only used if type is 'hostPath'. + hostPath: "" + # -- PVC specific settings, only used if type is 'pvc'. + accessMode: ReadWriteOnce + size: 10Gi + # -- Custom annotations to be added to the PVC + annotations: {} + # -- If undefined (the default) or set to null, no storageClassName spec is set, choosing the default provisioner. + storageClass: '' + ## -- Use an existing PVC for this mount + # existingClaim: '' # -- Configuration for metrics collection and monitoring metrics: From df3e66c5f9fe803e9663a9621f932ef22eb2d72d Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Thu, 6 Nov 2025 00:04:39 +0300 Subject: [PATCH 03/10] feat(probes): add startup probe for handling slow initial startup Add startupProbe to give Jellyfin sufficient time to start, especially with large media libraries or slow storage. This prevents Kubernetes from killing the pod during initial startup phase. Changes: - Add startupProbe configuration in values.yaml with sensible defaults - failureThreshold: 30 (allows up to 5 minutes for startup) - periodSeconds: 10 - Uses tcpSocket check on http port - Update deployment.yaml to include startupProbe from values - Bump chart version 2.5.0 -> 2.5.1 - Add changelog annotation for Artifact Hub Benefits: - Prevents pod restarts during slow initial startup (large media libraries) - Liveness and readiness probes remain responsive after successful startup - Fully configurable and can be disabled by setting startupProbe: null - No breaking changes, backward compatible The startup probe gives Jellyfin up to 5 minutes (30 attempts * 10 seconds) to complete initialization. After the first success, liveness and readiness probes take over with their normal timing. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- charts/jellyfin/Chart.yaml | 6 +++++- charts/jellyfin/templates/deployment.yaml | 4 ++++ charts/jellyfin/values.yaml | 14 ++++++++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/charts/jellyfin/Chart.yaml b/charts/jellyfin/Chart.yaml index 96a351b..5fc2d25 100644 --- a/charts/jellyfin/Chart.yaml +++ b/charts/jellyfin/Chart.yaml @@ -8,5 +8,9 @@ keywords: - jellyfin - media - self-hosted -version: 2.5.0 +version: 2.5.1 appVersion: 10.11.2 +annotations: + artifacthub.io/changes: | + - kind: added + description: Add startup probe to prevent pod restarts during slow initial startup with large media libraries diff --git a/charts/jellyfin/templates/deployment.yaml b/charts/jellyfin/templates/deployment.yaml index c235001..2b4265f 100644 --- a/charts/jellyfin/templates/deployment.yaml +++ b/charts/jellyfin/templates/deployment.yaml @@ -108,6 +108,10 @@ spec: hostPort: 1900 protocol: UDP {{- end }} + {{- with .Values.startupProbe }} + startupProbe: + {{- toYaml . | nindent 12 }} + {{- end }} livenessProbe: {{- toYaml .Values.livenessProbe | nindent 12 }} readinessProbe: diff --git a/charts/jellyfin/values.yaml b/charts/jellyfin/values.yaml index 068c0ef..f5d4bf1 100644 --- a/charts/jellyfin/values.yaml +++ b/charts/jellyfin/values.yaml @@ -133,7 +133,20 @@ resources: {} # cpu: 100m # memory: 128Mi +# -- Configure startup probe for Jellyfin. +# This probe gives Jellyfin enough time to start, especially with large media libraries. +# After the startup probe succeeds once, liveness and readiness probes take over. +startupProbe: + tcpSocket: + port: http + initialDelaySeconds: 0 + periodSeconds: 10 + failureThreshold: 30 + # With these defaults, Jellyfin has up to 5 minutes (30 * 10s) to start. + # Adjust failureThreshold if you have very large media libraries or slow storage. + # -- Configure liveness probe for Jellyfin. +# This probe is disabled during startup (startup probe handles initial checks). livenessProbe: initialDelaySeconds: 10 tcpSocket: @@ -144,6 +157,7 @@ livenessProbe: # periodSeconds: 10 # -- Configure readiness probe for Jellyfin. +# This probe is disabled during startup (startup probe handles initial checks). readinessProbe: initialDelaySeconds: 10 tcpSocket: From 90861109aab568542a752bdba913e0895e55fc36 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Thu, 6 Nov 2025 00:51:45 +0300 Subject: [PATCH 04/10] fix(templates)!: deprecate initContainers in favor of extraInitContainers BREAKING CHANGE: The 'initContainers' parameter is now deprecated in favor of 'extraInitContainers' for naming consistency with 'extraContainers'. Both parameters will work until 2030 for backward compatibility. The previous implementation used '.Values.initContainers' in the template but 'extraInitContainers' was defined in values.yaml, causing the feature to be completely non-functional. Changes: - Fix extraInitContainers not working (was referencing wrong parameter) - Add deprecated 'initContainers' parameter to values.yaml with @deprecated tag - Update deployment.yaml to support both parameters with OR logic - Add TODO comment to remove initContainers support after 2030 - Create NOTES.txt with: - Deprecation warning when using initContainers - Post-installation instructions for accessing Jellyfin - Persistence status information - Links to documentation - Improve extraInitContainers documentation with usage example - Bump chart version 2.5.0 -> 3.0.0 (major due to deprecation) - Add comprehensive changelog to artifacthub.io/changes Migration guide: Old: initContainers: [...] New: extraInitContainers: [...] The old parameter will continue to work but users will see a deprecation warning in NOTES.txt after installation. Follows best practices from community charts (Prometheus, Elastic, etc.) where 'extra*' prefix indicates user-provided extension points. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- charts/jellyfin/Chart.yaml | 10 ++- charts/jellyfin/templates/NOTES.txt | 100 ++++++++++++++++++++++ charts/jellyfin/templates/deployment.yaml | 11 ++- charts/jellyfin/values.yaml | 18 +++- 4 files changed, 134 insertions(+), 5 deletions(-) create mode 100644 charts/jellyfin/templates/NOTES.txt diff --git a/charts/jellyfin/Chart.yaml b/charts/jellyfin/Chart.yaml index 96a351b..1cc1bcd 100644 --- a/charts/jellyfin/Chart.yaml +++ b/charts/jellyfin/Chart.yaml @@ -8,5 +8,13 @@ keywords: - jellyfin - media - self-hosted -version: 2.5.0 +version: 3.0.0 appVersion: 10.11.2 +annotations: + artifacthub.io/changes: | + - kind: deprecated + description: Deprecate initContainers parameter in favor of extraInitContainers for consistency (will be removed after 2030) + - kind: fixed + description: Fix extraInitContainers not working (was using wrong parameter name in template) + - kind: added + description: Add NOTES.txt with helpful post-installation information and deprecation warnings diff --git a/charts/jellyfin/templates/NOTES.txt b/charts/jellyfin/templates/NOTES.txt new file mode 100644 index 0000000..aea6e12 --- /dev/null +++ b/charts/jellyfin/templates/NOTES.txt @@ -0,0 +1,100 @@ +{{- if .Values.initContainers }} +############################################################################# +## ⚠️ WARNING ⚠️ ## +## ## +## The 'initContainers' parameter is DEPRECATED! ## +## Please migrate to 'extraInitContainers' instead. ## +## ## +## Support for 'initContainers' will be removed after 2030. ## +## ## +## Migration guide: ## +## Old: initContainers: [...] ## +## New: extraInitContainers: [...] ## +## ## +############################################################################# + +{{- end }} +Thank you for installing {{ .Chart.Name }}! + +Your release is named {{ .Release.Name }}. + +To learn more about the release, try: + + $ helm status {{ .Release.Name }} --namespace {{ .Release.Namespace }} + $ helm get all {{ .Release.Name }} --namespace {{ .Release.Namespace }} + +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + +Jellyfin is available at: + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + +Get the Jellyfin URL by running: + + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "jellyfin.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo "Jellyfin URL: http://$NODE_IP:$NODE_PORT" + +{{- else if contains "LoadBalancer" .Values.service.type }} + +Get the Jellyfin URL by running: + + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status by running: + kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "jellyfin.fullname" . }} + + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "jellyfin.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo "Jellyfin URL: http://$SERVICE_IP:{{ .Values.service.port }}" + +{{- else if contains "ClusterIP" .Values.service.type }} + +Get the Jellyfin URL by running: + + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "jellyfin.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Jellyfin URL: http://127.0.0.1:8080" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT + +{{- end }} + +{{- if .Values.persistence.config.enabled }} + +Configuration is persisted to: +{{- if .Values.persistence.config.existingClaim }} + Existing PVC: {{ .Values.persistence.config.existingClaim }} +{{- else }} + PVC: {{ include "jellyfin.fullname" . }}-config +{{- end }} +{{- else }} + +WARNING: Configuration persistence is disabled! + Data will be lost when the pod is restarted. + Enable persistence.config.enabled to persist your configuration. +{{- end }} + +{{- if .Values.persistence.media.enabled }} + +Media is {{ if eq .Values.persistence.media.type "hostPath" }}mounted from host path{{ else if eq .Values.persistence.media.type "pvc" }}persisted to PVC{{ else }}stored in emptyDir{{ end }}: +{{- if eq .Values.persistence.media.type "hostPath" }} + Host path: {{ .Values.persistence.media.hostPath }} +{{- else if eq .Values.persistence.media.type "pvc" }} + {{- if .Values.persistence.media.existingClaim }} + Existing PVC: {{ .Values.persistence.media.existingClaim }} + {{- else }} + PVC: {{ include "jellyfin.fullname" . }}-media + {{- end }} +{{- end }} +{{- else }} + +WARNING: Media persistence is disabled! + Your media files will be lost when the pod is restarted. + Enable persistence.media.enabled to persist your media. +{{- end }} + +For more information and documentation: + Repository: https://github.com/jellyfin/jellyfin-helm + Jellyfin Docs: https://jellyfin.org/docs/ diff --git a/charts/jellyfin/templates/deployment.yaml b/charts/jellyfin/templates/deployment.yaml index c235001..e184446 100644 --- a/charts/jellyfin/templates/deployment.yaml +++ b/charts/jellyfin/templates/deployment.yaml @@ -125,9 +125,16 @@ spec: {{- with .Values.extraContainers }} {{- toYaml . | nindent 8 }} {{- end }} - {{- with .Values.initContainers }} + {{- if or .Values.initContainers .Values.extraInitContainers }} + # Support both initContainers (deprecated) and extraInitContainers for backward compatibility. + # TODO: Remove initContainers support after 2030 initContainers: - {{- toYaml . | nindent 6 }} + {{- if .Values.initContainers }} + {{- toYaml .Values.initContainers | nindent 6 }} + {{- end }} + {{- if .Values.extraInitContainers }} + {{- toYaml .Values.extraInitContainers | nindent 6 }} + {{- end }} {{- end }} volumes: - name: config diff --git a/charts/jellyfin/values.yaml b/charts/jellyfin/values.yaml index 068c0ef..5e4e2f2 100644 --- a/charts/jellyfin/values.yaml +++ b/charts/jellyfin/values.yaml @@ -254,8 +254,22 @@ metrics: # -- Target labels to add to the scraped metrics targetLabels: [] -# -- additional init containers to run inside the pod. +# -- DEPRECATED: Use extraInitContainers instead. Will be removed after 2030. +# @deprecated - This parameter is deprecated, use extraInitContainers instead +initContainers: [] + +# -- Additional init containers to run inside the pod. +# Init containers run before the main application container starts. +# See: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/ +# Example: +# extraInitContainers: +# - name: init-config +# image: busybox:1.35 +# command: ['sh', '-c', 'echo "Initializing..." && sleep 5'] +# volumeMounts: +# - name: config +# mountPath: /config extraInitContainers: [] - + # -- additional sidecar containers to run inside the pod. extraContainers: [] From d20aac579403d77935cbe9ac519d09ec0047e9bd Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Thu, 6 Nov 2025 13:34:40 +0300 Subject: [PATCH 05/10] feat(deployment): add envFrom support for ConfigMap/Secret environment variables Add support for loading environment variables from ConfigMap or Secret resources using envFrom field, following Kubernetes best practices for configuration management. Changes: - Add jellyfin.envFrom parameter in values.yaml - Add envFrom section in deployment.yaml template (before env) - Provide comprehensive documentation with examples - Bump chart version 2.5.0 -> 2.6.0 (minor - new feature) - Add changelog annotation for Artifact Hub Use cases: - Load multiple environment variables from ConfigMap without defining each one - Load secrets from external secret management systems - Separate configuration from chart values (12-factor app pattern) - Simplify configuration for complex deployments Example usage: jellyfin: envFrom: - configMapRef: name: jellyfin-config - secretRef: name: jellyfin-secrets Follows standard Kubernetes patterns used by most community Helm charts. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- charts/jellyfin/Chart.yaml | 6 +++++- charts/jellyfin/templates/deployment.yaml | 4 ++++ charts/jellyfin/values.yaml | 13 +++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/charts/jellyfin/Chart.yaml b/charts/jellyfin/Chart.yaml index 96a351b..9c16315 100644 --- a/charts/jellyfin/Chart.yaml +++ b/charts/jellyfin/Chart.yaml @@ -8,5 +8,9 @@ keywords: - jellyfin - media - self-hosted -version: 2.5.0 +version: 2.6.0 appVersion: 10.11.2 +annotations: + artifacthub.io/changes: | + - kind: added + description: Add envFrom support to load environment variables from ConfigMap or Secret diff --git a/charts/jellyfin/templates/deployment.yaml b/charts/jellyfin/templates/deployment.yaml index c235001..2616bbd 100644 --- a/charts/jellyfin/templates/deployment.yaml +++ b/charts/jellyfin/templates/deployment.yaml @@ -59,6 +59,10 @@ spec: args: {{- toYaml . | nindent 12 }} {{- end }} + {{- with .Values.jellyfin.envFrom }} + envFrom: + {{- toYaml . | nindent 12 }} + {{- end }} {{- with .Values.jellyfin.env }} env: {{- toYaml . | nindent 12 }} diff --git a/charts/jellyfin/values.yaml b/charts/jellyfin/values.yaml index 068c0ef..83bb82c 100644 --- a/charts/jellyfin/values.yaml +++ b/charts/jellyfin/values.yaml @@ -191,7 +191,20 @@ jellyfin: command: [] # -- Additional arguments for the entrypoint command. args: [] + # -- Load environment variables from ConfigMap or Secret. + # See: https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/#configure-all-key-value-pairs-in-a-configmap-as-container-environment-variables + # Example: + # envFrom: + # - configMapRef: + # name: jellyfin-config + # - secretRef: + # name: jellyfin-secrets + envFrom: [] # -- Additional environment variables for the container. + # Example: + # env: + # - name: JELLYFIN_CACHE_DIR + # value: /cache env: [] persistence: From 1744d240119ea3f066104fdc423bbf747249484c Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 7 Nov 2025 19:53:08 +0300 Subject: [PATCH 06/10] test(service): add IPv6 and dual-stack configuration tests Add helm-unittest tests for IPv6 and dual-stack Service configuration: - Default behavior (no ipFamilyPolicy/ipFamilies set) - SingleStack, PreferDualStack, RequireDualStack policies - IPv4 only, IPv6 only configurations - Dual-stack with IPv4 primary and IPv6 primary - Combined policy and families configurations Add NOTES.txt with IPv6/dual-stack information: - Display configured IP family policy - Show IP families list - Warning when IPv6 is primary (probe compatibility note) - Warning when IPv6 used without policy Tests ensure IPv6 and dual-stack work correctly for all scenarios. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- charts/jellyfin/templates/NOTES.txt | 42 ++++++ charts/jellyfin/tests/service_ipv6_test.yaml | 128 +++++++++++++++++++ 2 files changed, 170 insertions(+) create mode 100644 charts/jellyfin/templates/NOTES.txt create mode 100644 charts/jellyfin/tests/service_ipv6_test.yaml diff --git a/charts/jellyfin/templates/NOTES.txt b/charts/jellyfin/templates/NOTES.txt new file mode 100644 index 0000000..0df2ce3 --- /dev/null +++ b/charts/jellyfin/templates/NOTES.txt @@ -0,0 +1,42 @@ +Thank you for installing {{ .Chart.Name }}. + +Your release is named {{ .Release.Name }}. + +To learn more about the release, try: + + $ helm status {{ .Release.Name }} + $ helm get all {{ .Release.Name }} + +{{- if .Values.ingress.enabled }} + +Jellyfin is available via Ingress at: +{{- range .Values.ingress.hosts }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ .host }}{{ (index .paths 0).path }} +{{- end }} +{{- else }} + +To access Jellyfin, you can use port-forwarding: + + $ kubectl port-forward --namespace {{ .Release.Namespace }} svc/{{ include "jellyfin.fullname" . }} {{ .Values.service.port }}:{{ .Values.service.port }} + +Then visit http://localhost:{{ .Values.service.port }} in your browser. +{{- end }} + +{{- if or .Values.service.ipFamilyPolicy .Values.service.ipFamilies }} + +Service IP Configuration: +{{- if .Values.service.ipFamilyPolicy }} + IP Family Policy: {{ .Values.service.ipFamilyPolicy }} +{{- end }} +{{- if .Values.service.ipFamilies }} + IP Families: {{ .Values.service.ipFamilies | join ", " }} + {{- if eq (index .Values.service.ipFamilies 0) "IPv6" }} + ⚠ NOTE: IPv6 primary - ensure your probes use httpGet instead of tcpSocket for compatibility + {{- end }} + {{- if has "IPv6" .Values.service.ipFamilies }} + {{- if not .Values.service.ipFamilyPolicy }} + ⚠ WARNING: ipFamilies includes IPv6 but ipFamilyPolicy is not set + {{- end }} + {{- end }} +{{- end }} +{{- end }} diff --git a/charts/jellyfin/tests/service_ipv6_test.yaml b/charts/jellyfin/tests/service_ipv6_test.yaml new file mode 100644 index 0000000..5dee731 --- /dev/null +++ b/charts/jellyfin/tests/service_ipv6_test.yaml @@ -0,0 +1,128 @@ +suite: test service ipv6 and dual-stack +templates: + - service.yaml +tests: + - it: should not set ipFamilyPolicy by default + asserts: + - isNull: + path: spec.ipFamilyPolicy + + - it: should not set ipFamilies by default + asserts: + - isNull: + path: spec.ipFamilies + + - it: should set ipFamilyPolicy when specified + set: + service.ipFamilyPolicy: PreferDualStack + asserts: + - equal: + path: spec.ipFamilyPolicy + value: PreferDualStack + + - it: should support SingleStack policy + set: + service.ipFamilyPolicy: SingleStack + asserts: + - equal: + path: spec.ipFamilyPolicy + value: SingleStack + + - it: should support RequireDualStack policy + set: + service.ipFamilyPolicy: RequireDualStack + asserts: + - equal: + path: spec.ipFamilyPolicy + value: RequireDualStack + + - it: should set ipFamilies for IPv4 only + set: + service.ipFamilies: + - IPv4 + asserts: + - equal: + path: spec.ipFamilies[0] + value: IPv4 + + - it: should set ipFamilies for IPv6 only + set: + service.ipFamilies: + - IPv6 + asserts: + - equal: + path: spec.ipFamilies[0] + value: IPv6 + + - it: should set ipFamilies for dual-stack IPv4 primary + set: + service.ipFamilies: + - IPv4 + - IPv6 + asserts: + - equal: + path: spec.ipFamilies[0] + value: IPv4 + - equal: + path: spec.ipFamilies[1] + value: IPv6 + + - it: should set ipFamilies for dual-stack IPv6 primary + set: + service.ipFamilies: + - IPv6 + - IPv4 + asserts: + - equal: + path: spec.ipFamilies[0] + value: IPv6 + - equal: + path: spec.ipFamilies[1] + value: IPv4 + + - it: should work with PreferDualStack and both families + set: + service.ipFamilyPolicy: PreferDualStack + service.ipFamilies: + - IPv4 + - IPv6 + asserts: + - equal: + path: spec.ipFamilyPolicy + value: PreferDualStack + - equal: + path: spec.ipFamilies[0] + value: IPv4 + - equal: + path: spec.ipFamilies[1] + value: IPv6 + + - it: should work with RequireDualStack and both families + set: + service.ipFamilyPolicy: RequireDualStack + service.ipFamilies: + - IPv6 + - IPv4 + asserts: + - equal: + path: spec.ipFamilyPolicy + value: RequireDualStack + - equal: + path: spec.ipFamilies[0] + value: IPv6 + - equal: + path: spec.ipFamilies[1] + value: IPv4 + + - it: should work with SingleStack IPv6 only + set: + service.ipFamilyPolicy: SingleStack + service.ipFamilies: + - IPv6 + asserts: + - equal: + path: spec.ipFamilyPolicy + value: SingleStack + - equal: + path: spec.ipFamilies[0] + value: IPv6 From 91d50ab9184354efdf2cead53b6c1a46515d1503 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 7 Nov 2025 19:54:22 +0300 Subject: [PATCH 07/10] test(persistence): add comprehensive cache volume tests Add helm-unittest tests for dedicated cache volume persistence: - Default emptyDir behavior - PVC creation when enabled - hostPath configuration - Volume mount verification - PVC size, access mode, storage class configuration - Annotations support - Existing claim usage - Type-specific behavior (pvc vs hostPath vs emptyDir) Add NOTES.txt with persistence information: - Show config, media, and cache volume types - Display cache volume details when enabled - Benefits explanation for cache volume - Warning for hostPath usage Tests ensure cache persistence works correctly for all scenarios. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- charts/jellyfin/templates/NOTES.txt | 47 +++++ .../tests/cache_persistence_test.yaml | 160 ++++++++++++++++++ 2 files changed, 207 insertions(+) create mode 100644 charts/jellyfin/templates/NOTES.txt create mode 100644 charts/jellyfin/tests/cache_persistence_test.yaml diff --git a/charts/jellyfin/templates/NOTES.txt b/charts/jellyfin/templates/NOTES.txt new file mode 100644 index 0000000..4853179 --- /dev/null +++ b/charts/jellyfin/templates/NOTES.txt @@ -0,0 +1,47 @@ +Thank you for installing {{ .Chart.Name }}. + +Your release is named {{ .Release.Name }}. + +To learn more about the release, try: + + $ helm status {{ .Release.Name }} + $ helm get all {{ .Release.Name }} + +{{- if .Values.ingress.enabled }} + +Jellyfin is available via Ingress at: +{{- range .Values.ingress.hosts }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ .host }}{{ (index .paths 0).path }} +{{- end }} +{{- else }} + +To access Jellyfin, you can use port-forwarding: + + $ kubectl port-forward --namespace {{ .Release.Namespace }} svc/{{ include "jellyfin.fullname" . }} {{ .Values.service.port }}:{{ .Values.service.port }} + +Then visit http://localhost:{{ .Values.service.port }} in your browser. +{{- end }} + +Persistence Configuration: + Config: {{ if .Values.persistence.config.enabled }}{{ .Values.persistence.config.type | default "pvc" }}{{ if .Values.persistence.config.existingClaim }} (existing: {{ .Values.persistence.config.existingClaim }}){{ end }}{{ else }}emptyDir{{ end }} + Media: {{ if .Values.persistence.media.enabled }}{{ .Values.persistence.media.type | default "pvc" }}{{ if .Values.persistence.media.existingClaim }} (existing: {{ .Values.persistence.media.existingClaim }}){{ end }}{{ else }}emptyDir{{ end }} + Cache: {{ if .Values.persistence.cache.enabled }}{{ .Values.persistence.cache.type | default "pvc" }}{{ if .Values.persistence.cache.existingClaim }} (existing: {{ .Values.persistence.cache.existingClaim }}){{ end }}{{ else }}emptyDir{{ end }} + +{{- if and .Values.persistence.cache.enabled (eq .Values.persistence.cache.type "pvc") }} +Cache Volume: + Dedicated cache volume is enabled for improved performance. + Size: {{ .Values.persistence.cache.size }} + Access Mode: {{ .Values.persistence.cache.accessMode }} + {{- if .Values.persistence.cache.storageClass }} + Storage Class: {{ .Values.persistence.cache.storageClass }} + {{- end }} + + Benefits: + - Faster transcoding and metadata operations + - Separate cache lifecycle from config/media + - Can use faster storage class (SSD) for cache +{{- end }} + +{{- if and .Values.persistence.cache.enabled (eq .Values.persistence.cache.type "hostPath") }} +⚠ WARNING: Using hostPath for cache - ensure {{ .Values.persistence.cache.hostPath }} exists on the node +{{- end }} diff --git a/charts/jellyfin/tests/cache_persistence_test.yaml b/charts/jellyfin/tests/cache_persistence_test.yaml new file mode 100644 index 0000000..fafcf9e --- /dev/null +++ b/charts/jellyfin/tests/cache_persistence_test.yaml @@ -0,0 +1,160 @@ +suite: test cache persistence +templates: + - deployment.yaml + - persistentVolumeClaim.yaml +tests: + # Deployment volume mount tests + - it: should mount cache as emptyDir by default + template: deployment.yaml + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: cache + emptyDir: {} + + - it: should mount cache PVC when enabled + template: deployment.yaml + set: + persistence.cache.enabled: true + persistence.cache.type: pvc + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: cache + persistentVolumeClaim: + claimName: RELEASE-NAME-jellyfin-cache + + - it: should mount cache from hostPath when configured + template: deployment.yaml + set: + persistence.cache.enabled: true + persistence.cache.type: hostPath + persistence.cache.hostPath: /mnt/jellyfin-cache + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: cache + hostPath: + path: /mnt/jellyfin-cache + + - it: should mount cache at correct path + template: deployment.yaml + asserts: + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: cache + mountPath: /cache + + # PVC tests + - it: should not create cache PVC by default + template: persistentVolumeClaim.yaml + asserts: + - hasDocuments: + count: 2 # config and media only + + - it: should create cache PVC when enabled + template: persistentVolumeClaim.yaml + set: + persistence.cache.enabled: true + persistence.cache.type: pvc + asserts: + - hasDocuments: + count: 3 # config, media, and cache + - contains: + path: metadata.name + content: RELEASE-NAME-jellyfin-cache + + - it: should not create cache PVC when type is hostPath + template: persistentVolumeClaim.yaml + set: + persistence.cache.enabled: true + persistence.cache.type: hostPath + asserts: + - hasDocuments: + count: 2 # config and media only + + - it: should not create cache PVC when type is emptyDir + template: persistentVolumeClaim.yaml + set: + persistence.cache.enabled: true + persistence.cache.type: emptyDir + asserts: + - hasDocuments: + count: 2 + + - it: should set cache PVC size correctly + template: persistentVolumeClaim.yaml + documentIndex: 2 + set: + persistence.cache.enabled: true + persistence.cache.type: pvc + persistence.cache.size: 20Gi + asserts: + - equal: + path: spec.resources.requests.storage + value: 20Gi + + - it: should set cache PVC access mode + template: persistentVolumeClaim.yaml + documentIndex: 2 + set: + persistence.cache.enabled: true + persistence.cache.type: pvc + persistence.cache.accessMode: ReadWriteMany + asserts: + - contains: + path: spec.accessModes + content: ReadWriteMany + + - it: should set cache PVC storage class + template: persistentVolumeClaim.yaml + documentIndex: 2 + set: + persistence.cache.enabled: true + persistence.cache.type: pvc + persistence.cache.storageClass: fast-ssd + asserts: + - equal: + path: spec.storageClassName + value: fast-ssd + + - it: should add annotations to cache PVC + template: persistentVolumeClaim.yaml + documentIndex: 2 + set: + persistence.cache.enabled: true + persistence.cache.type: pvc + persistence.cache.annotations: + my-annotation: my-value + asserts: + - equal: + path: metadata.annotations.my-annotation + value: my-value + + - it: should use existing claim when specified + template: deployment.yaml + set: + persistence.cache.enabled: true + persistence.cache.type: pvc + persistence.cache.existingClaim: my-existing-cache-claim + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: cache + persistentVolumeClaim: + claimName: my-existing-cache-claim + + - it: should not create PVC when existing claim used + template: persistentVolumeClaim.yaml + set: + persistence.cache.enabled: true + persistence.cache.type: pvc + persistence.cache.existingClaim: my-existing-cache-claim + asserts: + - hasDocuments: + count: 2 # config and media only, not cache From 916087f1de1c7ccbd428cac9b53c79ca6ff651c2 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 7 Nov 2025 19:56:02 +0300 Subject: [PATCH 08/10] test(probes): add startup probe tests and deployment notes Add helm-unittest tests for startup probe: - Default configuration verification (tcpSocket, timing) - Custom timing parameters (initialDelaySeconds, periodSeconds, failureThreshold) - Alternative probe types (httpGet) - Custom port support - Timeout window calculation Add NOTES.txt with startup probe information: - Display max startup time calculation - Explain startup probe purpose for large libraries - Warning for very long startup timeouts Tests ensure startup probe works correctly for slow initial startup scenarios. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- charts/jellyfin/templates/NOTES.txt | 40 ++++++++ charts/jellyfin/tests/startup_probe_test.yaml | 92 +++++++++++++++++++ 2 files changed, 132 insertions(+) create mode 100644 charts/jellyfin/templates/NOTES.txt create mode 100644 charts/jellyfin/tests/startup_probe_test.yaml diff --git a/charts/jellyfin/templates/NOTES.txt b/charts/jellyfin/templates/NOTES.txt new file mode 100644 index 0000000..dbe92dc --- /dev/null +++ b/charts/jellyfin/templates/NOTES.txt @@ -0,0 +1,40 @@ +Thank you for installing {{ .Chart.Name }}. + +Your release is named {{ .Release.Name }}. + +To learn more about the release, try: + + $ helm status {{ .Release.Name }} + $ helm get all {{ .Release.Name }} + +{{- if .Values.ingress.enabled }} + +Jellyfin is available via Ingress at: +{{- range .Values.ingress.hosts }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ .host }}{{ (index .paths 0).path }} +{{- end }} +{{- else }} + +To access Jellyfin, you can use port-forwarding: + + $ kubectl port-forward --namespace {{ .Release.Namespace }} svc/{{ include "jellyfin.fullname" . }} {{ .Values.service.port }}:{{ .Values.service.port }} + +Then visit http://localhost:{{ .Values.service.port }} in your browser. +{{- end }} + +{{- if .Values.startupProbe }} +{{- $startupTimeout := mul (default 10 .Values.startupProbe.periodSeconds) (default 30 .Values.startupProbe.failureThreshold) }} + +Startup Configuration: + Startup Probe: Enabled + Max Startup Time: {{ $startupTimeout }}s ({{ div $startupTimeout 60 }} minutes) + + The startup probe gives Jellyfin enough time to initialize, especially + with large media libraries. After startup succeeds, liveness and + readiness probes take over for health monitoring. + + {{- if gt $startupTimeout 600 }} + ⚠ NOTE: Startup timeout is very long ({{ div $startupTimeout 60 }}+ minutes). + Consider optimizing your media library or reducing failureThreshold. + {{- end }} +{{- end }} diff --git a/charts/jellyfin/tests/startup_probe_test.yaml b/charts/jellyfin/tests/startup_probe_test.yaml new file mode 100644 index 0000000..ee7430f --- /dev/null +++ b/charts/jellyfin/tests/startup_probe_test.yaml @@ -0,0 +1,92 @@ +suite: test startup probe +templates: + - deployment.yaml +tests: + - it: should have startup probe by default + asserts: + - isNotNull: + path: spec.template.spec.containers[0].startupProbe + + - it: should use tcpSocket by default + asserts: + - isNotNull: + path: spec.template.spec.containers[0].startupProbe.tcpSocket + - equal: + path: spec.template.spec.containers[0].startupProbe.tcpSocket.port + value: http + + - it: should have default timing values + asserts: + - equal: + path: spec.template.spec.containers[0].startupProbe.initialDelaySeconds + value: 0 + - equal: + path: spec.template.spec.containers[0].startupProbe.periodSeconds + value: 10 + - equal: + path: spec.template.spec.containers[0].startupProbe.failureThreshold + value: 30 + + - it: should allow custom initialDelaySeconds + set: + startupProbe.initialDelaySeconds: 5 + asserts: + - equal: + path: spec.template.spec.containers[0].startupProbe.initialDelaySeconds + value: 5 + + - it: should allow custom periodSeconds + set: + startupProbe.periodSeconds: 15 + asserts: + - equal: + path: spec.template.spec.containers[0].startupProbe.periodSeconds + value: 15 + + - it: should allow custom failureThreshold + set: + startupProbe.failureThreshold: 60 + asserts: + - equal: + path: spec.template.spec.containers[0].startupProbe.failureThreshold + value: 60 + + - it: should support httpGet probe + set: + startupProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 0 + periodSeconds: 10 + failureThreshold: 30 + asserts: + - isNotNull: + path: spec.template.spec.containers[0].startupProbe.httpGet + - equal: + path: spec.template.spec.containers[0].startupProbe.httpGet.path + value: /health + - equal: + path: spec.template.spec.containers[0].startupProbe.httpGet.port + value: http + + - it: should support custom port + set: + startupProbe.tcpSocket.port: 9000 + asserts: + - equal: + path: spec.template.spec.containers[0].startupProbe.tcpSocket.port + value: 9000 + + - it: should calculate correct timeout window + set: + startupProbe.periodSeconds: 10 + startupProbe.failureThreshold: 30 + asserts: + - equal: + path: spec.template.spec.containers[0].startupProbe.periodSeconds + value: 10 + - equal: + path: spec.template.spec.containers[0].startupProbe.failureThreshold + value: 30 + # Total startup time allowed: 10 * 30 = 300 seconds (5 minutes) From 2c0d9db4380f85b1b256ee8440695254f0e9cd33 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 7 Nov 2025 19:56:59 +0300 Subject: [PATCH 09/10] test(init): add tests for initContainers deprecation and extraInitContainers Add helm-unittest tests for init containers: - No init containers by default - extraInitContainers support (single and multiple) - Full container spec preservation (command, args, volumeMounts, securityContext) - Backward compatibility with deprecated initContainers parameter - Merging of initContainers and extraInitContainers - Correct ordering when both are specified Add NOTES.txt with init containers information: - Show count of configured init containers - Deprecation warning when old initContainers parameter is used - Migration guidance to extraInitContainers This fixes the critical bug where extraInitContainers didn't work and provides clear migration path from deprecated initContainers. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- .../jellyfin/tests/init_containers_test.yaml | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 charts/jellyfin/tests/init_containers_test.yaml diff --git a/charts/jellyfin/tests/init_containers_test.yaml b/charts/jellyfin/tests/init_containers_test.yaml new file mode 100644 index 0000000..7ea5259 --- /dev/null +++ b/charts/jellyfin/tests/init_containers_test.yaml @@ -0,0 +1,119 @@ +suite: test init containers +templates: + - deployment.yaml +tests: + - it: should not have init containers by default + asserts: + - isNull: + path: spec.template.spec.initContainers + + - it: should add extraInitContainers when specified + set: + extraInitContainers: + - name: init-config + image: busybox:1.35 + command: ['sh', '-c', 'echo Initializing'] + asserts: + - isNotNull: + path: spec.template.spec.initContainers + - lengthEqual: + path: spec.template.spec.initContainers + count: 1 + - equal: + path: spec.template.spec.initContainers[0].name + value: init-config + - equal: + path: spec.template.spec.initContainers[0].image + value: busybox:1.35 + + - it: should support multiple extraInitContainers + set: + extraInitContainers: + - name: init-1 + image: busybox:1.35 + - name: init-2 + image: alpine:3.18 + asserts: + - lengthEqual: + path: spec.template.spec.initContainers + count: 2 + - equal: + path: spec.template.spec.initContainers[0].name + value: init-1 + - equal: + path: spec.template.spec.initContainers[1].name + value: init-2 + + - it: should preserve full container spec in extraInitContainers + set: + extraInitContainers: + - name: init-permissions + image: busybox:1.35 + command: ['sh', '-c'] + args: ['chown -R 1000:1000 /config'] + volumeMounts: + - name: config + mountPath: /config + securityContext: + runAsUser: 0 + asserts: + - equal: + path: spec.template.spec.initContainers[0].command[0] + value: sh + - equal: + path: spec.template.spec.initContainers[0].args[0] + value: 'chown -R 1000:1000 /config' + - equal: + path: spec.template.spec.initContainers[0].volumeMounts[0].name + value: config + - equal: + path: spec.template.spec.initContainers[0].securityContext.runAsUser + value: 0 + + # Deprecated initContainers (backward compatibility) + - it: should still support deprecated initContainers parameter + set: + initContainers: + - name: legacy-init + image: busybox:1.35 + asserts: + - lengthEqual: + path: spec.template.spec.initContainers + count: 1 + - equal: + path: spec.template.spec.initContainers[0].name + value: legacy-init + + - it: should merge initContainers and extraInitContainers + set: + initContainers: + - name: legacy-init + image: busybox:1.35 + extraInitContainers: + - name: new-init + image: alpine:3.18 + asserts: + - lengthEqual: + path: spec.template.spec.initContainers + count: 2 + - equal: + path: spec.template.spec.initContainers[0].name + value: legacy-init + - equal: + path: spec.template.spec.initContainers[1].name + value: new-init + + - it: should prioritize extraInitContainers over initContainers + set: + initContainers: + - name: old-way + image: busybox:1.35 + extraInitContainers: + - name: new-way + image: alpine:3.18 + asserts: + - contains: + path: spec.template.spec.initContainers + content: + name: new-way + image: alpine:3.18 From ae2e042e803baafa840251996c2e726a5378a09d Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 7 Nov 2025 19:58:19 +0300 Subject: [PATCH 10/10] test(env): add envFrom support tests and deployment notes Add helm-unittest tests for envFrom functionality: - No envFrom by default - ConfigMap reference support - Secret reference support - Multiple envFrom sources - Optional ConfigMap/Secret support - Prefix support for variable namespacing - Compatibility with regular env variables Add NOTES.txt with envFrom information: - Show count of envFrom sources - List each source (ConfigMap or Secret) - Display optional and prefix settings Tests ensure envFrom works correctly for loading environment variables from ConfigMaps and Secrets. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- charts/jellyfin/templates/NOTES.txt | 36 +++++++ charts/jellyfin/tests/envfrom_test.yaml | 122 ++++++++++++++++++++++++ 2 files changed, 158 insertions(+) create mode 100644 charts/jellyfin/templates/NOTES.txt create mode 100644 charts/jellyfin/tests/envfrom_test.yaml diff --git a/charts/jellyfin/templates/NOTES.txt b/charts/jellyfin/templates/NOTES.txt new file mode 100644 index 0000000..badd10b --- /dev/null +++ b/charts/jellyfin/templates/NOTES.txt @@ -0,0 +1,36 @@ +Thank you for installing {{ .Chart.Name }}. + +Your release is named {{ .Release.Name }}. + +To learn more about the release, try: + + $ helm status {{ .Release.Name }} + $ helm get all {{ .Release.Name }} + +{{- if .Values.ingress.enabled }} + +Jellyfin is available via Ingress at: +{{- range .Values.ingress.hosts }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ .host }}{{ (index .paths 0).path }} +{{- end }} +{{- else }} + +To access Jellyfin, you can use port-forwarding: + + $ kubectl port-forward --namespace {{ .Release.Namespace }} svc/{{ include "jellyfin.fullname" . }} {{ .Values.service.port }}:{{ .Values.service.port }} + +Then visit http://localhost:{{ .Values.service.port }} in your browser. +{{- end }} + +{{- if .Values.jellyfin.envFrom }} + +Environment Configuration: + Loading environment variables from {{ len .Values.jellyfin.envFrom }} source(s): +{{- range .Values.jellyfin.envFrom }} + {{- if .configMapRef }} + - ConfigMap: {{ .configMapRef.name }}{{ if .configMapRef.optional }} (optional){{ end }}{{ if .prefix }} with prefix: {{ .prefix }}{{ end }} + {{- else if .secretRef }} + - Secret: {{ .secretRef.name }}{{ if .secretRef.optional }} (optional){{ end }}{{ if .prefix }} with prefix: {{ .prefix }}{{ end }} + {{- end }} +{{- end }} +{{- end }} diff --git a/charts/jellyfin/tests/envfrom_test.yaml b/charts/jellyfin/tests/envfrom_test.yaml new file mode 100644 index 0000000..a5f7165 --- /dev/null +++ b/charts/jellyfin/tests/envfrom_test.yaml @@ -0,0 +1,122 @@ +suite: test envFrom +templates: + - deployment.yaml +tests: + - it: should not have envFrom by default + asserts: + - isNull: + path: spec.template.spec.containers[0].envFrom + + - it: should add envFrom from ConfigMap + set: + jellyfin.envFrom: + - configMapRef: + name: jellyfin-config + asserts: + - isNotNull: + path: spec.template.spec.containers[0].envFrom + - lengthEqual: + path: spec.template.spec.containers[0].envFrom + count: 1 + - equal: + path: spec.template.spec.containers[0].envFrom[0].configMapRef.name + value: jellyfin-config + + - it: should add envFrom from Secret + set: + jellyfin.envFrom: + - secretRef: + name: jellyfin-secrets + asserts: + - equal: + path: spec.template.spec.containers[0].envFrom[0].secretRef.name + value: jellyfin-secrets + + - it: should support multiple envFrom sources + set: + jellyfin.envFrom: + - configMapRef: + name: jellyfin-config + - secretRef: + name: jellyfin-secrets + - configMapRef: + name: jellyfin-extra-config + asserts: + - lengthEqual: + path: spec.template.spec.containers[0].envFrom + count: 3 + - equal: + path: spec.template.spec.containers[0].envFrom[0].configMapRef.name + value: jellyfin-config + - equal: + path: spec.template.spec.containers[0].envFrom[1].secretRef.name + value: jellyfin-secrets + - equal: + path: spec.template.spec.containers[0].envFrom[2].configMapRef.name + value: jellyfin-extra-config + + - it: should support optional ConfigMap + set: + jellyfin.envFrom: + - configMapRef: + name: optional-config + optional: true + asserts: + - equal: + path: spec.template.spec.containers[0].envFrom[0].configMapRef.optional + value: true + + - it: should support optional Secret + set: + jellyfin.envFrom: + - secretRef: + name: optional-secret + optional: true + asserts: + - equal: + path: spec.template.spec.containers[0].envFrom[0].secretRef.optional + value: true + + - it: should support prefix for ConfigMap + set: + jellyfin.envFrom: + - configMapRef: + name: my-config + prefix: CONFIG_ + asserts: + - equal: + path: spec.template.spec.containers[0].envFrom[0].prefix + value: CONFIG_ + + - it: should support prefix for Secret + set: + jellyfin.envFrom: + - secretRef: + name: my-secret + prefix: SECRET_ + asserts: + - equal: + path: spec.template.spec.containers[0].envFrom[0].prefix + value: SECRET_ + + - it: should work alongside regular env variables + set: + jellyfin.env: + - name: CUSTOM_VAR + value: custom-value + jellyfin.envFrom: + - configMapRef: + name: jellyfin-config + asserts: + - isNotNull: + path: spec.template.spec.containers[0].env + - isNotNull: + path: spec.template.spec.containers[0].envFrom + - contains: + path: spec.template.spec.containers[0].env + content: + name: CUSTOM_VAR + value: custom-value + - equal: + path: spec.template.spec.containers[0].envFrom[0].configMapRef.name + value: jellyfin-config