nixos/preload: init
[NixPkgs.git] / nixos / modules / services / torrent / rtorrent.nix
blob64cda7fb675f8c076cb7562976b1b5568ab37118
1 { config, options, pkgs, lib, ... }:
3 with lib;
5 let
7   cfg = config.services.rtorrent;
8   opt = options.services.rtorrent;
10 in {
11   options.services.rtorrent = {
12     enable = mkEnableOption (lib.mdDoc "rtorrent");
14     dataDir = mkOption {
15       type = types.str;
16       default = "/var/lib/rtorrent";
17       description = lib.mdDoc ''
18         The directory where rtorrent stores its data files.
19       '';
20     };
22     dataPermissions = mkOption {
23       type = types.str;
24       default = "0750";
25       example = "0755";
26       description = lib.mdDoc ''
27         Unix Permissions in octal on the rtorrent directory.
28       '';
29     };
31     downloadDir = mkOption {
32       type = types.str;
33       default = "${cfg.dataDir}/download";
34       defaultText = literalExpression ''"''${config.${opt.dataDir}}/download"'';
35       description = lib.mdDoc ''
36         Where to put downloaded files.
37       '';
38     };
40     user = mkOption {
41       type = types.str;
42       default = "rtorrent";
43       description = lib.mdDoc ''
44         User account under which rtorrent runs.
45       '';
46     };
48     group = mkOption {
49       type = types.str;
50       default = "rtorrent";
51       description = lib.mdDoc ''
52         Group under which rtorrent runs.
53       '';
54     };
56     package = mkOption {
57       type = types.package;
58       default = pkgs.rtorrent;
59       defaultText = literalExpression "pkgs.rtorrent";
60       description = lib.mdDoc ''
61         The rtorrent package to use.
62       '';
63     };
65     port = mkOption {
66       type = types.port;
67       default = 50000;
68       description = lib.mdDoc ''
69         The rtorrent port.
70       '';
71     };
73     openFirewall = mkOption {
74       type = types.bool;
75       default = false;
76       description = lib.mdDoc ''
77         Whether to open the firewall for the port in {option}`services.rtorrent.port`.
78       '';
79     };
81     rpcSocket = mkOption {
82       type = types.str;
83       readOnly = true;
84       default = "/run/rtorrent/rpc.sock";
85       description = lib.mdDoc ''
86         RPC socket path.
87       '';
88     };
90     configText = mkOption {
91       type = types.lines;
92       default = "";
93       description = lib.mdDoc ''
94         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.
95       '';
96     };
97   };
99   config = mkIf cfg.enable {
101     users.groups = mkIf (cfg.group == "rtorrent") {
102       rtorrent = {};
103     };
105     users.users = mkIf (cfg.user == "rtorrent") {
106       rtorrent = {
107         group = cfg.group;
108         shell = pkgs.bashInteractive;
109         home = cfg.dataDir;
110         description = "rtorrent Daemon user";
111         isSystemUser = true;
112       };
113     };
115     networking.firewall.allowedTCPPorts = mkIf (cfg.openFirewall) [ cfg.port ];
117     services.rtorrent.configText = mkBefore ''
118       # Instance layout (base paths)
119       method.insert = cfg.basedir, private|const|string, (cat,"${cfg.dataDir}/")
120       method.insert = cfg.watch,   private|const|string, (cat,(cfg.basedir),"watch/")
121       method.insert = cfg.logs,    private|const|string, (cat,(cfg.basedir),"log/")
122       method.insert = cfg.logfile, private|const|string, (cat,(cfg.logs),(system.time),".log")
123       method.insert = cfg.rpcsock, private|const|string, (cat,"${cfg.rpcSocket}")
125       # Create instance directories
126       execute.throw = sh, -c, (cat, "mkdir -p ", (cfg.basedir), "/session ", (cfg.watch), " ", (cfg.logs))
128       # Listening port for incoming peer traffic (fixed; you can also randomize it)
129       network.port_range.set = ${toString cfg.port}-${toString cfg.port}
130       network.port_random.set = no
132       # Tracker-less torrent and UDP tracker support
133       # (conservative settings for 'private' trackers, change for 'public')
134       dht.mode.set = disable
135       protocol.pex.set = no
136       trackers.use_udp.set = no
138       # Peer settings
139       throttle.max_uploads.set = 100
140       throttle.max_uploads.global.set = 250
142       throttle.min_peers.normal.set = 20
143       throttle.max_peers.normal.set = 60
144       throttle.min_peers.seed.set = 30
145       throttle.max_peers.seed.set = 80
146       trackers.numwant.set = 80
148       protocol.encryption.set = allow_incoming,try_outgoing,enable_retry
150       # Limits for file handle resources, this is optimized for
151       # an `ulimit` of 1024 (a common default). You MUST leave
152       # a ceiling of handles reserved for rTorrent's internal needs!
153       network.http.max_open.set = 50
154       network.max_open_files.set = 600
155       network.max_open_sockets.set = 3000
157       # Memory resource usage (increase if you have a large number of items loaded,
158       # and/or the available resources to spend)
159       pieces.memory.max.set = 1800M
160       network.xmlrpc.size_limit.set = 4M
162       # Basic operational settings (no need to change these)
163       session.path.set = (cat, (cfg.basedir), "session/")
164       directory.default.set = "${cfg.downloadDir}"
165       log.execute = (cat, (cfg.logs), "execute.log")
166       ##log.xmlrpc = (cat, (cfg.logs), "xmlrpc.log")
167       execute.nothrow = sh, -c, (cat, "echo >", (session.path), "rtorrent.pid", " ", (system.pid))
169       # Other operational settings (check & adapt)
170       encoding.add = utf8
171       system.umask.set = 0027
172       system.cwd.set = (cfg.basedir)
173       network.http.dns_cache_timeout.set = 25
174       schedule2 = monitor_diskspace, 15, 60, ((close_low_diskspace, 1000M))
176       # Watch directories (add more as you like, but use unique schedule names)
177       #schedule2 = watch_start, 10, 10, ((load.start, (cat, (cfg.watch), "start/*.torrent")))
178       #schedule2 = watch_load, 11, 10, ((load.normal, (cat, (cfg.watch), "load/*.torrent")))
180       # Logging:
181       #   Levels = critical error warn notice info debug
182       #   Groups = connection_* dht_* peer_* rpc_* storage_* thread_* tracker_* torrent_*
183       print = (cat, "Logging to ", (cfg.logfile))
184       log.open_file = "log", (cfg.logfile)
185       log.add_output = "info", "log"
186       ##log.add_output = "tracker_debug", "log"
188       # XMLRPC
189       scgi_local = (cfg.rpcsock)
190       schedule = scgi_group,0,0,"execute.nothrow=chown,\":rtorrent\",(cfg.rpcsock)"
191       schedule = scgi_permission,0,0,"execute.nothrow=chmod,\"g+w,o=\",(cfg.rpcsock)"
192     '';
194     systemd = {
195       services = {
196         rtorrent = let
197           rtorrentConfigFile = pkgs.writeText "rtorrent.rc" cfg.configText;
198         in {
199           description = "rTorrent system service";
200           after = [ "network.target" ];
201           path = [ cfg.package pkgs.bash ];
202           wantedBy = [ "multi-user.target" ];
203           serviceConfig = {
204             User = cfg.user;
205             Group = cfg.group;
206             Type = "simple";
207             Restart = "on-failure";
208             WorkingDirectory = cfg.dataDir;
209             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"'';
210             ExecStart="${cfg.package}/bin/rtorrent -n -o system.daemon.set=true -o import=${rtorrentConfigFile}";
211             RuntimeDirectory = "rtorrent";
212             RuntimeDirectoryMode = 755;
213           };
214         };
215       };
217       tmpfiles.rules = [ "d '${cfg.dataDir}' ${cfg.dataPermissions} ${cfg.user} ${cfg.group} -" ];
218     };
219   };