diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index ad943c98fc9b6..60493b7edd6fe 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -1553,6 +1553,7 @@ ./services/torrent/opentracker.nix ./services/torrent/peerflix.nix ./services/torrent/qbittorrent.nix + ./services/torrent/qui.nix ./services/torrent/rtorrent.nix ./services/torrent/torrentstream.nix ./services/torrent/transmission.nix diff --git a/nixos/modules/services/torrent/qui.nix b/nixos/modules/services/torrent/qui.nix new file mode 100644 index 0000000000000..2efa863e6986e --- /dev/null +++ b/nixos/modules/services/torrent/qui.nix @@ -0,0 +1,112 @@ +{ + config, + pkgs, + lib, + ... +}: +let + cfg = config.services.qui; + format = pkgs.formats.toml { }; + configFile = format.generate "config.toml" cfg.settings; +in +{ + options = { + services.qui = { + enable = lib.mkEnableOption "Qui, A fast, single-binary qBittorrent web UI"; + + package = lib.mkPackageOption pkgs "qui" { }; + + openFirewall = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Open ports in the firewall for the qui web interface."; + }; + sessionSecretPath = lib.mkOption { + type = lib.types.nullOr lib.types.str; + description = "Path to file containing secret"; + default = null; + }; + + settings = + with lib; + mkOption { + type = types.submodule { + freeformType = format.type; + + options = { + host = mkOption { + type = types.nullOr types.str; + description = "Address to bind"; + default = "localhost"; + }; + port = mkOption { + type = types.nullOr types.int; + description = "Port to bind"; + default = 7476; + }; + dataDir = mkOption { + type = types.nullOr types.path; + description = "The directory where data is stored"; + default = "/var/lib/qui"; + }; + }; + }; + default = { }; + description = "Qui configuration, see for reference."; + }; + + user = lib.mkOption { + type = lib.types.str; + default = "qui"; + description = "User account under which qui runs."; + }; + + group = lib.mkOption { + type = lib.types.str; + default = "qui"; + description = "Group under which qui runs."; + }; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.services.qui = { + description = "qui"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + environment = { + QUI__SESSION_SECRET_FILE = cfg.sessionSecretPath; + }; + + serviceConfig = { + Type = "simple"; + User = cfg.user; + Group = cfg.group; + StateDirectory = "qui"; + ExecStart = "${cfg.package}/bin/qui serve --config-dir ${configFile}"; + Restart = "on-failure"; + NoNewPrivileges = true; + PrivateTmp = true; + ProtectHome = true; + ProtectSystem = "full"; + ReadWritePaths = [ "/var/lib/qui" ]; + }; + }; + + networking.firewall = lib.mkIf cfg.openFirewall { + allowedTCPPorts = [ cfg.settings.port ]; + }; + + users.users = lib.mkIf (cfg.user == "qui") { + qui = { + group = cfg.group; + home = cfg.settings.dataDir; + isSystemUser = true; + }; + }; + + users.groups = lib.mkIf (cfg.group == "qui") { + qui = { }; + }; + }; +} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index c1f95c651af9a..a0d2861320a70 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -1332,6 +1332,7 @@ in qtile = runTestOn [ "x86_64-linux" "aarch64-linux" ] ./qtile/default.nix; qtile-extras = runTestOn [ "x86_64-linux" "aarch64-linux" ] ./qtile-extras/default.nix; quake3 = runTest ./quake3.nix; + qui = runTest ./qui.nix; quicktun = runTest ./quicktun.nix; quickwit = runTest ./quickwit.nix; rabbitmq = runTest ./rabbitmq.nix; diff --git a/nixos/tests/qui.nix b/nixos/tests/qui.nix new file mode 100644 index 0000000000000..ebef935fe82f0 --- /dev/null +++ b/nixos/tests/qui.nix @@ -0,0 +1,18 @@ +{ lib, ... }: + +{ + name = "qui"; + meta.maintainers = with lib.maintainers; [ tcheronneau ]; + + nodes.machine = + { pkgs, ... }: + { + services.qui.enable = true; + }; + + testScript = '' + machine.wait_for_unit("qui.service") + machine.wait_for_open_port(7476) + machine.succeed("curl --fail http://localhost:7476/") + ''; +}