{ config, inputs, lib, pkgs, this, ... }: with lib; let cfg = config.nixfiles.modules.wireguard; in { options.nixfiles.modules.wireguard = { client = { enable = mkEnableOption "WireGuard client"; enableTrafficRouting = mkOption { description = "Whether to enable traffic routing through the sever."; type = with types; bool; default = !this.isHeadless; }; }; server = { enable = mkEnableOption "WireGuard server"; ipv4.address = mkOption { description = "IPv4 address to bind to."; type = with types; str; default = my.configurations.manwe.wireguard.ipv4.address; }; ipv6.address = mkOption { description = "IPv4 address to bind to."; type = with types; str; default = my.configurations.manwe.wireguard.ipv6.address; }; address = mkOption { description = "Endpoint address to use"; type = with types; str; default = my.configurations.manwe.ipv4.address; }; port = mkOption { description = "Endpoint port to use."; type = with types; int; default = 6969; }; publicKey = mkOption { description = "Server's public key."; type = with types; str; default = my.configurations.manwe.wireguard.publicKey; }; peers = mkOption { description = "List of peers."; type = with types; listOf attrs; default = mapAttrsToList ( _: attr: with attr; { inherit (wireguard) publicKey; allowedIPs = with wireguard; [ "${ipv4.address}/32" "${ipv6.address}/128" ]; } ) ( filterAttrs (_: attr: attr.hostname != this.hostname && hasAttr "wireguard" attr) my.configurations ); }; }; interface = mkOption { description = "Name of the interface to use WireGuard with."; type = with types; str; default = "wg69"; }; ipv4.subnet = mkOption { description = "CIDR notation for the IPv4 subnet to use over WireGuard."; type = with types; str; default = "10.69.0.0/16"; }; ipv6.subnet = mkOption { description = "CIDR notation for the IPv6 subnet to use over WireGuard."; type = with types; str; default = "fd69::/16"; }; }; config = { assertions = [ { assertion = config.security.sudo.enable; message = "Sudo is not enabled."; } { assertion = any (x: x == "wheel") config.my.extraGroups; message = ''User is not in the "wheel" group.''; } ]; } // mkMerge [ (mkIf (cfg.client.enable || cfg.server.enable) { secrets."wireguard-private-key-${this.hostname}".file = "${inputs.self}/secrets/wireguard-private-key-${this.hostname}"; networking.firewall.trustedInterfaces = [ cfg.interface ]; topology = with cfg; { nodes.${this.hostname}.interfaces.${interface} = { network = interface; icon = "interfaces.wireguard"; }; }; }) (mkIf cfg.client.enable { networking.wg-quick.interfaces.${cfg.interface} = mkMerge [ (with this.wireguard; { privateKeyFile = config.secrets."wireguard-private-key-${this.hostname}".path; address = [ "${ipv4.address}/16" "${ipv6.address}/16" ]; }) (with cfg.server; { peers = [ { inherit publicKey; endpoint = "${address}:${toString port}"; allowedIPs = if cfg.client.enableTrafficRouting then [ "0.0.0.0/0" "::/0" ] else [ cfg.ipv4.subnet cfg.ipv6.subnet ]; persistentKeepalive = 25; } ]; dns = [ ipv4.address ipv6.address ]; # This assumes that the host has Unbound running. }) ]; environment.systemPackages = with pkgs; [ (writeShellApplication { name = "wg-toggle"; runtimeInputs = [ iproute2 jq wireguard-tools ]; text = '' ip46() { sudo ip -4 "$@" sudo ip -6 "$@" } fwmark=$(sudo wg show ${cfg.interface} fwmark) || exit if ip -j rule list lookup "$fwmark" | jq -e 'length > 0' >/dev/null; then ip46 rule del lookup main suppress_prefixlength 0 ip46 rule del lookup "$fwmark" else ip46 rule add not fwmark "$fwmark" lookup "$fwmark" ip46 rule add lookup main suppress_prefixlength 0 fi ''; }) ]; }) (mkIf cfg.server.enable { networking = { wireguard = { enable = true; interfaces.${cfg.interface} = with cfg.server; { privateKeyFile = config.secrets."wireguard-private-key-${this.hostname}".path; ips = [ "${ipv4.address}/16" "${ipv6.address}/16" ]; listenPort = port; inherit peers; allowedIPsAsRoutes = false; }; }; nat = { enable = true; enableIPv6 = true; externalInterface = mkDefault "eth0"; internalInterfaces = [ cfg.interface ]; internalIPs = [ cfg.ipv4.subnet ]; internalIPv6s = [ cfg.ipv6.subnet ]; }; firewall.allowedUDPPorts = [ cfg.server.port ]; }; services.prometheus.exporters.wireguard = { enable = true; listenAddress = mkDefault this.wireguard.ipv4.address; withRemoteIp = true; port = 9586; }; topology = with cfg; { networks = { ${interface} = { name = interface; cidrv4 = ipv4.subnet; cidrv6 = ipv6.subnet; icon = "interfaces.wireguard"; }; }; nodes.${this.hostname}.interfaces.${interface}.physicalConnections = mapAttrsToList ( name: _: config.lib.topology.mkConnection name interface ) (filterAttrs (n: v: !v.isOther && n != this.hostname && hasAttr "wireguard" v) my.configurations); }; }) ]; }