{ config, lib, pkgs, ... }: with lib; let keysPath = "/var/lib/wesher/secrets"; cfg = config.services.wesher; in { options = with types; { services.wesher = { enable = mkEnableOption "wesher wireguard overlay mesh network manager"; package = mkOption { type = package; default = pkgs.wesher; defaultText = literalExpression "pkgs.wesher"; description = "Wesher package to use."; }; clusterKey = mkOption { type = nullOr str; default = null; description = "shared key for cluster membership to use on first initialization, if no key was previously used by Wesher. Must be 32 bytes base64 encoded; will be generated if not provided. Setting this parameter value will not overwrite an existing cluster key; to do so please delete ${keysPath}"; }; bindAddr = mkOption { type = nullOr str; default = null; description = "IP address to bind to for cluster membership (cannot be used with --bind-iface)"; }; bindIface = mkOption { type = nullOr str; default = null; description = "Interface to bind to for cluster membership (cannot be used with --bind-addr)"; }; join = mkOption { type = listOf str; default = []; description = "list of hostnames or IP addresses to existing cluster members; if not provided, will attempt resuming any known state or otherwise wait for further members"; }; clusterPort = mkOption { type = port; default = 7946; description = "port used for membership gossip traffic (both TCP and UDP); must be the same accross cluster"; }; wireguardPort = mkOption { type = port; default = 51820; description = "port used for wireguard traffic (UDP); must be the same accross cluster"; }; overlayNet = mkOption { type = str; default = "10.0.0.0/8"; description = "the network in which to allocate addresses for the overlay mesh network (CIDR format); smaller networks increase the chance of IP collision"; }; interface = mkOption { type = str; default = "wgoverlay"; description = "name of the wireguard interface to create and manage"; }; logLevel = mkOption { type = str; default = "warn"; description = "set the verbosity (one of debug/info/warn/error)"; }; }; }; config = mkIf cfg.enable (let binWesher = cfg.package + "/bin/wesher"; in { system.activationScripts.wesher = if (cfg.clusterKey != null) then '' if [ ! -e ${keysPath} ] then mkdir --mode=700 -p ${builtins.dirOf keysPath} echo "WESHER_CLUSTER_KEY=${cfg.clusterKey}" > ${keysPath} fi '' else '' if [ ! -e ${keysPath} ] then mkdir --mode=700 -p ${builtins.dirOf keysPath} echo "WESHER_CLUSTER_KEY=$(head -c 32 /dev/urandom | base64)" > ${keysPath} fi ''; systemd.services.wesher = { description = "wesher wireguard overlay mesh network manager"; bindsTo = [ "network-online.target" ]; after = [ "network-online.target" ]; wantedBy = [ "multi-user.target" ]; environment = { WESHER_JOIN = builtins.concatStringsSep "," cfg.join; WESHER_CLUSTER_PORT = builtins.toString cfg.clusterPort; WESHER_WIREGUARD_PORT = builtins.toString cfg.wireguardPort; WESHER_OVERLAY_NET = cfg.overlayNet; WESHER_INTERFACE = cfg.interface; WESHER_LOG_LEVEL = cfg.logLevel; WESHER_NO_ETC_HOSTS = "true"; } // (if (cfg.bindAddr != null) then { WESHER_BIND_ADDR = cfg.bindAddr; } else {}) // (if (cfg.bindIface != null) then { WESHER_BIND_IFACE = cfg.bindIface; } else {}) ; serviceConfig = { ExecStart = "${binWesher}"; Restart = "always"; EnvironmentFile = keysPath; User = "wesher"; DynamicUser = true; StateDirectory = "wesher"; AmbientCapabilities = "CAP_NET_ADMIN CAP_NET_BIND_SERVICE"; CapabilityBoundingSet = "CAP_NET_ADMIN CAP_NET_BIND_SERVICE"; MemoryDenyWriteExecute = true; ProtectControlGroups = true; ProtectKernelModules = true; ProtectKernelTunables = true; RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK"; RestrictNamespaces = true; RestrictRealtime = true; SystemCallArchitectures = "native"; SystemCallFilter = "~@clock @cpu-emulation @debug @keyring @module @mount @obsolete @raw-io @resources"; }; }; networking.firewall.allowedUDPPorts = mkIf cfg.enable [ cfg.clusterPort cfg.wireguardPort ]; networking.firewall.allowedTCPPorts = mkIf cfg.enable [ cfg.clusterPort ]; }); }