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_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_PASS=changeme
# VPN (optional - for download privacy)
VPN_ENABLED=false
# VPN Configuration (optional - use with --profile vpn)
# Get Wireguard config from your VPN provider
VPN_PROVIDER=mullvad
VPN_USER=
VPN_PASS=
VPN_WIREGUARD_PRIVATE_KEY=
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
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
```
Users
┌─────────────────────────────────────────────────────────────┐
│ Netcup RS 8000 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Jellyfin │ │ Sonarr │ │ Radarr │ │
│ │ (Stream) │ │ (TV) │ │ (Movies) │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ └────────────────┼────────────────┘ │
│ │ │
│ ┌───────┴───────┐ │
│ │ Local NVMe │ │
│ │ Storage │ │
│ │ (3TB) │ │
│ └───────────────┘ │
│ Cloudflare Tunnel → Traefik │
└─────────────────────────────────────────────────────────────┘
┌─────────────┼─────────────┐
▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌───────────┐
│ Jellyfin │ │Jellyseerr │ │ Navidrome │
│ (Stream) │ │(Requests) │ │ (Music) │
└─────┬─────┘ └─────┬─────┘ └───────────┘
│ │
│ ▼
│ ┌─────────────────────┐
│ │ 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
- **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
```bash
git clone https://gitea.jeffemmett.com/jeffemmett/plex-media.git
cd plex-media
git clone https://gitea.jeffemmett.com/jeffemmett/jellyfin-media.git
cd jellyfin-media
cp .env.example .env
```
@ -46,55 +77,132 @@ cp .env.example .env
./scripts/deploy-to-netcup.sh
```
### 3. Upload Your Media
### 3. Add Cloudflare Tunnel Hostnames
```bash
# Upload TV shows
./scripts/upload-to-netcup.sh /home/jeffe/Shows shows
Add these hostnames to your Cloudflare tunnel config (`/root/cloudflared/config.yml`):
# Upload movies
./scripts/upload-to-netcup.sh /path/to/movies movies
```yaml
# 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
| Service | Port | Description |
|---------|------|-------------|
| Jellyfin | 8096 | Video streaming (movies & TV) |
| Navidrome | 4533 | Music streaming server |
| Sonarr | 8989 | TV show management |
| Radarr | 7878 | Movie management |
| Prowlarr | 9696 | Indexer management |
| Transmission | 9091 | Download client |
| Service | URL | Description |
|---------|-----|-------------|
| Jellyfin | https://movies.jeffemmett.com | Video streaming (movies & TV) |
| Navidrome | https://music.jeffemmett.com | Music streaming server |
| Jellyseerr | https://requests.jeffemmett.com | Media request interface |
| Sonarr | https://sonarr.jeffemmett.com | TV show management |
| Radarr | https://radarr.jeffemmett.com | Movie management |
| 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:
- **Movies & TV**: https://movies.jeffemmett.com (Jellyfin)
- **Music**: https://music.jeffemmett.com (Navidrome)
**Important:** The \*arr admin interfaces should be protected. Options:
### 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:
- **Android**: Ultrasonic, Symfonium, DSub
- **iOS**: play:Sub, Amperfy, SubStreamer
- **Desktop**: Sonixd, Sublime Music
To add Cloudflare Access protection:
1. Go to Cloudflare Zero Trust → Access → Applications
2. Create application for each admin subdomain
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
```
/opt/media-server/
├── media/
│ ├── movies/ # Movie files
│ ├── shows/ # TV show files
│ └── music/ # Music files
│ ├── movies/ # Movie files
│ ├── shows/ # TV show files
│ └── music/ # Music files
├── config/
│ ├── jellyfin/ # Jellyfin config
│ ├── sonarr/ # Sonarr config
│ ├── radarr/ # Radarr config
│ └── prowlarr/ # Prowlarr config
└── downloads/
└── complete/ # Completed downloads
│ ├── jellyfin/ # Jellyfin config
│ ├── jellyseerr/ # Jellyseerr config
│ ├── sonarr/ # Sonarr config
│ ├── radarr/ # Radarr config
│ ├── lidarr/ # Lidarr config
│ ├── prowlarr/ # Prowlarr config
│ └── qbittorrent/ # qBittorrent config
├── downloads/
│ └── complete/ # Completed downloads
└── cache/
└── jellyfin/ # Transcoding cache
```
## Upload Script Usage
@ -111,6 +219,39 @@ Navidrome is Subsonic-compatible. Use any Subsonic client:
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
MIT

View File

@ -23,6 +23,28 @@ services:
- "traefik.http.middlewares.jellyfin-headers.headers.customRequestHeaders.X-Forwarded-Proto=https"
- "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:
image: deluan/navidrome:latest
container_name: navidrome
@ -63,10 +85,15 @@ services:
- ./config/sonarr:/config
- ./media/shows:/tv
- ./downloads:/downloads
ports:
- 8989:8989
networks:
- 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:
image: linuxserver/radarr:latest
@ -80,10 +107,15 @@ services:
- ./config/radarr:/config
- ./media/movies:/movies
- ./downloads:/downloads
ports:
- 7878:7878
networks:
- 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:
image: linuxserver/prowlarr:latest
@ -95,11 +127,93 @@ services:
- TZ=Europe/Berlin
volumes:
- ./config/prowlarr:/config
ports:
- 9696:9696
networks:
- 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:
image: linuxserver/transmission:latest
container_name: transmission
@ -119,6 +233,8 @@ services:
- 51413:51413/udp
networks:
- media-network
profiles:
- legacy
networks:
media-network:

View File

@ -48,9 +48,9 @@ ssh $REMOTE_HOST << 'REMOTE_SCRIPT'
cd /opt/media-server
# 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 downloads/{complete/movies,complete/tv}
mkdir -p downloads/{complete/movies,complete/tv,complete/music}
mkdir -p media/{movies,shows,music}
# Set permissions
@ -77,10 +77,18 @@ echo ""
echo "Access your services:"
echo " Jellyfin: https://movies.jeffemmett.com"
echo " Music: https://music.jeffemmett.com"
echo " Sonarr: http://SERVER_IP:8989"
echo " Radarr: http://SERVER_IP:7878"
echo " Prowlarr: http://SERVER_IP:9696"
echo " Transmission: http://SERVER_IP:9091"
echo " Requests: https://requests.jeffemmett.com"
echo " Sonarr: https://sonarr.jeffemmett.com"
echo " Radarr: https://radarr.jeffemmett.com"
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 "SSH to server: ssh netcup"
echo "View logs: ssh netcup 'cd /opt/media-server && docker compose logs -f'"