1 { config, lib, pkgs, ... }:
6 cfg = config.services.rutorrent;
8 rtorrentPluginDependencies = with pkgs; {
10 unpack = [ unzip unrar ];
12 mediainfo = [ mediainfo ];
13 spectrogram = [ sox ];
14 screenshots = [ ffmpeg ];
17 phpPluginDependencies = with pkgs; {
18 _cloudflare = [ python3 ];
21 getPluginDependencies = dependencies: concatMap (p: attrByPath [ p ] [] dependencies);
25 services.rutorrent = {
26 enable = mkEnableOption "ruTorrent";
30 description = "FQDN for the ruTorrent instance.";
35 default = "/var/lib/rutorrent";
36 description = "Storage path of ruTorrent.";
41 default = "rutorrent";
43 User which runs the ruTorrent service.
49 default = "rutorrent";
51 Group which runs the ruTorrent service.
55 rpcSocket = mkOption {
57 default = config.services.rtorrent.rpcSocket;
58 defaultText = "config.services.rtorrent.rpcSocket";
60 Path to rtorrent rpc socket.
65 type = with types; listOf (either str package);
66 default = [ "httprpc" ];
67 example = literalExpression ''[ "httprpc" "data" "diskspace" "edit" "erasedata" "theme" "trafic" ]'';
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.
74 poolSettings = mkOption {
75 type = with types; attrsOf (oneOf [ str int bool ]);
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;
85 Options for ruTorrent's PHP pool. See the documentation on <literal>php-fpm.conf</literal> for details on configuration directives.
94 Whether to enable nginx virtual host management.
95 Further nginx configuration can be done by adapting <literal>services.nginx.virtualHosts.<name></literal>.
96 See <xref linkend="opt-services.nginx.virtualHosts"/> for further information.
100 exposeInsecureRPC2mount = mkOption {
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.<name>.basicAuth</literal> option.
112 config = mkIf cfg.enable (mkMerge [
114 usedRpcPlugins = intersectLists cfg.plugins [ "httprpc" "rpc" ];
116 { assertion = (length usedRpcPlugins < 2);
117 message = "Please specify only one of httprpc or rpc plugins";
119 { assertion = !(length usedRpcPlugins > 0 && cfg.nginx.exposeInsecureRPC2mount);
120 message = "Please do not use exposeInsecureRPC2mount if you use one of httprpc or rpc plugins";
125 nginxVhostCfg = config.services.nginx.virtualHosts."${cfg.hostName}";
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.
135 rtorrent.path = getPluginDependencies rtorrentPluginDependencies cfg.plugins;
136 rutorrent-setup = let
137 rutorrentConfig = pkgs.writeText "rutorrent-config.php" ''
139 // configuration parameters
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.
149 'proto' => 'http', // 'http' or 'https'
150 'host' => 'PROXY_HOST_HERE',
154 @define('RPC_TIME_OUT', 5, true); // in seconds
156 @define('LOG_RPC_CALLS', false, true);
157 @define('LOG_RPC_FAULTS', true, true);
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;
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
190 $localhosts = array( // list of local interfaces
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
207 wantedBy = [ "multi-user.target" ];
208 before = [ "phpfpm-rutorrent.service" ];
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}
223 serviceConfig.Type = "oneshot";
227 tmpfiles.rules = [ "d '${cfg.dataDir}' 0775 ${cfg.user} ${cfg.group} -" ];
230 users.groups."${cfg.group}" = {};
236 extraGroups = [ config.services.rtorrent.group ];
237 description = "ruTorrent Daemon user";
241 "${config.services.rtorrent.user}" = {
242 extraGroups = [ cfg.group ];
247 (mkIf cfg.nginx.enable (mkMerge [
249 phpfpm.pools.rutorrent = let
250 envPath = lib.makeBinPath (getPluginDependencies phpPluginDependencies cfg.plugins);
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;
259 in if (envPath == "") then pool else pool // { phpEnv.PATH = envPath; };
267 "~ [^/]\.php(/|$)" = {
269 fastcgi_split_path_info ^(.+?\.php)(/.*)$;
270 if (!-f $document_root$fastcgi_script_name) {
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;
290 (mkIf cfg.nginx.exposeInsecureRPC2mount {
291 services.nginx.virtualHosts."${cfg.hostName}".locations."/RPC2" = {
293 include ${pkgs.nginx}/conf/scgi_params;
294 scgi_pass unix:${cfg.rpcSocket};
298 services.rtorrent.group = "nginx";