91bfc42989
Scaled to replicas: 0: fornax (coordinator/ui/workers), jellyfin via media, overseerr, plex, radarr, sonarr, bazarr, prowlarr, flaresolverr, ollama, harmonic-wordpress, docuseal, listmonk, memos, plane, redpanda, wp-coordinator
212 lines
6.9 KiB
YAML
212 lines
6.9 KiB
YAML
# 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
|