diff --git a/CLAUDE.md b/CLAUDE.md index 25b14d2..c9e5519 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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` diff --git a/arty.yml b/arty.yml index c7438ad..646d4a9 100644 --- a/arty.yml +++ b/arty.yml @@ -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 diff --git a/compose.yaml b/compose.yaml index 401578f..1c23a4b 100644 --- a/compose.yaml +++ b/compose.yaml @@ -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 diff --git a/jelly/compose.yaml b/jelly/compose.yaml new file mode 100644 index 0000000..5ff0fb7 --- /dev/null +++ b/jelly/compose.yaml @@ -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