From 5a4f7c273415b0a1a217207ab09e848a71d2fe1b Mon Sep 17 00:00:00 2001 From: Danilo Reyes Date: Wed, 1 Apr 2026 23:34:28 -0600 Subject: [PATCH] declare qbittorrent --- flake.lock | 17 +++ flake.nix | 4 + hosts/server/configuration.nix | 129 ++++++++++--------- modules/servers/homepage/service-widgets.nix | 2 +- modules/servers/qbittorrent.nix | 104 +++++++++------ 5 files changed, 157 insertions(+), 99 deletions(-) diff --git a/flake.lock b/flake.lock index 049367b..b78010a 100644 --- a/flake.lock +++ b/flake.lock @@ -1178,6 +1178,7 @@ "sops-nix": "sops-nix", "stylix": "stylix", "synctube": "synctube", + "trackerslist": "trackerslist", "ucodenix": "ucodenix", "wallpapers": "wallpapers" } @@ -1426,6 +1427,22 @@ "type": "github" } }, + "trackerslist": { + "flake": false, + "locked": { + "lastModified": 1775081381, + "narHash": "sha256-LXlP3qKShp/8YVA0/lSUJkIbFsInnYtg8jAT5IfjfJk=", + "owner": "ngosang", + "repo": "trackerslist", + "rev": "446d148b04c96e8ffcb4f45f563713764a843ad0", + "type": "github" + }, + "original": { + "owner": "ngosang", + "repo": "trackerslist", + "type": "github" + } + }, "ucodenix": { "inputs": { "cpu-microcodes": "cpu-microcodes" diff --git a/flake.nix b/flake.nix index d462245..3f8809b 100644 --- a/flake.nix +++ b/flake.nix @@ -76,6 +76,10 @@ url = "github:StuffAnThings/qbit_manage"; flake = false; }; + trackerslist = { + url = "github:ngosang/trackerslist"; + flake = false; + }; gallery-dl-src = { url = "github:mikf/gallery-dl"; flake = false; diff --git a/hosts/server/configuration.nix b/hosts/server/configuration.nix index cb4cad9..8229faa 100644 --- a/hosts/server/configuration.nix +++ b/hosts/server/configuration.nix @@ -9,7 +9,6 @@ let lidarrMbGapId = 968; qbittorrentRouteTable = 200; serverInterface = config.my.interfaces.server; - jawzUid = config.users.users.jawz.uid; in { imports = [ @@ -100,71 +99,85 @@ 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 + systemd.services.qbittorrent-vpn-routing = + lib.mkIf (config.my.secureHost && config.services.qbittorrent.enable) + { + description = "Route qBittorrent user traffic through the VPS WireGuard tunnel"; + before = [ "qbittorrent.service" ]; + wantedBy = [ "multi-user.target" ]; + after = [ + "network-online.target" + "wireguard-wg0.service" + ]; + wants = [ + "network-online.target" + "wireguard-wg0.service" + ]; + path = with pkgs; [ + coreutils + gnugrep + iproute2 + shadow + ]; + script = '' + qbit_uid="$(id -u ${config.services.qbittorrent.user})" - if ! ip -4 addr show dev wg0 >/dev/null 2>&1; then - echo "wg0 is not available" - exit 1 - fi + for _ in $(seq 1 30); do + if ip -4 addr show dev wg0 >/dev/null 2>&1; then + break + fi + sleep 1 + done - 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} + if ! ip -4 addr show dev wg0 >/dev/null 2>&1; then + echo "wg0 is not available" + exit 1 + fi - 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} + 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} - 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 + 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 '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 + while ip -4 rule show | grep -q "uidrange ''${qbit_uid}-''${qbit_uid} lookup ${toString qbittorrentRouteTable}"; do + ip -4 rule del uidrange "''${qbit_uid}-''${qbit_uid}" 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 '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 - 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 rule add from ${config.my.ips.wg-server}/32 lookup ${toString qbittorrentRouteTable} priority 9999 + ip -4 rule add uidrange "''${qbit_uid}-''${qbit_uid}" lookup ${toString qbittorrentRouteTable} priority 10000 + ''; + preStop = '' + qbit_uid="$(id -u ${config.services.qbittorrent.user})" - 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"; - }; - }; + 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 ''${qbit_uid}-''${qbit_uid} lookup ${toString qbittorrentRouteTable}"; do + ip -4 rule del uidrange "''${qbit_uid}-''${qbit_uid}" 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/modules/servers/homepage/service-widgets.nix b/modules/servers/homepage/service-widgets.nix index 05e2a25..7d65fda 100644 --- a/modules/servers/homepage/service-widgets.nix +++ b/modules/servers/homepage/service-widgets.nix @@ -147,7 +147,7 @@ }; qbittorrent = let - url = "https://${config.my.ips.server}:${toString config.my.servers.qbittorrent.port}"; + url = "http://${config.my.ips.server}:${toString config.my.servers.qbittorrent.port}"; name = "qbittorrent"; in lib.mkIf config.my.servers.qbittorrent.enable { diff --git a/modules/servers/qbittorrent.nix b/modules/servers/qbittorrent.nix index be35cfe..33a94d6 100644 --- a/modules/servers/qbittorrent.nix +++ b/modules/servers/qbittorrent.nix @@ -6,12 +6,13 @@ ... }: let - inherit (inputs) qbit_manage; - vuetorrent = pkgs.fetchzip { - url = "https://github.com/VueTorrent/VueTorrent/releases/download/v2.31.0/vuetorrent.zip"; - sha256 = "sha256-kVDnDoCoJlY2Ew71lEMeE67kNOrKTJEMqNj2OfP01qw="; - stripRoot = true; - }; + inherit (inputs) qbit_manage trackerslist; + qbitProfileDir = "/var/lib/qbittorrent"; + qbitUser = "qbittorrent"; + wgInterface = "wg0"; + qbitDownloadDir = "/srv/pool/multimedia/downloads/torrent"; + qbitIncompleteDir = "${qbitDownloadDir}/.incomplete"; + additionalTrackers = lib.strings.trim (builtins.readFile "${trackerslist}/trackers_all.txt"); qbit_manageEnv = pkgs.python3.withPackages ( ps: builtins.attrValues { @@ -33,8 +34,7 @@ let } ); torrentCompletionScript = pkgs.writeShellScript "qbit-torrent-completion" '' - chown jawz:piracy -R "$1" - chmod -R 775 "$1" + chmod -R u+rwX,g+rwX,o-rwx "$1" ''; in { @@ -51,51 +51,75 @@ in }; config = lib.mkIf (config.my.servers.qbittorrent.enable && config.my.secureHost) { my.network.firewall.additionalPorts = [ config.my.servers.qbittorrent.port ]; - home-manager.users.jawz.xdg.dataFile.vuetorrent.source = vuetorrent; - home-manager.users.jawz.imports = [ - ( - { lib, ... }: - { - home.activation.qbittorrentAutorunCommand = lib.hm.dag.entryAfter [ "writeBoundary" ] '' - conf=/home/jawz/.config/qBittorrent/qBittorrent.conf - if [ -f "$conf" ]; then - sed -i \ - 's|^program=.*$|program=${torrentCompletionScript} %F|' \ - "$conf" - fi - ''; - } - ) + services.qbittorrent = { + enable = true; + user = qbitUser; + group = "piracy"; + profileDir = qbitProfileDir; + webuiPort = config.my.servers.qbittorrent.port; + torrentingPort = config.my.ports.qbittorrent; + extraArgs = [ "--confirm-legal-notice" ]; + serverConfig = { + AutoRun = { + enabled = true; + program = "${torrentCompletionScript} %F"; + }; + BitTorrent.Session = { + AddExtensionToIncompleteFiles = true; + AddTrackersEnabled = true; + AdditionalTrackers = additionalTrackers; + AnnounceIP = config.my.ips.vps; + DefaultSavePath = qbitDownloadDir; + GlobalMaxRatio = 1; + GlobalMaxSeedingMinutes = 120; + Interface = wgInterface; + InterfaceAddress = config.my.ips.wg-server; + InterfaceName = wgInterface; + MaxActiveDownloads = 5; + MaxActiveTorrents = 100; + MaxActiveUploads = 90; + QueueingSystemEnabled = true; + ShareLimitAction = "Stop"; + TempPath = qbitIncompleteDir; + TempPathEnabled = true; + }; + Core.AutoDeleteAddedTorrentFile = "Never"; + Network.PortForwardingEnabled = false; + Preferences = { + Bittorrent.CustomizeTrackersListUrl = "https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_all.txt"; + WebUI = { + AlternativeUIEnabled = true; + HTTPS.Enabled = false; + Password_PBKDF2 = "@ByteArray(ZYy4l3ORHihzBrRYqIfmOA==:SYv4Gor5ZSI9FfAPOxAAdhlmz/h+vViEHnoW6tfJYLNnFL9DQ8udqkO9Na83RJauzhVVGvAgauPq/y4UNPyl3g==)"; + ReverseProxySupportEnabled = false; + RootFolder = "${pkgs.vuetorrent}/share/vuetorrent"; + Username = "9VcZWt3d0u6mmMOhryUZAcpIe8WML9J7Icj00Vu5fuOFXRB0ECAItetKAK1NnfrD"; + }; + }; + }; + }; + users.users.${qbitUser} = { + home = qbitProfileDir; + createHome = true; + }; + systemd.tmpfiles.rules = [ + "d ${qbitDownloadDir} 0775 ${qbitUser} piracy -" + "d ${qbitIncompleteDir} 0775 ${qbitUser} piracy -" ]; sops.secrets = let - mkQbitSecret = file: mode: { - inherit mode; - inherit (config.users.users.jawz) group; - sopsFile = ../../secrets/keys.yaml; - owner = config.users.users.jawz.name; - path = "/home/jawz/.config/qBittorrent/ssl/${file}"; - }; mkUnpackerrSecret = { sopsFile = ../../secrets/secrets.yaml; owner = config.users.users.jawz.name; }; in { - "certificates/qbit_cert" = mkQbitSecret "server.crt" "0644"; - "certificates/qbit_key" = mkQbitSecret "server.key" "0600"; "unpackerr/sonarr-api" = mkUnpackerrSecret; "unpackerr/radarr-api" = mkUnpackerrSecret; }; systemd = { - packages = [ - pkgs.qbittorrent-nox - torrentCompletionScript - ]; - services."qbittorrent-nox@jawz" = { - enable = true; - overrideStrategy = "asDropin"; - wantedBy = [ "multi-user.target" ]; + packages = [ torrentCompletionScript ]; + services = { }; user = { services = {