Last active
May 5, 2025 07:02
Revisions
-
Vigrond revised this gist
Jan 4, 2023 . 1 changed file with 97 additions and 97 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -81,107 +81,107 @@ This config will need to be updated: Copy the below config into `~/projects/jellyfin/nginx/nginx.conf`. ``` # nginx config Borrowed from https://jellyfin.org/docs/general/networking/nginx/ user nginx; ## Default: nobody worker_processes 1; ## Default: 1 pid /tmp/nginx.pid; events { worker_connections 1024; ## Default: 1024 } http { index index.html index.htm index.php; server { listen 80; listen [::]:80; server_name jellyfin.yourdomain.com; # Uncomment to redirect HTTP to HTTPS return 301 https://$host$request_uri; } server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name jellyfin.yourdomain.com; ## The default `client_max_body_size` is 1M, this might not be enough for some posters, etc. client_max_body_size 20M; ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem; include /etc/letsencrypt/options-ssl-nginx.conf; ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; add_header Strict-Transport-Security "max-age=31536000" always; ssl_trusted_certificate /etc/letsencrypt/live/yourdomain.com/chain.pem; ssl_stapling on; ssl_stapling_verify on; # Security / XSS Mitigation Headers # NOTE: X-Frame-Options may cause issues with the webOS app add_header X-Frame-Options "SAMEORIGIN"; add_header X-XSS-Protection "1; mode=block"; add_header X-Content-Type-Options "nosniff"; # Content Security Policy # See: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP # Enforces https content and restricts JS/CSS to origin # External Javascript (such as cast_sender.js for Chromecast) must be whitelisted. # NOTE: The default CSP headers may cause issues with the webOS app # NOTE 2: cast_sender.js links may need to be updated. Check jellyfin console for load errors add_header Content-Security-Policy "default-src https: data: blob: http://image.tmdb.org; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' https://www.gstatic.com/cv/js/sender/v1/cast_sender.js https://www.gstatic.com/eureka/clank/108/cast_sender.js https://www.gstatic.com/eureka/clank/107/cast_sender.js https://www.gstatic.com/eureka/clank/cast_sender.js https://www.youtube.com blob:; worker-src 'self' blob:; connect-src 'self'; object-src 'none'; frame-ancestors 'self'"; location = / { return 302 http://$host/web/; #return 302 https://$host/web/; } location / { # Proxy main Jellyfin traffic proxy_pass http://jellyfin:8096; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Protocol $scheme; proxy_set_header X-Forwarded-Host $http_host; # Disable buffering when the nginx proxy gets very resource heavy upon streaming proxy_buffering off; } # location block for /web - This is purely for aesthetics so /web/#!/ works instead of having to go to /web/index.html/#!/ location = /web/ { # Proxy main Jellyfin traffic proxy_pass http://jellyfin:8096/web/index.html; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Protocol $scheme; proxy_set_header X-Forwarded-Host $http_host; } location /socket { # Proxy Jellyfin Websockets traffic proxy_pass http://jellyfin:8096; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Protocol $scheme; proxy_set_header X-Forwarded-Host $http_host; } } } ``` ### Create the docker-compose file @@ -506,6 +506,6 @@ ___ ### `jellyfin` acts like it is playing, but the screen is black Check your supported bitrate. For example, my first generation Chromecast only supports bitrates up to 8 Mbps. You can set the bitrate in user -> settings -> playback -> `Google Cast Streaming Quality`. Note: This seems to only change bitrate, not resolution as implied by the dropdown menu. -
Vigrond revised this gist
Jan 4, 2023 . 1 changed file with 0 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -234,7 +234,6 @@ services: # Optional - alternative address used for autodiscovery environment: - JELLYFIN_PublishedServerUrl=https://jellyfin.yourdomain.com ``` ### Create the SSL Certificate using certbot -
Vigrond revised this gist
Jan 4, 2023 . 1 changed file with 512 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1 +1,512 @@ # Setting up Jellyfin and Chromecast using Docker, Nginx, and dnsmasq 1/03/2023 + [Requirements](#requirements) + [Guide Specific Requirements](#guide-specific-requirements) + [Installing Docker](#installing-docker) + [Installing Jellyfin and Nginx](#installing-jellyfin-and-nginx) + [Set up our DNS server](#set-up-our-dns-server) + [Block Chromecast's hardcoded DNS server](#block-chromecasts-hardcoded-dns-server) + [Set your router's Primary DNS to your local DNS server](#set-your-routers-primary-dns-to-your-local-dns-server) + [Ensure Content-Security-Policy is correct](#ensure-content-security-policy-is-correct) + [Configure jellyfin networking settings](#configure-jellyfin-networking-settings) + [Troubleshooting](#troubleshooting) ## Introduction This will serve as a guide for setting up Jellyfin Web UI with Chromecast. This has proven to be a challenge because: * Chromecast requires that HTTP traffic be encrypted into HTTPS with a valid certificate. * Chromecast is hard coded with google DNS servers (8.8.8.8,8.8.4.4) * Basically, Chromecast assumes that you are a 3rd party website hosted on the public internet somewhere like Youtube. Which you are not, you are a local server hosting Jellyfin. This guide will make your Jellyfin server appear as such. The above points introduce some challenges such as: * Creating a valid signed HTTPS certificate for a public domain name * Making the above certificate work properly on a local network * Rerouting Google DNS addresses to our own internal DNS server * Creating a Content-Security-Policy so browsers do not block cross origin Chromecast js scripts ## Requirements: * A valid public domain name and access to its DNS records. This guide uses a domain registered on domains.google.com. **Creating a subdomain / CNAME in your DNS settings is NOT necessary.** * A router that allows static routing settings (most modern routers). * An internal DNS server on your local network. This guide uses an Ubuntu laptop as the DNS server, NGINX proxy server, and Jellyfin server. ## Guide Specific Requirements: This guide uses a specific setup that may or may not apply to your environment. * Ubuntu 22.04 for hosting Jellyfin, a DNS server, and an NGINX proxy * Jellyfin 10.8.8 * Docker 20.10.17 and docker-compose 1.29.2 for containerization of jellyfin and NGINX * dnsmasq DNS server ## Installing Docker Follow instructions here https://docs.docker.com/desktop/install/ubuntu/ and ensure the test script works. Consider linking `docker-compose` (vs using `docker compose` without the dash) for accuracy of the guide. ## Installing Jellyfin and Nginx We will setup a docker compose file to handle Jellyfin and NGINX ### Create the necessary directories: ``` mkdir -p ~/projects/jellyfin && cd ~/projects/jellyfin && mkdir -p cache config media nginx ``` This will create the `jellyfin` folder in a projects folder in your home directory. It will also create the necessary docker `volume` folders for jellyfin and nginx. ### Create the Nginx Dockerfile ``` cat <<"EOT" >> ~/projects/jellyfin/nginx/Dockerfile FROM nginx:latest RUN apt-get update RUN apt-get install -y certbot EOT ``` This creates an Nginx container with certbot installed. ### Create the Nginx configuration file This config will need to be updated: * Replace mentions of `yourdomain.com` with your actual domain name. * Once jellyfin is running, update the `Content-Security-Policy` header with the appropriate `cast_sender.js` links. If `cast_sender.js` is not loading, it will be obvious in the console errors in the `jellyfin` web ui. (will remind again at the end of the guide). These `cast_sender.js` links may need to be updated as `jellyfin` is updated. (This will work without `add_header Content-Security-Policy`, but do you trust code running on your local network that much?) Copy the below config into `~/projects/jellyfin/nginx/nginx.conf`. ``` # nginx config Borrowed from https://jellyfin.org/docs/general/networking/nginx/ user nginx; ## Default: nobody worker_processes 1; ## Default: 1 pid /tmp/nginx.pid; events { worker_connections 1024; ## Default: 1024 } http { index index.html index.htm index.php; server { listen 80; listen [::]:80; server_name jellyfin.yourdomain.com; # Uncomment to redirect HTTP to HTTPS return 301 https://$host$request_uri; } server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name jellyfin.yourdomain.com; ## The default `client_max_body_size` is 1M, this might not be enough for some posters, etc. client_max_body_size 20M; ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem; include /etc/letsencrypt/options-ssl-nginx.conf; ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; add_header Strict-Transport-Security "max-age=31536000" always; ssl_trusted_certificate /etc/letsencrypt/live/yourdomain.com/chain.pem; ssl_stapling on; ssl_stapling_verify on; # Security / XSS Mitigation Headers # NOTE: X-Frame-Options may cause issues with the webOS app add_header X-Frame-Options "SAMEORIGIN"; add_header X-XSS-Protection "1; mode=block"; add_header X-Content-Type-Options "nosniff"; # Content Security Policy # See: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP # Enforces https content and restricts JS/CSS to origin # External Javascript (such as cast_sender.js for Chromecast) must be whitelisted. # NOTE: The default CSP headers may cause issues with the webOS app # NOTE 2: cast_sender.js links may need to be updated. Check jellyfin console for load errors add_header Content-Security-Policy "default-src https: data: blob: http://image.tmdb.org; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' https://www.gstatic.com/cv/js/sender/v1/cast_sender.js https://www.gstatic.com/eureka/clank/108/cast_sender.js https://www.gstatic.com/eureka/clank/107/cast_sender.js https://www.gstatic.com/eureka/clank/cast_sender.js https://www.youtube.com blob:; worker-src 'self' blob:; connect-src 'self'; object-src 'none'; frame-ancestors 'self'"; location = / { return 302 http://$host/web/; #return 302 https://$host/web/; } location / { # Proxy main Jellyfin traffic proxy_pass http://jellyfin:8096; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Protocol $scheme; proxy_set_header X-Forwarded-Host $http_host; # Disable buffering when the nginx proxy gets very resource heavy upon streaming proxy_buffering off; } # location block for /web - This is purely for aesthetics so /web/#!/ works instead of having to go to /web/index.html/#!/ location = /web/ { # Proxy main Jellyfin traffic proxy_pass http://jellyfin:8096/web/index.html; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Protocol $scheme; proxy_set_header X-Forwarded-Host $http_host; } location /socket { # Proxy Jellyfin Websockets traffic proxy_pass http://jellyfin:8096; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Protocol $scheme; proxy_set_header X-Forwarded-Host $http_host; } } } ``` ### Create the docker-compose file This config utilizes hardware acceleration as described here https://jellyfin.org/docs/general/administration/hardware-acceleration#hardware-acceleration-on-docker-linux This will enable `jellyfin` to use a GPU to do encoding and decoding. In this example, I have an NVIDIA Quadro card I want to take advantage of, so I have installed the `nvidia container toolkit` documented here https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html#getting-started **If you do not want to utilize hardware acceleration, remove the `deploy` tree in the config** Update the environment variable `JELLYFIN_PublishedServerUrl` in this configuration to match your domain name. Copy the below config into `~/projects/jellyfin/docker-compose.yml`. ``` version: '3.5' services: web: build: nginx container_name: jellyfin_web restart: always ports: - "80:80" - "443:443" volumes: - ./nginx/nginx.conf:/etc/nginx/nginx.conf - ./nginx/letsencrypt:/etc/letsencrypt - ./nginx/ssl:/etc/nginx/ssl logging: driver: "json-file" options: max-size: "500k" max-file: "10" jellyfin: image: jellyfin/jellyfin container_name: jellyfin volumes: - ./config:/config - ./cache:/cache - ./media:/media # Optional - Hardware acceleration deploy: resources: reservations: devices: - capabilities: [gpu] restart: 'unless-stopped' # Optional - alternative address used for autodiscovery environment: - JELLYFIN_PublishedServerUrl=https://jellyfin.yourdomain.com ``` ### Create the SSL Certificate using certbot First we need to run our `docker-compose` file. Nginx will automatically fail because it will not be able to find the SSL certificate specified in our `nginx.conf`. But we don't need to worry about that right now. Go ahead and run `docker-compose up` while in the `~/projects/jellyfin` directory Once it loads, you'll probably see something like: ``` user@group:~/projects/jellyfin$ docker-compose up Starting jellyfin_web ... done Starting jellyfin ... done Attaching to jellyfin_web, jellyfin web_1 | /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration web_1 | /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/ web_1 | /docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh web_1 | 10-listen-on-ipv6-by-default.sh: info: IPv6 listen already enabled web_1 | /docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh web_1 | /docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh web_1 | /docker-entrypoint.sh: Configuration complete; ready for start up web_1 | 2023/01/04 09:46:55 [emerg] 1#1: cannot load certificate... ... ``` #### Let us spin up another `nginx container` and setup the SSL certificate. Open up a new terminal and navigate to our project directory `cd ~/projects/jellyfin` Let us start a new nginx container with a bash prompt presented to us: `docker-compose run web /bin/bash` Now with certbot already installed in our `Dockerfile`, we can simply run: `certbot certonly --manual -d *.yourdomain.com --agree-tos -m youremail@address.com --preferred-challenges=dns` * Replace `yourdomain.com` while preserving the `*.` wildcard in front of it. * Replace `youremail@address.com`with your own email This command will perform a DNS-01 challenge to validate you control your domain name: ``` Performing the following challenges: dns-01 challenge for yourdomain.com - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Please deploy a DNS TXT record under the name _acme-challenge.yourdomain.com with the following value: 917nbBrGIYv0WbvM0URhPvcWFKh5wpryZQtXtJfti_8 Before continuing, verify the record is deployed. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Press Enter to Continue ``` #### Create a custom DNS record Now we need to login to our domain registrar and navigate to the DNS settings. We will set a custom record using the info `certbot` is giving us: ``` Host name: _acme-challenge.yourdomain.com Type: TXT TTL: 1 Hour Data: 917nbBrGIYv0WbvM0URhPvcWFKh5wpryZQtXtJfti_8 ``` Every registrar is different and may require some fiddling. For example, with `domains.google.com`, we only want to put in `_acme-challenge` into the `Host name` field, as Google autofills the `.yourdomain.com` afterwards. Once this is done, press `Enter` to continue. If successful, `certbot` will place the created SSL certificates into `/etc/letsencrypt`. Type `exit` to shut down the container and return to our project directory. #### Verify server is running Type `docker ps` to verify that `jellyfin` and `jellyfin_web` containers are running and the status is healthy. You may also look at the output of `docker-compose up`. ## Set up our DNS server We will use `dnsmasq` for our DNS service. Keep in mind that if `libvirtd` is installed, it uses its own installation of `dnsmasq`. We will not be using this version, and install one that automatically sets up a service for us. `sudo apt-get install dnsmasq` You may receive and error message that the service failed to start on a specified address. This will be fixed in the next step. ### Configuring dnsmasq Open the `dnsmasq` configuration file: `sudo gedit /etc/dnsmasq.conf` We are presented with a well-commented configuration file. Luckily, we only need to worry about a few settings: Ensure no-resolv is uncommented: ``` # If you don't want dnsmasq to read /etc/resolv.conf or any other # file, getting its servers from this file instead (see below), then # uncomment this. no-resolv ``` Ensure DNS forwarding addresses are set. This example uses Quad9's DNS service: ``` # Add other name servers here, with domain specs if they are for # non-public domains. server=9.9.9.9 server=149.112.112.112 ``` Set our `jellyfin` redirect address where `192.168.0.2` is replaced your jellyfin machine's local IP address. Static/Manual IP settings on your network configuration is highly recommended: ``` # Add domains which you want to force to an IP address here. # The example below send any host in double-click.net to a local # web-server. address=/jellyfin.yourdomain.com/192.168.0.2 ``` Set the ethernet interface. You can find yours by using `ip -br -f inet address` and looking for the name of the device that lists the correct IP. (alternatively, you may set the `listen-address`) Whatever IP is assigned to the device (or the `listen-address`) will be the local DNS server address. ``` # If you want dnsmasq to listen for DHCP and DNS requests only on # specified interfaces (and the loopback) give the name of the # interface (eg eth0) here. # Repeat the line for more than one interface. interface=eth0 ``` Ensure `bind-interfaces` is uncommented: ``` # On systems which support it, dnsmasq binds the wildcard address, # even when it is listening on only some interfaces. It then discards # requests that it shouldn't reply to. This has the advantage of # working even when interfaces come and go and change address. If you # want dnsmasq to really bind only the interfaces it is listening on, # uncomment this option. About the only time you may need this is when # running another nameserver on the same machine. bind-interfaces ``` Ensure you save the file after done editing. ### Restart the dnsmasq service `service dnsmasq restart` Ensure it is active and running `service dnsmasq status` ## Block Chromecast's hardcoded DNS server To block Chromecast's access to `8.8.8.8` and instead use your own DNS server you need to login to your router admin panel and find the `Routing` settings. For a `TP-Link AX1800` router, the route entry would look as follows: ``` Network Destination: 8.8.8.8 Subnet Mask: 255.255.255.255 Default Gateway (your router's address) 192.168.0.1 Interface LAN Description Google DNS 1 ``` Any device trying to access `8.8.8.8` is now redirected to your router. ## Set your router's Primary DNS to your local DNS server Now that `8.8.8.8` is blocked, set the router's `Primary DNS server` to your `local DNS server address`. Now would be a good time to restart the Chromecast if it has been on. ## Ensure Content-Security-Policy is correct Start up your Chrome browser and ensure the dev tools are open. ( right click -> inspect ) In the `Network` tab ensure `Disable Cache` is enabled. Switch to the `Console` tab and then enter in `jellyfin.yourserver.com` This should automatically load `https` and display a secure connection (lock icon next to the address bar) Once logged into your jellyfin dashboard, ensure the console log does not display any errors that look like: ``` Refused to load the script 'https://www.gstatic.com/eureka/clank/108/cast_sender.js' because it violates the following Content Security Policy directive... ``` If you do, you need to add the URL in the error to the `add_header Content-Security-Policy` line in your `nginx.conf`. As mentioned before, this will work without `add_header Content-Security-Policy`, but do you trust the code running on your local server that much? ## Configure jellyfin networking settings Access the Networking settings by going to Dashboard - > Networking Set `LAN Networks` to your local area network range with slash notation. Example: `192.168.0.0/24` Set `Known Proxies` to your NGINX Proxy server name, `jellyfin.yourserver.com` **Do not change any HTTPS settings, it is not necessary with the nginx proxy** ## Troubleshooting ### `jellyfin.yourserver.com` isn't loading Ensure `docker-compose up` has been ran in your project folder Check docker logs `docker logs jellyfin`, `docker logs jellyfin_web` for any errors Ensure your device's network settings is set to use your local DNS address. You can check using nmap: `nmap jellyfin.yourserver.com` and it should point to the correct local IP address. Ensure the DNS server is running. `service dnsmasq status` Ensure `jellyfin`'s networking HTTPS settings are disabled ___ ### `Google Cast` isn't showing up in the cast list Ensure you're running the site in Google Chrome or Chromium browsers Ensure there are no errors loading `cast_sender.js` in the browser console logs Ensure your Chromecast is plugged in and powered on. ___ ### I get an error saying Chromecast is unable to communicate with my `jellyfin` server Your Chromecast likely still has access to the public `8.8.8.8` DNS server. Double check your router settings. `nmap 8.8.8.8` should fail ___ ### `jellyfin` acts like it is playing, but the screen is black Check your supported bitrate. For example, my first generation Chromecast only supports bitrates up to 8k. You can set the bitrate in user -> settings -> playback -> `Google Cast Streaming Quality`. Note: This seems to only change bitrate, not resolution as implied by the dropdown menu. -
Vigrond revised this gist
Jan 4, 2023 . 2 changed files with 1 addition and 3 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,3 +0,0 @@ This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1 @@ *Setting up Jellyfin with Chromecast -
Vigrond renamed this gist
Jan 4, 2023 . 1 changed file with 0 additions and 0 deletions.There are no files selected for viewing
File renamed without changes. -
Vigrond created this gist
Jan 4, 2023 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,3 @@ test `test`