feat(streaming): add jellyfin stack
New Portainer stack: jellyfin, jellystat + postgres, wizarr and jellyplex-watched. Bulk data (metadata, trickplay, cache, transcodes) binds to /storage1; only SQLite config lands on the root disk. Trickplay gets an explicit sub-bind because 10.11 moved it to /config/data/trickplay, which is not relocatable from the UI. Transcodes are disk-backed rather than tmpfs because jellyfin#16608 orphans segments on the Remux/DirectStream jobs that are routine on this server.
This commit is contained in:
@@ -0,0 +1,93 @@
|
|||||||
|
# streaming stack — Jellyfin and its satellites.
|
||||||
|
#
|
||||||
|
# Storage split is deliberate: SQLite config on the root SSD, all bulk data
|
||||||
|
# (metadata, trickplay, cache, transcodes) on /storage1. The root disk is only
|
||||||
|
# 194G and has filled twice; nothing here may grow unbounded on it.
|
||||||
|
#
|
||||||
|
# Trickplay gets an explicit sub-bind because Jellyfin 10.11 moved it to
|
||||||
|
# /config/data/trickplay and it is NOT relocatable from the UI.
|
||||||
|
#
|
||||||
|
# Transcodes are disk-backed, not tmpfs: jellyfin#16608 means Remux and
|
||||||
|
# DirectStream jobs finish before the segment cleaner's first check, orphaning
|
||||||
|
# segments. Plex already writes transcodes to virtiofs at a 48% transcode rate.
|
||||||
|
#
|
||||||
|
# Env convention: every service gets stack.env via env_file; var names there are
|
||||||
|
# exactly what the containers read. No ${VAR} interpolation, no Portainer UI
|
||||||
|
# env vars.
|
||||||
|
|
||||||
|
services:
|
||||||
|
jellyfin:
|
||||||
|
container_name: jellyfin
|
||||||
|
image: lscr.io/linuxserver/jellyfin:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
networks: [streaming, edge]
|
||||||
|
env_file:
|
||||||
|
- stack.env
|
||||||
|
environment:
|
||||||
|
- JELLYFIN_PublishedServerUrl=https://jellyfin.ginnoir.com
|
||||||
|
volumes:
|
||||||
|
- /etc/localtime:/etc/localtime:ro
|
||||||
|
- /config/jellyfin:/config
|
||||||
|
- /storage1/labdata/jellyfin/metadata:/config/metadata
|
||||||
|
- /storage1/labdata/jellyfin/cache:/config/cache
|
||||||
|
- /storage1/labdata/jellyfin/trickplay:/config/data/trickplay
|
||||||
|
- /storage1/labdata/jellyfin/transcodes:/transcode
|
||||||
|
- /storage1/Movies:/data/movies:ro
|
||||||
|
- /storage1/TV:/data/tv:ro
|
||||||
|
- /storage1/Anime:/data/anime:ro
|
||||||
|
ports:
|
||||||
|
- "8096:8096"
|
||||||
|
|
||||||
|
jellystat-db:
|
||||||
|
container_name: jellystat-db
|
||||||
|
image: postgres:16-alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
networks: [streaming]
|
||||||
|
env_file:
|
||||||
|
- stack.env
|
||||||
|
labels:
|
||||||
|
- "com.centurylabs.watchtower.enable=false"
|
||||||
|
volumes:
|
||||||
|
- /config/jellystat-db:/var/lib/postgresql/data
|
||||||
|
|
||||||
|
jellystat:
|
||||||
|
container_name: jellystat
|
||||||
|
image: cyfershepard/jellystat:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
networks: [streaming, edge]
|
||||||
|
depends_on:
|
||||||
|
- jellystat-db
|
||||||
|
env_file:
|
||||||
|
- stack.env
|
||||||
|
volumes:
|
||||||
|
- /config/jellystat:/app/backend/backup-data
|
||||||
|
ports:
|
||||||
|
- "3009:3000"
|
||||||
|
|
||||||
|
wizarr:
|
||||||
|
container_name: wizarr
|
||||||
|
image: ghcr.io/wizarrrr/wizarr:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
networks: [streaming, edge]
|
||||||
|
env_file:
|
||||||
|
- stack.env
|
||||||
|
volumes:
|
||||||
|
- /config/wizarr:/data/database
|
||||||
|
ports:
|
||||||
|
- "5690:5690"
|
||||||
|
|
||||||
|
jellyplex-watched:
|
||||||
|
container_name: jellyplex-watched
|
||||||
|
image: luigi311/jellyplex-watched:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
networks: [streaming, edge]
|
||||||
|
env_file:
|
||||||
|
- stack.env
|
||||||
|
|
||||||
|
networks:
|
||||||
|
streaming:
|
||||||
|
name: streaming
|
||||||
|
driver: bridge
|
||||||
|
edge:
|
||||||
|
name: edge
|
||||||
|
external: true
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# streaming stack config — injected into every service via env_file.
|
||||||
|
# Var names are exactly what the containers read; no ${VAR} interpolation,
|
||||||
|
# no Portainer UI env vars needed.
|
||||||
|
|
||||||
|
# --- shared ---
|
||||||
|
TZ=America/Chicago
|
||||||
|
PUID=1000
|
||||||
|
PGID=1000
|
||||||
|
|
||||||
|
# --- jellystat-db (postgres:16-alpine) ---
|
||||||
|
POSTGRES_DB=jfstat
|
||||||
|
POSTGRES_USER=jellystat
|
||||||
|
POSTGRES_PASSWORD=bf2d47d8e1a87baf2b4942704a5acfaf5c2aad336fdd480e
|
||||||
|
|
||||||
|
# --- jellystat ---
|
||||||
|
POSTGRES_IP=jellystat-db
|
||||||
|
POSTGRES_PORT=5432
|
||||||
|
JWT_SECRET=0dd9fcdb632277b76977dbada8226e547cc773e1deafc8d778d4038f8d59393b
|
||||||
|
|
||||||
|
# --- jellyplex-watched ---
|
||||||
|
# Plex is host-native, not containerised, so it is reached on the LAN IP.
|
||||||
|
PLEX_URL=http://192.168.1.69:32400
|
||||||
|
PLEX_TOKEN=hM9aycswHKSsUrxiHqwq
|
||||||
|
JELLYFIN_BASEURL=http://jellyfin:8096
|
||||||
|
JELLYFIN_TOKEN=
|
||||||
|
SYNC_FROM_PLEX_TO_JELLYFIN=True
|
||||||
|
SYNC_FROM_JELLYFIN_TO_PLEX=True
|
||||||
|
# Start in dry-run. Task 16 flips this to False after the mapping is verified.
|
||||||
|
DRYRUN=True
|
||||||
|
SLEEP_DURATION=21600
|
||||||
|
LOG_LEVEL=INFO
|
||||||
Reference in New Issue
Block a user