This commit is contained in:
Christoph Schmatzler
2025-08-14 09:09:56 +02:00
parent c1fcf33afa
commit 2926d7412b
7 changed files with 377 additions and 364 deletions

24
flake.lock generated
View File

@@ -100,11 +100,11 @@
"nixpkgs": "nixpkgs" "nixpkgs": "nixpkgs"
}, },
"locked": { "locked": {
"lastModified": 1755107032, "lastModified": 1755121891,
"narHash": "sha256-ckb/RX9rJ/FslBA3K4hYAXgVW/7JdQ50Z+28XZT96zg=", "narHash": "sha256-UtYkukiGnPRJ5rpd4W/wFVrLMh8fqtNkqHTPgHEtrqU=",
"owner": "nix-community", "owner": "nix-community",
"repo": "home-manager", "repo": "home-manager",
"rev": "4b6dd06c6a92308c06da5e0e55f2c505237725c9", "rev": "279ca5addcdcfa31ac852b3ecb39fc372684f426",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -132,11 +132,11 @@
"homebrew-cask": { "homebrew-cask": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1755104931, "lastModified": 1755147939,
"narHash": "sha256-jtXcymAnYH/hCvEGaUlt5vpFwqx00/r5Wly9UCqy7vQ=", "narHash": "sha256-PD93pCq6q6xtWxtFwK9WCugELrcy50Om7/VLgpV0raM=",
"owner": "homebrew", "owner": "homebrew",
"repo": "homebrew-cask", "repo": "homebrew-cask",
"rev": "53dc289ce38d2561d853b482d0f03914a7f2f985", "rev": "5fd00a85f58743b2ac654bfbdda762b8e7d4fbcb",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -148,11 +148,11 @@
"homebrew-core": { "homebrew-core": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1755104716, "lastModified": 1755152577,
"narHash": "sha256-6jp9InEQfaVJR4kSdwb0+D6603DanSUEFF3uYYPIQVM=", "narHash": "sha256-ix1DIoJEn4V0dOErK12AkIL/iPHtGmGR+/HjyNLwnXQ=",
"owner": "homebrew", "owner": "homebrew",
"repo": "homebrew-core", "repo": "homebrew-core",
"rev": "8aa4cd22422bcb03de07c08c24773257d928e988", "rev": "b3da0366f58c2f89d8c0d1cabdc7cd5bdc732313",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -240,11 +240,11 @@
}, },
"nixpkgs_2": { "nixpkgs_2": {
"locked": { "locked": {
"lastModified": 1755109924, "lastModified": 1755154419,
"narHash": "sha256-2xroOWuRFMLvqknLQJ8gmpdXuvg9t5qDUJ3KQKq5/GE=", "narHash": "sha256-m8tJUveXe6y73A2wvXRomSC5WwBsNfLgZI3qteAAqyU=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "b7f46264dd0a1891d52c9a8b919c2eebbc527638", "rev": "1a58620315b9d6d3c8110ec35ee46bbacb2fe633",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@@ -43,11 +43,10 @@
]; ];
flake.darwinConfigurations = inputs.nixpkgs.lib.genAttrs darwinHosts ( flake.darwinConfigurations = inputs.nixpkgs.lib.genAttrs darwinHosts (
hostname: hostname: let
let syncthingOverlay = import ./overlays/syncthing-darwin.nix;
syncthingOverlay = import ./overlays/syncthing-darwin.nix; syncthingModule = (syncthingOverlay null {}).darwinSyncthingModule;
syncthingModule = (syncthingOverlay null {}).darwinSyncthingModule; in
in
inputs.darwin.lib.darwinSystem { inputs.darwin.lib.darwinSystem {
system = "aarch64-darwin"; system = "aarch64-darwin";
specialArgs = specialArgs =
@@ -61,8 +60,8 @@
syncthingModule syncthingModule
{ {
nixpkgs.overlays = [ syncthingOverlay ]; nixpkgs.overlays = [syncthingOverlay];
nix-homebrew = { nix-homebrew = {
inherit user; inherit user;
enable = true; enable = true;

View File

@@ -1,7 +1,4 @@
{ {user, ...}: {
user,
...
}: {
imports = [ imports = [
../shared.nix ../shared.nix
]; ];

View File

@@ -65,7 +65,6 @@
}; };
}; };
services.postgresql = { services.postgresql = {
enable = true; enable = true;
package = pkgs.postgresql_17; package = pkgs.postgresql_17;

View File

@@ -27,4 +27,4 @@
}; };
}; };
}; };
} }

View File

@@ -1,10 +1,14 @@
{ pkgs, lib, ... }:
{ {
services.tailscale = { pkgs,
enable = true; lib,
} // lib.optionalAttrs pkgs.stdenv.isLinux { ...
useRoutingFeatures = "server"; }: {
openFirewall = true; services.tailscale =
}; {
enable = true;
}
// lib.optionalAttrs pkgs.stdenv.isLinux {
openFirewall = true;
useRoutingFeatures = "server";
};
} }

View File

@@ -1,108 +1,119 @@
final: prev: { final: prev: {
darwinSyncthingModule = { config, lib, pkgs, ... }: darwinSyncthingModule = {
with lib; config,
let lib,
cfg = config.services.syncthing; pkgs,
defaultUser = "syncthing"; ...
defaultGroup = defaultUser; }:
settingsFormat = pkgs.formats.json { }; with lib; let
cleanedConfig = converge (filterAttrsRecursive (_: v: v != null && v != { })) cfg.settings; cfg = config.services.syncthing;
defaultUser = "syncthing";
defaultGroup = defaultUser;
settingsFormat = pkgs.formats.json {};
cleanedConfig = converge (filterAttrsRecursive (_: v: v != null && v != {})) cfg.settings;
isUnixGui = (builtins.substring 0 1 cfg.guiAddress) == "/"; isUnixGui = (builtins.substring 0 1 cfg.guiAddress) == "/";
curlAddressArgs = path: curlAddressArgs = path:
if isUnixGui if isUnixGui
then "--unix-socket ${cfg.guiAddress} http://.${path}" then "--unix-socket ${cfg.guiAddress} http://.${path}"
else "${cfg.guiAddress}${path}"; else "${cfg.guiAddress}${path}";
devices = mapAttrsToList (_: device: device // { deviceID = device.id; }) cfg.settings.devices; devices = mapAttrsToList (_: device: device // {deviceID = device.id;}) cfg.settings.devices;
anyAutoAccept = builtins.any (dev: dev.autoAcceptFolders) devices; anyAutoAccept = builtins.any (dev: dev.autoAcceptFolders) devices;
folders = mapAttrsToList (_: folder: folder // { folders = mapAttrsToList (_: folder:
folder
// {
devices = let devices = let
folderDevices = folder.devices; folderDevices = folder.devices;
in in
map (device: map (
if builtins.isString device then device:
{ deviceId = cfg.settings.devices.${device}.id; } if builtins.isString device
else if builtins.isAttrs device then then {deviceId = cfg.settings.devices.${device}.id;}
{ deviceId = cfg.settings.devices.${device.name}.id; } // device else if builtins.isAttrs device
else then {deviceId = cfg.settings.devices.${device.name}.id;} // device
throw "Invalid type for devices in folder; expected list or attrset." else throw "Invalid type for devices in folder; expected list or attrset."
) folderDevices; )
folderDevices;
}) (filterAttrs (_: folder: folder.enable) cfg.settings.folders); }) (filterAttrs (_: folder: folder.enable) cfg.settings.folders);
jq = "${pkgs.jq}/bin/jq"; jq = "${pkgs.jq}/bin/jq";
updateConfig = pkgs.writers.writeBash "merge-syncthing-config" ( updateConfig = pkgs.writers.writeBash "merge-syncthing-config" (
'' ''
set -efu set -efu
umask 0077 umask 0077
curl() { curl() {
while while
! ${pkgs.libxml2}/bin/xmllint \ ! ${pkgs.libxml2}/bin/xmllint \
--xpath 'string(configuration/gui/apikey)' \ --xpath 'string(configuration/gui/apikey)' \
${cfg.configDir}/config.xml \ ${cfg.configDir}/config.xml \
>"$TMPDIR/api_key" >"$TMPDIR/api_key"
do sleep 1; done do sleep 1; done
(printf "X-API-Key: "; cat "$TMPDIR/api_key") >"$TMPDIR/headers" (printf "X-API-Key: "; cat "$TMPDIR/api_key") >"$TMPDIR/headers"
${pkgs.curl}/bin/curl -sSLk -H "@$TMPDIR/headers" \ ${pkgs.curl}/bin/curl -sSLk -H "@$TMPDIR/headers" \
--retry 1000 --retry-delay 1 --retry-all-errors \ --retry 1000 --retry-delay 1 --retry-all-errors \
"$@" "$@"
} }
'' ''
+ (lib.pipe { + (lib.pipe {
devs = { devs = {
new_conf_IDs = map (v: v.id) devices; new_conf_IDs = map (v: v.id) devices;
GET_IdAttrName = "deviceID"; GET_IdAttrName = "deviceID";
override = cfg.overrideDevices; override = cfg.overrideDevices;
conf = devices; conf = devices;
baseAddress = curlAddressArgs "/rest/config/devices"; baseAddress = curlAddressArgs "/rest/config/devices";
}; };
dirs = { dirs = {
new_conf_IDs = map (v: v.id) folders; new_conf_IDs = map (v: v.id) folders;
GET_IdAttrName = "id"; GET_IdAttrName = "id";
override = cfg.overrideFolders; override = cfg.overrideFolders;
conf = folders; conf = folders;
baseAddress = curlAddressArgs "/rest/config/folders"; baseAddress = curlAddressArgs "/rest/config/folders";
}; };
} [ } [
(mapAttrs (conf_type: s: (mapAttrs (
conf_type: s:
lib.pipe s.conf [ lib.pipe s.conf [
(map (new_cfg: (map (
let new_cfg: let
jsonPreSecretsFile = pkgs.writeTextFile { jsonPreSecretsFile = pkgs.writeTextFile {
name = "${conf_type}-${new_cfg.id}-conf-pre-secrets.json"; name = "${conf_type}-${new_cfg.id}-conf-pre-secrets.json";
text = builtins.toJSON new_cfg; text = builtins.toJSON new_cfg;
}; };
injectSecretsJqCmd = { injectSecretsJqCmd =
"devs" = "${jq} ."; {
"dirs" = let "devs" = "${jq} .";
folder = new_cfg; "dirs" = let
devicesWithSecrets = lib.pipe folder.devices [ folder = new_cfg;
(lib.filter (device: (builtins.isAttrs device) && device ? encryptionPasswordFile)) devicesWithSecrets = lib.pipe folder.devices [
(map (device: { (lib.filter (device: (builtins.isAttrs device) && device ? encryptionPasswordFile))
deviceId = device.deviceId; (map (device: {
variableName = "secret_${builtins.hashString "sha256" device.encryptionPasswordFile}"; deviceId = device.deviceId;
secretPath = device.encryptionPasswordFile; variableName = "secret_${builtins.hashString "sha256" device.encryptionPasswordFile}";
})) secretPath = device.encryptionPasswordFile;
]; }))
jqUpdates = map (device: '' ];
.devices[] |= ( jqUpdates =
if .deviceId == "${device.deviceId}" then map (device: ''
del(.encryptionPasswordFile) | .devices[] |= (
.encryptionPassword = ''$${device.variableName} if .deviceId == "${device.deviceId}" then
else del(.encryptionPasswordFile) |
. .encryptionPassword = ''$${device.variableName}
end else
) .
'') devicesWithSecrets; end
jqRawFiles = map (device: "--rawfile ${device.variableName} ${lib.escapeShellArg device.secretPath}") devicesWithSecrets; )
in '')
"${jq} ${lib.concatStringsSep " " jqRawFiles} ${lib.escapeShellArg (lib.concatStringsSep "|" ([ "." ] ++ jqUpdates))}"; devicesWithSecrets;
}.${conf_type}; jqRawFiles = map (device: "--rawfile ${device.variableName} ${lib.escapeShellArg device.secretPath}") devicesWithSecrets;
in in "${jq} ${lib.concatStringsSep " " jqRawFiles} ${lib.escapeShellArg (lib.concatStringsSep "|" (["."] ++ jqUpdates))}";
'' }.${
conf_type
};
in ''
${injectSecretsJqCmd} ${jsonPreSecretsFile} | curl --json @- -X POST ${s.baseAddress} ${injectSecretsJqCmd} ${jsonPreSecretsFile} | curl --json @- -X POST ${s.baseAddress}
'' ''
)) ))
@@ -119,268 +130,271 @@ final: prev: {
curl -X DELETE ${s.baseAddress}/$id curl -X DELETE ${s.baseAddress}/$id
done done
'' ''
)) ))
builtins.attrValues builtins.attrValues
(lib.concatStringsSep "\n") (lib.concatStringsSep "\n")
]) ])
+ (lib.pipe cleanedConfig [ + (lib.pipe cleanedConfig [
builtins.attrNames builtins.attrNames
(lib.subtractLists [ "folders" "devices" ]) (lib.subtractLists ["folders" "devices"])
(map (subOption: '' (map (subOption: ''
curl -X PUT -d ${lib.escapeShellArg (builtins.toJSON cleanedConfig.${subOption})} ${curlAddressArgs "/rest/config/${subOption}"} curl -X PUT -d ${lib.escapeShellArg (builtins.toJSON cleanedConfig.${subOption})} ${curlAddressArgs "/rest/config/${subOption}"}
'')) ''))
(lib.concatStringsSep "\n") (lib.concatStringsSep "\n")
]) ])
+ '' + ''
if curl ${curlAddressArgs "/rest/config/restart-required"} | if curl ${curlAddressArgs "/rest/config/restart-required"} |
${jq} -e .requiresRestart > /dev/null; then ${jq} -e .requiresRestart > /dev/null; then
curl -X POST ${curlAddressArgs "/rest/system/restart"} curl -X POST ${curlAddressArgs "/rest/system/restart"}
fi fi
'' ''
); );
in in {
{ options = {
options = { services.syncthing = {
services.syncthing = { enable = mkEnableOption "Syncthing, a self-hosted open-source alternative to Dropbox and Bittorrent Sync";
enable = mkEnableOption "Syncthing, a self-hosted open-source alternative to Dropbox and Bittorrent Sync";
cert = mkOption { cert = mkOption {
type = types.nullOr types.str; type = types.nullOr types.str;
default = null; default = null;
description = "Path to the cert.pem file, which will be copied into Syncthing's configDir."; description = "Path to the cert.pem file, which will be copied into Syncthing's configDir.";
}; };
key = mkOption { key = mkOption {
type = types.nullOr types.str; type = types.nullOr types.str;
default = null; default = null;
description = "Path to the key.pem file, which will be copied into Syncthing's configDir."; description = "Path to the key.pem file, which will be copied into Syncthing's configDir.";
}; };
overrideDevices = mkOption { overrideDevices = mkOption {
type = types.bool; type = types.bool;
default = true; default = true;
description = "Whether to delete the devices which are not configured via the devices option."; description = "Whether to delete the devices which are not configured via the devices option.";
}; };
overrideFolders = mkOption { overrideFolders = mkOption {
type = types.bool; type = types.bool;
default = !anyAutoAccept; default = !anyAutoAccept;
description = "Whether to delete the folders which are not configured via the folders option."; description = "Whether to delete the folders which are not configured via the folders option.";
}; };
settings = mkOption { settings = mkOption {
type = types.submodule { type = types.submodule {
freeformType = settingsFormat.type; freeformType = settingsFormat.type;
options = { options = {
options = mkOption { options = mkOption {
default = { }; default = {};
description = "The options element contains all other global configuration options"; description = "The options element contains all other global configuration options";
type = types.submodule { type = types.submodule {
freeformType = settingsFormat.type; freeformType = settingsFormat.type;
options = { options = {
localAnnounceEnabled = mkOption { localAnnounceEnabled = mkOption {
type = types.nullOr types.bool; type = types.nullOr types.bool;
default = null; default = null;
description = "Whether to send announcements to the local LAN."; description = "Whether to send announcements to the local LAN.";
}; };
globalAnnounceEnabled = mkOption { globalAnnounceEnabled = mkOption {
type = types.nullOr types.bool; type = types.nullOr types.bool;
default = null; default = null;
description = "Whether to send announcements to the global discovery servers."; description = "Whether to send announcements to the global discovery servers.";
}; };
relaysEnabled = mkOption { relaysEnabled = mkOption {
type = types.nullOr types.bool; type = types.nullOr types.bool;
default = null; default = null;
description = "When true, relays will be connected to and potentially used for device to device connections."; description = "When true, relays will be connected to and potentially used for device to device connections.";
}; };
urAccepted = mkOption { urAccepted = mkOption {
type = types.nullOr types.int; type = types.nullOr types.int;
default = null; default = null;
description = "Whether the user has accepted to submit anonymous usage data."; description = "Whether the user has accepted to submit anonymous usage data.";
};
}; };
}; };
}; };
};
devices = mkOption { devices = mkOption {
default = { }; default = {};
description = "Peers/devices which Syncthing should communicate with."; description = "Peers/devices which Syncthing should communicate with.";
type = types.attrsOf (types.submodule ({ name, ... }: { type = types.attrsOf (types.submodule ({name, ...}: {
freeformType = settingsFormat.type; freeformType = settingsFormat.type;
options = { options = {
name = mkOption { name = mkOption {
type = types.str; type = types.str;
default = name; default = name;
description = "The name of the device."; description = "The name of the device.";
};
id = mkOption {
type = types.str;
description = "The device ID.";
};
autoAcceptFolders = mkOption {
type = types.bool;
default = false;
description = "Automatically create or share folders that this device advertises at the default path.";
};
}; };
})); id = mkOption {
}; type = types.str;
description = "The device ID.";
};
autoAcceptFolders = mkOption {
type = types.bool;
default = false;
description = "Automatically create or share folders that this device advertises at the default path.";
};
};
}));
};
folders = mkOption { folders = mkOption {
default = { }; default = {};
description = "Folders which should be shared by Syncthing."; description = "Folders which should be shared by Syncthing.";
type = types.attrsOf (types.submodule ({ name, ... }: { type = types.attrsOf (types.submodule ({name, ...}: {
freeformType = settingsFormat.type; freeformType = settingsFormat.type;
options = { options = {
enable = mkOption { enable = mkOption {
type = types.bool; type = types.bool;
default = true; default = true;
description = "Whether to share this folder."; description = "Whether to share this folder.";
}; };
path = mkOption { path = mkOption {
type = types.str; type = types.str;
default = name; default = name;
description = "The path to the folder which should be shared."; description = "The path to the folder which should be shared.";
}; };
id = mkOption { id = mkOption {
type = types.str; type = types.str;
default = name; default = name;
description = "The ID of the folder. Must be the same on all devices."; description = "The ID of the folder. Must be the same on all devices.";
}; };
label = mkOption { label = mkOption {
type = types.str; type = types.str;
default = name; default = name;
description = "The label of the folder."; description = "The label of the folder.";
}; };
type = mkOption { type = mkOption {
type = types.enum [ "sendreceive" "sendonly" "receiveonly" "receiveencrypted" ]; type = types.enum ["sendreceive" "sendonly" "receiveonly" "receiveencrypted"];
default = "sendreceive"; default = "sendreceive";
description = "Controls how the folder is handled by Syncthing."; description = "Controls how the folder is handled by Syncthing.";
}; };
devices = mkOption { devices = mkOption {
type = types.listOf (types.oneOf [ type = types.listOf (types.oneOf [
types.str types.str
(types.submodule { (types.submodule {
freeformType = settingsFormat.type; freeformType = settingsFormat.type;
options = { options = {
name = mkOption { name = mkOption {
type = types.str; type = types.str;
description = "The name of a device defined in the devices option."; description = "The name of a device defined in the devices option.";
};
encryptionPasswordFile = mkOption {
type = types.nullOr types.path;
default = null;
description = "Path to encryption password file.";
};
}; };
}) encryptionPasswordFile = mkOption {
]); type = types.nullOr types.path;
default = [ ]; default = null;
description = "The devices this folder should be shared with."; description = "Path to encryption password file.";
}; };
};
})
]);
default = [];
description = "The devices this folder should be shared with.";
}; };
})); };
}; }));
}; };
}; };
default = { };
description = "Extra configuration options for Syncthing.";
};
guiAddress = mkOption {
type = types.str;
default = "127.0.0.1:8384";
description = "The address to serve the web interface at.";
};
user = mkOption {
type = types.str;
default = defaultUser;
description = "The user to run Syncthing as.";
};
group = mkOption {
type = types.str;
default = defaultGroup;
description = "The group to run Syncthing under.";
};
dataDir = mkOption {
type = types.path;
default = "/var/lib/syncthing";
description = "The path where synchronised directories will exist.";
};
configDir = mkOption {
type = types.path;
default = cfg.dataDir + "/.config/syncthing";
description = "The path where the settings and keys will exist.";
};
openDefaultPorts = mkOption {
type = types.bool;
default = false;
description = "Whether to open the default ports in the firewall (not applicable on Darwin).";
};
package = mkPackageOption pkgs "syncthing" { };
};
};
config = mkIf cfg.enable {
assertions = [
{
assertion = !(cfg.overrideFolders && anyAutoAccept);
message = "services.syncthing.overrideFolders will delete auto-accepted folders from the configuration, creating path conflicts.";
}
];
environment.systemPackages = [ cfg.package ];
launchd.user.agents.syncthing = {
serviceConfig = {
ProgramArguments = [
"${cfg.package}/bin/syncthing"
"-no-browser"
"-gui-address=${if isUnixGui then "unix://" else ""}${cfg.guiAddress}"
"-config=${cfg.configDir}"
"-data=${cfg.configDir}"
];
EnvironmentVariables = {
STNORESTART = "yes";
STNOUPGRADE = "yes";
};
KeepAlive = true;
RunAtLoad = true;
ProcessType = "Background";
StandardOutPath = "${cfg.configDir}/syncthing.log";
StandardErrorPath = "${cfg.configDir}/syncthing.log";
}; };
default = {};
description = "Extra configuration options for Syncthing.";
}; };
launchd.user.agents.syncthing-init = mkIf (cleanedConfig != { }) { guiAddress = mkOption {
serviceConfig = { type = types.str;
ProgramArguments = [ "${updateConfig}" ]; default = "127.0.0.1:8384";
RunAtLoad = true; description = "The address to serve the web interface at.";
KeepAlive = false;
ProcessType = "Background";
StandardOutPath = "${cfg.configDir}/syncthing-init.log";
StandardErrorPath = "${cfg.configDir}/syncthing-init.log";
};
}; };
system.activationScripts.syncthing = mkIf (cfg.cert != null || cfg.key != null) '' user = mkOption {
echo "Setting up Syncthing certificates..." type = types.str;
mkdir -p ${cfg.configDir} default = defaultUser;
${optionalString (cfg.cert != null) '' description = "The user to run Syncthing as.";
cp ${toString cfg.cert} ${cfg.configDir}/cert.pem };
chmod 644 ${cfg.configDir}/cert.pem
''} group = mkOption {
${optionalString (cfg.key != null) '' type = types.str;
cp ${toString cfg.key} ${cfg.configDir}/key.pem default = defaultGroup;
chmod 600 ${cfg.configDir}/key.pem description = "The group to run Syncthing under.";
''} };
'';
dataDir = mkOption {
type = types.path;
default = "/var/lib/syncthing";
description = "The path where synchronised directories will exist.";
};
configDir = mkOption {
type = types.path;
default = cfg.dataDir + "/.config/syncthing";
description = "The path where the settings and keys will exist.";
};
openDefaultPorts = mkOption {
type = types.bool;
default = false;
description = "Whether to open the default ports in the firewall (not applicable on Darwin).";
};
package = mkPackageOption pkgs "syncthing" {};
}; };
}; };
config = mkIf cfg.enable {
assertions = [
{
assertion = !(cfg.overrideFolders && anyAutoAccept);
message = "services.syncthing.overrideFolders will delete auto-accepted folders from the configuration, creating path conflicts.";
}
];
environment.systemPackages = [cfg.package];
launchd.user.agents.syncthing = {
serviceConfig = {
ProgramArguments = [
"${cfg.package}/bin/syncthing"
"-no-browser"
"-gui-address=${
if isUnixGui
then "unix://"
else ""
}${cfg.guiAddress}"
"-config=${cfg.configDir}"
"-data=${cfg.configDir}"
];
EnvironmentVariables = {
STNORESTART = "yes";
STNOUPGRADE = "yes";
};
KeepAlive = true;
RunAtLoad = true;
ProcessType = "Background";
StandardOutPath = "${cfg.configDir}/syncthing.log";
StandardErrorPath = "${cfg.configDir}/syncthing.log";
};
};
launchd.user.agents.syncthing-init = mkIf (cleanedConfig != {}) {
serviceConfig = {
ProgramArguments = ["${updateConfig}"];
RunAtLoad = true;
KeepAlive = false;
ProcessType = "Background";
StandardOutPath = "${cfg.configDir}/syncthing-init.log";
StandardErrorPath = "${cfg.configDir}/syncthing-init.log";
};
};
system.activationScripts.syncthing = mkIf (cfg.cert != null || cfg.key != null) ''
echo "Setting up Syncthing certificates..."
mkdir -p ${cfg.configDir}
${optionalString (cfg.cert != null) ''
cp ${toString cfg.cert} ${cfg.configDir}/cert.pem
chmod 644 ${cfg.configDir}/cert.pem
''}
${optionalString (cfg.key != null) ''
cp ${toString cfg.key} ${cfg.configDir}/key.pem
chmod 600 ${cfg.configDir}/key.pem
''}
'';
};
};
} }