{ config, lib, pkgs, ... }: with lib; let cfg = config.nixfiles.modules.rtorrent; in { options.nixfiles.modules.rtorrent = { enable = mkEnableOption "rTorrent"; flood = { enable = mkEnableOption "Flood" // {default = cfg.enable;}; domain = mkOption { description = "Domain name sans protocol scheme."; type = with types; str; default = "flood.${config.networking.domain}"; }; }; }; config = let user = "rtorrent"; group = "rtorrent"; baseDir = "/var/lib/rtorrent"; rpcSocket = "${baseDir}/rpc.socket"; in mkIf cfg.enable (mkMerge [ (let port = 50000; in { systemd = { services.rtorrent = { description = "rTorrent"; after = ["network.target" "local-fs.target"]; serviceConfig = let leechDir = "${baseDir}/leech"; seedDir = "${baseDir}/seed"; sessionDir = "${baseDir}/session"; logDir = "${baseDir}/log"; configFile = let moveCompleted = let pkg = pkgs.writeShellApplication { name = "move-completed"; runtimeInputs = with pkgs; [ coreutils-full gnused findutils ]; text = '' set -x leech_path="$1" seed_path="$2" # seed_path="$(echo "$2" | sed 's@+@ @g;s@%@\\x@g' | xargs -0 printf '%b')" mkdir -pv "$seed_path" mv -fv "$leech_path" "$seed_path" ''; }; in "${pkg}/bin/move-completed"; in pkgs.writeText "rtorrent.rc" '' method.insert = cfg.leech, private|const|string, (cat, "${leechDir}") method.insert = cfg.seed, private|const|string, (cat, "${seedDir}") method.insert = cfg.session, private|const|string, (cat, "${sessionDir}") method.insert = cfg.log, private|const|string, (cat, "${logDir}") method.insert = cfg.rpcsocket, private|const|string, (cat, "${rpcSocket}") directory.default.set = (cat, (cfg.leech)) session.path.set = (cat, (cfg.session)) network.port_range.set = ${toString port}-${toString port} network.port_random.set = no dht.mode.set = disable protocol.pex.set = no trackers.use_udp.set = no protocol.encryption.set = allow_incoming,try_outgoing,enable_retry pieces.memory.max.set = ${toString (pow 2 11)}M pieces.preload.type.set = 2 network.xmlrpc.size_limit.set = ${toString (pow 2 17)} network.max_open_files.set = ${toString (pow 2 10)} network.max_open_sockets.set = ${toString (pow 2 10)} network.http.max_open.set = ${toString (pow 2 8)} throttle.global_down.max_rate.set_kb = 0 throttle.global_up.max_rate.set_kb = 0 encoding.add = UTF-8 system.umask.set = 0027 system.cwd.set = (directory.default) network.scgi.open_local = (cat, (cfg.rpcsocket)) method.insert = d.move_completed, simple, "\ d.directory.set=$argument.1=;\ execute=${moveCompleted}, $argument.0=, $argument.1=;\ d.save_full_session=\ " method.insert = d.leech_path, simple, "\ if=(d.is_multi_file),\ (cat, (d.directory), /),\ (cat, (d.directory), /, (d.name))\ " method.insert = d.seed_path, simple, "\ cat=$cfg.seed=, /, $d.custom1=\ " method.set_key = event.download.finished, move_complete, "\ d.move_completed=$d.leech_path=, $d.seed_path=\ " log.open_file = "log", (cat, (cfg.log), "/", "default.log") log.add_output = "info", "log" log.execute = (cat, (cfg.log), "/", "execute.log") ''; in { Restart = "on-failure"; RestartSec = 3; KillMode = "process"; KillSignal = "SIGHUP"; User = user; Group = group; ExecStartPre = concatStringsSep " " [ "${pkgs.coreutils-full}/bin/mkdir -p" leechDir seedDir sessionDir logDir ]; ExecStart = concatStringsSep " " [ "${pkgs.rtorrent}/bin/rtorrent" "-n" "-o system.daemon.set=true" "-o network.bind_address.set=0.0.0.0" "-o import=${configFile}" ]; ExecStop = concatStringsSep " " [ "${pkgs.coreutils-full}/bin/rm -rf" rpcSocket ]; RuntimeDirectory = "rtorrent"; RuntimeDirectoryMode = 0750; UMask = 0027; AmbientCapabilities = [""]; CapabilityBoundingSet = [""]; LockPersonality = true; MemoryDenyWriteExecute = true; NoNewPrivileges = true; PrivateDevices = true; PrivateTmp = true; PrivateUsers = true; ProtectClock = true; ProtectControlGroups = true; ProtectHome = true; ProtectHostname = true; ProtectKernelLogs = true; ProtectKernelModules = true; ProtectKernelTunables = true; ProcSubset = "pid"; RemoveIPC = true; RestrictAddressFamilies = ["AF_UNIX" "AF_INET" "AF_INET6"]; RestrictNamespaces = true; RestrictRealtime = true; RestrictSUIDSGID = true; SystemCallArchitectures = "native"; SystemCallFilter = ["@system-service" "~@resources" "~@privileged"]; }; wantedBy = ["multi-user.target"]; }; tmpfiles.rules = ["d '${baseDir}' 0750 ${user} ${group} -"]; }; users = { users.${user} = { inherit group; shell = pkgs.bashInteractive; home = baseDir; description = "rTorrent"; isSystemUser = true; }; groups.${group} = {}; }; my.extraGroups = [group]; networking.firewall.allowedTCPPorts = [port]; boot.kernel.sysctl = { "net.core.rmem_max" = mkOverride 500 (pow 2 24); "net.core.wmem_max" = mkOverride 500 (pow 2 24); "net.ipv4.tcp_fin_timeout" = mkOverride 500 30; "net.ipv4.tcp_rmem" = mkOverride 500 (mkTcpMem 12 23 24); "net.ipv4.tcp_slow_start_after_idle" = 0; "net.ipv4.tcp_tw_recycle" = mkOverride 500 1; "net.ipv4.tcp_tw_reuse" = mkOverride 500 1; "net.ipv4.tcp_wmem" = mkOverride 500 (mkTcpMem 12 23 24); }; }) (let port = 50001; pkg = pkgs.nodePackages.flood; in mkIf cfg.flood.enable { nixfiles.modules.nginx = { enable = true; upstreams.flood.servers."127.0.0.1:${toString port}" = {}; virtualHosts.${cfg.flood.domain} = { root = "${pkg}/lib/node_modules/flood/dist/assets"; locations = { "/".tryFiles = "$uri /index.html"; "/api" = { proxyPass = "http://flood"; extraConfig = '' proxy_buffering off; proxy_cache off; ''; }; }; extraConfig = nginxInternalOnly; }; }; systemd.services.flood = { description = "Flood"; after = ["network.target" "rtorrent.service"]; path = with pkgs; [mediainfo]; serviceConfig = { Restart = "on-failure"; RestartSec = 3; User = user; Group = group; ExecStart = concatStringsSep " " [ "${pkg}/bin/flood" "--allowedpath=${baseDir}" "--baseuri=/" "--rundir=${baseDir}/flood" "--host=127.0.0.1" "--port=${toString port}" "--rtsocket=${rpcSocket}" "--ssl=false" "--auth=none" ]; RuntimeDirectory = "rtorrent"; RuntimeDirectoryMode = 0750; UMask = 0027; AmbientCapabilities = [""]; CapabilityBoundingSet = [""]; LockPersonality = true; NoNewPrivileges = true; PrivateDevices = true; PrivateTmp = true; PrivateUsers = true; ProtectClock = true; ProtectControlGroups = true; ProtectHome = true; ProtectHostname = true; ProtectKernelLogs = true; ProtectKernelModules = true; ProtectKernelTunables = true; ProcSubset = "pid"; ProtectProc = "invisible"; RemoveIPC = true; RestrictAddressFamilies = ["AF_UNIX" "AF_INET" "AF_INET6"]; RestrictNamespaces = true; RestrictRealtime = true; RestrictSUIDSGID = true; SystemCallArchitectures = "native"; SystemCallFilter = [ "~@cpu-emulation" "~@debug" "~@mount" "~@obsolete" "~@privileged" "~@resources" ]; }; wantedBy = ["multi-user.target"]; }; }) ]); }