Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create qbittorrent-nox service #279716

2 changes: 2 additions & 0 deletions nixos/doc/manual/release-notes/rl-2405.section.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m

- [Clevis](https://github.com/latchset/clevis), a pluggable framework for automated decryption, used to unlock encrypted devices in initrd. Available as [boot.initrd.clevis.enable](#opt-boot.initrd.clevis.enable).

- Systemd's service for [qbittorrent-nox](https://github.com/qbittorrent/qBittorrent), a headless version of `qbittorrent`, the bittorrent client. Available as [`services.qbittorrent-nox.enable`](#opt-services.qbittorrent-nox.enable), please refer to other configurations under [`services.qbittorrent-nox`](#opt-services.qbittorrent-nox).

- [TuxClocker](https://github.com/Lurkki14/tuxclocker), a hardware control and monitoring program. Available as [programs.tuxclocker](#opt-programs.tuxclocker.enable).

## Backward Incompatibilities {#sec-release-24.05-incompatibilities}
Expand Down
2 changes: 2 additions & 0 deletions nixos/modules/misc/ids.nix
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,7 @@ in
rstudio-server = 324;
localtimed = 325;
automatic-timezoned = 326;
qbittorrent-nox = 327;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't be done anymore, you should use (in order of preference):

  • systemd's DynamicUser (potentially with User if you need a named user)
  • users.users.<name>.

Given the usual approach for HTPC/seedbox services, using users.users is probably the way to go.


# When adding a uid, make sure it doesn't match an existing gid. And don't use uids above 399!

Expand Down Expand Up @@ -666,6 +667,7 @@ in
rstudio-server = 324;
localtimed = 325;
automatic-timezoned = 326;
qbittorrent-nox = 327;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same note as the uid.


# When adding a gid, make sure it doesn't match an existing
# uid. Users and groups with the same name should have equal
Expand Down
1 change: 1 addition & 0 deletions nixos/modules/module-list.nix
Original file line number Diff line number Diff line change
Expand Up @@ -1237,6 +1237,7 @@
./services/torrent/magnetico.nix
./services/torrent/opentracker.nix
./services/torrent/peerflix.nix
./services/torrent/qbittorrent-nox.nix
./services/torrent/rtorrent.nix
./services/torrent/transmission.nix
./services/torrent/torrentstream.nix
Expand Down
141 changes: 141 additions & 0 deletions nixos/modules/services/torrent/qbittorrent-nox.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
{ config, lib, pkgs, ... }:

with lib;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you should avoid this kind of use of with to address #208242 for this module.

use inherits where you find yourself excessively reusing lib.foo


let
cfg = config.services.qbittorrent-nox;
in {
options = {
services = {
qbittorrent-nox = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would instead name it qbittorent, add a package option, and use qbittorent-nox as the default package.

enable = mkEnableOption (lib.mdDoc "qbittorrent-nox daemon");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related to my proposition to rename the module:

Suggested change
enable = mkEnableOption (lib.mdDoc "qbittorrent-nox daemon");
enable = mkEnableOption (lib.mdDoc "qBittorent daemon");


web = {
port = mkOption {
type = types.port;
default = 8080;
description = lib.mdDoc ''
qbittorrent-nox web UI port.
'';
Comment on lines +17 to +19
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
description = lib.mdDoc ''
qbittorrent-nox web UI port.
'';
description = lib.mdDoc "qBittorent web UI port"';

};
openFirewall = mkOption {
type = types.bool;
default = false;
description = lib.mdDoc ''
Whether to open the firewall for the ports in
{option}`services.qbittorrent-nox.web.port`.
'';
Comment on lines +24 to +27
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's only one port in that option

Suggested change
description = lib.mdDoc ''
Whether to open the firewall for the ports in
{option}`services.qbittorrent-nox.web.port`.
'';
description = lib.mdDoc ''
Whether to open the firewall for
{option}`services.qbittorrent-nox.web.port`.
'';

};
};

torrenting = {
port = mkOption {
type = types.port;
default = 48197;
description = lib.mdDoc ''
qbittorrent-nox web UI port.
'';
Comment on lines +35 to +37
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
description = lib.mdDoc ''
qbittorrent-nox web UI port.
'';
description = lib.mdDoc "qBittorent peering port.";

};

openFirewall = mkOption {
default = false;
type = types.bool;
description = lib.mdDoc ''
Whether to open the firewall for the ports in
{option}`services.qbittorrent-nox.torrenting.port`.
'';
Comment on lines +43 to +46
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's only one port in that option

Suggested change
description = lib.mdDoc ''
Whether to open the firewall for the ports in
{option}`services.qbittorrent-nox.torrenting.port`.
'';
description = lib.mdDoc ''
Whether to open the firewall for
{option}`services.qbittorrent-nox.torrenting.port`.
'';

};
};

dataDir = mkOption {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is a dataDir true here for qbit, profiles seems to be the way it manages itself, a profilesdir commandline option is available.

type = types.path;
default = "/var/lib/qbittorrent-nox";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
default = "/var/lib/qbittorrent-nox";
default = "/var/lib/qbittorrent";

description = lib.mdDoc ''
The directory where qbittorrent-nox will create files.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that the downloaded files? Or state files?

Assuming the latter:

Suggested change
The directory where qbittorrent-nox will create files.
The directory where qBittorent will store its state files.

'';
};

user = mkOption {
type = types.str;
default = "qbittorrent";
description = lib.mdDoc ''
User account under which qbittorrent-nox runs.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
User account under which qbittorrent-nox runs.
User account under which qBittorent runs.

'';
};

group = mkOption {
type = types.str;
default = "qbittorrent";
description = lib.mdDoc ''
Group under which qbittorrent-nox runs.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Group under which qbittorrent-nox runs.
Group under which qBittorent runs.

'';
};

package = mkPackageOption pkgs "qbittorrent-nox" { };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We usually put the package option after enable.

Suggested change
package = mkPackageOption pkgs "qbittorrent-nox" { };
package = mkPackageOption pkgs "qBittorent" { default = [ "qbittorent-nox" ]; };

};
};
};

config = mkIf cfg.enable {

services.qbittorrent-nox.package = mkDefault (pkgs.qbittorrent-nox);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is useless, mkPackageOption already sets the default value.


systemd.tmpfiles.rules = [
"d '${cfg.dataDir}' 0770 ${cfg.user} ${cfg.group}"
"d '${cfg.dataDir}/.config' 0770 ${cfg.user} ${cfg.group}"
"d '${cfg.dataDir}/.config/qBittorrent' 0770 ${cfg.user} ${cfg.group}"
];
Comment on lines +83 to +87
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use systemd.tmpfiles.settings.


systemd.services.qbittorrent-nox = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same note about renaming the service

Suggested change
systemd.services.qbittorrent-nox = {
systemd.services.qbittorrent = {

after = [ "network.target" "local-fs.target" "network-online.target" "nss-lookup.target" ];
wantedBy = [ "multi-user.target" ];
path = [ cfg.package ];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not needed, better served by systemd's service hardening options.

unitConfig = {
Description = "qBittorrent-nox Daemon";
Documentation = "man:qbittorrent-nox(1)";
};
serviceConfig = {
ExecStart = ''
${cfg.package}/bin/qbittorrent-nox \
--profile=${cfg.dataDir} \
--webui-port=${toString cfg.web.port} \
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

neither port options should be done with the command line options, qbittorrent has a configuration file we can define these in, we know where its state will be after all

--torrenting-port=${toString cfg.torrenting.port}
'';
Type = "exec";
User = cfg.user;
Group = cfg.group;
UMask = "0002";
PrivateTmp = "false";
TimeoutStopSec = 1800;
};
# preStart = preStart;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dead code.

};

networking.firewall = mkMerge [
(mkIf (cfg.torrenting.openFirewall) {
allowedTCPPorts = [ cfg.torrenting.port ];
allowedUDPPorts = [ cfg.torrenting.port ];
})
(mkIf (cfg.web.openFirewall) {
allowedTCPPorts = [ cfg.web.port ];
})
];

environment.systemPackages = [ cfg.package ];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why?


users.users = mkIf (cfg.user == "qbittorrent") {
qbittorrent = {
group = cfg.group;
uid = config.ids.uids.qbittorrent-nox;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As explained, please refrain from adding more uid/gid values to NixOS.

Suggested change
uid = config.ids.uids.qbittorrent-nox;

home = cfg.dataDir;
description = "qbittorrent daemon user";
};
};

users.groups = mkIf (cfg.group == "qbittorrent") {
qbittorrent = {
gid = config.ids.gids.qbittorrent-nox;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same remark as for uid.

Suggested change
gid = config.ids.gids.qbittorrent-nox;

};
};
};
}
29 changes: 29 additions & 0 deletions nixos/tests/qbittorrent-nox.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import ./make-test-python.nix ({ pkgs, ...} : {
name = "qbittorrent-nox";
meta = with pkgs.lib.maintainers; {
maintainers = [ camilosampedro ];
};

nodes = {
simple = {
services.qbittorrent-nox = {
enable = true;
package = pkgs.qbittorrent-nox;
port = 8091;
web = {
enable = true;
openFirewall = true;
};
};
};

};

testScript = ''
start_all()

simple.wait_for_unit("qbittorrent-nox")
simple.wait_for_open_port(8091)
simple.wait_until_succeeds("curl --fail http://simple:8091")
'';
})
Loading