1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.btsync;
7
8 bittorrentSync = cfg.package;
9
10 listenAddr = cfg.httpListenAddr + ":" + (toString cfg.httpListenPort);
11
12 boolStr = x: if x then "true" else "false";
13 optionalEmptyStr = b: v: optionalString (b != "") v;
14
15 webUIConfig = optionalString cfg.enableWebUI
16 ''
17 "webui":
18 {
19 ${optionalEmptyStr cfg.httpLogin "\"login\": \"${cfg.httpLogin}\","}
20 ${optionalEmptyStr cfg.httpPass "\"password\": \"${cfg.httpPass}\","}
21 ${optionalEmptyStr cfg.apiKey "\"api_key\": \"${cfg.apiKey}\","}
22 ${optionalEmptyStr cfg.directoryRoot "\"directory_root\": \"${cfg.directoryRoot}\","}
23 "listen": "${listenAddr}"
24 }
25 '';
26
27 knownHosts = e:
28 optionalString (e ? "knownHosts")
29 (concatStringsSep "," (map (v: "\"${v}\"") e."knownHosts"));
30
31 sharedFoldersRecord =
32 concatStringsSep "," (map (entry:
33 let helper = attr: v:
34 if (entry ? attr) then boolStr entry.attr else boolStr v;
35 in
36 ''
37 {
38 "secret": "${entry.secret}",
39 "dir": "${entry.directory}",
40
41 "use_relay_server": ${helper "useRelayServer" true},
42 "use_tracker": ${helper "useTracker" true},
43 "use_dht": ${helper "useDHT" false},
44
45 "search_lan": ${helper "searchLAN" true},
46 "use_sync_trash": ${helper "useSyncTrash" true},
47
48 "known_hosts": [${knownHosts entry}]
49 }
50 '') cfg.sharedFolders);
51
52 sharedFoldersConfig = optionalString (cfg.sharedFolders != [])
53 ''
54 "shared_folders":
55 [
56 ${sharedFoldersRecord}
57 ]
58 '';
59
60 configFile = pkgs.writeText "btsync.config"
61 ''
62 {
63 "device_name": "${cfg.deviceName}",
64 "storage_path": "${cfg.storagePath}",
65 "listening_port": ${toString cfg.listeningPort},
66 "use_gui": false,
67
68 "check_for_updates": ${boolStr cfg.checkForUpdates},
69 "use_upnp": ${boolStr cfg.useUpnp},
70 "download_limit": ${toString cfg.downloadLimit},
71 "upload_limit": ${toString cfg.uploadLimit},
72 "lan_encrypt_data": ${boolStr cfg.encryptLAN},
73
74 ${webUIConfig}
75 ${sharedFoldersConfig}
76 }
77 '';
78in
79{
80 options = {
81 services.btsync = {
82 enable = mkOption {
83 type = types.bool;
84 default = false;
85 description = ''
86 If enabled, start the Bittorrent Sync daemon. Once enabled, you can
87 interact with the service through the Web UI, or configure it in your
88 NixOS configuration. Enabling the <literal>btsync</literal> service
89 also installs a systemd user unit which can be used to start
90 user-specific copies of the daemon. Once installed, you can use
91 <literal>systemctl --user start btsync</literal> as your user to start
92 the daemon using the configuration file located at
93 <literal>$HOME/.config/btsync.conf</literal>.
94 '';
95 };
96
97 deviceName = mkOption {
98 type = types.str;
99 example = "Voltron";
100 description = ''
101 Name of the Bittorrent Sync device.
102 '';
103 };
104
105 listeningPort = mkOption {
106 type = types.int;
107 default = 0;
108 example = 44444;
109 description = ''
110 Listening port. Defaults to 0 which randomizes the port.
111 '';
112 };
113
114 checkForUpdates = mkOption {
115 type = types.bool;
116 default = true;
117 description = ''
118 Determines whether to check for updates and alert the user
119 about them in the UI.
120 '';
121 };
122
123 useUpnp = mkOption {
124 type = types.bool;
125 default = true;
126 description = ''
127 Use Universal Plug-n-Play (UPnP)
128 '';
129 };
130
131 downloadLimit = mkOption {
132 type = types.int;
133 default = 0;
134 example = 1024;
135 description = ''
136 Download speed limit. 0 is unlimited (default).
137 '';
138 };
139
140 uploadLimit = mkOption {
141 type = types.int;
142 default = 0;
143 example = 1024;
144 description = ''
145 Upload speed limit. 0 is unlimited (default).
146 '';
147 };
148
149 httpListenAddr = mkOption {
150 type = types.str;
151 default = "0.0.0.0";
152 example = "1.2.3.4";
153 description = ''
154 HTTP address to bind to.
155 '';
156 };
157
158 httpListenPort = mkOption {
159 type = types.int;
160 default = 9000;
161 description = ''
162 HTTP port to bind on.
163 '';
164 };
165
166 httpLogin = mkOption {
167 type = types.str;
168 example = "allyourbase";
169 default = "";
170 description = ''
171 HTTP web login username.
172 '';
173 };
174
175 httpPass = mkOption {
176 type = types.str;
177 example = "arebelongtous";
178 default = "";
179 description = ''
180 HTTP web login password.
181 '';
182 };
183
184 encryptLAN = mkOption {
185 type = types.bool;
186 default = true;
187 description = "Encrypt LAN data.";
188 };
189
190 enableWebUI = mkOption {
191 type = types.bool;
192 default = false;
193 description = ''
194 Enable Web UI for administration. Bound to the specified
195 <literal>httpListenAddress</literal> and
196 <literal>httpListenPort</literal>.
197 '';
198 };
199
200 package = mkOption {
201 type = types.package;
202 example = literalExample "pkgs.bittorrentSync20";
203 description = ''
204 Branch of bittorrent sync to use.
205 '';
206 };
207
208 storagePath = mkOption {
209 type = types.path;
210 default = "/var/lib/btsync/";
211 example = "/var/lib/btsync/";
212 description = ''
213 Where BitTorrent Sync will store it's database files (containing
214 things like username info and licenses). Generally, you should not
215 need to ever change this.
216 '';
217 };
218
219 apiKey = mkOption {
220 type = types.str;
221 default = "";
222 description = "API key, which enables the developer API.";
223 };
224
225 directoryRoot = mkOption {
226 type = types.str;
227 default = "";
228 example = "/media";
229 description = "Default directory to add folders in the web UI.";
230 };
231
232 sharedFolders = mkOption {
233 default = [];
234 example =
235 [ { secret = "AHMYFPCQAHBM7LQPFXQ7WV6Y42IGUXJ5Y";
236 directory = "/home/user/sync_test";
237 useRelayServer = true;
238 useTracker = true;
239 useDHT = false;
240 searchLAN = true;
241 useSyncTrash = true;
242 knownHosts =
243 [ "192.168.1.2:4444"
244 "192.168.1.3:4444"
245 ];
246 }
247 ];
248 description = ''
249 Shared folder list. If enabled, web UI must be
250 disabled. Secrets can be generated using <literal>btsync
251 --generate-secret</literal>. Note that this secret will be
252 put inside the Nix store, so it is realistically not very
253 secret.
254
255 If you would like to be able to modify the contents of this
256 directories, it is recommended that you make your user a
257 member of the <literal>btsync</literal> group.
258
259 Directories in this list should be in the
260 <literal>btsync</literal> group, and that group must have
261 write access to the directory. It is also recommended that
262 <literal>chmod g+s</literal> is applied to the directory
263 so that any sub directories created will also belong to
264 the <literal>btsync</literal> group. Also,
265 <literal>setfacl -d -m group:btsync:rwx</literal> and
266 <literal>setfacl -m group:btsync:rwx</literal> should also
267 be applied so that the sub directories are writable by
268 the group.
269 '';
270 };
271 };
272 };
273
274 config = mkIf cfg.enable {
275 assertions =
276 [ { assertion = cfg.deviceName != "";
277 message = "Device name cannot be empty.";
278 }
279 { assertion = cfg.enableWebUI -> cfg.sharedFolders == [];
280 message = "If using shared folders, the web UI cannot be enabled.";
281 }
282 { assertion = cfg.apiKey != "" -> cfg.enableWebUI;
283 message = "If you're using an API key, you must enable the web server.";
284 }
285 ];
286
287 services.btsync.package = mkOptionDefault pkgs.bittorrentSync14;
288
289 users.extraUsers.btsync = {
290 description = "Bittorrent Sync Service user";
291 home = cfg.storagePath;
292 createHome = true;
293 uid = config.ids.uids.btsync;
294 group = "btsync";
295 };
296
297 users.extraGroups = [
298 { name = "btsync";
299 }];
300
301 systemd.services.btsync = with pkgs; {
302 description = "Bittorrent Sync Service";
303 wantedBy = [ "multi-user.target" ];
304 after = [ "network.target" "local-fs.target" ];
305 serviceConfig = {
306 Restart = "on-abort";
307 UMask = "0002";
308 User = "btsync";
309 ExecStart =
310 "${bittorrentSync}/bin/btsync --nodaemon --config ${configFile}";
311 };
312 };
313
314 systemd.user.services.btsync = with pkgs; {
315 description = "Bittorrent Sync user service";
316 after = [ "network.target" "local-fs.target" ];
317 serviceConfig = {
318 Restart = "on-abort";
319 ExecStart =
320 "${bittorrentSync}/bin/btsync --nodaemon --config %h/.config/btsync.conf";
321 };
322 };
323
324 environment.systemPackages = [ cfg.package ];
325 };
326}