diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 1ff5f5ff0c25..e72547c33cfc 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -809,6 +809,7 @@ ./services/torrent/magnetico.nix ./services/torrent/opentracker.nix ./services/torrent/peerflix.nix + ./services/torrent/rtorrent.nix ./services/torrent/transmission.nix ./services/ttys/agetty.nix ./services/ttys/gpm.nix diff --git a/nixos/modules/services/torrent/rtorrent.nix b/nixos/modules/services/torrent/rtorrent.nix new file mode 100644 index 000000000000..be57c03b1721 --- /dev/null +++ b/nixos/modules/services/torrent/rtorrent.nix @@ -0,0 +1,209 @@ +{ config, pkgs, lib, ... }: + +with lib; + +let + + cfg = config.services.rtorrent; + +in { + options.services.rtorrent = { + enable = mkEnableOption "rtorrent"; + + dataDir = mkOption { + type = types.str; + default = "/var/lib/rtorrent"; + description = '' + The directory where rtorrent stores its data files. + ''; + }; + + downloadDir = mkOption { + type = types.str; + default = "${cfg.dataDir}/download"; + description = '' + Where to put downloaded files. + ''; + }; + + user = mkOption { + type = types.str; + default = "rtorrent"; + description = '' + User account under which rtorrent runs. + ''; + }; + + group = mkOption { + type = types.str; + default = "rtorrent"; + description = '' + Group under which rtorrent runs. + ''; + }; + + package = mkOption { + type = types.package; + default = pkgs.rtorrent; + defaultText = "pkgs.rtorrent"; + description = '' + The rtorrent package to use. + ''; + }; + + port = mkOption { + type = types.port; + default = 50000; + description = '' + The rtorrent port. + ''; + }; + + openFirewall = mkOption { + type = types.bool; + default = false; + description = '' + Whether to open the firewall for the port in . + ''; + }; + + rpcSocket = mkOption { + type = types.str; + readOnly = true; + default = "/run/rtorrent/rpc.sock"; + description = '' + RPC socket path. + ''; + }; + + configText = mkOption { + type = types.lines; + default = ""; + description = '' + The content of rtorrent.rc. The modernized configuration template with the values specified in this module will be prepended using mkBefore. You can use mkForce to overwrite the config completly. + ''; + }; + }; + + config = mkIf cfg.enable { + + users.groups = mkIf (cfg.group == "rtorrent") { + rtorrent = {}; + }; + + users.users = mkIf (cfg.user == "rtorrent") { + rtorrent = { + group = cfg.group; + shell = pkgs.bashInteractive; + home = cfg.dataDir; + description = "rtorrent Daemon user"; + isSystemUser = true; + }; + }; + + networking.firewall.allowedTCPPorts = mkIf (cfg.openFirewall) [ cfg.port ]; + + services.rtorrent.configText = mkBefore '' + # Instance layout (base paths) + method.insert = cfg.basedir, private|const|string, (cat,"${cfg.dataDir}/") + method.insert = cfg.watch, private|const|string, (cat,(cfg.basedir),"watch/") + method.insert = cfg.logs, private|const|string, (cat,(cfg.basedir),"log/") + method.insert = cfg.logfile, private|const|string, (cat,(cfg.logs),(system.time),".log") + method.insert = cfg.rpcsock, private|const|string, (cat,"${cfg.rpcSocket}") + + # Create instance directories + execute.throw = sh, -c, (cat, "mkdir -p ", (cfg.basedir), "/session ", (cfg.watch), " ", (cfg.logs)) + + # Listening port for incoming peer traffic (fixed; you can also randomize it) + network.port_range.set = ${toString cfg.port}-${toString cfg.port} + network.port_random.set = no + + # Tracker-less torrent and UDP tracker support + # (conservative settings for 'private' trackers, change for 'public') + dht.mode.set = disable + protocol.pex.set = no + trackers.use_udp.set = no + + # Peer settings + throttle.max_uploads.set = 100 + throttle.max_uploads.global.set = 250 + + throttle.min_peers.normal.set = 20 + throttle.max_peers.normal.set = 60 + throttle.min_peers.seed.set = 30 + throttle.max_peers.seed.set = 80 + trackers.numwant.set = 80 + + protocol.encryption.set = allow_incoming,try_outgoing,enable_retry + + # Limits for file handle resources, this is optimized for + # an `ulimit` of 1024 (a common default). You MUST leave + # a ceiling of handles reserved for rTorrent's internal needs! + network.http.max_open.set = 50 + network.max_open_files.set = 600 + network.max_open_sockets.set = 3000 + + # Memory resource usage (increase if you have a large number of items loaded, + # and/or the available resources to spend) + pieces.memory.max.set = 1800M + network.xmlrpc.size_limit.set = 4M + + # Basic operational settings (no need to change these) + session.path.set = (cat, (cfg.basedir), "session/") + directory.default.set = "${cfg.downloadDir}" + log.execute = (cat, (cfg.logs), "execute.log") + ##log.xmlrpc = (cat, (cfg.logs), "xmlrpc.log") + execute.nothrow = sh, -c, (cat, "echo >", (session.path), "rtorrent.pid", " ", (system.pid)) + + # Other operational settings (check & adapt) + encoding.add = utf8 + system.umask.set = 0027 + system.cwd.set = (cfg.basedir) + network.http.dns_cache_timeout.set = 25 + schedule2 = monitor_diskspace, 15, 60, ((close_low_diskspace, 1000M)) + + # Watch directories (add more as you like, but use unique schedule names) + #schedule2 = watch_start, 10, 10, ((load.start, (cat, (cfg.watch), "start/*.torrent"))) + #schedule2 = watch_load, 11, 10, ((load.normal, (cat, (cfg.watch), "load/*.torrent"))) + + # Logging: + # Levels = critical error warn notice info debug + # Groups = connection_* dht_* peer_* rpc_* storage_* thread_* tracker_* torrent_* + print = (cat, "Logging to ", (cfg.logfile)) + log.open_file = "log", (cfg.logfile) + log.add_output = "info", "log" + ##log.add_output = "tracker_debug", "log" + + # XMLRPC + scgi_local = (cfg.rpcsock) + schedule = scgi_group,0,0,"execute.nothrow=chown,\":rtorrent\",(cfg.rpcsock)" + schedule = scgi_permission,0,0,"execute.nothrow=chmod,\"g+w,o=\",(cfg.rpcsock)" + ''; + + systemd = { + services = { + rtorrent = let + rtorrentConfigFile = pkgs.writeText "rtorrent.rc" cfg.configText; + in { + description = "rTorrent system service"; + after = [ "network.target" ]; + path = [ cfg.package pkgs.bash ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + User = cfg.user; + Group = cfg.group; + Type = "simple"; + Restart = "on-failure"; + WorkingDirectory = cfg.dataDir; + ExecStartPre=''${pkgs.bash}/bin/bash -c "if test -e ${cfg.dataDir}/session/rtorrent.lock && test -z $(${pkgs.procps}/bin/pidof rtorrent); then rm -f ${cfg.dataDir}/session/rtorrent.lock; fi"''; + ExecStart="${cfg.package}/bin/rtorrent -n -o system.daemon.set=true -o import=${rtorrentConfigFile}"; + RuntimeDirectory = "rtorrent"; + RuntimeDirectoryMode = 755; + }; + }; + }; + + tmpfiles.rules = [ "d '${cfg.dataDir}' 0750 ${cfg.user} ${cfg.group} -" ]; + }; + }; +}