nixos/syncthing: add declarative.extraOptions

Allows setting arbitrary config options through the REST API.

Also switches to the [new](https://docs.syncthing.net/rest/config.html)
config endpoints.

Changed files
+57 -41
nixos
modules
services
networking
tests
+53 -39
nixos/modules/services/networking/syncthing.nix
···
folder.enable
) cfg.declarative.folders);
-
# get the api key by parsing the config.xml
-
getApiKey = pkgs.writers.writeDash "getAPIKey" ''
-
${pkgs.libxml2}/bin/xmllint \
-
--xpath 'string(configuration/gui/apikey)'\
-
${cfg.configDir}/config.xml
-
'';
-
updateConfig = pkgs.writers.writeDash "merge-syncthing-config" ''
set -efu
-
# wait for syncthing port to open
-
until ${pkgs.curl}/bin/curl -Ss ${cfg.guiAddress} -o /dev/null; do
-
sleep 1
-
done
-
API_KEY=$(${getApiKey})
-
OLD_CFG=$(${pkgs.curl}/bin/curl -Ss \
-
-H "X-API-Key: $API_KEY" \
-
${cfg.guiAddress}/rest/system/config)
-
# generate the new config by merging with the nixos config options
-
NEW_CFG=$(echo "$OLD_CFG" | ${pkgs.jq}/bin/jq -s '.[] as $in | $in * {
-
"devices": (${builtins.toJSON devices}${optionalString (! cfg.declarative.overrideDevices) " + $in.devices"}),
-
"folders": (${builtins.toJSON folders}${optionalString (! cfg.declarative.overrideFolders) " + $in.folders"})
-
}')
-
# POST the new config to syncthing
-
echo "$NEW_CFG" | ${pkgs.curl}/bin/curl -Ss \
-
-H "X-API-Key: $API_KEY" \
-
${cfg.guiAddress}/rest/system/config -d @-
-
# restart syncthing after sending the new config
-
${pkgs.curl}/bin/curl -Ss \
-
-H "X-API-Key: $API_KEY" \
-
-X POST \
-
${cfg.guiAddress}/rest/system/restart
'';
in {
###### interface
···
type = types.nullOr types.str;
default = null;
description = ''
-
Path to users cert.pem file, will be copied into the syncthing's
<literal>configDir</literal>
'';
};
···
type = types.nullOr types.str;
default = null;
description = ''
-
Path to users key.pem file, will be copied into the syncthing's
<literal>configDir</literal>
'';
};
···
devices = mkOption {
default = {};
description = ''
-
Peers/devices which syncthing should communicate with.
'';
example = {
bigbox = {
···
folders = mkOption {
default = {};
description = ''
-
folders which should be shared by syncthing.
'';
example = literalExample ''
{
···
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
'';
···
upstream's docs</link>.
'';
};
-
};
}));
};
};
guiAddress = mkOption {
···
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.
'';
···
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.
'';
};
···
imports = [
(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.
'')
];
···
};
};
syncthing-init = mkIf (
-
cfg.declarative.devices != {} || cfg.declarative.folders != {}
) {
after = [ "syncthing.service" ];
wantedBy = [ "multi-user.target" ];
···
folder.enable
) cfg.declarative.folders);
updateConfig = pkgs.writers.writeDash "merge-syncthing-config" ''
set -efu
+
# get the api key by parsing the config.xml
+
while
+
! api_key=$(${pkgs.libxml2}/bin/xmllint \
+
--xpath 'string(configuration/gui/apikey)' \
+
${cfg.configDir}/config.xml)
+
do sleep 1; done
+
curl() {
+
while
+
${pkgs.curl}/bin/curl -Ss -H "X-API-Key: $api_key" \
+
--retry 100 --retry-delay 1 --retry-connrefused "$@"
+
status=$?
+
[ "$status" -eq 52 ] # retry on empty reply from server
+
do sleep 1; done
+
return "$status"
+
}
+
# query the old config
+
old_cfg=$(curl ${cfg.guiAddress}/rest/config)
+
# 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}')
+
+
# send the new config
+
curl -X PUT -d "$new_cfg" ${cfg.guiAddress}/rest/config
+
+
# restart Syncthing if required
+
if curl ${cfg.guiAddress}/rest/config/restart-required |
+
${pkgs.jq}/bin/jq -e .requiresRestart > /dev/null; then
+
curl -X POST ${cfg.guiAddress}/rest/system/restart
+
fi
'';
in {
###### interface
···
type = types.nullOr types.str;
default = null;
description = ''
+
Path to users cert.pem file, will be copied into Syncthing's
<literal>configDir</literal>
'';
};
···
type = types.nullOr types.str;
default = null;
description = ''
+
Path to users key.pem file, will be copied into Syncthing's
<literal>configDir</literal>
'';
};
···
devices = mkOption {
default = {};
description = ''
+
Peers/devices which Syncthing should communicate with.
'';
example = {
bigbox = {
···
folders = mkOption {
default = {};
description = ''
+
Folders which should be shared by Syncthing.
'';
example = literalExample ''
{
···
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
'';
···
upstream's docs</link>.
'';
};
};
}));
};
+
+
extraOptions = mkOption {
+
type = types.addCheck (pkgs.formats.json {}).type isAttrs;
+
default = {};
+
description = ''
+
Extra configuration options for Syncthing.
+
'';
+
example = {
+
options.localAnnounceEnabled = false;
+
gui.theme = "black";
+
};
+
};
};
guiAddress = mkOption {
···
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.
'';
···
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.
'';
};
···
imports = [
(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.
'')
];
···
};
};
syncthing-init = mkIf (
+
cfg.declarative.devices != {} || cfg.declarative.folders != {} || cfg.declarative.extraOptions != {}
) {
+
description = "Syncthing configuration updater";
after = [ "syncthing.service" ];
wantedBy = [ "multi-user.target" ];
+2
nixos/tests/syncthing-init.nix
···
path = "/tmp/test";
devices = [ "testDevice" ];
};
};
};
};
···
assert "testFolder" in config
assert "${testId}" in config
'';
})
···
path = "/tmp/test";
devices = [ "testDevice" ];
};
+
extraOptions.gui.user = "guiUser";
};
};
};
···
assert "testFolder" in config
assert "${testId}" in config
+
assert "guiUser" in config
'';
})
+2 -2
nixos/tests/syncthing.nix
···
"xmllint --xpath 'string(configuration/gui/apikey)' %s/config.xml" % confdir
).strip()
oldConf = host.succeed(
-
"curl -Ssf -H 'X-API-Key: %s' 127.0.0.1:8384/rest/system/config" % APIKey
)
conf = json.loads(oldConf)
conf["devices"].append({"deviceID": deviceID, "id": name})
···
)
newConf = json.dumps(conf)
host.succeed(
-
"curl -Ssf -H 'X-API-Key: %s' 127.0.0.1:8384/rest/system/config -d %s"
% (APIKey, shlex.quote(newConf))
)
···
"xmllint --xpath 'string(configuration/gui/apikey)' %s/config.xml" % confdir
).strip()
oldConf = host.succeed(
+
"curl -Ssf -H 'X-API-Key: %s' 127.0.0.1:8384/rest/config" % APIKey
)
conf = json.loads(oldConf)
conf["devices"].append({"deviceID": deviceID, "id": name})
···
)
newConf = json.dumps(conf)
host.succeed(
+
"curl -Ssf -H 'X-API-Key: %s' 127.0.0.1:8384/rest/config -X PUT -d %s"
% (APIKey, shlex.quote(newConf))
)