diff --git a/content/en/3.serveex/5.media/1.jellyfin.md b/content/en/3.serveex/5.media/1.jellyfin.md index fe3383c..40d3ff5 100644 --- a/content/en/3.serveex/5.media/1.jellyfin.md +++ b/content/en/3.serveex/5.media/1.jellyfin.md @@ -5,14 +5,6 @@ description: Install Jellyfin, a free and open-source media server, to stream yo :ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60} -# Jellyfin - -::note -🎯 __Objectives:__ - -- Install Jellyfin -- Access your media from outside your network -:: [Jellyfin](https://jellyfin.org) is a free, open-source alternative to Plex and Emby. Unlike Plex, it has no paid tiers, no telemetry, and doesn't require an online account to use or manage your server: everything runs locally and stays yours. @@ -43,12 +35,17 @@ tree: --- :: +::steps{level="3"} +### Create the media folders + Create the `movies`, `tvseries`, and `library` folders in `/media`: ```bash [Terminal] mkdir -p /media/movies /media/library /media/tvseries ``` +### Deploy the stack + Open Dockge, click `compose`, name the stack `jellyfin`, and add the following config: ```yaml [compose.yaml] @@ -84,6 +81,8 @@ services: ``` :: +### Set your environment variables + Find your PUID and GUID by running: ```bash [Terminal] @@ -104,194 +103,159 @@ Deploy the stack. The local interface is available at `http://yourserverip:8096` The `/dev/dri` device is only needed for hardware-accelerated transcoding on Intel/AMD GPUs. Remove it if your server doesn't have one, or adapt it for an NVIDIA GPU following [linuxserver.io's documentation](https://docs.linuxserver.io/images/docker-jellyfin/#hardware-acceleration). :: +### Done ! +:: + ## Configure Jellyfin On first visit, Jellyfin walks you through a setup wizard: - Choose a display language and create your admin account. - Add a media library, pointing to `/media/movies` for movies and `/media/tvseries` for TV shows. -- (Optional) Enable hardware acceleration in _Dashboard > Playback_ if you have a compatible GPU. -And that's it! Add your media to `/media/movies` and `/media/tvseries` on your server, then install the Jellyfin app on your devices to watch locally or remotely. +And that's it! Add your media to `/media/movies` and `/media/tvseries` on your server. See below to start watching, at home or remotely. ::note If your media is stored on a network disk (e.g. NAS or external hard drive over the network), refer to the [Samba mount guide](/general/networking/samba) so Jellyfin can access it. :: -## Expose Jellyfin with Swag -To access Jellyfin outside your local network, we'll expose it through Swag. +## Hardware Transcoding + +Jellyfin re-encodes video on the fly whenever a client can't play a file as-is: the device's screen resolution is smaller than the source, its network connection is too slow for the file's bitrate, or it doesn't support the file's codec, HDR format, or subtitle type. This is called **transcoding**. Done purely in software, on the CPU, it's slow and can max out a modest server with just one or two simultaneous streams. **Hardware transcoding** offloads this work to your CPU's built-in GPU (Intel QuickSync, on most homelab hardware), which handles it far faster and lets the CPU stay free for everything else. + +**Tone mapping** is a related, separate feature: converting HDR video (which needs a compatible HDR display to look right) down to SDR so it displays correctly on a screen, TV, or client that doesn't support HDR, instead of looking washed out or overly dark. ::note - -We assume you have the subdomain `jellyfin.mydomain.com` with a `CNAME` pointing to `mydomain.com` in your [DNS zone](/general/networking/dns). And of course, [unless you use Cloudflare Zero Trust](/serveex/security/cloudflare), your box's port `443` must be forwarded to your server's port `443` in [NAT rules](/general/networking/nat). +This only applies to Intel (and some AMD) integrated GPUs passed through with `/dev/dri`, already included in this guide's `compose.yaml`. NVIDIA GPUs need a different setup entirely, covered in [linuxserver.io's documentation](https://docs.linuxserver.io/images/docker-jellyfin/#hardware-acceleration). If your server has no compatible GPU, transcoding still works, just entirely on the CPU. :: -Go to Dockge and edit SWAG's compose file by adding Jellyfin's network: +::steps{level="3"} +### Add the OpenCL mod + +Tone mapping specifically needs an Intel OpenCL runtime, which this image doesn't ship with by default. Add the dedicated mod to your `compose.yaml`: ```yaml [compose.yaml] ---- services: - swag: - container_name: # ... - # ... - networks: # Attach container to custom network - # ... - - jellyfin # Name of the declared network - -networks: # Define the custom network - # ... - jellyfin: # Declared network name - name: jellyfin_default # Actual external network name - external: true # Marks it as externally defined + jellyfin: + environment: + - DOCKER_MODS=linuxserver/mods:jellyfin-opencl-intel ``` -Redeploy the stack and wait for SWAG to be fully operational. - ::note - -Here we assume the Jellyfin network name is `jellyfin_default`. You can check the connection by visiting SWAG's dashboard at `http://yourserverip:81`. +Don't use the generic `universal-package-install` mod with `intel-opencl-icd` for this: it installs an older runtime from Ubuntu's own repositories that's a known source of tone mapping bugs. This dedicated mod ships a newer, purpose-built one. :: -In the Swag folders, create the file `jellyfin.subdomain.conf`: +Redeploy the stack for the mod to take effect. -::tip{icon=""} -✨ __Tip:__ Use [File Browser Quantum](/serveex/files/file-browser-quantum) to navigate and edit files instead of using terminal commands. +### Enable hardware acceleration + +In Jellyfin, go to _Dashboard > Playback_ and set **Hardware acceleration** to **Intel QuickSync (QSV)** (or **VA-API**, if QSV isn't available for your CPU generation). + +Then uncheck any codec your specific GPU doesn't support hardware-decode for. [Intel's own hardware acceleration page](https://jellyfin.org/docs/general/administration/hardware-acceleration/intel/) lists exactly which codecs each CPU generation supports. + +### Enable tone mapping + +Further down the same page, enable **Tone mapping**. This only takes effect once the OpenCL mod above is installed and the stack redeployed; enabling it without that step first will make HDR transcodes fail instead of just skipping the tone mapping. + +### Done ! :: -```bash [Terminal] -sudo nano /docker/swag/config/nginx/proxy-confs/jellyfin.subdomain.conf -``` +## Watching at Home -Paste the following configuration: +While connected to your home network (Wi-Fi or Ethernet, on the same LAN as your server), no exposure or VPN is needed at all. Use [Moonfin](https://github.com/Moonfin-Client/Moonfin-Core) wherever it's available (phones, tablets, Android TV/Google TV, desktop, web) instead of the official Jellyfin app: it looks and works the same everywhere, and can sync your settings across devices via its own server plugin. Connect to `http://yourserverlanip:8096`. -```nginx [jellyfin.subdomain.conf] -## Version 2023/12/19 +Samsung Tizen and LG webOS get a separate, dedicated [Moonfin app](https://github.com/Moonfin-Client/Smart-TV) instead (same name, different codebase), and Apple TV is best served by [Neptune](https://apps.apple.com/us/app/neptune-media-player/id6756797773), a dedicated tvOS client with native HEVC/AV1/VP9 decoding and full Atmos passthrough. Where none of these are available (Fire TV, Roku, Kodi), see the table below: -server { - listen 443 ssl; - listen [::]:443 ssl; - - server_name jellyfin.*; - - include /config/nginx/ssl.conf; - - client_max_body_size 0; - - # enable for ldap auth (requires ldap-location.conf in the location block) - #include /config/nginx/ldap-server.conf; - - # enable for Authelia (requires authelia-location.conf in the location block) - #include /config/nginx/authelia-server.conf; - - # enable for Authentik (requires authentik-location.conf in the location block) - #include /config/nginx/authentik-server.conf; - - location / { - # enable the next two lines for http auth - #auth_basic "Restricted"; - #auth_basic_user_file /config/nginx/.htpasswd; - - # enable for ldap auth (requires ldap-server.conf in the server block) - #include /config/nginx/ldap-location.conf; - - # enable for Authelia (requires authelia-server.conf in the server block) - #include /config/nginx/authelia-location.conf; - - # enable for Authentik (requires authentik-server.conf in the server block) - #include /config/nginx/authentik-location.conf; - - include /config/nginx/proxy.conf; - include /config/nginx/resolver.conf; - set $upstream_app jellyfin; - set $upstream_port 8096; - set $upstream_proto http; - proxy_pass $upstream_proto://$upstream_app:$upstream_port; - } -} -``` - -Press :kbd{value="Ctrl+O"}, then :kbd{value="Enter"} to save, and :kbd{value="Ctrl+X"} to exit. - -Wait a few minutes, then open `https://jellyfin.mydomain.com` in your browser. +| Platform | Recommended app | +|----------|-----------------| +| Phone / Tablet (Android, iOS) | [Moonfin](https://github.com/Moonfin-Client/Moonfin-Core) (Play Store / App Store) | +| Android TV / Google TV | [Moonfin](https://github.com/Moonfin-Client/Moonfin-Core) (Play Store or APK) | +| Apple TV | [Neptune](https://apps.apple.com/us/app/neptune-media-player/id6756797773) (App Store) | +| Samsung Tizen / LG webOS | [Moonfin](https://github.com/Moonfin-Client/Smart-TV) (sideloaded); the official apps exist but lag behind | +| Fire TV | Official Jellyfin app (Amazon Appstore) | +| Roku | Official Jellyfin app | +| Kodi | [Jellyfin for Kodi](https://jellyfin.org/docs/general/clients/kodi/) (syncs your library into Kodi), or [JellyCon](https://github.com/jellyfin/jellycon) for lighter streaming-only boxes | ::caution __If it fails:__ check your firewall rules. :: -## Protecting Jellyfin with TinyAuth -Add [TinyAuth](/serveex/security/tinyauth)'s forward-auth check directly to `jellyfin.subdomain.conf`, the same way as [the TinyAuth guide](/serveex/security/tinyauth#protecting-an-app-via-reverse-proxy): +## Watching Away From Home -```nginx [jellyfin.subdomain.conf]{22-34,37-38} -## Version 2023/12/19 +::warning +Jellyfin won't be exposed to the internet on this site, unlike most other apps. Its native apps (phone, TV, Chromecast...) talk directly to its API rather than a browser, so a forward-auth layer like [TinyAuth](/serveex/security/tinyauth) or [Authentik](/serveex/advanced/authentik) can't sit in front of it without breaking login and playback on those clients, and Jellyfin itself has no built-in 2FA or OIDC to fall back on. So instead of exposing it, we'll reach it through the [WireGuard VPN](/serveex/security/wireguard) already set up earlier in this guide. +:: -server { - listen 443 ssl; - listen [::]:443 ssl; +Instead of routing a whole device's traffic through the VPN, or even the whole `10.8.0.0/24` VPN subnet, we'll use a tighter **split tunnel**: each remote device only routes traffic for your server's own VPN address through the tunnel. Everything else (browsing, other apps) stays on the device's normal connection, and the VPN is only ever used to reach that one machine. - server_name jellyfin.*; +::note - include /config/nginx/ssl.conf; +This assumes your Jellyfin server already runs its own WireGuard client, connecting it to your VPN as described in [Client Server Setup](/serveex/security/wireguard#client-server-setup). Note the VPN address wg-easy assigned it (e.g. `10.8.0.2`); that's the address every device below will target. +:: - client_max_body_size 0; +::caution +This split tunnel is a client-side convention, not a server-enforced security boundary: `AllowedIPs` is a routing setting, and wg-easy doesn't add any firewall rule to back it up by default. Anyone with access to a device (or its `.conf` file) could edit `AllowedIPs` back to something broader and reach whatever else your WireGuard server can route to, including other connected peers. If a peer is one of your own devices, that's usually an acceptable risk. If you're handing a peer to a third party (a friend, family member) who should only ever reach Jellyfin, see [Restricting a third-party peer to just Jellyfin](#restricting-a-third-party-peer-to-just-jellyfin) below for a way to actually enforce that server-side. +:: - # enable for ldap auth (requires ldap-location.conf in the location block) - #include /config/nginx/ldap-server.conf; +::steps{level="3"} +### Create a client in wg-easy for each device - # enable for Authelia (requires authelia-location.conf in the location block) - #include /config/nginx/authelia-server.conf; +Following [Retrieve Configuration Files](/serveex/security/wireguard#retrieve-configuration-files), create one client per device (phone, computer, TV/streaming box) and download its config file. - # enable for Authentik (requires authentik-location.conf in the location block) - #include /config/nginx/authentik-server.conf; +### Narrow the tunnel to just the server - location /tinyauth { - internal; - proxy_pass http://tinyauth:3000/api/auth/nginx; - proxy_pass_request_body off; - proxy_set_header Content-Length ""; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Forwarded-Host $http_host; - proxy_set_header X-Forwarded-Uri $request_uri; - } +Open each downloaded `.conf` file and replace its `AllowedIPs` line so it only covers your Jellyfin server's VPN address, as a `/32` (a single IP, not a range): - location @tinyauth_login { - return 302 https://tinyauth.mydomain.com/login?redirect_uri=$scheme://$http_host$request_uri; - } - - location / { - auth_request /tinyauth; - error_page 401 = @tinyauth_login; - - # enable the next two lines for http auth - #auth_basic "Restricted"; - #auth_basic_user_file /config/nginx/.htpasswd; - - # enable for ldap auth (requires ldap-server.conf in the server block) - #include /config/nginx/ldap-location.conf; - - # enable for Authelia (requires authelia-server.conf in the server block) - #include /config/nginx/authelia-location.conf; - - # enable for Authentik (requires authentik-server.conf in the server block) - #include /config/nginx/authentik-location.conf; - - include /config/nginx/proxy.conf; - include /config/nginx/resolver.conf; - set $upstream_app jellyfin; - set $upstream_port 8096; - set $upstream_proto http; - proxy_pass $upstream_proto://$upstream_app:$upstream_port; - } -} +```properties [wg0.conf] +AllowedIPs = 10.8.0.2/32 ``` ::note -The `location /tinyauth` block runs inside SWAG's own container, so SWAG needs to be on TinyAuth's Docker network to reach it by name (`tinyauth` here). This should already be set up from [exposing TinyAuth itself](/serveex/security/tinyauth#exposing-tinyauth-with-swag). If you run into an error, double-check SWAG's compose file still has that network attached. +Replace `10.8.0.2` with whatever address your server actually got. Leaving the default `10.8.0.0/24` here would tunnel traffic for every VPN peer, not just your server. :: -::tip{icon=""} -✨ __Tip:__ You can protect this app with Authentik instead of TinyAuth by opening `jellyfin.subdomain.conf` and uncommenting `include /config/nginx/authentik-server.conf;`{lang=nginx} and `include /config/nginx/authentik-location.conf;`{lang=nginx}. Don't forget to [create an application and provider in Authentik](/serveex/advanced/authentik#protecting-an-app-via-reverse-proxy). +### Import the config into each device + +- **Phone:** install the WireGuard app, then import the edited `.conf` file (or scan a QR code generated from it). +- **Computer:** install the [WireGuard client](https://www.wireguard.com/install/) and import the file. +- **TV:** if it runs Android TV or Google TV, install WireGuard from the Play Store. Copy the `.conf` file onto a USB drive, plug it into the TV or box, then use the app's **Add from file** option to browse the USB drive and import it. Most other TV operating systems (Tizen, webOS...) can't install a VPN client directly, so connect through a device that already has the tunnel active instead (an Android TV box, or casting from a phone or computer that's connected). + +::warning +A `.conf` file is a credential, not just a settings file: whoever has it can connect to your VPN as that device. Once it's imported, delete it from the USB drive (and anywhere else you copied it, like Downloads folders), and never share it with anyone else. :: -And you're done! +### Done ! +:: + +### Restricting a third-party peer to just Jellyfin + +[wg-easy](/serveex/security/wireguard) (v15.3+) can enforce this itself, with a real firewall rule inside its own container rather than a client-side setting. This is the right approach when the peer belongs to someone else, not one of your own devices. + +::steps{level="4"} +#### Enable the per-client firewall + +In [wg-easy](/serveex/security/wireguard)'s admin panel, go to the interface settings and enable **Per-Client Firewall**. It's marked experimental, but works by generating a dedicated `iptables` chain inside the wg-easy container. + +#### Restrict the peer's allowed destination + +Edit the third party's client and set its **Firewall Allowed IPs** to your Jellyfin server's VPN address, e.g. `10.8.0.2`, or narrower still, `10.8.0.2:8096/tcp` to also restrict the port and protocol. + +::note + +Leaving this field empty falls back to the client's own `AllowedIPs`, which isn't restrictive: it must be set explicitly for this peer. This also cuts off that peer's regular internet access through the tunnel, since only traffic to Jellyfin is now allowed, which is the intended effect for a guest peer. +:: + +#### Verify the rule was applied + +```bash [Terminal] +docker exec wg-easy iptables -L WG_CLIENTS -n -v +``` + +You should see an `ACCEPT` line for that peer's IP pointing only at your Jellyfin server, followed by a catch-all `DROP`. + +#### Done ! +:: + +Once connected, open the Jellyfin app or `http://10.8.0.2:8096` (your server's VPN address) from that device. Only traffic to that address goes through the VPN; the rest of the device's connection is untouched.