11 cfg = config.services.rutorrent;
13 rtorrentPluginDependencies = with pkgs; {
20 mediainfo = [ mediainfo ];
21 spectrogram = [ sox ];
22 screenshots = [ ffmpeg ];
25 phpPluginDependencies = with pkgs; {
26 _cloudflare = [ python3 ];
29 getPluginDependencies = dependencies: concatMap (p: attrByPath [ p ] [ ] dependencies);
34 services.rutorrent = {
35 enable = mkEnableOption "ruTorrent";
39 description = "FQDN for the ruTorrent instance.";
44 default = "/var/lib/rutorrent";
45 description = "Storage path of ruTorrent.";
50 default = "rutorrent";
52 User which runs the ruTorrent service.
58 default = "rutorrent";
60 Group which runs the ruTorrent service.
64 rpcSocket = mkOption {
66 default = config.services.rtorrent.rpcSocket;
67 defaultText = "config.services.rtorrent.rpcSocket";
69 Path to rtorrent rpc socket.
74 type = with types; listOf (either str package);
75 default = [ "httprpc" ];
76 example = literalExpression ''[ "httprpc" "data" "diskspace" "edit" "erasedata" "theme" "trafic" ]'';
78 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.
79 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.
83 poolSettings = mkOption {
93 "pm.max_children" = 32;
94 "pm.start_servers" = 2;
95 "pm.min_spare_servers" = 2;
96 "pm.max_spare_servers" = 4;
97 "pm.max_requests" = 500;
100 Options for ruTorrent's PHP pool. See the documentation on <literal>php-fpm.conf</literal> for details on configuration directives.
109 Whether to enable nginx virtual host management.
110 Further nginx configuration can be done by adapting <literal>services.nginx.virtualHosts.<name></literal>.
111 See <xref linkend="opt-services.nginx.virtualHosts"/> for further information.
115 exposeInsecureRPC2mount = mkOption {
119 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.
120 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.
127 config = mkIf cfg.enable (mkMerge [
131 usedRpcPlugins = intersectLists cfg.plugins [
138 assertion = (length usedRpcPlugins < 2);
139 message = "Please specify only one of httprpc or rpc plugins";
142 assertion = !(length usedRpcPlugins > 0 && cfg.nginx.exposeInsecureRPC2mount);
143 message = "Please do not use exposeInsecureRPC2mount if you use one of httprpc or rpc plugins";
149 nginxVhostCfg = config.services.nginx.virtualHosts."${cfg.hostName}";
154 cfg.nginx.exposeInsecureRPC2mount
155 && (nginxVhostCfg.basicAuth == { } || nginxVhostCfg.basicAuthFile == null)
158 You are using exposeInsecureRPC2mount without using basic auth on the virtual host. The exposed rpc mount allow for remote command execution.
160 Please make sure it is not accessible from the outside.
166 rtorrent.path = getPluginDependencies rtorrentPluginDependencies cfg.plugins;
169 rutorrentConfig = pkgs.writeText "rutorrent-config.php" ''
171 // configuration parameters
174 @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);
175 @define('HTTP_TIME_OUT', 30, true); // in seconds
176 @define('HTTP_USE_GZIP', true, true);
177 $httpIP = null; // IP string. Or null for any.
181 'proto' => 'http', // 'http' or 'https'
182 'host' => 'PROXY_HOST_HERE',
186 @define('RPC_TIME_OUT', 5, true); // in seconds
188 @define('LOG_RPC_CALLS', false, true);
189 @define('LOG_RPC_FAULTS', true, true);
192 @define('PHP_USE_GZIP', false, true);
193 @define('PHP_GZIP_LEVEL', 2, true);
195 $schedule_rand = 10; // rand for schedulers start, +0..X seconds
197 $do_diagnostic = true;
198 $log_file = '${cfg.dataDir}/logs/errors.log'; // path to log file (comment or leave blank to disable logging)
200 $saveUploadedTorrents = true; // Save uploaded torrents to profile/torrents directory or not
201 $overwriteUploadedTorrents = false; // Overwrite existing uploaded torrents in profile/torrents directory or make unique name
203 $topDirectory = '/'; // Upper available directory. Absolute path with trail slash.
204 $forbidUserSettings = false;
207 $scgi_host = "unix://${cfg.rpcSocket}";
209 $XMLRPCMountPoint = "/RPC2"; // DO NOT DELETE THIS LINE!!! DO NOT COMMENT THIS LINE!!!
211 $throttleMaxSpeed = 327625*1024;
213 $pathToExternals = array(
214 "php" => "${pkgs.php}/bin/php", // Something like /usr/bin/php. If empty, will be found in PATH.
215 "curl" => "${pkgs.curl}/bin/curl", // Something like /usr/bin/curl. If empty, will be found in PATH.
216 "gzip" => "${pkgs.gzip}/bin/gzip", // Something like /usr/bin/gzip. If empty, will be found in PATH.
217 "id" => "${pkgs.coreutils}/bin/id", // Something like /usr/bin/id. If empty, will be found in PATH.
218 "stat" => "${pkgs.coreutils}/bin/stat", // Something like /usr/bin/stat. If empty, will be found in PATH.
219 "pgrep" => "${pkgs.procps}/bin/pgrep", // TODO why can't we use phpEnv.PATH
222 $localhosts = array( // list of local interfaces
227 $profilePath = '${cfg.dataDir}/share'; // Path to user profiles
228 $profileMask = 0770; // Mask for files and directory creation in user profiles.
229 // Both Webserver and rtorrent users must have read-write access to it.
230 // For example, if Webserver and rtorrent users are in the same group then the value may be 0770.
232 $tempDirectory = null; // Temp directory. Absolute path with trail slash. If null, then autodetect will be used.
234 $canUseXSendFile = false; // If true then use X-Sendfile feature if it exist
240 wantedBy = [ "multi-user.target" ];
241 before = [ "phpfpm-rutorrent.service" ];
243 ln -sf ${pkgs.rutorrent}/{css,images,js,lang,index.html} ${cfg.dataDir}/
244 mkdir -p ${cfg.dataDir}/{conf,logs,plugins} ${cfg.dataDir}/share/{settings,torrents,users}
245 ln -sf ${pkgs.rutorrent}/conf/{access.ini,plugins.ini} ${cfg.dataDir}/conf/
246 ln -sf ${rutorrentConfig} ${cfg.dataDir}/conf/config.php
248 cp -r ${pkgs.rutorrent}/php ${cfg.dataDir}/
250 ${optionalString (cfg.plugins != [ ])
252 concatMapStringsSep " " (p: "${pkgs.rutorrent}/plugins/${p}") cfg.plugins
253 } ${cfg.dataDir}/plugins/''
256 chown -R ${cfg.user}:${cfg.group} ${cfg.dataDir}/{conf,share,logs,plugins}
257 chmod -R 755 ${cfg.dataDir}/{conf,share,logs,plugins}
259 serviceConfig.Type = "oneshot";
263 tmpfiles.rules = [ "d '${cfg.dataDir}' 0775 ${cfg.user} ${cfg.group} -" ];
266 users.groups."${cfg.group}" = { };
272 extraGroups = [ config.services.rtorrent.group ];
273 description = "ruTorrent Daemon user";
277 "${config.services.rtorrent.user}" = {
278 extraGroups = [ cfg.group ];
283 (mkIf cfg.nginx.enable (mkMerge [
286 phpfpm.pools.rutorrent =
288 envPath = lib.makeBinPath (getPluginDependencies phpPluginDependencies cfg.plugins);
291 group = config.services.rtorrent.group;
293 mapAttrs (name: mkDefault) {
294 "listen.owner" = config.services.nginx.user;
295 "listen.group" = config.services.nginx.group;
300 if (envPath == "") then pool else pool // { phpEnv.PATH = envPath; };
308 "~ [^/]\.php(/|$)" = {
310 fastcgi_split_path_info ^(.+?\.php)(/.*)$;
311 if (!-f $document_root$fastcgi_script_name) {
315 # Mitigate https://httpoxy.org/ vulnerabilities
316 fastcgi_param HTTP_PROXY "";
318 fastcgi_pass unix:${config.services.phpfpm.pools.rutorrent.socket};
319 fastcgi_index index.php;
321 include ${pkgs.nginx}/conf/fastcgi.conf;
331 (mkIf cfg.nginx.exposeInsecureRPC2mount {
332 services.nginx.virtualHosts."${cfg.hostName}".locations."/RPC2" = {
334 include ${pkgs.nginx}/conf/scgi_params;
335 scgi_pass unix:${cfg.rpcSocket};
339 services.rtorrent.group = "nginx";