diff --git a/flake.lock b/flake.lock index 1fcb204..2c4c946 100644 --- a/flake.lock +++ b/flake.lock @@ -100,11 +100,11 @@ "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1755107032, - "narHash": "sha256-ckb/RX9rJ/FslBA3K4hYAXgVW/7JdQ50Z+28XZT96zg=", + "lastModified": 1755121891, + "narHash": "sha256-UtYkukiGnPRJ5rpd4W/wFVrLMh8fqtNkqHTPgHEtrqU=", "owner": "nix-community", "repo": "home-manager", - "rev": "4b6dd06c6a92308c06da5e0e55f2c505237725c9", + "rev": "279ca5addcdcfa31ac852b3ecb39fc372684f426", "type": "github" }, "original": { @@ -132,11 +132,11 @@ "homebrew-cask": { "flake": false, "locked": { - "lastModified": 1755104931, - "narHash": "sha256-jtXcymAnYH/hCvEGaUlt5vpFwqx00/r5Wly9UCqy7vQ=", + "lastModified": 1755147939, + "narHash": "sha256-PD93pCq6q6xtWxtFwK9WCugELrcy50Om7/VLgpV0raM=", "owner": "homebrew", "repo": "homebrew-cask", - "rev": "53dc289ce38d2561d853b482d0f03914a7f2f985", + "rev": "5fd00a85f58743b2ac654bfbdda762b8e7d4fbcb", "type": "github" }, "original": { @@ -148,11 +148,11 @@ "homebrew-core": { "flake": false, "locked": { - "lastModified": 1755104716, - "narHash": "sha256-6jp9InEQfaVJR4kSdwb0+D6603DanSUEFF3uYYPIQVM=", + "lastModified": 1755152577, + "narHash": "sha256-ix1DIoJEn4V0dOErK12AkIL/iPHtGmGR+/HjyNLwnXQ=", "owner": "homebrew", "repo": "homebrew-core", - "rev": "8aa4cd22422bcb03de07c08c24773257d928e988", + "rev": "b3da0366f58c2f89d8c0d1cabdc7cd5bdc732313", "type": "github" }, "original": { @@ -240,11 +240,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1755109924, - "narHash": "sha256-2xroOWuRFMLvqknLQJ8gmpdXuvg9t5qDUJ3KQKq5/GE=", + "lastModified": 1755154419, + "narHash": "sha256-m8tJUveXe6y73A2wvXRomSC5WwBsNfLgZI3qteAAqyU=", "owner": "nixos", "repo": "nixpkgs", - "rev": "b7f46264dd0a1891d52c9a8b919c2eebbc527638", + "rev": "1a58620315b9d6d3c8110ec35ee46bbacb2fe633", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 43b722a..9b6025f 100644 --- a/flake.nix +++ b/flake.nix @@ -43,11 +43,10 @@ ]; flake.darwinConfigurations = inputs.nixpkgs.lib.genAttrs darwinHosts ( - hostname: - let - syncthingOverlay = import ./overlays/syncthing-darwin.nix; - syncthingModule = (syncthingOverlay null {}).darwinSyncthingModule; - in + hostname: let + syncthingOverlay = import ./overlays/syncthing-darwin.nix; + syncthingModule = (syncthingOverlay null {}).darwinSyncthingModule; + in inputs.darwin.lib.darwinSystem { system = "aarch64-darwin"; specialArgs = @@ -61,8 +60,8 @@ syncthingModule { - nixpkgs.overlays = [ syncthingOverlay ]; - + nixpkgs.overlays = [syncthingOverlay]; + nix-homebrew = { inherit user; enable = true; diff --git a/hosts/darwin/jason/default.nix b/hosts/darwin/jason/default.nix index 106a6fc..b051070 100644 --- a/hosts/darwin/jason/default.nix +++ b/hosts/darwin/jason/default.nix @@ -1,7 +1,4 @@ -{ - user, - ... -}: { +{user, ...}: { imports = [ ../shared.nix ]; diff --git a/hosts/nixos/tahani/default.nix b/hosts/nixos/tahani/default.nix index 637fa98..42c070d 100644 --- a/hosts/nixos/tahani/default.nix +++ b/hosts/nixos/tahani/default.nix @@ -65,7 +65,6 @@ }; }; - services.postgresql = { enable = true; package = pkgs.postgresql_17; diff --git a/modules/home-manager/base/editors/neovim/plugins/oil.nix b/modules/home-manager/base/editors/neovim/plugins/oil.nix index d8e284c..82f910d 100644 --- a/modules/home-manager/base/editors/neovim/plugins/oil.nix +++ b/modules/home-manager/base/editors/neovim/plugins/oil.nix @@ -27,4 +27,4 @@ }; }; }; -} \ No newline at end of file +} diff --git a/modules/networking/tailscale.nix b/modules/networking/tailscale.nix index 54ff59b..06f06f1 100644 --- a/modules/networking/tailscale.nix +++ b/modules/networking/tailscale.nix @@ -1,10 +1,14 @@ -{ pkgs, lib, ... }: - { - services.tailscale = { - enable = true; - } // lib.optionalAttrs pkgs.stdenv.isLinux { - useRoutingFeatures = "server"; - openFirewall = true; - }; + pkgs, + lib, + ... +}: { + services.tailscale = + { + enable = true; + } + // lib.optionalAttrs pkgs.stdenv.isLinux { + openFirewall = true; + useRoutingFeatures = "server"; + }; } diff --git a/overlays/syncthing-darwin.nix b/overlays/syncthing-darwin.nix index 046d2ab..0027eff 100644 --- a/overlays/syncthing-darwin.nix +++ b/overlays/syncthing-darwin.nix @@ -1,108 +1,119 @@ final: prev: { - darwinSyncthingModule = { config, lib, pkgs, ... }: - with lib; - let - cfg = config.services.syncthing; - defaultUser = "syncthing"; - defaultGroup = defaultUser; - settingsFormat = pkgs.formats.json { }; - cleanedConfig = converge (filterAttrsRecursive (_: v: v != null && v != { })) cfg.settings; + darwinSyncthingModule = { + config, + lib, + pkgs, + ... + }: + with lib; let + 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: - if isUnixGui - then "--unix-socket ${cfg.guiAddress} http://.${path}" - else "${cfg.guiAddress}${path}"; + curlAddressArgs = path: + if isUnixGui + then "--unix-socket ${cfg.guiAddress} http://.${path}" + else "${cfg.guiAddress}${path}"; - devices = mapAttrsToList (_: device: device // { deviceID = device.id; }) cfg.settings.devices; - anyAutoAccept = builtins.any (dev: dev.autoAcceptFolders) devices; + devices = mapAttrsToList (_: device: device // {deviceID = device.id;}) cfg.settings.devices; + anyAutoAccept = builtins.any (dev: dev.autoAcceptFolders) devices; - folders = mapAttrsToList (_: folder: folder // { + folders = mapAttrsToList (_: folder: + folder + // { devices = let folderDevices = folder.devices; in - map (device: - if builtins.isString device then - { deviceId = cfg.settings.devices.${device}.id; } - else if builtins.isAttrs device then - { deviceId = cfg.settings.devices.${device.name}.id; } // device - else - throw "Invalid type for devices in folder; expected list or attrset." - ) folderDevices; + map ( + device: + if builtins.isString device + then {deviceId = cfg.settings.devices.${device}.id;} + else if builtins.isAttrs device + then {deviceId = cfg.settings.devices.${device.name}.id;} // device + else throw "Invalid type for devices in folder; expected list or attrset." + ) + folderDevices; }) (filterAttrs (_: folder: folder.enable) cfg.settings.folders); - jq = "${pkgs.jq}/bin/jq"; - updateConfig = pkgs.writers.writeBash "merge-syncthing-config" ( - '' - set -efu - umask 0077 + jq = "${pkgs.jq}/bin/jq"; + updateConfig = pkgs.writers.writeBash "merge-syncthing-config" ( + '' + set -efu + umask 0077 - curl() { - while - ! ${pkgs.libxml2}/bin/xmllint \ - --xpath 'string(configuration/gui/apikey)' \ - ${cfg.configDir}/config.xml \ - >"$TMPDIR/api_key" - do sleep 1; done - (printf "X-API-Key: "; cat "$TMPDIR/api_key") >"$TMPDIR/headers" - ${pkgs.curl}/bin/curl -sSLk -H "@$TMPDIR/headers" \ - --retry 1000 --retry-delay 1 --retry-all-errors \ - "$@" - } - '' - + (lib.pipe { - devs = { - new_conf_IDs = map (v: v.id) devices; - GET_IdAttrName = "deviceID"; - override = cfg.overrideDevices; - conf = devices; - baseAddress = curlAddressArgs "/rest/config/devices"; - }; - dirs = { - new_conf_IDs = map (v: v.id) folders; - GET_IdAttrName = "id"; - override = cfg.overrideFolders; - conf = folders; - baseAddress = curlAddressArgs "/rest/config/folders"; - }; - } [ - (mapAttrs (conf_type: s: + curl() { + while + ! ${pkgs.libxml2}/bin/xmllint \ + --xpath 'string(configuration/gui/apikey)' \ + ${cfg.configDir}/config.xml \ + >"$TMPDIR/api_key" + do sleep 1; done + (printf "X-API-Key: "; cat "$TMPDIR/api_key") >"$TMPDIR/headers" + ${pkgs.curl}/bin/curl -sSLk -H "@$TMPDIR/headers" \ + --retry 1000 --retry-delay 1 --retry-all-errors \ + "$@" + } + '' + + (lib.pipe { + devs = { + new_conf_IDs = map (v: v.id) devices; + GET_IdAttrName = "deviceID"; + override = cfg.overrideDevices; + conf = devices; + baseAddress = curlAddressArgs "/rest/config/devices"; + }; + dirs = { + new_conf_IDs = map (v: v.id) folders; + GET_IdAttrName = "id"; + override = cfg.overrideFolders; + conf = folders; + baseAddress = curlAddressArgs "/rest/config/folders"; + }; + } [ + (mapAttrs ( + conf_type: s: lib.pipe s.conf [ - (map (new_cfg: - let + (map ( + new_cfg: let jsonPreSecretsFile = pkgs.writeTextFile { name = "${conf_type}-${new_cfg.id}-conf-pre-secrets.json"; text = builtins.toJSON new_cfg; }; - injectSecretsJqCmd = { - "devs" = "${jq} ."; - "dirs" = let - folder = new_cfg; - devicesWithSecrets = lib.pipe folder.devices [ - (lib.filter (device: (builtins.isAttrs device) && device ? encryptionPasswordFile)) - (map (device: { - deviceId = device.deviceId; - variableName = "secret_${builtins.hashString "sha256" device.encryptionPasswordFile}"; - secretPath = device.encryptionPasswordFile; - })) - ]; - jqUpdates = map (device: '' - .devices[] |= ( - if .deviceId == "${device.deviceId}" then - del(.encryptionPasswordFile) | - .encryptionPassword = ''$${device.variableName} - else - . - end - ) - '') devicesWithSecrets; - jqRawFiles = map (device: "--rawfile ${device.variableName} ${lib.escapeShellArg device.secretPath}") devicesWithSecrets; - in - "${jq} ${lib.concatStringsSep " " jqRawFiles} ${lib.escapeShellArg (lib.concatStringsSep "|" ([ "." ] ++ jqUpdates))}"; - }.${conf_type}; - in - '' + injectSecretsJqCmd = + { + "devs" = "${jq} ."; + "dirs" = let + folder = new_cfg; + devicesWithSecrets = lib.pipe folder.devices [ + (lib.filter (device: (builtins.isAttrs device) && device ? encryptionPasswordFile)) + (map (device: { + deviceId = device.deviceId; + variableName = "secret_${builtins.hashString "sha256" device.encryptionPasswordFile}"; + secretPath = device.encryptionPasswordFile; + })) + ]; + jqUpdates = + map (device: '' + .devices[] |= ( + if .deviceId == "${device.deviceId}" then + del(.encryptionPasswordFile) | + .encryptionPassword = ''$${device.variableName} + else + . + end + ) + '') + devicesWithSecrets; + jqRawFiles = map (device: "--rawfile ${device.variableName} ${lib.escapeShellArg device.secretPath}") devicesWithSecrets; + in "${jq} ${lib.concatStringsSep " " jqRawFiles} ${lib.escapeShellArg (lib.concatStringsSep "|" (["."] ++ jqUpdates))}"; + }.${ + conf_type + }; + in '' ${injectSecretsJqCmd} ${jsonPreSecretsFile} | curl --json @- -X POST ${s.baseAddress} '' )) @@ -119,268 +130,271 @@ final: prev: { curl -X DELETE ${s.baseAddress}/$id done '' - )) - builtins.attrValues - (lib.concatStringsSep "\n") - ]) - + (lib.pipe cleanedConfig [ - builtins.attrNames - (lib.subtractLists [ "folders" "devices" ]) - (map (subOption: '' - curl -X PUT -d ${lib.escapeShellArg (builtins.toJSON cleanedConfig.${subOption})} ${curlAddressArgs "/rest/config/${subOption}"} - '')) - (lib.concatStringsSep "\n") - ]) - + '' - if curl ${curlAddressArgs "/rest/config/restart-required"} | - ${jq} -e .requiresRestart > /dev/null; then - curl -X POST ${curlAddressArgs "/rest/system/restart"} - fi - '' - ); - in - { - options = { - services.syncthing = { - enable = mkEnableOption "Syncthing, a self-hosted open-source alternative to Dropbox and Bittorrent Sync"; + )) + builtins.attrValues + (lib.concatStringsSep "\n") + ]) + + (lib.pipe cleanedConfig [ + builtins.attrNames + (lib.subtractLists ["folders" "devices"]) + (map (subOption: '' + curl -X PUT -d ${lib.escapeShellArg (builtins.toJSON cleanedConfig.${subOption})} ${curlAddressArgs "/rest/config/${subOption}"} + '')) + (lib.concatStringsSep "\n") + ]) + + '' + if curl ${curlAddressArgs "/rest/config/restart-required"} | + ${jq} -e .requiresRestart > /dev/null; then + curl -X POST ${curlAddressArgs "/rest/system/restart"} + fi + '' + ); + in { + options = { + services.syncthing = { + enable = mkEnableOption "Syncthing, a self-hosted open-source alternative to Dropbox and Bittorrent Sync"; - cert = mkOption { - type = types.nullOr types.str; - default = null; - description = "Path to the cert.pem file, which will be copied into Syncthing's configDir."; - }; + cert = mkOption { + type = types.nullOr types.str; + default = null; + description = "Path to the cert.pem file, which will be copied into Syncthing's configDir."; + }; - key = mkOption { - type = types.nullOr types.str; - default = null; - description = "Path to the key.pem file, which will be copied into Syncthing's configDir."; - }; + key = mkOption { + type = types.nullOr types.str; + default = null; + description = "Path to the key.pem file, which will be copied into Syncthing's configDir."; + }; - overrideDevices = mkOption { - type = types.bool; - default = true; - description = "Whether to delete the devices which are not configured via the devices option."; - }; + overrideDevices = mkOption { + type = types.bool; + default = true; + description = "Whether to delete the devices which are not configured via the devices option."; + }; - overrideFolders = mkOption { - type = types.bool; - default = !anyAutoAccept; - description = "Whether to delete the folders which are not configured via the folders option."; - }; + overrideFolders = mkOption { + type = types.bool; + default = !anyAutoAccept; + description = "Whether to delete the folders which are not configured via the folders option."; + }; - settings = mkOption { - type = types.submodule { - freeformType = settingsFormat.type; - options = { - options = mkOption { - default = { }; - description = "The options element contains all other global configuration options"; - type = types.submodule { - freeformType = settingsFormat.type; - options = { - localAnnounceEnabled = mkOption { - type = types.nullOr types.bool; - default = null; - description = "Whether to send announcements to the local LAN."; - }; - globalAnnounceEnabled = mkOption { - type = types.nullOr types.bool; - default = null; - description = "Whether to send announcements to the global discovery servers."; - }; - relaysEnabled = mkOption { - type = types.nullOr types.bool; - default = null; - description = "When true, relays will be connected to and potentially used for device to device connections."; - }; - urAccepted = mkOption { - type = types.nullOr types.int; - default = null; - description = "Whether the user has accepted to submit anonymous usage data."; - }; + settings = mkOption { + type = types.submodule { + freeformType = settingsFormat.type; + options = { + options = mkOption { + default = {}; + description = "The options element contains all other global configuration options"; + type = types.submodule { + freeformType = settingsFormat.type; + options = { + localAnnounceEnabled = mkOption { + type = types.nullOr types.bool; + default = null; + description = "Whether to send announcements to the local LAN."; + }; + globalAnnounceEnabled = mkOption { + type = types.nullOr types.bool; + default = null; + description = "Whether to send announcements to the global discovery servers."; + }; + relaysEnabled = mkOption { + type = types.nullOr types.bool; + default = null; + description = "When true, relays will be connected to and potentially used for device to device connections."; + }; + urAccepted = mkOption { + type = types.nullOr types.int; + default = null; + description = "Whether the user has accepted to submit anonymous usage data."; }; }; }; + }; - devices = mkOption { - default = { }; - description = "Peers/devices which Syncthing should communicate with."; - type = types.attrsOf (types.submodule ({ name, ... }: { - freeformType = settingsFormat.type; - options = { - name = mkOption { - type = types.str; - default = name; - 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."; - }; + devices = mkOption { + default = {}; + description = "Peers/devices which Syncthing should communicate with."; + type = types.attrsOf (types.submodule ({name, ...}: { + freeformType = settingsFormat.type; + options = { + name = mkOption { + type = types.str; + default = name; + 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."; + }; + }; + })); + }; - folders = mkOption { - default = { }; - description = "Folders which should be shared by Syncthing."; - type = types.attrsOf (types.submodule ({ name, ... }: { - freeformType = settingsFormat.type; - options = { - enable = mkOption { - type = types.bool; - default = true; - description = "Whether to share this folder."; - }; - path = mkOption { - type = types.str; - default = name; - description = "The path to the folder which should be shared."; - }; - id = mkOption { - type = types.str; - default = name; - description = "The ID of the folder. Must be the same on all devices."; - }; - label = mkOption { - type = types.str; - default = name; - description = "The label of the folder."; - }; - type = mkOption { - type = types.enum [ "sendreceive" "sendonly" "receiveonly" "receiveencrypted" ]; - default = "sendreceive"; - description = "Controls how the folder is handled by Syncthing."; - }; - devices = mkOption { - type = types.listOf (types.oneOf [ - types.str - (types.submodule { - freeformType = settingsFormat.type; - options = { - name = mkOption { - type = types.str; - 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."; - }; + folders = mkOption { + default = {}; + description = "Folders which should be shared by Syncthing."; + type = types.attrsOf (types.submodule ({name, ...}: { + freeformType = settingsFormat.type; + options = { + enable = mkOption { + type = types.bool; + default = true; + description = "Whether to share this folder."; + }; + path = mkOption { + type = types.str; + default = name; + description = "The path to the folder which should be shared."; + }; + id = mkOption { + type = types.str; + default = name; + description = "The ID of the folder. Must be the same on all devices."; + }; + label = mkOption { + type = types.str; + default = name; + description = "The label of the folder."; + }; + type = mkOption { + type = types.enum ["sendreceive" "sendonly" "receiveonly" "receiveencrypted"]; + default = "sendreceive"; + description = "Controls how the folder is handled by Syncthing."; + }; + devices = mkOption { + type = types.listOf (types.oneOf [ + types.str + (types.submodule { + freeformType = settingsFormat.type; + options = { + name = mkOption { + type = types.str; + description = "The name of a device defined in the devices option."; }; - }) - ]); - default = [ ]; - description = "The devices this folder should be shared with."; - }; + encryptionPasswordFile = mkOption { + type = types.nullOr types.path; + default = null; + 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 != { }) { - serviceConfig = { - ProgramArguments = [ "${updateConfig}" ]; - RunAtLoad = true; - KeepAlive = false; - ProcessType = "Background"; - StandardOutPath = "${cfg.configDir}/syncthing-init.log"; - StandardErrorPath = "${cfg.configDir}/syncthing-init.log"; - }; + guiAddress = mkOption { + type = types.str; + default = "127.0.0.1:8384"; + description = "The address to serve the web interface at."; }; - 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 - ''} - ''; + 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"; + }; + }; + + 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 + ''} + ''; + }; + }; }