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