summaryrefslogtreecommitdiff
path: root/jellyfin
diff options
context:
space:
mode:
Diffstat (limited to 'jellyfin')
-rw-r--r--jellyfin/.env.example2
-rw-r--r--jellyfin/README.md59
-rw-r--r--jellyfin/docker-compose.yml133
-rwxr-xr-xjellyfin/setup-media-user.sh75
4 files changed, 269 insertions, 0 deletions
diff --git a/jellyfin/.env.example b/jellyfin/.env.example
new file mode 100644
index 0000000..35c83a9
--- /dev/null
+++ b/jellyfin/.env.example
@@ -0,0 +1,2 @@
+SONARR_API_KEY=your_sonarr_api_key_here
+RADARR_API_KEY=your_radarr_api_key_here
diff --git a/jellyfin/README.md b/jellyfin/README.md
new file mode 100644
index 0000000..7f64c6d
--- /dev/null
+++ b/jellyfin/README.md
@@ -0,0 +1,59 @@
+# Jellyfin + *arr Stack
+
+Media server stack consisting of:
+- **Jellyfin** — Media server (movies, TV, music, books)
+- **Sonarr** — TV show management
+- **Radarr** — Movie management
+- **Prowlarr** — Indexer management
+- **FlareSolverr** — Cloudflare bypass for indexers
+- **Unpackerr** — Auto-extract downloaded archives
+- **Recyclarr** — Automatic TRaSH-Guide sync for Sonarr/Radarr quality profiles
+
+## Setup
+
+### 1. Create the media user
+
+Run the setup script to create a dedicated `media` user (UID 1001) and set correct permissions:
+```
+bash setup-media-user.sh
+```
+
+This script:
+- Creates a `media` user/group with UID/GID 1001
+- Fixes ownership on config directories
+- Sets group permissions on `/srv/` media directories
+
+### 2. Configure environment
+
+Copy `.env.example` to `.env` and set your API keys:
+```
+cp .env.example .env
+```
+
+Get your API keys from Sonarr (Settings > General) and Radarr (Settings > General).
+
+### 3. Start
+
+```
+docker compose up -d
+```
+
+## Access
+
+| Service | URL |
+|-------------|--------------------------|
+| Jellyfin | `http://<host>:5000` |
+| Sonarr | `http://<host>:8989` |
+| Radarr | `http://<host>:7878` |
+| Prowlarr | `http://<host>:9696` |
+| FlareSolverr | `http://<host>:8191` |
+
+## What to Configure
+
+- **User/group IDs** — All services run as UID 1001/GID 1001. Change `user:`, `PUID`, and `PGID` throughout if your media user differs.
+- **Time zone** — Replace `TZ=America/Los_Angeles` throughout with your timezone.
+- **Media volumes** — All services mount `/srv:/data`. Your media root should be `/srv/` with subdirectories like `vids/`, `mus/`, etc. Adjust the host path if your storage layout differs.
+- **Fonts** — Jellyfin mounts `/usr/local/share/fonts/` for custom fonts. Remove if not needed.
+- **Hardware transcoding** — Jellyfin has `/dev/dri` passed through for VAAPI. Remove or adjust for your GPU setup.
+- **API keys** — Set `SONARR_API_KEY` and `RADARR_API_KEY` in `.env` for Unpackerr. You also need to configure these keys inside Unpackerr's web UI on first run.
+- **Recyclarr** — Configure `recyclarr-config/recyclarr.yml` to sync TRaSH-Guide profiles. Runs daily at 4 AM.
diff --git a/jellyfin/docker-compose.yml b/jellyfin/docker-compose.yml
new file mode 100644
index 0000000..23785b7
--- /dev/null
+++ b/jellyfin/docker-compose.yml
@@ -0,0 +1,133 @@
+services:
+ jellyfin:
+ image: jellyfin/jellyfin
+ container_name: jellyfin
+ user: 1001:1001
+ ports:
+ - 5000:8096/tcp
+ - 7359:7359/udp
+ group_add:
+ - "44" # video (for /dev/dri/card0)
+ - "992" # render (for /dev/dri/renderD128)
+ devices:
+ - /dev/dri:/dev/dri
+ volumes:
+ - ./config:/config
+ - ./cache:/cache
+ - type: bind
+ source: /srv/
+ target: /media
+ read_only: true
+ - type: bind
+ source: /usr/local/share/fonts/
+ target: /usr/local/share/fonts/custom
+ read_only: true
+ restart: unless-stopped
+ environment:
+ - LIBVA_DRIVER_NAME=i965
+ extra_hosts:
+ - 'host.docker.internal:host-gateway'
+
+ sonarr:
+ image: linuxserver/sonarr
+ container_name: sonarr
+ environment:
+ - PUID=1001
+ - PGID=1001
+ - TZ=America/Los_Angeles # Change this to your timezone, e.g. America/Los_Angeles
+ - UMASK=002
+ ports:
+ - 8989:8989
+ volumes:
+ - ./sonarr-config:/config
+ - /srv:/data
+ restart: unless-stopped
+
+ radarr:
+ image: linuxserver/radarr
+ container_name: radarr
+ environment:
+ - PUID=1001
+ - PGID=1001
+ - TZ=America/Los_Angeles # Change this to your timezone, e.g. America/Los_Angeles
+ - UMASK=002
+ ports:
+ - 7878:7878
+ volumes:
+ - ./radarr-config:/config
+ - /srv:/data
+ restart: unless-stopped
+
+ prowlarr:
+ image: linuxserver/prowlarr
+ container_name: prowlarr
+ environment:
+ - PUID=1001
+ - PGID=1001
+ - TZ=America/Los_Angeles
+ - UMASK=002
+ ports:
+ - 9696:9696
+ volumes:
+ - ./prowlarr-config:/config
+ restart: unless-stopped
+
+ flaresolverr:
+ image: ghcr.io/flaresolverr/flaresolverr:latest
+ container_name: flaresolverr
+ environment:
+ - LOG_LEVEL=info
+ - TZ=America/Los_Angeles
+ ports:
+ - 8191:8191
+ restart: unless-stopped
+
+ unpackerr:
+ image: golift/unpackerr
+ container_name: unpackerr
+ user: 1000:1001
+ env_file:
+ - .env
+ environment:
+ - TZ=America/Los_Angeles
+ - UN_SONARR_0_URL=http://sonarr:8989
+ - UN_SONARR_0_API_KEY=${SONARR_API_KEY}
+ - UN_SONARR_0_PATHS_0=/data/torrents/downloads
+ - UN_SONARR_0_PROTOCOLS=torrent
+ - UN_RADARR_0_URL=http://radarr:7878
+ - UN_RADARR_0_API_KEY=${RADARR_API_KEY}
+ - UN_RADARR_0_PATHS_0=/data/torrents/downloads
+ - UN_RADARR_0_PROTOCOLS=torrent
+ - UN_CMDHOOK_0_COMMAND=chmod -R g+rw "{{ .Path }}"
+ - UN_CMDHOOK_0_EXTRACTED=true
+ - UN_CMDHOOK_0_SHELL=true
+ volumes:
+ - /srv:/data
+ restart: unless-stopped
+
+ recyclarr:
+ image: ghcr.io/recyclarr/recyclarr
+ container_name: recyclarr
+ user: 1001:1001
+ environment:
+ - TZ=America/Los_Angeles
+ - CRON_SCHEDULE=0 4 * * *
+ volumes:
+ - ./recyclarr-config:/config
+ restart: unless-stopped
+
+ # If you add a download client later, mount it the same way:
+ # qbittorrent:
+ # image: linuxserver/qbittorrent
+ # container_name: qbittorrent
+ # environment:
+ # - PUID=1001
+ # - PGID=1001
+ # - TZ=America/Los_Angeles
+ # - UMASK=002
+ # ports:
+ # - 8080:8080
+ # volumes:
+ # - ./qbittorrent-config:/config
+ # - /srv:/data
+ # restart: unless-stopped
diff --git a/jellyfin/setup-media-user.sh b/jellyfin/setup-media-user.sh
new file mode 100755
index 0000000..d470c16
--- /dev/null
+++ b/jellyfin/setup-media-user.sh
@@ -0,0 +1,75 @@
+#!/bin/bash
+set -e
+
+echo "=== Creating dedicated media user (UID 1001) and group (GID 1001) ==="
+
+# Create the media group
+sudo groupadd -g 1001 media
+
+# Create the media user (no home dir, no login shell)
+sudo useradd -u 1001 -g media -s /usr/sbin/nologin -M media
+
+# Add tyler to media group so tyler can access files created by the media user
+sudo usermod -aG media tyler
+
+# Add media user to video and render groups for hardware transcoding access
+sudo usermod -aG video,render media
+
+echo "=== Fixing Jellyfin config/cache ownership ==="
+sudo chown -R 1001:1001 /home/tyler/docker/jellyfin/config
+sudo chown -R 1001:1001 /home/tyler/docker/jellyfin/cache
+
+echo "=== Creating config directories for Sonarr and Radarr ==="
+sudo mkdir -p /home/tyler/docker/jellyfin/sonarr-config
+sudo mkdir -p /home/tyler/docker/jellyfin/radarr-config
+sudo chown -R 1001:1001 /home/tyler/docker/jellyfin/sonarr-config
+sudo chown -R 1001:1001 /home/tyler/docker/jellyfin/radarr-config
+
+echo "=== Setting media group ownership on /srv/ directories ==="
+# Change group to media on all media directories
+sudo chown -R :media /srv/vids /srv/mus /srv/books /srv/photos /srv/roms /srv/torrents
+
+echo "=== Clearing conflicting POSIX ACLs (if any) ==="
+# ACLs from NAS/Samba can override Unix group permissions.
+# Strip them so our chmod/chown rules actually take effect.
+if command -v setfacl &>/dev/null; then
+ sudo setfacl -R -b /srv/vids /srv/mus /srv/books /srv/photos /srv/roms /srv/torrents 2>/dev/null || true
+else
+ echo "Warning: setfacl not found. Install the 'acl' package if you have ACL issues."
+fi
+
+echo "=== Setting directory sticky bit (g+s) so new files inherit media group ==="
+sudo find /srv/vids /srv/mus /srv/books /srv/photos /srv/roms /srv/torrents -type d -exec chmod g+s {} +
+
+echo "=== Setting base permissions ==="
+# Group read+execute on all directories
+sudo find /srv/vids /srv/mus /srv/books /srv/photos /srv/roms /srv/torrents -type d -exec chmod g+rx {} +
+
+# Group read on all files
+sudo find /srv/vids /srv/mus /srv/books /srv/photos /srv/roms /srv/torrents -type f -exec chmod g+r {} +
+
+echo "=== Setting write permissions for Sonarr/Radarr managed directories ==="
+# Allow group write on top-level dirs and recursively within them
+sudo chmod g+w /srv/vids/movies /srv/vids/shows /srv/torrents
+sudo find /srv/vids/movies /srv/vids/shows /srv/torrents -type d -exec chmod g+w {} +
+sudo find /srv/vids/movies /srv/vids/shows /srv/torrents -type f -exec chmod g+w {} +
+
+echo "=== Done! ==="
+echo ""
+echo "You will need to log out and back in (or run 'newgrp media') for the"
+echo "group changes to take effect in your current shell."
+echo ""
+echo "Start the stack with: docker compose up -d"
+echo ""
+echo "=== Web UI Configuration ==="
+echo "Sonarr (http://<host>:8989):"
+echo " - Root folder: /data/vids/shows"
+echo " - Download client path: /data/torrents"
+echo ""
+echo "Radarr (http://<host>:7878):"
+echo " - Root folder: /data/vids/movies"
+echo " - Download client path: /data/torrents"
+echo ""
+echo "If you add a download client later, mount it with /srv:/data too"
+echo "and set its download directory to /data/torrents."
+echo "This ensures paths match and hard links / fast moves work correctly."