Change:
Since , I'm getting long timeouts on my deployments with: in No warning, no deprecation, nothing, this is bad. My SyncThing configuration: Steps to reproduce the behavior: to this PR breakage Clear instructions on why this is happening or fixes or warnings. for the maintainers for the people actively taking part in those recent changes
Can you check if the following also fails for you? I just updated my system using and I did experience the issue after the first , which also updated and triggered a restart of . However I tried to play a bit with the dash script that runs, and then I reverted it back and didn't fail (I ran again) - it seems to be because was already running. Did you try to simply restart ? Or test if the issue persists after reboots?
Your command fails for me too. I didn't restart anything yet, I didn't reboot, this issue needs to be solved without any manual intervention, otherwise this is badly broken. It has to upgrade gracefully or it has to provide proper migration instructions in the release notes (which are not there, neither in the Discourse post for unstable breaking changes).
this was not meant to be a strongly breaking change. Putting a message in would have been nice, but I haven't thought about it when I merged the PR. We hoped the module options renaming would be enough. Please forgive us for the inconvenience. Now regarding your issue: tries to do it's best when switching generations, but sometimes big rebuilds cannot be switched fully smoothly. Please try to reboot, or at least restart . My intuition tells me that didn't run when 's API was ready - although the systemd service itself was running at that point. If my guess is correct, your issue might not be strictly due to , but it somehow appeared only now - possibly due to other changes done between your previous NixOS generations - or simply due to the nature of the particular generation switch you made now.
weird, seems like raito has no declarative stuff configured so there should be no syncthing-init service anyway, since we only configure it if settings are set. but maybe that fails due to some default values making the set not-empty. I will take a look tomorrow to fix this to work as expected. Yeah the problem is always, that people only report their errors if after we merge them because no one chims into open PRs to check if it breaks their workflow :) But yeah, sorry for the breakage. but I'm certain we will fix those minor issues in time.
Please provide enough notice for everyone, this issue is quite hairy. Unfortunately, those are all wrong assumptions I think, I do not have a system syncthing service, so this is meaningless. I need to start the user service and then the syncthing-init will still fail, relying on a system service. This is a massive breakage for people having in their configuration… No matter what I do I cannot get syncthing-init to start.
I think here, proper testing coverage would have uncovered the system service issue ;) and the missing declarative options. :) But I am not maintainer of that subsystem, more a user.
I guess the quickfix would be to set syncthing-init not if systemService = false is set. I will prepare a PR for that
I'm a bit confused - what's the point of setting , , and (in fact, what's the point of using the NixOS module at all) if you're not using the systemd service nor the declarative configuration? Just to get syncthing in PATH and open the firewall? EDIT: and the upstream systemd units, i guess
I also wonder: how come configuration hasn't failed before? did you rely upon in any way? Is this merely an issue of a systemd service you don't need that now is enabled and fails?
So my take on this is, since there was neither folders, devices or anything else set, the old check just skipped creating the service, so raito never had the syncthing-init service until we merged this PR, which played with the mkIf logic and now it's enabled by default and breaking things.
If indeed this is the case, we need to find out why in Raito's case.
well the default value for settings (checked in the repl): so that was changed, also the check got broken with
I probably never relied on that.
I always had a non-declarative SyncThing configuration from the start.
I thought it was creating a user systemd service, but you are right by reading the code, it is not the case. I think it must be some mixup with Home-Manager.
I still don't understand why fails when . What if someone wishes to set but still set some - I don't think we should change the logic in I do think this logic should documented though, such that users like will be able to disable if it causes them issues they don't want to debug.
How did you check that ? Do you think it is even possible to truely set ? I'd like to play with that too. I have proposed an alternative fix to this issue at - touching only the documentation for the option instructing how to completely disable .
ah I just put raitos config in a file and ran . To get settings to be {} on default we would need to remove all the default values in the nested types, since the nested default values override the outer ones.
Branch-off is tomorrow night, we really need a fix here :) — were you able to find a convenient solution? (I still suffer from like 10 minutes long restart of home-manager due to this…)
To be more specific, and explain this for others, I created the following : I deleted all lines in the definitions: And while , I ran: And in the REPL I typed: Which adds the functions to the repl's scope. Then I typed: This seems problematic to me. Do you think this can be solved? EDIT: Yes, with :
we'd appreciate your review on or on ? did you try to simply set ? In my PR () I suggest to simply document this.
I vote we revert for now as it's not clear how to resolve this without breaking anyone's setup and that PR's changes seem unnecessary anyway (simply adding s to existing options should be fine). Arguably my "fix" in was also not well thought out. Assuming you mean , that will just get merged with the other settings and be a no-op.
sadly thats not the case, with the settings we mirror how the API expects values and not everything we can set is under folders, devices or options. before that people could set folders twice (once through folders and once through extraSettings) which could lead to weird behaviour
Ran into this, the service times-out after 1min. I also use , my goal is to get NixOS to open the firewall, but manage the service via HM. My config is: Could we make also disable ?
I feel like you should probably not enable the service if you only want to open ports. At least I definitely don't think that's the intended use case for .
Agreed it's not ideal, but the HM module has no easy way to configure the ports and I'd rather do this than hardcode the default ports myself. What do you think of changing to instead of ?
Sounds good to me. is the place to add this.. Then what is the intended usecase for ? Perhaps the documentation for it could be improved a bit..
should reference a target, not a service. If anything we should then have a that pulls in everything which in turn has As for systemService, I'll expand on that later.

Discussion:
opt = options.services.syncthing; defaultUser = "syncthing"; defaultGroup = defaultUser; <ins> settingsFormat = pkgs.formats.json { }; cleanedConfig = converge (filterAttrsRecursive (_: v: v != null && v != {})) cfg.settings; </ins> <del> devices = mapAttrsToList (name: device: { </del> <ins> devices = mapAttrsToList (_: device: device // { </ins> deviceID = device.id; <del> inherit (device) name addresses introducer autoAcceptFolders; }) cfg.devices; folders = mapAttrsToList ( _: folder: { inherit (folder) path id label type; devices = map (device: { deviceId = cfg.devices.${device}.id; }) folder.devices; rescanIntervalS = folder.rescanInterval; fsWatcherEnabled = folder.watch; fsWatcherDelayS = folder.watchDelay; ignorePerms = folder.ignorePerms; ignoreDelete = folder.ignoreDelete; versioning = folder.versioning; }) (filterAttrs ( _: folder: </del> <ins> }) cfg.settings.devices; folders = mapAttrsToList (_: folder: folder // throwIf (folder?rescanInterval || folder?watch || folder?watchDelay) '' The options services.syncthing.settings.folders.<name>.{rescanInterval,watch,watchDelay} were removed. Please use, respectively, {rescanIntervalS,fsWatcherEnabled,fsWatcherDelayS} instead. '' { devices = map (device: if builtins.isString device then { deviceId = cfg.settings.devices.${device}.id; } else device ) folder.devices; }) (filterAttrs (_: folder: </ins> folder.enable <del> ) cfg.folders); </del> <ins> ) cfg.settings.folders); </ins> updateConfig = pkgs.writers.writeDash "merge-syncthing-config" '' set -efu

old_cfg=$(curl ${cfg.guiAddress}/rest/config) # generate the new config by merging with the NixOS config options <del> new_cfg=$(printf '%sn' "$old_cfg" | ${pkgs.jq}/bin/jq -c '. * { "devices": ('${escapeShellArg (builtins.toJSON devices)}'${optionalString (cfg.devices == {} || ! cfg.overrideDevices) " + .devices"}), "folders": ('${escapeShellArg (builtins.toJSON folders)}'${optionalString (cfg.folders == {} || ! cfg.overrideFolders) " + .folders"}) } * '${escapeShellArg (builtins.toJSON cfg.extraOptions)}) </del> <ins> new_cfg=$(printf '%sn' "$old_cfg" | ${pkgs.jq}/bin/jq -c ${escapeShellArg ''. * ${builtins.toJSON cleanedConfig} * { "devices": ('${escapeShellArg (builtins.toJSON devices)}'${optionalString (cfg.settings.devices == {} || ! cfg.overrideDevices) " + .devices"}), "folders": ('${escapeShellArg (builtins.toJSON folders)}'${optionalString (cfg.settings.folders == {} || ! cfg.overrideFolders) " + .folders"}) }''}) </ins> # send the new config curl -X PUT -d "$new_cfg" ${cfg.guiAddress}/rest/config

default = true; description = mdDoc '' Whether to delete the devices which are not configured via the <del> [devices](#opt-services.syncthing.devices) option. </del> <ins> [devices](#opt-services.syncthing.settings.devices) option. </ins> If set to `false`, devices added via the web interface will persist and will have to be deleted manually. ''; }; <del> devices = mkOption { default = {}; 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. ''; 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 = lib.mdDoc '' The name of the device. ''; }; addresses = mkOption { type = types.listOf types.str; default = []; description = lib.mdDoc '' The addresses used to connect to the device. If this is left empty, dynamic configuration is attempted. ''; }; id = mkOption { type = types.str; description = mdDoc '' The device ID. See <https://docs.syncthing.net/dev/device-ids.html>. ''; }; introducer = mkOption { type = types.bool; default = false; description = mdDoc '' Whether the device should act as an introducer and be allowed to add folders on this computer. See <https://docs.syncthing.net/users/introducer.html>. ''; }; autoAcceptFolders = mkOption { type = types.bool; default = false; description = mdDoc '' Automatically create or share folders that this device advertises at the default path. See <https://docs.syncthing.net/users/config.html?highlight=autoaccept#config-file-format>. ''; }; }; })); }; </del> overrideFolders = mkOption { type = types.bool; default = true; description = mdDoc '' Whether to delete the folders which are not configured via the <del> [folders](#opt-services.syncthing.folders) option. </del> <ins> [folders](#opt-services.syncthing.settings.folders) option. </ins> If set to `false`, folders added via the web interface will persist and will have to be deleted manually. ''; }; <del> 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, ... }: { </del> <ins> settings = mkOption { type = types.submodule { freeformType = settingsFormat.type; </ins> options = { <del> 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. </del> <ins> # global options options = mkOption { default = {}; description = mdDoc '' The options element contains all other global configuration options </ins> ''; <del> }; </del> <ins> 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. ''; }; </ins> <del> 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. ''; }; </del> <ins> localAnnouncePort = mkOption { type = types.nullOr types.int; default = null; description = lib.mdDoc '' The port on which to listen and send IPv4 broadcast announcements to. ''; }; </ins> <del> id = mkOption { type = types.str; default = name; description = lib.mdDoc '' The ID of the folder. Must be the same on all devices. ''; }; </del> <ins> 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. ''; }; </ins> <del> label = mkOption { type = types.str; default = name; description = lib.mdDoc '' The label of the folder. ''; </del> <ins> 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). ''; }; }; }); </ins> }; <ins> # device settings </ins> devices = mkOption { <del> type = types.listOf types.str; default = []; </del> <ins> default = {}; </ins> description = mdDoc '' <del> The devices this folder should be shared with. Each device must be defined in the [devices](#opt-services.syncthing.devices) option. ''; }; </del> <ins> Peers/devices which Syncthing should communicate with. </ins> <del> 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 <https://docs.syncthing.net/users/versioning.html>. ''; 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" '''; }; } ] </del> <ins> Note that you can still add devices manually, but those changes will be reverted on restart if [overrideDevices](#opt-services.syncthing.overrideDevices) is enabled. </ins> ''; <del> type = with types; nullOr (submodule { </del> <ins> 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; </ins> options = { <del> type = mkOption { type = enum [ "external" "simple" "staggered" "trashcan" ]; description = mdDoc '' The type of versioning. See <https://docs.syncthing.net/users/versioning.html>. </del> <ins> name = mkOption { type = types.str; default = name; description = lib.mdDoc '' The name of the device. </ins> ''; }; <del> fsPath = mkOption { default = ""; type = either str path; </del> <ins> id = mkOption { type = types.str; </ins> description = mdDoc '' <del> Path to the versioning folder. See <https://docs.syncthing.net/users/versioning.html>. </del> <ins> The device ID. See <https://docs.syncthing.net/dev/device-ids.html>. </ins> ''; }; <del> params = mkOption { type = attrsOf (either str path); </del> <ins> autoAcceptFolders = mkOption { type = types.bool; default = false; </ins> description = mdDoc '' <del> The parameters for versioning. Structure depends on [versioning.type](#opt-services.syncthing.folders._name_.versioning.type). See <https://docs.syncthing.net/users/versioning.html>. </del> <ins> Automatically create or share folders that this device advertises at the default path. See <https://docs.syncthing.net/users/config.html?highlight=autoaccept#config-file-format>. </ins> ''; }; }; <del> }); </del> <ins> })); </ins> }; <del> rescanInterval = mkOption { type = types.int; default = 3600; description = lib.mdDoc '' How often the folder should be rescanned for changes. ''; }; </del> <ins> # folder settings folders = mkOption { default = {}; description = mdDoc '' Folders which should be shared by Syncthing. </ins> <del> type = mkOption { type = types.enum [ "sendreceive" "sendonly" "receiveonly" "receiveencrypted" ]; default = "sendreceive"; description = lib.mdDoc '' Whether to only send changes for this folder, only receive them or both. `receiveencrypted` can be used for untrusted devices. See <https://docs.syncthing.net/users/untrusted.html> for reference. </del> <ins> Note that you can still add folders manually, but those changes will be reverted on restart if [overrideFolders](#opt-services.syncthing.overrideFolders) is enabled. </ins> ''; <del> }; watch = mkOption { type = types.bool; default = true; description = lib.mdDoc '' Whether the folder should be watched for changes by inotify. </del> <ins> example = literalExpression '' { "/home/user/sync" = { id = "syncme"; devices = [ "bigbox" ]; }; } </ins> ''; <del> }; </del> <ins> type = types.attrsOf (types.submodule ({ name, ... }: { freeformType = settingsFormat.type; options = { </ins> <del> watchDelay = mkOption { type = types.int; default = 10; description = lib.mdDoc '' The delay after an inotify event is triggered. ''; }; </del> <ins> 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. ''; }; </ins> <del> ignorePerms = mkOption { type = types.bool; default = true; description = lib.mdDoc '' Whether to ignore permission changes. ''; }; </del> <ins> 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. ''; }; </ins> <del> ignoreDelete = mkOption { type = types.bool; default = false; description = mdDoc '' Whether to skip deleting files that are deleted by peers. See <https://docs.syncthing.net/advanced/folder-ignoredelete.html>. ''; </del> <ins> 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 <https://docs.syncthing.net/users/versioning.html>. ''; 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 <https://docs.syncthing.net/users/versioning.html>. ''; }; }; }); }; 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). ''; }; }; })); </ins> }; <del> }; })); }; extraOptions = mkOption { type = types.addCheck (pkgs.formats.json {}).type isAttrs; </del> <ins> }; }; </ins> default = {}; description = mdDoc '' Extra configuration options for Syncthing.

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 web interface. '') <ins> (mkRenamedOptionModule [ "services" "syncthing" "extraOptions" ] [ "services" "syncthing" "settings" ]) (mkRenamedOptionModule [ "services" "syncthing" "folders" ] [ "services" "syncthing" "settings" "folders" ]) (mkRenamedOptionModule [ "services" "syncthing" "devices" ] [ "services" "syncthing" "settings" "devices" ]) (mkRenamedOptionModule [ "services" "syncthing" "options" ] [ "services" "syncthing" "settings" "options" ]) </ins> ] ++ map (o: mkRenamedOptionModule [ "services" "syncthing" "declarative" o ] [ "services" "syncthing" o ] ) [ "cert" "key" "devices" "folders" "overrideDevices" "overrideFolders" "extraOptions"];

]; }; }; <del> syncthing-init = mkIf ( cfg.devices != {} || cfg.folders != {} || cfg.extraOptions != {} ) { </del> <ins> syncthing-init = mkIf (cleanedConfig != {}) { </ins> description = "Syncthing configuration updater"; requisite = [ "syncthing.service" ]; after = [ "syncthing.service" ];

switchTest = handleTest ./switch-test.nix {}; sympa = handleTest ./sympa.nix {}; syncthing = handleTest ./syncthing.nix {}; <ins> syncthing-no-settings = handleTest ./syncthing-no-settings.nix {}; </ins> syncthing-init = handleTest ./syncthing-init.nix {}; syncthing-relay = handleTest ./syncthing-relay.nix {}; systemd = handleTest ./systemd.nix {};

nodes.machine = { services.syncthing = { enable = true; <del> devices.${testName} = { </del> <ins> settings.devices.testDevice = { </ins> id = testId; }; <del> folders.testFolder = { </del> <ins> settings.folders.testFolder = { </ins> path = "/tmp/test"; <del> devices = [ testName ]; </del> <ins> devices = [ "testDevice" ]; </ins> }; <del> extraOptions.gui.user = "guiUser"; </del> <ins> settings.gui.user = "guiUser"; </ins> }; };

<ins> import ./make-test-python.nix ({ lib, pkgs, ... }: { name = "syncthing"; meta.maintainers = with pkgs.lib.maintainers; [ chkno ]; nodes = { a = { environment.systemPackages = with pkgs; [ curl libxml2 syncthing ]; services.syncthing = { enable = true; }; }; }; # Test that indeed a syncthing-init.service systemd service is not created. # testScript = /* python */ '' a.succeed("systemctl list-unit-files | awk '$1 == "syncthing-init.service" {exit 1;}'") ''; }) </ins>