diff --git a/nixos/modules/services/networking/syncthing.nix b/nixos/modules/services/networking/syncthing.nix index b5ebda6da045fa6..93c975b7a5c0cb6 100644 --- a/nixos/modules/services/networking/syncthing.nix +++ b/nixos/modules/services/networking/syncthing.nix @@ -12,6 +12,7 @@ let devices = mapAttrsToList (_: device: device // { deviceID = device.id; }) cfg.settings.devices; + devicesIDs = mapAttrsToList (name: device: device.id) cfg.settings.devices; folders = mapAttrsToList (_: folder: folder // { devices = map (device: @@ -21,12 +22,13 @@ let device ) folder.devices; }) cfg.settings.folders; + foldersIDs = mapAttrsToList (name: folder: folder.id) cfg.settings.folders; + jq = "${pkgs.jq}/bin/jq"; updateConfig = pkgs.writers.writeDash "merge-syncthing-config" '' set -efu # be careful not to leak secrets in the filesystem or in process listings - umask 0077 # get the api key by parsing the config.xml @@ -45,21 +47,66 @@ let "$@" } - # query the old config - old_cfg=$(curl ${cfg.guiAddress}/rest/config) - - # generate the new config by merging with the NixOS config options - new_cfg=$(printf '%s\n' "$old_cfg" | ${pkgs.jq}/bin/jq -c ${escapeShellArg ''. * ${builtins.toJSON cfg.settings} * { - "devices": (${builtins.toJSON devices}${optionalString (cfg.settings.devices == {} || ! cfg.overrideDevices) " + .devices"}), - "folders": (${builtins.toJSON folders}${optionalString (cfg.settings.folders == {} || ! cfg.overrideFolders) " + .folders"}) - }''}) - - # send the new config - curl -X PUT -d "$new_cfg" ${cfg.guiAddress}/rest/config + ${/* Syncthing's rest API for the folders and devices is identical. Hence we + iterate the cfg.settings.{devices,folder} Nix variables using the + conf_type which is either "devices" or "folders": */ + lib.concatStringsSep "\n" (map (conf_type: let + new_conf_IDs = (concatStringsSep " " { + devices = devicesIDs; + folders = foldersIDs; + }.${conf_type}); + override = { + devices = cfg.overrideDevices; + folders = cfg.overrideFolders; + }.${conf_type}; + conf = { + inherit devices folders; + }.${conf_type}; + # The API's id naming is slightly different for devices and folders + GET_IdAttrName = { + devices = "deviceID"; + folders = "id"; + }.${conf_type}; + # All URLs and curl commands are based on: https://docs.syncthing.net/rest/config.html + baseAddress = "${cfg.guiAddress}/rest/config/${conf_type}"; + in '' + # We iterate the IDs list, and run a curl -X POST command for each, that + # should update that device/folder only. + IFS=$'\n'; for id in ${new_conf_IDs}; do + new_cfg="$(echo ${escapeShellArg (builtins.toJSON conf)} | ${jq} '.[] | select(.${GET_IdAttrName} == "'$id'")')" + # Quoting https://docs.syncthing.net/rest/config.html: > PUT takes an + # array and POST a single object. In both cases if a given folder/device + # already exists, it’s replaced, otherwise a new one is added. + curl -d "$new_cfg" -X POST ${baseAddress} + done + ${optionalString override /* + If we need to override devices/folders, we iterate all currently configured + IDs, via another `curl -X GET`, and we delete all IDs that are not part of + the Nix configured list of IDs + */ '' + old_conf_${conf_type}_ids="$(curl -X GET ${baseAddress} | ${jq} --raw-output '.[].${GET_IdAttrName}')" + IFS=$'\n'; for id in ''${old_conf_${conf_type}_ids}; do + if echo ${new_conf_IDs} | grep -q $id; then + continue + else + curl -X DELETE ${baseAddress}/$id + fi + done + ''} + '') ["devices" "folders"])} + ${/* Now we update the subOption "options", "gui" and "ldap" one by one, in + order to not override the folders and devices we handled in the previous + Nix Loop. + */ lib.concatStringsSep "\n" (map (subOption: + optionalString (builtins.hasAttr subOption cfg.settings) '' + curl -X PUT -d ${escapeShellArg (builtins.toJSON cfg.settings.${subOption})} \ + ${cfg.guiAddress}/rest/config/${subOption} + '') [ "options" "gui" "ldap" ] + )} # restart Syncthing if required if curl ${cfg.guiAddress}/rest/config/restart-required | - ${pkgs.jq}/bin/jq -e .requiresRestart > /dev/null; then + ${jq} -e .requiresRestart > /dev/null; then curl -X POST ${cfg.guiAddress}/rest/system/restart fi '';