From a4345b8e243a0a44f35fb716f40fc7d28f8ce50f Mon Sep 17 00:00:00 2001 From: Tom Carrio Date: Mon, 25 Nov 2024 00:54:49 -0500 Subject: [PATCH] feat: support for nginx-hosted tailnet-proxied plex --- nixos/mixins/services/plex.nix | 239 +++++++----------- nixos/modules/services/remote-builder.nix | 8 +- .../services/tailscale-autoconnect.nix | 5 + nixos/server/nix-proxy-droplet/default.nix | 11 +- 4 files changed, 115 insertions(+), 148 deletions(-) diff --git a/nixos/mixins/services/plex.nix b/nixos/mixins/services/plex.nix index 909f0ed3..f54f6ff1 100644 --- a/nixos/mixins/services/plex.nix +++ b/nixos/mixins/services/plex.nix @@ -1,8 +1,87 @@ { lib, pkgs, config, ... }: let + external_domain = "media.carrio.me"; internal_domain = "media.int.carrio.me"; tailnet_domain = "glass.griffin-cobra.ts.net"; dataDir = "/data/plex/plex/"; + + acmeCertConfig = { + group = "nginx"; + dnsProvider = "cloudflare"; + + # https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#EnvironmentFile= + environmentFile = config.age.secrets.cloudflare-dns-verification.path; + }; + + extraConfig = '' + #Some players don't reopen a socket and playback stops totally instead of resuming after an extended pause + send_timeout 100m; + + # Why this is important: https://blog.cloudflare.com/ocsp-stapling-how-cloudflare-just-made-ssl-30/ + ssl_stapling on; + ssl_stapling_verify on; + + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_prefer_server_ciphers on; + #Intentionally not hardened for security for player support and encryption video streams has a lot of overhead with something like AES-256-GCM-SHA384. + ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA'; + + # Forward real ip and host to Plex + 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 Host $server_addr; + proxy_set_header Referer $server_addr; + proxy_set_header Origin $server_addr; + + # Plex has A LOT of javascript, xml and html. This helps a lot, but if it causes playback issues with devices turn it off. + gzip on; + gzip_vary on; + gzip_min_length 1000; + gzip_proxied any; + gzip_types text/plain text/css text/xml application/xml text/javascript application/x-javascript image/svg+xml; + gzip_disable "MSIE [1-6]\."; + + # Nginx default client_max_body_size is 1MB, which breaks Camera Upload feature from the phones. + # Increasing the limit fixes the issue. Anyhow, if 4K videos are expected to be uploaded, the size might need to be increased even more + client_max_body_size 100M; + + # Plex headers + proxy_set_header X-Plex-Client-Identifier $http_x_plex_client_identifier; + proxy_set_header X-Plex-Device $http_x_plex_device; + proxy_set_header X-Plex-Device-Name $http_x_plex_device_name; + proxy_set_header X-Plex-Platform $http_x_plex_platform; + proxy_set_header X-Plex-Platform-Version $http_x_plex_platform_version; + proxy_set_header X-Plex-Product $http_x_plex_product; + proxy_set_header X-Plex-Token $http_x_plex_token; + proxy_set_header X-Plex-Version $http_x_plex_version; + proxy_set_header X-Plex-Nocache $http_x_plex_nocache; + proxy_set_header X-Plex-Provides $http_x_plex_provides; + proxy_set_header X-Plex-Device-Vendor $http_x_plex_device_vendor; + proxy_set_header X-Plex-Model $http_x_plex_model; + + # Websockets + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + + # Buffering off send to the client as soon as the data is received from Plex. + proxy_redirect off; + proxy_buffering off; + ''; + + baseVirtualHostConfig = { + # Disallow insecure HTTP connection + forceSSL = true; + # http2 can more performant for streaming: https://blog.cloudflare.com/introducing-http2/ + http2 = true; + + inherit extraConfig; + + locations."/" = { + proxyPass = "http://127.0.0.1:32400/"; + }; + }; in { imports = [ @@ -14,7 +93,8 @@ in services.plex = { inherit dataDir; enable = true; - openFirewall = false; + # Exposes on the local intranet + openFirewall = true; # Various media services are granted necessary access to volumes via 'media-server' group group = "media-server"; @@ -33,13 +113,11 @@ in acceptTerms = true; defaults.email = "tom@carrio.dev"; certs = { - "${internal_domain}" = { + "${internal_domain}" = acmeCertConfig // { domain = internal_domain; - group = "nginx"; - dnsProvider = "cloudflare"; - - # https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#EnvironmentFile= - environmentFile = config.age.secrets.cloudflare-dns-verification.path; + }; + "${external_domain}" = acmeCertConfig // { + domain = external_domain; }; }; }; @@ -76,149 +154,24 @@ in # Plex NixOS Docs: https://nixos.wiki/wiki/Plex services.nginx = { enable = true; - # give a name to the virtual host. It also becomes the server name. - virtualHosts."${internal_domain}" = { - # Since we want a secure connection, we force SSL - forceSSL = true; - - # http2 can more performant for streaming: https://blog.cloudflare.com/introducing-http2/ - http2 = true; - - # Provide the ssl cert and key for the vhost + virtualHosts."${internal_domain}" = baseVirtualHostConfig // { sslCertificate = "/var/lib/acme/${internal_domain}/fullchain.pem"; sslCertificateKey = "/var/lib/acme/${internal_domain}/key.pem"; - - extraConfig = '' - #Some players don't reopen a socket and playback stops totally instead of resuming after an extended pause - send_timeout 100m; - - # Why this is important: https://blog.cloudflare.com/ocsp-stapling-how-cloudflare-just-made-ssl-30/ - ssl_stapling on; - ssl_stapling_verify on; - - ssl_protocols TLSv1 TLSv1.1 TLSv1.2; - ssl_prefer_server_ciphers on; - #Intentionally not hardened for security for player support and encryption video streams has a lot of overhead with something like AES-256-GCM-SHA384. - ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA'; - - # Forward real ip and host to Plex - 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 Host $server_addr; - proxy_set_header Referer $server_addr; - proxy_set_header Origin $server_addr; - - # Plex has A LOT of javascript, xml and html. This helps a lot, but if it causes playback issues with devices turn it off. - gzip on; - gzip_vary on; - gzip_min_length 1000; - gzip_proxied any; - gzip_types text/plain text/css text/xml application/xml text/javascript application/x-javascript image/svg+xml; - gzip_disable "MSIE [1-6]\."; - - # Nginx default client_max_body_size is 1MB, which breaks Camera Upload feature from the phones. - # Increasing the limit fixes the issue. Anyhow, if 4K videos are expected to be uploaded, the size might need to be increased even more - client_max_body_size 100M; - - # Plex headers - proxy_set_header X-Plex-Client-Identifier $http_x_plex_client_identifier; - proxy_set_header X-Plex-Device $http_x_plex_device; - proxy_set_header X-Plex-Device-Name $http_x_plex_device_name; - proxy_set_header X-Plex-Platform $http_x_plex_platform; - proxy_set_header X-Plex-Platform-Version $http_x_plex_platform_version; - proxy_set_header X-Plex-Product $http_x_plex_product; - proxy_set_header X-Plex-Token $http_x_plex_token; - proxy_set_header X-Plex-Version $http_x_plex_version; - proxy_set_header X-Plex-Nocache $http_x_plex_nocache; - proxy_set_header X-Plex-Provides $http_x_plex_provides; - proxy_set_header X-Plex-Device-Vendor $http_x_plex_device_vendor; - proxy_set_header X-Plex-Model $http_x_plex_model; - - # Websockets - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - - # Buffering off send to the client as soon as the data is received from Plex. - proxy_redirect off; - proxy_buffering off; - ''; - locations."/" = { - proxyPass = "http://127.0.0.1:32400/"; - }; }; - virtualHosts."${tailnet_domain}" = { - # Since we want a secure connection, we force SSL - forceSSL = true; - - # http2 can more performant for streaming: https://blog.cloudflare.com/introducing-http2/ - http2 = true; - - # Provide the ssl cert and key for the vhost + virtualHosts."${tailnet_domain}" = baseVirtualHostConfig // { sslCertificate = "/var/lib/acme/${tailnet_domain}/cert.pem"; sslCertificateKey = "/var/lib/acme/${tailnet_domain}/key.pem"; + }; - extraConfig = '' - #Some players don't reopen a socket and playback stops totally instead of resuming after an extended pause - send_timeout 100m; - - # Why this is important: https://blog.cloudflare.com/ocsp-stapling-how-cloudflare-just-made-ssl-30/ - ssl_stapling on; - ssl_stapling_verify on; - - ssl_protocols TLSv1 TLSv1.1 TLSv1.2; - ssl_prefer_server_ciphers on; - #Intentionally not hardened for security for player support and encryption video streams has a lot of overhead with something like AES-256-GCM-SHA384. - ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA'; - - # Forward real ip and host to Plex - 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 Host $server_addr; - proxy_set_header Referer $server_addr; - proxy_set_header Origin $server_addr; - - # Plex has A LOT of javascript, xml and html. This helps a lot, but if it causes playback issues with devices turn it off. - gzip on; - gzip_vary on; - gzip_min_length 1000; - gzip_proxied any; - gzip_types text/plain text/css text/xml application/xml text/javascript application/x-javascript image/svg+xml; - gzip_disable "MSIE [1-6]\."; - - # Nginx default client_max_body_size is 1MB, which breaks Camera Upload feature from the phones. - # Increasing the limit fixes the issue. Anyhow, if 4K videos are expected to be uploaded, the size might need to be increased even more - client_max_body_size 100M; - - # Plex headers - proxy_set_header X-Plex-Client-Identifier $http_x_plex_client_identifier; - proxy_set_header X-Plex-Device $http_x_plex_device; - proxy_set_header X-Plex-Device-Name $http_x_plex_device_name; - proxy_set_header X-Plex-Platform $http_x_plex_platform; - proxy_set_header X-Plex-Platform-Version $http_x_plex_platform_version; - proxy_set_header X-Plex-Product $http_x_plex_product; - proxy_set_header X-Plex-Token $http_x_plex_token; - proxy_set_header X-Plex-Version $http_x_plex_version; - proxy_set_header X-Plex-Nocache $http_x_plex_nocache; - proxy_set_header X-Plex-Provides $http_x_plex_provides; - proxy_set_header X-Plex-Device-Vendor $http_x_plex_device_vendor; - proxy_set_header X-Plex-Model $http_x_plex_model; - - # Websockets - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - - # Buffering off send to the client as soon as the data is received from Plex. - proxy_redirect off; - proxy_buffering off; - ''; - locations."/" = { - proxyPass = "http://127.0.0.1:32400/"; - }; + virtualHosts."${external_domain}" = baseVirtualHostConfig // { + sslCertificate = "/var/lib/acme/${external_domain}/fullchain.pem"; + sslCertificateKey = "/var/lib/acme/${external_domain}/key.pem"; }; }; + + networking.firewall = { + allowedTCPPorts = [ 443 ]; + allowedUDPPorts = [ 443 ]; + }; } \ No newline at end of file diff --git a/nixos/modules/services/remote-builder.nix b/nixos/modules/services/remote-builder.nix index 40d97435..8f8f462e 100644 --- a/nixos/modules/services/remote-builder.nix +++ b/nixos/modules/services/remote-builder.nix @@ -36,9 +36,9 @@ in { config = lib.mkIf cfg.enable { nix.distributedBuilds = true; nix.buildMachines = [] ++ (lib.optional cfg.hosts.glass.enable glass); - ### optional, useful when the builder has a faster internet connection than yours - # nix.extraOptions = '' - # builders-use-substitutes = true - # ''; + ## optional, useful when the builder has a faster internet connection than yours + nix.extraOptions = '' + builders-use-substitutes = true + ''; }; } \ No newline at end of file diff --git a/nixos/modules/services/tailscale-autoconnect.nix b/nixos/modules/services/tailscale-autoconnect.nix index f92b13e5..be146562 100644 --- a/nixos/modules/services/tailscale-autoconnect.nix +++ b/nixos/modules/services/tailscale-autoconnect.nix @@ -32,6 +32,11 @@ # wait for tailscaled to settle sleep 2 + if [ ! -d "${config.age.secrets.tailscale-token.path}" ]; then + echo "Failed to find token secret at path '${config.age.secrets.tailscale-token.path}'" + exit 2 + fi + # check if we are already authenticated to tailscale status="$(${tailscale}/bin/tailscale status -json | ${jq}/bin/jq -r .BackendState)" if [ $status = "Running" ]; then # if so, then do nothing diff --git a/nixos/server/nix-proxy-droplet/default.nix b/nixos/server/nix-proxy-droplet/default.nix index ce3fd90e..1384d6bf 100644 --- a/nixos/server/nix-proxy-droplet/default.nix +++ b/nixos/server/nix-proxy-droplet/default.nix @@ -2,6 +2,7 @@ let external_domain = "media.carrio.me"; tailnet_domain = "glass.griffin-cobra.ts.net"; + tailnet_dns_resolver = "100.100.100.100"; in { imports = [ @@ -33,7 +34,8 @@ in }; oxc.services.tailscale.enable = true; - oxc.services.tailscale.autoconnect = true; + oxc.services.tailscale.autoconnect = false; + services.smartd.enable = lib.mkForce false; # Plex NixOS Docs: https://nixos.wiki/wiki/Plex services.nginx = { @@ -54,6 +56,8 @@ in #Some players don't reopen a socket and playback stops totally instead of resuming after an extended pause send_timeout 100m; + resolver ${tailnet_dns_resolver} valid=30s; + # Why this is important: https://blog.cloudflare.com/ocsp-stapling-how-cloudflare-just-made-ssl-30/ ssl_stapling on; ssl_stapling_verify on; @@ -112,5 +116,10 @@ in }; }; + networking.firewall = { + allowedTCPPorts = [ 443 ]; + allowedUDPPorts = [ 443 ]; + }; + nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; }