From 4eeed32002de7c035657cd8326ea915345f7881c Mon Sep 17 00:00:00 2001 From: Danilo Reyes Date: Wed, 1 Apr 2026 23:55:32 -0600 Subject: [PATCH] qbittorrent + qbit_manage migrated to nixpkgs --- flake.lock | 17 --- flake.nix | 4 - hosts/server/configuration.nix | 167 +++++++++++++++-------------- hosts/vps/configuration.nix | 10 +- modules/scripts/download/nixos.nix | 14 +-- modules/servers/qbittorrent.nix | 69 +++++------- 6 files changed, 124 insertions(+), 157 deletions(-) diff --git a/flake.lock b/flake.lock index b78010a..d075346 100644 --- a/flake.lock +++ b/flake.lock @@ -1140,22 +1140,6 @@ "url": "https://git.lebubu.org/vibe-coded/prem2resolve.git" } }, - "qbit_manage": { - "flake": false, - "locked": { - "lastModified": 1774123356, - "narHash": "sha256-7B2JZNK8ESbfNLCVUL40Rldj67LCdDHOa+BkL7xmOcY=", - "owner": "StuffAnThings", - "repo": "qbit_manage", - "rev": "b2b035b7c06d7e81ecbd322c9c2ea18986718ae3", - "type": "github" - }, - "original": { - "owner": "StuffAnThings", - "repo": "qbit_manage", - "type": "github" - } - }, "root": { "inputs": { "clip-tools": "clip-tools", @@ -1174,7 +1158,6 @@ "nixtendo-switch": "nixtendo-switch", "nur": "nur", "prem2resolve": "prem2resolve", - "qbit_manage": "qbit_manage", "sops-nix": "sops-nix", "stylix": "stylix", "synctube": "synctube", diff --git a/flake.nix b/flake.nix index 3f8809b..4e8345e 100644 --- a/flake.nix +++ b/flake.nix @@ -72,10 +72,6 @@ url = "git+https://git.lebubu.org/jawz/fonts.git"; flake = false; }; - qbit_manage = { - url = "github:StuffAnThings/qbit_manage"; - flake = false; - }; trackerslist = { url = "github:ngosang/trackerslist"; flake = false; diff --git a/hosts/server/configuration.nix b/hosts/server/configuration.nix index 8229faa..cf28fdb 100644 --- a/hosts/server/configuration.nix +++ b/hosts/server/configuration.nix @@ -8,7 +8,10 @@ let lidarrMbGapId = 968; qbittorrentRouteTable = 200; + qbitUser = config.services.qbittorrent.user; serverInterface = config.my.interfaces.server; + wgInterface = "wg0"; + wgServerIp = config.my.ips.wg-server; in { imports = [ @@ -29,7 +32,7 @@ in network.firewall = { enabledServicePorts = true; additionalPorts = [ - 2049 # idk + 2049 config.my.ports.syncthingGui config.my.ports.syncthingRelay config.my.ports.sonarqube @@ -75,9 +78,9 @@ in iproute2.rttablesExtraConfig = '' ${toString qbittorrentRouteTable} qbittorrent ''; - wireguard.interfaces.wg0 = lib.mkIf config.my.secureHost { + wireguard.interfaces.${wgInterface} = lib.mkIf config.my.secureHost { allowedIPsAsRoutes = false; - ips = [ "${config.my.ips.wg-server}/32" ]; + ips = [ "${wgServerIp}/32" ]; privateKeyFile = config.sops.secrets."server/private".path; peers = [ { @@ -96,97 +99,101 @@ in }; firewall = { allowedUDPPorts = config.networking.firewall.allowedTCPPorts; - interfaces.wg0.allowedTCPPorts = [ config.my.servers.nextcloud.port ]; + interfaces.${wgInterface}.allowedTCPPorts = [ config.my.servers.nextcloud.port ]; }; }; - 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})" + 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-${wgInterface}.service" + ]; + wants = [ + "network-online.target" + "wireguard-${wgInterface}.service" + ]; + path = with pkgs; [ + coreutils + gnugrep + iproute2 + shadow + ]; + script = '' + qbit_uid="$(id -u ${qbitUser})" - 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 + for _ in $(seq 1 30); do + if ip -4 addr show dev ${wgInterface} >/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 ${wgInterface} >/dev/null 2>&1; then + echo "${wgInterface} 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 ${wgInterface} src ${wgServerIp} + ip -4 route replace ${config.my.subnets.wg-friends} dev ${wgInterface} src ${wgServerIp} + ip -4 route replace ${config.my.subnets.wg-guests} dev ${wgInterface} src ${wgServerIp} - 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 route replace table ${toString qbittorrentRouteTable} ${config.my.subnets.wg-homelab} dev ${wgInterface} src ${wgServerIp} + ip -4 route replace table ${toString qbittorrentRouteTable} ${config.my.subnets.wg-friends} dev ${wgInterface} src ${wgServerIp} + ip -4 route replace table ${toString qbittorrentRouteTable} ${config.my.subnets.wg-guests} dev ${wgInterface} src ${wgServerIp} + 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 ${wgInterface} src ${wgServerIp} - 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 "''${qbit_uid}-''${qbit_uid}" lookup ${toString qbittorrentRouteTable} priority 10000 - ''; - preStop = '' - qbit_uid="$(id -u ${config.services.qbittorrent.user})" + while ip -4 rule show | grep -q 'from ${wgServerIp} lookup ${toString qbittorrentRouteTable}'; do + ip -4 rule del from ${wgServerIp}/32 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} || true - done + ip -4 rule add from ${wgServerIp}/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 ${qbitUser})" - 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 + while ip -4 rule show | grep -q 'from ${wgServerIp} lookup ${toString qbittorrentRouteTable}'; do + ip -4 rule del from ${wgServerIp}/32 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"; - }; + 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 ${wgInterface} || true + ip -4 route del ${config.my.subnets.wg-friends} dev ${wgInterface} || true + ip -4 route del ${config.my.subnets.wg-guests} dev ${wgInterface} || 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; + }; }; - users.groups.lidarr-mb-gap.gid = lidarrMbGapId; - users.users.lidarr-mb-gap = { - uid = lidarrMbGapId; - isSystemUser = true; - group = "lidarr-mb-gap"; - home = "/var/lib/lidarr-mb-gap"; + users = { + groups.lidarr-mb-gap.gid = lidarrMbGapId; + users = { + jawz.packages = builtins.attrValues { + inherit (pkgs) podman-compose attic-client; + }; + lidarr-mb-gap = { + uid = lidarrMbGapId; + isSystemUser = true; + group = "lidarr-mb-gap"; + home = "/var/lib/lidarr-mb-gap"; + }; + }; }; services = { btrfs.autoScrub = { diff --git a/hosts/vps/configuration.nix b/hosts/vps/configuration.nix index 0c0e9ab..0fcb30f 100644 --- a/hosts/vps/configuration.nix +++ b/hosts/vps/configuration.nix @@ -22,14 +22,16 @@ let wgHomelab = config.my.subnets.wg-homelab; }; ports = { - inherit (config.my.ports) giteaSsh; - inherit (config.my.ports) qbittorrent; - inherit (config.my.ports) ssh; + inherit (config.my.ports) + giteaSsh + qbittorrent + ssh + wg + ; web = [ 80 443 ]; - inherit (config.my.ports) wg; syncthing = config.my.ports.syncthingRelay; synapseFederation = config.my.ports.synapseSsl; }; diff --git a/modules/scripts/download/nixos.nix b/modules/scripts/download/nixos.nix index b276ef2..7ecc903 100644 --- a/modules/scripts/download/nixos.nix +++ b/modules/scripts/download/nixos.nix @@ -6,11 +6,7 @@ }: let download = import ./common.nix { - inherit - config - lib - pkgs - ; + inherit config lib pkgs; }; in { @@ -67,12 +63,10 @@ in }; in { - "download@main" = lib.mkIf config.my.units.download.enable ( - downloadTimer "*-*-* 06,18:02:00" 30 // { } - ); - "download@push" = lib.mkIf config.my.units.download.enable (downloadTimer "*:0/5" 30 // { }); + "download@main" = lib.mkIf config.my.units.download.enable (downloadTimer "*-*-* 06,18:02:00" 30); + "download@push" = lib.mkIf config.my.units.download.enable (downloadTimer "*:0/5" 30); "download@manga" = lib.mkIf config.my.units.downloadManga.enable ( - downloadTimer "*-*-* 03:08:00" 30 // { } + downloadTimer "*-*-* 03:08:00" 30 ); tuhmayto = lib.mkIf config.my.units.download.enable { enable = true; diff --git a/modules/servers/qbittorrent.nix b/modules/servers/qbittorrent.nix index 33a94d6..a49aa25 100644 --- a/modules/servers/qbittorrent.nix +++ b/modules/servers/qbittorrent.nix @@ -6,33 +6,19 @@ ... }: let - inherit (inputs) qbit_manage trackerslist; + inherit (inputs) trackerslist; qbitProfileDir = "/var/lib/qbittorrent"; qbitUser = "qbittorrent"; wgInterface = "wg0"; + qbitManageConfigPath = "/home/jawz/.config/qbit_manage/config.yml"; 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 { - inherit (ps) - argon2-cffi - bencode-py - croniter - fastapi - gitpython - humanize - pytimeparse2 - qbittorrent-api - requests - retrying - ruamel-yaml - slowapi - uvicorn - ; - } - ); + unstablePkgs = import inputs.nixpkgs-unstable { + inherit (pkgs.stdenv.hostPlatform) system; + config.allowUnfree = true; + }; + qbitManagePkg = unstablePkgs."qbit-manage"; torrentCompletionScript = pkgs.writeShellScript "qbit-torrent-completion" '' chmod -R u+rwX,g+rwX,o-rwx "$1" ''; @@ -60,10 +46,23 @@ in torrentingPort = config.my.ports.qbittorrent; extraArgs = [ "--confirm-legal-notice" ]; serverConfig = { + Core.AutoDeleteAddedTorrentFile = "Never"; + Network.PortForwardingEnabled = false; AutoRun = { enabled = true; program = "${torrentCompletionScript} %F"; }; + 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"; + }; + }; BitTorrent.Session = { AddExtensionToIncompleteFiles = true; AddTrackersEnabled = true; @@ -83,29 +82,13 @@ in 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; + isSystemUser = true; + home = qbitProfileDir; }; - systemd.tmpfiles.rules = [ - "d ${qbitDownloadDir} 0775 ${qbitUser} piracy -" - "d ${qbitIncompleteDir} 0775 ${qbitUser} piracy -" - ]; sops.secrets = let mkUnpackerrSecret = { @@ -119,8 +102,10 @@ in }; systemd = { packages = [ torrentCompletionScript ]; - services = { - }; + tmpfiles.rules = [ + "d ${qbitDownloadDir} 0775 ${qbitUser} piracy -" + "d ${qbitIncompleteDir} 0775 ${qbitUser} piracy -" + ]; user = { services = { unpackerr = lib.mkIf config.my.servers.unpackerr.enable { @@ -151,7 +136,7 @@ in serviceConfig = { Type = "oneshot"; TimeoutStartSec = "5min"; - ExecStart = "${qbit_manageEnv}/bin/python ${qbit_manage}/qbit_manage.py -r -c /home/jawz/.config/qbit_manage/config.yml"; + ExecStart = "${qbitManagePkg}/bin/qbit-manage -r -c ${qbitManageConfigPath}"; }; }; };