summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
authorlassulus <lassulus@lassul.us>2019-04-21 23:05:07 +0200
committerLassulus <github@lassul.us>2019-05-20 17:56:17 +0900
commita3e7e1bbc8e4fea44fa2bdaac74a2371f1989a82 (patch)
tree5b34c297478601674f633d075bd98b3979fafd15 /nixos
parent9d8c27e4f4b129f910a992d77b1c740c0a783f08 (diff)
downloadnixpkgs-a3e7e1bbc8e4fea44fa2bdaac74a2371f1989a82.tar
nixpkgs-a3e7e1bbc8e4fea44fa2bdaac74a2371f1989a82.tar.gz
nixpkgs-a3e7e1bbc8e4fea44fa2bdaac74a2371f1989a82.tar.bz2
nixpkgs-a3e7e1bbc8e4fea44fa2bdaac74a2371f1989a82.tar.lz
nixpkgs-a3e7e1bbc8e4fea44fa2bdaac74a2371f1989a82.tar.xz
nixpkgs-a3e7e1bbc8e4fea44fa2bdaac74a2371f1989a82.tar.zst
nixpkgs-a3e7e1bbc8e4fea44fa2bdaac74a2371f1989a82.zip
nixos/syncthing: add options for declarative device/folder config
Diffstat (limited to 'nixos')
-rw-r--r--nixos/modules/services/networking/syncthing.nix260
-rw-r--r--nixos/tests/all-tests.nix1
-rw-r--r--nixos/tests/syncthing-init.nix30
3 files changed, 291 insertions, 0 deletions
diff --git a/nixos/modules/services/networking/syncthing.nix b/nixos/modules/services/networking/syncthing.nix
index 114a64dfb17..89dae7bb3f8 100644
--- a/nixos/modules/services/networking/syncthing.nix
+++ b/nixos/modules/services/networking/syncthing.nix
@@ -5,6 +5,57 @@ with lib;
 let
   cfg = config.services.syncthing;
   defaultUser = "syncthing";
+
+  devices = mapAttrsToList (name: device: {
+    deviceID = device.id;
+    inherit (device) name addresses introducer;
+  }) cfg.declarative.devices;
+
+  folders = mapAttrsToList ( _: folder: {
+    inherit (folder) path id label type;
+    devices = map (device: { deviceId = cfg.declarative.devices.${device}.id; }) folder.devices;
+    rescanIntervalS = folder.rescanInterval;
+    fsWatcherEnabled = folder.watch;
+    fsWatcherDelayS = folder.watchDelay;
+    ignorePerms = folder.ignorePerms;
+  }) 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
   options = {
@@ -16,6 +67,187 @@ in {
         available on http://127.0.0.1:8384/.
       '';
 
+      declarative = {
+        cert = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          description = ''
+            Path to users cert.pem file, will be copied into the syncthing's
+            <literal>configDir</literal>
+          '';
+        };
+
+        key = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          description = ''
+            Path to users key.pem file, will be copied into the syncthing's
+            <literal>configDir</literal>
+          '';
+        };
+
+        overrideDevices = mkOption {
+          type = types.bool;
+          default = true;
+          description = ''
+            Whether to delete the devices which are not configured via the
+            <literal>declarative.devices</literal> option.
+            If set to false, devices added via the webinterface will
+            persist but will have to be deleted manually.
+          '';
+        };
+
+        devices = mkOption {
+          default = {};
+          description = ''
+            Peers/devices which syncthing should communicate with.
+          '';
+          example = [
+            {
+              name = "bigbox";
+              id = "7CFNTQM-IMTJBHJ-3UWRDIU-ZGQJFR6-VCXZ3NB-XUH3KZO-N52ITXR-LAIYUAU";
+              addresses = [ "tcp://192.168.0.10:51820" ];
+            }
+          ];
+          type = types.attrsOf (types.submodule ({ config, ... }: {
+            options = {
+
+              name = mkOption {
+                type = types.str;
+                default = config._module.args.name;
+                description = ''
+                  Name of the device
+                '';
+              };
+
+              addresses = mkOption {
+                type = types.listOf types.str;
+                default = [];
+                description = ''
+                  The addresses used to connect to the device.
+                  If this is let empty, dynamic configuration is attempted
+                '';
+              };
+
+              id = mkOption {
+                type = types.str;
+                description = ''
+                  The id of the other peer, this is mandatory. It's documented at
+                  https://docs.syncthing.net/dev/device-ids.html
+                '';
+              };
+
+              introducer = mkOption {
+                type = types.bool;
+                default = false;
+                description = ''
+                  If the device should act as an introducer and be allowed
+                  to add folders on this computer.
+                '';
+              };
+
+            };
+          }));
+        };
+
+        overrideFolders = mkOption {
+          type = types.bool;
+          default = true;
+          description = ''
+            Whether to delete the folders which are not configured via the
+            <literal>declarative.folders</literal> option.
+            If set to false, folders added via the webinterface will persist
+            but will have to be deleted manually.
+          '';
+        };
+
+        folders = mkOption {
+          default = {};
+          description = ''
+            folders which should be shared by syncthing.
+          '';
+          type = types.attrsOf (types.submodule ({ config, ... }: {
+            options = {
+
+              path = mkOption {
+                type = types.str;
+                default = config._module.args.name;
+                description = ''
+                  The path to the folder which should be shared.
+                '';
+              };
+
+              id = mkOption {
+                type = types.str;
+                default = config._module.args.name;
+                description = ''
+                  The id of the folder. Must be the same on all devices.
+                '';
+              };
+
+              label = mkOption {
+                type = types.str;
+                default = config._module.args.name;
+                description = ''
+                  The label of the folder.
+                '';
+              };
+
+              devices = mkOption {
+                type = types.listOf types.str;
+                default = [];
+                description = ''
+                  The devices this folder should be shared with. Must be defined
+                  in the <literal>declarative.devices</literal> attribute.
+                '';
+              };
+
+              rescanInterval = mkOption {
+                type = types.int;
+                default = 3600;
+                description = ''
+                  How often the folders should be rescaned for changes.
+                '';
+              };
+
+              type = mkOption {
+                type = types.enum [ "sendreceive" "sendonly" "receiveonly" ];
+                default = "sendreceive";
+                description = ''
+                  Whether to send only changes from this folder, only receive them
+                  or propagate both.
+                '';
+              };
+
+              watch = mkOption {
+                type = types.bool;
+                default = true;
+                description = ''
+                  Whether the folder should be watched for changes by inotify.
+                '';
+              };
+
+              watchDelay = mkOption {
+                type = types.int;
+                default = 10;
+                description = ''
+                  The delay after an inotify event is triggered.
+                '';
+              };
+
+              ignorePerms = mkOption {
+                type = types.bool;
+                default = true;
+                description = ''
+                  Whether to propagate permission changes.
+                '';
+              };
+
+            };
+          }));
+        };
+      };
+
       guiAddress = mkOption {
         type = types.str;
         default = "127.0.0.1:8384";
@@ -151,6 +383,23 @@ in {
           RestartForceExitStatus="3 4";
           User = cfg.user;
           Group = cfg.group;
+          ExecStartPre = mkIf (cfg.declarative.cert != null || cfg.declarative.key != null)
+            "+${pkgs.writers.writeBash "syncthing-copy-keys" ''
+              mkdir -p ${cfg.configDir}
+              chown ${cfg.user}:${cfg.group} ${cfg.configDir}
+              chmod 700 ${cfg.configDir}
+              ${optionalString (cfg.declarative.cert != null) ''
+                cp ${toString cfg.declarative.cert} ${cfg.configDir}/cert.pem
+                chown ${cfg.user}:${cfg.group} ${cfg.configDir}/cert.pem
+                chmod 400 ${cfg.configDir}/cert.pem
+              ''}
+              ${optionalString (cfg.declarative.key != null) ''
+                cp ${toString cfg.declarative.key} ${cfg.configDir}/key.pem
+                chown ${cfg.user}:${cfg.group} ${cfg.configDir}/key.pem
+                chmod 400 ${cfg.configDir}/key.pem
+              ''}
+            ''}"
+          ;
           ExecStart = ''
             ${cfg.package}/bin/syncthing \
               -no-browser \
@@ -159,6 +408,17 @@ in {
           '';
         };
       };
+      syncthing-init = {
+        after = [ "syncthing.service" ];
+        wantedBy = [ "multi-user.target" ];
+
+        serviceConfig = {
+          User = cfg.user;
+          RemainAfterExit = true;
+          Type = "oneshot";
+          ExecStart = updateConfig;
+        };
+      };
 
       syncthing-resume = {
         wantedBy = [ "suspend.target" ];
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index c31d9d78da6..5be7c4292e5 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -231,6 +231,7 @@ in
   strongswan-swanctl = handleTest ./strongswan-swanctl.nix {};
   sudo = handleTest ./sudo.nix {};
   switchTest = handleTest ./switch-test.nix {};
+  syncthing-init = handleTest ./syncthing-init.nix {};
   syncthing-relay = handleTest ./syncthing-relay.nix {};
   systemd = handleTest ./systemd.nix {};
   systemd-confinement = handleTest ./systemd-confinement.nix {};
diff --git a/nixos/tests/syncthing-init.nix b/nixos/tests/syncthing-init.nix
new file mode 100644
index 00000000000..811a466ff94
--- /dev/null
+++ b/nixos/tests/syncthing-init.nix
@@ -0,0 +1,30 @@
+import ./make-test.nix ({ lib, pkgs, ... }: let
+
+  testId = "7CFNTQM-IMTJBHJ-3UWRDIU-ZGQJFR6-VCXZ3NB-XUH3KZO-N52ITXR-LAIYUAU";
+
+in {
+  name = "syncthing-init";
+  meta.maintainers = with pkgs.stdenv.lib.maintainers; [ lassulus ];
+
+  machine = {
+    services.syncthing = {
+      enable = true;
+      declarative = {
+        devices.testDevice = {
+          id = testId;
+        };
+        folders.testFolder = {
+          path = "/tmp/test";
+          devices = [ "testDevice" ];
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    $machine->waitForUnit("syncthing-init.service");
+    $machine->succeed("cat /var/lib/syncthing/config.xml") =~ /${testId}/ or die;
+    $machine->succeed("cat /var/lib/syncthing/config.xml") =~ /testFolder/ or die;
+  '';
+})
+