{ config, inputs, lib, pkgs, this, ... }: with lib; let cfg = config.nixfiles.modules.wireguard-ng; DNSSetup = optionalString config.services.resolved.enable ( let resolvectl = "${config.systemd.package}/bin/resolvectl"; in '' ${resolvectl} dns ${cfg.interface} ${cfg.server.ipv6.address} ${cfg.server.ipv4.address} ${resolvectl} domain ${cfg.interface} local ${my.domain.shire} ${resolvectl} dnssec ${cfg.interface} no ${resolvectl} dnsovertls ${cfg.interface} no '' ); in { options.nixfiles.modules.wireguard-ng = { client = { enable = mkEnableOption "WireGuard client"; enableTrafficRouting = mkOption { description = "Whether to enable traffic routing through the sever."; type = with types; bool; # default = !this.isHeadless; default = false; }; }; server = { enable = mkEnableOption "WireGuard server"; ipv4.address = mkOption { description = "IPv4 address to bind to."; type = with types; str; default = my.configurations.tulkas.wireguard-ng.ipv4.address; }; ipv6.address = mkOption { description = "IPv4 address to bind to."; type = with types; str; default = my.configurations.tulkas.wireguard-ng.ipv6.address; }; address = mkOption { description = "Endpoint address to use"; type = with types; str; default = my.configurations.tulkas.ipv4.address; }; port = mkOption { description = "Endpoint port to use."; type = with types; int; default = 7070; }; publicKey = mkOption { description = "Server's public key."; type = with types; str; default = my.configurations.tulkas.wireguard.publicKey; }; peers = mkOption { description = "List of peers."; type = with types; listOf attrs; default = mapAttrsToList ( _: attr: with attr; { inherit (wireguard-ng) publicKey; allowedIPs = with wireguard-ng; [ "${ipv6.address}/128" "${ipv4.address}/32" ]; } ) ( filterAttrs ( _: attr: attr.hostname != this.hostname && hasAttr "wireguard-ng" attr ) my.configurations ); }; }; interface = mkOption { description = "Name of the interface to use WireGuard with."; type = with types; str; default = "wg70"; }; ipv4.subnet = mkOption { description = "CIDR notation for the IPv4 subnet to use over WireGuard."; type = with types; str; default = "10.70.0.0/16"; }; ipv6.subnet = mkOption { description = "CIDR notation for the IPv6 subnet to use over WireGuard."; type = with types; str; default = "fd70::/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-ng; { 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.ipv6.subnet cfg.ipv4.subnet ]; } ]; dns = [ ipv6.address ipv4.address ]; postUp = DNSSetup; }) ]; environment.systemPackages = with pkgs; [ (writeShellApplication { name = "wg-toggle-ng"; runtimeInputs = [ iproute2 jq ]; text = '' ip46() { sudo ip -4 "$@" sudo ip -6 "$@" } fwmark=$(sudo awg 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 = [ "${ipv6.address}/16" "${ipv4.address}/16" ]; listenPort = port; inherit peers; postSetup = DNSSetup; 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-ng.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-ng" v) my.configurations ); }; }) ]; }