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