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}