fix: qbittorrent

This commit is contained in:
Nickolaj Jepsen 2025-12-16 23:09:57 +01:00
parent 41906e75fb
commit d769b830a2
8 changed files with 72 additions and 66 deletions

12
flake.lock generated
View file

@ -58,11 +58,11 @@
"quickshell": "quickshell" "quickshell": "quickshell"
}, },
"locked": { "locked": {
"lastModified": 1765865292, "lastModified": 1765916864,
"narHash": "sha256-BmL32FIn6YtjFG/5Z+BcApTc5Z8mKNLv9wnAU0aRYIE=", "narHash": "sha256-mXKYRVK5YndrvgbIKCyz4BRuLkyEqgceF/djXmA6cD8=",
"owner": "AvengeMedia", "owner": "AvengeMedia",
"repo": "DankMaterialShell", "repo": "DankMaterialShell",
"rev": "2947ff41313566c4eb77fbddf085176141eb337a", "rev": "672754b0b5efd9e61ea8080c40614ad3b4fd5dbf",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -770,11 +770,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1765874996, "lastModified": 1765918270,
"narHash": "sha256-qQjl+fX5ySQzzx+Cc8oua3CSsZA5lK6Ev6XEmjxKbHs=", "narHash": "sha256-TbNcuaNCIRp4ZcZBQ1lyXW6GMyHhY5+gWCHqKTLZ3So=",
"owner": "nix-community", "owner": "nix-community",
"repo": "NUR", "repo": "NUR",
"rev": "1d585f45ed47164867626b03b7f192f2cddaa606", "rev": "e62aaff51af4cbc43149a71b67931005416d1138",
"type": "github" "type": "github"
}, },
"original": { "original": {

View file

@ -1,7 +1,7 @@
_: { _: {
monitors = [ monitors = [
{ {
name = "DP-1"; name = "DP-3";
resolution = { resolution = {
width = 2560; width = 2560;
height = 1440; height = 1440;
@ -14,7 +14,7 @@ _: {
}; };
} }
{ {
name = "DP-3"; name = "DP-2";
resolution = { resolution = {
width = 2560; width = 2560;
height = 1440; height = 1440;

View file

@ -12,8 +12,8 @@ lib.mkIf config.fireproof.homelab.enable (let
src = pkgs.fetchFromGitHub { src = pkgs.fetchFromGitHub {
owner = "nickolaj-jepsen"; owner = "nickolaj-jepsen";
repo = "glance"; repo = "glance";
rev = "7ea37f329e17908cdcc306f0fbb23a62e7c50584"; rev = "c490067f87186cac9084a76010a646119b7793e1";
hash = "sha256-ZU9iswhgQPeMZeQmzgNhFBcO2TzWYrmIWPnKSAA0fFM="; hash = "sha256-zsanWSWO/gY4ZuYssdcoGKVw/Yk29qaF5Gn5XUYKQhk=";
}; };
}); });

View file

@ -5,31 +5,13 @@
... ...
}: }:
lib.mkIf config.fireproof.homelab.enable (let lib.mkIf config.fireproof.homelab.enable (let
inherit (config.fireproof) username;
user = "media";
group = "media";
# VPN namespace configuration # VPN namespace configuration
vpnNamespace = "vpn"; vpnNamespace = "qbittorrent-vpn";
vpnInterface = "wg0"; vpnInterface = "qbt-wg0";
# Ports # Ports
webUiPort = 8082; webUiPort = 8082;
torrentPort = 51413; torrentPort = 51413;
mkVirtualHost = port: {
enableACME = true;
forceSSL = true;
locations."/" = {
proxyPass = "http://localhost:${toString port}";
extraConfig = ''
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
'';
};
};
in { in {
# Secrets for Mullvad WireGuard config # Secrets for Mullvad WireGuard config
# mullvad-wg.age should contain just the WireGuard config (not the Address line): # mullvad-wg.age should contain just the WireGuard config (not the Address line):
@ -66,30 +48,11 @@ in {
# Create network namespace if it doesn't exist # Create network namespace if it doesn't exist
ip netns add ${vpnNamespace} || true ip netns add ${vpnNamespace} || true
# Create veth pair for communication between namespaces # Set up loopback interface in namespace
ip link add veth-vpn type veth peer name veth-vpn-br || true
ip link set veth-vpn-br netns ${vpnNamespace} || true
# Configure host side
ip addr add 10.200.200.1/24 dev veth-vpn || true
ip link set veth-vpn up
# Configure namespace side
ip netns exec ${vpnNamespace} ip addr add 10.200.200.2/24 dev veth-vpn-br || true
ip netns exec ${vpnNamespace} ip link set veth-vpn-br up
ip netns exec ${vpnNamespace} ip link set lo up ip netns exec ${vpnNamespace} ip link set lo up
# Enable IP forwarding for the veth bridge
echo 1 > /proc/sys/net/ipv4/ip_forward
iptables -t nat -A POSTROUTING -s 10.200.200.0/24 -o veth-vpn -j MASQUERADE || true
# Allow traffic from namespace to host for web UI
iptables -A FORWARD -i veth-vpn -o veth-vpn -j ACCEPT || true
''; '';
ExecStop = pkgs.writeShellScript "destroy-vpn-netns" '' ExecStop = pkgs.writeShellScript "destroy-vpn-netns" ''
ip link del veth-vpn || true
ip netns del ${vpnNamespace} || true ip netns del ${vpnNamespace} || true
iptables -t nat -D POSTROUTING -s 10.200.200.0/24 -o veth-vpn -j MASQUERADE || true
''; '';
}; };
}; };
@ -107,8 +70,12 @@ in {
RemainAfterExit = true; RemainAfterExit = true;
ExecStart = pkgs.writeShellScript "setup-wg-vpn" '' ExecStart = pkgs.writeShellScript "setup-wg-vpn" ''
set -ex set -ex
# Create WireGuard interface in namespace # Clean up any existing WireGuard interface first
ip link add ${vpnInterface} type wireguard || true ip link del ${vpnInterface} 2>/dev/null || true
ip netns exec ${vpnNamespace} ip link del ${vpnInterface} 2>/dev/null || true
# Create WireGuard interface
ip link add ${vpnInterface} type wireguard
ip link set ${vpnInterface} netns ${vpnNamespace} ip link set ${vpnInterface} netns ${vpnNamespace}
# Configure WireGuard with Mullvad config # Configure WireGuard with Mullvad config
@ -116,7 +83,7 @@ in {
# Set the interface address from secret file # Set the interface address from secret file
WG_ADDR=$(cat ${config.age.secrets.mullvad-wg-address.path}) WG_ADDR=$(cat ${config.age.secrets.mullvad-wg-address.path})
ip netns exec ${vpnNamespace} ip addr add "$WG_ADDR" dev ${vpnInterface} || true ip netns exec ${vpnNamespace} ip addr add "$WG_ADDR" dev ${vpnInterface}
ip netns exec ${vpnNamespace} ip link set ${vpnInterface} up ip netns exec ${vpnNamespace} ip link set ${vpnInterface} up
# Route all traffic through WireGuard (default route) # Route all traffic through WireGuard (default route)
@ -134,25 +101,43 @@ in {
}; };
# qBittorrent service running inside the VPN namespace # qBittorrent service running inside the VPN namespace
services.qbittorrent = {
enable = true;
user = "media";
group = "media";
webuiPort = webUiPort;
torrentingPort = torrentPort;
serverConfig = {
LegalNotice.Accepted = true;
Preferences = {
WebUI = {
Address = "*";
Port = webUiPort;
};
Connection = {
PortRangeMin = torrentPort;
};
Downloads = {
SavePath = "/mnt/data/torrent";
};
};
};
};
# Override the qbittorrent service to run in VPN namespace
systemd.services.qbittorrent = { systemd.services.qbittorrent = {
description = "qBittorrent-nox service";
documentation = ["man:qbittorrent-nox(1)"];
after = [ after = [
"network.target" "network.target"
"wg-${vpnNamespace}.service" "wg-${vpnNamespace}.service"
]; ];
requires = ["wg-${vpnNamespace}.service"]; requires = ["wg-${vpnNamespace}.service"];
wantedBy = ["multi-user.target"];
serviceConfig = { serviceConfig = {
Type = "simple";
User = user;
Group = group;
StateDirectory = "qbittorrent";
# Run in the VPN namespace # Run in the VPN namespace
NetworkNamespacePath = "/var/run/netns/${vpnNamespace}"; NetworkNamespacePath = "/var/run/netns/${vpnNamespace}";
ExecStart = "${pkgs.qbittorrent-nox}/bin/qbittorrent-nox --webui-port=${toString webUiPort}"; # Bind mount the DNS config into the namespace
Restart = "on-failure"; BindReadOnlyPaths = [
TimeoutStopSec = 1800; "/etc/netns/${vpnNamespace}/resolv.conf:/etc/resolv.conf"
];
}; };
}; };
@ -162,14 +147,15 @@ in {
after = ["qbittorrent.service"]; after = ["qbittorrent.service"];
requires = ["qbittorrent.service"]; requires = ["qbittorrent.service"];
wantedBy = ["multi-user.target"]; wantedBy = ["multi-user.target"];
path = with pkgs; [iproute2 socat];
serviceConfig = { serviceConfig = {
Type = "simple"; Type = "simple";
Restart = "on-failure"; Restart = "on-failure";
ExecStart = "${pkgs.socat}/bin/socat TCP-LISTEN:${toString webUiPort},fork,reuseaddr EXEC:'${pkgs.iproute2}/bin/ip netns exec ${vpnNamespace} ${pkgs.socat}/bin/socat STDIO TCP\\:127.0.0.1\\:${toString webUiPort}'"; ExecStart = "${pkgs.socat}/bin/socat TCP-LISTEN:${toString webUiPort},fork,reuseaddr,bind=0.0.0.0 EXEC:'${pkgs.iproute2}/bin/ip netns exec ${vpnNamespace} ${pkgs.socat}/bin/socat STDIO TCP\\:127.0.0.1\\:${toString webUiPort}'";
}; };
}; };
# Firewall rules for torrent port (forwarded through VPN) # Firewall rules
networking.firewall.allowedTCPPorts = [webUiPort]; networking.firewall.allowedTCPPorts = [webUiPort];
networking.firewall.allowedUDPPorts = [torrentPort]; networking.firewall.allowedUDPPorts = [torrentPort];
@ -178,7 +164,19 @@ in {
"qbittorrent.nickolaj.com".allowed_groups = ["arr"]; "qbittorrent.nickolaj.com".allowed_groups = ["arr"];
}; };
nginx.virtualHosts = { nginx.virtualHosts = {
"qbittorrent.nickolaj.com" = mkVirtualHost webUiPort; "qbittorrent.nickolaj.com" = {
enableACME = true;
forceSSL = true;
locations."/" = {
proxyPass = "http://localhost:${toString webUiPort}";
extraConfig = ''
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
'';
};
};
}; };
restic.backups.homelab.paths = [ restic.backups.homelab.paths = [

View file

@ -0,0 +1,8 @@
age-encryption.org/v1
-> ssh-ed25519 uxq+Zw 5GTpmg1JYdchll7KGFza4+Wr2HUrspkUy263EjosImI
FI/rKWr8SRFjX46ABgBAtOfF+tm5Si1T6uWC5K0EXmQ
-> 1\y>D-grease b s$Uo}
rw0Ut3iYFuMxeoj+6/VBi5qrKioY/Es
--- 7hpSVWwDE2aWWdKs60R94/87gI3IcqVY9z8PV4SlTpg
ü»0úŒµ ”äÍH
Ã,l± $°)^È)Ñ¨Ö ¹£iˆFà¨pŸ™Øåì¾s‰7Á

Binary file not shown.

Binary file not shown.