在NixOS上设置Plex

几周前,我家实验室的硬盘(是的,我知道)坏了。 这是一个悲伤的时刻,特别是因为我在上面运行Plex,并且我依赖它来满足我的音乐和有声书需求。

好处是,这给了我重新考虑我的Plex配置的机会。 在家中托管它对于存储成本和控制方面很好,但是很难与朋友共享或在外出时访问,特别是对于具有NATed IPv4的情况,所以我决定转移到云端。

目录

选择服务器和存储方法

我选择了Hetzner Cloud,因为我喜欢他们的服务,并且他们使用清洁能源。

最大的挑战是存储。Hetzner的1TB的卷大约收费50欧元/月(其他提供商的定价相当)。

然而,后来我的朋友Eric告诉我了rclone以及它作为虚拟磁盘挂载Blob存储(价格便宜)的能力。 这意味着Plex将所有文件都视为实际存在,如果它尝试读取一个文件,如果它没有被缓存,它会在需要时下载。

在掌握了这个知识后,我开始设置服务器。

设置NixOS

NixOS是一个声明式和可复现的操作系统。 你有一个配置文件在 /etc/nixos/configuration.nix,定义了你安装的应用程序、配置和系统设置。 如果你搞砸了,你总是可以回滚。

我首先创建了一个在Hetzner上的服务器,使用了任何发行版(我选择了一个 CPX11 和Ubuntu),然后按照 Hetzner Cloud 的安装脚本的指示操作。

如果您沿着这样做,请确保选择至少 40GB 的磁盘空间的服务器。

在引导NixOS之后,我通过运行 passwd 更改了root密码,并升级了NixOS(请参阅NixOS升级)。 如果你想进一步保护你的NixOS安装,Christine Dodrill有一个很好的指南,名为Paranoid NixOS Setup

设置存储

我决定使用Backblaze B2,因为我以前用过它,它比S3便宜,而且我不支持亚马逊。 如果你想使用其他存储服务,rclone支持很多提供商

在为媒体创建存储桶之后,我创建了一个应用密钥,并记录了 keyIDapplicationKey

然后,我在我的Nix配置文件 /etc/nixos/configuration.nix 中添加了以下行,以安装rclone并创建一个用于存储桶的 /etc/rclone/rclone.conf

environment.systemPackages = [ pkgs.rclone ];

environment.etc = {
  "rclone/rclone.conf" = {
    text = ''
      [b2]
      type = b2
      account = <keyID>
      key = <applicationKey>
      hard_delete = true
      versions = false
    '';
    mode = "0644";
  };
};

如果你按照这样做,请确保将 <keyID><applicationKey> 替换为正确的值。

顺便说一句,NixOS预先安装了nano,所以如果你想使用一个真正的编辑器,你可以使用以下命令获取它:

$ nix-shell -p vim

对于磁盘挂载,我创建了一个Systemd服务,它在启动时挂载存储桶,并在启动时自动启动。

systemd.services.plex_media = {
  enable = true;
  description = "挂载媒体目录";
  wantedBy = ["multi-user.target"];
  serviceConfig = {
    ExecStartPre = "/run/current-system/sw/bin/mkdir -p /mnt/media";
    ExecStart = ''
      ${pkgs.rclone}/bin/rclone mount 'b2:<bucket name>/' /mnt/media \
        --config=/etc/rclone/rclone.conf \
        --allow-other \
        --allow-non-empty \
        --log-level=INFO \
        --buffer-size=50M \
        --drive-acknowledge-abuse=true \
        --no-modtime \
        --vfs-cache-mode full \
        --vfs-cache-max-size 20G \
        --vfs-read-chunk-size=32M \
        --vfs-read-chunk-size-limit=256M
    '';
    ExecStop = "/run/wrappers/bin/fusermount -u /mnt/media";
    Type = "notify";
    Restart = "always";
    RestartSec = "10s";
    Environment = ["PATH=${pkgs.fuse}/bin:$PATH"];
  };
};

如果你按照这样做,请确保将 <bucket name> 替换为正确的值。

--vfs-* 参数用于配置虚拟文件系统。 我只有40GB的本地磁盘空间,所以我将缓存大小设置为20GB(使用 --vfs-cache-max-size)。

然后我执行 nixos-rebuild switch 来应用配置,将一些数据上传到存储桶,并列出 /mnt/media,以确保一切正常工作。

配置Plex

NixOS预定义了一个Plex服务,我像这样使用它:

nixpkgs.config.allowUnfree = true; # Plex是非自由的

services.plex = {
  enable = true;
  dataDir = "/var/lib/plex";
  openFirewall = true;
  user = "plex";
  group = "plex";
};

通过这个配置,Nix将在防火墙中打开正确的端口,创建一个名为 plex 的用户,带有一个名为 plex 的组,并在 /var/lib/plex 中安装Plex Media Server。

添加Audiobooks插件

我想使用 Audiobooks.bundle 元数据代理以获得更好的匹配效果,所以我将下面的代码添加到 plex.nix 顶部的 let 部分:

let
  audiobooksPlugin = pkgs.stdenv.mkDerivation {
    name = "Audiobooks.bundle";
    src = pkgs.fetchurl {
      url = https://github.com/macr0dev/Audiobooks.bundle/archive/9b1de6b66cd8fe11c7d27623d8579f43df9f8b86.zip;
      sha256 = "539492e3b06fca2ceb5f0cb6c5e47462d38019317b242f6f74d55c3b2d5f6e1d";
    };
    buildInputs = [ pkgs.unzip ];
    installPhase = "mkdir -p $out; cp -R * $out/";
  };
in
  # ...

这段代码将获取 9b1de6b commit 的 audiobooks 插件,并确保 SHA256 正确。

然后,我告诉Plex使用这个插件:

services.plex.managePlugins = true;
services.plex.extraPlugins = [audiobooksPlugin];

如果你看到错误提示,说 services.plex.managePlugins 不再起效果了,请删除那行。

在这一点上,再次运行 nixos-rebuild switch 之后,我就能够在 https://<域名或IP>:32400 访问Plex界面了。

Plex需要进行初始配置,但只允许本地连接。 一种方法是使用SSH隧道,可以像这样打开它:

$ ssh -L 32400:localhost:32400 user@domain-or-ip

然后在本地浏览器中打开 http://localhost:32400/web 并设置Plex。

配置Nginx

我想要一个漂亮的域名,并且使用443端口上的HTTPS(而不是32400端口上的HTTP),所以我接下来设置了NginxLet's Encrypt

首先,我在Plex配置中将 openFirewall 设置为false。 然后,我允许80端口和443端口的HTTP和HTTPS,以及除了32400端口之外的所有Plex端口,因为我们希望通过Nginx来代理Web界面。

services.plex = {
  openFirewall = false;
  # ...
};

networking.firewall = {
  allowedTCPPorts = [ 3005 8324 32469 80 443 ];
  allowedUDPPorts = [ 1900 5353 32410 32412 32413 32414 ];
};

然后,我配置了ACME:

security.acme.acceptTerms = true;
security.acme.defaults.email = "<你的电子邮件>";

默认的提供商是Let's Encrypt,您可以在此处找到他们的服务条款: Policy and Legal Repository

现在,是时候添加Nginx服务了。 我使用推荐的设置,只使用支持PFS的AES256密码。 由于这是用于代理Plex请求,因此我还转发了一些标头。 以下是代码:

services.nginx = {
  enable = true;

  # 推荐的设置
  recommendedGzipSettings = true;
  recommendedOptimisation = true;
  recommendedProxySettings = true;
  recommendedTlsSettings = true;

  # 仅允许使用支持PFS的AES256密码
  sslCiphers = "AES256+EECDH:AES256+EDH:!aNULL";

  virtualHosts = {
    "<你的域名>" = {
      forceSSL = true;
      enableACME = true;
      extraConfig = ''
        # 某些播放器不会重新打开套接字,播放暂停时间较长后并不会继续播放,而是完全停止
        send_timeout 100m;
        # Plex标头
        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;
        # 缓冲关闭,当数据从 Plex 接收时,立即将其发送到客户端
        proxy_redirect off;
        proxy_buffering off;
      '';
      locations."/" = {
        proxyPass = "http://localhost:32400";
        proxyWebsockets = true;
      };
    };
  };
};

如果你在按照这个进行操作,请确保将 <你的域名> 替换为正确的值。

为了进一步增强安全性,我为每个请求设置了一些头部:

services.nginx.commonHttpConfig = ''
  # 添加带有预加载的HSTS头部到HTTPS请求。
  # 不鼓励在HTTP请求中添加此头部
  map $scheme $hsts_header {
      https   "max-age=31536000; includeSubdomains; preload";
  }
  add_header Strict-Transport-Security $hsts_header;
  # 为您的服务启用CSP。
  #add_header Content-Security-Policy "script-src 'self'; object-src 'none'; base-uri 'none';" always;
  # 最小化泄露给其他域名的信息
  add_header 'Referrer-Policy' 'origin-when-cross-origin';
  # 禁止作为框架的嵌入
  add_header X-Frame-Options DENY;
  # 防止在其他MIME类型中注入代码(XSS攻击)
  add_header X-Content-Type-Options nosniff;
  # 启用浏览器的XSS保护。
  # 当CSP配置得当时可能是不必要的(见上文)
  add_header X-XSS-Protection "1; mode=block";
'';

最后,我再次运行了 nixos-rebuild switch 来应用配置。 然后我在浏览器中打开 https://my-domain 并开始创建Plex库。

成本如何?

CPX11的成本为每月4.75欧元(启用备份),B2的成本为每月0.005美元/GB的存储费用 + 每GB下载的0.01美元。 存储费用严重依赖于存储的媒体数量和下载的媒体数量。 我的设置每月支付大约10欧元。

总结

现在,只剩下进一步配置NixOS来设置主机名、时区、安装 htop 等软件包,并启用自动升级

如果对你有用,这里是我测试这篇博客文章时的configuration.nix

如果你发现问题或有问题,请随时让我知道,我非常乐意帮助!