feat: added bookmarks

This commit is contained in:
Nick 2025-12-04 15:57:23 -06:00
parent dda9c7dad7
commit 6c645d1dbe
6 changed files with 15 additions and 22 deletions

View file

@ -0,0 +1,359 @@
{
config,
lib,
pkgs,
...
}:
with lib;
let
cfg = config.services.rqbit;
in
{
options.services.rqbit = {
enable = mkEnableOption "rqbit BitTorrent client";
package = mkOption {
type = types.package;
default = pkgs.rqbit;
defaultText = literalExpression "pkgs.rqbit";
description = "The rqbit package to use.";
};
dataDir = mkOption {
type = types.path;
default = "/var/lib/rqbit";
description = "Directory to store downloaded torrents.";
};
# HTTP API Configuration
httpApi = {
listenAddress = mkOption {
type = types.str;
default = "127.0.0.1";
description = "IP address to listen on for the web UI and API.";
};
listenPort = mkOption {
type = types.port;
default = 3030;
description = "Port for the web UI and API.";
};
openFirewall = mkOption {
type = types.bool;
default = false;
description = "Open the firewall for the web UI port.";
};
};
# BitTorrent TCP Configuration
tcp = {
minPort = mkOption {
type = types.port;
default = 4240;
description = "Minimum port for incoming BitTorrent connections.";
};
maxPort = mkOption {
type = types.port;
default = 4260;
description = "Maximum port for incoming BitTorrent connections.";
};
disable = mkOption {
type = types.bool;
default = false;
description = "Disable listening for incoming TCP connections.";
};
openFirewall = mkOption {
type = types.bool;
default = false;
description = "Open firewall ports for incoming BitTorrent connections.";
};
};
# DHT Configuration
dht = {
disable = mkOption {
type = types.bool;
default = false;
description = "Disable DHT (Distributed Hash Table) for peer discovery.";
};
disablePersistence = mkOption {
type = types.bool;
default = false;
description = "Disable DHT state persistence (useful for multiple instances).";
};
};
# UPnP Configuration
upnp = {
disablePortForward = mkOption {
type = types.bool;
default = false;
description = "Disable UPnP port forwarding.";
};
enableServer = mkOption {
type = types.bool;
default = false;
description = "Enable UPnP Media Server to stream torrents.";
};
serverFriendlyName = mkOption {
type = types.nullOr types.str;
default = null;
description = "Friendly name for the UPnP server.";
example = "rqbit Media Server";
};
};
# Rate Limiting
rateLimit = {
download = mkOption {
type = types.nullOr types.int;
default = null;
description = "Download rate limit in bytes per second.";
example = 1048576; # 1 MB/s
};
upload = mkOption {
type = types.nullOr types.int;
default = null;
description = "Upload rate limit in bytes per second.";
example = 524288; # 512 KB/s
};
};
# Logging Configuration
logging = {
level = mkOption {
type = types.nullOr (
types.enum [
"trace"
"debug"
"info"
"warn"
"error"
]
);
default = null;
description = "Console log level.";
};
file = mkOption {
type = types.nullOr types.path;
default = null;
description = "Log file path (in addition to console logging).";
example = "/var/log/rqbit/rqbit.log";
};
fileRustLog = mkOption {
type = types.str;
default = "librqbit=debug,info";
description = "RUST_LOG value for the log file.";
};
};
# Performance Configuration
performance = {
workerThreads = mkOption {
type = types.nullOr types.ints.positive;
default = null;
description = "Number of worker threads for the executor.";
};
maxBlockingThreads = mkOption {
type = types.ints.positive;
default = 8;
description = "Maximum blocking threads for disk I/O operations.";
};
singleThreadRuntime = mkOption {
type = types.bool;
default = false;
description = "Use tokio's single-threaded runtime (for debugging).";
};
concurrentInitLimit = mkOption {
type = types.ints.positive;
default = 5;
description = "Maximum number of torrents that can initialize simultaneously.";
};
};
# Peer Configuration
peer = {
connectTimeout = mkOption {
type = types.str;
default = "2s";
description = "Peer connection timeout.";
example = "1.5s";
};
readWriteTimeout = mkOption {
type = types.str;
default = "10s";
description = "Peer read/write timeout.";
example = "5s";
};
};
# Tracker Configuration
tracker = {
refreshInterval = mkOption {
type = types.nullOr types.str;
default = null;
description = "Force a specific tracker refresh interval.";
example = "30s";
};
trackersFile = mkOption {
type = types.nullOr types.path;
default = null;
description = "File with tracker URLs to use for all torrents.";
};
};
# Advanced Options
socksProxy = mkOption {
type = types.nullOr types.str;
default = null;
description = "SOCKS5 proxy URL.";
example = "socks5://user:pass@localhost:1080";
};
blocklistUrl = mkOption {
type = types.nullOr types.str;
default = null;
description = "URL to download a P2P blocklist from.";
example = "https://example.com/blocklist.txt";
};
umask = mkOption {
type = types.nullOr types.str;
default = null;
description = "Set the process umask for file creation permissions.";
example = "022";
};
# User/Group Configuration
user = mkOption {
type = types.str;
default = "rqbit";
description = "User account under which rqbit runs.";
};
group = mkOption {
type = types.str;
default = "rqbit";
description = "Group under which rqbit runs.";
};
extraArgs = mkOption {
type = types.listOf types.str;
default = [ ];
description = "Extra command-line arguments to pass to rqbit.";
example = literalExpression ''[ "--experimental-mmap-storage" ]'';
};
};
config = mkIf cfg.enable {
systemd.services.rqbit = {
description = "rqbit BitTorrent Client";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
preStart = mkIf (cfg.logging.file != null) ''
mkdir -p $(dirname ${cfg.logging.file})
chown ${cfg.user}:${cfg.group} $(dirname ${cfg.logging.file})
'';
serviceConfig = {
Type = "simple";
User = cfg.user;
Group = cfg.group;
Environment = [
"XDG_CACHE_HOME=${cfg.dataDir}/.cache"
"XDG_DATA_HOME=${cfg.dataDir}/.local/share"
];
ExecStart =
let
args = [
"${cfg.package}/bin/rqbit"
"--http-api-listen-addr ${cfg.httpApi.listenAddress}:${toString cfg.httpApi.listenPort}"
]
++ optional (cfg.logging.level != null) "-v ${cfg.logging.level}"
++ optional (cfg.logging.file != null) "--log-file ${cfg.logging.file}"
++ optional (cfg.logging.file != null) "--log-file-rust-log ${cfg.logging.fileRustLog}"
++ optional (cfg.tracker.refreshInterval != null) "-i ${cfg.tracker.refreshInterval}"
++ optional cfg.performance.singleThreadRuntime "-s"
++ optional cfg.dht.disable "--disable-dht"
++ optional cfg.dht.disablePersistence "--disable-dht-persistence"
++ optional (cfg.peer.connectTimeout != "2s") "--peer-connect-timeout ${cfg.peer.connectTimeout}"
++ optional (
cfg.peer.readWriteTimeout != "10s"
) "--peer-read-write-timeout ${cfg.peer.readWriteTimeout}"
++ optional (cfg.performance.workerThreads != null) "-t ${toString cfg.performance.workerThreads}"
++ optional cfg.tcp.disable "--disable-tcp-listen"
++ optional (cfg.tcp.minPort != 4240) "--tcp-min-port ${toString cfg.tcp.minPort}"
++ optional (cfg.tcp.maxPort != 4260) "--tcp-max-port ${toString cfg.tcp.maxPort}"
++ optional cfg.upnp.disablePortForward "--disable-upnp-port-forward"
++ optional cfg.upnp.enableServer "--enable-upnp-server"
++ optional (
cfg.upnp.serverFriendlyName != null
) "--upnp-server-friendly-name '${cfg.upnp.serverFriendlyName}'"
++ optional (
cfg.performance.maxBlockingThreads != 8
) "--max-blocking-threads ${toString cfg.performance.maxBlockingThreads}"
++ optional (cfg.socksProxy != null) "--socks-url ${cfg.socksProxy}"
++ optional (
cfg.performance.concurrentInitLimit != 5
) "--concurrent-init-limit ${toString cfg.performance.concurrentInitLimit}"
++ optional (cfg.umask != null) "--umask ${cfg.umask}"
++ optional (
cfg.rateLimit.download != null
) "--ratelimit-download ${toString cfg.rateLimit.download}"
++ optional (cfg.rateLimit.upload != null) "--ratelimit-upload ${toString cfg.rateLimit.upload}"
++ optional (cfg.blocklistUrl != null) "--blocklist-url ${cfg.blocklistUrl}"
++ optional (cfg.tracker.trackersFile != null) "--trackers-filename ${cfg.tracker.trackersFile}"
++ cfg.extraArgs
++ [
"server"
"start"
cfg.dataDir
];
in
concatStringsSep " " args;
Restart = "on-failure";
StateDirectory = "rqbit";
NoNewPrivileges = true;
PrivateTmp = true;
ProtectSystem = "strict";
ReadWritePaths = [ cfg.dataDir ] ++ optional (cfg.logging.file != null) (dirOf cfg.logging.file);
};
};
users.users = mkIf (cfg.user == "rqbit") {
rqbit = {
isSystemUser = true;
group = cfg.group;
description = "rqbit BitTorrent client user";
};
};
users.groups = mkIf (cfg.group == "rqbit") {
rqbit = { };
};
networking.firewall = mkIf (cfg.httpApi.openFirewall || cfg.tcp.openFirewall) {
allowedTCPPorts = optional cfg.httpApi.openFirewall cfg.httpApi.listenPort;
allowedTCPPortRanges = optional cfg.tcp.openFirewall {
from = cfg.tcp.minPort;
to = cfg.tcp.maxPort;
};
};
};
}