Skip to content

Commit 121352f

Browse files
Merge release-25.11 into staging-next-25.11
2 parents 36dfaa8 + bf11712 commit 121352f

33 files changed

Lines changed: 1003 additions & 727 deletions

File tree

nixos/doc/manual/release-notes/rl-2605.section.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212

1313
- [knot-resolver](https://www.knot-resolver.cz/) in version 6. Available as `services.knot-resolver`. A module for knot-resolver 5 was already available as `services.kresd`.
1414

15+
- [qui](https://github.com/autobrr/qui), a modern alternative webUI for qBittorrent, with multi-instance support. Written in Go/React. Available as [services.qui](#opt-services.qui.enable).
16+
1517
## Backward Incompatibilities {#sec-release-26.05-incompatibilities}
1618

1719
<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->

nixos/modules/module-list.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1539,6 +1539,7 @@
15391539
./services/torrent/opentracker.nix
15401540
./services/torrent/peerflix.nix
15411541
./services/torrent/qbittorrent.nix
1542+
./services/torrent/qui.nix
15421543
./services/torrent/rqbit.nix
15431544
./services/torrent/rtorrent.nix
15441545
./services/torrent/torrentstream.nix

nixos/modules/services/monitoring/prometheus/default.nix

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ let
9797
++ (
9898
if (cfg.enableAgentMode) then
9999
[
100-
"--enable-feature=agent"
100+
"--agent"
101101
]
102102
else
103103
[
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
{
2+
config,
3+
lib,
4+
pkgs,
5+
...
6+
}:
7+
8+
let
9+
inherit (lib)
10+
getExe
11+
maintainers
12+
mkEnableOption
13+
mkIf
14+
mkOption
15+
mkPackageOption
16+
;
17+
inherit (lib.types)
18+
bool
19+
path
20+
port
21+
str
22+
submodule
23+
;
24+
cfg = config.services.qui;
25+
26+
stateDir = "/var/lib/qui";
27+
configFormat = pkgs.formats.toml { };
28+
configFile = configFormat.generate "qui.toml" cfg.settings;
29+
in
30+
{
31+
options = {
32+
services.qui = {
33+
enable = mkEnableOption "qui";
34+
35+
package = mkPackageOption pkgs "qui" { };
36+
37+
user = mkOption {
38+
type = str;
39+
default = "qui";
40+
description = "User to run qui as.";
41+
};
42+
43+
group = mkOption {
44+
type = str;
45+
default = "qui";
46+
example = "torrents";
47+
description = "Group to run qui as.";
48+
};
49+
50+
openFirewall = mkOption {
51+
type = bool;
52+
default = false;
53+
description = "Whether or not to open ports in the firewall for qui.";
54+
};
55+
56+
secretFile = mkOption {
57+
type = path;
58+
example = "/run/secrets/qui-session.txt";
59+
description = ''
60+
Path to a file that contains the session secret. The session secret
61+
can be generated with `openssl rand -hex 32`.
62+
'';
63+
};
64+
65+
settings = mkOption {
66+
default = { };
67+
example = {
68+
port = 7777;
69+
logLevel = "DEBUG";
70+
metricsEnabled = true;
71+
};
72+
type = submodule {
73+
freeformType = configFormat.type;
74+
options = {
75+
host = mkOption {
76+
type = str;
77+
default = "127.0.0.1";
78+
description = "The host address qui listens on.";
79+
};
80+
81+
port = mkOption {
82+
type = port;
83+
default = 7476;
84+
description = "The port qui listens on.";
85+
};
86+
};
87+
};
88+
description = ''
89+
qui configuration options.
90+
91+
Refer to the [template config](https://github.com/autobrr/qui/blob/main/internal/config/config.go)
92+
in the source code for the available options.
93+
The documentation contains the available [environment variables](https://getqui.com/docs/configuration/environment/),
94+
this can be used to get an overview.
95+
'';
96+
};
97+
98+
};
99+
};
100+
101+
config = mkIf cfg.enable {
102+
assertions = [
103+
{
104+
assertion = !(cfg.settings ? sessionSecret);
105+
message = ''
106+
Session secrets should not be passed via settings, as
107+
these are stored in the world-readable nix store.
108+
109+
Use the secretFile option instead.'';
110+
}
111+
];
112+
113+
systemd.services.qui = {
114+
description = "qui: alternative qBittorrent webUI";
115+
after = [ "network-online.target" ];
116+
wants = [ "network-online.target" ];
117+
wantedBy = [ "multi-user.target" ];
118+
119+
serviceConfig = {
120+
Type = "simple";
121+
User = cfg.user;
122+
Group = cfg.group;
123+
124+
LoadCredential = "sessionSecret:${cfg.secretFile}";
125+
Environment = [ "QUI__SESSION_SECRET_FILE=%d/sessionSecret" ];
126+
StateDirectory = "qui";
127+
128+
ExecStartPre = ''
129+
${pkgs.coreutils}/bin/install -m 600 '${configFile}' '%S/qui/config.toml'
130+
'';
131+
ExecStart = "${getExe cfg.package} serve --config-dir %S/qui";
132+
Restart = "on-failure";
133+
134+
# Based on qbittorrent and nemorosa hardening settings
135+
# Similar to what systemd hardening helper suggests
136+
CapabilityBoundingSet = "";
137+
LockPersonality = true;
138+
MemoryDenyWriteExecute = true;
139+
NoNewPrivileges = true;
140+
PrivateDevices = true;
141+
PrivateNetwork = false;
142+
PrivateTmp = true;
143+
PrivateUsers = true;
144+
ProcSubset = "pid";
145+
ProtectClock = true;
146+
ProtectControlGroups = true;
147+
ProtectHome = "yes";
148+
ProtectHostname = true;
149+
ProtectKernelLogs = true;
150+
ProtectKernelModules = true;
151+
ProtectKernelTunables = true;
152+
ProtectProc = "invisible";
153+
# This should allow for hardlinking to torrent client files
154+
ProtectSystem = "full";
155+
RemoveIPC = true;
156+
RestrictAddressFamilies = [
157+
"AF_INET"
158+
"AF_INET6"
159+
"AF_NETLINK"
160+
"AF_UNIX"
161+
];
162+
RestrictNamespaces = true;
163+
RestrictRealtime = true;
164+
RestrictSUIDSGID = true;
165+
SystemCallArchitectures = "native";
166+
SystemCallFilter = [ "@system-service" ];
167+
};
168+
};
169+
170+
networking.firewall = mkIf cfg.openFirewall {
171+
allowedTCPPorts = [ cfg.settings.port ];
172+
};
173+
174+
users = {
175+
users = mkIf (cfg.user == "qui") {
176+
qui = {
177+
group = cfg.group;
178+
description = "qui user";
179+
isSystemUser = true;
180+
home = stateDir;
181+
};
182+
};
183+
184+
groups = mkIf (cfg.group == "qui") {
185+
qui = { };
186+
};
187+
};
188+
};
189+
190+
meta.maintainers = with maintainers; [ undefined-landmark ];
191+
}

nixos/modules/services/web-apps/firefly-iii-data-importer.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,7 @@ in
267267
"${cfg.dataDir}/storage/framework/sessions"
268268
"${cfg.dataDir}/storage/framework/testing"
269269
"${cfg.dataDir}/storage/framework/views"
270+
"${cfg.dataDir}/storage/import-jobs"
270271
"${cfg.dataDir}/storage/jobs"
271272
"${cfg.dataDir}/storage/logs"
272273
"${cfg.dataDir}/storage/submission-routines"

nixos/tests/all-tests.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1325,6 +1325,7 @@ in
13251325
qtile = runTestOn [ "x86_64-linux" "aarch64-linux" ] ./qtile/default.nix;
13261326
qtile-extras = runTestOn [ "x86_64-linux" "aarch64-linux" ] ./qtile-extras/default.nix;
13271327
quake3 = runTest ./quake3.nix;
1328+
qui = runTest ./qui.nix;
13281329
quicktun = runTest ./quicktun.nix;
13291330
quickwit = runTest ./quickwit.nix;
13301331
rabbitmq = runTest ./rabbitmq.nix;

nixos/tests/qui.nix

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{ lib, ... }:
2+
3+
{
4+
name = "qui";
5+
meta.maintainers = with lib.maintainers; [ undefined-landmark ];
6+
7+
nodes.machine =
8+
{ pkgs, ... }:
9+
let
10+
# We create this secret in the Nix store (making it readable by everyone).
11+
# DO NOT DO THIS OUTSIDE OF TESTS!!
12+
testSecretFile = pkgs.writeText "session_secret" "not-secret";
13+
in
14+
{
15+
services.qui = {
16+
enable = true;
17+
secretFile = testSecretFile;
18+
};
19+
20+
# Use port other than default to test if settings options work.
21+
specialisation.settingsPort.configuration = {
22+
services.qui = {
23+
enable = true;
24+
secretFile = testSecretFile;
25+
settings.port = 7777;
26+
};
27+
};
28+
};
29+
30+
testScript =
31+
{ nodes, ... }:
32+
let
33+
settingsPort = "${nodes.machine.system.build.toplevel}/specialisation/settingsPort";
34+
in
35+
# python
36+
''
37+
def test_webui(port):
38+
machine.wait_for_unit("qui.service")
39+
machine.wait_for_open_port(port)
40+
machine.wait_until_succeeds(f"curl --fail http://localhost:{port}")
41+
42+
test_webui(7476)
43+
44+
machine.succeed("${settingsPort}/bin/switch-to-configuration test")
45+
test_webui(7777)
46+
'';
47+
}

0 commit comments

Comments
 (0)