Skip to content

Commit

Permalink
feat: support for nginx-hosted tailnet-proxied plex
Browse files Browse the repository at this point in the history
  • Loading branch information
tcarrio committed Nov 25, 2024
1 parent 8ccba48 commit a4345b8
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 148 deletions.
239 changes: 96 additions & 143 deletions nixos/mixins/services/plex.nix
Original file line number Diff line number Diff line change
@@ -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 = [
Expand All @@ -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";
Expand All @@ -33,13 +113,11 @@ in
acceptTerms = true;
defaults.email = "[email protected]";
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;
};
};
};
Expand Down Expand Up @@ -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 ];
};
}
8 changes: 4 additions & 4 deletions nixos/modules/services/remote-builder.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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
'';
};
}
5 changes: 5 additions & 0 deletions nixos/modules/services/tailscale-autoconnect.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 10 additions & 1 deletion nixos/server/nix-proxy-droplet/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down Expand Up @@ -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 = {
Expand All @@ -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;
Expand Down Expand Up @@ -112,5 +116,10 @@ in
};
};

networking.firewall = {
allowedTCPPorts = [ 443 ];
allowedUDPPorts = [ 443 ];
};

nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
}

0 comments on commit a4345b8

Please sign in to comment.