1{
2 config,
3 pkgs,
4 lib,
5 ...
6}:
7let
8 cfg = config.services.plex;
9in
10{
11 imports = [
12 (lib.mkRemovedOptionModule [
13 "services"
14 "plex"
15 "managePlugins"
16 ] "Please omit or define the option: `services.plex.extraPlugins' instead.")
17 ];
18
19 options = {
20 services.plex = {
21 enable = lib.mkEnableOption "Plex Media Server";
22
23 dataDir = lib.mkOption {
24 type = lib.types.str;
25 default = "/var/lib/plex";
26 description = ''
27 The directory where Plex stores its data files.
28 '';
29 };
30
31 openFirewall = lib.mkOption {
32 type = lib.types.bool;
33 default = false;
34 description = ''
35 Open ports in the firewall for the media server.
36 '';
37 };
38
39 user = lib.mkOption {
40 type = lib.types.str;
41 default = "plex";
42 description = ''
43 User account under which Plex runs.
44 '';
45 };
46
47 group = lib.mkOption {
48 type = lib.types.str;
49 default = "plex";
50 description = ''
51 Group under which Plex runs.
52 '';
53 };
54
55 extraPlugins = lib.mkOption {
56 type = lib.types.listOf lib.types.path;
57 default = [ ];
58 description = ''
59 A list of paths to extra plugin bundles to install in Plex's plugin
60 directory. Every time the systemd unit for Plex starts up, all of the
61 symlinks in Plex's plugin directory will be cleared and this module
62 will symlink all of the paths specified here to that directory.
63 '';
64 example = lib.literalExpression ''
65 [
66 (builtins.path {
67 name = "Audnexus.bundle";
68 path = pkgs.fetchFromGitHub {
69 owner = "djdembeck";
70 repo = "Audnexus.bundle";
71 rev = "v0.2.8";
72 sha256 = "sha256-IWOSz3vYL7zhdHan468xNc6C/eQ2C2BukQlaJNLXh7E=";
73 };
74 })
75 ]
76 '';
77 };
78
79 extraScanners = lib.mkOption {
80 type = lib.types.listOf lib.types.path;
81 default = [ ];
82 description = ''
83 A list of paths to extra scanners to install in Plex's scanners
84 directory.
85
86 Every time the systemd unit for Plex starts up, all of the symlinks
87 in Plex's scanners directory will be cleared and this module will
88 symlink all of the paths specified here to that directory.
89 '';
90 example = lib.literalExpression ''
91 [
92 (fetchFromGitHub {
93 owner = "ZeroQI";
94 repo = "Absolute-Series-Scanner";
95 rev = "773a39f502a1204b0b0255903cee4ed02c46fde0";
96 sha256 = "4l+vpiDdC8L/EeJowUgYyB3JPNTZ1sauN8liFAcK+PY=";
97 })
98 ]
99 '';
100 };
101
102 accelerationDevices = lib.mkOption {
103 type = lib.types.listOf lib.types.str;
104 default = [ "*" ];
105 example = [ "/dev/dri/renderD128" ];
106 description = ''
107 A list of device paths to hardware acceleration devices that Plex should
108 have access to. This is useful when transcoding media files.
109 The special value `"*"` will allow all devices.
110 '';
111 };
112
113 package = lib.mkPackageOption pkgs "plex" {
114 extraDescription = ''
115 Plex subscribers may wish to use their own package here,
116 pointing to subscriber-only server versions.
117 '';
118 };
119 };
120 };
121
122 config = lib.mkIf cfg.enable {
123 # Most of this is just copied from the RPM package's systemd service file.
124 systemd.services.plex = {
125 description = "Plex Media Server";
126 after = [ "network.target" ];
127 wantedBy = [ "multi-user.target" ];
128
129 serviceConfig = {
130 Type = "simple";
131 User = cfg.user;
132 Group = cfg.group;
133
134 # Run the pre-start script with full permissions (the "!" prefix) so it
135 # can create the data directory if necessary.
136 ExecStartPre =
137 let
138 preStartScript = pkgs.writeScript "plex-run-prestart" ''
139 #!${pkgs.bash}/bin/bash
140
141 # Create data directory if it doesn't exist
142 if ! test -d "$PLEX_DATADIR"; then
143 echo "Creating initial Plex data directory in: $PLEX_DATADIR"
144 install -d -m 0755 -o "${cfg.user}" -g "${cfg.group}" "$PLEX_DATADIR"
145 fi
146 '';
147 in
148 "!${preStartScript}";
149
150 ExecStart = "${cfg.package}/bin/plexmediaserver";
151 KillSignal = "SIGQUIT";
152 PIDFile = "${cfg.dataDir}/Plex Media Server/plexmediaserver.pid";
153 Restart = "on-failure";
154
155 # Hardening
156 NoNewPrivileges = true;
157 PrivateTmp = true;
158 PrivateDevices = cfg.accelerationDevices == [ ];
159 DeviceAllow = lib.mkIf (
160 cfg.accelerationDevices != [ ] && !lib.elem "*" cfg.accelerationDevices
161 ) cfg.accelerationDevices;
162 ProtectSystem = true;
163 ProtectHome = true;
164 ProtectControlGroups = true;
165 ProtectKernelModules = true;
166 ProtectKernelTunables = true;
167 RestrictAddressFamilies = [
168 "AF_UNIX"
169 "AF_INET"
170 "AF_INET6"
171 "AF_NETLINK"
172 ];
173 # This could be made to work if the namespaces needed were known
174 # RestrictNamespaces = true;
175 RestrictRealtime = true;
176 RestrictSUIDSGID = true;
177 MemoryDenyWriteExecute = true;
178 LockPersonality = true;
179 };
180
181 environment = {
182 # Configuration for our FHS userenv script
183 PLEX_DATADIR = cfg.dataDir;
184 PLEX_PLUGINS = lib.concatMapStringsSep ":" builtins.toString cfg.extraPlugins;
185 PLEX_SCANNERS = lib.concatMapStringsSep ":" builtins.toString cfg.extraScanners;
186
187 # The following variables should be set by the FHS userenv script:
188 # PLEX_MEDIA_SERVER_APPLICATION_SUPPORT_DIR
189 # PLEX_MEDIA_SERVER_HOME
190
191 # Allow access to GPU acceleration; the Plex LD_LIBRARY_PATH is added
192 # by the FHS userenv script.
193 LD_LIBRARY_PATH = "/run/opengl-driver/lib";
194
195 PLEX_MEDIA_SERVER_MAX_PLUGIN_PROCS = "6";
196 PLEX_MEDIA_SERVER_TMPDIR = "/tmp";
197 PLEX_MEDIA_SERVER_USE_SYSLOG = "true";
198 LC_ALL = "en_US.UTF-8";
199 LANG = "en_US.UTF-8";
200 };
201 };
202
203 networking.firewall = lib.mkIf cfg.openFirewall {
204 allowedTCPPorts = [
205 32400
206 3005
207 8324
208 32469
209 ];
210 allowedUDPPorts = [
211 1900
212 5353
213 32410
214 32412
215 32413
216 32414
217 ];
218 };
219
220 users.users = lib.mkIf (cfg.user == "plex") {
221 plex = {
222 group = cfg.group;
223 uid = config.ids.uids.plex;
224 };
225 };
226
227 users.groups = lib.mkIf (cfg.group == "plex") {
228 plex = {
229 gid = config.ids.gids.plex;
230 };
231 };
232 };
233}