summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--hosts/ahmed/configuration.nix1
-rw-r--r--hosts/ahmed/hellohtml.linus.onl/default.nix2
-rw-r--r--hosts/ahmed/torrenting/default.nix37
-rw-r--r--modules/nixos/default.nix1
-rw-r--r--modules/nixos/qbittorrent/default.nix166
-rw-r--r--overlays/default.nix14
6 files changed, 220 insertions, 1 deletions
diff --git a/hosts/ahmed/configuration.nix b/hosts/ahmed/configuration.nix
index 27c35eb..9d49d32 100644
--- a/hosts/ahmed/configuration.nix
+++ b/hosts/ahmed/configuration.nix
@@ -17,6 +17,7 @@
./linus.onl
./nofitications.linus.onl
./ssh
+ ./torrenting
./home
];
diff --git a/hosts/ahmed/hellohtml.linus.onl/default.nix b/hosts/ahmed/hellohtml.linus.onl/default.nix
index 4859ed3..f973c11 100644
--- a/hosts/ahmed/hellohtml.linus.onl/default.nix
+++ b/hosts/ahmed/hellohtml.linus.onl/default.nix
@@ -20,7 +20,7 @@ in {
];
# Use NGINX as reverse proxy.
- services.nginx.virtualHosts.${mainDomain}= {
+ services.nginx.virtualHosts.${mainDomain} = {
# Set up secondary domain name to also point to this host. Only the
# client (browser) should treat these as separate. On the server, they
# are the same.
diff --git a/hosts/ahmed/torrenting/default.nix b/hosts/ahmed/torrenting/default.nix
new file mode 100644
index 0000000..c49baa6
--- /dev/null
+++ b/hosts/ahmed/torrenting/default.nix
@@ -0,0 +1,37 @@
+# This module configures the my torrenting setup. It uses qBittorrent over a VPN.
+{pkgs, options, config, ...}: let
+ downloadPath = "/srv/media/";
+
+ interface = "tun0";
+in {
+ # Configure the actual qBittorrent service.
+ services.qbittorrent = {
+ enable = true;
+
+ openFirewall = true; # TEMP: reverse proxy will cover this instead
+
+ settings = {
+ BitTorrent = {
+ # Use the specified download path for finished torrents.
+ "Session\\DefaultSavePath" = downloadPath;
+ "Session\\TempPath" = "${config.services.qbittorrent.profile}/qBittorrent/temp";
+ "Session\\TempPathEnabled" = true;
+ };
+
+ # Instruct qBittorrent to only use VPN interface.
+ };
+ };
+
+ # Create the directory to which media will be downloaded.
+ # This is also used by Jellyfin to serve the files.
+ systemd.tmpfiles.rules = let
+ user = options.services.qbittorrent.user.default;
+ group = options.services.qbittorrent.group.default;
+ in [
+ "d ${downloadPath} 0755 ${user} ${group}"
+ ];
+
+ # Create a connection to Mullvad's WireGuard server.
+
+ # Use NGINX as a reverse proxy for qBittorrent's WebUI.
+}
diff --git a/modules/nixos/default.nix b/modules/nixos/default.nix
index b813155..77a5faa 100644
--- a/modules/nixos/default.nix
+++ b/modules/nixos/default.nix
@@ -1,4 +1,5 @@
{
on-demand-minecraft = import ./on-demand-minecraft;
hellohtml = import ./hellohtml;
+ qbittorrent = import ./qbittorrent;
}
diff --git a/modules/nixos/qbittorrent/default.nix b/modules/nixos/qbittorrent/default.nix
new file mode 100644
index 0000000..2d2c591
--- /dev/null
+++ b/modules/nixos/qbittorrent/default.nix
@@ -0,0 +1,166 @@
+# This module defines a service which runs a headless qBittorrent instance
+{
+ config,
+ options,
+ lib,
+ pkgs,
+ ...
+}: let
+ defaultUser = "qbittorrent";
+ defaultGroup = "qbittorrent";
+
+ cfg = config.services.qbittorrent;
+in {
+ options.services.qbittorrent = {
+ enable = lib.mkEnableOption "headless qBittorrent instance";
+
+ port = lib.mkOption {
+ description = "The port on which to serve the WebUI.";
+ type = lib.types.port;
+ default = 8080;
+ };
+
+ openFirewall = lib.mkOption {
+ description = ''
+ Open holes in the firewall so clients on LAN can connect to the web
+ interface. You must set up port forwarding if you want it accessable
+ from the wider internet.
+ '';
+ type = lib.types.bool;
+ default = false;
+ };
+
+ profile = lib.mkOption {
+ description = ''
+ The directory where qBittorrent should store it's data files. Note that
+ these are for qBittorrent itself, not the files it downloads. Those are
+ controlled through the configuration option `XXX`.
+ '';
+ type = lib.types.path;
+ default = "/var/lib/qBittorrent";
+ };
+
+ user = lib.mkOption {
+ description = ''
+ The user to run the qBittorrent service as.
+
+ The user is not automatically created if it is changed from the default value.
+ '';
+ type = lib.types.str;
+ default = defaultUser;
+ };
+
+ group = lib.mkOption {
+ description = ''
+ The group to run the qBittorrent service as.
+
+ The group is not automatically created if it is changed from the default value.
+ '';
+ type = lib.types.str;
+ default = defaultGroup;
+ };
+
+ package = lib.mkOption {
+ description = "qBittorrent package to use";
+ type = lib.types.package;
+ default = pkgs.qbittorrent-nox;
+ };
+
+ settings = lib.mkOption rec {
+ description = ''
+ An attribute set whose values overrides the ones specified in
+ `qBittorrent.conf`.
+
+ These values are applied on top of the existing configuration when the
+ service starts. This is a necessary compromise between determinism and
+ usability, as qBittorrent also saves all kinds of gunk in the
+ configuration file.
+ '';
+ type = lib.types.attrs;
+ default = {
+ LegalNotice = {
+ Accepted = false;
+ };
+ };
+ apply = lib.recursiveUpdate default;
+ example = {
+ LegalNotice = {
+ Accepted = true;
+ };
+ Preferences = {
+ "Connection\\PortRangeMin" = 20082;
+ "Downloads\\SavePath" = "/mnt";
+ "WebUI\\UseUPnP" = false;
+ };
+ };
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ # Create the user/group if required.
+ users.users = lib.mkIf (cfg.user == defaultUser) {
+ ${defaultUser} = {
+ description = "Runs ${options.services.qbittorrent.enable.description}";
+ group = cfg.group;
+ isSystemUser = true;
+ };
+ };
+ users.groups = lib.mkIf (cfg.group == defaultGroup) {
+ ${defaultGroup} = {};
+ };
+
+ # Set up a service to run qBittorrent
+ # See: https://github.com/qbittorrent/qBittorrent/blob/615b76f78c8ab92ad57bed42fc4266950c9f0251/dist/unix/systemd/qbittorrent-nox%40.service.in
+ systemd.services.qbittorrent = {
+ enable = true;
+
+ unitConfig = {
+ Wants = ["network-online.target"];
+ After = ["local-fs.target" "network-online.target" "nss-lookup.target"];
+ };
+
+ serviceConfig = {
+ Type = "simple";
+
+ User = cfg.user;
+ Group = cfg.group;
+ PrivateTmp = false;
+
+ ExecStartPre = let
+ format = pkgs.formats.ini {};
+ settingsFile = format.generate "qBittorrent.conf" cfg.settings;
+ configPath = "${cfg.profile}/qBittorrent/config/qBittorrent.conf";
+
+ start-pre-script = pkgs.writeShellScript "qbittorrent-start-pre" ''
+ set -ue
+
+ # Create data directory if it doesn't exist
+ if ! test -d ${cfg.profile}; then
+ echo "Creating initial qBittorrent data directory in: ${cfg.profile}"
+ install -d -m 0755 -o ${cfg.user} -g ${cfg.group} ${cfg.profile}/qBittorrent/config/
+ fi
+
+ # Force-apply configuration.
+ ${pkgs.crudini}/bin/crudini --ini-options=nospace --merge ${configPath} <${settingsFile}
+ '';
+ in
+ # Requires full permissions to create data directory, hence the "!".
+ "!${start-pre-script}";
+ ExecStart = pkgs.writeShellScript "qbittorrent-start" ''
+ exec ${cfg.package}/bin/qbittorrent-nox --webui-port=${toString cfg.port} --profile=${cfg.profile}
+ '';
+ TimeoutStopSec = 1800;
+
+ # Set as low-priority
+ IOSchedulingClass = "idle";
+ IOSchedulingPriority = "7";
+ };
+
+ wantedBy = ["multi-user.target"];
+ };
+
+ networking.firewall = lib.mkIf cfg.openFirewall {
+ allowedTCPPorts = [cfg.port];
+ };
+ };
+}
diff --git a/overlays/default.nix b/overlays/default.nix
index b87fbfb..438d924 100644
--- a/overlays/default.nix
+++ b/overlays/default.nix
@@ -19,6 +19,20 @@
+ (final.lib.strings.optionalString (final.stdenv.cc.isGNU or false) " -Wno-maybe-uninitialized");
});
+ # Use a slightly newer version of qBittorrent that doesn't include the password bug.
+ #
+ # See: https://old.reddit.com/r/qBittorrent/comments/1827zqn/locked_out_of_qbittorrent/kahat1u/?context=3
+ # See: https://www.qbittorrent.org/news#mon-nov-27th-2023---qbittorrent-v4.6.2-release
+ qbittorrent-nox = prev.qbittorrent-nox.overrideAttrs (old: rec {
+ version = "4.6.2";
+ src = final.fetchFromGitHub {
+ owner = "qbittorrent";
+ repo = "qBittorrent";
+ rev = "release-${version}";
+ hash = "sha256-+leX0T+yJUG6F7WbHa3nCexQZmd7RRfK8Uc+suMJ+vI=";
+ };
+ });
+
# Use newest version.
noweb = prev.noweb.overrideAttrs (old: rec {
version = "2_13rc3";