diff options
| author | Tyler Hoang <tyler@tylerhoang.xyz> | 2026-07-09 21:49:56 -0700 |
|---|---|---|
| committer | Tyler Hoang <tyler@tylerhoang.xyz> | 2026-07-09 21:49:56 -0700 |
| commit | 1d043702ef136e8f2e516c5304a57be5718f1f6f (patch) | |
| tree | 9b3ad070f11dad5994c58acd41a15db8043745aa /jellyfin | |
12 services: adguard, ampache, azuracast, flood, glance, glances,
grafana, immich, invidious, jellyfin, openwebui, watchtower
Each service includes a docker-compose.yml, README.md with setup
instructions, and .env.example where applicable.
Diffstat (limited to 'jellyfin')
| -rw-r--r-- | jellyfin/.env.example | 2 | ||||
| -rw-r--r-- | jellyfin/README.md | 59 | ||||
| -rw-r--r-- | jellyfin/docker-compose.yml | 133 | ||||
| -rwxr-xr-x | jellyfin/setup-media-user.sh | 75 |
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." |
