lol

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.

+57 -41
+53 -39
nixos/modules/services/networking/syncthing.nix
··· 25 25 folder.enable 26 26 ) cfg.declarative.folders); 27 27 28 - # get the api key by parsing the config.xml 29 - getApiKey = pkgs.writers.writeDash "getAPIKey" '' 30 - ${pkgs.libxml2}/bin/xmllint \ 31 - --xpath 'string(configuration/gui/apikey)'\ 32 - ${cfg.configDir}/config.xml 33 - ''; 34 - 35 28 updateConfig = pkgs.writers.writeDash "merge-syncthing-config" '' 36 29 set -efu 37 - # wait for syncthing port to open 38 - until ${pkgs.curl}/bin/curl -Ss ${cfg.guiAddress} -o /dev/null; do 39 - sleep 1 40 - done 41 30 42 - API_KEY=$(${getApiKey}) 43 - OLD_CFG=$(${pkgs.curl}/bin/curl -Ss \ 44 - -H "X-API-Key: $API_KEY" \ 45 - ${cfg.guiAddress}/rest/system/config) 31 + # get the api key by parsing the config.xml 32 + while 33 + ! api_key=$(${pkgs.libxml2}/bin/xmllint \ 34 + --xpath 'string(configuration/gui/apikey)' \ 35 + ${cfg.configDir}/config.xml) 36 + do sleep 1; done 46 37 47 - # generate the new config by merging with the nixos config options 48 - NEW_CFG=$(echo "$OLD_CFG" | ${pkgs.jq}/bin/jq -s '.[] as $in | $in * { 49 - "devices": (${builtins.toJSON devices}${optionalString (! cfg.declarative.overrideDevices) " + $in.devices"}), 50 - "folders": (${builtins.toJSON folders}${optionalString (! cfg.declarative.overrideFolders) " + $in.folders"}) 51 - }') 38 + curl() { 39 + while 40 + ${pkgs.curl}/bin/curl -Ss -H "X-API-Key: $api_key" \ 41 + --retry 100 --retry-delay 1 --retry-connrefused "$@" 42 + status=$? 43 + [ "$status" -eq 52 ] # retry on empty reply from server 44 + do sleep 1; done 45 + return "$status" 46 + } 52 47 53 - # POST the new config to syncthing 54 - echo "$NEW_CFG" | ${pkgs.curl}/bin/curl -Ss \ 55 - -H "X-API-Key: $API_KEY" \ 56 - ${cfg.guiAddress}/rest/system/config -d @- 48 + # query the old config 49 + old_cfg=$(curl ${cfg.guiAddress}/rest/config) 57 50 58 - # restart syncthing after sending the new config 59 - ${pkgs.curl}/bin/curl -Ss \ 60 - -H "X-API-Key: $API_KEY" \ 61 - -X POST \ 62 - ${cfg.guiAddress}/rest/system/restart 51 + # generate the new config by merging with the NixOS config options 52 + new_cfg=$(echo "$old_cfg" | ${pkgs.jq}/bin/jq -c '. * { 53 + "devices": (${builtins.toJSON devices}${optionalString (! cfg.declarative.overrideDevices) " + .devices"}), 54 + "folders": (${builtins.toJSON folders}${optionalString (! cfg.declarative.overrideFolders) " + .folders"}) 55 + } * ${builtins.toJSON cfg.declarative.extraOptions}') 56 + 57 + # send the new config 58 + curl -X PUT -d "$new_cfg" ${cfg.guiAddress}/rest/config 59 + 60 + # restart Syncthing if required 61 + if curl ${cfg.guiAddress}/rest/config/restart-required | 62 + ${pkgs.jq}/bin/jq -e .requiresRestart > /dev/null; then 63 + curl -X POST ${cfg.guiAddress}/rest/system/restart 64 + fi 63 65 ''; 64 66 in { 65 67 ###### interface ··· 77 79 type = types.nullOr types.str; 78 80 default = null; 79 81 description = '' 80 - Path to users cert.pem file, will be copied into the syncthing's 82 + Path to users cert.pem file, will be copied into Syncthing's 81 83 <literal>configDir</literal> 82 84 ''; 83 85 }; ··· 86 88 type = types.nullOr types.str; 87 89 default = null; 88 90 description = '' 89 - Path to users key.pem file, will be copied into the syncthing's 91 + Path to users key.pem file, will be copied into Syncthing's 90 92 <literal>configDir</literal> 91 93 ''; 92 94 }; ··· 105 107 devices = mkOption { 106 108 default = {}; 107 109 description = '' 108 - Peers/devices which syncthing should communicate with. 110 + Peers/devices which Syncthing should communicate with. 109 111 ''; 110 112 example = { 111 113 bigbox = { ··· 168 170 folders = mkOption { 169 171 default = {}; 170 172 description = '' 171 - folders which should be shared by syncthing. 173 + Folders which should be shared by Syncthing. 172 174 ''; 173 175 example = literalExample '' 174 176 { ··· 227 229 versioning = mkOption { 228 230 default = null; 229 231 description = '' 230 - How to keep changed/deleted files with syncthing. 232 + How to keep changed/deleted files with Syncthing. 231 233 There are 4 different types of versioning with different parameters. 232 234 See https://docs.syncthing.net/users/versioning.html 233 235 ''; ··· 335 337 upstream's docs</link>. 336 338 ''; 337 339 }; 338 - 339 340 }; 340 341 })); 341 342 }; 343 + 344 + extraOptions = mkOption { 345 + type = types.addCheck (pkgs.formats.json {}).type isAttrs; 346 + default = {}; 347 + description = '' 348 + Extra configuration options for Syncthing. 349 + ''; 350 + example = { 351 + options.localAnnounceEnabled = false; 352 + gui.theme = "black"; 353 + }; 354 + }; 342 355 }; 343 356 344 357 guiAddress = mkOption { ··· 378 391 default = null; 379 392 example = "socks5://address.com:1234"; 380 393 description = '' 381 - Overwrites all_proxy environment variable for the syncthing process to 394 + Overwrites all_proxy environment variable for the Syncthing process to 382 395 the given value. This is normaly used to let relay client connect 383 396 through SOCKS5 proxy server. 384 397 ''; ··· 412 425 Open the default ports in the firewall: 413 426 - TCP 22000 for transfers 414 427 - UDP 21027 for discovery 415 - 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. 428 + 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. 416 429 Alternatively, if are running only a single instance on this machine using the default ports, enable this. 417 430 ''; 418 431 }; ··· 431 444 432 445 imports = [ 433 446 (mkRemovedOptionModule ["services" "syncthing" "useInotify"] '' 434 - This option was removed because syncthing now has the inotify functionality included under the name "fswatcher". 447 + This option was removed because Syncthing now has the inotify functionality included under the name "fswatcher". 435 448 It can be enabled on a per-folder basis through the webinterface. 436 449 '') 437 450 ]; ··· 516 529 }; 517 530 }; 518 531 syncthing-init = mkIf ( 519 - cfg.declarative.devices != {} || cfg.declarative.folders != {} 532 + cfg.declarative.devices != {} || cfg.declarative.folders != {} || cfg.declarative.extraOptions != {} 520 533 ) { 534 + description = "Syncthing configuration updater"; 521 535 after = [ "syncthing.service" ]; 522 536 wantedBy = [ "multi-user.target" ]; 523 537
+2
nixos/tests/syncthing-init.nix
··· 17 17 path = "/tmp/test"; 18 18 devices = [ "testDevice" ]; 19 19 }; 20 + extraOptions.gui.user = "guiUser"; 20 21 }; 21 22 }; 22 23 }; ··· 27 28 28 29 assert "testFolder" in config 29 30 assert "${testId}" in config 31 + assert "guiUser" in config 30 32 ''; 31 33 })
+2 -2
nixos/tests/syncthing.nix
··· 25 25 "xmllint --xpath 'string(configuration/gui/apikey)' %s/config.xml" % confdir 26 26 ).strip() 27 27 oldConf = host.succeed( 28 - "curl -Ssf -H 'X-API-Key: %s' 127.0.0.1:8384/rest/system/config" % APIKey 28 + "curl -Ssf -H 'X-API-Key: %s' 127.0.0.1:8384/rest/config" % APIKey 29 29 ) 30 30 conf = json.loads(oldConf) 31 31 conf["devices"].append({"deviceID": deviceID, "id": name}) ··· 39 39 ) 40 40 newConf = json.dumps(conf) 41 41 host.succeed( 42 - "curl -Ssf -H 'X-API-Key: %s' 127.0.0.1:8384/rest/system/config -d %s" 42 + "curl -Ssf -H 'X-API-Key: %s' 127.0.0.1:8384/rest/config -X PUT -d %s" 43 43 % (APIKey, shlex.quote(newConf)) 44 44 ) 45 45