Skip to content

Commit

Permalink
nixos/qbittorrent: init testing and misc fixes
Browse files Browse the repository at this point in the history
only set config if not null and remove write perm

silly copy n paste mistake

return stickybit

Add default for serverConfig and add tests

Add extraArgs option and test

Make webuiPort option optional

Add interaction via api to tests

Use reboot instead of shutdown and start

nixfmt tests

Add restartTriggers so that a new config is loaded on change

Use specialisations in tests

To simulate module option changes

nixfmt tests
  • Loading branch information
fsnkty committed Jan 28, 2025
1 parent bc29724 commit d9c2b86
Show file tree
Hide file tree
Showing 3 changed files with 216 additions and 6 deletions.
29 changes: 23 additions & 6 deletions nixos/modules/services/torrent/qbittorrent.nix
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ let
else
mkKeyValueDefault { } sep k v;
};
configFile = pkgs.writeText "qBittorrent.conf" (gendeepINI cfg.serverConfig);
in
{
options.services.qbittorrent = {
Expand Down Expand Up @@ -75,16 +76,18 @@ in

webuiPort = mkOption {
default = 8080;
type = port;
type = nullOr port;
description = "the port passed to qbittorrent via `--webui-port`";
};

torrentingPort = mkOption {
default = null;
type = nullOr port;
description = "the port passed to qbittorrent via `--torrenting-port`";
};

serverConfig = mkOption {
default = null;
type = unspecified;
description = ''
Free-form settings mapped to the `qBittorrent.conf` file in the profile.
Expand All @@ -105,6 +108,17 @@ in
}
'';
};

extraArgs = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = ''
Extra arguments passed to qbittorrent. See `qbittorrent -h`, or the [source code](https://github.com/qbittorrent/qBittorrent/blob/master/src/app/cmdoptions.cpp), for the available arguments.
'';
example = [
"--confirm-legal-notice"
];
};
};
config = lib.mkIf cfg.enable {
systemd = {
Expand All @@ -118,10 +132,10 @@ in
mode = "700";
inherit (cfg) user group;
};
"${cfg.profileDir}/qBittorrent/config/qBittorrent.conf"."L+" = {
mode = "1500";
"${cfg.profileDir}/qBittorrent/config/qBittorrent.conf"."L+" = lib.mkIf (cfg.serverConfig != null) {
mode = "1400";
inherit (cfg) user group;
argument = "${pkgs.writeText "qBittorrent.conf" (gendeepINI cfg.serverConfig)}";
argument = "${configFile}";
};
};
};
Expand All @@ -134,6 +148,7 @@ in
"nss-lookup.target"
];
wantedBy = [ "multi-user.target" ];
restartTriggers = lib.optional (cfg.serverConfig != null) configFile;

serviceConfig = {
Type = "simple";
Expand All @@ -143,9 +158,10 @@ in
[
(getExe cfg.package)
"--profile=${cfg.profileDir}"
"--webui-port=${toString cfg.webuiPort}"
]
++ lib.optional (cfg.webuiPort != null) "--webui-port=${toString cfg.webuiPort}"
++ lib.optional (cfg.torrentingPort != null) "--torrenting-port=${toString cfg.torrentingPort}"
++ cfg.extraArgs
);
TimeoutStopSec = 1800;

Expand Down Expand Up @@ -195,7 +211,8 @@ in
};

networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall (
[ cfg.webuiPort ] ++ lib.optional (cfg.torrentingPort != null) cfg.torrentingPort
lib.optional (cfg.webuiPort != null) cfg.webuiPort
++ lib.optional (cfg.torrentingPort != null) cfg.torrentingPort
);
};
meta.maintainers = with maintainers; [ fsnkty ];
Expand Down
1 change: 1 addition & 0 deletions nixos/tests/all-tests.nix
Original file line number Diff line number Diff line change
Expand Up @@ -877,6 +877,7 @@ in {
public-inbox = handleTest ./public-inbox.nix {};
pufferpanel = handleTest ./pufferpanel.nix {};
pulseaudio = discoverTests (import ./pulseaudio.nix);
qbittorrent = handleTest ./qbittorrent.nix {};
qboot = handleTestOn ["x86_64-linux" "i686-linux"] ./qboot.nix {};
qemu-vm-restrictnetwork = handleTest ./qemu-vm-restrictnetwork.nix {};
qemu-vm-volatile-root = runTest ./qemu-vm-volatile-root.nix;
Expand Down
192 changes: 192 additions & 0 deletions nixos/tests/qbittorrent.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
import ./make-test-python.nix (
{ pkgs, ... }:
{
name = "qbittorrent";

meta = with pkgs.lib.maintainers; {
maintainers = [ fsnkty ];
};

nodes = {
simple = {
services.qbittorrent.enable = true;

specialisation.portChange.configuration = {
services.qbittorrent = {
enable = true;
webuiPort = 5555;
torrentingPort = 44444;
};
};

specialisation.openPorts.configuration = {
services.qbittorrent = {
enable = true;
openFirewall = true;
webuiPort = 8080;
torrentingPort = 55555;
};
};

specialisation.serverConfig.configuration = {
services.qbittorrent = {
enable = true;
webuiPort = null;
serverConfig.Preferences.WebUI.Port = "8181";
};
};
};
# Seperate vm because it's not possible to reboot into a specialisation with
# switch-to-configuration: https://github.com/NixOS/nixpkgs/issues/82851
# For one of the test we check if manual changes are overridden during
# reboot, therefore it's necessary to reboot into a declarative setup.
declarative = {
services.qbittorrent = {
enable = true;
webuiPort = null;
serverConfig = {
Preferences = {
WebUI = {
Username = "user";
# Default password: adminadmin
Password_PBKDF2 = "@ByteArray(6DIf26VOpTCYbgNiO6DAFQ==:e6241eaAWGzRotQZvVA5/up9fj5wwSAThLgXI2lVMsYTu1StUgX9MgmElU3Sa/M8fs+zqwZv9URiUOObjqJGNw==)";
Port = "8181";
};
};
};
};

specialisation.serverConfigChange.configuration = {
services.qbittorrent = {
enable = true;
webuiPort = null;
serverConfig.Preferences.WebUI.Port = "7171";
};
};
};
};

testScript =
{ nodes, ... }:
let
simpleSpecPath = "${nodes.simple.system.build.toplevel}/specialisation";
declarativeSpecPath = "${nodes.declarative.system.build.toplevel}/specialisation";
portChange = "${simpleSpecPath}/portChange";
openPorts = "${simpleSpecPath}/openPorts";
serverConfig = "${simpleSpecPath}/serverConfig";
serverConfigChange = "${declarativeSpecPath}/serverConfigChange";
in
''
simple.start(allow_reboot=True)
declarative.start(allow_reboot=True)
def test_webui(machine, port):
machine.wait_for_unit("qbittorrent.service")
machine.wait_for_open_port(port)
machine.wait_until_succeeds(f"curl --fail http://localhost:{port}")
# To simulate an interactive change in the settings
def setPreferences_api(machine, port, post_creds, post_data):
qb_url = f"http://localhost:{port}"
api_url = f"{qb_url}/api/v2"
cookie_path = "/tmp/qbittorrent.cookie"
machine.succeed(
f'curl --header "Referer: {qb_url}" \
--data "{post_creds}" {api_url}/auth/login \
-c {cookie_path}'
)
machine.succeed(
f'curl --header "Referer: {qb_url}" \
--data "{post_data}" {api_url}/app/setPreferences \
-b {cookie_path}'
)
# A randomly generated password is printed in the service log when no
# password it set
def get_temp_pass(machine):
_, password = machine.execute(
"journalctl -u qbittorrent.service |\
grep 'The WebUI administrator password was not set.' |\
awk '{ print $NF }' | tr -d '\n'"
)
return password
# Non declarative tests
with subtest("webui works with all default settings"):
test_webui(simple, 8080)
with subtest("check if manual changes in settings are saved correctly"):
temp_pass = get_temp_pass(simple)
## Change some settings
api_post = [r"json={\"listen_port\": 33333}", r"json={\"web_ui_port\": 9090}"]
for x in api_post:
setPreferences_api(
machine=simple,
port=8080,
post_creds=f"username=admin&password={temp_pass}",
post_data=x,
)
simple.wait_for_open_port(33333)
test_webui(simple, 9090)
## Test which settings are reset
## As webuiPort is passed as an cli it should reset after reboot
## As torrentingPort is not passed as an cli it should not reset after
## reboot
simple.reboot()
test_webui(simple, 8080)
simple.wait_for_open_port(33333)
with subtest("ports are changed on config change"):
simple.succeed("${portChange}/bin/switch-to-configuration test")
test_webui(simple, 5555)
simple.wait_for_open_port(44444)
with subtest("firewall is opened correctly"):
simple.succeed("${openPorts}/bin/switch-to-configuration test")
test_webui(simple, 8080)
declarative.wait_until_succeeds("curl --fail http://simple:8080")
declarative.wait_for_open_port(55555, "simple")
with subtest("switching from simple to declarative works"):
simple.succeed("${serverConfig}/bin/switch-to-configuration test")
test_webui(simple, 8181)
# Declarative tests
with subtest("serverConfig is applied correctly"):
test_webui(declarative, 8181)
with subtest("manual changes are overridden during reboot"):
## Change some settings
setPreferences_api(
machine=declarative,
port=8181, # as set through serverConfig
post_creds="username=user&password=adminadmin",
post_data=r"json={\"web_ui_port\": 9191}",
)
test_webui(declarative, 9191)
## Test which settings are reset
## The generated qBittorrent.conf is, apparently, reapplied after reboot.
## Because the port is set in `serverConfig` this overrides the manually
## set port.
declarative.reboot()
test_webui(declarative, 8181)
with subtest("changes in serverConfig are applied correctly"):
declarative.succeed("${serverConfigChange}/bin/switch-to-configuration test")
test_webui(declarative, 7171)
'';
}
)

0 comments on commit d9c2b86

Please sign in to comment.