Add Jellyseerr request system and *arr stack integration

- Add Jellyseerr for media request management
- Add Lidarr for music automation
- Replace Transmission with qBittorrent
- Add optional Gluetun VPN container
- Add Traefik labels for all services (sonarr, radarr, lidarr, prowlarr, downloads, requests)
- Update README with architecture diagram, setup guide, and service URLs
- Update deploy script with new config directories

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2025-12-19 00:28:37 -05:00
parent ed267545d7
commit dabdfea451
4 changed files with 337 additions and 65 deletions

View File

@ -17,12 +17,19 @@ ND_SCANSCHEDULE=1h
ND_LOGLEVEL=info ND_LOGLEVEL=info
ND_SESSIONTIMEOUT=24h ND_SESSIONTIMEOUT=24h
# Transmission (optional auth) # qBittorrent (default download client)
# Default login: admin / adminadmin (change in WebUI after first login)
# Transmission (legacy - use with --profile legacy)
TRANSMISSION_USER=admin TRANSMISSION_USER=admin
TRANSMISSION_PASS=changeme TRANSMISSION_PASS=changeme
# VPN (optional - for download privacy) # VPN Configuration (optional - use with --profile vpn)
VPN_ENABLED=false # Get Wireguard config from your VPN provider
VPN_PROVIDER=mullvad VPN_PROVIDER=mullvad
VPN_USER= VPN_WIREGUARD_PRIVATE_KEY=
VPN_PASS= VPN_WIREGUARD_ADDRESS=
VPN_COUNTRY=Germany
# Jellyseerr
# Configure via web UI at https://requests.jeffemmett.com

237
README.md
View File

@ -1,27 +1,58 @@
# Media Server on Netcup RS 8000 # Media Server on Netcup RS 8000
A self-hosted media server stack running on Netcup RS 8000 with Jellyfin for streaming. A self-hosted media server stack with automated request management, running on Netcup RS 8000 with Jellyfin for streaming.
## Architecture ## Architecture
``` ```
Users
┌─────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────┐
│ Netcup RS 8000 │ │ Cloudflare Tunnel → Traefik │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ └─────────────────────────────────────────────────────────────┘
│ │ Jellyfin │ │ Sonarr │ │ Radarr │ │
│ │ (Stream) │ │ (TV) │ │ (Movies) │ │ ┌─────────────┼─────────────┐
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ ▼ ▼ ▼
│ │ │ │ │ ┌───────────┐ ┌───────────┐ ┌───────────┐
│ └────────────────┼────────────────┘ │ │ Jellyfin │ │Jellyseerr │ │ Navidrome │
│ │ │ │ (Stream) │ │(Requests) │ │ (Music) │
│ ┌───────┴───────┐ │ └─────┬─────┘ └─────┬─────┘ └───────────┘
│ │ Local NVMe │ │ │ │
│ │ Storage │ │ │ ▼
│ │ (3TB) │ │ │ ┌─────────────────────┐
│ └───────────────┘ │ │ │ Request Flow │
│ │ ┌───────────────┐ │
│ │ │ Sonarr │ │ ← TV Shows
│ │ │ Radarr │ │ ← Movies
│ │ │ Lidarr │ │ ← Music
│ │ └───────┬───────┘ │
│ │ │ │
│ │ ┌───────▼───────┐ │
│ │ │ Prowlarr │ │ ← Indexer Management
│ │ └───────┬───────┘ │
│ │ │ │
│ │ ┌───────▼───────┐ │
│ │ │ qBittorrent │ │ ← Downloads (optional VPN)
│ │ └───────┬───────┘ │
│ └──────────┼──────────┘
│ │
▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ Local NVMe Storage (3TB) │
│ /media/movies /media/shows /media/music │
└─────────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────────┘
``` ```
## How It Works
1. **Users request media** via Jellyseerr (`requests.jeffemmett.com`)
2. **Jellyseerr forwards** requests to Sonarr/Radarr/Lidarr
3. **Prowlarr searches** configured indexers for the content
4. **qBittorrent downloads** the files (optionally through VPN)
5. **\*arr apps organize** files into proper folders
6. **Jellyfin detects** new media and makes it available for streaming
## Server Specs ## Server Specs
- **Netcup RS 8000 G12 Pro**: €45/month - **Netcup RS 8000 G12 Pro**: €45/month
@ -35,8 +66,8 @@ A self-hosted media server stack running on Netcup RS 8000 with Jellyfin for str
### 1. Clone and Configure ### 1. Clone and Configure
```bash ```bash
git clone https://gitea.jeffemmett.com/jeffemmett/plex-media.git git clone https://gitea.jeffemmett.com/jeffemmett/jellyfin-media.git
cd plex-media cd jellyfin-media
cp .env.example .env cp .env.example .env
``` ```
@ -46,55 +77,132 @@ cp .env.example .env
./scripts/deploy-to-netcup.sh ./scripts/deploy-to-netcup.sh
``` ```
### 3. Upload Your Media ### 3. Add Cloudflare Tunnel Hostnames
```bash Add these hostnames to your Cloudflare tunnel config (`/root/cloudflared/config.yml`):
# Upload TV shows
./scripts/upload-to-netcup.sh /home/jeffe/Shows shows
# Upload movies ```yaml
./scripts/upload-to-netcup.sh /path/to/movies movies # Media Request System
- hostname: requests.jeffemmett.com
service: http://localhost:80
- hostname: sonarr.jeffemmett.com
service: http://localhost:80
- hostname: radarr.jeffemmett.com
service: http://localhost:80
- hostname: prowlarr.jeffemmett.com
service: http://localhost:80
- hostname: lidarr.jeffemmett.com
service: http://localhost:80
- hostname: downloads.jeffemmett.com
service: http://localhost:80
``` ```
Then restart cloudflared: `docker restart cloudflared`
### 4. Configure Services (First Time Setup)
#### Step 1: Configure Prowlarr (Indexers)
1. Go to `https://prowlarr.jeffemmett.com`
2. Add indexers (torrent sites you have access to)
3. Go to Settings → Apps → Add Radarr, Sonarr, Lidarr
#### Step 2: Configure Download Client
1. Go to `https://downloads.jeffemmett.com`
2. Default login: `admin` / `adminadmin` (change immediately!)
3. Settings → Downloads → Default Save Path: `/downloads`
#### Step 3: Configure Radarr (Movies)
1. Go to `https://radarr.jeffemmett.com`
2. Settings → Media Management → Root Folder: `/movies`
3. Settings → Download Clients → Add qBittorrent:
- Host: `qbittorrent`
- Port: `8080`
#### Step 4: Configure Sonarr (TV Shows)
1. Go to `https://sonarr.jeffemmett.com`
2. Settings → Media Management → Root Folder: `/tv`
3. Settings → Download Clients → Add qBittorrent (same as above)
#### Step 5: Configure Lidarr (Music)
1. Go to `https://lidarr.jeffemmett.com`
2. Settings → Media Management → Root Folder: `/music`
3. Settings → Download Clients → Add qBittorrent (same as above)
#### Step 6: Configure Jellyseerr (Requests)
1. Go to `https://requests.jeffemmett.com`
2. Sign in with Jellyfin (use your Jellyfin server URL: `http://jellyfin:8096`)
3. Add Radarr/Sonarr servers:
- Hostname: `radarr` or `sonarr` (internal Docker hostname)
- Port: `7878` (Radarr) or `8989` (Sonarr)
- API Key: Get from Radarr/Sonarr Settings → General
## Services ## Services
| Service | Port | Description | | Service | URL | Description |
|---------|------|-------------| |---------|-----|-------------|
| Jellyfin | 8096 | Video streaming (movies & TV) | | Jellyfin | https://movies.jeffemmett.com | Video streaming (movies & TV) |
| Navidrome | 4533 | Music streaming server | | Navidrome | https://music.jeffemmett.com | Music streaming server |
| Sonarr | 8989 | TV show management | | Jellyseerr | https://requests.jeffemmett.com | Media request interface |
| Radarr | 7878 | Movie management | | Sonarr | https://sonarr.jeffemmett.com | TV show management |
| Prowlarr | 9696 | Indexer management | | Radarr | https://radarr.jeffemmett.com | Movie management |
| Transmission | 9091 | Download client | | Lidarr | https://lidarr.jeffemmett.com | Music management |
| Prowlarr | https://prowlarr.jeffemmett.com | Indexer management |
| qBittorrent | https://downloads.jeffemmett.com | Download client |
## Access ## Security Recommendations
All services accessible via Cloudflare Tunnel: **Important:** The \*arr admin interfaces should be protected. Options:
- **Movies & TV**: https://movies.jeffemmett.com (Jellyfin)
- **Music**: https://music.jeffemmett.com (Navidrome)
### Navidrome Mobile Apps 1. **Cloudflare Access** (Recommended): Add authentication via Zero Trust dashboard
2. **SSH Tunnel**: Access admin UIs only via `ssh -L 8989:localhost:8989 netcup`
3. **Basic Auth**: Add Traefik middleware for HTTP basic auth
Navidrome is Subsonic-compatible. Use any Subsonic client: To add Cloudflare Access protection:
- **Android**: Ultrasonic, Symfonium, DSub 1. Go to Cloudflare Zero Trust → Access → Applications
- **iOS**: play:Sub, Amperfy, SubStreamer 2. Create application for each admin subdomain
- **Desktop**: Sonixd, Sublime Music 3. Add authentication policy (email, one-time PIN, etc.)
## VPN Support
For download privacy, enable the VPN profile:
```bash
# Add to .env:
VPN_PROVIDER=mullvad
VPN_WIREGUARD_PRIVATE_KEY=your_private_key
VPN_WIREGUARD_ADDRESS=10.x.x.x/32
# Start with VPN:
docker compose --profile vpn up -d
```
Then modify qBittorrent to route through gluetun:
```yaml
# In docker-compose-server.yml, uncomment:
network_mode: "service:gluetun"
# And comment out the networks/labels sections
```
## Folder Structure ## Folder Structure
``` ```
/opt/media-server/ /opt/media-server/
├── media/ ├── media/
│ ├── movies/ # Movie files │ ├── movies/ # Movie files
│ ├── shows/ # TV show files │ ├── shows/ # TV show files
│ └── music/ # Music files │ └── music/ # Music files
├── config/ ├── config/
│ ├── jellyfin/ # Jellyfin config │ ├── jellyfin/ # Jellyfin config
│ ├── sonarr/ # Sonarr config │ ├── jellyseerr/ # Jellyseerr config
│ ├── radarr/ # Radarr config │ ├── sonarr/ # Sonarr config
│ └── prowlarr/ # Prowlarr config │ ├── radarr/ # Radarr config
└── downloads/ │ ├── lidarr/ # Lidarr config
└── complete/ # Completed downloads │ ├── prowlarr/ # Prowlarr config
│ └── qbittorrent/ # qBittorrent config
├── downloads/
│ └── complete/ # Completed downloads
└── cache/
└── jellyfin/ # Transcoding cache
``` ```
## Upload Script Usage ## Upload Script Usage
@ -111,6 +219,39 @@ Navidrome is Subsonic-compatible. Use any Subsonic client:
The script uses rsync for efficient incremental uploads. The script uses rsync for efficient incremental uploads.
## Mobile Apps
### Jellyfin (Video)
- **Android**: Jellyfin for Android
- **iOS**: Swiftfin, Jellyfin Mobile
### Navidrome (Music)
Navidrome is Subsonic-compatible. Use any Subsonic client:
- **Android**: Ultrasonic, Symfonium, DSub
- **iOS**: play:Sub, Amperfy, SubStreamer
- **Desktop**: Sonixd, Sublime Music
## Troubleshooting
### Downloads not starting
1. Check Prowlarr has working indexers
2. Verify qBittorrent is accessible: `docker logs qbittorrent`
3. Check Radarr/Sonarr logs for errors
### Media not appearing in Jellyfin
1. Verify files are in correct folder (`/media/movies`, `/media/shows`)
2. Trigger library scan in Jellyfin → Dashboard → Libraries
3. Check file permissions: should be owned by PUID:PGID (1000:1000)
### VPN connection issues
```bash
# Check gluetun logs
docker logs gluetun
# Verify VPN is working
docker exec gluetun curl ifconfig.me
```
## License ## License
MIT MIT

View File

@ -23,6 +23,28 @@ services:
- "traefik.http.middlewares.jellyfin-headers.headers.customRequestHeaders.X-Forwarded-Proto=https" - "traefik.http.middlewares.jellyfin-headers.headers.customRequestHeaders.X-Forwarded-Proto=https"
- "traefik.docker.network=traefik-public" - "traefik.docker.network=traefik-public"
# Request Management - User-facing interface for media requests
jellyseerr:
image: fallenbagel/jellyseerr:latest
container_name: jellyseerr
restart: unless-stopped
environment:
- LOG_LEVEL=debug
- TZ=Europe/Berlin
volumes:
- ./config/jellyseerr:/app/config
networks:
- media-network
- traefik-public
labels:
- "traefik.enable=true"
- "traefik.http.routers.jellyseerr.rule=Host(`requests.jeffemmett.com`)"
- "traefik.http.routers.jellyseerr.entrypoints=web"
- "traefik.http.routers.jellyseerr.middlewares=jellyseerr-headers"
- "traefik.http.services.jellyseerr.loadbalancer.server.port=5055"
- "traefik.http.middlewares.jellyseerr-headers.headers.customRequestHeaders.X-Forwarded-Proto=https"
- "traefik.docker.network=traefik-public"
navidrome: navidrome:
image: deluan/navidrome:latest image: deluan/navidrome:latest
container_name: navidrome container_name: navidrome
@ -63,10 +85,15 @@ services:
- ./config/sonarr:/config - ./config/sonarr:/config
- ./media/shows:/tv - ./media/shows:/tv
- ./downloads:/downloads - ./downloads:/downloads
ports:
- 8989:8989
networks: networks:
- media-network - media-network
- traefik-public
labels:
- "traefik.enable=true"
- "traefik.http.routers.sonarr.rule=Host(`sonarr.jeffemmett.com`)"
- "traefik.http.routers.sonarr.entrypoints=web"
- "traefik.http.services.sonarr.loadbalancer.server.port=8989"
- "traefik.docker.network=traefik-public"
radarr: radarr:
image: linuxserver/radarr:latest image: linuxserver/radarr:latest
@ -80,10 +107,15 @@ services:
- ./config/radarr:/config - ./config/radarr:/config
- ./media/movies:/movies - ./media/movies:/movies
- ./downloads:/downloads - ./downloads:/downloads
ports:
- 7878:7878
networks: networks:
- media-network - media-network
- traefik-public
labels:
- "traefik.enable=true"
- "traefik.http.routers.radarr.rule=Host(`radarr.jeffemmett.com`)"
- "traefik.http.routers.radarr.entrypoints=web"
- "traefik.http.services.radarr.loadbalancer.server.port=7878"
- "traefik.docker.network=traefik-public"
prowlarr: prowlarr:
image: linuxserver/prowlarr:latest image: linuxserver/prowlarr:latest
@ -95,11 +127,93 @@ services:
- TZ=Europe/Berlin - TZ=Europe/Berlin
volumes: volumes:
- ./config/prowlarr:/config - ./config/prowlarr:/config
ports:
- 9696:9696
networks: networks:
- media-network - media-network
- traefik-public
labels:
- "traefik.enable=true"
- "traefik.http.routers.prowlarr.rule=Host(`prowlarr.jeffemmett.com`)"
- "traefik.http.routers.prowlarr.entrypoints=web"
- "traefik.http.services.prowlarr.loadbalancer.server.port=9696"
- "traefik.docker.network=traefik-public"
lidarr:
image: linuxserver/lidarr:latest
container_name: lidarr
restart: unless-stopped
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Berlin
volumes:
- ./config/lidarr:/config
- ./media/music:/music
- ./downloads:/downloads
networks:
- media-network
- traefik-public
labels:
- "traefik.enable=true"
- "traefik.http.routers.lidarr.rule=Host(`lidarr.jeffemmett.com`)"
- "traefik.http.routers.lidarr.entrypoints=web"
- "traefik.http.services.lidarr.loadbalancer.server.port=8686"
- "traefik.docker.network=traefik-public"
# VPN for download privacy (optional - enable in .env)
gluetun:
image: qmcgaw/gluetun:latest
container_name: gluetun
restart: unless-stopped
cap_add:
- NET_ADMIN
devices:
- /dev/net/tun:/dev/net/tun
environment:
- VPN_SERVICE_PROVIDER=${VPN_PROVIDER:-mullvad}
- VPN_TYPE=wireguard
- WIREGUARD_PRIVATE_KEY=${VPN_WIREGUARD_PRIVATE_KEY:-}
- WIREGUARD_ADDRESSES=${VPN_WIREGUARD_ADDRESS:-}
- SERVER_COUNTRIES=${VPN_COUNTRY:-Germany}
- TZ=Europe/Berlin
volumes:
- ./config/gluetun:/gluetun
ports:
# qBittorrent WebUI
- 8080:8080
# BitTorrent ports
- 6881:6881
- 6881:6881/udp
networks:
- media-network
profiles:
- vpn
# Download client (routes through VPN when vpn profile enabled)
qbittorrent:
image: linuxserver/qbittorrent:latest
container_name: qbittorrent
restart: unless-stopped
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Berlin
- WEBUI_PORT=8080
volumes:
- ./config/qbittorrent:/config
- ./downloads:/downloads
# When VPN enabled, use gluetun network
# network_mode: "service:gluetun"
networks:
- media-network
- traefik-public
labels:
- "traefik.enable=true"
- "traefik.http.routers.qbittorrent.rule=Host(`downloads.jeffemmett.com`)"
- "traefik.http.routers.qbittorrent.entrypoints=web"
- "traefik.http.services.qbittorrent.loadbalancer.server.port=8080"
- "traefik.docker.network=traefik-public"
# Legacy Transmission (kept for compatibility, prefer qBittorrent)
transmission: transmission:
image: linuxserver/transmission:latest image: linuxserver/transmission:latest
container_name: transmission container_name: transmission
@ -119,6 +233,8 @@ services:
- 51413:51413/udp - 51413:51413/udp
networks: networks:
- media-network - media-network
profiles:
- legacy
networks: networks:
media-network: media-network:

View File

@ -48,9 +48,9 @@ ssh $REMOTE_HOST << 'REMOTE_SCRIPT'
cd /opt/media-server cd /opt/media-server
# Create required directories # Create required directories
mkdir -p config/{jellyfin,sonarr,radarr,prowlarr,transmission} mkdir -p config/{jellyfin,jellyseerr,sonarr,radarr,prowlarr,lidarr,qbittorrent,gluetun,navidrome}
mkdir -p cache/jellyfin mkdir -p cache/jellyfin
mkdir -p downloads/{complete/movies,complete/tv} mkdir -p downloads/{complete/movies,complete/tv,complete/music}
mkdir -p media/{movies,shows,music} mkdir -p media/{movies,shows,music}
# Set permissions # Set permissions
@ -77,10 +77,18 @@ echo ""
echo "Access your services:" echo "Access your services:"
echo " Jellyfin: https://movies.jeffemmett.com" echo " Jellyfin: https://movies.jeffemmett.com"
echo " Music: https://music.jeffemmett.com" echo " Music: https://music.jeffemmett.com"
echo " Sonarr: http://SERVER_IP:8989" echo " Requests: https://requests.jeffemmett.com"
echo " Radarr: http://SERVER_IP:7878" echo " Sonarr: https://sonarr.jeffemmett.com"
echo " Prowlarr: http://SERVER_IP:9696" echo " Radarr: https://radarr.jeffemmett.com"
echo " Transmission: http://SERVER_IP:9091" echo " Lidarr: https://lidarr.jeffemmett.com"
echo " Prowlarr: https://prowlarr.jeffemmett.com"
echo " Downloads: https://downloads.jeffemmett.com"
echo ""
echo "Next steps:"
echo " 1. Add hostnames to Cloudflare tunnel config (see README.md)"
echo " 2. Configure Prowlarr with indexers"
echo " 3. Configure Radarr/Sonarr/Lidarr with download client"
echo " 4. Configure Jellyseerr to connect to Jellyfin"
echo "" echo ""
echo "SSH to server: ssh netcup" echo "SSH to server: ssh netcup"
echo "View logs: ssh netcup 'cd /opt/media-server && docker compose logs -f'" echo "View logs: ssh netcup 'cd /opt/media-server && docker compose logs -f'"