grafana-alloy: don't build the frontend twice
[NixPkgs.git] / nixos / modules / services / web-apps / rutorrent.nix
blobcce701fef49781048a358e289c2e7016b98f474f
1 { config, lib, pkgs, ... }:
3 with lib;
5 let
6   cfg = config.services.rutorrent;
8   rtorrentPluginDependencies = with pkgs; {
9     _task = [ procps ];
10     unpack = [ unzip unrar ];
11     rss = [ curl ];
12     mediainfo = [ mediainfo ];
13     spectrogram = [ sox ];
14     screenshots = [ ffmpeg ];
15   };
17   phpPluginDependencies = with pkgs; {
18     _cloudflare = [ python3 ];
19   };
21   getPluginDependencies = dependencies: concatMap (p: attrByPath [ p ] [] dependencies);
23 in {
24   options = {
25     services.rutorrent = {
26       enable = mkEnableOption "ruTorrent";
28       hostName = mkOption {
29         type = types.str;
30         description = "FQDN for the ruTorrent instance.";
31       };
33       dataDir = mkOption {
34         type = types.str;
35         default = "/var/lib/rutorrent";
36         description = "Storage path of ruTorrent.";
37       };
39       user = mkOption {
40         type = types.str;
41         default = "rutorrent";
42         description = ''
43           User which runs the ruTorrent service.
44         '';
45       };
47       group = mkOption {
48         type = types.str;
49         default = "rutorrent";
50         description = ''
51           Group which runs the ruTorrent service.
52         '';
53       };
55       rpcSocket = mkOption {
56         type = types.str;
57         default = config.services.rtorrent.rpcSocket;
58         defaultText = "config.services.rtorrent.rpcSocket";
59         description = ''
60           Path to rtorrent rpc socket.
61         '';
62       };
64       plugins = mkOption {
65         type = with types; listOf (either str package);
66         default = [ "httprpc" ];
67         example = literalExpression ''[ "httprpc" "data" "diskspace" "edit" "erasedata" "theme" "trafic" ]'';
68         description = ''
69           List of plugins to enable. See the list of <link xlink:href="https://github.com/Novik/ruTorrent/wiki/Plugins#currently-there-are-the-following-plugins">available plugins</link>. Note: the <literal>unpack</literal> plugin needs the nonfree <literal>unrar</literal> package.
70           You need to either enable one of the <literal>rpc</literal> or <literal>httprpc</literal> plugin or enable the <xref linkend="opt-services.rutorrent.nginx.exposeInsecureRPC2mount"/> option.
71         '';
72       };
74       poolSettings = mkOption {
75         type = with types; attrsOf (oneOf [ str int bool ]);
76         default = {
77           "pm" = "dynamic";
78           "pm.max_children" = 32;
79           "pm.start_servers" = 2;
80           "pm.min_spare_servers" = 2;
81           "pm.max_spare_servers" = 4;
82           "pm.max_requests" = 500;
83         };
84         description = ''
85           Options for ruTorrent's PHP pool. See the documentation on <literal>php-fpm.conf</literal> for details on configuration directives.
86         '';
87       };
89       nginx = {
90         enable = mkOption {
91           type = types.bool;
92           default = false;
93           description = ''
94             Whether to enable nginx virtual host management.
95             Further nginx configuration can be done by adapting <literal>services.nginx.virtualHosts.&lt;name&gt;</literal>.
96             See <xref linkend="opt-services.nginx.virtualHosts"/> for further information.
97           '';
98         };
100         exposeInsecureRPC2mount = mkOption {
101           type = types.bool;
102           default = false;
103           description = ''
104             If you do not enable one of the <literal>rpc</literal> or <literal>httprpc</literal> plugins you need to expose an RPC mount through scgi using this option.
105             Warning: This allow to run arbitrary commands, as the rtorrent user, so make sure to use authentification. The simplest way would be to use the <literal>services.nginx.virtualHosts.&lt;name&gt;.basicAuth</literal> option.
106           '';
107         };
108       };
109     };
110   };
112   config = mkIf cfg.enable (mkMerge [
113     { assertions = let
114         usedRpcPlugins = intersectLists cfg.plugins [ "httprpc" "rpc" ];
115       in [
116         { assertion = (length usedRpcPlugins < 2);
117           message = "Please specify only one of httprpc or rpc plugins";
118         }
119         { assertion = !(length usedRpcPlugins > 0 && cfg.nginx.exposeInsecureRPC2mount);
120           message = "Please do not use exposeInsecureRPC2mount if you use one of httprpc or rpc plugins";
121         }
122       ];
124       warnings = let
125         nginxVhostCfg = config.services.nginx.virtualHosts."${cfg.hostName}";
126       in []
127         ++ (optional (cfg.nginx.exposeInsecureRPC2mount && (nginxVhostCfg.basicAuth == {} || nginxVhostCfg.basicAuthFile == null )) ''
128           You are using exposeInsecureRPC2mount without using basic auth on the virtual host. The exposed rpc mount allow for remote command execution.
130           Please make sure it is not accessible from the outside.
131         '');
133       systemd = {
134         services = {
135           rtorrent.path = getPluginDependencies rtorrentPluginDependencies cfg.plugins;
136           rutorrent-setup = let
137             rutorrentConfig = pkgs.writeText "rutorrent-config.php" ''
138               <?php
139                 // configuration parameters
141                 // for snoopy client
142                 @define('HTTP_USER_AGENT', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.87 Safari/537.36', true);
143                 @define('HTTP_TIME_OUT', 30, true);     // in seconds
144                 @define('HTTP_USE_GZIP', true, true);
145                 $httpIP = null;                         // IP string. Or null for any.
146                 $httpProxy = array
147                 (
148                   'use'         => false,
149                   'proto'       => 'http',              // 'http' or 'https'
150                   'host'        => 'PROXY_HOST_HERE',
151                   'port'        => 3128
152                 );
154                 @define('RPC_TIME_OUT', 5, true);       // in seconds
156                 @define('LOG_RPC_CALLS', false, true);
157                 @define('LOG_RPC_FAULTS', true, true);
159                 // for php
160                 @define('PHP_USE_GZIP', false, true);
161                 @define('PHP_GZIP_LEVEL', 2, true);
163                 $schedule_rand = 10;                    // rand for schedulers start, +0..X seconds
165                 $do_diagnostic = true;
166                 $log_file = '${cfg.dataDir}/logs/errors.log';           // path to log file (comment or leave blank to disable logging)
168                 $saveUploadedTorrents = true;           // Save uploaded torrents to profile/torrents directory or not
169                 $overwriteUploadedTorrents = false;     // Overwrite existing uploaded torrents in profile/torrents directory or make unique name
171                 $topDirectory = '/';                    // Upper available directory. Absolute path with trail slash.
172                 $forbidUserSettings = false;
174                 $scgi_port = 0;
175                 $scgi_host = "unix://${cfg.rpcSocket}";
177                 $XMLRPCMountPoint = "/RPC2";            // DO NOT DELETE THIS LINE!!! DO NOT COMMENT THIS LINE!!!
179                 $throttleMaxSpeed = 327625*1024;
181                 $pathToExternals = array(
182                   "php"         => "${pkgs.php}/bin/php",                       // Something like /usr/bin/php. If empty, will be found in PATH.
183                   "curl"        => "${pkgs.curl}/bin/curl",                     // Something like /usr/bin/curl. If empty, will be found in PATH.
184                   "gzip"        => "${pkgs.gzip}/bin/gzip",                     // Something like /usr/bin/gzip. If empty, will be found in PATH.
185                   "id"  => "${pkgs.coreutils}/bin/id",                  // Something like /usr/bin/id. If empty, will be found in PATH.
186                   "stat"        => "${pkgs.coreutils}/bin/stat",                        // Something like /usr/bin/stat. If empty, will be found in PATH.
187                   "pgrep" => "${pkgs.procps}/bin/pgrep",  // TODO why can't we use phpEnv.PATH
188                 );
190                 $localhosts = array(                    // list of local interfaces
191                   "127.0.0.1",
192                   "localhost",
193                 );
195                 $profilePath = '${cfg.dataDir}/share';          // Path to user profiles
196                 $profileMask = 0770;                    // Mask for files and directory creation in user profiles.
197                 // Both Webserver and rtorrent users must have read-write access to it.
198                 // For example, if Webserver and rtorrent users are in the same group then the value may be 0770.
200                 $tempDirectory = null;                  // Temp directory. Absolute path with trail slash. If null, then autodetect will be used.
202                 $canUseXSendFile = false;               // If true then use X-Sendfile feature if it exist
204                 $locale = "UTF8";
205             '';
206           in {
207             wantedBy = [ "multi-user.target" ];
208             before = [ "phpfpm-rutorrent.service" ];
209             script = ''
210               ln -sf ${pkgs.rutorrent}/{css,images,js,lang,index.html} ${cfg.dataDir}/
211               mkdir -p ${cfg.dataDir}/{conf,logs,plugins} ${cfg.dataDir}/share/{settings,torrents,users}
212               ln -sf ${pkgs.rutorrent}/conf/{access.ini,plugins.ini} ${cfg.dataDir}/conf/
213               ln -sf ${rutorrentConfig} ${cfg.dataDir}/conf/config.php
215               cp -r ${pkgs.rutorrent}/php ${cfg.dataDir}/
217               ${optionalString (cfg.plugins != [])
218                 ''cp -r ${concatMapStringsSep " " (p: "${pkgs.rutorrent}/plugins/${p}") cfg.plugins} ${cfg.dataDir}/plugins/''}
220               chown -R ${cfg.user}:${cfg.group} ${cfg.dataDir}/{conf,share,logs,plugins}
221               chmod -R 755 ${cfg.dataDir}/{conf,share,logs,plugins}
222             '';
223             serviceConfig.Type = "oneshot";
224           };
225         };
227         tmpfiles.rules = [ "d '${cfg.dataDir}' 0775 ${cfg.user} ${cfg.group} -" ];
228       };
230       users.groups."${cfg.group}" = {};
232       users.users = {
233         "${cfg.user}" = {
234           home = cfg.dataDir;
235           group = cfg.group;
236           extraGroups = [ config.services.rtorrent.group ];
237           description = "ruTorrent Daemon user";
238           isSystemUser = true;
239         };
241         "${config.services.rtorrent.user}" = {
242           extraGroups = [ cfg.group ];
243         };
244       };
245     }
247     (mkIf cfg.nginx.enable (mkMerge [
248       { services = {
249         phpfpm.pools.rutorrent = let
250           envPath = lib.makeBinPath (getPluginDependencies phpPluginDependencies cfg.plugins);
251           pool = {
252             user = cfg.user;
253             group = config.services.rtorrent.group;
254             settings = mapAttrs (name: mkDefault) {
255               "listen.owner" = config.services.nginx.user;
256               "listen.group" = config.services.nginx.group;
257             } // cfg.poolSettings;
258           };
259         in if (envPath == "") then pool else pool // { phpEnv.PATH = envPath; };
261           nginx = {
262             enable = true;
263             virtualHosts = {
264               ${cfg.hostName} = {
265                 root = cfg.dataDir;
266                 locations = {
267                   "~ [^/]\.php(/|$)" = {
268                     extraConfig = ''
269                       fastcgi_split_path_info ^(.+?\.php)(/.*)$;
270                       if (!-f $document_root$fastcgi_script_name) {
271                         return 404;
272                       }
274                       # Mitigate https://httpoxy.org/ vulnerabilities
275                       fastcgi_param HTTP_PROXY "";
277                       fastcgi_pass unix:${config.services.phpfpm.pools.rutorrent.socket};
278                       fastcgi_index index.php;
280                       include ${pkgs.nginx}/conf/fastcgi.conf;
281                     '';
282                   };
283                 };
284               };
285             };
286           };
287         };
288       }
290       (mkIf cfg.nginx.exposeInsecureRPC2mount {
291         services.nginx.virtualHosts."${cfg.hostName}".locations."/RPC2" = {
292           extraConfig = ''
293             include ${pkgs.nginx}/conf/scgi_params;
294             scgi_pass unix:${cfg.rpcSocket};
295           '';
296         };
298         services.rtorrent.group = "nginx";
299       })
300     ]))
301   ]);