Skip to content

Commit

Permalink
nixos/syncthing: Use API to merge / override configurations
Browse files Browse the repository at this point in the history
If one sets either of `override{Device,folder}s` to false, the jq `*`
operator doesn't merge well the devices and folders, creating duplicate
IDs for folders as observed in #230146. This PR makes the script iterate
via a Bash for loop the devices and folders IDs and merges the keys
using upstream's `curl -X PATCH` support for single objects.

Hence this commit fixes #230146.
  • Loading branch information
doronbehar committed May 17, 2023
1 parent a4b47b6 commit 44036d4
Showing 1 changed file with 60 additions and 13 deletions.
73 changes: 60 additions & 13 deletions nixos/modules/services/networking/syncthing.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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 //
throwIf (folder?rescanInterval || folder?watch || folder?watchDelay) ''
Expand All @@ -25,12 +26,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
Expand All @@ -49,21 +51,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
'';
Expand Down

0 comments on commit 44036d4

Please sign in to comment.