From 92b3d5c56e4ce5ab4f86fa1eca85b2348e3ed160 Mon Sep 17 00:00:00 2001 From: Tim Van Baak Date: Wed, 7 Feb 2024 05:38:03 +0000 Subject: [PATCH] syncthings: allow for multiple option instances --- modules/syncthings.nix | 836 +++++++++++++++++++++-------------------- 1 file changed, 422 insertions(+), 414 deletions(-) diff --git a/modules/syncthings.nix b/modules/syncthings.nix index 391864e..c49d452 100644 --- a/modules/syncthings.nix +++ b/modules/syncthings.nix @@ -3,8 +3,8 @@ with lib; let - cfg = config.services.syncthing; - opt = options.services.syncthing; + cfg = config.services.syncthings; + opt = options.services.syncthings; defaultUser = "syncthing"; defaultGroup = defaultUser; settingsFormat = pkgs.formats.json { }; @@ -145,460 +145,468 @@ let in { ###### interface options = { - services.syncthing = { - - enable = mkEnableOption - (lib.mdDoc "Syncthing, a self-hosted open-source alternative to Dropbox and Bittorrent Sync"); - - cert = mkOption { - type = types.nullOr types.str; - default = null; - description = mdDoc '' - Path to the `cert.pem` file, which will be copied into Syncthing's - [configDir](#opt-services.syncthing.configDir). - ''; - }; - - key = mkOption { - type = types.nullOr types.str; - default = null; - description = mdDoc '' - Path to the `key.pem` file, which will be copied into Syncthing's - [configDir](#opt-services.syncthing.configDir). - ''; - }; - - overrideDevices = mkOption { - type = types.bool; - default = true; - description = mdDoc '' - Whether to delete the devices which are not configured via the - [devices](#opt-services.syncthing.settings.devices) option. - If set to `false`, devices added via the web - interface will persist and will have to be deleted manually. - ''; - }; - - overrideFolders = mkOption { - type = types.bool; - default = true; - description = mdDoc '' - Whether to delete the folders which are not configured via the - [folders](#opt-services.syncthing.settings.folders) option. - If set to `false`, folders added via the web - interface will persist and will have to be deleted manually. - ''; - }; - - settings = mkOption { - type = types.submodule { - freeformType = settingsFormat.type; + services.syncthings = { + instances = mkOption { + description = mdDoc "Syncthing instance definitions"; + default = {}; + type = types.attrsOf (types.submodule { options = { - # global options - options = mkOption { - default = {}; + + enable = mkEnableOption + (lib.mdDoc "Syncthing, a self-hosted open-source alternative to Dropbox and Bittorrent Sync"); + + cert = mkOption { + type = types.nullOr types.str; + default = null; description = mdDoc '' - The options element contains all other global configuration options + Path to the `cert.pem` file, which will be copied into Syncthing's + [configDir](#opt-services.syncthing.configDir). ''; - type = types.submodule ({ name, ... }: { - freeformType = settingsFormat.type; - options = { - localAnnounceEnabled = mkOption { - type = types.nullOr types.bool; - default = null; - description = lib.mdDoc '' - Whether to send announcements to the local LAN, also use such announcements to find other devices. - ''; - }; - - localAnnouncePort = mkOption { - type = types.nullOr types.int; - default = null; - description = lib.mdDoc '' - The port on which to listen and send IPv4 broadcast announcements to. - ''; - }; - - relaysEnabled = mkOption { - type = types.nullOr types.bool; - default = null; - description = lib.mdDoc '' - When true, relays will be connected to and potentially used for device to device connections. - ''; - }; - - urAccepted = mkOption { - type = types.nullOr types.int; - default = null; - description = lib.mdDoc '' - Whether the user has accepted to submit anonymous usage data. - The default, 0, mean the user has not made a choice, and Syncthing will ask at some point in the future. - "-1" means no, a number above zero means that that version of usage reporting has been accepted. - ''; - }; - - limitBandwidthInLan = mkOption { - type = types.nullOr types.bool; - default = null; - description = lib.mdDoc '' - Whether to apply bandwidth limits to devices in the same broadcast domain as the local device. - ''; - }; - - maxFolderConcurrency = mkOption { - type = types.nullOr types.int; - default = null; - description = lib.mdDoc '' - This option controls how many folders may concurrently be in I/O-intensive operations such as syncing or scanning. - The mechanism is described in detail in a [separate chapter](https://docs.syncthing.net/advanced/option-max-concurrency.html). - ''; - }; - }; - }); }; - # device settings - devices = mkOption { - default = {}; + key = mkOption { + type = types.nullOr types.str; + default = null; description = mdDoc '' - 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](#opt-services.syncthing.overrideDevices) - is enabled. + Path to the `key.pem` file, which will be copied into Syncthing's + [configDir](#opt-services.syncthing.configDir). ''; - example = { - bigbox = { - id = "7CFNTQM-IMTJBHJ-3UWRDIU-ZGQJFR6-VCXZ3NB-XUH3KZO-N52ITXR-LAIYUAU"; - addresses = [ "tcp://192.168.0.10:51820" ]; - }; - }; - type = types.attrsOf (types.submodule ({ name, ... }: { - freeformType = settingsFormat.type; - options = { - - name = mkOption { - type = types.str; - default = name; - description = lib.mdDoc '' - The name of the device. - ''; - }; - - id = mkOption { - type = types.str; - description = mdDoc '' - The device ID. See . - ''; - }; - - autoAcceptFolders = mkOption { - type = types.bool; - default = false; - description = mdDoc '' - Automatically create or share folders that this device advertises at the default path. - See . - ''; - }; - - }; - })); }; - # folder settings - folders = mkOption { - default = {}; + overrideDevices = mkOption { + type = types.bool; + default = true; description = mdDoc '' - Folders which should be shared by Syncthing. + Whether to delete the devices which are not configured via the + [devices](#opt-services.syncthing.settings.devices) option. + If set to `false`, devices added via the web + interface will persist and will have to be deleted manually. + ''; + }; - Note that you can still add folders manually, but those changes - will be reverted on restart if [overrideFolders](#opt-services.syncthing.overrideFolders) - is enabled. + overrideFolders = mkOption { + type = types.bool; + default = true; + description = mdDoc '' + Whether to delete the folders which are not configured via the + [folders](#opt-services.syncthing.settings.folders) option. + If set to `false`, folders added via the web + interface will persist and will have to be deleted manually. ''; - example = literalExpression '' - { - "/home/user/sync" = { - id = "syncme"; - devices = [ "bigbox" ]; - }; - } - ''; - type = types.attrsOf (types.submodule ({ name, ... }: { + }; + + settings = mkOption { + type = types.submodule { freeformType = settingsFormat.type; options = { - - enable = mkOption { - type = types.bool; - default = true; - description = lib.mdDoc '' - 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 { - # TODO for release 23.05: allow relative paths again and set - # working directory to cfg.dataDir - type = types.str // { - check = x: types.str.check x && (substring 0 1 x == "/" || substring 0 2 x == "~/"); - description = types.str.description + " starting with / or ~/"; - }; - default = name; - description = lib.mdDoc '' - The path to the folder which should be shared. - Only absolute paths (starting with `/`) and paths relative to - the [user](#opt-services.syncthing.user)'s home directory - (starting with `~/`) are allowed. - ''; - }; - - id = mkOption { - type = types.str; - default = name; - description = lib.mdDoc '' - The ID of the folder. Must be the same on all devices. - ''; - }; - - label = mkOption { - type = types.str; - default = name; - description = lib.mdDoc '' - The label of the folder. - ''; - }; - - devices = mkOption { - type = types.listOf types.str; - default = []; + # global options + options = mkOption { + default = {}; description = mdDoc '' - The devices this folder should be shared with. Each device must - be defined in the [devices](#opt-services.syncthing.settings.devices) option. + The options element contains all other global configuration options ''; - }; - - versioning = mkOption { - default = null; - description = mdDoc '' - How to keep changed/deleted files with Syncthing. - There are 4 different types of versioning with different parameters. - See . - ''; - example = literalExpression '' - [ - { - versioning = { - type = "simple"; - params.keep = "10"; - }; - } - { - versioning = { - type = "trashcan"; - params.cleanoutDays = "1000"; - }; - } - { - versioning = { - type = "staggered"; - fsPath = "/syncthing/backup"; - params = { - cleanInterval = "3600"; - maxAge = "31536000"; - }; - }; - } - { - versioning = { - type = "external"; - params.versionsPath = pkgs.writers.writeBash "backup" ''' - folderpath="$1" - filepath="$2" - rm -rf "$folderpath/$filepath" - '''; - }; - } - ] - ''; - type = with types; nullOr (submodule { + type = types.submodule ({ name, ... }: { freeformType = settingsFormat.type; options = { - type = mkOption { - type = enum [ "external" "simple" "staggered" "trashcan" ]; - description = mdDoc '' - The type of versioning. - See . + localAnnounceEnabled = mkOption { + type = types.nullOr types.bool; + default = null; + description = lib.mdDoc '' + Whether to send announcements to the local LAN, also use such announcements to find other devices. + ''; + }; + + localAnnouncePort = mkOption { + type = types.nullOr types.int; + default = null; + description = lib.mdDoc '' + The port on which to listen and send IPv4 broadcast announcements to. + ''; + }; + + relaysEnabled = mkOption { + type = types.nullOr types.bool; + default = null; + description = lib.mdDoc '' + When true, relays will be connected to and potentially used for device to device connections. + ''; + }; + + urAccepted = mkOption { + type = types.nullOr types.int; + default = null; + description = lib.mdDoc '' + Whether the user has accepted to submit anonymous usage data. + The default, 0, mean the user has not made a choice, and Syncthing will ask at some point in the future. + "-1" means no, a number above zero means that that version of usage reporting has been accepted. + ''; + }; + + limitBandwidthInLan = mkOption { + type = types.nullOr types.bool; + default = null; + description = lib.mdDoc '' + Whether to apply bandwidth limits to devices in the same broadcast domain as the local device. + ''; + }; + + maxFolderConcurrency = mkOption { + type = types.nullOr types.int; + default = null; + description = lib.mdDoc '' + This option controls how many folders may concurrently be in I/O-intensive operations such as syncing or scanning. + The mechanism is described in detail in a [separate chapter](https://docs.syncthing.net/advanced/option-max-concurrency.html). ''; }; }; }); }; - copyOwnershipFromParent = mkOption { - type = types.bool; - default = false; + # device settings + devices = mkOption { + default = {}; description = mdDoc '' - On Unix systems, tries to copy file/folder ownership from the parent directory (the directory it’s located in). - Requires running Syncthing as a privileged user, or granting it additional capabilities (e.g. CAP_CHOWN on Linux). - ''; - }; - }; - })); - }; + Peers/devices which Syncthing should communicate with. - }; - }; - default = {}; - description = mdDoc '' - Extra configuration options for Syncthing. - See . - Note that this attribute set does not exactly match the documented - xml format. Instead, this is the format of the json rest api. There - are slight differences. For example, this xml: - ```xml - - default - 1 - - ``` - corresponds to the json: - ```json - { - options: { - listenAddresses = [ - "default" - ]; - minHomeDiskFree = { - unit = "%"; - value = 1; + Note that you can still add devices manually, but those changes + will be reverted on restart if [overrideDevices](#opt-services.syncthing.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, ... }: { + freeformType = settingsFormat.type; + options = { + + name = mkOption { + type = types.str; + default = name; + description = lib.mdDoc '' + The name of the device. + ''; + }; + + id = mkOption { + type = types.str; + description = mdDoc '' + The device ID. See . + ''; + }; + + autoAcceptFolders = mkOption { + type = types.bool; + default = false; + description = mdDoc '' + Automatically create or share folders that this device advertises at the default path. + See . + ''; + }; + + }; + })); + }; + + # folder settings + folders = mkOption { + default = {}; + description = mdDoc '' + Folders which should be shared by Syncthing. + + Note that you can still add folders manually, but those changes + will be reverted on restart if [overrideFolders](#opt-services.syncthing.overrideFolders) + is enabled. + ''; + example = literalExpression '' + { + "/home/user/sync" = { + id = "syncme"; + devices = [ "bigbox" ]; + }; + } + ''; + type = types.attrsOf (types.submodule ({ name, ... }: { + freeformType = settingsFormat.type; + options = { + + enable = mkOption { + type = types.bool; + default = true; + description = lib.mdDoc '' + 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 { + # TODO for release 23.05: allow relative paths again and set + # working directory to cfg.dataDir + type = types.str // { + check = x: types.str.check x && (substring 0 1 x == "/" || substring 0 2 x == "~/"); + description = types.str.description + " starting with / or ~/"; + }; + default = name; + description = lib.mdDoc '' + The path to the folder which should be shared. + Only absolute paths (starting with `/`) and paths relative to + the [user](#opt-services.syncthing.user)'s home directory + (starting with `~/`) are allowed. + ''; + }; + + id = mkOption { + type = types.str; + default = name; + description = lib.mdDoc '' + The ID of the folder. Must be the same on all devices. + ''; + }; + + label = mkOption { + type = types.str; + default = name; + description = lib.mdDoc '' + The label of the folder. + ''; + }; + + devices = mkOption { + type = types.listOf types.str; + default = []; + description = mdDoc '' + The devices this folder should be shared with. Each device must + be defined in the [devices](#opt-services.syncthing.settings.devices) option. + ''; + }; + + versioning = mkOption { + default = null; + description = mdDoc '' + How to keep changed/deleted files with Syncthing. + There are 4 different types of versioning with different parameters. + See . + ''; + example = literalExpression '' + [ + { + versioning = { + type = "simple"; + params.keep = "10"; + }; + } + { + versioning = { + type = "trashcan"; + params.cleanoutDays = "1000"; + }; + } + { + versioning = { + type = "staggered"; + fsPath = "/syncthing/backup"; + params = { + cleanInterval = "3600"; + maxAge = "31536000"; + }; + }; + } + { + versioning = { + type = "external"; + params.versionsPath = pkgs.writers.writeBash "backup" ''' + folderpath="$1" + filepath="$2" + rm -rf "$folderpath/$filepath" + '''; + }; + } + ] + ''; + type = with types; nullOr (submodule { + freeformType = settingsFormat.type; + options = { + type = mkOption { + type = enum [ "external" "simple" "staggered" "trashcan" ]; + description = mdDoc '' + The type of versioning. + See . + ''; + }; + }; + }); + }; + + copyOwnershipFromParent = mkOption { + type = types.bool; + default = false; + description = mdDoc '' + On Unix systems, tries to copy file/folder ownership from the parent directory (the directory it’s located in). + Requires running Syncthing as a privileged user, or granting it additional capabilities (e.g. CAP_CHOWN on Linux). + ''; + }; + }; + })); + }; + + }; + }; + default = {}; + description = mdDoc '' + Extra configuration options for Syncthing. + See . + Note that this attribute set does not exactly match the documented + xml format. Instead, this is the format of the json rest api. There + are slight differences. For example, this xml: + ```xml + + default + 1 + + ``` + corresponds to the json: + ```json + { + options: { + listenAddresses = [ + "default" + ]; + minHomeDiskFree = { + unit = "%"; + value = 1; + }; + }; + } + ``` + ''; + example = { + options.localAnnounceEnabled = false; + gui.theme = "black"; }; }; - } - ``` - ''; - example = { - options.localAnnounceEnabled = false; - gui.theme = "black"; - }; - }; - guiAddress = mkOption { - type = types.str; - default = "127.0.0.1:8384"; - description = lib.mdDoc '' - The address to serve the web interface at. - ''; - }; + guiAddress = mkOption { + type = types.str; + default = "127.0.0.1:8384"; + description = lib.mdDoc '' + The address to serve the web interface at. + ''; + }; - systemService = mkOption { - type = types.bool; - default = true; - description = lib.mdDoc '' - Whether to auto-launch Syncthing as a system service. - ''; - }; + systemService = mkOption { + type = types.bool; + default = true; + description = lib.mdDoc '' + Whether to auto-launch Syncthing as a system service. + ''; + }; - user = mkOption { - type = types.str; - default = defaultUser; - example = "yourUser"; - description = mdDoc '' - The user to run Syncthing as. - By default, a user named `${defaultUser}` will be created whose home - directory is [dataDir](#opt-services.syncthing.dataDir). - ''; - }; + user = mkOption { + type = types.str; + default = defaultUser; + example = "yourUser"; + description = mdDoc '' + The user to run Syncthing as. + By default, a user named `${defaultUser}` will be created whose home + directory is [dataDir](#opt-services.syncthing.dataDir). + ''; + }; - group = mkOption { - type = types.str; - default = defaultGroup; - example = "yourGroup"; - description = mdDoc '' - The group to run Syncthing under. - By default, a group named `${defaultGroup}` will be created. - ''; - }; + group = mkOption { + type = types.str; + default = defaultGroup; + example = "yourGroup"; + description = mdDoc '' + The group to run Syncthing under. + By default, a group named `${defaultGroup}` will be created. + ''; + }; - all_proxy = mkOption { - type = with types; nullOr str; - default = null; - example = "socks5://address.com:1234"; - description = mdDoc '' - 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 . - ''; - }; + all_proxy = mkOption { + type = with types; nullOr str; + default = null; + example = "socks5://address.com:1234"; + description = mdDoc '' + 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 = lib.mdDoc '' - The path where synchronised directories will exist. - ''; - }; + dataDir = mkOption { + type = types.path; + default = "/var/lib/syncthing"; + example = "/home/yourUser"; + description = lib.mdDoc '' + The path where synchronised directories will exist. + ''; + }; - configDir = let - cond = versionAtLeast config.system.stateVersion "19.03"; - in mkOption { - type = types.path; - description = lib.mdDoc '' - The path where the settings and keys will exist. - ''; - default = cfg.dataDir + optionalString cond "/.config/syncthing"; - defaultText = literalMD '' - * if `stateVersion >= 19.03`: + configDir = let + cond = versionAtLeast config.system.stateVersion "19.03"; + in mkOption { + type = types.path; + description = lib.mdDoc '' + The path where the settings and keys will exist. + ''; + default = cfg.dataDir + optionalString cond "/.config/syncthing"; + defaultText = literalMD '' + * if `stateVersion >= 19.03`: - config.${opt.dataDir} + "/.config/syncthing" - * otherwise: + config.${opt.dataDir} + "/.config/syncthing" + * otherwise: - config.${opt.dataDir} - ''; - }; + config.${opt.dataDir} + ''; + }; - databaseDir = mkOption { - type = types.path; - description = lib.mdDoc '' - The directory containing the database and logs. - ''; - default = cfg.configDir; - defaultText = literalExpression "config.${opt.configDir}"; - }; + databaseDir = mkOption { + type = types.path; + description = lib.mdDoc '' + The directory containing the database and logs. + ''; + default = cfg.configDir; + defaultText = literalExpression "config.${opt.configDir}"; + }; - extraFlags = mkOption { - type = types.listOf types.str; - default = []; - example = [ "--reset-deltas" ]; - description = lib.mdDoc '' - Extra flags passed to the syncthing command in the service definition. - ''; - }; + extraFlags = mkOption { + type = types.listOf types.str; + default = []; + example = [ "--reset-deltas" ]; + description = lib.mdDoc '' + Extra flags passed to the syncthing command in the service definition. + ''; + }; - openDefaultPorts = mkOption { - type = types.bool; - default = false; - example = true; - description = lib.mdDoc '' - Whether to open the default ports in the firewall: TCP/UDP 22000 for transfers - and UDP 21027 for discovery. + openDefaultPorts = mkOption { + type = types.bool; + default = false; + example = true; + description = lib.mdDoc '' + Whether to open the default ports in the firewall: TCP/UDP 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. - ''; - }; + 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 = literalExpression "pkgs.syncthing"; - description = lib.mdDoc '' - The Syncthing package to use. - ''; + package = mkOption { + type = types.package; + default = pkgs.syncthing; + defaultText = literalExpression "pkgs.syncthing"; + description = lib.mdDoc '' + The Syncthing package to use. + ''; + }; + }; + }); }; }; };