at 25.11-pre 12 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7 8with lib; 9 10let 11 cfg = config.services.rutorrent; 12 13 rtorrentPluginDependencies = with pkgs; { 14 _task = [ procps ]; 15 unpack = [ 16 unzip 17 unrar 18 ]; 19 rss = [ curl ]; 20 mediainfo = [ mediainfo ]; 21 spectrogram = [ sox ]; 22 screenshots = [ ffmpeg ]; 23 }; 24 25 phpPluginDependencies = with pkgs; { 26 _cloudflare = [ python3 ]; 27 }; 28 29 getPluginDependencies = dependencies: concatMap (p: attrByPath [ p ] [ ] dependencies); 30 31in 32{ 33 options = { 34 services.rutorrent = { 35 enable = mkEnableOption "ruTorrent"; 36 37 hostName = mkOption { 38 type = types.str; 39 description = "FQDN for the ruTorrent instance."; 40 }; 41 42 dataDir = mkOption { 43 type = types.str; 44 default = "/var/lib/rutorrent"; 45 description = "Storage path of ruTorrent."; 46 }; 47 48 user = mkOption { 49 type = types.str; 50 default = "rutorrent"; 51 description = '' 52 User which runs the ruTorrent service. 53 ''; 54 }; 55 56 group = mkOption { 57 type = types.str; 58 default = "rutorrent"; 59 description = '' 60 Group which runs the ruTorrent service. 61 ''; 62 }; 63 64 rpcSocket = mkOption { 65 type = types.str; 66 default = config.services.rtorrent.rpcSocket; 67 defaultText = "config.services.rtorrent.rpcSocket"; 68 description = '' 69 Path to rtorrent rpc socket. 70 ''; 71 }; 72 73 plugins = mkOption { 74 type = with types; listOf (either str package); 75 default = [ "httprpc" ]; 76 example = literalExpression ''[ "httprpc" "data" "diskspace" "edit" "erasedata" "theme" "trafic" ]''; 77 description = '' 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. 80 ''; 81 }; 82 83 poolSettings = mkOption { 84 type = 85 with types; 86 attrsOf (oneOf [ 87 str 88 int 89 bool 90 ]); 91 default = { 92 "pm" = "dynamic"; 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; 98 }; 99 description = '' 100 Options for ruTorrent's PHP pool. See the documentation on <literal>php-fpm.conf</literal> for details on configuration directives. 101 ''; 102 }; 103 104 nginx = { 105 enable = mkOption { 106 type = types.bool; 107 default = false; 108 description = '' 109 Whether to enable nginx virtual host management. 110 Further nginx configuration can be done by adapting <literal>services.nginx.virtualHosts.&lt;name&gt;</literal>. 111 See <xref linkend="opt-services.nginx.virtualHosts"/> for further information. 112 ''; 113 }; 114 115 exposeInsecureRPC2mount = mkOption { 116 type = types.bool; 117 default = false; 118 description = '' 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.&lt;name&gt;.basicAuth</literal> option. 121 ''; 122 }; 123 }; 124 }; 125 }; 126 127 config = mkIf cfg.enable (mkMerge [ 128 { 129 assertions = 130 let 131 usedRpcPlugins = intersectLists cfg.plugins [ 132 "httprpc" 133 "rpc" 134 ]; 135 in 136 [ 137 { 138 assertion = (length usedRpcPlugins < 2); 139 message = "Please specify only one of httprpc or rpc plugins"; 140 } 141 { 142 assertion = !(length usedRpcPlugins > 0 && cfg.nginx.exposeInsecureRPC2mount); 143 message = "Please do not use exposeInsecureRPC2mount if you use one of httprpc or rpc plugins"; 144 } 145 ]; 146 147 warnings = 148 let 149 nginxVhostCfg = config.services.nginx.virtualHosts."${cfg.hostName}"; 150 in 151 [ ] 152 ++ (optional 153 ( 154 cfg.nginx.exposeInsecureRPC2mount 155 && (nginxVhostCfg.basicAuth == { } || nginxVhostCfg.basicAuthFile == null) 156 ) 157 '' 158 You are using exposeInsecureRPC2mount without using basic auth on the virtual host. The exposed rpc mount allow for remote command execution. 159 160 Please make sure it is not accessible from the outside. 161 '' 162 ); 163 164 systemd = { 165 services = { 166 rtorrent.path = getPluginDependencies rtorrentPluginDependencies cfg.plugins; 167 rutorrent-setup = 168 let 169 rutorrentConfig = pkgs.writeText "rutorrent-config.php" '' 170 <?php 171 // configuration parameters 172 173 // for snoopy client 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. 178 $httpProxy = array 179 ( 180 'use' => false, 181 'proto' => 'http', // 'http' or 'https' 182 'host' => 'PROXY_HOST_HERE', 183 'port' => 3128 184 ); 185 186 @define('RPC_TIME_OUT', 5, true); // in seconds 187 188 @define('LOG_RPC_CALLS', false, true); 189 @define('LOG_RPC_FAULTS', true, true); 190 191 // for php 192 @define('PHP_USE_GZIP', false, true); 193 @define('PHP_GZIP_LEVEL', 2, true); 194 195 $schedule_rand = 10; // rand for schedulers start, +0..X seconds 196 197 $do_diagnostic = true; 198 $log_file = '${cfg.dataDir}/logs/errors.log'; // path to log file (comment or leave blank to disable logging) 199 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 202 203 $topDirectory = '/'; // Upper available directory. Absolute path with trail slash. 204 $forbidUserSettings = false; 205 206 $scgi_port = 0; 207 $scgi_host = "unix://${cfg.rpcSocket}"; 208 209 $XMLRPCMountPoint = "/RPC2"; // DO NOT DELETE THIS LINE!!! DO NOT COMMENT THIS LINE!!! 210 211 $throttleMaxSpeed = 327625*1024; 212 213 $pathToExternals = array( 214 "php" => "${pkgs.php82}/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 220 ); 221 222 $localhosts = array( // list of local interfaces 223 "127.0.0.1", 224 "localhost", 225 ); 226 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. 231 232 $tempDirectory = null; // Temp directory. Absolute path with trail slash. If null, then autodetect will be used. 233 234 $canUseXSendFile = false; // If true then use X-Sendfile feature if it exist 235 236 $locale = "UTF8"; 237 ''; 238 in 239 { 240 wantedBy = [ "multi-user.target" ]; 241 before = [ "phpfpm-rutorrent.service" ]; 242 script = '' 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 247 248 cp -r ${pkgs.rutorrent}/php ${cfg.dataDir}/ 249 250 ${optionalString (cfg.plugins != [ ]) 251 ''cp -r ${ 252 concatMapStringsSep " " (p: "${pkgs.rutorrent}/plugins/${p}") cfg.plugins 253 } ${cfg.dataDir}/plugins/'' 254 } 255 256 chown -R ${cfg.user}:${cfg.group} ${cfg.dataDir}/{conf,share,logs,plugins} 257 chmod -R 755 ${cfg.dataDir}/{conf,share,logs,plugins} 258 ''; 259 serviceConfig.Type = "oneshot"; 260 }; 261 }; 262 263 tmpfiles.rules = [ "d '${cfg.dataDir}' 0775 ${cfg.user} ${cfg.group} -" ]; 264 }; 265 266 users.groups."${cfg.group}" = { }; 267 268 users.users = { 269 "${cfg.user}" = { 270 home = cfg.dataDir; 271 group = cfg.group; 272 extraGroups = [ config.services.rtorrent.group ]; 273 description = "ruTorrent Daemon user"; 274 isSystemUser = true; 275 }; 276 277 "${config.services.rtorrent.user}" = { 278 extraGroups = [ cfg.group ]; 279 }; 280 }; 281 } 282 283 (mkIf cfg.nginx.enable (mkMerge [ 284 { 285 services = { 286 phpfpm.pools.rutorrent = 287 let 288 envPath = lib.makeBinPath (getPluginDependencies phpPluginDependencies cfg.plugins); 289 pool = { 290 user = cfg.user; 291 group = config.services.rtorrent.group; 292 settings = 293 mapAttrs (name: mkDefault) { 294 "listen.owner" = config.services.nginx.user; 295 "listen.group" = config.services.nginx.group; 296 } 297 // cfg.poolSettings; 298 }; 299 in 300 if (envPath == "") then pool else pool // { phpEnv.PATH = envPath; }; 301 302 nginx = { 303 enable = true; 304 virtualHosts = { 305 ${cfg.hostName} = { 306 root = cfg.dataDir; 307 locations = { 308 "~ [^/]\\.php(/|$)" = { 309 extraConfig = '' 310 fastcgi_split_path_info ^(.+?\.php)(/.*)$; 311 if (!-f $document_root$fastcgi_script_name) { 312 return 404; 313 } 314 315 # Mitigate https://httpoxy.org/ vulnerabilities 316 fastcgi_param HTTP_PROXY ""; 317 318 fastcgi_pass unix:${config.services.phpfpm.pools.rutorrent.socket}; 319 fastcgi_index index.php; 320 321 include ${pkgs.nginx}/conf/fastcgi.conf; 322 ''; 323 }; 324 }; 325 }; 326 }; 327 }; 328 }; 329 } 330 331 (mkIf cfg.nginx.exposeInsecureRPC2mount { 332 services.nginx.virtualHosts."${cfg.hostName}".locations."/RPC2" = { 333 extraConfig = '' 334 include ${pkgs.nginx}/conf/scgi_params; 335 scgi_pass unix:${cfg.rpcSocket}; 336 ''; 337 }; 338 339 services.rtorrent.group = "nginx"; 340 }) 341 ])) 342 ]); 343}