{ config, lib, ... }: let externalInterface = config.my.interfaces.${config.networking.hostName}; wgInterface = "wg0"; ips = { homeServer = config.my.ips.wg-server; wgWorkstation = config.my.ips.wg-workstation; wgFriend1 = config.my.ips.wg-friend1; wgFriend6 = config.my.ips.wg-friend6; }; subnets = { wgFriends = config.my.subnets.wg-friends; wgGuests = config.my.subnets.wg-guests; wgHomelab = config.my.subnets.wg-homelab; }; ports = { inherit (config.my.ports) giteaSsh qbittorrent ssh wg ; web = [ 80 443 ]; syncthing = config.my.ports.syncthingRelay; synapseFederation = config.my.ports.synapseSsl; }; portsStr = { syncthing = toString config.my.ports.syncthingRelay; synapseFederation = toString config.my.ports.synapseSsl; synapseClient = toString config.my.servers.synapse.port; syncplay = toString config.my.servers.syncplay.port; synctube = toString config.my.servers.synctube.port; stash = toString config.my.servers.stash.port; jellyfin = toString config.my.servers.jellyfin.port; audiobookshelf = toString config.my.servers.audiobookshelf.port; kavita = toString config.my.servers.kavita.port; openWebui = toString config.my.ports.openWebui; sillytavern = toString config.my.ports.sillytavern; ollama = toString config.my.ports.ollama; comfyui = toString config.my.ports.comfyui; }; forwardedPorts = [ { comment = "snat ssh forward"; port = ports.giteaSsh; proto = "tcp"; } { comment = "snat qbittorrent tcp forward"; port = ports.qbittorrent; proto = "tcp"; } { comment = "snat qbittorrent udp forward"; port = ports.qbittorrent; proto = "udp"; } ]; mkForwardPort = { port, proto, ... }: { sourcePort = port; inherit proto; destination = "${ips.homeServer}:${toString port}"; }; mkSnatRule = { comment, port, proto, ... }: ''iifname "${externalInterface}" oifname "${wgInterface}" ip daddr ${ips.homeServer}/32 ${proto} dport ${toString port} masquerade comment "${comment}"''; in { networking = { nat = { inherit externalInterface; enable = true; internalInterfaces = [ wgInterface ]; forwardPorts = map mkForwardPort forwardedPorts; }; nftables = { enable = true; tables.vps-snat = { family = "ip"; content = '' chain postrouting { type nat hook postrouting priority srcnat; iifname "${wgInterface}" oifname "${externalInterface}" ip saddr ${subnets.wgHomelab} masquerade comment "snat homelab egress" ${lib.concatStringsSep "\n " (map mkSnatRule forwardedPorts)} } ''; }; }; firewall = { enable = true; filterForward = true; checkReversePath = "loose"; allowedTCPPorts = [ ports.ssh ports.qbittorrent ] ++ ports.web; allowedUDPPorts = [ ports.wg ports.qbittorrent ]; extraForwardRules = '' iifname "${wgInterface}" ip saddr ${subnets.wgFriends} ip daddr ${ips.homeServer}/32 tcp dport ${portsStr.syncthing} accept iifname "${wgInterface}" ip saddr ${ips.homeServer}/32 ip daddr ${subnets.wgFriends} tcp dport ${portsStr.syncthing} accept iifname "${wgInterface}" ip saddr ${subnets.wgFriends} ip daddr ${ips.homeServer}/32 tcp dport { ${portsStr.synapseClient}, ${portsStr.synapseFederation}, ${portsStr.syncplay}, ${portsStr.synctube} } accept iifname "${wgInterface}" ip saddr ${subnets.wgFriends} ip daddr ${ips.homeServer}/32 icmp type echo-request accept iifname "${wgInterface}" ip saddr ${ips.wgFriend1}/32 ip daddr ${ips.homeServer}/32 tcp dport ${portsStr.stash} accept iifname "${wgInterface}" ip saddr ${ips.wgFriend6}/32 ip daddr ${ips.homeServer}/32 tcp dport ${portsStr.stash} accept iifname "${wgInterface}" ip saddr ${subnets.wgGuests} ip daddr ${ips.homeServer}/32 tcp dport { ${portsStr.stash}, ${portsStr.jellyfin}, ${portsStr.audiobookshelf}, ${portsStr.kavita} } accept iifname "${wgInterface}" ip saddr ${subnets.wgGuests} ip daddr ${ips.homeServer}/32 icmp type echo-request accept iifname "${wgInterface}" ip saddr ${subnets.wgHomelab} ip daddr ${ips.homeServer}/32 accept iifname "${wgInterface}" ip saddr ${subnets.wgHomelab} ip daddr ${ips.wgWorkstation}/32 tcp dport { ${portsStr.openWebui}, ${portsStr.sillytavern}, ${portsStr.ollama}, ${portsStr.comfyui} } accept iifname "${wgInterface}" ip saddr ${ips.wgWorkstation}/32 ip daddr ${subnets.wgHomelab} tcp sport { ${portsStr.openWebui}, ${portsStr.sillytavern}, ${portsStr.ollama}, ${portsStr.comfyui} } accept iifname "${wgInterface}" ip saddr ${subnets.wgHomelab} oifname "${externalInterface}" accept iifname "${wgInterface}" ip saddr ${subnets.wgFriends} oifname "${externalInterface}" accept iifname "${wgInterface}" ip saddr ${subnets.wgGuests} oifname "${externalInterface}" accept ip saddr ${subnets.wgFriends} ip daddr ${subnets.wgHomelab} drop ip saddr ${subnets.wgHomelab} ip daddr ${subnets.wgFriends} drop ip saddr ${subnets.wgGuests} ip daddr ${subnets.wgHomelab} drop ip saddr ${subnets.wgHomelab} ip daddr ${subnets.wgGuests} drop ip saddr ${subnets.wgGuests} ip daddr ${subnets.wgFriends} drop ip saddr ${subnets.wgFriends} ip daddr ${subnets.wgGuests} drop ''; }; }; }