Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

syncthing: expand declarative config to Darwin #6104

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
93 changes: 62 additions & 31 deletions modules/services/syncthing.nix
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ let

isUnixGui = (builtins.substring 0 1 cfg.guiAddress) == "/";

# syncthing's configuration directory (see https://docs.syncthing.net/users/config.html)
syncthing_dir = if pkgs.stdenv.isDarwin then
"$HOME/Library/Application Support/Syncthing"
else
"\${XDG_STATE_HOME:-$HOME/.local/state}/syncthing";

# Syncthing supports serving the GUI over Unix sockets. If that happens, the
# API is served over the Unix socket as well. This function returns the correct
# curl arguments for the address portion of the curl command for both network
Expand Down Expand Up @@ -44,27 +50,46 @@ let
cat = "${pkgs.coreutils}/bin/cat";
curl = "${pkgs.curl}/bin/curl";
install = "${pkgs.coreutils}/bin/install";
mktemp = "${pkgs.coreutils}/bin/mktemp";
syncthing = "${pkgs.syncthing}/bin/syncthing";

updateConfig = pkgs.writers.writeBash "merge-syncthing-config" (''
set -efu

# be careful not to leak secrets in the filesystem or in process listings
umask 0077
copyKeys = pkgs.writers.writeBash "syncthing-copy-keys" ''
${install} -dm700 "${syncthing_dir}"
${optionalString (cfg.cert != null) ''
${install} -Dm400 ${toString cfg.cert} "${syncthing_dir}/cert.pem"
''}
${optionalString (cfg.key != null) ''
${install} -Dm400 ${toString cfg.key} "${syncthing_dir}/key.pem"
''}
'';

curlShellFunction = ''
# systemd sets and creates RUNTIME_DIRECTORY on Linux
# on Darwin, we create it manually via mktemp
RUNTIME_DIRECTORY="''${RUNTIME_DIRECTORY:=$(${mktemp} -d)}"

curl() {
# get the api key by parsing the config.xml
while
! ${pkgs.libxml2}/bin/xmllint \
--xpath 'string(configuration/gui/apikey)' \
''${XDG_STATE_HOME:-$HOME/.local/state}/syncthing/config.xml \
"${syncthing_dir}/config.xml" \
>"$RUNTIME_DIRECTORY/api_key"
do ${sleep} 1; done
(${printf} "X-API-Key: "; ${cat} "$RUNTIME_DIRECTORY/api_key") >"$RUNTIME_DIRECTORY/headers"
${curl} -sSLk -H "@$RUNTIME_DIRECTORY/headers" \
--retry 1000 --retry-delay 1 --retry-all-errors \
"$@"
}
'';

updateConfig = pkgs.writers.writeBash "merge-syncthing-config" (''
set -efu

# be careful not to leak secrets in the filesystem or in process listings
umask 0077

${curlShellFunction}
'' +

/* Syncthing's rest API for the folders and devices is almost identical.
Expand Down Expand Up @@ -173,7 +198,6 @@ in {
services.syncthing = {
enable = mkEnableOption ''
Syncthing, a self-hosted open-source alternative to Dropbox and Bittorrent Sync.
Further declarative configuration options only supported on Linux devices.
'';

cert = mkOption {
Expand Down Expand Up @@ -618,22 +642,8 @@ in {
};

Service = {
ExecStartPre = mkIf (cfg.cert != null || cfg.key != null) "+${
pkgs.writers.writeBash "syncthing-copy-keys" ''
syncthing_dir="''${XDG_STATE_HOME:-$HOME/.local/state}/syncthing"
${install} -dm700 "$syncthing_dir"
${optionalString (cfg.cert != null) ''
${install} -Dm400 ${
toString cfg.cert
} "$syncthing_dir/cert.pem"
''}
${optionalString (cfg.key != null) ''
${install} -Dm400 ${
toString cfg.key
} "$syncthing_dir/key.pem"
''}
''
}";
ExecStartPre =
mkIf (cfg.cert != null || cfg.key != null) "+${copyKeys}";
ExecStart = escapeShellArgs syncthingArgs;
Restart = "on-failure";
SuccessExitStatus = [ 3 4 ];
Expand Down Expand Up @@ -672,15 +682,36 @@ in {
};
};

launchd.agents.syncthing = {
enable = true;
config = {
ProgramArguments = syncthingArgs;
KeepAlive = {
Crashed = true;
SuccessfulExit = false;
launchd.agents = let
# agent `syncthing` uses `${syncthing_dir}/${watch_file}` to notify agent `syncthing-init`
watch_file = ".launchd_update_config";
in {
syncthing = {
enable = true;
config = {
ProgramArguments = [
"${pkgs.writers.writeBash "syncthing-wrapper" ''
${copyKeys} # simulate systemd's `syncthing-init.Service.ExecStartPre`
touch "${syncthing_dir}/${watch_file}" # notify syncthing-init agent
exec ${lib.escapeShellArgs syncthingArgs}
''}"
];
KeepAlive = {
Crashed = true;
SuccessfulExit = false;
};
ProcessType = "Background";
};
};

syncthing-init = {
enable = true;
config = {
ProgramArguments = [ "${updateConfig}" ];
WatchPaths = [
"${config.home.homeDirectory}/Library/Application Support/Syncthing/${watch_file}"
];
};
ProcessType = "Background";
};
};
})
Expand Down