# Seerr — family request portal (watch.nook.family) # Init container ensures Jellyfin connection is always configured from git. # If settings.json has no Jellyfin apiKey (e.g. after reset), it is restored from the ConfigMap. --- apiVersion: v1 kind: ConfigMap metadata: name: seerr-defaults namespace: media data: jellyfin.json: | { "name": "Jellyfin", "ip": "jellyfin.media.svc.cluster.local", "port": 8096, "useSsl": false, "urlBase": "", "externalHostname": "https://jellyfin.nook.family", "jellyfinForgotPasswordUrl": "", "libraries": [ {"id":"0c41907140d802bb58430fed7e2cd79e","name":"Anime","enabled":true,"type":"show"}, {"id":"b34574845035cd222917cc69435b145f","name":"Concerts","enabled":true,"type":"movie"}, {"id":"c248dc0bec4b0ce8fb01231d1f12c5c1","name":"Documentaries","enabled":true,"type":"movie"}, {"id":"f137a2dd21bbc1b99aa5c0f6bf02a805","name":"Movies","enabled":true,"type":"movie"}, {"id":"c4266309d8a74d4abbc72d91516a1be9","name":"Stand-up Comedy","enabled":true,"type":"movie"}, {"id":"767bffe4f11c93ef34b805451a696a4e","name":"TV Shows","enabled":true,"type":"show"} ], "serverId": "87e202a4e9ad49d1bf73927f714fbce6", "apiKey": "35e87cb67e3a4b908678ba9a257d2571" } restore.js: | const fs = require('fs'); const SETTINGS = '/app/config/settings.json'; const DEFAULTS = '/defaults/jellyfin.json'; let settings = {}; if (fs.existsSync(SETTINGS)) { try { settings = JSON.parse(fs.readFileSync(SETTINGS, 'utf8')); } catch(e) {} } // Seerr's Settings.load runs in raw mode during the overseerrMerge check, // which replaces this.data entirely with the file contents. If settings.main // is absent, it crashes at "this.data.main.apiKey" (settings/index.js:418). // Ensure main exists with mediaServerType=JELLYFIN so the merge check is skipped. if (!settings.main) { settings.main = {}; } if (settings.main.mediaServerType !== 2) { settings.main.mediaServerType = 2; // 2 = JELLYFIN (numeric enum, not the string) console.log('Set main.mediaServerType = 2 (JELLYFIN)'); } if (!settings.main.applicationUrl) { settings.main.applicationUrl = 'https://watch.nook.family'; console.log('Set main.applicationUrl'); } // Mark setup as complete so the wizard never shows if (!settings.public) { settings.public = {}; } if (!settings.public.initialized) { settings.public.initialized = true; console.log('Set public.initialized = true'); } if (!settings.jellyfin || !settings.jellyfin.apiKey) { console.log('Jellyfin apiKey missing — restoring from defaults'); settings.jellyfin = JSON.parse(fs.readFileSync(DEFAULTS, 'utf8')); console.log('Restored Jellyfin config'); } else { // Ensure libraries are populated and enabled even if they were cleared const defaults = JSON.parse(fs.readFileSync(DEFAULTS, 'utf8')); if (!settings.jellyfin.libraries || settings.jellyfin.libraries.length === 0) { console.log('Libraries missing — restoring from defaults'); settings.jellyfin.libraries = defaults.libraries; } console.log('Jellyfin config present, libraries:', settings.jellyfin.libraries.length); } // Seed Sonarr if not configured if (!settings.sonarr || settings.sonarr.length === 0) { console.log('Sonarr not configured — seeding from defaults'); settings.sonarr = [{ "name": "Sonarr", "hostname": "sonarr.media.svc", "port": 8989, "apiKey": "4fb7acb509d7469c88e0b46a6fd494e8", "useSsl": false, "baseUrl": "", "activeProfileId": 4, "activeProfileName": "HD-1080p", "activeDirectory": "/media/tv/shows", "is4k": false, "is4kServer": false, "isDefault": true, "enableSeasonFolders": true, "externalUrl": "", "syncEnabled": false, "preventSearch": false, "id": 0 }]; } else { console.log('Sonarr already configured, skipping seed'); } // Seed Radarr if not configured if (!settings.radarr || settings.radarr.length === 0) { console.log('Radarr not configured — seeding from defaults'); settings.radarr = [{ "name": "Radarr", "hostname": "radarr.media.svc", "port": 7878, "apiKey": "c2b1b65e5c9e46d9b98bf105520f26b5", "useSsl": false, "baseUrl": "", "activeProfileId": 4, "activeProfileName": "HD-1080p", "activeDirectory": "/media/movies", "is4k": false, "is4kServer": false, "isDefault": true, "externalUrl": "", "syncEnabled": false, "preventSearch": false, "minimumAvailability": "released", "id": 0 }]; } else { console.log('Radarr already configured, skipping seed'); } fs.writeFileSync(SETTINGS, JSON.stringify(settings, null, 2)); console.log('Settings written successfully'); --- apiVersion: apps/v1 kind: Deployment metadata: name: overseerr namespace: media labels: app: overseerr spec: replicas: 0 strategy: type: Recreate selector: matchLabels: app: overseerr template: metadata: labels: app: overseerr annotations: restartedAt: "2026-04-11T08:00:00Z" spec: initContainers: # Restore Jellyfin connection config if it was cleared (e.g. after pod restart with bad key) - name: restore-jellyfin-config image: node:22-alpine command: - node - /defaults/restore.js volumeMounts: - name: config mountPath: /app/config - name: seerr-defaults mountPath: /defaults containers: - name: overseerr image: seerr/seerr:latest env: - name: TZ value: "America/Chicago" ports: - containerPort: 5055 volumeMounts: - name: config mountPath: /app/config resources: requests: memory: 256Mi cpu: 100m limits: memory: 1Gi cpu: 500m livenessProbe: httpGet: path: /api/v1/status port: 5055 initialDelaySeconds: 30 periodSeconds: 30 readinessProbe: httpGet: path: /api/v1/status port: 5055 initialDelaySeconds: 15 periodSeconds: 10 volumes: - name: config persistentVolumeClaim: claimName: overseerr-config - name: seerr-defaults configMap: name: seerr-defaults --- apiVersion: v1 kind: Service metadata: name: overseerr namespace: media spec: selector: app: overseerr ports: - name: http port: 5055 targetPort: 5055 type: ClusterIP