|
|
|
@ -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" ]; |
|
|
|
|