{ config, lib, pkgs, ... }: with lib; let cfg = config.modules.syncthing; # Helper function to create a serviceConfig entry if the condition is met mkServiceConfigOption = name: value: mkIf (value != null) { "${name}" = value; }; # Construct the settings object for Syncthing syncthingSettings = mkMerge [ # GUI configuration (mkIf cfg.gui.enable { gui = mkMerge [ (mkIf (cfg.gui.user != null) { user = cfg.gui.user; }) ]; }) # Devices configuration (mkIf (cfg.devices != {}) { devices = mapAttrs (name: device: { id = device.id; } // optionalAttrs (device.name != null) { name = device.name; } // optionalAttrs (device.addresses != []) { addresses = device.addresses; }) cfg.devices; }) # Folders configuration (mkIf (cfg.folders != {}) { folders = mapAttrs (name: folder: { path = folder.path; devices = folder.devices; } // optionalAttrs (folder.ignorePerms != null) { ignorePerms = folder.ignorePerms; } // optionalAttrs (folder.type != null) { type = folder.type; } // optionalAttrs (folder.rescanIntervalS != null) { rescanIntervalS = folder.rescanIntervalS; } // optionalAttrs (folder.versioning != null) { versioning = folder.versioning; }) cfg.folders; }) # Extra options cfg.extraOptions ]; in { options = { modules.syncthing = { enable = mkEnableOption "Deploy syncthing"; openDefaultPorts = mkOption { type = types.bool; default = true; description = "Open ports in the firewall for Syncthing"; }; disableDefaultFolder = mkOption { type = types.bool; default = true; description = "Don't create default ~/Sync folder"; }; gui = { enable = mkEnableOption "Enable GUI configuration"; user = mkOption { type = types.nullOr types.str; default = null; description = "GUI username"; example = "myuser"; }; passwordFile = mkOption { type = types.nullOr types.path; default = null; description = "Path to file containing GUI password"; example = "config.age.secrets.syncthing-gui-password.path"; }; }; identity = { keyPath = mkOption { type = types.nullOr types.path; default = null; description = "Path to Syncthing private key for stable device ID"; example = "config.age.secrets.syncthing-key.path"; }; certPath = mkOption { type = types.nullOr types.path; default = null; description = "Path to Syncthing certificate for stable device ID"; example = "config.age.secrets.syncthing-cert.path"; }; }; devices = mkOption { type = types.attrsOf (types.submodule { options = { id = mkOption { type = types.str; description = "Device ID"; example = "DMWVMM6-MKEQVB4-I4UZTRH-5A6E24O-XHQTL3K-AAI5R5L-MXNMUGX-QTGRHQ2"; }; name = mkOption { type = types.nullOr types.str; default = null; description = "Device name (optional)"; }; addresses = mkOption { type = types.listOf types.str; default = []; description = "Device addresses"; example = [ "tcp://192.168.1.100:22000" ]; }; }; }); default = {}; description = "Syncthing devices configuration"; example = { "laptop" = { id = "DMWVMM6-MKEQVB4-I4UZTRH-5A6E24O-XHQTL3K-AAI5R5L-MXNMUGX-QTGRHQ2"; }; "phone" = { id = "ANOTHER-DEVICE-ID-GOES-HERE"; addresses = [ "tcp://192.168.1.101:22000" ]; }; }; }; folders = mkOption { type = types.attrsOf (types.submodule { options = { path = mkOption { type = types.str; description = "Local folder path"; example = "/home/myuser/Documents"; }; devices = mkOption { type = types.listOf (types.either types.str (types.submodule { options = { name = mkOption { type = types.str; description = "Device name"; }; encryptionPasswordFile = mkOption { type = types.path; description = "Path to file containing encryption password"; }; }; })); default = []; description = "List of devices that can access this folder"; example = [ "laptop" "phone" ]; }; ignorePerms = mkOption { type = types.nullOr types.bool; default = null; description = "Whether to ignore file permissions"; }; type = mkOption { type = types.nullOr (types.enum [ "sendreceive" "sendonly" "receiveonly" ]); default = null; description = "Folder type"; }; rescanIntervalS = mkOption { type = types.nullOr types.int; default = null; description = "Rescan interval in seconds"; }; versioning = mkOption { type = types.nullOr (types.submodule { options = { type = mkOption { type = types.enum [ "external" "simple" "staggered" "trashcan" ]; description = "Versioning type"; }; params = mkOption { type = types.attrsOf types.str; default = {}; description = "Versioning parameters"; }; }; }); default = null; description = "Folder versioning configuration"; }; }; }); default = {}; description = "Syncthing folders configuration"; example = { "Documents" = { path = "/home/myuser/Documents"; devices = [ "laptop" "phone" ]; ignorePerms = false; }; "Sensitive" = { path = "/home/myuser/Sensitive"; devices = [ "laptop" { name = "phone"; encryptionPasswordFile = "/run/secrets/syncthing-sensitive-password"; } ]; }; }; }; extraOptions = mkOption { type = types.attrsOf types.anything; default = {}; description = "Additional Syncthing configuration options"; }; }; }; config = mkIf cfg.enable { services.syncthing = { enable = true; openDefaultPorts = cfg.openDefaultPorts; # Set stable identity if provided key = mkIf (cfg.identity.keyPath != null) cfg.identity.keyPath; cert = mkIf (cfg.identity.certPath != null) cfg.identity.certPath; # Combine all settings settings = syncthingSettings; }; # Configure systemd service options collectively systemd.services.syncthing = { # Add environment variable to disable default folder creation environment.STNODEFAULTFOLDER = mkIf cfg.disableDefaultFolder "true"; # Add supplementary groups for secret access serviceConfig.SupplementaryGroups = [ "syncthing-secrets" ]; }; # Create a group for accessing secrets users.groups.syncthing-secrets = {}; }; }