# services/jellyfin/deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: jellyfin namespace: jellyfin labels: app: jellyfin spec: replicas: 1 strategy: type: RollingUpdate rollingUpdate: maxSurge: 0 maxUnavailable: 1 selector: matchLabels: app: jellyfin template: metadata: labels: app: jellyfin spec: nodeSelector: jellyfin: "true" securityContext: runAsUser: 1000 fsGroup: 65532 fsGroupChangePolicy: OnRootMismatch runAsGroup: 65532 initContainers: - name: fetch-oidc-plugin image: alpine:3.20 securityContext: runAsUser: 0 env: - name: OIDC_PLUGIN_REPO value: "registry.bstein.dev/streaming/oidc-plugin" - name: OIDC_PLUGIN_TAG value: "10.11.5" - name: ORAS_USERNAME valueFrom: secretKeyRef: name: harbor-robot key: username optional: true - name: ORAS_PASSWORD valueFrom: secretKeyRef: name: harbor-robot key: password optional: true volumeMounts: - name: oidc-plugin mountPath: /plugin-src command: ["/bin/sh", "-c"] args: - | set -euo pipefail apk add --no-cache curl tar ORAS_VERSION=1.2.0 curl -sSL "https://github.com/oras-project/oras/releases/download/v${ORAS_VERSION}/oras_${ORAS_VERSION}_linux_amd64.tar.gz" | tar -xz -C /usr/local/bin oras ref="${OIDC_PLUGIN_REPO}:${OIDC_PLUGIN_TAG}" cd /plugin-src if [ -n "${ORAS_USERNAME:-}" ] && [ -n "${ORAS_PASSWORD:-}" ]; then oras login "$(echo "${OIDC_PLUGIN_REPO}" | cut -d/ -f1)" -u "${ORAS_USERNAME}" -p "${ORAS_PASSWORD}" fi oras pull --allow-path-traversal "${ref}" ls -lh /plugin-src - name: install-oidc-plugin image: alpine:3.20 securityContext: runAsUser: 0 env: - name: OIDC_PLUGIN_VERSION value: "1.0.2.0" - name: OIDC_ISSUER value: "https://sso.bstein.dev/realms/atlas" - name: OIDC_REDIRECT_URI value: "https://stream.bstein.dev/api/oidc/callback" - name: OIDC_LOGOUT_URI value: "https://sso.bstein.dev/realms/atlas/protocol/openid-connect/logout?redirect_uri=https://stream.bstein.dev/" - name: OIDC_SCOPES value: "openid,profile,email" - name: OIDC_ROLE_CLAIM value: "groups" - name: OIDC_CLIENT_ID valueFrom: secretKeyRef: name: jellyfin-oidc key: client-id - name: OIDC_CLIENT_SECRET valueFrom: secretKeyRef: name: jellyfin-oidc key: client-secret volumeMounts: - name: config mountPath: /config - name: oidc-plugin mountPath: /plugin-src command: ["/bin/sh", "-c"] args: - | set -euo pipefail if [ -z "${OIDC_CLIENT_ID:-}" ] || [ -z "${OIDC_CLIENT_SECRET:-}" ]; then echo "OIDC_CLIENT_ID or OIDC_CLIENT_SECRET missing; create secret jellyfin-oidc" >&2 exit 1 fi rm -rf "/config/plugins/LDAP Authentication_20.0.0.0" apk add --no-cache unzip plugin_dir="/config/plugins/OIDC Authentication_${OIDC_PLUGIN_VERSION}" config_dir="/config/plugins/configurations" plugin_zip="/plugin-src/OIDC_Authentication_${OIDC_PLUGIN_VERSION}-net9.zip" if [ ! -s "${plugin_zip}" ]; then echo "Plugin zip missing at ${plugin_zip}" >&2 echo "Contents of /plugin-src:" >&2 ls -lah /plugin-src >&2 || true exit 1 fi rm -rf "${plugin_dir}" mkdir -p "${plugin_dir}" "${config_dir}" unzip -o "${plugin_zip}" -d "${plugin_dir}" rm -f "${plugin_dir}"/Microsoft.Extensions.*.dll cat >"${plugin_dir}/meta.json" <<'EOF' { "category": "Authentication", "changelog": "OIDC SSO authentication plugin; auto user creation and role mapping", "description": "OpenID Connect (OIDC) authentication provider for Jellyfin with SSO support.", "guid": "a1b2c3d4-e5f6-47a8-b9c0-d1e2f3a4b5c6", "name": "OIDC Authentication", "overview": "Enable Single Sign-On (SSO) for Jellyfin using an OpenID Connect provider.", "owner": "lolerskatez", "targetAbi": "10.11.5.0", "timestamp": "2025-12-17T04:00:00Z", "version": "1.0.2.0", "status": "Active", "autoUpdate": false, "imagePath": "", "assemblies": [] } EOF scope_lines="" for s in $(echo "${OIDC_SCOPES}" | tr ',' ' '); do trimmed="$(echo "${s}" | xargs)" [ -z "${trimmed}" ] && continue scope_lines="${scope_lines} ${trimmed}\n" done config_file="${config_dir}/JellyfinOIDCPlugin.v2.xml" cat >"${config_file}" < ${OIDC_ISSUER} ${OIDC_CLIENT_ID} ${OIDC_CLIENT_SECRET} $(printf "%b" "${scope_lines}") ${OIDC_ROLE_CLAIM} ${OIDC_REDIRECT_URI} ${OIDC_LOGOUT_URI} true false EOF chown -R 1000:65532 "${plugin_dir}" "${config_file}" runtimeClassName: nvidia containers: - name: jellyfin image: docker.io/jellyfin/jellyfin:10.11.5 imagePullPolicy: IfNotPresent ports: - name: http containerPort: 8096 env: - name: NVIDIA_DRIVER_CAPABILITIES value: "compute,video,utility" - name: JELLYFIN_PublishedServerUrl value: "https://stream.bstein.dev" - name: PUID value: "1000" - name: PGID value: "65532" - name: UMASK value: "002" lifecycle: postStart: exec: command: - /bin/sh - -c - | set -e target="/jellyfin/jellyfin-web/index.html" marker='api/oidc/inject' if grep -q "${marker}" "${target}"; then exit 0 fi tmp="$(mktemp)" awk -v marker="${marker}" 'BEGIN{inserted=0} /<\/head>/ && !inserted {print " "; inserted=1} {print}' "${target}" > "${tmp}" cp "${tmp}" "${target}" resources: limits: nvidia.com/gpu: 1 # cpu: "4" # memory: 8Gi requests: nvidia.com/gpu: 1 cpu: "500m" memory: 1Gi volumeMounts: - name: config mountPath: /config - name: cache mountPath: /cache - name: media mountPath: /media securityContext: runAsUser: 0 runAsGroup: 0 allowPrivilegeEscalation: false readOnlyRootFilesystem: false volumes: - name: config persistentVolumeClaim: claimName: jellyfin-config-astreae - name: cache persistentVolumeClaim: claimName: jellyfin-cache-astreae - name: media persistentVolumeClaim: claimName: jellyfin-media-asteria-new - name: oidc-plugin emptyDir: {}