diff --git a/hosts/chidi/default.nix b/hosts/chidi/default.nix index 5203de3..c1e6b15 100644 --- a/hosts/chidi/default.nix +++ b/hosts/chidi/default.nix @@ -6,13 +6,13 @@ ... }: { imports = [ - ../../modules/core.nix - ../../modules/darwin.nix - ../../modules/darwin-syncthing.nix - ../../modules/dock.nix - ../../modules/homebrew.nix ../../modules/syncthing.nix - ../../modules/tailscale.nix + ../../profiles/core.nix + ../../profiles/darwin.nix + ../../profiles/dock.nix + ../../profiles/homebrew.nix + ../../profiles/syncthing.nix + ../../profiles/tailscale.nix inputs.sops-nix.darwinModules.sops ]; @@ -34,29 +34,29 @@ _module.args = {inherit user constants inputs;}; imports = [ inputs.nixvim.homeModules.nixvim - ../../modules/atuin.nix - ../../modules/bash.nix - ../../modules/bat.nix - ../../modules/direnv.nix - ../../modules/eza.nix - ../../modules/fish.nix - ../../modules/fzf.nix - ../../modules/ghostty.nix - ../../modules/git.nix - ../../modules/home.nix - ../../modules/jjui.nix - ../../modules/jujutsu.nix - ../../modules/lazygit.nix - ../../modules/mise.nix - ../../modules/neovim - ../../modules/opencode.nix - ../../modules/ripgrep.nix - ../../modules/ssh.nix - ../../modules/starship.nix - ../../modules/zellij.nix - ../../modules/zk.nix - ../../modules/zoxide.nix - ../../modules/zsh.nix + ../../profiles/atuin.nix + ../../profiles/bash.nix + ../../profiles/bat.nix + ../../profiles/direnv.nix + ../../profiles/eza.nix + ../../profiles/fish.nix + ../../profiles/fzf.nix + ../../profiles/ghostty.nix + ../../profiles/git.nix + ../../profiles/home.nix + ../../profiles/jjui.nix + ../../profiles/jujutsu.nix + ../../profiles/lazygit.nix + ../../profiles/mise.nix + ../../profiles/neovim + ../../profiles/opencode.nix + ../../profiles/ripgrep.nix + ../../profiles/ssh.nix + ../../profiles/starship.nix + ../../profiles/zellij.nix + ../../profiles/zk.nix + ../../profiles/zoxide.nix + ../../profiles/zsh.nix ]; fonts.fontconfig.enable = true; programs.git.settings.user.email = "christoph@tuist.dev"; diff --git a/hosts/jason/default.nix b/hosts/jason/default.nix index 0d55f34..f15b322 100644 --- a/hosts/jason/default.nix +++ b/hosts/jason/default.nix @@ -4,13 +4,13 @@ ... }: { imports = [ - ../../modules/core.nix - ../../modules/darwin.nix - ../../modules/darwin-syncthing.nix - ../../modules/dock.nix - ../../modules/homebrew.nix ../../modules/syncthing.nix - ../../modules/tailscale.nix + ../../profiles/core.nix + ../../profiles/darwin.nix + ../../profiles/dock.nix + ../../profiles/homebrew.nix + ../../profiles/syncthing.nix + ../../profiles/tailscale.nix inputs.sops-nix.darwinModules.sops ]; @@ -50,29 +50,29 @@ _module.args = {inherit user constants inputs;}; imports = [ inputs.nixvim.homeModules.nixvim - ../../modules/atuin.nix - ../../modules/bash.nix - ../../modules/bat.nix - ../../modules/direnv.nix - ../../modules/eza.nix - ../../modules/fish.nix - ../../modules/fzf.nix - ../../modules/ghostty.nix - ../../modules/git.nix - ../../modules/home.nix - ../../modules/jjui.nix - ../../modules/jujutsu.nix - ../../modules/lazygit.nix - ../../modules/mise.nix - ../../modules/neovim - ../../modules/opencode.nix - ../../modules/ripgrep.nix - ../../modules/ssh.nix - ../../modules/starship.nix - ../../modules/zellij.nix - ../../modules/zk.nix - ../../modules/zoxide.nix - ../../modules/zsh.nix + ../../profiles/atuin.nix + ../../profiles/bash.nix + ../../profiles/bat.nix + ../../profiles/direnv.nix + ../../profiles/eza.nix + ../../profiles/fish.nix + ../../profiles/fzf.nix + ../../profiles/ghostty.nix + ../../profiles/git.nix + ../../profiles/home.nix + ../../profiles/jjui.nix + ../../profiles/jujutsu.nix + ../../profiles/lazygit.nix + ../../profiles/mise.nix + ../../profiles/neovim + ../../profiles/opencode.nix + ../../profiles/ripgrep.nix + ../../profiles/ssh.nix + ../../profiles/starship.nix + ../../profiles/zellij.nix + ../../profiles/zk.nix + ../../profiles/zoxide.nix + ../../profiles/zsh.nix ]; fonts.fontconfig.enable = true; programs.git.settings.user.email = "christoph@schmatzler.com"; diff --git a/hosts/michael/default.nix b/hosts/michael/default.nix index 0a5a211..110886c 100644 --- a/hosts/michael/default.nix +++ b/hosts/michael/default.nix @@ -12,10 +12,10 @@ (modulesPath + "/profiles/qemu-guest.nix") ./disk-config.nix ./hardware-configuration.nix - ../../modules/core.nix - ../../modules/fail2ban.nix - ../../modules/gitea.nix - ../../modules/nixos.nix + ../../profiles/core.nix + ../../profiles/fail2ban.nix + ../../profiles/gitea.nix + ../../profiles/nixos.nix inputs.disko.nixosModules.disko ]; @@ -27,22 +27,22 @@ _module.args = {inherit user constants inputs;}; imports = [ inputs.nixvim.homeModules.nixvim - ../../modules/bash.nix - ../../modules/bat.nix - ../../modules/direnv.nix - ../../modules/eza.nix - ../../modules/fish.nix - ../../modules/fzf.nix - ../../modules/git.nix - ../../modules/home.nix - ../../modules/jjui.nix - ../../modules/jujutsu.nix - ../../modules/lazygit.nix - ../../modules/neovim - ../../modules/ripgrep.nix - ../../modules/ssh.nix - ../../modules/starship.nix - ../../modules/zoxide.nix + ../../profiles/bash.nix + ../../profiles/bat.nix + ../../profiles/direnv.nix + ../../profiles/eza.nix + ../../profiles/fish.nix + ../../profiles/fzf.nix + ../../profiles/git.nix + ../../profiles/home.nix + ../../profiles/jjui.nix + ../../profiles/jujutsu.nix + ../../profiles/lazygit.nix + ../../profiles/neovim + ../../profiles/ripgrep.nix + ../../profiles/ssh.nix + ../../profiles/starship.nix + ../../profiles/zoxide.nix ]; }; diff --git a/hosts/tahani/default.nix b/hosts/tahani/default.nix index 71313eb..37e54eb 100644 --- a/hosts/tahani/default.nix +++ b/hosts/tahani/default.nix @@ -8,10 +8,10 @@ ... }: { imports = [ - ../../modules/core.nix - ../../modules/nixos.nix - ../../modules/syncthing.nix - ../../modules/tailscale.nix + ../../profiles/core.nix + ../../profiles/nixos.nix + ../../profiles/syncthing.nix + ../../profiles/tailscale.nix inputs.sops-nix.nixosModules.sops ]; @@ -23,28 +23,28 @@ _module.args = {inherit user constants inputs;}; imports = [ inputs.nixvim.homeModules.nixvim - ../../modules/atuin.nix - ../../modules/bash.nix - ../../modules/bat.nix - ../../modules/direnv.nix - ../../modules/eza.nix - ../../modules/fish.nix - ../../modules/fzf.nix - ../../modules/git.nix - ../../modules/home.nix - ../../modules/jjui.nix - ../../modules/jujutsu.nix - ../../modules/lazygit.nix - ../../modules/mise.nix - ../../modules/neovim - ../../modules/opencode.nix - ../../modules/ripgrep.nix - ../../modules/ssh.nix - ../../modules/starship.nix - ../../modules/zellij.nix - ../../modules/zk.nix - ../../modules/zoxide.nix - ../../modules/zsh.nix + ../../profiles/atuin.nix + ../../profiles/bash.nix + ../../profiles/bat.nix + ../../profiles/direnv.nix + ../../profiles/eza.nix + ../../profiles/fish.nix + ../../profiles/fzf.nix + ../../profiles/git.nix + ../../profiles/home.nix + ../../profiles/jjui.nix + ../../profiles/jujutsu.nix + ../../profiles/lazygit.nix + ../../profiles/mise.nix + ../../profiles/neovim + ../../profiles/opencode.nix + ../../profiles/ripgrep.nix + ../../profiles/ssh.nix + ../../profiles/starship.nix + ../../profiles/zellij.nix + ../../profiles/zk.nix + ../../profiles/zoxide.nix + ../../profiles/zsh.nix ]; home.packages = [ diff --git a/modules/darwin-syncthing.nix b/modules/darwin-syncthing.nix deleted file mode 100644 index 1679f55..0000000 --- a/modules/darwin-syncthing.nix +++ /dev/null @@ -1,437 +0,0 @@ -{ - config, - lib, - pkgs, - ... -}: -with lib; let - cfg = config.services.syncthing; - settingsFormat = pkgs.formats.json {}; - cleanedConfig = converge (filterAttrsRecursive (_: v: v != null && v != {})) cfg.settings; - - isUnixGui = (builtins.substring 0 1 cfg.guiAddress) == "/"; - - 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; - - 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; - }) (filterAttrs (_: folder: folder.enable) cfg.settings.folders); - - 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: - lib.pipe s.conf [ - (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} ${jsonPreSecretsFile} | curl --json @- -X POST ${s.baseAddress} - '' - )) - (lib.concatStringsSep "\n") - ] - + lib.optionalString s.override '' - stale_${conf_type}_ids="$(curl -X GET ${s.baseAddress} | ${jq} \ - --argjson new_ids ${lib.escapeShellArg (builtins.toJSON s.new_conf_IDs)} \ - --raw-output \ - '[.[].${s.GET_IdAttrName}] - $new_ids | .[]' - )" - for id in ''${stale_${conf_type}_ids}; do - >&2 echo "Deleting stale device: $id" - 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"; - - 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."; - }; - - 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."; - }; - - 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."; - }; - }; - })); - }; - - 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."; - }; - }; - }) - ]); - 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 = "syncthing"; - description = "The user to run Syncthing as."; - }; - - group = - mkOption { - type = types.str; - default = "syncthing"; - 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 - ''} - ''; - }; -} diff --git a/modules/syncthing.nix b/modules/syncthing.nix index 92f135e..1679f55 100644 --- a/modules/syncthing.nix +++ b/modules/syncthing.nix @@ -1,53 +1,437 @@ { - user, + config, + lib, pkgs, ... -}: let - isDarwin = pkgs.stdenv.isDarwin; - homeDir = - if isDarwin - then "/Users/${user}" - else "/home/${user}"; - group = - if isDarwin - then "staff" - else "users"; +}: +with lib; let + cfg = config.services.syncthing; + settingsFormat = pkgs.formats.json {}; + cleanedConfig = converge (filterAttrsRecursive (_: v: v != null && v != {})) cfg.settings; + + isUnixGui = (builtins.substring 0 1 cfg.guiAddress) == "/"; + + 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; + + 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; + }) (filterAttrs (_: folder: folder.enable) cfg.settings.folders); + + 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: + lib.pipe s.conf [ + (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} ${jsonPreSecretsFile} | curl --json @- -X POST ${s.baseAddress} + '' + )) + (lib.concatStringsSep "\n") + ] + + lib.optionalString s.override '' + stale_${conf_type}_ids="$(curl -X GET ${s.baseAddress} | ${jq} \ + --argjson new_ids ${lib.escapeShellArg (builtins.toJSON s.new_conf_IDs)} \ + --raw-output \ + '[.[].${s.GET_IdAttrName}] - $new_ids | .[]' + )" + for id in ''${stale_${conf_type}_ids}; do + >&2 echo "Deleting stale device: $id" + 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 { - services.syncthing = { - enable = true; - openDefaultPorts = !isDarwin; - dataDir = "${homeDir}/.local/share/syncthing"; - configDir = "${homeDir}/.config/syncthing"; - user = "${user}"; - group = group; - guiAddress = "0.0.0.0:8384"; - overrideFolders = true; - overrideDevices = true; + options = { + services.syncthing = { + enable = mkEnableOption "Syncthing, a self-hosted open-source alternative to Dropbox and Bittorrent Sync"; - settings = { - devices = { - "tahani" = { - id = "6B7OZZF-TEAMUGO-FBOELXP-Z4OY7EU-5ZHLB5T-V6Z3UDB-Q2DYR43-QBYW6QM"; - addresses = ["tcp://tahani:22000"]; + cert = + mkOption { + type = types.nullOr types.str; + default = null; + description = "Path to the cert.pem file, which will be copied into Syncthing's configDir."; }; - "jason" = { - id = "42II2VO-QYPJG26-ZS3MB2I-AOPVZ67-JJNSE76-U54CO5Y-634A5OG-ECU4YQA"; - addresses = ["tcp://jason:22000"]; - }; - "chidi" = { - id = "N7W6SUT-QO6J4BE-T3Y65SM-OFGYGNV-TGYBJPX-JVN4Z72-AENZ247-KWXOQA6"; - addresses = ["tcp://chidi:22000"]; - }; - }; - folders = { - "nixos-config" = { - path = "${homeDir}/nixos-config"; - devices = ["tahani" "jason" "chidi"]; + key = + mkOption { + type = types.nullOr types.str; + default = null; + description = "Path to the key.pem file, which will be copied into Syncthing's configDir."; }; - }; - options.globalAnnounceEnabled = false; + 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."; + }; + + 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."; + }; + }; + })); + }; + + 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."; + }; + }; + }) + ]); + 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 = "syncthing"; + description = "The user to run Syncthing as."; + }; + + group = + mkOption { + type = types.str; + default = "syncthing"; + 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 + ''} + ''; + }; } diff --git a/modules/atuin.nix b/profiles/atuin.nix similarity index 100% rename from modules/atuin.nix rename to profiles/atuin.nix diff --git a/modules/bash.nix b/profiles/bash.nix similarity index 100% rename from modules/bash.nix rename to profiles/bash.nix diff --git a/modules/bat.nix b/profiles/bat.nix similarity index 100% rename from modules/bat.nix rename to profiles/bat.nix diff --git a/modules/core.nix b/profiles/core.nix similarity index 100% rename from modules/core.nix rename to profiles/core.nix diff --git a/modules/darwin.nix b/profiles/darwin.nix similarity index 100% rename from modules/darwin.nix rename to profiles/darwin.nix diff --git a/modules/direnv.nix b/profiles/direnv.nix similarity index 100% rename from modules/direnv.nix rename to profiles/direnv.nix diff --git a/modules/dock.nix b/profiles/dock.nix similarity index 100% rename from modules/dock.nix rename to profiles/dock.nix diff --git a/modules/eza.nix b/profiles/eza.nix similarity index 100% rename from modules/eza.nix rename to profiles/eza.nix diff --git a/modules/fail2ban.nix b/profiles/fail2ban.nix similarity index 100% rename from modules/fail2ban.nix rename to profiles/fail2ban.nix diff --git a/modules/fish.nix b/profiles/fish.nix similarity index 100% rename from modules/fish.nix rename to profiles/fish.nix diff --git a/modules/fzf.nix b/profiles/fzf.nix similarity index 100% rename from modules/fzf.nix rename to profiles/fzf.nix diff --git a/modules/ghostty.nix b/profiles/ghostty.nix similarity index 100% rename from modules/ghostty.nix rename to profiles/ghostty.nix diff --git a/modules/git.nix b/profiles/git.nix similarity index 100% rename from modules/git.nix rename to profiles/git.nix diff --git a/modules/gitea.nix b/profiles/gitea.nix similarity index 100% rename from modules/gitea.nix rename to profiles/gitea.nix diff --git a/modules/home.nix b/profiles/home.nix similarity index 100% rename from modules/home.nix rename to profiles/home.nix diff --git a/modules/homebrew.nix b/profiles/homebrew.nix similarity index 100% rename from modules/homebrew.nix rename to profiles/homebrew.nix diff --git a/modules/jjui.nix b/profiles/jjui.nix similarity index 100% rename from modules/jjui.nix rename to profiles/jjui.nix diff --git a/modules/jujutsu.nix b/profiles/jujutsu.nix similarity index 100% rename from modules/jujutsu.nix rename to profiles/jujutsu.nix diff --git a/modules/lazygit.nix b/profiles/lazygit.nix similarity index 100% rename from modules/lazygit.nix rename to profiles/lazygit.nix diff --git a/modules/mise.nix b/profiles/mise.nix similarity index 100% rename from modules/mise.nix rename to profiles/mise.nix diff --git a/modules/neovim/autocmd.nix b/profiles/neovim/autocmd.nix similarity index 100% rename from modules/neovim/autocmd.nix rename to profiles/neovim/autocmd.nix diff --git a/modules/neovim/default.nix b/profiles/neovim/default.nix similarity index 100% rename from modules/neovim/default.nix rename to profiles/neovim/default.nix diff --git a/modules/neovim/mappings.nix b/profiles/neovim/mappings.nix similarity index 100% rename from modules/neovim/mappings.nix rename to profiles/neovim/mappings.nix diff --git a/modules/neovim/options.nix b/profiles/neovim/options.nix similarity index 100% rename from modules/neovim/options.nix rename to profiles/neovim/options.nix diff --git a/modules/neovim/plugins/blink-cmp.nix b/profiles/neovim/plugins/blink-cmp.nix similarity index 100% rename from modules/neovim/plugins/blink-cmp.nix rename to profiles/neovim/plugins/blink-cmp.nix diff --git a/modules/neovim/plugins/conform.nix b/profiles/neovim/plugins/conform.nix similarity index 100% rename from modules/neovim/plugins/conform.nix rename to profiles/neovim/plugins/conform.nix diff --git a/modules/neovim/plugins/copilot.nix b/profiles/neovim/plugins/copilot.nix similarity index 100% rename from modules/neovim/plugins/copilot.nix rename to profiles/neovim/plugins/copilot.nix diff --git a/modules/neovim/plugins/grug-far.nix b/profiles/neovim/plugins/grug-far.nix similarity index 100% rename from modules/neovim/plugins/grug-far.nix rename to profiles/neovim/plugins/grug-far.nix diff --git a/modules/neovim/plugins/harpoon.nix b/profiles/neovim/plugins/harpoon.nix similarity index 100% rename from modules/neovim/plugins/harpoon.nix rename to profiles/neovim/plugins/harpoon.nix diff --git a/modules/neovim/plugins/hunk.nix b/profiles/neovim/plugins/hunk.nix similarity index 100% rename from modules/neovim/plugins/hunk.nix rename to profiles/neovim/plugins/hunk.nix diff --git a/modules/neovim/plugins/lsp.nix b/profiles/neovim/plugins/lsp.nix similarity index 100% rename from modules/neovim/plugins/lsp.nix rename to profiles/neovim/plugins/lsp.nix diff --git a/modules/neovim/plugins/mini.nix b/profiles/neovim/plugins/mini.nix similarity index 100% rename from modules/neovim/plugins/mini.nix rename to profiles/neovim/plugins/mini.nix diff --git a/modules/neovim/plugins/oil.nix b/profiles/neovim/plugins/oil.nix similarity index 100% rename from modules/neovim/plugins/oil.nix rename to profiles/neovim/plugins/oil.nix diff --git a/modules/neovim/plugins/toggleterm.nix b/profiles/neovim/plugins/toggleterm.nix similarity index 100% rename from modules/neovim/plugins/toggleterm.nix rename to profiles/neovim/plugins/toggleterm.nix diff --git a/modules/neovim/plugins/treesitter.nix b/profiles/neovim/plugins/treesitter.nix similarity index 100% rename from modules/neovim/plugins/treesitter.nix rename to profiles/neovim/plugins/treesitter.nix diff --git a/modules/neovim/plugins/zk.nix b/profiles/neovim/plugins/zk.nix similarity index 100% rename from modules/neovim/plugins/zk.nix rename to profiles/neovim/plugins/zk.nix diff --git a/modules/nixos.nix b/profiles/nixos.nix similarity index 100% rename from modules/nixos.nix rename to profiles/nixos.nix diff --git a/modules/open-project.nix b/profiles/open-project.nix similarity index 100% rename from modules/open-project.nix rename to profiles/open-project.nix diff --git a/modules/opencode.nix b/profiles/opencode.nix similarity index 100% rename from modules/opencode.nix rename to profiles/opencode.nix diff --git a/modules/packages.nix b/profiles/packages.nix similarity index 100% rename from modules/packages.nix rename to profiles/packages.nix diff --git a/modules/ripgrep.nix b/profiles/ripgrep.nix similarity index 100% rename from modules/ripgrep.nix rename to profiles/ripgrep.nix diff --git a/modules/ssh.nix b/profiles/ssh.nix similarity index 100% rename from modules/ssh.nix rename to profiles/ssh.nix diff --git a/modules/starship.nix b/profiles/starship.nix similarity index 100% rename from modules/starship.nix rename to profiles/starship.nix diff --git a/profiles/syncthing.nix b/profiles/syncthing.nix new file mode 100644 index 0000000..92f135e --- /dev/null +++ b/profiles/syncthing.nix @@ -0,0 +1,53 @@ +{ + user, + pkgs, + ... +}: let + isDarwin = pkgs.stdenv.isDarwin; + homeDir = + if isDarwin + then "/Users/${user}" + else "/home/${user}"; + group = + if isDarwin + then "staff" + else "users"; +in { + services.syncthing = { + enable = true; + openDefaultPorts = !isDarwin; + dataDir = "${homeDir}/.local/share/syncthing"; + configDir = "${homeDir}/.config/syncthing"; + user = "${user}"; + group = group; + guiAddress = "0.0.0.0:8384"; + overrideFolders = true; + overrideDevices = true; + + settings = { + devices = { + "tahani" = { + id = "6B7OZZF-TEAMUGO-FBOELXP-Z4OY7EU-5ZHLB5T-V6Z3UDB-Q2DYR43-QBYW6QM"; + addresses = ["tcp://tahani:22000"]; + }; + "jason" = { + id = "42II2VO-QYPJG26-ZS3MB2I-AOPVZ67-JJNSE76-U54CO5Y-634A5OG-ECU4YQA"; + addresses = ["tcp://jason:22000"]; + }; + "chidi" = { + id = "N7W6SUT-QO6J4BE-T3Y65SM-OFGYGNV-TGYBJPX-JVN4Z72-AENZ247-KWXOQA6"; + addresses = ["tcp://chidi:22000"]; + }; + }; + + folders = { + "nixos-config" = { + path = "${homeDir}/nixos-config"; + devices = ["tahani" "jason" "chidi"]; + }; + }; + + options.globalAnnounceEnabled = false; + }; + }; +} diff --git a/modules/tailscale.nix b/profiles/tailscale.nix similarity index 100% rename from modules/tailscale.nix rename to profiles/tailscale.nix diff --git a/modules/wallpaper.nix b/profiles/wallpaper.nix similarity index 100% rename from modules/wallpaper.nix rename to profiles/wallpaper.nix diff --git a/modules/zellij.nix b/profiles/zellij.nix similarity index 100% rename from modules/zellij.nix rename to profiles/zellij.nix diff --git a/modules/zk.nix b/profiles/zk.nix similarity index 100% rename from modules/zk.nix rename to profiles/zk.nix diff --git a/modules/zoxide.nix b/profiles/zoxide.nix similarity index 100% rename from modules/zoxide.nix rename to profiles/zoxide.nix diff --git a/modules/zsh.nix b/profiles/zsh.nix similarity index 100% rename from modules/zsh.nix rename to profiles/zsh.nix