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