diff --git a/nixos/modules/services/networking/syncthing.nix b/nixos/modules/services/networking/syncthing.nix index 3c58cd9ddad..63b249df735 100644 --- a/nixos/modules/services/networking/syncthing.nix +++ b/nixos/modules/services/networking/syncthing.nix @@ -5,15 +5,16 @@ with lib; let cfg = config.services.syncthing; defaultUser = "syncthing"; + defaultGroup = defaultUser; devices = mapAttrsToList (name: device: { deviceID = device.id; inherit (device) name addresses introducer; - }) cfg.declarative.devices; + }) cfg.devices; folders = mapAttrsToList ( _: folder: { inherit (folder) path id label type; - devices = map (device: { deviceId = cfg.declarative.devices.${device}.id; }) folder.devices; + devices = map (device: { deviceId = cfg.devices.${device}.id; }) folder.devices; rescanIntervalS = folder.rescanInterval; fsWatcherEnabled = folder.watch; fsWatcherDelayS = folder.watchDelay; @@ -23,7 +24,7 @@ let }) (filterAttrs ( _: folder: folder.enable - ) cfg.declarative.folders); + ) cfg.folders); updateConfig = pkgs.writers.writeDash "merge-syncthing-config" '' set -efu @@ -50,9 +51,9 @@ let # generate the new config by merging with the NixOS config options new_cfg=$(echo "$old_cfg" | ${pkgs.jq}/bin/jq -c '. * { - "devices": (${builtins.toJSON devices}${optionalString (! cfg.declarative.overrideDevices) " + .devices"}), - "folders": (${builtins.toJSON folders}${optionalString (! cfg.declarative.overrideFolders) " + .folders"}) - } * ${builtins.toJSON cfg.declarative.extraOptions}') + "devices": (${builtins.toJSON devices}${optionalString (! cfg.overrideDevices) " + .devices"}), + "folders": (${builtins.toJSON folders}${optionalString (! cfg.overrideFolders) " + .folders"}) + } * ${builtins.toJSON cfg.extraOptions}') # send the new config curl -X PUT -d "$new_cfg" ${cfg.guiAddress}/rest/config @@ -68,172 +69,177 @@ in { options = { services.syncthing = { - enable = mkEnableOption '' - Syncthing - the self-hosted open-source alternative - to Dropbox and Bittorrent Sync. Initial interface will be - available on http://127.0.0.1:8384/. - ''; + enable = mkEnableOption + "Syncthing, a self-hosted open-source alternative to Dropbox and Bittorrent Sync"; - declarative = { - cert = mkOption { - type = types.nullOr types.str; - default = null; - description = '' - Path to users cert.pem file, will be copied into Syncthing's - configDir - ''; - }; + cert = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Path to the cert.pem file, which will be copied into Syncthing's + configDir. + ''; + }; - key = mkOption { - type = types.nullOr types.str; - default = null; - description = '' - Path to users key.pem file, will be copied into Syncthing's - configDir - ''; - }; + key = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Path to the key.pem file, which will be copied into Syncthing's + configDir. + ''; + }; - overrideDevices = mkOption { - type = types.bool; - default = true; - description = '' - Whether to delete the devices which are not configured via the - declarative.devices option. - If set to false, devices added via the webinterface will - persist but will have to be deleted manually. - ''; - }; + overrideDevices = mkOption { + type = types.bool; + default = true; + description = '' + Whether to delete the devices which are not configured via the + devices option. + If set to false, devices added via the web + interface will persist and will have to be deleted manually. + ''; + }; - devices = mkOption { - default = {}; - description = '' - Peers/devices which Syncthing should communicate with. - ''; - example = { - bigbox = { - id = "7CFNTQM-IMTJBHJ-3UWRDIU-ZGQJFR6-VCXZ3NB-XUH3KZO-N52ITXR-LAIYUAU"; - addresses = [ "tcp://192.168.0.10:51820" ]; - }; + devices = mkOption { + default = {}; + description = '' + Peers/devices which Syncthing should communicate with. + + Note that you can still add devices manually, but those changes + will be reverted on restart if overrideDevices + is enabled. + ''; + example = { + bigbox = { + id = "7CFNTQM-IMTJBHJ-3UWRDIU-ZGQJFR6-VCXZ3NB-XUH3KZO-N52ITXR-LAIYUAU"; + addresses = [ "tcp://192.168.0.10:51820" ]; }; - type = types.attrsOf (types.submodule ({ name, ... }: { - options = { - - name = mkOption { - type = types.str; - default = name; - description = '' - Name of the device - ''; - }; - - addresses = mkOption { - type = types.listOf types.str; - default = []; - description = '' - The addresses used to connect to the device. - If this is let empty, dynamic configuration is attempted - ''; - }; - - id = mkOption { - type = types.str; - description = '' - The id of the other peer, this is mandatory. It's documented at - https://docs.syncthing.net/dev/device-ids.html - ''; - }; - - introducer = mkOption { - type = types.bool; - default = false; - description = '' - If the device should act as an introducer and be allowed - to add folders on this computer. - ''; - }; + }; + type = types.attrsOf (types.submodule ({ name, ... }: { + options = { + name = mkOption { + type = types.str; + default = name; + description = '' + The name of the device. + ''; }; - })); - }; - overrideFolders = mkOption { - type = types.bool; - default = true; - description = '' - Whether to delete the folders which are not configured via the - declarative.folders option. - If set to false, folders added via the webinterface will persist - but will have to be deleted manually. - ''; - }; + addresses = mkOption { + type = types.listOf types.str; + default = []; + description = '' + The addresses used to connect to the device. + If this is left empty, dynamic configuration is attempted. + ''; + }; - folders = mkOption { - default = {}; - description = '' - Folders which should be shared by Syncthing. - ''; - example = literalExample '' - { - "/home/user/sync" = { - id = "syncme"; - devices = [ "bigbox" ]; - }; - } - ''; - type = types.attrsOf (types.submodule ({ name, ... }: { - options = { + id = mkOption { + type = types.str; + description = '' + The device ID. See . + ''; + }; - enable = mkOption { - type = types.bool; - default = true; - description = '' - share this folder. - This option is useful when you want to define all folders - in one place, but not every machine should share all folders. - ''; - }; + introducer = mkOption { + type = types.bool; + default = false; + description = '' + Whether the device should act as an introducer and be allowed + to add folders on this computer. + See . + ''; + }; - path = mkOption { - type = types.str; - default = name; - description = '' - The path to the folder which should be shared. - ''; - }; + }; + })); + }; - id = mkOption { - type = types.str; - default = name; - description = '' - The id of the folder. Must be the same on all devices. - ''; - }; + overrideFolders = mkOption { + type = types.bool; + default = true; + description = '' + Whether to delete the folders which are not configured via the + folders option. + If set to false, folders added via the web + interface will persist and will have to be deleted manually. + ''; + }; - label = mkOption { - type = types.str; - default = name; - description = '' - The label of the folder. - ''; - }; + folders = mkOption { + default = {}; + description = '' + Folders which should be shared by Syncthing. - devices = mkOption { - type = types.listOf types.str; - default = []; - description = '' - The devices this folder should be shared with. Must be defined - in the declarative.devices attribute. - ''; - }; + Note that you can still add devices manually, but those changes + will be reverted on restart if overrideDevices + is enabled. + ''; + example = literalExample '' + { + "/home/user/sync" = { + id = "syncme"; + devices = [ "bigbox" ]; + }; + } + ''; + type = types.attrsOf (types.submodule ({ name, ... }: { + options = { - versioning = mkOption { - default = null; - description = '' - How to keep changed/deleted files with Syncthing. - There are 4 different types of versioning with different parameters. - See https://docs.syncthing.net/users/versioning.html - ''; - example = [ + enable = mkOption { + type = types.bool; + default = true; + description = '' + Whether to share this folder. + This option is useful when you want to define all folders + in one place, but not every machine should share all folders. + ''; + }; + + path = mkOption { + type = types.str; + default = name; + description = '' + The path to the folder which should be shared. + ''; + }; + + id = mkOption { + type = types.str; + default = name; + description = '' + The ID of the folder. Must be the same on all devices. + ''; + }; + + label = mkOption { + type = types.str; + default = name; + description = '' + The label of the folder. + ''; + }; + + devices = mkOption { + type = types.listOf types.str; + default = []; + description = '' + The devices this folder should be shared with. Each device must + be defined in the devices option. + ''; + }; + + versioning = mkOption { + default = null; + description = '' + How to keep changed/deleted files with Syncthing. + There are 4 different types of versioning with different parameters. + See . + ''; + example = literalExample '' + [ { versioning = { type = "simple"; @@ -259,98 +265,99 @@ in { { versioning = { type = "external"; - params.versionsPath = pkgs.writers.writeBash "backup" '' + params.versionsPath = pkgs.writers.writeBash "backup" ''' folderpath="$1" filepath="$2" rm -rf "$folderpath/$filepath" - ''; + '''; }; } - ]; - type = with types; nullOr (submodule { - options = { - type = mkOption { - type = enum [ "external" "simple" "staggered" "trashcan" ]; - description = '' - Type of versioning. - See https://docs.syncthing.net/users/versioning.html - ''; - }; - params = mkOption { - type = attrsOf (either str path); - description = '' - Parameters for versioning. Structure depends on versioning.type. - See https://docs.syncthing.net/users/versioning.html - ''; - }; + ] + ''; + type = with types; nullOr (submodule { + options = { + type = mkOption { + type = enum [ "external" "simple" "staggered" "trashcan" ]; + description = '' + The type of versioning. + See . + ''; }; - }); - }; - - rescanInterval = mkOption { - type = types.int; - default = 3600; - description = '' - How often the folders should be rescaned for changes. - ''; - }; - - type = mkOption { - type = types.enum [ "sendreceive" "sendonly" "receiveonly" ]; - default = "sendreceive"; - description = '' - Whether to send only changes from this folder, only receive them - or propagate both. - ''; - }; - - watch = mkOption { - type = types.bool; - default = true; - description = '' - Whether the folder should be watched for changes by inotify. - ''; - }; - - watchDelay = mkOption { - type = types.int; - default = 10; - description = '' - The delay after an inotify event is triggered. - ''; - }; - - ignorePerms = mkOption { - type = types.bool; - default = true; - description = '' - Whether to propagate permission changes. - ''; - }; - - ignoreDelete = mkOption { - type = types.bool; - default = false; - description = '' - Whether to delete files in destination. See - upstream's docs. - ''; - }; + params = mkOption { + type = attrsOf (either str path); + description = '' + The parameters for versioning. Structure depends on + versioning.type. + See . + ''; + }; + }; + }); }; - })); - }; - extraOptions = mkOption { - type = types.addCheck (pkgs.formats.json {}).type isAttrs; - default = {}; - description = '' - Extra configuration options for Syncthing. - ''; - example = { - options.localAnnounceEnabled = false; - gui.theme = "black"; + rescanInterval = mkOption { + type = types.int; + default = 3600; + description = '' + How often the folder should be rescanned for changes. + ''; + }; + + type = mkOption { + type = types.enum [ "sendreceive" "sendonly" "receiveonly" ]; + default = "sendreceive"; + description = '' + Whether to only send changes for this folder, only receive them + or both. + ''; + }; + + watch = mkOption { + type = types.bool; + default = true; + description = '' + Whether the folder should be watched for changes by inotify. + ''; + }; + + watchDelay = mkOption { + type = types.int; + default = 10; + description = '' + The delay after an inotify event is triggered. + ''; + }; + + ignorePerms = mkOption { + type = types.bool; + default = true; + description = '' + Whether to ignore permission changes. + ''; + }; + + ignoreDelete = mkOption { + type = types.bool; + default = false; + description = '' + Whether to skip deleting files that are deleted by peers. + See . + ''; + }; }; + })); + }; + + extraOptions = mkOption { + type = types.addCheck (pkgs.formats.json {}).type isAttrs; + default = {}; + description = '' + Extra configuration options for Syncthing. + See . + ''; + example = { + options.localAnnounceEnabled = false; + gui.theme = "black"; }; }; @@ -358,31 +365,35 @@ in { type = types.str; default = "127.0.0.1:8384"; description = '' - Address to serve the GUI. + The address to serve the web interface at. ''; }; systemService = mkOption { type = types.bool; default = true; - description = "Auto launch Syncthing as a system service."; + description = '' + Whether to auto-launch Syncthing as a system service. + ''; }; user = mkOption { type = types.str; default = defaultUser; + example = "yourUser"; description = '' - Syncthing will be run under this user (user will be created if it doesn't exist. - This can be your user name). + The user to run Syncthing as. + By default, a user named ${defaultUser} will be created. ''; }; group = mkOption { type = types.str; - default = defaultUser; + default = defaultGroup; + example = "yourGroup"; description = '' - Syncthing will be run under this group (group will not be created if it doesn't exist. - This can be your user name). + The group to run Syncthing under. + By default, a group named ${defaultGroup} will be created. ''; }; @@ -391,63 +402,67 @@ in { default = null; example = "socks5://address.com:1234"; description = '' - Overwrites all_proxy environment variable for the Syncthing process to - the given value. This is normaly used to let relay client connect - through SOCKS5 proxy server. + Overwrites the all_proxy environment variable for the Syncthing process to + the given value. This is normally used to let Syncthing connect + through a SOCKS5 proxy server. + See . ''; }; dataDir = mkOption { type = types.path; default = "/var/lib/syncthing"; + example = "/home/yourUser"; description = '' - Path where synced directories will exist. + The path where synchronised directories will exist. ''; }; - configDir = mkOption { + configDir = let + cond = versionAtLeast config.system.stateVersion "19.03"; + in mkOption { type = types.path; description = '' - Path where the settings and keys will exist. + The path where the settings and keys will exist. ''; - default = - let - nixos = config.system.stateVersion; - cond = versionAtLeast nixos "19.03"; - in cfg.dataDir + (optionalString cond "/.config/syncthing"); + default = cfg.dataDir + (optionalString cond "/.config/syncthing"); + defaultText = literalExample "dataDir${optionalString cond " + \"/.config/syncthing\""}"; }; openDefaultPorts = mkOption { type = types.bool; default = false; - example = literalExample "true"; + example = true; description = '' - Open the default ports in the firewall: - - TCP 22000 for transfers - - UDP 21027 for discovery - If multiple users are running Syncthing on this machine, you will need to manually open a set of ports for each instance and leave this disabled. - Alternatively, if are running only a single instance on this machine using the default ports, enable this. + Whether to open the default ports in the firewall: TCP 22000 for transfers + and UDP 21027 for discovery. + + If multiple users are running Syncthing on this machine, you will need + to manually open a set of ports for each instance and leave this disabled. + Alternatively, if you are running only a single instance on this machine + using the default ports, enable this. ''; }; package = mkOption { type = types.package; default = pkgs.syncthing; - defaultText = "pkgs.syncthing"; - example = literalExample "pkgs.syncthing"; + defaultText = literalExample "pkgs.syncthing"; description = '' - Syncthing package to use. + The Syncthing package to use. ''; }; }; }; imports = [ - (mkRemovedOptionModule ["services" "syncthing" "useInotify"] '' + (mkRemovedOptionModule [ "services" "syncthing" "useInotify" ] '' This option was removed because Syncthing now has the inotify functionality included under the name "fswatcher". - It can be enabled on a per-folder basis through the webinterface. + It can be enabled on a per-folder basis through the web interface. '') - ]; + ] ++ map (o: + mkRenamedOptionModule [ "services" "syncthing" "declarative" o ] [ "services" "syncthing" o ] + ) [ "cert" "key" "devices" "folders" "overrideDevices" "overrideFolders" "extraOptions"]; ###### implementation @@ -470,8 +485,8 @@ in { }; }; - users.groups = mkIf (cfg.systemService && cfg.group == defaultUser) { - ${defaultUser}.gid = + users.groups = mkIf (cfg.systemService && cfg.group == defaultGroup) { + ${defaultGroup}.gid = config.ids.gids.syncthing; }; @@ -491,14 +506,14 @@ in { RestartForceExitStatus="3 4"; User = cfg.user; Group = cfg.group; - ExecStartPre = mkIf (cfg.declarative.cert != null || cfg.declarative.key != null) + ExecStartPre = mkIf (cfg.cert != null || cfg.key != null) "+${pkgs.writers.writeBash "syncthing-copy-keys" '' install -dm700 -o ${cfg.user} -g ${cfg.group} ${cfg.configDir} - ${optionalString (cfg.declarative.cert != null) '' - install -Dm400 -o ${cfg.user} -g ${cfg.group} ${toString cfg.declarative.cert} ${cfg.configDir}/cert.pem + ${optionalString (cfg.cert != null) '' + install -Dm400 -o ${cfg.user} -g ${cfg.group} ${toString cfg.cert} ${cfg.configDir}/cert.pem ''} - ${optionalString (cfg.declarative.key != null) '' - install -Dm400 -o ${cfg.user} -g ${cfg.group} ${toString cfg.declarative.key} ${cfg.configDir}/key.pem + ${optionalString (cfg.key != null) '' + install -Dm400 -o ${cfg.user} -g ${cfg.group} ${toString cfg.key} ${cfg.configDir}/key.pem ''} ''}" ; @@ -529,7 +544,7 @@ in { }; }; syncthing-init = mkIf ( - cfg.declarative.devices != {} || cfg.declarative.folders != {} || cfg.declarative.extraOptions != {} + cfg.devices != {} || cfg.folders != {} || cfg.extraOptions != {} ) { description = "Syncthing configuration updater"; after = [ "syncthing.service" ]; diff --git a/nixos/tests/syncthing-init.nix b/nixos/tests/syncthing-init.nix index f359f0af1c2..8b60ad7faf0 100644 --- a/nixos/tests/syncthing-init.nix +++ b/nixos/tests/syncthing-init.nix @@ -9,16 +9,14 @@ in { machine = { services.syncthing = { enable = true; - declarative = { - devices.testDevice = { - id = testId; - }; - folders.testFolder = { - path = "/tmp/test"; - devices = [ "testDevice" ]; - }; - extraOptions.gui.user = "guiUser"; + devices.testDevice = { + id = testId; }; + folders.testFolder = { + path = "/tmp/test"; + devices = [ "testDevice" ]; + }; + extraOptions.gui.user = "guiUser"; }; };