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>
This commit is contained in:
parent
3b85484d65
commit
4113ccf83d
|
|
@ -33,3 +33,9 @@ VPN_COUNTRY=Germany
|
||||||
|
|
||||||
# Jellyseerr
|
# Jellyseerr
|
||||||
# Configure via web UI at https://requests.jeffemmett.com
|
# Configure via web UI at https://requests.jeffemmett.com
|
||||||
|
|
||||||
|
# Hetzner Storage Box (Optional - for tiered storage/archival)
|
||||||
|
# Sign up at: https://www.hetzner.com/storage/storage-box/
|
||||||
|
# After signing up, run: sudo ./scripts/setup-hetzner-storage.sh
|
||||||
|
HETZNER_USER=
|
||||||
|
HETZNER_PASS=
|
||||||
|
|
|
||||||
128
README.md
128
README.md
|
|
@ -41,12 +41,15 @@ A self-hosted media server stack with automated request management, running on N
|
||||||
┌─────────────────────────────────────────────────────────────┐
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
│ Local NVMe Storage (3TB) │
|
│ Local NVMe Storage (3TB) │
|
||||||
│ /media/movies /media/shows /media/music │
|
│ /media/movies /media/shows /media/music │
|
||||||
|
├─────────────────────────────────────────────────────────────┤
|
||||||
|
│ Hetzner Storage Box (Optional Tier) │
|
||||||
|
│ /media/archive/movies /media/archive/shows (Cold storage)│
|
||||||
└─────────────────────────────────────────────────────────────┘
|
└─────────────────────────────────────────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
## How It Works
|
## How It Works
|
||||||
|
|
||||||
1. **Users request media** via Jellyseerr (`requests.jeffemmett.com`)
|
1. **Users request media** via Jellyseerr (`requests.jefflix.lol`)
|
||||||
2. **Jellyseerr forwards** requests to Sonarr/Radarr/Lidarr
|
2. **Jellyseerr forwards** requests to Sonarr/Radarr/Lidarr
|
||||||
3. **Prowlarr searches** configured indexers for the content
|
3. **Prowlarr searches** configured indexers for the content
|
||||||
4. **qBittorrent downloads** the files (optionally through VPN)
|
4. **qBittorrent downloads** the files (optionally through VPN)
|
||||||
|
|
@ -83,19 +86,19 @@ Add these hostnames to your Cloudflare tunnel config (`/root/cloudflared/config.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
# Media Request System
|
# Media Request System
|
||||||
- hostname: requests.jeffemmett.com
|
- hostname: requests.jefflix.lol
|
||||||
service: http://localhost:80
|
service: http://localhost:80
|
||||||
- hostname: invite.jeffemmett.com
|
- hostname: invite.jefflix.lol
|
||||||
service: http://localhost:80
|
service: http://localhost:80
|
||||||
- hostname: sonarr.jeffemmett.com
|
- hostname: sonarr.jefflix.lol
|
||||||
service: http://localhost:80
|
service: http://localhost:80
|
||||||
- hostname: radarr.jeffemmett.com
|
- hostname: radarr.jefflix.lol
|
||||||
service: http://localhost:80
|
service: http://localhost:80
|
||||||
- hostname: prowlarr.jeffemmett.com
|
- hostname: prowlarr.jefflix.lol
|
||||||
service: http://localhost:80
|
service: http://localhost:80
|
||||||
- hostname: lidarr.jeffemmett.com
|
- hostname: lidarr.jefflix.lol
|
||||||
service: http://localhost:80
|
service: http://localhost:80
|
||||||
- hostname: downloads.jeffemmett.com
|
- hostname: downloads.jefflix.lol
|
||||||
service: http://localhost:80
|
service: http://localhost:80
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -104,34 +107,34 @@ Then restart cloudflared: `docker restart cloudflared`
|
||||||
### 4. Configure Services (First Time Setup)
|
### 4. Configure Services (First Time Setup)
|
||||||
|
|
||||||
#### Step 1: Configure Prowlarr (Indexers)
|
#### Step 1: Configure Prowlarr (Indexers)
|
||||||
1. Go to `https://prowlarr.jeffemmett.com`
|
1. Go to `https://prowlarr.jefflix.lol`
|
||||||
2. Add indexers (torrent sites you have access to)
|
2. Add indexers (torrent sites you have access to)
|
||||||
3. Go to Settings → Apps → Add Radarr, Sonarr, Lidarr
|
3. Go to Settings → Apps → Add Radarr, Sonarr, Lidarr
|
||||||
|
|
||||||
#### Step 2: Configure Download Client
|
#### Step 2: Configure Download Client
|
||||||
1. Go to `https://downloads.jeffemmett.com`
|
1. Go to `https://downloads.jefflix.lol`
|
||||||
2. Default login: `admin` / `adminadmin` (change immediately!)
|
2. Default login: `admin` / `adminadmin` (change immediately!)
|
||||||
3. Settings → Downloads → Default Save Path: `/downloads`
|
3. Settings → Downloads → Default Save Path: `/downloads`
|
||||||
|
|
||||||
#### Step 3: Configure Radarr (Movies)
|
#### Step 3: Configure Radarr (Movies)
|
||||||
1. Go to `https://radarr.jeffemmett.com`
|
1. Go to `https://radarr.jefflix.lol`
|
||||||
2. Settings → Media Management → Root Folder: `/movies`
|
2. Settings → Media Management → Root Folder: `/movies`
|
||||||
3. Settings → Download Clients → Add qBittorrent:
|
3. Settings → Download Clients → Add qBittorrent:
|
||||||
- Host: `qbittorrent`
|
- Host: `qbittorrent`
|
||||||
- Port: `8080`
|
- Port: `8080`
|
||||||
|
|
||||||
#### Step 4: Configure Sonarr (TV Shows)
|
#### Step 4: Configure Sonarr (TV Shows)
|
||||||
1. Go to `https://sonarr.jeffemmett.com`
|
1. Go to `https://sonarr.jefflix.lol`
|
||||||
2. Settings → Media Management → Root Folder: `/tv`
|
2. Settings → Media Management → Root Folder: `/tv`
|
||||||
3. Settings → Download Clients → Add qBittorrent (same as above)
|
3. Settings → Download Clients → Add qBittorrent (same as above)
|
||||||
|
|
||||||
#### Step 5: Configure Lidarr (Music)
|
#### Step 5: Configure Lidarr (Music)
|
||||||
1. Go to `https://lidarr.jeffemmett.com`
|
1. Go to `https://lidarr.jefflix.lol`
|
||||||
2. Settings → Media Management → Root Folder: `/music`
|
2. Settings → Media Management → Root Folder: `/music`
|
||||||
3. Settings → Download Clients → Add qBittorrent (same as above)
|
3. Settings → Download Clients → Add qBittorrent (same as above)
|
||||||
|
|
||||||
#### Step 6: Configure Jellyseerr (Requests)
|
#### Step 6: Configure Jellyseerr (Requests)
|
||||||
1. Go to `https://requests.jeffemmett.com`
|
1. Go to `https://requests.jefflix.lol`
|
||||||
2. Sign in with Jellyfin (use your Jellyfin server URL: `http://jellyfin:8096`)
|
2. Sign in with Jellyfin (use your Jellyfin server URL: `http://jellyfin:8096`)
|
||||||
3. Add Radarr/Sonarr servers:
|
3. Add Radarr/Sonarr servers:
|
||||||
- Hostname: `radarr` or `sonarr` (internal Docker hostname)
|
- Hostname: `radarr` or `sonarr` (internal Docker hostname)
|
||||||
|
|
@ -159,13 +162,13 @@ Then restart cloudflared: `docker restart cloudflared`
|
||||||
|---------|-----|-------------|
|
|---------|-----|-------------|
|
||||||
| Jellyfin | https://movies.jeffemmett.com | Video streaming (movies & TV) |
|
| Jellyfin | https://movies.jeffemmett.com | Video streaming (movies & TV) |
|
||||||
| Navidrome | https://music.jeffemmett.com | Music streaming server |
|
| Navidrome | https://music.jeffemmett.com | Music streaming server |
|
||||||
| Jellyseerr | https://requests.jeffemmett.com | Media request interface |
|
| Jellyseerr | https://requests.jefflix.lol | Media request interface |
|
||||||
| Wizarr | https://invite.jeffemmett.com | User invitation system |
|
| Wizarr | https://invite.jeffemmett.com | User invitation system |
|
||||||
| Sonarr | https://sonarr.jeffemmett.com | TV show management |
|
| Sonarr | https://sonarr.jefflix.lol | TV show management |
|
||||||
| Radarr | https://radarr.jeffemmett.com | Movie management |
|
| Radarr | https://radarr.jefflix.lol | Movie management |
|
||||||
| Lidarr | https://lidarr.jeffemmett.com | Music management |
|
| Lidarr | https://lidarr.jefflix.lol | Music management |
|
||||||
| Prowlarr | https://prowlarr.jeffemmett.com | Indexer management |
|
| Prowlarr | https://prowlarr.jefflix.lol | Indexer management |
|
||||||
| qBittorrent | https://downloads.jeffemmett.com | Download client |
|
| qBittorrent | https://downloads.jefflix.lol | Download client |
|
||||||
|
|
||||||
## Security Recommendations
|
## Security Recommendations
|
||||||
|
|
||||||
|
|
@ -221,8 +224,91 @@ network_mode: "service:gluetun"
|
||||||
│ └── complete/ # Completed downloads
|
│ └── complete/ # Completed downloads
|
||||||
└── cache/
|
└── cache/
|
||||||
└── jellyfin/ # Transcoding 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**:
|
||||||
|
```bash
|
||||||
|
ssh netcup
|
||||||
|
cd /opt/media-server
|
||||||
|
sudo ./scripts/setup-hetzner-storage.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Restart media stack**:
|
||||||
|
```bash
|
||||||
|
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:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 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
|
## Upload Script Usage
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
@ -288,7 +374,7 @@ Lyrics are automatically fetched from LRCLIB and displayed in Symfonium during p
|
||||||
|
|
||||||
### Spotify Playlist Import
|
### Spotify Playlist Import
|
||||||
Lidarr syncs with your Spotify playlists:
|
Lidarr syncs with your Spotify playlists:
|
||||||
1. Go to `https://lidarr.jeffemmett.com`
|
1. Go to `https://lidarr.jefflix.lol`
|
||||||
2. Settings → Import Lists → Add → Spotify Playlists
|
2. Settings → Import Lists → Add → Spotify Playlists
|
||||||
3. Authenticate with Spotify
|
3. Authenticate with Spotify
|
||||||
4. Select playlists to monitor
|
4. Select playlists to monitor
|
||||||
|
|
|
||||||
|
|
@ -10,13 +10,14 @@ services:
|
||||||
volumes:
|
volumes:
|
||||||
- ./config/jellyfin:/config
|
- ./config/jellyfin:/config
|
||||||
- ./cache/jellyfin:/cache
|
- ./cache/jellyfin:/cache
|
||||||
- ./media:/media
|
# Hetzner Storage Box - all media served from here
|
||||||
|
- /mnt/hetzner-media/media:/media:ro
|
||||||
networks:
|
networks:
|
||||||
- media-network
|
- media-network
|
||||||
- traefik-public
|
- traefik-public
|
||||||
labels:
|
labels:
|
||||||
- "traefik.enable=true"
|
- "traefik.enable=true"
|
||||||
- "traefik.http.routers.jellyfin.rule=Host(`movies.jeffemmett.com`) || Host(`movies.jefflix.lol`) || Host(`jefflix.lol`) || Host(`www.jefflix.lol`)"
|
- "traefik.http.routers.jellyfin.rule=Host(`movies.jeffemmett.com`) || Host(`movies.jefflix.lol`)"
|
||||||
- "traefik.http.routers.jellyfin.entrypoints=web"
|
- "traefik.http.routers.jellyfin.entrypoints=web"
|
||||||
- "traefik.http.routers.jellyfin.middlewares=jellyfin-headers"
|
- "traefik.http.routers.jellyfin.middlewares=jellyfin-headers"
|
||||||
- "traefik.http.services.jellyfin.loadbalancer.server.port=8096"
|
- "traefik.http.services.jellyfin.loadbalancer.server.port=8096"
|
||||||
|
|
@ -38,7 +39,7 @@ services:
|
||||||
- traefik-public
|
- traefik-public
|
||||||
labels:
|
labels:
|
||||||
- "traefik.enable=true"
|
- "traefik.enable=true"
|
||||||
- "traefik.http.routers.jellyseerr.rule=Host(`requests.jeffemmett.com`) || Host(`requests.jefflix.lol`)"
|
- "traefik.http.routers.jellyseerr.rule=Host(`requests.jefflix.lol`)"
|
||||||
- "traefik.http.routers.jellyseerr.entrypoints=web"
|
- "traefik.http.routers.jellyseerr.entrypoints=web"
|
||||||
- "traefik.http.routers.jellyseerr.middlewares=jellyseerr-headers"
|
- "traefik.http.routers.jellyseerr.middlewares=jellyseerr-headers"
|
||||||
- "traefik.http.services.jellyseerr.loadbalancer.server.port=5055"
|
- "traefik.http.services.jellyseerr.loadbalancer.server.port=5055"
|
||||||
|
|
@ -60,7 +61,8 @@ services:
|
||||||
- ND_IMAGECACHESIZE=500MB
|
- ND_IMAGECACHESIZE=500MB
|
||||||
volumes:
|
volumes:
|
||||||
- ./config/navidrome:/data
|
- ./config/navidrome:/data
|
||||||
- ./media/music:/music:ro
|
# Hetzner Storage Box - music library
|
||||||
|
- /mnt/hetzner-media/media/music:/music:ro
|
||||||
networks:
|
networks:
|
||||||
- media-network
|
- media-network
|
||||||
- traefik-public
|
- traefik-public
|
||||||
|
|
@ -83,14 +85,15 @@ services:
|
||||||
- TZ=Europe/Berlin
|
- TZ=Europe/Berlin
|
||||||
volumes:
|
volumes:
|
||||||
- ./config/sonarr:/config
|
- ./config/sonarr:/config
|
||||||
- ./media/shows:/tv
|
# Hetzner Storage Box - TV shows and downloads
|
||||||
- ./downloads:/downloads
|
- /mnt/hetzner-media/media/shows:/tv
|
||||||
|
- /mnt/hetzner-media/downloads:/downloads
|
||||||
networks:
|
networks:
|
||||||
- media-network
|
- media-network
|
||||||
- traefik-public
|
- traefik-public
|
||||||
labels:
|
labels:
|
||||||
- "traefik.enable=true"
|
- "traefik.enable=true"
|
||||||
- "traefik.http.routers.sonarr.rule=Host(`sonarr.jeffemmett.com`)"
|
- "traefik.http.routers.sonarr.rule=Host(`sonarr.jefflix.lol`)"
|
||||||
- "traefik.http.routers.sonarr.entrypoints=web"
|
- "traefik.http.routers.sonarr.entrypoints=web"
|
||||||
- "traefik.http.services.sonarr.loadbalancer.server.port=8989"
|
- "traefik.http.services.sonarr.loadbalancer.server.port=8989"
|
||||||
- "traefik.docker.network=traefik-public"
|
- "traefik.docker.network=traefik-public"
|
||||||
|
|
@ -105,14 +108,15 @@ services:
|
||||||
- TZ=Europe/Berlin
|
- TZ=Europe/Berlin
|
||||||
volumes:
|
volumes:
|
||||||
- ./config/radarr:/config
|
- ./config/radarr:/config
|
||||||
- ./media/movies:/movies
|
# Hetzner Storage Box - movies and downloads
|
||||||
- ./downloads:/downloads
|
- /mnt/hetzner-media/media/movies:/movies
|
||||||
|
- /mnt/hetzner-media/downloads:/downloads
|
||||||
networks:
|
networks:
|
||||||
- media-network
|
- media-network
|
||||||
- traefik-public
|
- traefik-public
|
||||||
labels:
|
labels:
|
||||||
- "traefik.enable=true"
|
- "traefik.enable=true"
|
||||||
- "traefik.http.routers.radarr.rule=Host(`radarr.jeffemmett.com`)"
|
- "traefik.http.routers.radarr.rule=Host(`radarr.jefflix.lol`)"
|
||||||
- "traefik.http.routers.radarr.entrypoints=web"
|
- "traefik.http.routers.radarr.entrypoints=web"
|
||||||
- "traefik.http.services.radarr.loadbalancer.server.port=7878"
|
- "traefik.http.services.radarr.loadbalancer.server.port=7878"
|
||||||
- "traefik.docker.network=traefik-public"
|
- "traefik.docker.network=traefik-public"
|
||||||
|
|
@ -132,7 +136,7 @@ services:
|
||||||
- traefik-public
|
- traefik-public
|
||||||
labels:
|
labels:
|
||||||
- "traefik.enable=true"
|
- "traefik.enable=true"
|
||||||
- "traefik.http.routers.prowlarr.rule=Host(`prowlarr.jeffemmett.com`)"
|
- "traefik.http.routers.prowlarr.rule=Host(`prowlarr.jefflix.lol`)"
|
||||||
- "traefik.http.routers.prowlarr.entrypoints=web"
|
- "traefik.http.routers.prowlarr.entrypoints=web"
|
||||||
- "traefik.http.services.prowlarr.loadbalancer.server.port=9696"
|
- "traefik.http.services.prowlarr.loadbalancer.server.port=9696"
|
||||||
- "traefik.docker.network=traefik-public"
|
- "traefik.docker.network=traefik-public"
|
||||||
|
|
@ -147,14 +151,15 @@ services:
|
||||||
- TZ=Europe/Berlin
|
- TZ=Europe/Berlin
|
||||||
volumes:
|
volumes:
|
||||||
- ./config/lidarr:/config
|
- ./config/lidarr:/config
|
||||||
- ./media/music:/music
|
# Hetzner Storage Box - music and downloads
|
||||||
- ./downloads:/downloads
|
- /mnt/hetzner-media/media/music:/music
|
||||||
|
- /mnt/hetzner-media/downloads:/downloads
|
||||||
networks:
|
networks:
|
||||||
- media-network
|
- media-network
|
||||||
- traefik-public
|
- traefik-public
|
||||||
labels:
|
labels:
|
||||||
- "traefik.enable=true"
|
- "traefik.enable=true"
|
||||||
- "traefik.http.routers.lidarr.rule=Host(`lidarr.jeffemmett.com`)"
|
- "traefik.http.routers.lidarr.rule=Host(`lidarr.jefflix.lol`)"
|
||||||
- "traefik.http.routers.lidarr.entrypoints=web"
|
- "traefik.http.routers.lidarr.entrypoints=web"
|
||||||
- "traefik.http.services.lidarr.loadbalancer.server.port=8686"
|
- "traefik.http.services.lidarr.loadbalancer.server.port=8686"
|
||||||
- "traefik.docker.network=traefik-public"
|
- "traefik.docker.network=traefik-public"
|
||||||
|
|
@ -188,7 +193,7 @@ services:
|
||||||
profiles:
|
profiles:
|
||||||
- vpn
|
- vpn
|
||||||
|
|
||||||
# Download client (routes through VPN when vpn profile enabled)
|
# Download client - downloads directly to Hetzner Storage Box
|
||||||
qbittorrent:
|
qbittorrent:
|
||||||
image: linuxserver/qbittorrent:latest
|
image: linuxserver/qbittorrent:latest
|
||||||
container_name: qbittorrent
|
container_name: qbittorrent
|
||||||
|
|
@ -200,7 +205,8 @@ services:
|
||||||
- WEBUI_PORT=8080
|
- WEBUI_PORT=8080
|
||||||
volumes:
|
volumes:
|
||||||
- ./config/qbittorrent:/config
|
- ./config/qbittorrent:/config
|
||||||
- ./downloads:/downloads
|
# Hetzner Storage Box - all downloads go here
|
||||||
|
- /mnt/hetzner-media/downloads:/downloads
|
||||||
# When VPN enabled, use gluetun network
|
# When VPN enabled, use gluetun network
|
||||||
# network_mode: "service:gluetun"
|
# network_mode: "service:gluetun"
|
||||||
networks:
|
networks:
|
||||||
|
|
@ -208,7 +214,7 @@ services:
|
||||||
- traefik-public
|
- traefik-public
|
||||||
labels:
|
labels:
|
||||||
- "traefik.enable=true"
|
- "traefik.enable=true"
|
||||||
- "traefik.http.routers.qbittorrent.rule=Host(`downloads.jeffemmett.com`)"
|
- "traefik.http.routers.qbittorrent.rule=Host(`downloads.jefflix.lol`)"
|
||||||
- "traefik.http.routers.qbittorrent.entrypoints=web"
|
- "traefik.http.routers.qbittorrent.entrypoints=web"
|
||||||
- "traefik.http.services.qbittorrent.loadbalancer.server.port=8080"
|
- "traefik.http.services.qbittorrent.loadbalancer.server.port=8080"
|
||||||
- "traefik.docker.network=traefik-public"
|
- "traefik.docker.network=traefik-public"
|
||||||
|
|
@ -247,7 +253,7 @@ services:
|
||||||
- TRANSMISSION_WEB_HOME=/web
|
- TRANSMISSION_WEB_HOME=/web
|
||||||
volumes:
|
volumes:
|
||||||
- ./config/transmission:/config
|
- ./config/transmission:/config
|
||||||
- ./downloads:/downloads
|
- /mnt/hetzner-media/downloads:/downloads
|
||||||
- ./watch:/watch
|
- ./watch:/watch
|
||||||
ports:
|
ports:
|
||||||
- 9091:9091
|
- 9091:9091
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,240 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# Media Archive Manager - Move content between local and Hetzner storage
|
||||||
|
# Usage: media-archive.sh [archive|restore|status|list-old]
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
LOCAL_MEDIA="/opt/media-server/media"
|
||||||
|
HETZNER_MEDIA="/mnt/hetzner-media/archive"
|
||||||
|
LOG_FILE="/opt/media-server/logs/archive.log"
|
||||||
|
DAYS_OLD=90 # Content older than this is considered for archiving
|
||||||
|
|
||||||
|
# Colors
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
# Ensure log directory exists
|
||||||
|
mkdir -p "$(dirname "$LOG_FILE")"
|
||||||
|
|
||||||
|
log() {
|
||||||
|
echo "[$(date -Iseconds)] $1" | tee -a "$LOG_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_mount() {
|
||||||
|
if ! mountpoint -q "$HETZNER_MEDIA" 2>/dev/null; then
|
||||||
|
# Try to trigger automount
|
||||||
|
ls "$HETZNER_MEDIA" &>/dev/null 2>&1
|
||||||
|
sleep 2
|
||||||
|
if ! mountpoint -q "$HETZNER_MEDIA" 2>/dev/null; then
|
||||||
|
echo -e "${RED}Error: Hetzner storage not mounted at $HETZNER_MEDIA${NC}"
|
||||||
|
echo "Run: sudo systemctl start mnt-hetzner\\x2dmedia.mount"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo -e "${GREEN}Hetzner storage mounted${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
show_status() {
|
||||||
|
echo -e "${BLUE}Storage Status${NC}"
|
||||||
|
echo "=============="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo -e "${YELLOW}Local Storage (/opt/media-server/media):${NC}"
|
||||||
|
if [ -d "$LOCAL_MEDIA" ]; then
|
||||||
|
du -sh "$LOCAL_MEDIA"/* 2>/dev/null || echo " (empty or not accessible)"
|
||||||
|
else
|
||||||
|
echo " Not found"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo -e "${YELLOW}Hetzner Storage (/mnt/hetzner-media):${NC}"
|
||||||
|
if mountpoint -q "/mnt/hetzner-media" 2>/dev/null || ls "/mnt/hetzner-media" &>/dev/null 2>&1; then
|
||||||
|
df -h "/mnt/hetzner-media"
|
||||||
|
echo ""
|
||||||
|
echo "Archive contents:"
|
||||||
|
du -sh "$HETZNER_MEDIA"/* 2>/dev/null || echo " (empty)"
|
||||||
|
else
|
||||||
|
echo " Not mounted"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
list_old_content() {
|
||||||
|
echo -e "${BLUE}Content older than $DAYS_OLD days${NC}"
|
||||||
|
echo "================================"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
for media_type in movies shows music; do
|
||||||
|
echo -e "${YELLOW}$media_type:${NC}"
|
||||||
|
if [ -d "$LOCAL_MEDIA/$media_type" ]; then
|
||||||
|
find "$LOCAL_MEDIA/$media_type" -maxdepth 1 -mindepth 1 -type d -mtime +$DAYS_OLD 2>/dev/null | while read dir; do
|
||||||
|
size=$(du -sh "$dir" 2>/dev/null | cut -f1)
|
||||||
|
name=$(basename "$dir")
|
||||||
|
echo " [$size] $name"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
archive_content() {
|
||||||
|
local media_type=$1
|
||||||
|
local name=$2
|
||||||
|
|
||||||
|
if [ -z "$media_type" ] || [ -z "$name" ]; then
|
||||||
|
echo "Usage: media-archive.sh archive <movies|shows|music> <name>"
|
||||||
|
echo " media-archive.sh archive <movies|shows|music> --older-than <days>"
|
||||||
|
echo ""
|
||||||
|
echo "Examples:"
|
||||||
|
echo " media-archive.sh archive movies 'The Matrix (1999)'"
|
||||||
|
echo " media-archive.sh archive shows --older-than 180"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
check_mount
|
||||||
|
|
||||||
|
# Handle batch archival
|
||||||
|
if [ "$name" == "--older-than" ]; then
|
||||||
|
local days=${3:-$DAYS_OLD}
|
||||||
|
echo -e "${YELLOW}Archiving $media_type older than $days days...${NC}"
|
||||||
|
|
||||||
|
find "$LOCAL_MEDIA/$media_type" -maxdepth 1 -mindepth 1 -type d -mtime +$days 2>/dev/null | while read dir; do
|
||||||
|
local item_name=$(basename "$dir")
|
||||||
|
archive_single "$media_type" "$item_name"
|
||||||
|
done
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
archive_single "$media_type" "$name"
|
||||||
|
}
|
||||||
|
|
||||||
|
archive_single() {
|
||||||
|
local media_type=$1
|
||||||
|
local name=$2
|
||||||
|
local source="$LOCAL_MEDIA/$media_type/$name"
|
||||||
|
local dest="$HETZNER_MEDIA/$media_type/$name"
|
||||||
|
|
||||||
|
if [ ! -d "$source" ]; then
|
||||||
|
echo -e "${RED}Error: $source not found${NC}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local size=$(du -sh "$source" 2>/dev/null | cut -f1)
|
||||||
|
echo -e "${BLUE}Archiving: $name ($size)${NC}"
|
||||||
|
|
||||||
|
# Create destination directory
|
||||||
|
mkdir -p "$HETZNER_MEDIA/$media_type"
|
||||||
|
|
||||||
|
# Use rsync for reliable transfer with progress
|
||||||
|
log "Archiving $media_type/$name ($size) to Hetzner"
|
||||||
|
rsync -avh --progress --remove-source-files "$source/" "$dest/"
|
||||||
|
|
||||||
|
# Remove empty source directory
|
||||||
|
rmdir "$source" 2>/dev/null || true
|
||||||
|
|
||||||
|
echo -e "${GREEN}Archived: $name${NC}"
|
||||||
|
log "Completed archiving $media_type/$name"
|
||||||
|
}
|
||||||
|
|
||||||
|
restore_content() {
|
||||||
|
local media_type=$1
|
||||||
|
local name=$2
|
||||||
|
|
||||||
|
if [ -z "$media_type" ] || [ -z "$name" ]; then
|
||||||
|
echo "Usage: media-archive.sh restore <movies|shows|music> <name>"
|
||||||
|
echo ""
|
||||||
|
echo "List available archives:"
|
||||||
|
echo " ls /mnt/hetzner-media/archive/<type>/"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
check_mount
|
||||||
|
|
||||||
|
local source="$HETZNER_MEDIA/$media_type/$name"
|
||||||
|
local dest="$LOCAL_MEDIA/$media_type/$name"
|
||||||
|
|
||||||
|
if [ ! -d "$source" ]; then
|
||||||
|
echo -e "${RED}Error: $source not found in archive${NC}"
|
||||||
|
echo ""
|
||||||
|
echo "Available in $media_type archive:"
|
||||||
|
ls -1 "$HETZNER_MEDIA/$media_type/" 2>/dev/null || echo " (empty)"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local size=$(du -sh "$source" 2>/dev/null | cut -f1)
|
||||||
|
echo -e "${BLUE}Restoring: $name ($size)${NC}"
|
||||||
|
|
||||||
|
log "Restoring $media_type/$name ($size) from Hetzner"
|
||||||
|
rsync -avh --progress "$source/" "$dest/"
|
||||||
|
|
||||||
|
echo -e "${GREEN}Restored: $name${NC}"
|
||||||
|
echo "Content available at: $dest"
|
||||||
|
log "Completed restoring $media_type/$name"
|
||||||
|
}
|
||||||
|
|
||||||
|
sync_to_hetzner() {
|
||||||
|
# One-way sync from local to Hetzner (backup mode)
|
||||||
|
check_mount
|
||||||
|
|
||||||
|
echo -e "${BLUE}Syncing local media to Hetzner (backup)...${NC}"
|
||||||
|
|
||||||
|
for media_type in movies shows music; do
|
||||||
|
if [ -d "$LOCAL_MEDIA/$media_type" ]; then
|
||||||
|
echo -e "${YELLOW}Syncing $media_type...${NC}"
|
||||||
|
mkdir -p "$HETZNER_MEDIA/$media_type"
|
||||||
|
rsync -avh --progress "$LOCAL_MEDIA/$media_type/" "$HETZNER_MEDIA/$media_type/"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo -e "${GREEN}Sync complete${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main command handler
|
||||||
|
case "${1:-status}" in
|
||||||
|
status)
|
||||||
|
show_status
|
||||||
|
;;
|
||||||
|
list-old|list)
|
||||||
|
list_old_content
|
||||||
|
;;
|
||||||
|
archive)
|
||||||
|
archive_content "$2" "$3" "$4"
|
||||||
|
;;
|
||||||
|
restore)
|
||||||
|
restore_content "$2" "$3"
|
||||||
|
;;
|
||||||
|
sync|backup)
|
||||||
|
sync_to_hetzner
|
||||||
|
;;
|
||||||
|
help|--help|-h)
|
||||||
|
echo "Media Archive Manager"
|
||||||
|
echo "====================="
|
||||||
|
echo ""
|
||||||
|
echo "Usage: media-archive.sh <command> [options]"
|
||||||
|
echo ""
|
||||||
|
echo "Commands:"
|
||||||
|
echo " status Show storage status and usage"
|
||||||
|
echo " list-old List content older than $DAYS_OLD days"
|
||||||
|
echo " archive <type> <name> Archive specific content to Hetzner"
|
||||||
|
echo " archive <type> --older-than <days> Archive all content older than N days"
|
||||||
|
echo " restore <type> <name> Restore content from Hetzner to local"
|
||||||
|
echo " sync Sync all local media to Hetzner (backup)"
|
||||||
|
echo ""
|
||||||
|
echo "Types: movies, shows, music"
|
||||||
|
echo ""
|
||||||
|
echo "Examples:"
|
||||||
|
echo " media-archive.sh status"
|
||||||
|
echo " media-archive.sh list-old"
|
||||||
|
echo " media-archive.sh archive movies 'The Matrix (1999)'"
|
||||||
|
echo " media-archive.sh archive shows --older-than 180"
|
||||||
|
echo " media-archive.sh restore movies 'The Matrix (1999)'"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown command: $1"
|
||||||
|
echo "Run 'media-archive.sh help' for usage"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
@ -0,0 +1,158 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# Setup Hetzner Storage Box via SSHFS on Netcup RS 8000
|
||||||
|
# Storage Box: u521871 (BX21 - 5-10TB)
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
HETZNER_USER="u521871"
|
||||||
|
HETZNER_HOST="${HETZNER_USER}.your-storagebox.de"
|
||||||
|
HETZNER_PORT="23"
|
||||||
|
SSH_KEY="/root/.ssh/hetzner_storagebox"
|
||||||
|
MOUNT_POINT="/mnt/hetzner-media"
|
||||||
|
|
||||||
|
# Colors
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
echo -e "${GREEN}Hetzner Storage Box SSHFS Setup${NC}"
|
||||||
|
echo "=================================="
|
||||||
|
echo "User: $HETZNER_USER"
|
||||||
|
echo "Host: $HETZNER_HOST"
|
||||||
|
echo "Mount: $MOUNT_POINT"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check if running as root
|
||||||
|
if [ "$EUID" -ne 0 ]; then
|
||||||
|
echo -e "${RED}Please run as root (sudo)${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check SSH key exists
|
||||||
|
if [ ! -f "$SSH_KEY" ]; then
|
||||||
|
echo -e "${RED}SSH key not found at $SSH_KEY${NC}"
|
||||||
|
echo "Generate one with: ssh-keygen -t ed25519 -f $SSH_KEY -C 'storagebox@netcup'"
|
||||||
|
echo "Then upload with: cat ${SSH_KEY}.pub | ssh -p23 ${HETZNER_USER}@${HETZNER_HOST} install-ssh-key"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Install sshfs
|
||||||
|
echo -e "${GREEN}Installing SSHFS...${NC}"
|
||||||
|
apt-get update -qq
|
||||||
|
apt-get install -y sshfs
|
||||||
|
|
||||||
|
# Create mount point
|
||||||
|
echo -e "${GREEN}Creating mount point...${NC}"
|
||||||
|
mkdir -p "$MOUNT_POINT"
|
||||||
|
|
||||||
|
# Test SSH connection (use 'ls' as Hetzner has restricted shell)
|
||||||
|
echo -e "${GREEN}Testing SSH connection...${NC}"
|
||||||
|
if ! ssh -p "$HETZNER_PORT" -i "$SSH_KEY" -o BatchMode=yes -o ConnectTimeout=10 \
|
||||||
|
"${HETZNER_USER}@${HETZNER_HOST}" "ls" &>/dev/null; then
|
||||||
|
echo -e "${RED}SSH connection failed!${NC}"
|
||||||
|
echo "Make sure:"
|
||||||
|
echo " 1. SSH is enabled in Hetzner Console"
|
||||||
|
echo " 2. Your key is uploaded: cat ${SSH_KEY}.pub | ssh -p23 ${HETZNER_USER}@${HETZNER_HOST} install-ssh-key"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo -e "${GREEN}SSH connection successful${NC}"
|
||||||
|
|
||||||
|
# Create directory structure on Hetzner
|
||||||
|
echo -e "${GREEN}Creating directory structure on Hetzner...${NC}"
|
||||||
|
ssh -p "$HETZNER_PORT" -i "$SSH_KEY" "${HETZNER_USER}@${HETZNER_HOST}" "mkdir -p media/movies" 2>/dev/null || true
|
||||||
|
ssh -p "$HETZNER_PORT" -i "$SSH_KEY" "${HETZNER_USER}@${HETZNER_HOST}" "mkdir -p media/shows" 2>/dev/null || true
|
||||||
|
ssh -p "$HETZNER_PORT" -i "$SSH_KEY" "${HETZNER_USER}@${HETZNER_HOST}" "mkdir -p media/music" 2>/dev/null || true
|
||||||
|
ssh -p "$HETZNER_PORT" -i "$SSH_KEY" "${HETZNER_USER}@${HETZNER_HOST}" "mkdir -p downloads/complete/movies" 2>/dev/null || true
|
||||||
|
ssh -p "$HETZNER_PORT" -i "$SSH_KEY" "${HETZNER_USER}@${HETZNER_HOST}" "mkdir -p downloads/complete/tv" 2>/dev/null || true
|
||||||
|
ssh -p "$HETZNER_PORT" -i "$SSH_KEY" "${HETZNER_USER}@${HETZNER_HOST}" "mkdir -p downloads/complete/music" 2>/dev/null || true
|
||||||
|
ssh -p "$HETZNER_PORT" -i "$SSH_KEY" "${HETZNER_USER}@${HETZNER_HOST}" "mkdir -p downloads/incomplete" 2>/dev/null || true
|
||||||
|
echo -e "${GREEN}Directories created${NC}"
|
||||||
|
|
||||||
|
# Create systemd mount unit
|
||||||
|
echo -e "${GREEN}Creating systemd mount unit...${NC}"
|
||||||
|
cat > /etc/systemd/system/mnt-hetzner\\x2dmedia.mount << EOF
|
||||||
|
[Unit]
|
||||||
|
Description=Hetzner Storage Box (u521871 - SSHFS)
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Mount]
|
||||||
|
What=${HETZNER_USER}@${HETZNER_HOST}:/
|
||||||
|
Where=${MOUNT_POINT}
|
||||||
|
Type=fuse.sshfs
|
||||||
|
Options=port=${HETZNER_PORT},IdentityFile=${SSH_KEY},allow_other,default_permissions,reconnect,ServerAliveInterval=15,ServerAliveCountMax=3,_netdev,uid=1000,gid=1000
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Create automount unit for on-demand mounting
|
||||||
|
cat > /etc/systemd/system/mnt-hetzner\\x2dmedia.automount << EOF
|
||||||
|
[Unit]
|
||||||
|
Description=Automount Hetzner Storage Box
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Automount]
|
||||||
|
Where=${MOUNT_POINT}
|
||||||
|
TimeoutIdleSec=0
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Enable user_allow_other in fuse.conf
|
||||||
|
if ! grep -q "^user_allow_other" /etc/fuse.conf 2>/dev/null; then
|
||||||
|
echo "user_allow_other" >> /etc/fuse.conf
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Reload systemd and start mount
|
||||||
|
echo -e "${GREEN}Enabling and starting mount...${NC}"
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable mnt-hetzner\\x2dmedia.mount
|
||||||
|
systemctl start mnt-hetzner\\x2dmedia.mount
|
||||||
|
|
||||||
|
# Verify mount
|
||||||
|
sleep 2
|
||||||
|
if mountpoint -q "$MOUNT_POINT"; then
|
||||||
|
echo -e "${GREEN}Mount successful!${NC}"
|
||||||
|
echo ""
|
||||||
|
df -h "$MOUNT_POINT"
|
||||||
|
echo ""
|
||||||
|
echo "Directory structure:"
|
||||||
|
ls -la "$MOUNT_POINT"
|
||||||
|
else
|
||||||
|
echo -e "${RED}Mount may have failed. Checking status...${NC}"
|
||||||
|
systemctl status mnt-hetzner\\x2dmedia.mount
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Save config reference
|
||||||
|
cat > /opt/media-server/.hetzner-config << EOF
|
||||||
|
HETZNER_USER=$HETZNER_USER
|
||||||
|
HETZNER_HOST=$HETZNER_HOST
|
||||||
|
MOUNT_POINT=$MOUNT_POINT
|
||||||
|
SSH_KEY=$SSH_KEY
|
||||||
|
MOUNT_TYPE=sshfs
|
||||||
|
SETUP_DATE=$(date -Iseconds)
|
||||||
|
EOF
|
||||||
|
chmod 600 /opt/media-server/.hetzner-config
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e "${GREEN}Setup complete!${NC}"
|
||||||
|
echo ""
|
||||||
|
echo "Storage mounted at: $MOUNT_POINT"
|
||||||
|
echo ""
|
||||||
|
echo "Directory structure:"
|
||||||
|
echo " $MOUNT_POINT/media/movies - Movie library"
|
||||||
|
echo " $MOUNT_POINT/media/shows - TV show library"
|
||||||
|
echo " $MOUNT_POINT/media/music - Music library"
|
||||||
|
echo " $MOUNT_POINT/downloads/ - qBittorrent downloads"
|
||||||
|
echo ""
|
||||||
|
echo "Next steps:"
|
||||||
|
echo " 1. Deploy updated docker-compose: docker compose up -d"
|
||||||
|
echo " 2. Configure Jellyfin library paths"
|
||||||
|
echo " 3. Verify qBittorrent download path"
|
||||||
|
echo ""
|
||||||
|
|
@ -0,0 +1,156 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# Setup Hetzner Storage Box mount on Netcup RS 8000
|
||||||
|
# Run this script ON the Netcup server after signing up for Hetzner Storage Box
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
echo -e "${GREEN}Hetzner Storage Box Setup${NC}"
|
||||||
|
echo "=========================="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Check if running as root
|
||||||
|
if [ "$EUID" -ne 0 ]; then
|
||||||
|
echo -e "${RED}Please run as root (sudo)${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Prompt for Hetzner credentials if not provided via env
|
||||||
|
if [ -z "$HETZNER_USER" ]; then
|
||||||
|
read -p "Enter Hetzner Storage Box username (e.g., u123456): " HETZNER_USER
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$HETZNER_PASS" ]; then
|
||||||
|
read -sp "Enter Hetzner Storage Box password: " HETZNER_PASS
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Validate input
|
||||||
|
if [ -z "$HETZNER_USER" ] || [ -z "$HETZNER_PASS" ]; then
|
||||||
|
echo -e "${RED}Error: Username and password are required${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
HETZNER_HOST="${HETZNER_USER}.your-storagebox.de"
|
||||||
|
MOUNT_POINT="/mnt/hetzner-media"
|
||||||
|
CREDENTIALS_FILE="/etc/smbcredentials/hetzner"
|
||||||
|
|
||||||
|
echo -e "${YELLOW}Configuration:${NC}"
|
||||||
|
echo " Host: $HETZNER_HOST"
|
||||||
|
echo " User: $HETZNER_USER"
|
||||||
|
echo " Mount: $MOUNT_POINT"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Install required packages
|
||||||
|
echo -e "${GREEN}Installing CIFS utilities...${NC}"
|
||||||
|
apt-get update -qq
|
||||||
|
apt-get install -y cifs-utils
|
||||||
|
|
||||||
|
# Create mount point
|
||||||
|
echo -e "${GREEN}Creating mount point...${NC}"
|
||||||
|
mkdir -p "$MOUNT_POINT"
|
||||||
|
mkdir -p "$MOUNT_POINT/archive"
|
||||||
|
mkdir -p "$MOUNT_POINT/movies"
|
||||||
|
mkdir -p "$MOUNT_POINT/shows"
|
||||||
|
mkdir -p "$MOUNT_POINT/music"
|
||||||
|
|
||||||
|
# Create credentials file
|
||||||
|
echo -e "${GREEN}Creating credentials file...${NC}"
|
||||||
|
mkdir -p /etc/smbcredentials
|
||||||
|
cat > "$CREDENTIALS_FILE" << EOF
|
||||||
|
username=$HETZNER_USER
|
||||||
|
password=$HETZNER_PASS
|
||||||
|
EOF
|
||||||
|
chmod 600 "$CREDENTIALS_FILE"
|
||||||
|
|
||||||
|
# Create systemd mount unit
|
||||||
|
echo -e "${GREEN}Creating systemd mount unit...${NC}"
|
||||||
|
cat > /etc/systemd/system/mnt-hetzner\\x2dmedia.mount << EOF
|
||||||
|
[Unit]
|
||||||
|
Description=Hetzner Storage Box (Media Archive)
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Mount]
|
||||||
|
What=//${HETZNER_HOST}/${HETZNER_USER}
|
||||||
|
Where=${MOUNT_POINT}
|
||||||
|
Type=cifs
|
||||||
|
Options=credentials=${CREDENTIALS_FILE},iocharset=utf8,rw,vers=3.0,uid=1000,gid=1000,file_mode=0664,dir_mode=0775,nofail,_netdev
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Create automount unit for on-demand mounting
|
||||||
|
cat > /etc/systemd/system/mnt-hetzner\\x2dmedia.automount << EOF
|
||||||
|
[Unit]
|
||||||
|
Description=Automount Hetzner Storage Box
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Automount]
|
||||||
|
Where=${MOUNT_POINT}
|
||||||
|
TimeoutIdleSec=300
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Reload systemd and enable mount
|
||||||
|
echo -e "${GREEN}Enabling mount...${NC}"
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable mnt-hetzner\\x2dmedia.automount
|
||||||
|
systemctl start mnt-hetzner\\x2dmedia.automount
|
||||||
|
|
||||||
|
# Test the mount
|
||||||
|
echo -e "${GREEN}Testing mount...${NC}"
|
||||||
|
if mountpoint -q "$MOUNT_POINT" || ls "$MOUNT_POINT" &>/dev/null; then
|
||||||
|
echo -e "${GREEN}Mount successful!${NC}"
|
||||||
|
df -h "$MOUNT_POINT"
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}Mount will activate on first access (automount enabled)${NC}"
|
||||||
|
# Trigger automount by accessing
|
||||||
|
ls "$MOUNT_POINT" &>/dev/null
|
||||||
|
sleep 2
|
||||||
|
if mountpoint -q "$MOUNT_POINT"; then
|
||||||
|
echo -e "${GREEN}Mount successful!${NC}"
|
||||||
|
df -h "$MOUNT_POINT"
|
||||||
|
else
|
||||||
|
echo -e "${RED}Mount may have failed. Check with: systemctl status mnt-hetzner\\x2dmedia.mount${NC}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create directory structure on Hetzner
|
||||||
|
echo -e "${GREEN}Creating directory structure on Hetzner...${NC}"
|
||||||
|
mkdir -p "$MOUNT_POINT/archive/movies"
|
||||||
|
mkdir -p "$MOUNT_POINT/archive/shows"
|
||||||
|
mkdir -p "$MOUNT_POINT/archive/music"
|
||||||
|
|
||||||
|
# Save config for reference
|
||||||
|
cat > /opt/media-server/.hetzner-config << EOF
|
||||||
|
HETZNER_USER=$HETZNER_USER
|
||||||
|
HETZNER_HOST=$HETZNER_HOST
|
||||||
|
MOUNT_POINT=$MOUNT_POINT
|
||||||
|
SETUP_DATE=$(date -Iseconds)
|
||||||
|
EOF
|
||||||
|
chmod 600 /opt/media-server/.hetzner-config
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e "${GREEN}Setup complete!${NC}"
|
||||||
|
echo ""
|
||||||
|
echo "Mount point: $MOUNT_POINT"
|
||||||
|
echo ""
|
||||||
|
echo "Directory structure:"
|
||||||
|
echo " $MOUNT_POINT/archive/movies - Archived movies"
|
||||||
|
echo " $MOUNT_POINT/archive/shows - Archived TV shows"
|
||||||
|
echo " $MOUNT_POINT/archive/music - Archived music"
|
||||||
|
echo ""
|
||||||
|
echo "Next steps:"
|
||||||
|
echo " 1. Run: docker compose down && docker compose up -d"
|
||||||
|
echo " 2. Use media-archive.sh to move content to Hetzner"
|
||||||
|
echo ""
|
||||||
Loading…
Reference in New Issue