{ config, inputs, lib, pkgs, this, ... }: with lib; let cfg = config.nixfiles.modules.wireguard; DNSSetup = let resolvectl = "${config.systemd.package}/bin/resolvectl"; in '' ${resolvectl} dns ${cfg.interface} ${cfg.server.ipv6.address} ${cfg.server.ipv4.address} ${resolvectl} domain ${cfg.interface} ${my.domain.shire} ${resolvectl} dnssec ${cfg.interface} no ${resolvectl} dnsovertls ${cfg.interface} no ''; in { disabledModules = [ "services/networking/wg-quick.nix" "services/networking/wireguard.nix" ]; imports = [ "${inputs.nixpkgs-amneziawg}/nixos/modules/services/networking/wg-quick.nix" "${inputs.nixpkgs-amneziawg}/nixos/modules/services/networking/wireguard.nix" ]; 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; [ "${ipv6.address}/128" "${ipv4.address}/32" ]; } ) ( 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; { type = "amneziawg"; privateKeyFile = config.secrets."wireguard-private-key-${this.hostname}".path; address = [ "${ipv4.address}/16" "${ipv6.address}/16" ]; extraInterfaceConfig = '' jc = 228 jmin = 42 jmax = 420 s1 = 69 s2 = 96 ''; }) (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"; runtimeInputs = [ amneziawg-tools 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; type = "amneziawg"; 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 + '' awg set ${cfg.interface} \ jc 228 \ jmin 42 \ jmax 420 \ s1 69 \ s2 96 ''; 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 = false; # TODO Doesn't work with amneziawg-tools. 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); }; }) ]; }