From 645b022bcfecdb038233dd46d950b08eb32c9652 Mon Sep 17 00:00:00 2001 From: Danilo Reyes Date: Wed, 1 Apr 2026 20:32:31 -0600 Subject: [PATCH] nixformat + firewall rules to port forward qbittorrent --- config/overlay.nix | 4 +- hosts/server/configuration.nix | 73 +++++++++++++++++++++++++++++ hosts/vps/configuration.nix | 59 ++++++++++++++++++----- modules/scripts/download/common.nix | 3 +- modules/scripts/download/home.nix | 2 +- modules/scripts/download/nixos.nix | 2 - parts/packages.nix | 6 +-- 7 files changed, 127 insertions(+), 22 deletions(-) diff --git a/config/overlay.nix b/config/overlay.nix index 4d6c721..809abd5 100644 --- a/config/overlay.nix +++ b/config/overlay.nix @@ -59,7 +59,7 @@ in webcomix = prev.python3Packages.callPackage "${jawzScriptsSrc}/pkgs_pr/webcomix.nix" { }; download = prev.python3Packages.callPackage "${jawzScriptsSrc}/pkgs/download.nix" { gallery-dl = final.gallery-dl-dev; - webcomix = pyfinal.webcomix; + inherit (pyfinal) webcomix; }; }; }; @@ -71,7 +71,7 @@ in }; }); gallery-dl = final.gallery-dl-dev; - download = final.python3Packages.download; + inherit (final.python3Packages) download; inherit (pkgsU) code-cursor symbola diff --git a/hosts/server/configuration.nix b/hosts/server/configuration.nix index d6caae2..cb4cad9 100644 --- a/hosts/server/configuration.nix +++ b/hosts/server/configuration.nix @@ -7,6 +7,9 @@ }: let lidarrMbGapId = 968; + qbittorrentRouteTable = 200; + serverInterface = config.my.interfaces.server; + jawzUid = config.users.users.jawz.uid; in { imports = [ @@ -70,7 +73,11 @@ in }; networking = { hostName = "server"; + iproute2.rttablesExtraConfig = '' + ${toString qbittorrentRouteTable} qbittorrent + ''; wireguard.interfaces.wg0 = lib.mkIf config.my.secureHost { + allowedIPsAsRoutes = false; ips = [ "${config.my.ips.wg-server}/32" ]; privateKeyFile = config.sops.secrets."server/private".path; peers = [ @@ -78,6 +85,7 @@ in publicKey = "dFbiSekBwnZomarcS31o5+w6imHjMPNCipkfc2fZ3GY="; endpoint = "${config.my.ips.vps}:51820"; allowedIPs = [ + "0.0.0.0/0" "${config.my.ips.wg-vps}/32" config.my.subnets.wg-homelab config.my.subnets.wg-friends @@ -92,6 +100,71 @@ in interfaces.wg0.allowedTCPPorts = [ config.my.servers.nextcloud.port ]; }; }; + systemd.services.qbittorrent-vpn-routing = lib.mkIf config.my.secureHost { + description = "Route qBittorrent user traffic through the VPS WireGuard tunnel"; + wantedBy = [ "multi-user.target" ]; + after = [ "network-online.target" ]; + wants = [ "network-online.target" ]; + path = with pkgs; [ + coreutils + gnugrep + iproute2 + ]; + script = '' + for _ in $(seq 1 30); do + if ip -4 addr show dev wg0 >/dev/null 2>&1; then + break + fi + sleep 1 + done + + if ! ip -4 addr show dev wg0 >/dev/null 2>&1; then + echo "wg0 is not available" + exit 1 + fi + + ip -4 route replace ${config.my.subnets.wg-homelab} dev wg0 src ${config.my.ips.wg-server} + ip -4 route replace ${config.my.subnets.wg-friends} dev wg0 src ${config.my.ips.wg-server} + ip -4 route replace ${config.my.subnets.wg-guests} dev wg0 src ${config.my.ips.wg-server} + + ip -4 route replace table ${toString qbittorrentRouteTable} ${config.my.subnets.wg-homelab} dev wg0 src ${config.my.ips.wg-server} + ip -4 route replace table ${toString qbittorrentRouteTable} ${config.my.subnets.wg-friends} dev wg0 src ${config.my.ips.wg-server} + ip -4 route replace table ${toString qbittorrentRouteTable} ${config.my.subnets.wg-guests} dev wg0 src ${config.my.ips.wg-server} + ip -4 route replace table ${toString qbittorrentRouteTable} 10.88.0.0/16 dev podman0 src 10.88.0.1 + ip -4 route replace table ${toString qbittorrentRouteTable} 192.168.100.0/24 dev ${serverInterface} src ${config.my.ips.server} + ip -4 route replace table ${toString qbittorrentRouteTable} ${config.my.ips.vps}/32 via ${config.my.ips.router} dev ${serverInterface} src ${config.my.ips.server} + ip -4 route replace table ${toString qbittorrentRouteTable} default via ${config.my.ips.wg-vps} dev wg0 src ${config.my.ips.wg-server} + + while ip -4 rule show | grep -q 'uidrange ${toString jawzUid}-${toString jawzUid} lookup ${toString qbittorrentRouteTable}'; do + ip -4 rule del uidrange ${toString jawzUid}-${toString jawzUid} lookup ${toString qbittorrentRouteTable} + done + + while ip -4 rule show | grep -q 'from ${config.my.ips.wg-server} lookup ${toString qbittorrentRouteTable}'; do + ip -4 rule del from ${config.my.ips.wg-server}/32 lookup ${toString qbittorrentRouteTable} + done + + ip -4 rule add from ${config.my.ips.wg-server}/32 lookup ${toString qbittorrentRouteTable} priority 9999 + ip -4 rule add uidrange ${toString jawzUid}-${toString jawzUid} lookup ${toString qbittorrentRouteTable} priority 10000 + ''; + preStop = '' + while ip -4 rule show | grep -q 'from ${config.my.ips.wg-server} lookup ${toString qbittorrentRouteTable}'; do + ip -4 rule del from ${config.my.ips.wg-server}/32 lookup ${toString qbittorrentRouteTable} || true + done + + while ip -4 rule show | grep -q 'uidrange ${toString jawzUid}-${toString jawzUid} lookup ${toString qbittorrentRouteTable}'; do + ip -4 rule del uidrange ${toString jawzUid}-${toString jawzUid} lookup ${toString qbittorrentRouteTable} || true + done + + ip -4 route del ${config.my.subnets.wg-homelab} dev wg0 || true + ip -4 route del ${config.my.subnets.wg-friends} dev wg0 || true + ip -4 route del ${config.my.subnets.wg-guests} dev wg0 || true + ip -4 route flush table ${toString qbittorrentRouteTable} || true + ''; + serviceConfig = { + RemainAfterExit = true; + Type = "oneshot"; + }; + }; users.users.jawz.packages = builtins.attrValues { inherit (pkgs) podman-compose attic-client; }; diff --git a/hosts/vps/configuration.nix b/hosts/vps/configuration.nix index b4e51fd..0c0e9ab 100644 --- a/hosts/vps/configuration.nix +++ b/hosts/vps/configuration.nix @@ -23,6 +23,7 @@ let }; ports = { inherit (config.my.ports) giteaSsh; + inherit (config.my.ports) qbittorrent; inherit (config.my.ports) ssh; web = [ 80 @@ -33,7 +34,6 @@ let synapseFederation = config.my.ports.synapseSsl; }; portsStr = { - giteaSsh = toString ports.giteaSsh; syncthing = toString ports.syncthing; synapseFederation = toString ports.synapseFederation; synapseClient = toString config.my.servers.synapse.port; @@ -48,6 +48,38 @@ let 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 { imports = [ @@ -99,14 +131,8 @@ in nat = { inherit externalInterface; enable = true; - internalInterfaces = [ "wg0" ]; - forwardPorts = [ - { - sourcePort = ports.giteaSsh; - proto = "tcp"; - destination = "${ips.homeServer}:${portsStr.giteaSsh}"; - } - ]; + internalInterfaces = [ wgInterface ]; + forwardPorts = map mkForwardPort forwardedPorts; }; nftables = { enable = true; @@ -115,7 +141,8 @@ in content = '' chain postrouting { type nat hook postrouting priority srcnat; - iifname "${externalInterface}" oifname "${wgInterface}" ip daddr ${ips.homeServer}/32 tcp dport ${portsStr.giteaSsh} masquerade comment "snat ssh forward" + iifname "${wgInterface}" oifname "${externalInterface}" ip saddr ${subnets.wgHomelab} masquerade comment "snat homelab egress" + ${lib.concatStringsSep "\n " (map mkSnatRule forwardedPorts)} } ''; }; @@ -124,8 +151,15 @@ in enable = true; filterForward = true; checkReversePath = "loose"; - allowedTCPPorts = [ ports.ssh ] ++ ports.web; - allowedUDPPorts = [ ports.wg ]; + 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 @@ -142,6 +176,7 @@ in 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 diff --git a/modules/scripts/download/common.nix b/modules/scripts/download/common.nix index 4313595..f45c9f7 100644 --- a/modules/scripts/download/common.nix +++ b/modules/scripts/download/common.nix @@ -1,11 +1,10 @@ { config, - inputs, lib, pkgs, }: let - download = pkgs.download; + inherit (pkgs) download; gallerySecretsPath = lib.attrByPath [ "sops" "secrets" "gallery-dl/secrets" "path" ] null config; in { diff --git a/modules/scripts/download/home.nix b/modules/scripts/download/home.nix index a97fc8e..807d968 100644 --- a/modules/scripts/download/home.nix +++ b/modules/scripts/download/home.nix @@ -12,7 +12,7 @@ let inputs.self.lib.hmOnlyUser config osConfig "jawz" && (osConfig.my.units.download.enable || osConfig.my.units.downloadManga.enable); download = import ./common.nix { - inherit inputs lib pkgs; + inherit lib pkgs; config = if osConfig == null then { } else osConfig; }; in diff --git a/modules/scripts/download/nixos.nix b/modules/scripts/download/nixos.nix index 6df8500..b276ef2 100644 --- a/modules/scripts/download/nixos.nix +++ b/modules/scripts/download/nixos.nix @@ -1,5 +1,4 @@ { - inputs, pkgs, lib, config, @@ -9,7 +8,6 @@ let download = import ./common.nix { inherit config - inputs lib pkgs ; diff --git a/parts/packages.nix b/parts/packages.nix index d9ba864..45d3008 100644 --- a/parts/packages.nix +++ b/parts/packages.nix @@ -61,10 +61,10 @@ packages = (inputs.jawz-scripts.packages.${system} or { }) // { - download = pkgs.download; + inherit (pkgs) download; doom-emacs = portableEmacs.package; - gallery-dl = pkgs.gallery-dl; - gallery-dl-dev = pkgs.gallery-dl-dev; + inherit (pkgs) gallery-dl; + inherit (pkgs) gallery-dl-dev; mcp-tests = mcpTests; nixos-mcp = nixosMcp; nixos-mcp-server = mcpServerPkg;