From e6ed60548397627bf10f561f9438201dbba0a36e Mon Sep 17 00:00:00 2001 From: Azat Bahawi Date: Sun, 21 Apr 2024 02:15:42 +0300 Subject: 2024-04-21 --- modules/rtorrent.nix | 328 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 328 insertions(+) create mode 100644 modules/rtorrent.nix (limited to 'modules/rtorrent.nix') diff --git a/modules/rtorrent.nix b/modules/rtorrent.nix new file mode 100644 index 0000000..82ef1b2 --- /dev/null +++ b/modules/rtorrent.nix @@ -0,0 +1,328 @@ +{ + config, + lib, + libNginx, + 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 + { + ark.directories = [ baseDir ]; + + 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 = getExe ( + 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 + 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 " " [ + (getExe pkgs.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 = 750; + UMask = 27; + 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 = libNginx.config.noProxyBuffering; + }; + }; + extraConfig = libNginx.config.internalOnly; + }; + }; + + 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 " " [ + (getExe pkg) + "--allowedpath=${baseDir}" + "--baseuri=/" + "--rundir=${baseDir}/flood" + "--host=127.0.0.1" + "--port=${toString port}" + "--rtsocket=${rpcSocket}" + "--ssl=false" + "--auth=none" + ]; + + RuntimeDirectory = "rtorrent"; + RuntimeDirectoryMode = 750; + UMask = 27; + 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" ]; + }; + } + ) + ]); +} -- cgit v1.2.3