From f1ae87fba9de1f40f62e516997e5c5396f587f4e Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Wed, 5 Nov 2025 22:36:09 +0300 Subject: [PATCH 01/24] feat(gateway-api): add HTTPRoute support Add support for Kubernetes Gateway API HTTPRoute resource as a modern alternative to Ingress. The implementation follows the same pattern as the existing Ingress template. Changes: - Add HTTPRoute template with configurable parentRefs, hostnames, and rules - Add httpRoute section to values.yaml with generic examples - Update Chart version from 2.5.0 to 2.6.0 - Add Artifact Hub changelog annotation - Add HTTPRoute documentation to README Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- charts/jellyfin/Chart.yaml | 6 ++++- charts/jellyfin/README.md | 33 +++++++++++++++++++++++- charts/jellyfin/README.md.gotmpl | 27 +++++++++++++++++++ charts/jellyfin/templates/httproute.yaml | 33 ++++++++++++++++++++++++ charts/jellyfin/values.yaml | 19 ++++++++++++++ 5 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 charts/jellyfin/templates/httproute.yaml diff --git a/charts/jellyfin/Chart.yaml b/charts/jellyfin/Chart.yaml index 96a351b..8c9328f 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 support for Gateway API HTTPRoute resource diff --git a/charts/jellyfin/README.md b/charts/jellyfin/README.md index 10952b6..ff6e753 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.6.0](https://img.shields.io/badge/Version-2.6.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) A Helm chart for Jellyfin Media Server @@ -60,6 +60,10 @@ helm install my-jellyfin jellyfin/jellyfin -f values.yaml | extraContainers | list | `[]` | additional sidecar containers to run inside the pod. | | extraInitContainers | list | `[]` | additional init containers to run inside the pod. | | fullnameOverride | string | `""` | Override the default full name of the chart. | +| httpRoute | object | `{"annotations":{},"enabled":false,"hostnames":[],"parentRefs":[],"rules":[{"matches":[{"path":{"type":"PathPrefix","value":"/"}}]}]}` | HTTPRoute configuration for Gateway API. See: https://gateway-api.sigs.k8s.io/ | +| httpRoute.hostnames | list | `[]` | Hostnames to match for this HTTPRoute | +| httpRoute.parentRefs | list | `[]` | Gateway references to attach this HTTPRoute to | +| httpRoute.rules | list | `[{"matches":[{"path":{"type":"PathPrefix","value":"/"}}]}]` | Rules for routing traffic | | image.pullPolicy | string | `"IfNotPresent"` | Image pull policy (Always, IfNotPresent, or Never). | | image.repository | string | `"docker.io/jellyfin/jellyfin"` | Container image repository for Jellyfin. | | image.tag | string | `""` | Jellyfin container image tag. Leave empty to automatically use the Chart's app version. | @@ -134,6 +138,33 @@ helm install my-jellyfin jellyfin/jellyfin -f values.yaml ---------------------------------------------- Autogenerated from chart metadata using [helm-docs v1.14.2](https://github.com/norwoodj/helm-docs/releases/v1.14.2) +## Gateway API HTTPRoute + +This chart supports the Kubernetes Gateway API HTTPRoute resource as a modern alternative to Ingress. + +To use HTTPRoute, you need to have Gateway API CRDs installed in your cluster and a Gateway resource configured. + +Example configuration: + +```yaml +httpRoute: + enabled: true + annotations: {} + parentRefs: + - name: my-gateway + namespace: gateway-system + sectionName: https + hostnames: + - jellyfin.example.com + rules: + - matches: + - path: + type: PathPrefix + value: / +``` + +For more information about Gateway API, see: + ## Hardware acceleration Out of the box the pod does not have the necessary permissions to enable hardware acceleration (HWA) in Jellyfin. diff --git a/charts/jellyfin/README.md.gotmpl b/charts/jellyfin/README.md.gotmpl index ee08a35..bbcd888 100644 --- a/charts/jellyfin/README.md.gotmpl +++ b/charts/jellyfin/README.md.gotmpl @@ -59,6 +59,33 @@ helm install my-jellyfin jellyfin/jellyfin -f values.yaml {{ template "helm-docs.versionFooter" . }} +## Gateway API HTTPRoute + +This chart supports the Kubernetes Gateway API HTTPRoute resource as a modern alternative to Ingress. + +To use HTTPRoute, you need to have Gateway API CRDs installed in your cluster and a Gateway resource configured. + +Example configuration: + +```yaml +httpRoute: + enabled: true + annotations: {} + parentRefs: + - name: my-gateway + namespace: gateway-system + sectionName: https + hostnames: + - jellyfin.example.com + rules: + - matches: + - path: + type: PathPrefix + value: / +``` + +For more information about Gateway API, see: + ## Hardware acceleration Out of the box the pod does not have the necessary permissions to enable hardware acceleration (HWA) in Jellyfin. diff --git a/charts/jellyfin/templates/httproute.yaml b/charts/jellyfin/templates/httproute.yaml new file mode 100644 index 0000000..c701451 --- /dev/null +++ b/charts/jellyfin/templates/httproute.yaml @@ -0,0 +1,33 @@ +{{- if .Values.httpRoute.enabled }} +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ include "jellyfin.fullname" . }} + labels: + {{- include "jellyfin.labels" . | nindent 4 }} + {{- with .Values.httpRoute.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- with .Values.httpRoute.parentRefs }} + parentRefs: + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.httpRoute.hostnames }} + hostnames: + {{- toYaml . | nindent 4 }} + {{- end }} + rules: + {{- range .Values.httpRoute.rules }} + - matches: + {{- range .matches }} + - path: + type: {{ .path.type }} + value: {{ .path.value }} + {{- end }} + backendRefs: + - name: {{ include "jellyfin.fullname" $ }} + port: {{ $.Values.service.port }} + {{- end }} +{{- end }} diff --git a/charts/jellyfin/values.yaml b/charts/jellyfin/values.yaml index 068c0ef..0ae375c 100644 --- a/charts/jellyfin/values.yaml +++ b/charts/jellyfin/values.yaml @@ -124,6 +124,25 @@ ingress: # hosts: # - chart-example.local +# -- HTTPRoute configuration for Gateway API. See: https://gateway-api.sigs.k8s.io/ +httpRoute: + enabled: false + annotations: {} + # -- Gateway references to attach this HTTPRoute to + parentRefs: [] + # - name: my-gateway + # namespace: gateway-system + # sectionName: https + # -- Hostnames to match for this HTTPRoute + hostnames: [] + # - jellyfin.example.com + # -- Rules for routing traffic + rules: + - matches: + - path: + type: PathPrefix + value: / + # -- Resource requests and limits for the Jellyfin container. resources: {} # limits: From 250a126357361c208a0eab864fae2fa6b15b17d5 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Wed, 5 Nov 2025 22:56:01 +0300 Subject: [PATCH 02/24] 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 03/24] 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 4a45dc998a98113b06b1db0bd82eb4d14e8790c7 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Wed, 5 Nov 2025 23:32:38 +0300 Subject: [PATCH 04/24] docs(troubleshooting): add inotify limits documentation and workaround Add comprehensive troubleshooting documentation for inotify instance limit errors that cause Jellyfin crashes in environments with many containers. Changes: - Add Troubleshooting section to README with inotify limits explanation - Document proper solution: increase kernel limits via sysctl - Provide workaround for managed clusters: DOTNET_USE_POLLING_FILE_WATCHER - Add commented example in values.yaml for easy copy-paste - Update Chart version from 2.5.0 to 2.5.1 - Add Artifact Hub changelog annotation The proper solution is to increase inotify limits on Kubernetes nodes: ```bash sysctl -w fs.inotify.max_user_instances=512 ``` For managed Kubernetes where node access is restricted, users can use the polling workaround (less efficient but functional): ```yaml jellyfin: env: - name: DOTNET_USE_POLLING_FILE_WATCHER value: "1" ``` This addresses the crash issue mentioned in #64. Fixes: #64 Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- charts/jellyfin/Chart.yaml | 6 ++++- charts/jellyfin/README.md | 45 ++++++++++++++++++++++++++++++-- charts/jellyfin/README.md.gotmpl | 41 +++++++++++++++++++++++++++++ charts/jellyfin/values.yaml | 3 +++ 4 files changed, 92 insertions(+), 3 deletions(-) diff --git a/charts/jellyfin/Chart.yaml b/charts/jellyfin/Chart.yaml index 96a351b..47ccd67 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 troubleshooting documentation for inotify instance limits with workaround example diff --git a/charts/jellyfin/README.md b/charts/jellyfin/README.md index 10952b6..bbb9875 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 @@ -68,7 +68,7 @@ helm install my-jellyfin jellyfin/jellyfin -f values.yaml | jellyfin.args | list | `[]` | Additional arguments for the entrypoint command. | | 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. | +| jellyfin.env | list | `[]` | Additional environment variables for the container. Example: Workaround for inotify limits (see Troubleshooting section in README) | | livenessProbe | object | `{"initialDelaySeconds":10,"tcpSocket":{"port":"http"}}` | Configure liveness probe for Jellyfin. | | 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 | @@ -160,3 +160,44 @@ extraVolumeMounts: - name: hwa mountPath: /dev/dri ``` + +## Troubleshooting + +### inotify Instance Limit Reached + +**Problem:** Jellyfin crashes with error: +``` +System.IO.IOException: The configured user limit (128) on the number of inotify instances has been reached +``` + +**Root cause:** The Linux kernel has a limit on inotify instances (file system watchers) per user. Jellyfin uses inotify to monitor media libraries for changes. + +**Proper solution (recommended):** + +Increase the inotify limit on the Kubernetes nodes: + +```bash +# Temporary (until reboot) +sysctl -w fs.inotify.max_user_instances=512 + +# Permanent +echo "fs.inotify.max_user_instances=512" >> /etc/sysctl.conf +sysctl -p +``` + +Recommended values: +- `fs.inotify.max_user_instances`: 512 or higher +- `fs.inotify.max_user_watches`: 524288 or higher (if you have large media libraries) + +**Workaround (if you cannot modify host settings):** + +If you're running on a managed Kubernetes cluster where you cannot modify node-level settings, you can force Jellyfin to use polling instead of inotify. **Note: This is less efficient and may increase CPU usage and delay change detection.** + +```yaml +jellyfin: + env: + - name: DOTNET_USE_POLLING_FILE_WATCHER + value: "1" +``` + +This workaround disables inotify file watching in favor of periodic polling, which doesn't require inotify instances but is less efficient. diff --git a/charts/jellyfin/README.md.gotmpl b/charts/jellyfin/README.md.gotmpl index ee08a35..d0d5f30 100644 --- a/charts/jellyfin/README.md.gotmpl +++ b/charts/jellyfin/README.md.gotmpl @@ -85,3 +85,44 @@ extraVolumeMounts: - name: hwa mountPath: /dev/dri ``` + +## Troubleshooting + +### inotify Instance Limit Reached + +**Problem:** Jellyfin crashes with error: +``` +System.IO.IOException: The configured user limit (128) on the number of inotify instances has been reached +``` + +**Root cause:** The Linux kernel has a limit on inotify instances (file system watchers) per user. Jellyfin uses inotify to monitor media libraries for changes. + +**Proper solution (recommended):** + +Increase the inotify limit on the Kubernetes nodes: + +```bash +# Temporary (until reboot) +sysctl -w fs.inotify.max_user_instances=512 + +# Permanent +echo "fs.inotify.max_user_instances=512" >> /etc/sysctl.conf +sysctl -p +``` + +Recommended values: +- `fs.inotify.max_user_instances`: 512 or higher +- `fs.inotify.max_user_watches`: 524288 or higher (if you have large media libraries) + +**Workaround (if you cannot modify host settings):** + +If you're running on a managed Kubernetes cluster where you cannot modify node-level settings, you can force Jellyfin to use polling instead of inotify. **Note: This is less efficient and may increase CPU usage and delay change detection.** + +```yaml +jellyfin: + env: + - name: DOTNET_USE_POLLING_FILE_WATCHER + value: "1" +``` + +This workaround disables inotify file watching in favor of periodic polling, which doesn't require inotify instances but is less efficient. diff --git a/charts/jellyfin/values.yaml b/charts/jellyfin/values.yaml index 068c0ef..103122f 100644 --- a/charts/jellyfin/values.yaml +++ b/charts/jellyfin/values.yaml @@ -192,7 +192,10 @@ jellyfin: # -- Additional arguments for the entrypoint command. args: [] # -- Additional environment variables for the container. + # Example: Workaround for inotify limits (see Troubleshooting section in README) env: [] + # - name: DOTNET_USE_POLLING_FILE_WATCHER + # value: "1" persistence: config: From df3e66c5f9fe803e9663a9621f932ef22eb2d72d Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Thu, 6 Nov 2025 00:04:39 +0300 Subject: [PATCH 05/24] 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 06/24] 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 07/24] 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 24fa95525ab3f9424c8efedbca276dca4f744bd6 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 7 Nov 2025 19:27:38 +0300 Subject: [PATCH 08/24] feat(templates): add NetworkPolicy template with ingress/egress rules Add comprehensive NetworkPolicy support for Jellyfin pod with: - Ingress rules: configurable external access or namespace/pod selectors - Egress rules: DNS resolution, metadata providers, custom rules - Automatic Prometheus integration when metrics are enabled - DLNA/hostNetwork detection with fail-safe error message - Support for restrictedEgress mode for high-security environments The NetworkPolicy is disabled by default to maintain backward compatibility. When enabled, it provides fine-grained control over network traffic: - Who can access Jellyfin (ingress) - What external connections Jellyfin can make (egress) NetworkPolicy requires CNI plugin support (Calico, Cilium, etc.) and cannot be used with hostNetwork mode (DLNA). The template includes validation to prevent misconfiguration. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- charts/jellyfin/templates/networkpolicy.yaml | 109 +++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 charts/jellyfin/templates/networkpolicy.yaml diff --git a/charts/jellyfin/templates/networkpolicy.yaml b/charts/jellyfin/templates/networkpolicy.yaml new file mode 100644 index 0000000..5af3206 --- /dev/null +++ b/charts/jellyfin/templates/networkpolicy.yaml @@ -0,0 +1,109 @@ +{{- if .Values.networkPolicy.enabled }} +{{- if or .Values.jellyfin.enableDLNA .Values.podPrivileges.hostNetwork }} +{{- fail "NetworkPolicy cannot be enabled when hostNetwork is enabled (DLNA mode or podPrivileges.hostNetwork). NetworkPolicy does not apply to pods using hostNetwork. Please set networkPolicy.enabled=false or disable hostNetwork." }} +{{- end }} +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ include "jellyfin.fullname" . }} + labels: + {{- include "jellyfin.labels" . | nindent 4 }} +spec: + podSelector: + matchLabels: + {{- include "jellyfin.selectorLabels" . | nindent 6 }} + policyTypes: + {{- toYaml .Values.networkPolicy.policyTypes | nindent 4 }} + + {{- if has "Ingress" .Values.networkPolicy.policyTypes }} + ingress: + # Allow traffic to Jellyfin HTTP port + - ports: + - protocol: TCP + port: http + {{- if not .Values.networkPolicy.ingress.allowExternal }} + from: + {{- if or .Values.networkPolicy.ingress.podSelector .Values.networkPolicy.ingress.namespaceSelector }} + - podSelector: + {{- toYaml .Values.networkPolicy.ingress.podSelector | nindent 12 }} + {{- if .Values.networkPolicy.ingress.namespaceSelector }} + namespaceSelector: + {{- toYaml .Values.networkPolicy.ingress.namespaceSelector | nindent 12 }} + {{- end }} + {{- end }} + {{- end }} + + {{- if and .Values.metrics.enabled .Values.metrics.serviceMonitor.enabled }} + # Allow Prometheus metrics scraping + - ports: + - protocol: TCP + port: {{ .Values.metrics.serviceMonitor.port }} + from: + - podSelector: + {{- toYaml .Values.networkPolicy.metrics.podSelector | nindent 12 }} + {{- if .Values.networkPolicy.metrics.namespace }} + namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: {{ .Values.networkPolicy.metrics.namespace }} + {{- end }} + {{- end }} + + {{- with .Values.networkPolicy.ingress.customRules }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- end }} + + {{- if has "Egress" .Values.networkPolicy.policyTypes }} + egress: + {{- if .Values.networkPolicy.egress.allowDNS }} + # Allow DNS resolution (required for Jellyfin operation) + - to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: {{ .Values.networkPolicy.egress.dnsNamespace }} + podSelector: + matchLabels: + {{- toYaml .Values.networkPolicy.egress.dnsPodSelector | nindent 14 }} + ports: + - protocol: UDP + port: 53 + - protocol: TCP + port: 53 + {{- end }} + + {{- if .Values.networkPolicy.egress.allowAllEgress }} + # Allow all egress traffic (metadata providers, subtitles, images, etc.) + - to: + - ipBlock: + cidr: 0.0.0.0/0 + {{- else }} + {{- if .Values.networkPolicy.egress.restrictedEgress.allowMetadata }} + # Allow HTTPS for metadata providers (TMDB, TheTVDB, OpenSubtitles, etc.) + - to: + - ipBlock: + cidr: 0.0.0.0/0 + ports: + - protocol: TCP + port: 443 + {{- end }} + + {{- if .Values.networkPolicy.egress.restrictedEgress.allowInCluster }} + # Allow pod-to-pod communication within the cluster + - to: + - podSelector: {} + {{- end }} + + {{- range .Values.networkPolicy.egress.restrictedEgress.allowedCIDRs }} + # Custom CIDR egress rule + - to: + - ipBlock: + cidr: {{ . }} + {{- end }} + {{- end }} + + {{- with .Values.networkPolicy.egress.customRules }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- end }} +{{- end }} From d2051f1dfecce3aa85153f741eb7d131e226bd4a Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 7 Nov 2025 19:27:55 +0300 Subject: [PATCH 09/24] feat(values): add networkPolicy configuration section Add networkPolicy section to values.yaml with comprehensive configuration options: - enabled: boolean flag to enable/disable NetworkPolicy (default: false) - policyTypes: configure Ingress and/or Egress policies - ingress: control which pods/namespaces can access Jellyfin - allowExternal: permit access from any namespace (default: true) - podSelector/namespaceSelector: restrict access to specific pods/namespaces - customRules: advanced ingress rules for complex scenarios - egress: control outbound connections from Jellyfin - allowDNS: permit DNS resolution (default: true, required) - allowAllEgress: permit all internet access (default: true) - restrictedEgress: high-security mode with HTTPS-only metadata access - customRules: advanced egress rules - metrics: automatic Prometheus integration configuration All parameters include detailed documentation explaining: - Purpose and usage - Default values and recommended settings - Examples for common scenarios - Security implications The configuration supports use cases from simple deployments to zero-trust environments while maintaining sensible defaults. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- charts/jellyfin/values.yaml | 149 +++++++++++++++++++++++++++++++++++- 1 file changed, 148 insertions(+), 1 deletion(-) diff --git a/charts/jellyfin/values.yaml b/charts/jellyfin/values.yaml index 068c0ef..2dc9aac 100644 --- a/charts/jellyfin/values.yaml +++ b/charts/jellyfin/values.yaml @@ -256,6 +256,153 @@ metrics: # -- additional init containers to run inside the pod. extraInitContainers: [] - + # -- additional sidecar containers to run inside the pod. extraContainers: [] + +# -- Network Policy configuration for network isolation and security. +# Requires a CNI plugin that supports NetworkPolicy (Calico, Cilium, Weave, etc.). +# WARNING: NetworkPolicy cannot be enabled when hostNetwork is used (DLNA mode). +# The chart will fail with an error if both are enabled simultaneously. +networkPolicy: + # -- Enable NetworkPolicy for the Jellyfin pod. + # By default, this is disabled to maintain backward compatibility. + # When enabled, you can control which pods can access Jellyfin (ingress) + # and what external connections Jellyfin can make (egress). + enabled: false + + # -- Policy types to enforce. Both ingress and egress policies can be enabled. + # See: https://kubernetes.io/docs/concepts/services-networking/network-policies/#policy-types + policyTypes: + - Ingress + - Egress + + # -- Ingress rules configuration - controls which pods/namespaces can access Jellyfin. + ingress: + # -- Allow external access from any namespace and any pod. + # When true, any pod in the cluster can access Jellyfin (default behavior). + # When false, only pods matching podSelector/namespaceSelector can access. + # Set to false for production environments to restrict access. + allowExternal: true + + # -- Custom pod selector for allowed ingress traffic. + # Only used when allowExternal is false. + # Allows you to specify which pods can access Jellyfin based on labels. + # Example - only allow pods with specific label: + # podSelector: + # matchLabels: + # jellyfin-client: "true" + podSelector: {} + + # -- Namespace selector to allow cross-namespace ingress. + # Only used when allowExternal is false. + # Allows you to specify which namespaces can access Jellyfin. + # Example - only allow from ingress-nginx namespace: + # namespaceSelector: + # matchLabels: + # name: ingress-nginx + namespaceSelector: {} + + # -- Additional custom ingress rules. + # Allows for complex scenarios not covered by the standard template. + # These rules are added as-is to the NetworkPolicy ingress section. + # Example - allow from monitoring namespace: + # customRules: + # - from: + # - namespaceSelector: + # matchLabels: + # name: monitoring + # ports: + # - protocol: TCP + # port: 8096 + customRules: [] + + # -- Egress rules configuration - controls what external connections Jellyfin can make. + egress: + # -- Allow DNS resolution (required for Jellyfin to function). + # This adds an egress rule for kube-system namespace with kube-dns pods. + # DNS is required for resolving metadata provider domains and subtitle services. + # It is highly recommended to keep this enabled. + allowDNS: true + + # -- DNS namespace where DNS service is running. + # Usually "kube-system" but can differ in some Kubernetes distributions. + dnsNamespace: kube-system + + # -- DNS pod selector labels. + # Default selector is for kube-dns, but CoreDNS or other DNS providers + # may use different labels. Adjust if needed. + # Common alternatives: + # k8s-app: kube-dns (default) + # k8s-app: coredns + # app.kubernetes.io/name: coredns + dnsPodSelector: + k8s-app: kube-dns + + # -- Allow all egress traffic (internet access for metadata, subtitles, images). + # When true, Jellyfin can connect to any external destination (0.0.0.0/0). + # This is the recommended default as Jellyfin needs internet access for: + # - Downloading movie/TV show metadata (TMDB, TheTVDB, OMDb) + # - Fetching poster images, fanart, and other artwork + # - Downloading subtitles (OpenSubtitles) + # - Updating plugins + # When false, you must configure restrictedEgress or customRules. + allowAllEgress: true + + # -- Restricted egress mode for security-conscious deployments. + # Only used when allowAllEgress is false. + # Provides fine-grained control over outbound connections. + restrictedEgress: + # -- Allow HTTPS (443/TCP) for metadata providers. + # Most metadata providers (TMDB, TheTVDB, OpenSubtitles, Fanart.tv) + # use HTTPS, so this covers the majority of use cases. + # This allows connections to any IP on port 443. + allowMetadata: true + + # -- Allow communication within the cluster (pod-to-pod). + # Useful if Jellyfin needs to connect to other services in the cluster. + # This allows connections to any pod in any namespace. + allowInCluster: true + + # -- Additional IP CIDR blocks to allow egress. + # Useful for allowing specific IP ranges for metadata providers + # or other external services. + # Example - allow entire internet except private networks: + # allowedCIDRs: + # - 0.0.0.0/0 + # Example - allow specific metadata provider IP ranges: + # allowedCIDRs: + # - 13.224.0.0/14 # CloudFront (used by many CDNs) + allowedCIDRs: [] + + # -- Additional custom egress rules. + # Allows for complex scenarios not covered by the standard template. + # These rules are added as-is to the NetworkPolicy egress section. + # Example - allow connections to specific database: + # customRules: + # - to: + # - podSelector: + # matchLabels: + # app: postgresql + # ports: + # - protocol: TCP + # port: 5432 + customRules: [] + + # -- Prometheus metrics scraping configuration. + # Automatically allows ingress from Prometheus when metrics.serviceMonitor.enabled is true. + # This ensures Prometheus can scrape metrics without additional configuration. + metrics: + # -- Namespace where Prometheus is running. + # Leave empty to use the same namespace as Jellyfin. + # Set to the monitoring namespace if Prometheus is in a different namespace. + # Example: "monitoring" or "prometheus" + namespace: "" + + # -- Pod selector for Prometheus pods. + # These labels must match your Prometheus deployment. + # The chart will automatically add an ingress rule for pods matching these labels. + # Default selector works with prometheus-operator and kube-prometheus-stack. + # Adjust if your Prometheus uses different labels. + podSelector: + app.kubernetes.io/name: prometheus From 32494dd1fdf787f77a83982a58038b4ae1b28b0c Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 7 Nov 2025 19:28:13 +0300 Subject: [PATCH 10/24] feat(templates): add NOTES.txt with deployment status information Add NOTES.txt template that displays helpful information after deployment: - How to access Jellyfin (via Ingress or port-forward) - NetworkPolicy status and configuration summary when enabled - Ingress policy details (allowExternal, selectors, Prometheus) - Egress policy details (DNS, internet access, restrictions) - Warnings for misconfigurations (no selectors, DNS disabled) - Compatibility note about CNI requirements - Warning if NetworkPolicy + hostNetwork are both enabled The NOTES provide immediate visibility into the deployment configuration and help users quickly identify and troubleshoot network access issues. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- charts/jellyfin/templates/NOTES.txt | 88 +++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 charts/jellyfin/templates/NOTES.txt diff --git a/charts/jellyfin/templates/NOTES.txt b/charts/jellyfin/templates/NOTES.txt new file mode 100644 index 0000000..c07b044 --- /dev/null +++ b/charts/jellyfin/templates/NOTES.txt @@ -0,0 +1,88 @@ +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.networkPolicy.enabled }} + +Network Policy is ENABLED: +{{- if or .Values.jellyfin.enableDLNA .Values.podPrivileges.hostNetwork }} + WARNING: NetworkPolicy is incompatible with hostNetwork (DLNA mode). + The deployment will FAIL. Please disable networkPolicy.enabled or hostNetwork. +{{- else }} + + Ingress Policy: + {{- if .Values.networkPolicy.ingress.allowExternal }} + - ✓ Allowing connections from ANY pod in ANY namespace + {{- else }} + {{- if or .Values.networkPolicy.ingress.podSelector .Values.networkPolicy.ingress.namespaceSelector }} + - ✓ Allowing connections ONLY from pods matching: + {{- if .Values.networkPolicy.ingress.podSelector }} + Pod Labels: {{ .Values.networkPolicy.ingress.podSelector | toYaml | nindent 8 }} + {{- end }} + {{- if .Values.networkPolicy.ingress.namespaceSelector }} + Namespace Labels: {{ .Values.networkPolicy.ingress.namespaceSelector | toYaml | nindent 8 }} + {{- end }} + {{- else }} + - ⚠ WARNING: allowExternal=false but no selectors defined - NO pods can connect! + {{- end }} + {{- end }} + {{- if and .Values.metrics.enabled .Values.metrics.serviceMonitor.enabled }} + - ✓ Allowing Prometheus metrics scraping from {{ .Values.networkPolicy.metrics.podSelector | toYaml }} + {{- end }} + {{- if .Values.networkPolicy.ingress.customRules }} + - ✓ Custom ingress rules applied ({{ len .Values.networkPolicy.ingress.customRules }} rules) + {{- end }} + + Egress Policy: + {{- if .Values.networkPolicy.egress.allowDNS }} + - ✓ DNS resolution allowed ({{ .Values.networkPolicy.egress.dnsNamespace }}/{{ .Values.networkPolicy.egress.dnsPodSelector | toYaml }}) + {{- else }} + - ⚠ WARNING: DNS is DISABLED - Jellyfin will NOT function properly! + {{- end }} + {{- if .Values.networkPolicy.egress.allowAllEgress }} + - ✓ All egress allowed (metadata, subtitles, images) + {{- else }} + {{- if .Values.networkPolicy.egress.restrictedEgress.allowMetadata }} + - ✓ HTTPS/443 allowed (metadata providers) + {{- end }} + {{- if .Values.networkPolicy.egress.restrictedEgress.allowInCluster }} + - ✓ In-cluster communication allowed + {{- end }} + {{- if .Values.networkPolicy.egress.restrictedEgress.allowedCIDRs }} + - ✓ Custom CIDRs allowed: {{ .Values.networkPolicy.egress.restrictedEgress.allowedCIDRs | join ", " }} + {{- end }} + {{- if not (or .Values.networkPolicy.egress.restrictedEgress.allowMetadata .Values.networkPolicy.egress.restrictedEgress.allowInCluster .Values.networkPolicy.egress.restrictedEgress.allowedCIDRs) }} + - ⚠ WARNING: No egress rules defined - Jellyfin cannot download metadata! + {{- end }} + {{- end }} + {{- if .Values.networkPolicy.egress.customRules }} + - ✓ Custom egress rules applied ({{ len .Values.networkPolicy.egress.customRules }} rules) + {{- end }} + + Note: NetworkPolicy requires a CNI plugin with NetworkPolicy support (Calico, Cilium, etc.) +{{- end }} +{{- else }} + +Network Policy is DISABLED (default). +To enable network isolation, set networkPolicy.enabled=true in your values. +{{- end }} From aaa9c91a8e644d5905e78b004cfbd3d72db4d437 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 7 Nov 2025 19:28:38 +0300 Subject: [PATCH 11/24] test(networkpolicy): add comprehensive helm-unittest test suite Add 20+ unit tests for NetworkPolicy template covering: Creation conditions: - Not created by default (enabled: false) - Created when enabled: true - Fails with clear error when hostNetwork + NetworkPolicy enabled - Fails when DLNA + NetworkPolicy enabled Ingress rules: - Default allowExternal behavior (no 'from' restriction) - Restricted access with podSelector/namespaceSelector - Custom ingress rules application - Automatic Prometheus ingress when metrics enabled - Prometheus namespace selector when specified Egress rules: - DNS egress with default kube-system/kube-dns - Custom DNS namespace and pod selector - AllowAllEgress default behavior - Restricted egress modes (metadata, in-cluster) - Custom CIDR blocks - Custom egress rules application - DNS disabled scenario Policy configuration: - Correct policyTypes (Ingress, Egress) - Pod selector labels matching chart labels Tests ensure NetworkPolicy works correctly across all supported configurations and fails safely when misconfigured. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- charts/jellyfin/tests/networkpolicy_test.yaml | 297 ++++++++++++++++++ 1 file changed, 297 insertions(+) create mode 100644 charts/jellyfin/tests/networkpolicy_test.yaml diff --git a/charts/jellyfin/tests/networkpolicy_test.yaml b/charts/jellyfin/tests/networkpolicy_test.yaml new file mode 100644 index 0000000..1d54067 --- /dev/null +++ b/charts/jellyfin/tests/networkpolicy_test.yaml @@ -0,0 +1,297 @@ +suite: test networkpolicy +templates: + - networkpolicy.yaml +tests: + - it: should not create NetworkPolicy by default + asserts: + - hasDocuments: + count: 0 + + - it: should create NetworkPolicy when enabled + set: + networkPolicy.enabled: true + asserts: + - hasDocuments: + count: 1 + - isKind: + of: NetworkPolicy + - isAPIVersion: + of: networking.k8s.io/v1 + - equal: + path: metadata.name + value: RELEASE-NAME-jellyfin + + - it: should fail when NetworkPolicy enabled with DLNA + set: + networkPolicy.enabled: true + jellyfin.enableDLNA: true + asserts: + - failedTemplate: + errorMessage: "NetworkPolicy cannot be enabled when hostNetwork is enabled" + + - it: should fail when NetworkPolicy enabled with hostNetwork + set: + networkPolicy.enabled: true + podPrivileges.hostNetwork: true + asserts: + - failedTemplate: + errorMessage: "NetworkPolicy cannot be enabled when hostNetwork is enabled" + + - it: should have both Ingress and Egress policy types by default + set: + networkPolicy.enabled: true + asserts: + - contains: + path: spec.policyTypes + content: Ingress + - contains: + path: spec.policyTypes + content: Egress + + - it: should allow external ingress by default + set: + networkPolicy.enabled: true + asserts: + - isNotNull: + path: spec.ingress + # When allowExternal is true, there should be no 'from' in first ingress rule + - isNull: + path: spec.ingress[0].from + + - it: should restrict ingress when allowExternal is false + set: + networkPolicy.enabled: true + networkPolicy.ingress.allowExternal: false + networkPolicy.ingress.podSelector: + matchLabels: + app: test + asserts: + - isNotNull: + path: spec.ingress[0].from + - equal: + path: spec.ingress[0].from[0].podSelector.matchLabels.app + value: test + + - it: should add namespace selector for ingress when specified + set: + networkPolicy.enabled: true + networkPolicy.ingress.allowExternal: false + networkPolicy.ingress.namespaceSelector: + matchLabels: + name: frontend + asserts: + - equal: + path: spec.ingress[0].from[0].namespaceSelector.matchLabels.name + value: frontend + + - it: should create Prometheus ingress rule when metrics enabled + set: + networkPolicy.enabled: true + metrics.enabled: true + metrics.serviceMonitor.enabled: true + metrics.serviceMonitor.port: 8096 + asserts: + - contains: + path: spec.ingress + content: + ports: + - protocol: TCP + port: 8096 + from: + - podSelector: + app.kubernetes.io/name: prometheus + + - it: should add Prometheus namespace selector when specified + set: + networkPolicy.enabled: true + metrics.enabled: true + metrics.serviceMonitor.enabled: true + networkPolicy.metrics.namespace: monitoring + asserts: + - isNotNull: + path: spec.ingress[1].from[0].namespaceSelector + - equal: + path: spec.ingress[1].from[0].namespaceSelector.matchLabels.kubernetes.io/metadata.name + value: monitoring + + - it: should add custom ingress rules + set: + networkPolicy.enabled: true + networkPolicy.ingress.customRules: + - from: + - namespaceSelector: + matchLabels: + name: custom + ports: + - protocol: TCP + port: 9999 + asserts: + - contains: + path: spec.ingress + content: + from: + - namespaceSelector: + matchLabels: + name: custom + ports: + - protocol: TCP + port: 9999 + + - it: should allow DNS egress by default + set: + networkPolicy.enabled: true + asserts: + - contains: + path: spec.egress + content: + to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: kube-system + podSelector: + matchLabels: + k8s-app: kube-dns + ports: + - protocol: UDP + port: 53 + - protocol: TCP + port: 53 + + - it: should allow custom DNS namespace + set: + networkPolicy.enabled: true + networkPolicy.egress.dnsNamespace: custom-dns-ns + asserts: + - equal: + path: spec.egress[0].to[0].namespaceSelector.matchLabels.kubernetes.io/metadata.name + value: custom-dns-ns + + - it: should allow custom DNS pod selector + set: + networkPolicy.enabled: true + networkPolicy.egress.dnsPodSelector: + k8s-app: coredns + asserts: + - equal: + path: spec.egress[0].to[0].podSelector.matchLabels.k8s-app + value: coredns + + - it: should allow all egress by default + set: + networkPolicy.enabled: true + asserts: + - contains: + path: spec.egress + content: + to: + - ipBlock: + cidr: 0.0.0.0/0 + + - it: should not allow all egress when allowAllEgress is false + set: + networkPolicy.enabled: true + networkPolicy.egress.allowAllEgress: false + asserts: + - notContains: + path: spec.egress + content: + to: + - ipBlock: + cidr: 0.0.0.0/0 + + - it: should allow HTTPS when restrictedEgress.allowMetadata is true + set: + networkPolicy.enabled: true + networkPolicy.egress.allowAllEgress: false + networkPolicy.egress.restrictedEgress.allowMetadata: true + asserts: + - contains: + path: spec.egress + content: + to: + - ipBlock: + cidr: 0.0.0.0/0 + ports: + - protocol: TCP + port: 443 + + - it: should allow in-cluster communication when restrictedEgress.allowInCluster is true + set: + networkPolicy.enabled: true + networkPolicy.egress.allowAllEgress: false + networkPolicy.egress.restrictedEgress.allowInCluster: true + asserts: + - contains: + path: spec.egress + content: + to: + - podSelector: {} + + - it: should add custom CIDR blocks + set: + networkPolicy.enabled: true + networkPolicy.egress.allowAllEgress: false + networkPolicy.egress.restrictedEgress.allowedCIDRs: + - 10.0.0.0/8 + - 192.168.0.0/16 + asserts: + - contains: + path: spec.egress + content: + to: + - ipBlock: + cidr: 10.0.0.0/8 + - contains: + path: spec.egress + content: + to: + - ipBlock: + cidr: 192.168.0.0/16 + + - it: should add custom egress rules + set: + networkPolicy.enabled: true + networkPolicy.egress.customRules: + - to: + - podSelector: + matchLabels: + app: database + ports: + - protocol: TCP + port: 5432 + asserts: + - contains: + path: spec.egress + content: + to: + - podSelector: + matchLabels: + app: database + ports: + - protocol: TCP + port: 5432 + + - it: should not create DNS egress when allowDNS is false + set: + networkPolicy.enabled: true + networkPolicy.egress.allowDNS: false + asserts: + - notContains: + path: spec.egress + content: + ports: + - protocol: UDP + port: 53 + - protocol: TCP + port: 53 + + - it: should use correct pod selector labels + set: + networkPolicy.enabled: true + asserts: + - equal: + path: spec.podSelector.matchLabels.app\.kubernetes\.io/name + value: jellyfin + - equal: + path: spec.podSelector.matchLabels.app\.kubernetes\.io/instance + value: RELEASE-NAME From 5bfd6298488af5950f23bc42a065aa40c16ea1a2 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 7 Nov 2025 19:28:57 +0300 Subject: [PATCH 12/24] docs(readme): add comprehensive NetworkPolicy documentation Add "Network Security" section to README.md.gotmpl with: Requirements: - CNI plugin support (Calico, Cilium, Weave, Canal) - DLNA incompatibility explanation Configuration examples: - Basic usage (default settings) - Production configuration (Ingress controller only) - High security (restricted egress) - Monitoring integration (Prometheus) - Advanced scenarios (multiple namespaces, custom rules) Security considerations: - Metadata provider requirements (TMDB, TheTVDB, OpenSubtitles) - DNS access importance - Local metadata alternative - Testing recommendations Troubleshooting guide: - Metadata download issues - Web interface access problems - Prometheus scraping failures - Deployment validation errors The documentation provides complete guidance for users to implement NetworkPolicy according to their security requirements, from basic isolation to zero-trust configurations. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- charts/jellyfin/README.md.gotmpl | 201 +++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) diff --git a/charts/jellyfin/README.md.gotmpl b/charts/jellyfin/README.md.gotmpl index ee08a35..3c3c09f 100644 --- a/charts/jellyfin/README.md.gotmpl +++ b/charts/jellyfin/README.md.gotmpl @@ -85,3 +85,204 @@ extraVolumeMounts: - name: hwa mountPath: /dev/dri ``` + +## Network Security + +Jellyfin chart supports Kubernetes NetworkPolicy for network isolation and security hardening. NetworkPolicy allows you to control which pods can access Jellyfin (ingress) and what external connections Jellyfin can make (egress). + +### Requirements + +- **CNI Plugin**: NetworkPolicy requires a Container Network Interface (CNI) plugin that supports NetworkPolicies, such as: + - Calico + - Cilium + - Weave Net + - Canal + + Check with your cluster administrator if NetworkPolicy is supported in your cluster. + +- **DLNA Incompatibility**: NetworkPolicy cannot be enabled when `enableDLNA: true` or `podPrivileges.hostNetwork: true` is set, as pods using `hostNetwork` bypass NetworkPolicy rules. The chart will fail deployment with a clear error message if both are enabled. + +### Basic Usage + +By default, NetworkPolicy is disabled to maintain backward compatibility. To enable basic network isolation: + +```yaml +networkPolicy: + enabled: true +``` + +This will create a NetworkPolicy with the following defaults: +- **Ingress**: Allow connections from any pod in any namespace +- **Egress**: Allow DNS resolution and all internet access (required for metadata) + +### Production Configuration - Restrict to Ingress Controller + +For production environments, you typically want to restrict access to only allow traffic through the Ingress controller: + +```yaml +networkPolicy: + enabled: true + ingress: + allowExternal: false + namespaceSelector: + matchLabels: + name: ingress-nginx + podSelector: + matchLabels: + app.kubernetes.io/name: ingress-nginx + +ingress: + enabled: true + className: nginx + hosts: + - host: jellyfin.example.com + paths: + - path: / + pathType: Prefix +``` + +### High Security Configuration - Restricted Egress + +For security-conscious deployments that need to limit outbound connections: + +```yaml +networkPolicy: + enabled: true + ingress: + allowExternal: false + podSelector: + matchLabels: + jellyfin-client: "true" # Only pods with this label can access + egress: + allowDNS: true # Always needed + allowAllEgress: false # Block unrestricted internet + restrictedEgress: + allowMetadata: true # Allow HTTPS/443 for metadata providers (TMDB, etc.) + allowInCluster: false # Block pod-to-pod communication +``` + +**Note**: With this configuration, Jellyfin can only: +- Resolve DNS queries +- Connect to HTTPS (port 443) endpoints for metadata providers +- Cannot connect to other pods in the cluster +- Cannot access non-HTTPS services + +### Monitoring Integration + +If you're using Prometheus for monitoring, the chart automatically allows ingress from Prometheus pods when metrics are enabled: + +```yaml +networkPolicy: + enabled: true +metrics: + enabled: true + serviceMonitor: + enabled: true +``` + +By default, the chart allows ingress from pods with label `app.kubernetes.io/name: prometheus`. If your Prometheus uses different labels, customize the selector: + +```yaml +networkPolicy: + enabled: true + metrics: + namespace: monitoring # If Prometheus is in a different namespace + podSelector: + app: my-prometheus +``` + +### Advanced Configuration + +#### Multiple Namespaces Access + +Allow access from multiple namespaces using custom rules: + +```yaml +networkPolicy: + enabled: true + ingress: + allowExternal: false + customRules: + # Frontend namespace + - from: + - namespaceSelector: + matchLabels: + name: frontend + podSelector: + matchLabels: + access-jellyfin: "true" + ports: + - protocol: TCP + port: 8096 + + # Admin tools namespace + - from: + - namespaceSelector: + matchLabels: + name: admin-tools + ports: + - protocol: TCP + port: 8096 +``` + +#### Custom Egress Rules + +Allow connections to specific external services: + +```yaml +networkPolicy: + enabled: true + egress: + allowAllEgress: false + restrictedEgress: + allowMetadata: true + allowedCIDRs: + - 10.0.0.0/8 # Internal network + - 192.168.0.0/16 # Another internal network + customRules: + # Allow connection to external database + - to: + - ipBlock: + cidr: 203.0.113.0/24 + ports: + - protocol: TCP + port: 5432 +``` + +### Security Considerations + +1. **Metadata Providers**: Jellyfin requires internet access to download metadata (movie posters, descriptions, etc.) from: + - TheMovieDB (api.themoviedb.org) + - TheTVDB (api.thetvdb.com) + - OpenSubtitles (api.opensubtitles.com) + - Fanart.tv (fanart.tv) + + If you use `restrictedEgress.allowMetadata: true`, these will work as they all use HTTPS (port 443). + +2. **DNS Access**: DNS resolution is critical for Jellyfin operation. The chart prevents accidental DNS blocking by defaulting `allowDNS: true`. + +3. **Local Metadata**: If you want to completely block internet access, you can use local metadata (NFO files and local images). This requires manual setup and is not the default Jellyfin behavior. + +4. **Testing**: Always test NetworkPolicy changes in a development environment first. Misconfigured policies can block legitimate traffic. + +### Troubleshooting + +**Jellyfin can't download metadata/images:** +- Check that `egress.allowAllEgress: true` or `restrictedEgress.allowMetadata: true` is set +- Verify DNS egress is allowed: `egress.allowDNS: true` + +**Can't access Jellyfin web interface:** +- Verify ingress rules allow traffic from your access point (Ingress controller, LoadBalancer, etc.) +- Check NOTES.txt after deployment for detailed NetworkPolicy status + +**Prometheus can't scrape metrics:** +- Ensure `metrics.enabled: true` and `metrics.serviceMonitor.enabled: true` +- Verify `networkPolicy.metrics.podSelector` matches your Prometheus labels +- Set `networkPolicy.metrics.namespace` if Prometheus is in a different namespace + +**Deployment fails with "NetworkPolicy cannot be enabled...":** +- You have both `networkPolicy.enabled: true` and `hostNetwork: true` (or `enableDLNA: true`) +- NetworkPolicy doesn't work with host networking +- Either disable NetworkPolicy or disable host networking + +For more configuration options, see the full values documentation in [values.yaml](values.yaml). From a7cfada436877ca0d7e4a5c861f2d112a478ac02 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 7 Nov 2025 19:29:18 +0300 Subject: [PATCH 13/24] chore(chart): bump version to 2.6.0 with NetworkPolicy support Bump chart version from 2.5.0 to 2.6.0 (minor version) for new feature. Added artifacthub.io/changes annotations: - NetworkPolicy support for network isolation and security hardening - NOTES.txt with deployment status and configuration summary This is a backward-compatible change: - NetworkPolicy is disabled by default - Existing installations will not be affected - Users can opt-in to NetworkPolicy by setting networkPolicy.enabled=true The NetworkPolicy feature provides enterprise-grade network security controls for Jellyfin deployments in Kubernetes environments with CNI plugin support. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- charts/jellyfin/Chart.yaml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/charts/jellyfin/Chart.yaml b/charts/jellyfin/Chart.yaml index 96a351b..a742864 100644 --- a/charts/jellyfin/Chart.yaml +++ b/charts/jellyfin/Chart.yaml @@ -8,5 +8,14 @@ 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 NetworkPolicy support for network isolation and security hardening" + links: + - name: Documentation + url: https://kubernetes.io/docs/concepts/services-networking/network-policies/ + - kind: added + description: "Add NOTES.txt with deployment status and NetworkPolicy configuration summary" From 5be9dae42087097c23cd2d18e766e3f576def518 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 7 Nov 2025 19:30:56 +0300 Subject: [PATCH 14/24] docs(readme): regenerate README.md from template with helm-docs Regenerate README.md from README.md.gotmpl template using helm-docs. This adds the complete NetworkPolicy documentation section and updates the values table with all networkPolicy configuration parameters. The generated documentation includes: - Updated version badge (2.6.0) - Complete networkPolicy values table with descriptions - Network Security section with examples and troubleshooting - All parameters from values.yaml with auto-generated descriptions Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- charts/jellyfin/README.md | 224 +++++++++++++++++++++++++++++++++++++- 1 file changed, 223 insertions(+), 1 deletion(-) diff --git a/charts/jellyfin/README.md b/charts/jellyfin/README.md index 10952b6..323c8b1 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.6.0](https://img.shields.io/badge/Version-2.6.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) A Helm chart for Jellyfin Media Server @@ -86,6 +86,27 @@ helm install my-jellyfin jellyfin/jellyfin -f values.yaml | metrics.serviceMonitor.targetLabels | list | `[]` | Target labels to add to the scraped metrics | | metrics.serviceMonitor.tlsConfig | object | `{}` | TLS configuration for scraping metrics | | nameOverride | string | `""` | Override the default name of the chart. | +| networkPolicy | object | `{"egress":{"allowAllEgress":true,"allowDNS":true,"customRules":[],"dnsNamespace":"kube-system","dnsPodSelector":{"k8s-app":"kube-dns"},"restrictedEgress":{"allowInCluster":true,"allowMetadata":true,"allowedCIDRs":[]}},"enabled":false,"ingress":{"allowExternal":true,"customRules":[],"namespaceSelector":{},"podSelector":{}},"metrics":{"namespace":"","podSelector":{"app.kubernetes.io/name":"prometheus"}},"policyTypes":["Ingress","Egress"]}` | Network Policy configuration for network isolation and security. Requires a CNI plugin that supports NetworkPolicy (Calico, Cilium, Weave, etc.). WARNING: NetworkPolicy cannot be enabled when hostNetwork is used (DLNA mode). The chart will fail with an error if both are enabled simultaneously. | +| networkPolicy.egress | object | `{"allowAllEgress":true,"allowDNS":true,"customRules":[],"dnsNamespace":"kube-system","dnsPodSelector":{"k8s-app":"kube-dns"},"restrictedEgress":{"allowInCluster":true,"allowMetadata":true,"allowedCIDRs":[]}}` | Egress rules configuration - controls what external connections Jellyfin can make. | +| networkPolicy.egress.allowAllEgress | bool | `true` | Allow all egress traffic (internet access for metadata, subtitles, images). When true, Jellyfin can connect to any external destination (0.0.0.0/0). This is the recommended default as Jellyfin needs internet access for: - Downloading movie/TV show metadata (TMDB, TheTVDB, OMDb) - Fetching poster images, fanart, and other artwork - Downloading subtitles (OpenSubtitles) - Updating plugins When false, you must configure restrictedEgress or customRules. | +| networkPolicy.egress.allowDNS | bool | `true` | Allow DNS resolution (required for Jellyfin to function). This adds an egress rule for kube-system namespace with kube-dns pods. DNS is required for resolving metadata provider domains and subtitle services. It is highly recommended to keep this enabled. | +| networkPolicy.egress.customRules | list | `[]` | Additional custom egress rules. Allows for complex scenarios not covered by the standard template. These rules are added as-is to the NetworkPolicy egress section. Example - allow connections to specific database: customRules: - to: - podSelector: matchLabels: app: postgresql ports: - protocol: TCP port: 5432 | +| networkPolicy.egress.dnsNamespace | string | `"kube-system"` | DNS namespace where DNS service is running. Usually "kube-system" but can differ in some Kubernetes distributions. | +| networkPolicy.egress.dnsPodSelector | object | `{"k8s-app":"kube-dns"}` | DNS pod selector labels. Default selector is for kube-dns, but CoreDNS or other DNS providers may use different labels. Adjust if needed. Common alternatives: k8s-app: kube-dns (default) k8s-app: coredns app.kubernetes.io/name: coredns | +| networkPolicy.egress.restrictedEgress | object | `{"allowInCluster":true,"allowMetadata":true,"allowedCIDRs":[]}` | Restricted egress mode for security-conscious deployments. Only used when allowAllEgress is false. Provides fine-grained control over outbound connections. | +| networkPolicy.egress.restrictedEgress.allowInCluster | bool | `true` | Allow communication within the cluster (pod-to-pod). Useful if Jellyfin needs to connect to other services in the cluster. This allows connections to any pod in any namespace. | +| networkPolicy.egress.restrictedEgress.allowMetadata | bool | `true` | Allow HTTPS (443/TCP) for metadata providers. Most metadata providers (TMDB, TheTVDB, OpenSubtitles, Fanart.tv) use HTTPS, so this covers the majority of use cases. This allows connections to any IP on port 443. | +| networkPolicy.egress.restrictedEgress.allowedCIDRs | list | `[]` | Additional IP CIDR blocks to allow egress. Useful for allowing specific IP ranges for metadata providers or other external services. Example - allow entire internet except private networks: allowedCIDRs: - 0.0.0.0/0 Example - allow specific metadata provider IP ranges: allowedCIDRs: - 13.224.0.0/14 # CloudFront (used by many CDNs) | +| networkPolicy.enabled | bool | `false` | Enable NetworkPolicy for the Jellyfin pod. By default, this is disabled to maintain backward compatibility. When enabled, you can control which pods can access Jellyfin (ingress) and what external connections Jellyfin can make (egress). | +| networkPolicy.ingress | object | `{"allowExternal":true,"customRules":[],"namespaceSelector":{},"podSelector":{}}` | Ingress rules configuration - controls which pods/namespaces can access Jellyfin. | +| networkPolicy.ingress.allowExternal | bool | `true` | Allow external access from any namespace and any pod. When true, any pod in the cluster can access Jellyfin (default behavior). When false, only pods matching podSelector/namespaceSelector can access. Set to false for production environments to restrict access. | +| networkPolicy.ingress.customRules | list | `[]` | Additional custom ingress rules. Allows for complex scenarios not covered by the standard template. These rules are added as-is to the NetworkPolicy ingress section. Example - allow from monitoring namespace: customRules: - from: - namespaceSelector: matchLabels: name: monitoring ports: - protocol: TCP port: 8096 | +| networkPolicy.ingress.namespaceSelector | object | `{}` | Namespace selector to allow cross-namespace ingress. Only used when allowExternal is false. Allows you to specify which namespaces can access Jellyfin. Example - only allow from ingress-nginx namespace: namespaceSelector: matchLabels: name: ingress-nginx | +| networkPolicy.ingress.podSelector | object | `{}` | Custom pod selector for allowed ingress traffic. Only used when allowExternal is false. Allows you to specify which pods can access Jellyfin based on labels. Example - only allow pods with specific label: podSelector: matchLabels: jellyfin-client: "true" | +| networkPolicy.metrics | object | `{"namespace":"","podSelector":{"app.kubernetes.io/name":"prometheus"}}` | Prometheus metrics scraping configuration. Automatically allows ingress from Prometheus when metrics.serviceMonitor.enabled is true. This ensures Prometheus can scrape metrics without additional configuration. | +| networkPolicy.metrics.namespace | string | `""` | Namespace where Prometheus is running. Leave empty to use the same namespace as Jellyfin. Set to the monitoring namespace if Prometheus is in a different namespace. Example: "monitoring" or "prometheus" | +| networkPolicy.metrics.podSelector | object | `{"app.kubernetes.io/name":"prometheus"}` | Pod selector for Prometheus pods. These labels must match your Prometheus deployment. The chart will automatically add an ingress rule for pods matching these labels. Default selector works with prometheus-operator and kube-prometheus-stack. Adjust if your Prometheus uses different labels. | +| networkPolicy.policyTypes | list | `["Ingress","Egress"]` | Policy types to enforce. Both ingress and egress policies can be enabled. See: https://kubernetes.io/docs/concepts/services-networking/network-policies/#policy-types | | nodeSelector | object | `{}` | Node selector for pod scheduling. | | persistence.config.accessMode | string | `"ReadWriteOnce"` | | | persistence.config.annotations | object | `{}` | Custom annotations to be added to the PVC | @@ -160,3 +181,204 @@ extraVolumeMounts: - name: hwa mountPath: /dev/dri ``` + +## Network Security + +Jellyfin chart supports Kubernetes NetworkPolicy for network isolation and security hardening. NetworkPolicy allows you to control which pods can access Jellyfin (ingress) and what external connections Jellyfin can make (egress). + +### Requirements + +- **CNI Plugin**: NetworkPolicy requires a Container Network Interface (CNI) plugin that supports NetworkPolicies, such as: + - Calico + - Cilium + - Weave Net + - Canal + + Check with your cluster administrator if NetworkPolicy is supported in your cluster. + +- **DLNA Incompatibility**: NetworkPolicy cannot be enabled when `enableDLNA: true` or `podPrivileges.hostNetwork: true` is set, as pods using `hostNetwork` bypass NetworkPolicy rules. The chart will fail deployment with a clear error message if both are enabled. + +### Basic Usage + +By default, NetworkPolicy is disabled to maintain backward compatibility. To enable basic network isolation: + +```yaml +networkPolicy: + enabled: true +``` + +This will create a NetworkPolicy with the following defaults: +- **Ingress**: Allow connections from any pod in any namespace +- **Egress**: Allow DNS resolution and all internet access (required for metadata) + +### Production Configuration - Restrict to Ingress Controller + +For production environments, you typically want to restrict access to only allow traffic through the Ingress controller: + +```yaml +networkPolicy: + enabled: true + ingress: + allowExternal: false + namespaceSelector: + matchLabels: + name: ingress-nginx + podSelector: + matchLabels: + app.kubernetes.io/name: ingress-nginx + +ingress: + enabled: true + className: nginx + hosts: + - host: jellyfin.example.com + paths: + - path: / + pathType: Prefix +``` + +### High Security Configuration - Restricted Egress + +For security-conscious deployments that need to limit outbound connections: + +```yaml +networkPolicy: + enabled: true + ingress: + allowExternal: false + podSelector: + matchLabels: + jellyfin-client: "true" # Only pods with this label can access + egress: + allowDNS: true # Always needed + allowAllEgress: false # Block unrestricted internet + restrictedEgress: + allowMetadata: true # Allow HTTPS/443 for metadata providers (TMDB, etc.) + allowInCluster: false # Block pod-to-pod communication +``` + +**Note**: With this configuration, Jellyfin can only: +- Resolve DNS queries +- Connect to HTTPS (port 443) endpoints for metadata providers +- Cannot connect to other pods in the cluster +- Cannot access non-HTTPS services + +### Monitoring Integration + +If you're using Prometheus for monitoring, the chart automatically allows ingress from Prometheus pods when metrics are enabled: + +```yaml +networkPolicy: + enabled: true +metrics: + enabled: true + serviceMonitor: + enabled: true +``` + +By default, the chart allows ingress from pods with label `app.kubernetes.io/name: prometheus`. If your Prometheus uses different labels, customize the selector: + +```yaml +networkPolicy: + enabled: true + metrics: + namespace: monitoring # If Prometheus is in a different namespace + podSelector: + app: my-prometheus +``` + +### Advanced Configuration + +#### Multiple Namespaces Access + +Allow access from multiple namespaces using custom rules: + +```yaml +networkPolicy: + enabled: true + ingress: + allowExternal: false + customRules: + # Frontend namespace + - from: + - namespaceSelector: + matchLabels: + name: frontend + podSelector: + matchLabels: + access-jellyfin: "true" + ports: + - protocol: TCP + port: 8096 + + # Admin tools namespace + - from: + - namespaceSelector: + matchLabels: + name: admin-tools + ports: + - protocol: TCP + port: 8096 +``` + +#### Custom Egress Rules + +Allow connections to specific external services: + +```yaml +networkPolicy: + enabled: true + egress: + allowAllEgress: false + restrictedEgress: + allowMetadata: true + allowedCIDRs: + - 10.0.0.0/8 # Internal network + - 192.168.0.0/16 # Another internal network + customRules: + # Allow connection to external database + - to: + - ipBlock: + cidr: 203.0.113.0/24 + ports: + - protocol: TCP + port: 5432 +``` + +### Security Considerations + +1. **Metadata Providers**: Jellyfin requires internet access to download metadata (movie posters, descriptions, etc.) from: + - TheMovieDB (api.themoviedb.org) + - TheTVDB (api.thetvdb.com) + - OpenSubtitles (api.opensubtitles.com) + - Fanart.tv (fanart.tv) + + If you use `restrictedEgress.allowMetadata: true`, these will work as they all use HTTPS (port 443). + +2. **DNS Access**: DNS resolution is critical for Jellyfin operation. The chart prevents accidental DNS blocking by defaulting `allowDNS: true`. + +3. **Local Metadata**: If you want to completely block internet access, you can use local metadata (NFO files and local images). This requires manual setup and is not the default Jellyfin behavior. + +4. **Testing**: Always test NetworkPolicy changes in a development environment first. Misconfigured policies can block legitimate traffic. + +### Troubleshooting + +**Jellyfin can't download metadata/images:** +- Check that `egress.allowAllEgress: true` or `restrictedEgress.allowMetadata: true` is set +- Verify DNS egress is allowed: `egress.allowDNS: true` + +**Can't access Jellyfin web interface:** +- Verify ingress rules allow traffic from your access point (Ingress controller, LoadBalancer, etc.) +- Check NOTES.txt after deployment for detailed NetworkPolicy status + +**Prometheus can't scrape metrics:** +- Ensure `metrics.enabled: true` and `metrics.serviceMonitor.enabled: true` +- Verify `networkPolicy.metrics.podSelector` matches your Prometheus labels +- Set `networkPolicy.metrics.namespace` if Prometheus is in a different namespace + +**Deployment fails with "NetworkPolicy cannot be enabled...":** +- You have both `networkPolicy.enabled: true` and `hostNetwork: true` (or `enableDLNA: true`) +- NetworkPolicy doesn't work with host networking +- Either disable NetworkPolicy or disable host networking + +For more configuration options, see the full values documentation in [values.yaml](values.yaml). From 77241b18b50b901fef2c03d6b484ce8bee23f2f5 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 7 Nov 2025 19:48:09 +0300 Subject: [PATCH 15/24] test(httproute): add comprehensive unit tests and deployment notes Add helm-unittest test suite for HTTPRoute (Gateway API) covering: - Not created by default (enabled: false) - Created when enabled with required configuration - Annotations support - parentRefs configuration (name, namespace, sectionName) - Hostnames support (single and multiple) - Path matching rules (PathPrefix, Exact) - Multiple rules and multiple matches per rule - Backend references to Jellyfin service - Custom service port handling Add NOTES.txt with HTTPRoute-specific deployment information: - Gateway references and configuration status - Configured hostnames - Number of routes - Backend service details - Access URLs when hostnames configured - Warnings for missing parentRefs Tests ensure HTTPRoute works correctly across all Gateway API scenarios. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- charts/jellyfin/templates/NOTES.txt | 53 ++++++ charts/jellyfin/tests/httproute_test.yaml | 193 ++++++++++++++++++++++ 2 files changed, 246 insertions(+) create mode 100644 charts/jellyfin/templates/NOTES.txt create mode 100644 charts/jellyfin/tests/httproute_test.yaml diff --git a/charts/jellyfin/templates/NOTES.txt b/charts/jellyfin/templates/NOTES.txt new file mode 100644 index 0000000..3206486 --- /dev/null +++ b/charts/jellyfin/templates/NOTES.txt @@ -0,0 +1,53 @@ +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.httpRoute.enabled }} + +HTTPRoute (Gateway API) is ENABLED: +{{- if .Values.httpRoute.parentRefs }} + Gateway References: + {{- range .Values.httpRoute.parentRefs }} + - {{ .name }}{{ if .namespace }} (namespace: {{ .namespace }}){{ end }}{{ if .sectionName }} [{{ .sectionName }}]{{ end }} + {{- end }} +{{- else }} + ⚠ WARNING: No parentRefs defined - HTTPRoute will not attach to any Gateway! +{{- end }} +{{- if .Values.httpRoute.hostnames }} + Hostnames: + {{- range .Values.httpRoute.hostnames }} + - {{ . }} + {{- end }} +{{- end }} + Rules: {{ len .Values.httpRoute.rules }} route(s) configured + Backend: {{ include "jellyfin.fullname" . }}:{{ .Values.service.port }} + +Access Jellyfin via Gateway API at: +{{- if .Values.httpRoute.hostnames }} +{{- range .Values.httpRoute.hostnames }} + https://{{ . }}{{ (index $.Values.httpRoute.rules 0).matches 0 .path.value }} +{{- end }} +{{- else }} + (Configure httpRoute.hostnames for external access) +{{- end }} + +Note: Ensure your Gateway API CRDs are installed and Gateway is configured. +{{- else 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 }} diff --git a/charts/jellyfin/tests/httproute_test.yaml b/charts/jellyfin/tests/httproute_test.yaml new file mode 100644 index 0000000..7ee0ab4 --- /dev/null +++ b/charts/jellyfin/tests/httproute_test.yaml @@ -0,0 +1,193 @@ +suite: test httproute +templates: + - httproute.yaml +tests: + - it: should not create HTTPRoute by default + asserts: + - hasDocuments: + count: 0 + + - it: should create HTTPRoute when enabled + set: + httpRoute.enabled: true + httpRoute.parentRefs: + - name: my-gateway + namespace: gateway-system + httpRoute.rules: + - matches: + - path: + type: PathPrefix + value: / + asserts: + - hasDocuments: + count: 1 + - isKind: + of: HTTPRoute + - isAPIVersion: + of: gateway.networking.k8s.io/v1 + - equal: + path: metadata.name + value: RELEASE-NAME-jellyfin + + - it: should add annotations when specified + set: + httpRoute.enabled: true + httpRoute.annotations: + my-annotation: my-value + httpRoute.parentRefs: + - name: my-gateway + httpRoute.rules: + - matches: + - path: + type: PathPrefix + value: / + asserts: + - equal: + path: metadata.annotations.my-annotation + value: my-value + + - it: should set parentRefs correctly + set: + httpRoute.enabled: true + httpRoute.parentRefs: + - name: my-gateway + namespace: gateway-system + sectionName: https + httpRoute.rules: + - matches: + - path: + type: PathPrefix + value: / + asserts: + - equal: + path: spec.parentRefs[0].name + value: my-gateway + - equal: + path: spec.parentRefs[0].namespace + value: gateway-system + - equal: + path: spec.parentRefs[0].sectionName + value: https + + - it: should set hostnames when specified + set: + httpRoute.enabled: true + httpRoute.hostnames: + - jellyfin.example.com + - media.example.com + httpRoute.parentRefs: + - name: my-gateway + httpRoute.rules: + - matches: + - path: + type: PathPrefix + value: / + asserts: + - equal: + path: spec.hostnames[0] + value: jellyfin.example.com + - equal: + path: spec.hostnames[1] + value: media.example.com + + - it: should create rules with path matches + set: + httpRoute.enabled: true + httpRoute.parentRefs: + - name: my-gateway + httpRoute.rules: + - matches: + - path: + type: PathPrefix + value: /jellyfin + asserts: + - equal: + path: spec.rules[0].matches[0].path.type + value: PathPrefix + - equal: + path: spec.rules[0].matches[0].path.value + value: /jellyfin + + - it: should set backendRefs to jellyfin service + set: + httpRoute.enabled: true + service.port: 8096 + httpRoute.parentRefs: + - name: my-gateway + httpRoute.rules: + - matches: + - path: + type: PathPrefix + value: / + asserts: + - equal: + path: spec.rules[0].backendRefs[0].name + value: RELEASE-NAME-jellyfin + - equal: + path: spec.rules[0].backendRefs[0].port + value: 8096 + + - it: should support multiple rules + set: + httpRoute.enabled: true + httpRoute.parentRefs: + - name: my-gateway + httpRoute.rules: + - matches: + - path: + type: PathPrefix + value: / + - matches: + - path: + type: Exact + value: /api + asserts: + - lengthEqual: + path: spec.rules + count: 2 + - equal: + path: spec.rules[0].matches[0].path.value + value: / + - equal: + path: spec.rules[1].matches[0].path.value + value: /api + + - it: should support multiple matches in single rule + set: + httpRoute.enabled: true + httpRoute.parentRefs: + - name: my-gateway + httpRoute.rules: + - matches: + - path: + type: PathPrefix + value: /api + - path: + type: PathPrefix + value: /web + asserts: + - lengthEqual: + path: spec.rules[0].matches + count: 2 + - equal: + path: spec.rules[0].matches[0].path.value + value: /api + - equal: + path: spec.rules[0].matches[1].path.value + value: /web + + - it: should use custom service port + set: + httpRoute.enabled: true + service.port: 9000 + httpRoute.parentRefs: + - name: my-gateway + httpRoute.rules: + - matches: + - path: + type: PathPrefix + value: / + asserts: + - equal: + path: spec.rules[0].backendRefs[0].port + value: 9000 From 1744d240119ea3f066104fdc423bbf747249484c Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 7 Nov 2025 19:53:08 +0300 Subject: [PATCH 16/24] 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 17/24] 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 4bdedb2c21e65e9e3bbc868fd49a501b4eb5d0de Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 7 Nov 2025 19:55:17 +0300 Subject: [PATCH 18/24] docs(notes): add deployment notes with troubleshooting references Add NOTES.txt with: - Basic access instructions (Ingress or port-forward) - Reference to troubleshooting documentation in README - Links to Jellyfin documentation This complements the inotify troubleshooting documentation added in README.md.gotmpl, making users aware of available resources. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- charts/jellyfin/templates/NOTES.txt | 32 +++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 charts/jellyfin/templates/NOTES.txt diff --git a/charts/jellyfin/templates/NOTES.txt b/charts/jellyfin/templates/NOTES.txt new file mode 100644 index 0000000..7cc6fdf --- /dev/null +++ b/charts/jellyfin/templates/NOTES.txt @@ -0,0 +1,32 @@ +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 }} + +For troubleshooting common issues, see the README: +- inotify instance limit errors +- Hardware acceleration setup +- Network configuration + +Useful resources: + Documentation: https://jellyfin.org/docs/ + Troubleshooting: See README.md in the chart repository From 916087f1de1c7ccbd428cac9b53c79ca6ff651c2 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 7 Nov 2025 19:56:02 +0300 Subject: [PATCH 19/24] 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 20/24] 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 21/24] 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 From 68ebc46dbdc5f28fa1351019aa617c506d36ba88 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Thu, 6 Nov 2025 13:36:47 +0300 Subject: [PATCH 22/24] feat(deployment): add revisionHistoryLimit parameter for rollback history control Add revisionHistoryLimit parameter to control how many old ReplicaSets are retained for rollback purposes, reducing etcd storage usage compared to Kubernetes default of 10. Changes: - Add revisionHistoryLimit parameter in values.yaml (default: 3) - Add revisionHistoryLimit to deployment.yaml spec - Provide comprehensive documentation with recommendations - Bump chart version 2.5.0 -> 2.5.1 (patch - minor enhancement) - Add changelog annotation for Artifact Hub Benefits: - Reduce etcd storage usage (especially important in large clusters) - Maintain reasonable rollback capability (3 previous versions) - Allow customization based on deployment needs - Can be set to null to use Kubernetes default (10) Default rationale: - 3 revisions provides sufficient rollback capability for most use cases - Reduces cluster resource usage (etcd storage, API server load) - Follows best practices from production Kubernetes deployments - Users can override if they need more history Example usage: # Use default (3 revisions) revisionHistoryLimit: 3 # Increase for critical deployments revisionHistoryLimit: 10 # Use Kubernetes default (10) revisionHistoryLimit: null # Disable history (not recommended) revisionHistoryLimit: 0 See: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#revision-history-limit Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- charts/jellyfin/Chart.yaml | 6 +++++- charts/jellyfin/templates/deployment.yaml | 3 +++ charts/jellyfin/values.yaml | 6 ++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/charts/jellyfin/Chart.yaml b/charts/jellyfin/Chart.yaml index 96a351b..5c24a48 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 revisionHistoryLimit parameter to control rollback history retention (defaults to 3 instead of Kubernetes default 10) diff --git a/charts/jellyfin/templates/deployment.yaml b/charts/jellyfin/templates/deployment.yaml index c235001..0c4ea9a 100644 --- a/charts/jellyfin/templates/deployment.yaml +++ b/charts/jellyfin/templates/deployment.yaml @@ -14,6 +14,9 @@ spec: {{- toYaml . | trim | nindent 4 }} {{- end }} replicas: {{ .Values.replicaCount }} + {{- with .Values.revisionHistoryLimit }} + revisionHistoryLimit: {{ . }} + {{- end }} selector: matchLabels: {{- include "jellyfin.selectorLabels" . | nindent 6 }} diff --git a/charts/jellyfin/values.yaml b/charts/jellyfin/values.yaml index 068c0ef..9ea02d5 100644 --- a/charts/jellyfin/values.yaml +++ b/charts/jellyfin/values.yaml @@ -4,6 +4,12 @@ # -- Number of Jellyfin replicas to start. Should be left at 1. replicaCount: 1 +# -- Number of old ReplicaSets to retain for rollback history. +# Set to 0 to disable revision history (not recommended). +# If not specified, Kubernetes defaults to 10. +# See: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#revision-history-limit +revisionHistoryLimit: 3 + # -- Image pull secrets to authenticate with private repositories. # See: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ imagePullSecrets: [] From e1bcdc003b20fb393cd259236c13fa46f16422e7 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Fri, 7 Nov 2025 19:59:15 +0300 Subject: [PATCH 23/24] test(deployment): add revisionHistoryLimit tests and deployment notes Add helm-unittest tests for revisionHistoryLimit: - Default value (3) verification - Custom values support (0, 1, 5, 100) - Ability to disable revision history Add NOTES.txt with revisionHistoryLimit information: - Show configured revision history limit - Warning when disabled (0) - Note for minimal history (1) - Rollback command example for normal values This parameter controls how many old ReplicaSets are retained for rollback purposes, reducing etcd storage usage. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- charts/jellyfin/templates/NOTES.txt | 35 ++++++++++++++++ .../jellyfin/tests/revision_history_test.yaml | 41 +++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 charts/jellyfin/templates/NOTES.txt create mode 100644 charts/jellyfin/tests/revision_history_test.yaml diff --git a/charts/jellyfin/templates/NOTES.txt b/charts/jellyfin/templates/NOTES.txt new file mode 100644 index 0000000..83dd7c5 --- /dev/null +++ b/charts/jellyfin/templates/NOTES.txt @@ -0,0 +1,35 @@ +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 }} + +Deployment Configuration: + Revision History Limit: {{ .Values.revisionHistoryLimit }} + {{- $limit := int .Values.revisionHistoryLimit }} + {{- if eq $limit 0 }} + ⚠ WARNING: Revision history is disabled - rollback will not be possible! + {{- else if eq $limit 1 }} + ⚠ NOTE: Only 1 revision kept - limited rollback capability + {{- else }} + You can rollback to any of the last {{ .Values.revisionHistoryLimit }} revisions using: + kubectl rollout undo deployment/{{ include "jellyfin.fullname" . }} --namespace {{ .Release.Namespace }} + {{- end }} diff --git a/charts/jellyfin/tests/revision_history_test.yaml b/charts/jellyfin/tests/revision_history_test.yaml new file mode 100644 index 0000000..0a2b903 --- /dev/null +++ b/charts/jellyfin/tests/revision_history_test.yaml @@ -0,0 +1,41 @@ +suite: test revision history limit +templates: + - deployment.yaml +tests: + - it: should set revisionHistoryLimit to 3 by default + asserts: + - equal: + path: spec.revisionHistoryLimit + value: 3 + + - it: should allow custom revisionHistoryLimit + set: + revisionHistoryLimit: 5 + asserts: + - equal: + path: spec.revisionHistoryLimit + value: 5 + + - it: should allow revisionHistoryLimit of 0 + set: + revisionHistoryLimit: 0 + asserts: + - equal: + path: spec.revisionHistoryLimit + value: 0 + + - it: should allow revisionHistoryLimit of 1 + set: + revisionHistoryLimit: 1 + asserts: + - equal: + path: spec.revisionHistoryLimit + value: 1 + + - it: should allow large revisionHistoryLimit + set: + revisionHistoryLimit: 100 + asserts: + - equal: + path: spec.revisionHistoryLimit + value: 100 From 80462ad54aa8659fd9c1dd913c5325a31a790910 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Wed, 5 Nov 2025 23:53:08 +0300 Subject: [PATCH 24/24] ci(release): format release notes with changelog and emojis Automatically extract and format changelog from artifacthub.io/changes annotations in Chart.yaml and update GitHub release notes. Changes: - Install yq for YAML parsing - Add step to extract changelog from Chart.yaml - Format changelog with emojis based on change kind (added, fixed, etc.) - Build comprehensive release notes with: - Chart and app versions - Formatted changelog - Installation and upgrade instructions - Links to documentation - Update release notes after chart-releaser creates the release This addresses the issue where releases only contained "A Helm chart for Jellyfin Media Server" without any information about what changed. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- .github/workflows/release.yaml | 119 +++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 211098d..8cca81c 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -32,9 +32,128 @@ jobs: env: GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + - name: Install yq + run: | + # renovate: datasource=github-releases depName=mikefarah/yq + YQ_VERSION="4.44.3" + wget -q https://github.com/mikefarah/yq/releases/download/v${YQ_VERSION}/yq_linux_amd64 -O yq + chmod +x yq + sudo mv yq /usr/local/bin/yq + yq --version + - name: Run chart-releaser uses: helm/chart-releaser-action@cae68fefc6b5f367a0275617c9f83181ba54714f # v1.7.0 env: CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}" with: config: .github/cr.yaml + + - name: Extract chart metadata and format release notes + id: release-notes + run: | + CHART_PATH="charts/jellyfin" + CHART_NAME=$(yq eval '.name' "${CHART_PATH}/Chart.yaml") + CHART_VERSION=$(yq eval '.version' "${CHART_PATH}/Chart.yaml") + APP_VERSION=$(yq eval '.appVersion' "${CHART_PATH}/Chart.yaml") + RELEASE_TAG="${CHART_NAME}-${CHART_VERSION}" + + echo "tag=${RELEASE_TAG}" >> $GITHUB_OUTPUT + + CHANGELOG_RAW=$(yq eval '.annotations."artifacthub.io/changes"' "${CHART_PATH}/Chart.yaml") + + # Build changelog section + CHANGELOG_SECTION="" + if [ "$CHANGELOG_RAW" != "null" ] && [ -n "$CHANGELOG_RAW" ]; then + CHANGELOG_SECTION="## What's Changed + + " + TEMP_FILE=$(mktemp) + SEPARATOR="|" + echo "$CHANGELOG_RAW" | yq eval ".[] | .kind + \"${SEPARATOR}\" + .description" - > "$TEMP_FILE" + + while IFS="${SEPARATOR}" read -r kind description; do + case "$kind" in + "added") + emoji="✨" + label="Added" + ;; + "changed") + emoji="🔄" + label="Changed" + ;; + "deprecated") + emoji="⚠️" + label="Deprecated" + ;; + "removed") + emoji="🗑️" + label="Removed" + ;; + "fixed") + emoji="🐛" + label="Fixed" + ;; + "security") + emoji="🔒" + label="Security" + ;; + *) + emoji="📝" + label="Changed" + ;; + esac + CHANGELOG_SECTION="${CHANGELOG_SECTION}- ${emoji} **${label}**: ${description} + " + done < "$TEMP_FILE" + + rm -f "$TEMP_FILE" + CHANGELOG_SECTION="${CHANGELOG_SECTION} + " + fi + + # Build release body + RELEASE_BODY="## Jellyfin Helm Chart v${CHART_VERSION} + + **Application Version**: \`${APP_VERSION}\` + + ${CHANGELOG_SECTION}## Installation + + Add the Jellyfin Helm repository: + + \`\`\`bash + helm repo add jellyfin https://jellyfin.github.io/jellyfin-helm + helm repo update + \`\`\` + + Install the chart: + + \`\`\`bash + helm install my-jellyfin jellyfin/jellyfin --version ${CHART_VERSION} + \`\`\` + + ## Upgrade + + \`\`\`bash + helm upgrade my-jellyfin jellyfin/jellyfin --version ${CHART_VERSION} + \`\`\` + + --- + + 📦 **Chart**: \`jellyfin\` v${CHART_VERSION} + 🎬 **Jellyfin**: v${APP_VERSION} + 📖 **Documentation**: https://github.com/jellyfin/jellyfin-helm/blob/main/charts/jellyfin/README.md + " + + # Save to file + echo "$RELEASE_BODY" > release-notes.md + + - name: Update release notes + env: + GH_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + run: | + RELEASE_TAG="${{ steps.release-notes.outputs.tag }}" + + # Update release notes + gh release edit "$RELEASE_TAG" --notes-file release-notes.md + + echo "✅ Updated release notes for $RELEASE_TAG"