{ config, lib, pkgs, ... }: with lib; let cfg = config.services.resilio; resilioSync = pkgs.resilio-sync; sharedFoldersRecord = map (entry: { dir = entry.directory; use_relay_server = entry.useRelayServer; use_tracker = entry.useTracker; use_dht = entry.useDHT; search_lan = entry.searchLAN; use_sync_trash = entry.useSyncTrash; known_hosts = entry.knownHosts; }) cfg.sharedFolders; configFile = pkgs.writeText "config.json" (builtins.toJSON ({ device_name = cfg.deviceName; storage_path = cfg.storagePath; listening_port = cfg.listeningPort; use_gui = false; check_for_updates = cfg.checkForUpdates; use_upnp = cfg.useUpnp; download_limit = cfg.downloadLimit; upload_limit = cfg.uploadLimit; lan_encrypt_data = cfg.encryptLAN; } // optionalAttrs (cfg.directoryRoot != "") { directory_root = cfg.directoryRoot; } // optionalAttrs cfg.enableWebUI { webui = { listen = "${cfg.httpListenAddr}:${toString cfg.httpListenPort}"; } // (optionalAttrs (cfg.httpLogin != "") { login = cfg.httpLogin; }) // (optionalAttrs (cfg.httpPass != "") { password = cfg.httpPass; }) // (optionalAttrs (cfg.apiKey != "") { api_key = cfg.apiKey; }); } // optionalAttrs (sharedFoldersRecord != []) { shared_folders = sharedFoldersRecord; })); sharedFoldersSecretFiles = map (entry: { dir = entry.directory; secretFile = if builtins.hasAttr "secret" entry then toString (pkgs.writeTextFile { name = "secret-file"; text = entry.secret; }) else entry.secretFile; }) cfg.sharedFolders; runConfigPath = "/run/rslsync/config.json"; createConfig = pkgs.writeShellScriptBin "create-resilio-config" ( if cfg.sharedFolders != [ ] then '' ${pkgs.jq}/bin/jq \ '.shared_folders |= map(.secret = $ARGS.named[.dir])' \ ${ lib.concatMapStringsSep " \\\n " (entry: ''--arg '${entry.dir}' "$(cat '${entry.secretFile}')"'') sharedFoldersSecretFiles } \ <${configFile} \ >${runConfigPath} '' else '' # no secrets, passing through config cp ${configFile} ${runConfigPath}; '' ); in { options = { services.resilio = { enable = mkOption { type = types.bool; default = false; description = '' If enabled, start the Resilio Sync daemon. Once enabled, you can interact with the service through the Web UI, or configure it in your NixOS configuration. ''; }; deviceName = mkOption { type = types.str; example = "Voltron"; default = config.networking.hostName; defaultText = literalExpression "config.networking.hostName"; description = '' Name of the Resilio Sync device. ''; }; listeningPort = mkOption { type = types.int; default = 0; example = 44444; description = '' Listening port. Defaults to 0 which randomizes the port. ''; }; checkForUpdates = mkOption { type = types.bool; default = true; description = '' Determines whether to check for updates and alert the user about them in the UI. ''; }; useUpnp = mkOption { type = types.bool; default = true; description = '' Use Universal Plug-n-Play (UPnP) ''; }; downloadLimit = mkOption { type = types.int; default = 0; example = 1024; description = '' Download speed limit. 0 is unlimited (default). ''; }; uploadLimit = mkOption { type = types.int; default = 0; example = 1024; description = '' Upload speed limit. 0 is unlimited (default). ''; }; httpListenAddr = mkOption { type = types.str; default = "[::1]"; example = "0.0.0.0"; description = '' HTTP address to bind to. ''; }; httpListenPort = mkOption { type = types.int; default = 9000; description = '' HTTP port to bind on. ''; }; httpLogin = mkOption { type = types.str; example = "allyourbase"; default = ""; description = '' HTTP web login username. ''; }; httpPass = mkOption { type = types.str; example = "arebelongtous"; default = ""; description = '' HTTP web login password. ''; }; encryptLAN = mkOption { type = types.bool; default = true; description = "Encrypt LAN data."; }; enableWebUI = mkOption { type = types.bool; default = false; description = '' Enable Web UI for administration. Bound to the specified `httpListenAddress` and `httpListenPort`. ''; }; storagePath = mkOption { type = types.path; default = "/var/lib/resilio-sync/"; description = '' Where BitTorrent Sync will store it's database files (containing things like username info and licenses). Generally, you should not need to ever change this. ''; }; apiKey = mkOption { type = types.str; default = ""; description = "API key, which enables the developer API."; }; directoryRoot = mkOption { type = types.str; default = ""; example = "/media"; description = "Default directory to add folders in the web UI."; }; sharedFolders = mkOption { default = []; type = types.listOf (types.attrsOf types.anything); example = [ { secretFile = "/run/resilio-secret"; directory = "/home/user/sync_test"; useRelayServer = true; useTracker = true; useDHT = false; searchLAN = true; useSyncTrash = true; knownHosts = [ "192.168.1.2:4444" "192.168.1.3:4444" ]; } ]; description = '' Shared folder list. If enabled, web UI must be disabled. Secrets can be generated using `rslsync --generate-secret`. If you would like to be able to modify the contents of this directories, it is recommended that you make your user a member of the `rslsync` group. Directories in this list should be in the `rslsync` group, and that group must have write access to the directory. It is also recommended that `chmod g+s` is applied to the directory so that any sub directories created will also belong to the `rslsync` group. Also, `setfacl -d -m group:rslsync:rwx` and `setfacl -m group:rslsync:rwx` should also be applied so that the sub directories are writable by the group. ''; }; }; }; config = mkIf cfg.enable { assertions = [ { assertion = cfg.deviceName != ""; message = "Device name cannot be empty."; } { assertion = cfg.enableWebUI -> cfg.sharedFolders == []; message = "If using shared folders, the web UI cannot be enabled."; } { assertion = cfg.apiKey != "" -> cfg.enableWebUI; message = "If you're using an API key, you must enable the web server."; } ]; users.users.rslsync = { description = "Resilio Sync Service user"; home = cfg.storagePath; createHome = true; uid = config.ids.uids.rslsync; group = "rslsync"; }; users.groups.rslsync = {}; systemd.services.resilio = with pkgs; { description = "Resilio Sync Service"; wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; serviceConfig = { Restart = "on-abort"; UMask = "0002"; User = "rslsync"; RuntimeDirectory = "rslsync"; ExecStartPre = "${createConfig}/bin/create-resilio-config"; ExecStart = '' ${resilioSync}/bin/rslsync --nodaemon --config ${runConfigPath} ''; }; }; }; meta.maintainers = with maintainers; [ jwoudenberg ]; }