nixformat + firewall rules to port forward qbittorrent

This commit is contained in:
Danilo Reyes
2026-04-01 20:32:31 -06:00
parent 1877ad159e
commit 645b022bcf
7 changed files with 127 additions and 22 deletions

View File

@@ -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

View File

@@ -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;
};

View File

@@ -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

View File

@@ -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
{

View File

@@ -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

View File

@@ -1,5 +1,4 @@
{
inputs,
pkgs,
lib,
config,
@@ -9,7 +8,6 @@ let
download = import ./common.nix {
inherit
config
inputs
lib
pkgs
;

View File

@@ -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;