legends-of-equestria: init at 2024.05.01 (#296316)
[NixPkgs.git] / nixos / modules / services / torrent / rtorrent.nix
blob822e1ab1c3c518c6625d4d6e1e7319dde34c89fc
2   config,
3   options,
4   pkgs,
5   lib,
6   ...
7 }:
9 with lib;
11 let
13   cfg = config.services.rtorrent;
14   opt = options.services.rtorrent;
18   meta.maintainers = with lib.maintainers; [ thiagokokada ];
20   options.services.rtorrent = {
21     enable = mkEnableOption "rtorrent";
23     dataDir = mkOption {
24       type = types.str;
25       default = "/var/lib/rtorrent";
26       description = ''
27         The directory where rtorrent stores its data files.
28       '';
29     };
31     dataPermissions = mkOption {
32       type = types.str;
33       default = "0750";
34       example = "0755";
35       description = ''
36         Unix Permissions in octal on the rtorrent directory.
37       '';
38     };
40     downloadDir = mkOption {
41       type = types.str;
42       default = "${cfg.dataDir}/download";
43       defaultText = literalExpression ''"''${config.${opt.dataDir}}/download"'';
44       description = ''
45         Where to put downloaded files.
46       '';
47     };
49     user = mkOption {
50       type = types.str;
51       default = "rtorrent";
52       description = ''
53         User account under which rtorrent runs.
54       '';
55     };
57     group = mkOption {
58       type = types.str;
59       default = "rtorrent";
60       description = ''
61         Group under which rtorrent runs.
62       '';
63     };
65     package = mkPackageOption pkgs "rtorrent" { };
67     port = mkOption {
68       type = types.port;
69       default = 50000;
70       description = ''
71         The rtorrent port.
72       '';
73     };
75     openFirewall = mkOption {
76       type = types.bool;
77       default = false;
78       description = ''
79         Whether to open the firewall for the port in {option}`services.rtorrent.port`.
80       '';
81     };
83     rpcSocket = mkOption {
84       type = types.str;
85       readOnly = true;
86       default = "/run/rtorrent/rpc.sock";
87       description = ''
88         RPC socket path.
89       '';
90     };
92     configText = mkOption {
93       type = types.lines;
94       default = "";
95       description = ''
96         The content of {file}`rtorrent.rc`. The [modernized configuration template](https://rtorrent-docs.readthedocs.io/en/latest/cookbook.html#modernized-configuration-template) with the values specified in this module will be prepended using mkBefore. You can use mkForce to overwrite the config completely.
97       '';
98     };
99   };
101   config = mkIf cfg.enable {
103     users.groups = mkIf (cfg.group == "rtorrent") {
104       rtorrent = { };
105     };
107     users.users = mkIf (cfg.user == "rtorrent") {
108       rtorrent = {
109         group = cfg.group;
110         shell = pkgs.bashInteractive;
111         home = cfg.dataDir;
112         description = "rtorrent Daemon user";
113         isSystemUser = true;
114       };
115     };
117     networking.firewall.allowedTCPPorts = mkIf (cfg.openFirewall) [ cfg.port ];
119     services.rtorrent.configText = mkBefore ''
120       # Instance layout (base paths)
121       method.insert = cfg.basedir, private|const|string, (cat,"${cfg.dataDir}/")
122       method.insert = cfg.watch,   private|const|string, (cat,(cfg.basedir),"watch/")
123       method.insert = cfg.logs,    private|const|string, (cat,(cfg.basedir),"log/")
124       method.insert = cfg.logfile, private|const|string, (cat,(cfg.logs),(system.time),".log")
125       method.insert = cfg.rpcsock, private|const|string, (cat,"${cfg.rpcSocket}")
127       # Create instance directories
128       execute.throw = sh, -c, (cat, "mkdir -p ", (cfg.basedir), "/session ", (cfg.watch), " ", (cfg.logs))
130       # Listening port for incoming peer traffic (fixed; you can also randomize it)
131       network.port_range.set = ${toString cfg.port}-${toString cfg.port}
132       network.port_random.set = no
134       # Tracker-less torrent and UDP tracker support
135       # (conservative settings for 'private' trackers, change for 'public')
136       dht.mode.set = disable
137       protocol.pex.set = no
138       trackers.use_udp.set = no
140       # Peer settings
141       throttle.max_uploads.set = 100
142       throttle.max_uploads.global.set = 250
144       throttle.min_peers.normal.set = 20
145       throttle.max_peers.normal.set = 60
146       throttle.min_peers.seed.set = 30
147       throttle.max_peers.seed.set = 80
148       trackers.numwant.set = 80
150       protocol.encryption.set = allow_incoming,try_outgoing,enable_retry
152       # Limits for file handle resources, this is optimized for
153       # an `ulimit` of 1024 (a common default). You MUST leave
154       # a ceiling of handles reserved for rTorrent's internal needs!
155       network.http.max_open.set = 50
156       network.max_open_files.set = 600
157       network.max_open_sockets.set = 3000
159       # Memory resource usage (increase if you have a large number of items loaded,
160       # and/or the available resources to spend)
161       pieces.memory.max.set = 1800M
162       network.xmlrpc.size_limit.set = 4M
164       # Basic operational settings (no need to change these)
165       session.path.set = (cat, (cfg.basedir), "session/")
166       directory.default.set = "${cfg.downloadDir}"
167       log.execute = (cat, (cfg.logs), "execute.log")
168       ##log.xmlrpc = (cat, (cfg.logs), "xmlrpc.log")
169       execute.nothrow = sh, -c, (cat, "echo >", (session.path), "rtorrent.pid", " ", (system.pid))
171       # Other operational settings (check & adapt)
172       encoding.add = utf8
173       system.umask.set = 0027
174       system.cwd.set = (cfg.basedir)
175       network.http.dns_cache_timeout.set = 25
176       schedule2 = monitor_diskspace, 15, 60, ((close_low_diskspace, 1000M))
178       # Watch directories (add more as you like, but use unique schedule names)
179       #schedule2 = watch_start, 10, 10, ((load.start, (cat, (cfg.watch), "start/*.torrent")))
180       #schedule2 = watch_load, 11, 10, ((load.normal, (cat, (cfg.watch), "load/*.torrent")))
182       # Logging:
183       #   Levels = critical error warn notice info debug
184       #   Groups = connection_* dht_* peer_* rpc_* storage_* thread_* tracker_* torrent_*
185       print = (cat, "Logging to ", (cfg.logfile))
186       log.open_file = "log", (cfg.logfile)
187       log.add_output = "info", "log"
188       ##log.add_output = "tracker_debug", "log"
190       # XMLRPC
191       scgi_local = (cfg.rpcsock)
192       schedule = scgi_group,0,0,"execute.nothrow=chown,\":${cfg.group}\",(cfg.rpcsock)"
193       schedule = scgi_permission,0,0,"execute.nothrow=chmod,\"g+w,o=\",(cfg.rpcsock)"
194     '';
196     systemd = {
197       services = {
198         rtorrent =
199           let
200             rtorrentConfigFile = pkgs.writeText "rtorrent.rc" cfg.configText;
201           in
202           {
203             description = "rTorrent system service";
204             after = [ "network.target" ];
205             path = [
206               cfg.package
207               pkgs.bash
208             ];
209             wantedBy = [ "multi-user.target" ];
210             serviceConfig = {
211               User = cfg.user;
212               Group = cfg.group;
213               Type = "simple";
214               Restart = "on-failure";
215               WorkingDirectory = cfg.dataDir;
216               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"'';
217               ExecStart = "${cfg.package}/bin/rtorrent -n -o system.daemon.set=true -o import=${rtorrentConfigFile}";
218               RuntimeDirectory = "rtorrent";
219               RuntimeDirectoryMode = 750;
221               CapabilityBoundingSet = [ "" ];
222               LockPersonality = true;
223               NoNewPrivileges = true;
224               PrivateDevices = true;
225               PrivateTmp = true;
226               ProtectClock = true;
227               ProtectControlGroups = true;
228               # If the default user is changed, there is a good chance that they
229               # want to store data in e.g.: $HOME directory
230               # Relax hardening in this case
231               ProtectHome = lib.mkIf (cfg.user == "rtorrent") true;
232               ProtectHostname = true;
233               ProtectKernelLogs = true;
234               ProtectKernelModules = true;
235               ProtectKernelTunables = true;
236               ProtectProc = "invisible";
237               ProtectSystem = "full";
238               RestrictAddressFamilies = [
239                 "AF_UNIX"
240                 "AF_INET"
241                 "AF_INET6"
242               ];
243               RestrictNamespaces = true;
244               RestrictRealtime = true;
245               RestrictSUIDSGID = true;
246               SystemCallArchitectures = "native";
247               SystemCallFilter = [
248                 "@system-service"
249                 "~@privileged"
250               ];
251             };
252           };
253       };
255       tmpfiles.rules = [ "d '${cfg.dataDir}' ${cfg.dataPermissions} ${cfg.user} ${cfg.group} -" ];
256     };
257   };