1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8let
9 cfg = config.services.aria2;
10
11 homeDir = "/var/lib/aria2";
12 defaultRpcListenPort = 6800;
13 defaultDir = "${homeDir}/Downloads";
14
15 portRangesToString =
16 ranges:
17 lib.concatStringsSep "," (
18 map (
19 x:
20 if x.from == x.to then
21 builtins.toString x.from
22 else
23 builtins.toString x.from + "-" + builtins.toString x.to
24 ) ranges
25 );
26
27 customToKeyValue = lib.generators.toKeyValue {
28 mkKeyValue = lib.generators.mkKeyValueDefault {
29 mkValueString =
30 v: if builtins.isList v then portRangesToString v else lib.generators.mkValueStringDefault { } v;
31 } "=";
32 };
33in
34{
35 imports = [
36 (lib.mkRemovedOptionModule [
37 "services"
38 "aria2"
39 "rpcSecret"
40 ] "Use services.aria2.rpcSecretFile instead")
41 (lib.mkRemovedOptionModule [
42 "services"
43 "aria2"
44 "extraArguments"
45 ] "Use services.aria2.settings instead")
46 (lib.mkRenamedOptionModule
47 [ "services" "aria2" "downloadDir" ]
48 [ "services" "aria2" "settings" "dir" ]
49 )
50 (lib.mkRenamedOptionModule
51 [ "services" "aria2" "listenPortRange" ]
52 [ "services" "aria2" "settings" "listen-port" ]
53 )
54 (lib.mkRenamedOptionModule
55 [ "services" "aria2" "rpcListenPort" ]
56 [ "services" "aria2" "settings" "rpc-listen-port" ]
57 )
58 ];
59
60 options = {
61 services.aria2 = {
62 enable = lib.mkOption {
63 type = lib.types.bool;
64 default = false;
65 description = ''
66 Whether or not to enable the headless Aria2 daemon service.
67
68 Aria2 daemon can be controlled via the RPC interface using one of many
69 WebUIs (http://localhost:${toString defaultRpcListenPort}/ by default).
70
71 Targets are downloaded to `${defaultDir}` by default and are
72 accessible to users in the `aria2` group.
73 '';
74 };
75 openPorts = lib.mkOption {
76 type = lib.types.bool;
77 default = false;
78 description = ''
79 Open listen and RPC ports found in `settings.listen-port` and
80 `settings.rpc-listen-port` options in the firewall.
81 '';
82 };
83 rpcSecretFile = lib.mkOption {
84 type = lib.types.path;
85 example = "/run/secrets/aria2-rpc-token.txt";
86 description = ''
87 A file containing the RPC secret authorization token.
88 Read https://aria2.github.io/manual/en/html/aria2c.html#rpc-auth to know how this option value is used.
89 '';
90 };
91 downloadDirPermission = lib.mkOption {
92 type = lib.types.str;
93 default = "0770";
94 description = ''
95 The permission for `settings.dir`.
96
97 The default is 0770, which denies access for users not in the `aria2`
98 group.
99
100 You may want to adjust `serviceUMask` as well, which further restricts
101 the file permission for newly created files (i.e. the downloads).
102 '';
103 };
104 serviceUMask = lib.mkOption {
105 type = lib.types.str;
106 default = "0022";
107 example = "0002";
108 description = ''
109 The file mode creation mask for Aria2 service.
110
111 The default is 0022 for compatibility reason, as this is the default
112 used by systemd. However, this results in file permission 0644 for new
113 files, and denies `aria2` group member from modifying the file.
114
115 You may want to set this value to `0002` so you can manage the file
116 more easily.
117 '';
118 };
119 settings = lib.mkOption {
120 description = ''
121 Generates the `aria2.conf` file. Refer to [the documentation][0] for
122 all possible settings.
123
124 [0]: https://aria2.github.io/manual/en/html/aria2c.html#synopsis
125 '';
126 default = { };
127 type = lib.types.submodule {
128 freeformType =
129 with lib.types;
130 attrsOf (oneOf [
131 bool
132 int
133 float
134 singleLineStr
135 ]);
136 options = {
137 save-session = lib.mkOption {
138 type = lib.types.singleLineStr;
139 default = "${homeDir}/aria2.session";
140 description = "Save error/unfinished downloads to FILE on exit.";
141 };
142 dir = lib.mkOption {
143 type = lib.types.singleLineStr;
144 default = defaultDir;
145 description = "Directory to store downloaded files.";
146 };
147 conf-path = lib.mkOption {
148 type = lib.types.singleLineStr;
149 default = "${homeDir}/aria2.conf";
150 description = "Configuration file path.";
151 };
152 enable-rpc = lib.mkOption {
153 type = lib.types.bool;
154 default = true;
155 description = "Enable JSON-RPC/XML-RPC server.";
156 };
157 listen-port = lib.mkOption {
158 type = with lib.types; listOf (attrsOf port);
159 default = [
160 {
161 from = 6881;
162 to = 6999;
163 }
164 ];
165 description = "Set UDP listening port range used by DHT(IPv4, IPv6) and UDP tracker.";
166 };
167 rpc-listen-port = lib.mkOption {
168 type = lib.types.port;
169 default = defaultRpcListenPort;
170 description = "Specify a port number for JSON-RPC/XML-RPC server to listen to. Possible Values: 1024-65535";
171 };
172 };
173 };
174 };
175 };
176 };
177
178 config = lib.mkIf cfg.enable {
179 assertions = [
180 {
181 assertion = cfg.settings.enable-rpc;
182 message = "RPC has to be enabled, the default module option takes care of that.";
183 }
184 {
185 assertion = !(cfg.settings ? rpc-secret);
186 message = "Set the RPC secret through services.aria2.rpcSecretFile so it will not end up in the world-readable nix store.";
187 }
188 ];
189
190 # Need to open ports for proper functioning
191 networking.firewall = lib.mkIf cfg.openPorts {
192 allowedUDPPortRanges = config.services.aria2.settings.listen-port;
193 allowedTCPPorts = [ config.services.aria2.settings.rpc-listen-port ];
194 };
195
196 users.users.aria2 = {
197 group = "aria2";
198 uid = config.ids.uids.aria2;
199 description = "aria2 user";
200 home = homeDir;
201 createHome = false;
202 };
203
204 users.groups.aria2.gid = config.ids.gids.aria2;
205
206 systemd.tmpfiles.rules = [
207 "d '${homeDir}' 0770 aria2 aria2 - -"
208 "d '${config.services.aria2.settings.dir}' ${config.services.aria2.downloadDirPermission} aria2 aria2 - -"
209 ];
210
211 systemd.services.aria2 = {
212 description = "aria2 Service";
213 after = [ "network.target" ];
214 wantedBy = [ "multi-user.target" ];
215 preStart = ''
216 if [[ ! -e "${cfg.settings.save-session}" ]]
217 then
218 touch "${cfg.settings.save-session}"
219 fi
220 cp -f "${pkgs.writeText "aria2.conf" (customToKeyValue cfg.settings)}" "${cfg.settings.conf-path}"
221 chmod +w "${cfg.settings.conf-path}"
222 echo "rpc-secret=$(cat "$CREDENTIALS_DIRECTORY/rpcSecretFile")" >> "${cfg.settings.conf-path}"
223 '';
224
225 serviceConfig = {
226 Restart = "on-abort";
227 ExecStart = "${pkgs.aria2}/bin/aria2c --conf-path=${cfg.settings.conf-path}";
228 ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
229 User = "aria2";
230 Group = "aria2";
231 LoadCredential = "rpcSecretFile:${cfg.rpcSecretFile}";
232 UMask = cfg.serviceUMask;
233 };
234 };
235 };
236
237 meta.maintainers = [ lib.maintainers.timhae ];
238}