diff --git a/modules/nixos/guests/torrent/default.nix b/modules/nixos/guests/torrent/default.nix index fe7f6a5..875764a 100755 --- a/modules/nixos/guests/torrent/default.nix +++ b/modules/nixos/guests/torrent/default.nix @@ -125,9 +125,109 @@ in "d ${serviceCfg.varPaths.path0}/downloads 755 ${serviceCfg.name} ${serviceCfg.name} -" ]; - services.qbittorrent = { - after = [ "wg-quick-wg0.service" ]; - requires = [ "wg-quick-wg0.service" ]; + services = { + qbittorrent = { + after = [ "wg-quick-wg0.service" ]; + requires = [ "wg-quick-wg0.service" ]; + }; + + natpmp-portforward = { + description = "NAT-PMP Port Forwarding for VPN"; + after = [ + "wg-quick-wg0.service" + "qbittorrent.service" + ]; + requires = [ + "wg-quick-wg0.service" + "qbittorrent.service" + ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + Type = "simple"; + Restart = "always"; + RestartSec = "10s"; + }; + script = '' + PASSWORD=$(cat /run/secrets/qbittorrent-pass) + # Wait for qBittorrent to be ready + echo "Waiting for qBittorrent to start..." + sleep 10 + + # Enable IP forwarding + echo 1 > /proc/sys/net/ipv4/ip_forward + + while true; do + echo "Requesting port forwarding from VPN..." + + # Get the forwarded port for UDP + UDP_OUTPUT=$(${pkgs.libnatpmp}/bin/natpmpc -a 1 0 udp 60 -g 10.2.0.1 2>&1) + UDP_PORT=$(echo "$UDP_OUTPUT" | ${pkgs.gnugrep}/bin/grep "Mapped public port" | ${pkgs.gawk}/bin/awk '{print $4}' | head -1) + + # Get the forwarded port for TCP + TCP_OUTPUT=$(${pkgs.libnatpmp}/bin/natpmpc -a 1 0 tcp 60 -g 10.2.0.1 2>&1) + TCP_PORT=$(echo "$TCP_OUTPUT" | ${pkgs.gnugrep}/bin/grep "Mapped public port" | ${pkgs.gawk}/bin/awk '{print $4}' | head -1) + + if [ -n "$UDP_PORT" ] && [ -n "$TCP_PORT" ]; then + echo "Port forwarding successful: UDP=$UDP_PORT, TCP=$TCP_PORT" + + # Clear old PREROUTING rules to avoid duplicates + ${pkgs.iptables}/bin/iptables -t nat -D PREROUTING -i enp0s5 -p tcp -j DNAT --to-destination 10.2.0.2 2>/dev/null || true + ${pkgs.iptables}/bin/iptables -t nat -D PREROUTING -i enp0s5 -p udp -j DNAT --to-destination 10.2.0.2 2>/dev/null || true + + # Forward traffic from enp0s5 (VM's main interface) to wg0 (WireGuard interface) + ${pkgs.iptables}/bin/iptables -t nat -A PREROUTING -i enp0s5 -p tcp --dport "$TCP_PORT" -j DNAT --to-destination 10.2.0.2:$TCP_PORT + ${pkgs.iptables}/bin/iptables -t nat -A PREROUTING -i enp0s5 -p udp --dport "$UDP_PORT" -j DNAT --to-destination 10.2.0.2:$UDP_PORT + + # Enable masquerading for responses + ${pkgs.iptables}/bin/iptables -t nat -C POSTROUTING -o wg0 -j MASQUERADE 2>/dev/null || \ + ${pkgs.iptables}/bin/iptables -t nat -A POSTROUTING -o wg0 -j MASQUERADE + + # Allow forwarding between interfaces + ${pkgs.iptables}/bin/iptables -C FORWARD -i enp0s5 -o wg0 -j ACCEPT 2>/dev/null || \ + ${pkgs.iptables}/bin/iptables -A FORWARD -i enp0s5 -o wg0 -j ACCEPT + ${pkgs.iptables}/bin/iptables -C FORWARD -i wg0 -o enp0s5 -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || \ + ${pkgs.iptables}/bin/iptables -A FORWARD -i wg0 -o enp0s5 -m state --state RELATED,ESTABLISHED -j ACCEPT + + echo "Firewall forwarding rules updated for ports: UDP=$UDP_PORT, TCP=$TCP_PORT" + + # Open the ports in the firewall dynamically (INPUT chain) + ${pkgs.iptables}/bin/iptables -C INPUT -p udp --dport "$UDP_PORT" -j ACCEPT 2>/dev/null || \ + ${pkgs.iptables}/bin/iptables -A INPUT -p udp --dport "$UDP_PORT" -j ACCEPT + + ${pkgs.iptables}/bin/iptables -C INPUT -p tcp --dport "$TCP_PORT" -j ACCEPT 2>/dev/null || \ + ${pkgs.iptables}/bin/iptables -A INPUT -p tcp --dport "$TCP_PORT" -j ACCEPT + + echo "Firewall INPUT rules updated for ports: UDP=$UDP_PORT, TCP=$TCP_PORT" + + # Update qBittorrent listening port via API + echo "Logging into qBittorrent API..." + COOKIE=$(${pkgs.curl}/bin/curl -s -i \ + --header "Referer: http://localhost:${toString serviceCfg.ports.port0}" \ + --data "username=user&password=$PASSWORD" \ + "http://localhost:${toString serviceCfg.ports.port0}/api/v2/auth/login" | \ + ${pkgs.gnugrep}/bin/grep -i "set-cookie" | ${pkgs.gawk}/bin/awk -F'SID=|;' '{print $2}') + if [ -n "$COOKIE" ]; then + echo "Authentication successful, updating port..." + ${pkgs.curl}/bin/curl -s \ + --cookie "SID=$COOKIE" \ + --data "json={\"listen_port\":$UDP_PORT}" \ + "http://localhost:${toString serviceCfg.ports.port0}/api/v2/app/setPreferences" + + echo "Updated qBittorrent listening port to $UDP_PORT" + else + echo "WARNING: Failed to authenticate with qBittorrent API" + fi + else + echo "ERROR: Failed to get forwarded ports" + echo "UDP output: $UDP_OUTPUT" + echo "TCP output: $TCP_OUTPUT" + exit 1 + fi + + sleep 45 + done + ''; + }; }; }; @@ -173,7 +273,7 @@ in { mountPoint = "/run/secrets"; proto = "virtiofs"; - source = "/run/secrets/proton"; + source = "/run/secrets/torrent"; tag = "host_secrets"; } ]; @@ -181,9 +281,12 @@ in environment.systemPackages = builtins.attrValues { inherit (pkgs) - wireguard-tools - speedtest-go bottom + gawk + iptables + libnatpmp + speedtest-go + wireguard-tools ; }; }; @@ -214,10 +317,15 @@ in group = "caddy"; mode = "0400"; }; - "proton/wireguard-pass" = { + "torrent/wireguard-pass" = { owner = "root"; mode = "0400"; }; + "torrent/qbittorrent-pass" = { + owner = "root"; + mode = "0400"; + }; + }; security.acme.certs."${host}" = { @@ -240,4 +348,16 @@ in "d /var/log/caddy 755 caddy caddy -" ]; }; + + networking.firewall = { + allowedTCPPorts = [ + 38834 + torrentPort + ]; + allowedUDPPorts = [ + 38834 + torrentPort + ]; + }; + } diff --git a/secrets/secrets.yaml b/secrets/secrets.yaml index db9465f..b410887 100755 --- a/secrets/secrets.yaml +++ b/secrets/secrets.yaml @@ -55,8 +55,9 @@ firefly-iii: pass: ENC[AES256_GCM,data:WjHcoTuEzEq9pfw4QoqRjI4jhu5VPEMOXlHL0olg9dqUj4EGa1Shv5T/kIxdRFuao0y3zQ==,iv:4/fmFOxxDLzplsNGpSJMQOeoNviZw2c2pFlB1ZkRu+o=,tag:7TQ2q/kEFDU4tZxPx53ebw==,type:str] data: ENC[AES256_GCM,data:921LhcRTWVk24eEAQoDMV+RllSP3PbSXCCIDXlQA80Mq,iv:YXEgas77DgdyPTnBZa/ySjcERBIwmdDZJbijeNKNF24=,tag:Wj25wA7tLJ2bZ/faG9DUhg==,type:str] smtp: ENC[AES256_GCM,data:+e4MiRZ2WOZyWYpMf+By1Eb45ih4TA+svLI2+00yQk82,iv:+52+kJouMwkOSDEaOCA8V80+wT/VzNxgtCkOO68SCdk=,tag:YrtrJAXIhQpsUTEeYvrVwQ==,type:str] -proton: - wireguard-pass: ENC[AES256_GCM,data:u3riHMDyK+DxFAGpdP7zTqZMfp//W3pb3WRp/iS/pAf6ItY9PNFClm7Gh4Dn,iv:8jfPQALR+J3VZVL+a2XHwuL8P8yhZ4OcjhVbCTBADwE=,tag:H42pKD/tlDXUlIVVzdv+DQ==,type:str] +torrent: + wireguard-pass: ENC[AES256_GCM,data:fNNHuOvaRRpiS7c9n/l6lB0A1J4VboJxIh+hrMrTfjFS2grpgRATLHhjZ/wo,iv:CVZIG3Gq+O1/qPqu0XBH/5XsTpAe9xe52/CtBHaIOPI=,tag:8RfoFjz0Ecmx8O7Bt/90ig==,type:str] + qbittorrent-pass: ENC[AES256_GCM,data:W1p7cYWbBNeAtEEL7Tb0pG27TSniqTrNMN6gxFFlli27,iv:seiWOr6V8pyjioBkKKEtCXC17RctDScA37E7uFbnmzk=,tag:KYz92O6XUvJob74LnGlYNg==,type:str] backblaze: env: ENC[AES256_GCM,data:cdOYt77KocuGB3aqYz13oBokoLkEIgI1AW+cYC5uutgZYujG3PqoLEh6Gvbpzn3O+0OWg1/4UAYr4f2v7oCsgwFzPWS3HrhqC5+kIBjrPCyAnxDxlu2xaQ9hR+ogFh5UTDo=,iv:6+jx4Dj5CNV72DAss6NNYm44f9gSHco/EUBvL2o2CNI=,tag:6/cx84MgTDqQJxu/zINEeA==,type:str] repo: ENC[AES256_GCM,data:sRae9XELIfkWPaXelCdgEXIDbLTHVqGcRO0o+WA9aBfB8MUw92JjRCYgMgGXT0Apy38eszyuEHFB3XPpRmtQ7g==,iv:EilVA9zdHm6B9pTIhNxyj6Th1248nXvh0kpnEqZJ5HI=,tag:q9ASAgx5vgY0IePws4rT5Q==,type:str] @@ -73,7 +74,7 @@ sops: bXBOa1VSakoyaWxpODJEOU11QUZCaUEK8Ch9Ten3DdrPHF1DTH2qei85AlHUOaLD aNfzakake7ej+MxJYdKEU0bcWofNMKzIlZa2uM10KZSENDP8d8qlig== -----END AGE ENCRYPTED FILE----- - lastmodified: "2025-11-27T09:53:41Z" - mac: ENC[AES256_GCM,data:fV10gBOFgCTTMWAKv8tGnCz3TG0t0G19di4N4wEi2lsHeYDBk8ijumOM/wLY6Zds80y5u6s6hGwFpCcY5pChjQ/E/pOURHAY7rIqQmGm9yQnpECCfWLDJVgsQjliYRPD76WTEKlsevz/XCGfdITNuRAlU7tuWEEEgXvIKt+0Z/8=,iv:dA/zt7WQA5w+dZM53VunucZAQTBKRyzLaWaShhTMWzI=,tag:CUD7KGes5pNquMJfCm6dYQ==,type:str] + lastmodified: "2025-11-27T18:54:52Z" + mac: ENC[AES256_GCM,data:ZxkJZUJJ1AfDlmxAy8Botu73EPt+1prsdbX7RhU9bVNaEhpPzrvqlO74D8ek/OqFG51k1K4mdW5SWXWs/D5oR34i/yA+329j4jHNAe3Yajkx1gn/xDEa/kgiVGkc7dE3dnzmy5zr4X8U06khJl9rg+qLujke0GCgIv+82xkFFRI=,iv:0UYNIZTxXdPqrZsjVYNGfSlt6UH3+Q102EF6XeC5yh4=,tag:3oj0X73xRnGBXWdGsUv2xg==,type:str] unencrypted_suffix: _unencrypted version: 3.11.0 diff --git a/systems/ceres/config/networking.nix b/systems/ceres/config/networking.nix index 2b58aba..c9c3cf8 100755 --- a/systems/ceres/config/networking.nix +++ b/systems/ceres/config/networking.nix @@ -10,35 +10,37 @@ in { microvm.host.enable = true; - systemd.network.enable = true; - - systemd.network.netdevs."10-br-vms" = { - netdevConfig = { - Name = "br-vms"; - Kind = "bridge"; + systemd.network = { + enable = true; + netdevs."10-br-vms" = { + netdevConfig = { + Name = "br-vms"; + Kind = "bridge"; + }; }; - }; - systemd.network.networks."20-lan" = { - matchConfig.Name = [ - "enp10s0" - "vm-*" - ]; - networkConfig = { - Bridge = "br-vms"; + networks = { + "20-lan" = { + matchConfig.Name = [ + "enp10s0" + "vm-*" + ]; + networkConfig = { + Bridge = "br-vms"; + }; + }; + "30-br-vms" = { + matchConfig.Name = "br-vms"; + networkConfig = { + Address = "192.168.50.240/24"; + Gateway = "192.168.50.1"; + DNS = [ "192.168.50.1" ]; + }; + linkConfig.RequiredForOnline = "routable"; + }; }; }; - systemd.network.networks."30-br-vms" = { - matchConfig.Name = "br-vms"; - networkConfig = { - Address = "192.168.50.240/24"; - Gateway = "192.168.50.1"; - DNS = [ "192.168.50.1" ]; - }; - linkConfig.RequiredForOnline = "routable"; - }; - networking = { hostName = ceres.name; networkmanager.enable = false; @@ -59,6 +61,19 @@ in wireguardService.ports.port0 # WireGuard wireguardService.ports.port1 # WireGuard ]; + # Add port ranges for VPN dynamic port forwarding + allowedTCPPortRanges = [ + { + from = 30000; + to = 65535; + } + ]; + allowedUDPPortRanges = [ + { + from = 30000; + to = 65535; + } + ]; }; };