feat: add Jellyfin media server (jelly.pivoine.art)

Added Jellyfin stack for streaming photos and videos from HiDrive:
- Maps /mnt/hidrive/users/valknar/Pictures to /media/pictures (read-only)
- Maps /mnt/hidrive/users/valknar/Videos to /media/videos (read-only)
- Hardware transcoding support for optimal video playback
- Multi-device streaming (web, mobile, TV apps)
- Automatic media organization with metadata fetching

Jellyfin provides superior video playback compared to Filestash's
transcoding plugin, which has compatibility issues with named pipes
in containerized environments.

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-06 20:36:01 +01:00
parent 0af6e0f6a9
commit 45c14a2a14
4 changed files with 76 additions and 0 deletions

View File

@@ -24,6 +24,7 @@ Root `compose.yaml` uses Docker Compose's `include` directive to orchestrate mul
- **joplin**: Joplin Server note-taking and sync platform (PostgreSQL)
- **vert**: VERT file format converter (WebAssembly-based, stateless)
- **paint**: miniPaint web-based image editor (built from GitHub)
- **jelly**: Jellyfin media server with hardware transcoding
- **restic**: Backrest backup system with restic backend
- **sablier**: Dynamic scaling plugin for Traefik
- **vpn**: WireGuard VPN (wg-easy)
@@ -294,6 +295,33 @@ Access https://paint.pivoine.art to use the image editor. All editing happens in
**Note**: miniPaint is stateless and doesn't require backups as no data is persisted.
### Jellyfin (jelly/compose.yaml)
Jellyfin media server for streaming photos and videos:
- **jellyfin**: Jellyfin app exposed at `jelly.pivoine.art:8096`
- Self-hosted media streaming server
- Hardware transcoding support for video playback
- Automatic media library organization with metadata
- Multi-device support (web, mobile apps, TV apps)
- User management with watch history and favorites
- Subtitle support and on-the-fly transcoding
- Data persisted in `jellyfin_config` and `jellyfin_cache` volumes
**Media Sources**:
- **Pictures**: `/mnt/hidrive/users/valknar/Pictures` (read-only)
- **Videos**: `/mnt/hidrive/users/valknar/Videos` (read-only)
- Uses HiDrive WebDAV mount via davfs2 on host
**Configuration**:
- First access: Create admin account at https://jelly.pivoine.art
- Add media libraries pointing to `/media/pictures` and `/media/videos`
- Configure transcoding settings in Dashboard → Playback
- Enable hardware acceleration if available
**Usage**:
Access https://jelly.pivoine.art to browse and stream your media. Jellyfin will automatically organize your content, fetch metadata, and provide optimized streaming to any device.
**Note**: Jellyfin requires the HiDrive WebDAV mount to be active on the host at `/mnt/hidrive`.
### Restic (restic/compose.yaml)
Backrest backup system with restic backend:
- **backrest**: Backrest web UI exposed at `restic.pivoine.art:9898`

View File

@@ -120,6 +120,10 @@ envs:
PAINT_TRAEFIK_ENABLED: true
PAINT_COMPOSE_PROJECT_NAME: paint
PAINT_TRAEFIK_HOST: paint.pivoine.art
# Jellyfin
JELLY_TRAEFIK_ENABLED: true
JELLY_COMPOSE_PROJECT_NAME: jelly
JELLY_TRAEFIK_HOST: jelly.pivoine.art
# Proxy
PROXY_COMPOSE_PROJECT_NAME: proxy
PROXY_DOCKER_IMAGE: traefik:latest

View File

@@ -12,6 +12,7 @@ include:
- joplin/compose.yaml
- vert/compose.yaml
- paint/compose.yaml
- jelly/compose.yaml
- restic/compose.yaml
- umami/compose.yaml
- sablier/compose.yaml

43
jelly/compose.yaml Normal file
View File

@@ -0,0 +1,43 @@
services:
jellyfin:
image: jellyfin/jellyfin:latest
container_name: ${JELLY_COMPOSE_PROJECT_NAME}_app
restart: unless-stopped
volumes:
- jellyfin_config:/config
- jellyfin_cache:/cache
- /mnt/hidrive/users/valknar/Pictures:/media/pictures:ro
- /mnt/hidrive/users/valknar/Videos:/media/videos:ro
environment:
TZ: ${TIMEZONE:-Europe/Berlin}
networks:
- compose_network
labels:
- 'traefik.enable=${JELLY_TRAEFIK_ENABLED}'
# HTTP to HTTPS redirect
- 'traefik.http.middlewares.${JELLY_COMPOSE_PROJECT_NAME}-redirect-web-secure.redirectscheme.scheme=https'
- 'traefik.http.routers.${JELLY_COMPOSE_PROJECT_NAME}-web.middlewares=${JELLY_COMPOSE_PROJECT_NAME}-redirect-web-secure'
- 'traefik.http.routers.${JELLY_COMPOSE_PROJECT_NAME}-web.rule=Host(`${JELLY_TRAEFIK_HOST}`)'
- 'traefik.http.routers.${JELLY_COMPOSE_PROJECT_NAME}-web.entrypoints=web'
# HTTPS router
- 'traefik.http.routers.${JELLY_COMPOSE_PROJECT_NAME}-web-secure.rule=Host(`${JELLY_TRAEFIK_HOST}`)'
- 'traefik.http.routers.${JELLY_COMPOSE_PROJECT_NAME}-web-secure.tls.certresolver=resolver'
- 'traefik.http.routers.${JELLY_COMPOSE_PROJECT_NAME}-web-secure.entrypoints=web-secure'
- 'traefik.http.middlewares.${JELLY_COMPOSE_PROJECT_NAME}-web-secure-compress.compress=true'
- 'traefik.http.routers.${JELLY_COMPOSE_PROJECT_NAME}-web-secure.middlewares=${JELLY_COMPOSE_PROJECT_NAME}-web-secure-compress,security-headers@file'
# Service
- 'traefik.http.services.${JELLY_COMPOSE_PROJECT_NAME}-web-secure.loadbalancer.server.port=8096'
- 'traefik.docker.network=${NETWORK_NAME}'
# Watchtower
- 'com.centurylinklabs.watchtower.enable=${WATCHTOWER_LABEL_ENABLE}'
volumes:
jellyfin_config:
name: ${JELLY_COMPOSE_PROJECT_NAME}_config
jellyfin_cache:
name: ${JELLY_COMPOSE_PROJECT_NAME}_cache
networks:
compose_network:
name: ${NETWORK_NAME}
external: true