nixos/rqbit: init service module

This commit is contained in:
Dan Lock
2026-01-24 14:26:13 +00:00
parent 75fa16d399
commit 084807da0f
6 changed files with 195 additions and 7 deletions
@@ -20,6 +20,8 @@
- [reaction](https://reaction.ppom.me/), a daemon that scans program outputs for repeated patterns, and takes action. A common usage is to scan ssh and webserver logs, and to ban hosts that cause multiple authentication errors. A modern alternative to fail2ban. Available as [services.reaction](#opt-services.reaction.enable).
- [rqbit](https://github.com/ikatson/rqbit), a bittorrent client written in Rust. It has HTTP API and Web UI, and can be used as a library. Available as [services.rqbit](#opt-services.rqbit.enable).
- [Tailscale Serve](https://tailscale.com/kb/1552/tailscale-services), configure Tailscale Serve for exposing local services to your tailnet. Available as [services.tailscale.serve](#opt-services.tailscale.serve.enable).
- [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).
+1
View File
@@ -1553,6 +1553,7 @@
./services/torrent/peerflix.nix
./services/torrent/qbittorrent.nix
./services/torrent/qui.nix
./services/torrent/rqbit.nix
./services/torrent/rtorrent.nix
./services/torrent/torrentstream.nix
./services/torrent/transmission.nix
+151
View File
@@ -0,0 +1,151 @@
{
config,
lib,
pkgs,
...
}:
let
inherit (lib)
mkEnableOption
mkOption
types
mkPackageOption
mkIf
;
cfg = config.services.rqbit;
stateDir = "/var/lib/rqbit";
defaultDownloadDir = "${stateDir}/downloads";
in
{
options.services.rqbit = {
enable = mkEnableOption "rqbit BitTorrent daemon";
package = mkPackageOption pkgs "rqbit" { };
user = mkOption {
type = types.str;
default = "rqbit";
description = "User account under which rqbit runs.";
};
group = mkOption {
type = types.str;
default = "rqbit";
description = "Group account under which rqbit runs.";
};
downloadDir = mkOption {
type = types.path;
default = defaultDownloadDir;
example = "/mnt/storage/torrents";
description = "Directory where to download torrents.";
};
httpPort = mkOption {
type = types.port;
default = 3030;
description = "The listen port for the HTTP API.";
};
httpHost = mkOption {
type = types.str;
default = "127.0.0.1";
example = "0.0.0.0";
description = "The listen host for the HTTP API.";
};
peerPort = mkOption {
type = types.port;
default = 4240;
description = "The port to listen for incoming BitTorrent peer connections (TCP and uTP).";
};
openFirewall = mkEnableOption "opening of the HTTP and Peer ports in the firewall";
};
config = mkIf cfg.enable {
systemd.services.rqbit = {
description = "rqbit BitTorrent Service";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
environment = {
HOME = stateDir;
RQBIT_HTTP_API_LISTEN_ADDR = "${
if (lib.hasInfix ":" cfg.httpHost) then "[${cfg.httpHost}]" else cfg.httpHost
}:${toString cfg.httpPort}";
RQBIT_LISTEN_PORT = toString cfg.peerPort;
RQBIT_SESSION_PERSISTENCE_LOCATION = stateDir;
};
serviceConfig = {
ExecStart = "${lib.getExe cfg.package} server start ${cfg.downloadDir}";
User = cfg.user;
Group = cfg.group;
StateDirectory = "rqbit";
StateDirectoryMode = "0750";
# systemd-analyze security rqbit
ReadWritePaths = mkIf (cfg.downloadDir != defaultDownloadDir) [ cfg.downloadDir ];
ProtectSystem = "strict";
ProtectHome = "read-only";
PrivateTmp = true;
PrivateDevices = true;
ProtectProc = "invisible";
ProcSubset = "pid";
PrivateUsers = true;
RemoveIPC = true;
CapabilityBoundingSet = "";
NoNewPrivileges = true;
ProtectKernelTunables = true;
ProtectKernelModules = true;
ProtectKernelLogs = true;
ProtectControlGroups = true;
ProtectClock = true;
ProtectHostname = true;
RestrictNamespaces = true;
SystemCallFilter = [
"@system-service"
"@network-io"
"@file-system"
"~@privileged"
"~@resources"
];
SystemCallArchitectures = "native";
RestrictRealtime = true;
RestrictSUIDSGID = true;
MemoryDenyWriteExecute = true;
LockPersonality = true;
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_UNIX"
];
UMask = "0027";
};
};
users = {
users = mkIf (cfg.user == "rqbit") {
rqbit = {
inherit (cfg) group;
isSystemUser = true;
};
};
groups = mkIf (cfg.group == "rqbit") { rqbit = { }; };
};
networking.firewall = mkIf cfg.openFirewall {
allowedTCPPorts = [
cfg.httpPort
cfg.peerPort
];
allowedUDPPorts = [ cfg.peerPort ];
};
};
meta.maintainers = with lib.maintainers; [ CodedNil ];
}
+1
View File
@@ -1385,6 +1385,7 @@ in
rosenpass = runTest ./rosenpass.nix;
roundcube = runTest ./roundcube.nix;
routinator = handleTest ./routinator.nix { };
rqbit = handleTest ./rqbit.nix { };
rshim = handleTest ./rshim.nix { };
rspamd = handleTest ./rspamd.nix { };
rspamd-trainer = runTest ./rspamd-trainer.nix;
+30
View File
@@ -0,0 +1,30 @@
import ./make-test-python.nix (
{ pkgs, ... }:
let
port = 3030;
in
{
name = "rqbit";
meta = {
maintainers = with pkgs.lib.maintainers; [ CodedNil ];
};
nodes.machine =
{ pkgs, ... }:
{
services.rqbit = {
httpPort = port;
enable = true;
openFirewall = true;
};
};
testScript = /* python */ ''
machine.start()
machine.wait_for_unit("rqbit.service")
machine.wait_for_open_port(${toString port})
machine.succeed("curl --fail http://localhost:${toString port}")
'';
}
)
+10 -7
View File
@@ -8,6 +8,7 @@
buildNpmPackage,
nodejs,
nix-update-script,
nixosTests,
}:
let
pname = "rqbit";
@@ -62,13 +63,15 @@ rustPlatform.buildRustPackage {
doCheck = false;
passthru.webui = rqbit-webui;
passthru.updateScript = nix-update-script {
extraArgs = [
"--subpackage"
"webui"
];
passthru = {
webui = rqbit-webui;
updateScript = nix-update-script {
extraArgs = [
"--subpackage"
"webui"
];
};
tests.testService = nixosTests.rqbit;
};
meta = {