aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xdeploy_nixos4
-rw-r--r--nix/deuxfleurs.nix25
-rw-r--r--nix/wgautomesh.nix254
3 files changed, 160 insertions, 123 deletions
diff --git a/deploy_nixos b/deploy_nixos
index b716993..a35fec3 100755
--- a/deploy_nixos
+++ b/deploy_nixos
@@ -7,9 +7,9 @@ copy cluster/$CLUSTER/cluster.nix /etc/nixos/cluster.nix
copy cluster/$CLUSTER/node/$NIXHOST.nix /etc/nixos/node.nix
copy cluster/$CLUSTER/node/$NIXHOST.site.nix /etc/nixos/site.nix
-cmd mkdir -p /var/lib/wgautomesh
-write_pass deuxfleurs/cluster/$CLUSTER/wgautomesh_gossip_secret /var/lib/wgautomesh/gossip_secret
copy nix/wgautomesh.nix /etc/nixos/wgautomesh.nix
+cmd mkdir -p /var/lib/deuxfleurs
+write_pass deuxfleurs/cluster/$CLUSTER/wgautomesh_gossip_secret /var/lib/deuxfleurs/wgautomesh_gossip_secret
if [ "$CLUSTER" = "staging" ]; then
copy nix/nomad-driver-nix2.nix /etc/nixos/nomad-driver-nix2.nix
diff --git a/nix/deuxfleurs.nix b/nix/deuxfleurs.nix
index 7632486..b174b97 100644
--- a/nix/deuxfleurs.nix
+++ b/nix/deuxfleurs.nix
@@ -207,18 +207,19 @@ in
};
services.wgautomesh = {
enable = true;
- interface = "wg0";
- gossipPort = 1666;
- gossipSecretFile = "/var/lib/wgautomesh/gossip_secret";
- persistFile = "/var/lib/wgautomesh/state";
- upnpForwardPublicPort =
- if clusterNodeCfg.endpoint != null then
- strings.toInt (lists.last (split ":" clusterNodeCfg.endpoint))
- else null;
- peers = attrValues (mapAttrs (hostname: { publicKey, endpoint, address, ... }: {
- inherit address endpoint;
- pubkey = publicKey;
- }) cfg.clusterNodes);
+ gossipSecretFile = "/var/lib/deuxfleurs/wgautomesh_gossip_secret";
+ settings = {
+ interface = "wg0";
+ gossip_port = 1666;
+ upnp_forward_external_port =
+ if clusterNodeCfg.endpoint != null then
+ strings.toInt (lists.last (split ":" clusterNodeCfg.endpoint))
+ else null;
+ peers = attrValues (mapAttrs (hostname: { publicKey, endpoint, address, ... }: {
+ inherit address endpoint;
+ pubkey = publicKey;
+ }) cfg.clusterNodes);
+ };
};
# Old code for wg-quick, we can use this as a fallback if we fail to make wgautomesh work
# systemd.services."wg-quick-wg0".after = [ "unbound.service" ];
diff --git a/nix/wgautomesh.nix b/nix/wgautomesh.nix
index 55aa73f..b7125f2 100644
--- a/nix/wgautomesh.nix
+++ b/nix/wgautomesh.nix
@@ -7,123 +7,159 @@ in
{ lib, config, pkgs, ... }:
with lib;
-let
+let
cfg = config.services.wgautomesh;
+ settingsFormat = pkgs.formats.toml { };
+ configFile =
+ # Have to remove nulls manually as TOML generator will not just skip key
+ # if value is null
+ settingsFormat.generate "wgautomesh-config.toml"
+ (filterAttrs (k: v: v != null)
+ (mapAttrs
+ (k: v:
+ if k == "peers"
+ then map (e: filterAttrs (k: v: v != null) e) v
+ else v)
+ cfg.settings));
+ runtimeConfigFile =
+ if cfg.enableGossipEncryption
+ then "/run/wgautomesh/wgautomesh.toml"
+ else configFile;
in
- with builtins;
- {
- options.services.wgautomesh = {
- enable = mkEnableOption "wgautomesh";
- logLevel = mkOption {
- type = types.enum [ "trace" "debug" "info" "warn" "error" ];
- default = "info";
- description = "wgautomesh log level (trace/debug/info/warn/error)";
- };
- interface = mkOption {
- type = types.str;
- description = "Wireguard interface to manage";
- };
- gossipPort = mkOption {
- type = types.port;
- description = "wgautomesh gossip port";
- };
- gossipSecretFile = mkOption {
- type = types.nullOr types.str;
- description = "File containing the gossip secret encryption key";
- };
- persistFile = mkOption {
- type = types.nullOr types.str;
- description = "Path where to persist known peer addresses";
- };
- lanDiscovery = mkOption {
- type = types.bool;
- default = true;
- description = "Enable discovery using LAN broadcast";
- };
- openFirewall = mkOption {
- type = types.bool;
- default = true;
- description = "Automatically open gossip port in firewall";
- };
- upnpForwardPublicPort = mkOption {
- type = types.nullOr types.port;
- default = null;
- description = "Public port number to try to redirect to this machine using UPnP IGD";
- };
- peers = mkOption {
- type = types.listOf (types.submodule {
- options = {
- pubkey = mkOption {
- type = types.str;
- description = "Wireguard public key";
- };
- address = mkOption {
- type = types.str;
- description = "Wireguard peer address";
- };
- endpoint = mkOption {
- type = types.nullOr types.str;
- description = "bootstrap endpoint";
- };
+{
+ options.services.wgautomesh = {
+ enable = mkEnableOption (mdDoc "the wgautomesh daemon");
+ logLevel = mkOption {
+ type = types.enum [ "trace" "debug" "info" "warn" "error" ];
+ default = "info";
+ description = mdDoc "wgautomesh log level.";
+ };
+ enableGossipEncryption = mkOption {
+ type = types.bool;
+ default = true;
+ description = mdDoc "Enable encryption of gossip traffic.";
+ };
+ gossipSecretFile = mkOption {
+ type = types.path;
+ description = mdDoc ''
+ File containing the shared secret key to use for gossip encryption.
+ Required if `enableGossipEncryption` is set.
+ '';
+ };
+ enablePersistence = mkOption {
+ type = types.bool;
+ default = true;
+ description = mdDoc "Enable persistence of Wireguard peer info between restarts.";
+ };
+ openFirewall = mkOption {
+ type = types.bool;
+ default = true;
+ description = mdDoc "Automatically open gossip port in firewall (recommended).";
+ };
+ settings = mkOption {
+ type = types.submodule {
+ freeformType = settingsFormat.type;
+ options = {
+ interface = mkOption {
+ type = types.str;
+ description = mdDoc ''
+ Wireguard interface to manage (it is NOT created by wgautomesh, you
+ should use another NixOS option to create it such as
+ `networking.wireguard.interfaces.wg0 = {...};`).
+ '';
+ example = "wg0";
+ };
+ gossip_port = mkOption {
+ type = types.port;
+ description = mdDoc ''
+ wgautomesh gossip port, this MUST be the same number on all nodes in
+ the wgautomesh network.
+ '';
+ default = 1666;
+ };
+ lan_discovery = mkOption {
+ type = types.bool;
+ default = true;
+ description = mdDoc "Enable discovery of peers on the same LAN using UDP broadcast.";
};
- });
- description = "wgautomesh peer list";
+ upnp_forward_external_port = mkOption {
+ type = types.nullOr types.port;
+ default = null;
+ description = mdDoc ''
+ Public port number to try to redirect to this machine's Wireguard
+ daemon using UPnP IGD.
+ '';
+ };
+ peers = mkOption {
+ type = types.listOf (types.submodule {
+ options = {
+ pubkey = mkOption {
+ type = types.str;
+ description = mdDoc "Wireguard public key of this peer.";
+ };
+ address = mkOption {
+ type = types.str;
+ description = mdDoc ''
+ Wireguard address of this peer (a single IP address, multliple
+ addresses or address ranges are not supported).
+ '';
+ example = "10.0.0.42";
+ };
+ endpoint = mkOption {
+ type = types.nullOr types.str;
+ description = mdDoc ''
+ Bootstrap endpoint for connecting to this Wireguard peer if no
+ other address is known or none are working.
+ '';
+ default = null;
+ example = "wgnode.mydomain.example:51820";
+ };
+ };
+ });
+ default = [ ];
+ description = mdDoc "wgautomesh peer list.";
+ };
+ };
};
+ default = { };
+ description = mdDoc "Configuration for wgautomesh.";
};
+ };
- config = mkIf cfg.enable (
- let
- peerDefs = map (peer:
- let endpointDef = if peer.endpoint == null then ""
- else ''endpoint = "${peer.endpoint}"'';
- in
- ''
- [[peers]]
- pubkey = "${peer.pubkey}"
- address = "${peer.address}"
- ${endpointDef}
- '') cfg.peers;
- extraDefs = (if cfg.lanDiscovery then ["lan_discovery = true"] else [])
- ++ (if (cfg.gossipSecretFile != null)
- then [''gossip_secret_file = "${cfg.gossipSecretFile}"''] else [])
- ++ (if (cfg.persistFile != null)
- then [''persist_file = "${cfg.persistFile}"''] else [])
- ++ (if (cfg.upnpForwardPublicPort != null)
- then [''upnp_forward_external_port = ${toString cfg.upnpForwardPublicPort}''] else []);
- configfile = pkgs.writeText "wgautomesh.toml" ''
- interface = "${cfg.interface}"
- gossip_port = ${toString cfg.gossipPort}
- ${concatStringsSep "\n" extraDefs}
+ config = mkIf cfg.enable {
+ services.wgautomesh.settings = {
+ gossip_secret_file = mkIf cfg.enableGossipEncryption "$CREDENTIALS_DIRECTORY/gossip_secret";
+ persist_file = mkIf cfg.enablePersistence "/var/lib/wgautomesh/state";
+ };
- ${concatStringsSep "\n" peerDefs}
- '';
- in {
- systemd.services.wgautomesh = {
- enable = true;
- path = [ pkgs.wireguard-tools ];
- environment = {
- RUST_LOG = "wgautomesh=${cfg.logLevel}";
- };
- description = "wgautomesh";
- serviceConfig = {
- Type = "simple";
+ systemd.services.wgautomesh = {
+ path = [ pkgs.wireguard-tools ];
+ environment = { RUST_LOG = "wgautomesh=${cfg.logLevel}"; };
+ description = "wgautomesh";
+ serviceConfig = {
+ Type = "simple";
- ExecStart = "${wgautomesh}/bin/wgautomesh ${configfile}";
- Restart = "always";
- RestartSec = "30";
+ ExecStart = "${wgautomesh}/bin/wgautomesh ${runtimeConfigFile}";
+ Restart = "always";
+ RestartSec = "30";
+ LoadCredential = mkIf cfg.enableGossipEncryption [ "gossip_secret:${cfg.gossipSecretFile}" ];
- ExecStartPre = [ "+${pkgs.coreutils}/bin/chown wgautomesh /var/lib/wgautomesh/gossip_secret" ];
+ ExecStartPre = mkIf cfg.enableGossipEncryption [
+ ''${pkgs.envsubst}/bin/envsubst \
+ -i ${configFile} \
+ -o ${runtimeConfigFile}''
+ ];
- DynamicUser = true;
- User = "wgautomesh";
- StateDirectory = "wgautomesh";
- StateDirectoryMode = "0700";
- AmbientCapabilities = "CAP_NET_ADMIN";
- CapabilityBoundingSet = "CAP_NET_ADMIN";
- };
- wantedBy = [ "multi-user.target" ];
+ DynamicUser = true;
+ StateDirectory = "wgautomesh";
+ StateDirectoryMode = "0700";
+ RuntimeDirectory = "wgautomesh";
+ AmbientCapabilities = "CAP_NET_ADMIN";
+ CapabilityBoundingSet = "CAP_NET_ADMIN";
};
- networking.firewall.allowedUDPPorts = mkIf cfg.openFirewall [ cfg.gossipPort ];
- });
- }
-
+ wantedBy = [ "multi-user.target" ];
+ };
+ networking.firewall.allowedUDPPorts =
+ mkIf cfg.openFirewall [ cfg.settings.gossip_port ];
+ };
+}