Jellyfin media server for Netcup RS 8000
Go to file
Jeff Emmett 4113ccf83d Migrate admin services to jefflix.lol and add Hetzner Storage Box integration
- Move sonarr, radarr, lidarr, prowlarr to *.jefflix.lol
- Move jellyseerr (requests) and qbittorrent (downloads) to jefflix.lol
- Add Hetzner Storage Box SSHFS mount scripts for u521871
- Add media-archive.sh for content migration between local and Hetzner
- Update all volume mounts to use /mnt/hetzner-media for media storage
- Update README with new URLs and tiered storage documentation

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-21 00:49:01 -05:00
scripts Migrate admin services to jefflix.lol and add Hetzner Storage Box integration 2025-12-21 00:49:01 -05:00
.env.example Migrate admin services to jefflix.lol and add Hetzner Storage Box integration 2025-12-21 00:49:01 -05:00
.gitignore Remove R2 storage configs, use local NVMe storage 2025-11-27 21:25:43 -08:00
README.md Migrate admin services to jefflix.lol and add Hetzner Storage Box integration 2025-12-21 00:49:01 -05:00
docker-compose-server.yml Migrate admin services to jefflix.lol and add Hetzner Storage Box integration 2025-12-21 00:49:01 -05:00

README.md

Media Server on Netcup RS 8000

A self-hosted media server stack with automated request management, running on Netcup RS 8000 with Jellyfin for streaming.

Architecture

                    Users
                      │
                      ▼
┌─────────────────────────────────────────────────────────────┐
│              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              │
├─────────────────────────────────────────────────────────────┤
│              Hetzner Storage Box (Optional Tier)            │
│  /media/archive/movies  /media/archive/shows  (Cold storage)│
└─────────────────────────────────────────────────────────────┘

How It Works

  1. Users request media via Jellyseerr (requests.jefflix.lol)
  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
  • 20 CPU cores for transcoding
  • 64GB RAM for caching
  • 3TB NVMe storage for media
  • 1Gbps bandwidth included

Quick Start

1. Clone and Configure

git clone https://gitea.jeffemmett.com/jeffemmett/jellyfin-media.git
cd jellyfin-media
cp .env.example .env

2. Deploy to Netcup

./scripts/deploy-to-netcup.sh

3. Add Cloudflare Tunnel Hostnames

Add these hostnames to your Cloudflare tunnel config (/root/cloudflared/config.yml):

# Media Request System
- hostname: requests.jefflix.lol
  service: http://localhost:80
- hostname: invite.jefflix.lol
  service: http://localhost:80
- hostname: sonarr.jefflix.lol
  service: http://localhost:80
- hostname: radarr.jefflix.lol
  service: http://localhost:80
- hostname: prowlarr.jefflix.lol
  service: http://localhost:80
- hostname: lidarr.jefflix.lol
  service: http://localhost:80
- hostname: downloads.jefflix.lol
  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.jefflix.lol
  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.jefflix.lol
  2. Default login: admin / adminadmin (change immediately!)
  3. Settings → Downloads → Default Save Path: /downloads

Step 3: Configure Radarr (Movies)

  1. Go to https://radarr.jefflix.lol
  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.jefflix.lol
  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.jefflix.lol
  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.jefflix.lol
  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

Step 7: Configure Wizarr (User Invitations)

  1. Go to https://invite.jeffemmett.com
  2. Create your admin account on first visit
  3. Connect to Jellyfin:
    • Server URL: http://jellyfin:8096
    • API Key: Get from Jellyfin → Dashboard → API Keys → Create
  4. Create invite links:
    • Set expiration (1 day, 1 week, never, etc.)
    • Set usage limit (1 use, unlimited, etc.)
    • Share the generated link with friends
  5. When friends use the link, they'll be guided through:
    • Creating their Jellyfin account
    • Downloading mobile apps
    • Connecting to your server

Services

Service URL Description
Jellyfin https://movies.jeffemmett.com Video streaming (movies & TV)
Navidrome https://music.jeffemmett.com Music streaming server
Jellyseerr https://requests.jefflix.lol Media request interface
Wizarr https://invite.jeffemmett.com User invitation system
Sonarr https://sonarr.jefflix.lol TV show management
Radarr https://radarr.jefflix.lol Movie management
Lidarr https://lidarr.jefflix.lol Music management
Prowlarr https://prowlarr.jefflix.lol Indexer management
qBittorrent https://downloads.jefflix.lol Download client

Security Recommendations

Important: The *arr admin interfaces should be protected. Options:

  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

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:

# 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:

# 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
├── config/
│   ├── 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

/mnt/hetzner-media/      # Hetzner Storage Box (optional)
└── archive/
    ├── movies/        # Archived movies
    ├── shows/         # Archived TV shows
    └── music/         # Archived music

Tiered Storage with Hetzner Storage Box

For additional storage capacity, you can add a Hetzner Storage Box as an archive tier. This is cost-effective (~€3/TB/month) and keeps older content accessible without filling up local NVMe.

Architecture

┌─────────────────────────────────────────────────────────────┐
│                    Jellyfin Libraries                       │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  /media (Local NVMe - Fast)      /media/archive (Hetzner)   │
│  ├── movies/  ← Active           ├── movies/  ← Archived    │
│  ├── shows/   ← Recent           ├── shows/   ← Old seasons │
│  └── music/   ← Favorites        └── music/   ← Full lib    │
│                                                             │
│  Hot storage: New downloads      Cold storage: Older media  │
│  Fast transcoding & streaming    Slower but cost-effective  │
└─────────────────────────────────────────────────────────────┘

Setup

  1. Sign up for Hetzner Storage Box: https://www.hetzner.com/storage/storage-box/

    • Recommended: BX21 (5-10TB) for ~€11/month
    • Enable SMB/CIFS in Hetzner Console
  2. Run setup script on Netcup:

    ssh netcup
    cd /opt/media-server
    sudo ./scripts/setup-hetzner-storage.sh
    
  3. Restart media stack:

    docker compose down && docker compose up -d
    
  4. Add archive library in Jellyfin:

    • Dashboard → Libraries → Add Media Library
    • Content type: Movies (or Shows)
    • Folder: /media/archive/movies

Managing Archived Content

Use the media-archive.sh script to move content between local and Hetzner:

# Check storage status
./scripts/media-archive.sh status

# List content older than 90 days
./scripts/media-archive.sh list-old

# Archive a specific movie
./scripts/media-archive.sh archive movies "The Matrix (1999)"

# Archive all shows older than 180 days
./scripts/media-archive.sh archive shows --older-than 180

# Restore content from archive
./scripts/media-archive.sh restore movies "The Matrix (1999)"

# Backup all local media to Hetzner
./scripts/media-archive.sh sync

Pricing Reference

Plan Storage Monthly
BX11 1 TB ~€3.50
BX21 5-10 TB ~€11
BX31 20 TB ~€25
BX41 40 TB ~€47

Upload Script Usage

# From local machine
./scripts/upload-to-netcup.sh <local-path> <media-type>

# Examples:
./scripts/upload-to-netcup.sh /home/jeffe/Shows shows
./scripts/upload-to-netcup.sh /home/jeffe/Movies movies
./scripts/upload-to-netcup.sh /home/jeffe/Music music

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 and provides a Spotify-like self-hosted music experience.

The best mobile experience for Navidrome. Features:

  • Spotify-like UI with gapless playback
  • Offline downloads with smart caching
  • Android Auto, Chromecast, Wear OS support
  • Synced lyrics display
  • Multiple server support

Setup:

  1. Install Symfonium from Google Play (~$5 one-time)
  2. Add Server → Subsonic/Navidrome
  3. Server URL: https://music.jeffemmett.com
  4. Enter your Navidrome username/password

Other Clients

Platform App Cost Notes
Android Symfonium ~$5 Best experience, recommended
Android Ultrasonic Free Solid open-source alternative
iOS Amperfy Free Clean UI, CarPlay support
iOS play:Sub ~$5 Polished experience
Desktop Sonixd Free Electron, cross-platform
Desktop Sublime Music Free GTK-based, Linux-native
Web Built-in Free https://music.jeffemmett.com

Music Features

Sharing

Share tracks/albums with anyone (no account needed):

  1. In Navidrome web UI, click any track/album
  2. Click ⋮ menu → Share
  3. Set expiration (or never)
  4. Copy the public link

Lyrics

Lyrics are automatically fetched from LRCLIB and displayed in Symfonium during playback.

Spotify Playlist Import

Lidarr syncs with your Spotify playlists:

  1. Go to https://lidarr.jefflix.lol
  2. Settings → Import Lists → Add → Spotify Playlists
  3. Authenticate with Spotify
  4. Select playlists to monitor
  5. Lidarr downloads albums from artists in your playlists

Scrobbling (Optional)

Track your listening history with Last.fm or ListenBrainz:

  1. In Navidrome: Settings → Personal → Link account
  2. All plays from web UI and Symfonium will scrobble

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

# Check gluetun logs
docker logs gluetun

# Verify VPN is working
docker exec gluetun curl ifconfig.me

License

MIT