From 8b4a4c67b2a3fa5fcf95a98d6c2d2f161c77e224 Mon Sep 17 00:00:00 2001 From: Alexander Date: Sun, 26 Apr 2026 15:51:43 +0200 Subject: [PATCH] Add qBittorrent --- README.md | 3 +- docs/wiki/examples/example-1/index.md | 12 +- docs/wiki/setup/index.md | 38 ++++- nixarr/default.nix | 2 + nixarr/qbittorrent/default.nix | 198 ++++++++++++++++++++++++++ util/globals/default.nix | 5 + 6 files changed, 252 insertions(+), 6 deletions(-) create mode 100644 nixarr/qbittorrent/default.nix diff --git a/README.md b/README.md index b59e637..cc1b94a 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,8 @@ that I will remove or change options in a non-backwards-compatible way. ## Features - **Run services through a VPN:** You can run any service that this module - supports through a VPN, fx `nixarr.transmission.vpn.enable = true;` + supports through a VPN, fx `nixarr.transmission.vpn.enable = true;` or + `nixarr.qbittorrent.vpn.enable = true;` - **Automatic Directories, Users and Permissions:** The module automatically creates directories and users for your media library. It also sets sane permissions. diff --git a/docs/wiki/examples/example-1/index.md b/docs/wiki/examples/example-1/index.md index 50ee5a3..d5d2809 100644 --- a/docs/wiki/examples/example-1/index.md +++ b/docs/wiki/examples/example-1/index.md @@ -5,7 +5,7 @@ title: Basic Example This example does the following: - Runs a jellyfin server and exposes it to the internet with HTTPS support. -- Runs the transmission torrent client through a vpn +- Runs a torrent client (Transmission or qBittorrent) through a VPN - Runs all "*Arrs" supported by this module ```nix {.numberLines} @@ -52,3 +52,13 @@ This example does the following: jellyseerr.enable = true; }; ``` + +To use qBittorrent instead of Transmission, replace the `transmission` block with: + +```nix {.numberLines} + qbittorrent = { + enable = true; + vpn.enable = true; + peerPort = 50000; # Set this to the port forwarded by your VPN + }; +``` diff --git a/docs/wiki/setup/index.md b/docs/wiki/setup/index.md index 445bb27..53e82c0 100644 --- a/docs/wiki/setup/index.md +++ b/docs/wiki/setup/index.md @@ -39,6 +39,34 @@ already set. See the following links for more info: - [The `nixarr.transmission` options](https://nixarr.com/nixos-options/#nixarr.transmission.enable) - [Settings that can be passed through `nixarr.transmission.settings`] +## qBittorrent + +- Open your browser and go to `{URL}:8080`. +- On first launch, qBittorrent generates a temporary admin password shown in the + systemd journal. Retrieve it with: + ``` + sudo journalctl -u qbittorrent -b | grep password + ``` +- Log in with username `admin` and the temporary password. +- Go to "Tools" > "Options" > "Web UI": + - Change the default password to something secure. + +**Configuring download categories for the \*Arrs:** + +Each \*Arr service needs its own category in qBittorrent so downloads are +sorted into the correct directories: + +1. Right-click in the category panel on the left sidebar. +2. Click "Add category". +3. Add the following categories with their save paths: + - `radarr` → `/data/media/torrents/radarr` + - `sonarr` → `/data/media/torrents/sonarr` + - `lidarr` → `/data/media/torrents/lidarr` + - `readarr` → `/data/media/torrents/readarr` + +When adding qBittorrent as a download client in each \*Arr, set the category +to the corresponding name. + ## Radarr - Open your browser and go to `{URL}:7878`. @@ -51,8 +79,9 @@ already set. See the following links for more info: - Under `Permissions`, change `chmod Folder` to `775` - Under `Root Folders`, click `Add Root Folder`. Add `/data/media/library/movies/`, then click `Save Changes`. -- Go to "Settings" > "Download Clients" and add Transmission. Change the - category to `radarr`. +- Go to "Settings" > "Download Clients" and add your torrent client: + - **Transmission**: select Transmission, set the category to `radarr`. + - **qBittorrent**: select qBittorrent, set the category to `radarr`. **Recommendations:**: @@ -71,8 +100,9 @@ already set. See the following links for more info: - Under `Permissions`, change `chmod Folder` to `775` - Under `Root Folders`, click `Add Root Folder`. Add `/data/media/library/shows/`, then click `Save Changes`. -- Go to "Settings" > "Download Clients" and add Transmission. Change the - category to `sonarr`. +- Go to "Settings" > "Download Clients" and add your torrent client: + - **Transmission**: select Transmission, set the category to `sonarr`. + - **qBittorrent**: select qBittorrent, set the category to `sonarr`. **Recommendations:**: diff --git a/nixarr/default.nix b/nixarr/default.nix index c2cab36..8334294 100644 --- a/nixarr/default.nix +++ b/nixarr/default.nix @@ -22,6 +22,7 @@ in { ./openssh ./plex ./prowlarr + ./qbittorrent ./radarr ./readarr ./readarr-audiobook @@ -74,6 +75,7 @@ in { - [SABnzbd](#nixarr.sabnzbd.enable) - [Sonarr](#nixarr.sonarr.enable) - [Transmission](#nixarr.transmission.enable) + - [qBittorrent](#nixarr.qbittorrent.enable) Remember to read the options! ''; diff --git a/nixarr/qbittorrent/default.nix b/nixarr/qbittorrent/default.nix new file mode 100644 index 0000000..a060e30 --- /dev/null +++ b/nixarr/qbittorrent/default.nix @@ -0,0 +1,198 @@ +{ + config, + lib, + pkgs, + ... +}: +with lib; let + cfg = config.nixarr.qbittorrent; + globals = config.util-nixarr.globals; + nixarr = config.nixarr; + + downloadDir = "${nixarr.mediaDir}/torrents"; +in { + options.nixarr.qbittorrent = { + enable = mkOption { + type = types.bool; + default = false; + example = true; + description = "Whether or not to enable the qBittorrent service."; + }; + + package = mkPackageOption pkgs "qbittorrent-nox" {}; + + stateDir = mkOption { + type = types.path; + default = "${nixarr.stateDir}/qbittorrent"; + defaultText = literalExpression ''"''${nixarr.stateDir}/qbittorrent"''; + example = "/nixarr/.state/qbittorrent"; + description = '' + The location of the state directory for the qBittorrent service. + + > **Warning:** Setting this to any path, where the subpath is not + > owned by root, will fail! For example: + > + > ```nix + > stateDir = /home/user/nixarr/.state/qbittorrent + > ``` + > + > Is not supported, because `/home/user` is owned by `user`. + ''; + }; + + openFirewall = mkOption { + type = types.bool; + defaultText = literalExpression ''!nixarr.qbittorrent.vpn.enable''; + default = !cfg.vpn.enable; + example = true; + description = "Open firewall for `peerPort` and `uiPort`."; + }; + + vpn.enable = mkOption { + type = types.bool; + default = false; + example = true; + description = '' + **Required options:** [`nixarr.vpn.enable`](#nixarr.vpn.enable) + + Route qBittorrent traffic through the VPN. + ''; + }; + + peerPort = mkOption { + type = types.port; + default = 50000; + example = 12345; + description = "qBittorrent peer traffic port."; + }; + + uiPort = mkOption { + type = types.port; + default = 8080; + example = 12345; + description = "qBittorrent web-UI port."; + }; + + extraSettings = mkOption { + type = types.attrs; + default = {}; + description = '' + Extra config settings for the qBittorrent service passed to + `services.qbittorrent.serverConfig`. + + See [Explanation-of-Options-in-qBittorrent](https://github.com/qbittorrent/qBittorrent/wiki/Explanation-of-Options-in-qBittorrent). + ''; + }; + }; + + config = mkIf (nixarr.enable && cfg.enable) { + assertions = [ + { + assertion = cfg.vpn.enable -> nixarr.vpn.enable; + message = '' + The nixarr.qbittorrent.vpn.enable option requires the + nixarr.vpn.enable option to be set, but it was not. + ''; + } + ]; + + systemd.tmpfiles.rules = [ + "d '${cfg.stateDir}' 0750 qbittorrent media - -" + + "d '${nixarr.mediaDir}/torrents' 0755 qbittorrent media - -" + "d '${nixarr.mediaDir}/torrents/.incomplete' 0755 qbittorrent media - -" + "d '${nixarr.mediaDir}/torrents/manual' 0755 qbittorrent media - -" + "d '${nixarr.mediaDir}/torrents/lidarr' 0755 qbittorrent media - -" + "d '${nixarr.mediaDir}/torrents/radarr' 0755 qbittorrent media - -" + "d '${nixarr.mediaDir}/torrents/sonarr' 0755 qbittorrent media - -" + "d '${nixarr.mediaDir}/torrents/readarr' 0755 qbittorrent media - -" + ]; + + services.qbittorrent = { + enable = true; + package = cfg.package; + user = "qbittorrent"; + group = "media"; + profileDir = cfg.stateDir; + webuiPort = cfg.uiPort; + torrentingPort = cfg.peerPort; + openFirewall = cfg.openFirewall; + serverConfig = { + LegalNotice.Accepted = true; + BitTorrent.Session = { + DefaultSavePath = downloadDir; + TempPath = "${downloadDir}/.incomplete"; + TempPathEnabled = true; + Port = cfg.peerPort; + "BTProtocol" = "TCP"; + }; + Preferences = { + WebUI = { + Address = + if cfg.vpn.enable + then "192.168.15.1" + else "0.0.0.0"; + Port = cfg.uiPort; + }; + Downloads = { + SavePath = downloadDir; + TempPath = "${downloadDir}/.incomplete"; + TempPathEnabled = true; + }; + }; + } // cfg.extraSettings; + }; + + users.users.qbittorrent = { + isSystemUser = true; + group = "media"; + uid = 71; + }; + + systemd.services.qbittorrent = { + serviceConfig.IOSchedulingPriority = 7; + + vpnConfinement = mkIf cfg.vpn.enable { + enable = true; + vpnNamespace = "wg"; + }; + }; + + vpnNamespaces.wg = mkIf cfg.vpn.enable { + portMappings = [ + { + from = cfg.uiPort; + to = cfg.uiPort; + } + ]; + openVPNPorts = [ + { + port = cfg.peerPort; + protocol = "both"; + } + ]; + }; + + services.nginx = mkIf cfg.vpn.enable { + enable = true; + + recommendedTlsSettings = true; + recommendedOptimisation = true; + recommendedGzipSettings = true; + + virtualHosts."127.0.0.1:${builtins.toString cfg.uiPort}" = { + listen = [ + { + addr = "0.0.0.0"; + port = cfg.uiPort; + } + ]; + locations."/" = { + recommendedProxySettings = true; + proxyWebsockets = true; + proxyPass = "http://192.168.15.1:${builtins.toString cfg.uiPort}"; + }; + }; + }; + }; +} diff --git a/util/globals/default.nix b/util/globals/default.nix index 669623e..7dd3e2f 100644 --- a/util/globals/default.nix +++ b/util/globals/default.nix @@ -35,6 +35,7 @@ in { readarr-audiobook = 211; recyclarr = 269; sabnzbd = 38; + qbittorrent = 71; transmission = 70; # Removed 2025-10-29 # cross-seed = 183; @@ -112,6 +113,10 @@ in { user = "sonarr"; group = globals.libraryOwner.group; }; + qbittorrent = { + user = "qbittorrent"; + group = globals.libraryOwner.group; + }; transmission = { user = "transmission"; group = globals.libraryOwner.group;