qbittorrent + qbit_manage migrated to nixpkgs
All checks were successful
MCP Tests / mcp-tests (push) Successful in 24s

This commit is contained in:
Danilo Reyes
2026-04-01 23:55:32 -06:00
parent 5a4f7c2734
commit 4eeed32002
6 changed files with 124 additions and 157 deletions

17
flake.lock generated
View File

@@ -1140,22 +1140,6 @@
"url": "https://git.lebubu.org/vibe-coded/prem2resolve.git" "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": { "root": {
"inputs": { "inputs": {
"clip-tools": "clip-tools", "clip-tools": "clip-tools",
@@ -1174,7 +1158,6 @@
"nixtendo-switch": "nixtendo-switch", "nixtendo-switch": "nixtendo-switch",
"nur": "nur", "nur": "nur",
"prem2resolve": "prem2resolve", "prem2resolve": "prem2resolve",
"qbit_manage": "qbit_manage",
"sops-nix": "sops-nix", "sops-nix": "sops-nix",
"stylix": "stylix", "stylix": "stylix",
"synctube": "synctube", "synctube": "synctube",

View File

@@ -72,10 +72,6 @@
url = "git+https://git.lebubu.org/jawz/fonts.git"; url = "git+https://git.lebubu.org/jawz/fonts.git";
flake = false; flake = false;
}; };
qbit_manage = {
url = "github:StuffAnThings/qbit_manage";
flake = false;
};
trackerslist = { trackerslist = {
url = "github:ngosang/trackerslist"; url = "github:ngosang/trackerslist";
flake = false; flake = false;

View File

@@ -8,7 +8,10 @@
let let
lidarrMbGapId = 968; lidarrMbGapId = 968;
qbittorrentRouteTable = 200; qbittorrentRouteTable = 200;
qbitUser = config.services.qbittorrent.user;
serverInterface = config.my.interfaces.server; serverInterface = config.my.interfaces.server;
wgInterface = "wg0";
wgServerIp = config.my.ips.wg-server;
in in
{ {
imports = [ imports = [
@@ -29,7 +32,7 @@ in
network.firewall = { network.firewall = {
enabledServicePorts = true; enabledServicePorts = true;
additionalPorts = [ additionalPorts = [
2049 # idk 2049
config.my.ports.syncthingGui config.my.ports.syncthingGui
config.my.ports.syncthingRelay config.my.ports.syncthingRelay
config.my.ports.sonarqube config.my.ports.sonarqube
@@ -75,9 +78,9 @@ in
iproute2.rttablesExtraConfig = '' iproute2.rttablesExtraConfig = ''
${toString qbittorrentRouteTable} qbittorrent ${toString qbittorrentRouteTable} qbittorrent
''; '';
wireguard.interfaces.wg0 = lib.mkIf config.my.secureHost { wireguard.interfaces.${wgInterface} = lib.mkIf config.my.secureHost {
allowedIPsAsRoutes = false; allowedIPsAsRoutes = false;
ips = [ "${config.my.ips.wg-server}/32" ]; ips = [ "${wgServerIp}/32" ];
privateKeyFile = config.sops.secrets."server/private".path; privateKeyFile = config.sops.secrets."server/private".path;
peers = [ peers = [
{ {
@@ -96,97 +99,101 @@ in
}; };
firewall = { firewall = {
allowedUDPPorts = config.networking.firewall.allowedTCPPorts; 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 = systemd.services = {
lib.mkIf (config.my.secureHost && config.services.qbittorrent.enable) qbittorrent-vpn-routing = lib.mkIf (config.my.secureHost && config.services.qbittorrent.enable) {
{ description = "Route qBittorrent user traffic through the VPS WireGuard tunnel";
description = "Route qBittorrent user traffic through the VPS WireGuard tunnel"; before = [ "qbittorrent.service" ];
before = [ "qbittorrent.service" ]; wantedBy = [ "multi-user.target" ];
wantedBy = [ "multi-user.target" ]; after = [
after = [ "network-online.target"
"network-online.target" "wireguard-${wgInterface}.service"
"wireguard-wg0.service" ];
]; wants = [
wants = [ "network-online.target"
"network-online.target" "wireguard-${wgInterface}.service"
"wireguard-wg0.service" ];
]; path = with pkgs; [
path = with pkgs; [ coreutils
coreutils gnugrep
gnugrep iproute2
iproute2 shadow
shadow ];
]; script = ''
script = '' qbit_uid="$(id -u ${qbitUser})"
qbit_uid="$(id -u ${config.services.qbittorrent.user})"
for _ in $(seq 1 30); do for _ in $(seq 1 30); do
if ip -4 addr show dev wg0 >/dev/null 2>&1; then if ip -4 addr show dev ${wgInterface} >/dev/null 2>&1; then
break 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 fi
sleep 1
done
ip -4 route replace ${config.my.subnets.wg-homelab} dev wg0 src ${config.my.ips.wg-server} if ! ip -4 addr show dev ${wgInterface} >/dev/null 2>&1; then
ip -4 route replace ${config.my.subnets.wg-friends} dev wg0 src ${config.my.ips.wg-server} echo "${wgInterface} is not available"
ip -4 route replace ${config.my.subnets.wg-guests} dev wg0 src ${config.my.ips.wg-server} 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 ${config.my.subnets.wg-homelab} dev ${wgInterface} src ${wgServerIp}
ip -4 route replace table ${toString qbittorrentRouteTable} ${config.my.subnets.wg-friends} dev wg0 src ${config.my.ips.wg-server} ip -4 route replace ${config.my.subnets.wg-friends} dev ${wgInterface} src ${wgServerIp}
ip -4 route replace table ${toString qbittorrentRouteTable} ${config.my.subnets.wg-guests} dev wg0 src ${config.my.ips.wg-server} ip -4 route replace ${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 wg0 src ${config.my.ips.wg-server}
while ip -4 rule show | grep -q "uidrange ''${qbit_uid}-''${qbit_uid} lookup ${toString qbittorrentRouteTable}"; do ip -4 route replace table ${toString qbittorrentRouteTable} ${config.my.subnets.wg-homelab} dev ${wgInterface} src ${wgServerIp}
ip -4 rule del uidrange "''${qbit_uid}-''${qbit_uid}" lookup ${toString qbittorrentRouteTable} ip -4 route replace table ${toString qbittorrentRouteTable} ${config.my.subnets.wg-friends} dev ${wgInterface} src ${wgServerIp}
done 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 while ip -4 rule show | grep -q "uidrange ''${qbit_uid}-''${qbit_uid} lookup ${toString qbittorrentRouteTable}"; do
ip -4 rule del from ${config.my.ips.wg-server}/32 lookup ${toString qbittorrentRouteTable} ip -4 rule del uidrange "''${qbit_uid}-''${qbit_uid}" lookup ${toString qbittorrentRouteTable}
done done
ip -4 rule add from ${config.my.ips.wg-server}/32 lookup ${toString qbittorrentRouteTable} priority 9999 while ip -4 rule show | grep -q 'from ${wgServerIp} lookup ${toString qbittorrentRouteTable}'; do
ip -4 rule add uidrange "''${qbit_uid}-''${qbit_uid}" lookup ${toString qbittorrentRouteTable} priority 10000 ip -4 rule del from ${wgServerIp}/32 lookup ${toString qbittorrentRouteTable}
''; done
preStop = ''
qbit_uid="$(id -u ${config.services.qbittorrent.user})"
while ip -4 rule show | grep -q 'from ${config.my.ips.wg-server} lookup ${toString qbittorrentRouteTable}'; do ip -4 rule add from ${wgServerIp}/32 lookup ${toString qbittorrentRouteTable} priority 9999
ip -4 rule del from ${config.my.ips.wg-server}/32 lookup ${toString qbittorrentRouteTable} || true ip -4 rule add uidrange "''${qbit_uid}-''${qbit_uid}" lookup ${toString qbittorrentRouteTable} priority 10000
done '';
preStop = ''
qbit_uid="$(id -u ${qbitUser})"
while ip -4 rule show | grep -q "uidrange ''${qbit_uid}-''${qbit_uid} lookup ${toString qbittorrentRouteTable}"; do while ip -4 rule show | grep -q 'from ${wgServerIp} lookup ${toString qbittorrentRouteTable}'; do
ip -4 rule del uidrange "''${qbit_uid}-''${qbit_uid}" lookup ${toString qbittorrentRouteTable} || true ip -4 rule del from ${wgServerIp}/32 lookup ${toString qbittorrentRouteTable} || true
done done
ip -4 route del ${config.my.subnets.wg-homelab} dev wg0 || true while ip -4 rule show | grep -q "uidrange ''${qbit_uid}-''${qbit_uid} lookup ${toString qbittorrentRouteTable}"; do
ip -4 route del ${config.my.subnets.wg-friends} dev wg0 || true ip -4 rule del uidrange "''${qbit_uid}-''${qbit_uid}" lookup ${toString qbittorrentRouteTable} || true
ip -4 route del ${config.my.subnets.wg-guests} dev wg0 || true done
ip -4 route flush table ${toString qbittorrentRouteTable} || true
''; ip -4 route del ${config.my.subnets.wg-homelab} dev ${wgInterface} || true
serviceConfig = { ip -4 route del ${config.my.subnets.wg-friends} dev ${wgInterface} || true
RemainAfterExit = true; ip -4 route del ${config.my.subnets.wg-guests} dev ${wgInterface} || true
Type = "oneshot"; 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.users.lidarr-mb-gap = { groups.lidarr-mb-gap.gid = lidarrMbGapId;
uid = lidarrMbGapId; users = {
isSystemUser = true; jawz.packages = builtins.attrValues {
group = "lidarr-mb-gap"; inherit (pkgs) podman-compose attic-client;
home = "/var/lib/lidarr-mb-gap"; };
lidarr-mb-gap = {
uid = lidarrMbGapId;
isSystemUser = true;
group = "lidarr-mb-gap";
home = "/var/lib/lidarr-mb-gap";
};
};
}; };
services = { services = {
btrfs.autoScrub = { btrfs.autoScrub = {

View File

@@ -22,14 +22,16 @@ let
wgHomelab = config.my.subnets.wg-homelab; wgHomelab = config.my.subnets.wg-homelab;
}; };
ports = { ports = {
inherit (config.my.ports) giteaSsh; inherit (config.my.ports)
inherit (config.my.ports) qbittorrent; giteaSsh
inherit (config.my.ports) ssh; qbittorrent
ssh
wg
;
web = [ web = [
80 80
443 443
]; ];
inherit (config.my.ports) wg;
syncthing = config.my.ports.syncthingRelay; syncthing = config.my.ports.syncthingRelay;
synapseFederation = config.my.ports.synapseSsl; synapseFederation = config.my.ports.synapseSsl;
}; };

View File

@@ -6,11 +6,7 @@
}: }:
let let
download = import ./common.nix { download = import ./common.nix {
inherit inherit config lib pkgs;
config
lib
pkgs
;
}; };
in in
{ {
@@ -67,12 +63,10 @@ in
}; };
in in
{ {
"download@main" = lib.mkIf config.my.units.download.enable ( "download@main" = lib.mkIf config.my.units.download.enable (downloadTimer "*-*-* 06,18:02:00" 30);
downloadTimer "*-*-* 06,18:02:00" 30 // { } "download@push" = lib.mkIf config.my.units.download.enable (downloadTimer "*:0/5" 30);
);
"download@push" = lib.mkIf config.my.units.download.enable (downloadTimer "*:0/5" 30 // { });
"download@manga" = lib.mkIf config.my.units.downloadManga.enable ( "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 { tuhmayto = lib.mkIf config.my.units.download.enable {
enable = true; enable = true;

View File

@@ -6,33 +6,19 @@
... ...
}: }:
let let
inherit (inputs) qbit_manage trackerslist; inherit (inputs) trackerslist;
qbitProfileDir = "/var/lib/qbittorrent"; qbitProfileDir = "/var/lib/qbittorrent";
qbitUser = "qbittorrent"; qbitUser = "qbittorrent";
wgInterface = "wg0"; wgInterface = "wg0";
qbitManageConfigPath = "/home/jawz/.config/qbit_manage/config.yml";
qbitDownloadDir = "/srv/pool/multimedia/downloads/torrent"; qbitDownloadDir = "/srv/pool/multimedia/downloads/torrent";
qbitIncompleteDir = "${qbitDownloadDir}/.incomplete"; qbitIncompleteDir = "${qbitDownloadDir}/.incomplete";
additionalTrackers = lib.strings.trim (builtins.readFile "${trackerslist}/trackers_all.txt"); additionalTrackers = lib.strings.trim (builtins.readFile "${trackerslist}/trackers_all.txt");
qbit_manageEnv = pkgs.python3.withPackages ( unstablePkgs = import inputs.nixpkgs-unstable {
ps: inherit (pkgs.stdenv.hostPlatform) system;
builtins.attrValues { config.allowUnfree = true;
inherit (ps) };
argon2-cffi qbitManagePkg = unstablePkgs."qbit-manage";
bencode-py
croniter
fastapi
gitpython
humanize
pytimeparse2
qbittorrent-api
requests
retrying
ruamel-yaml
slowapi
uvicorn
;
}
);
torrentCompletionScript = pkgs.writeShellScript "qbit-torrent-completion" '' torrentCompletionScript = pkgs.writeShellScript "qbit-torrent-completion" ''
chmod -R u+rwX,g+rwX,o-rwx "$1" chmod -R u+rwX,g+rwX,o-rwx "$1"
''; '';
@@ -60,10 +46,23 @@ in
torrentingPort = config.my.ports.qbittorrent; torrentingPort = config.my.ports.qbittorrent;
extraArgs = [ "--confirm-legal-notice" ]; extraArgs = [ "--confirm-legal-notice" ];
serverConfig = { serverConfig = {
Core.AutoDeleteAddedTorrentFile = "Never";
Network.PortForwardingEnabled = false;
AutoRun = { AutoRun = {
enabled = true; enabled = true;
program = "${torrentCompletionScript} %F"; 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 = { BitTorrent.Session = {
AddExtensionToIncompleteFiles = true; AddExtensionToIncompleteFiles = true;
AddTrackersEnabled = true; AddTrackersEnabled = true;
@@ -83,29 +82,13 @@ in
TempPath = qbitIncompleteDir; TempPath = qbitIncompleteDir;
TempPathEnabled = true; 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} = { users.users.${qbitUser} = {
home = qbitProfileDir;
createHome = true; createHome = true;
isSystemUser = true;
home = qbitProfileDir;
}; };
systemd.tmpfiles.rules = [
"d ${qbitDownloadDir} 0775 ${qbitUser} piracy -"
"d ${qbitIncompleteDir} 0775 ${qbitUser} piracy -"
];
sops.secrets = sops.secrets =
let let
mkUnpackerrSecret = { mkUnpackerrSecret = {
@@ -119,8 +102,10 @@ in
}; };
systemd = { systemd = {
packages = [ torrentCompletionScript ]; packages = [ torrentCompletionScript ];
services = { tmpfiles.rules = [
}; "d ${qbitDownloadDir} 0775 ${qbitUser} piracy -"
"d ${qbitIncompleteDir} 0775 ${qbitUser} piracy -"
];
user = { user = {
services = { services = {
unpackerr = lib.mkIf config.my.servers.unpackerr.enable { unpackerr = lib.mkIf config.my.servers.unpackerr.enable {
@@ -151,7 +136,7 @@ in
serviceConfig = { serviceConfig = {
Type = "oneshot"; Type = "oneshot";
TimeoutStartSec = "5min"; 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}";
}; };
}; };
}; };