nixos-config/modules/qbittorrent/default.nix

198 lines
6.4 KiB
Nix

# SPDX-FileCopyrightText: 2022-2023 Simon Bruder <simon@sbruder.de>
#
# SPDX-License-Identifier: AGPL-3.0-or-later
{ config, inputs, lib, pkgs, ... }:
let
cfg = config.sbruder.qbittorrent;
in
{
options.sbruder.qbittorrent = {
enable = lib.mkEnableOption "the qbittorrent service";
homeDir = lib.mkOption {
type = lib.types.path;
default = "/var/lib/qbittorrent";
};
configDir = lib.mkOption {
type = lib.types.path;
default = "${cfg.homeDir}/config";
};
downloadDir = lib.mkOption {
type = lib.types.path;
default = "${cfg.homeDir}/download";
};
webuiPort = lib.mkOption {
type = lib.types.int;
default = 8099;
};
sopsFile = lib.mkOption {
type = lib.types.path;
default = ../../machines/${config.networking.hostName}/secrets.yaml;
description = ''
The sops secret file that includes the wireguard private key (wg-qbittorrent-private-key).
'';
};
wireguardConnectionDetails = lib.mkOption {
type = lib.types.attrs;
default = import ../../machines/${config.networking.hostName}/secrets/wireguard-qbittorrent.nix;
example = {
ips = [ "10.0.0.1/32" "fd00::1/128" ];
peers = lib.singleton {
publicKey = "MbLFO7MIFSdwVmZWQG//IUxVxwSHprRkhr/NWl2gils=";
allowedIPs = [ "0.0.0.0/0" "::0/0" ];
endpoint = "192.0.2.1:51820";
};
};
description = "The connection details for the WireGuard tunnel.";
};
fqdn = lib.mkOption {
type = lib.types.str;
description = "The fqdn nginx should listen on. It must not be used for anything else.";
};
};
config = lib.mkIf cfg.enable
{
users.users.qbittorrent = {
group = "qbittorrent";
home = cfg.homeDir;
isSystemUser = true;
};
users.groups.qbittorrent = { };
systemd.tmpfiles.rules = [
"d '${cfg.downloadDir}' 0775 qbittorrent users - -"
"d '${cfg.homeDir}' 0771 qbittorrent qbittorrent - -"
];
sops.secrets.wg-qbittorrent-private-key.sopsFile = cfg.sopsFile;
networking.wireguard.interfaces.wg-qbittorrent = {
interfaceNamespace = "qbittorrent";
preSetup = "ip netns add qbittorrent && ip -n qbittorrent link set lo up";
postShutdown = "ip netns del qbittorrent";
privateKeyFile = config.sops.secrets.wg-qbittorrent-private-key.path;
} // (builtins.removeAttrs cfg.wireguardConnectionDetails [ "nameserver" ]);
environment.etc."netns/qbittorrent/resolv.conf".text = ''
nameserver ${cfg.wireguardConnectionDetails.nameserver}
'';
systemd.services.qbittorrent = {
description = "qBittorrent Service";
after = [ "wireguard-wg-qbittorrent.service" ];
requires = [ "wireguard-wg-qbittorrent.service" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
PrivateNetwork = true;
NetworkNamespacePath = "/run/netns/qbittorrent";
Restart = "always";
ExecStart = "${pkgs.qbittorrent-nox}/bin/qbittorrent-nox --profile=${cfg.configDir} --webui-port=${toString cfg.webuiPort}";
User = "qbittorrent";
Group = "qbittorrent";
# Increase number of open file descriptors (default: 1024)
LimitNOFILE = 65536;
# Avoid using nscd (leaks dns)
InaccessiblePaths = [
"/run/nscd"
"/etc/nsswitch.conf"
];
# Make correct resolv.conf available for unit
BindReadOnlyPaths = [
"/etc/netns/qbittorrent/resolv.conf:/etc/resolv.conf"
];
# systemd-analyze --no-pager security qbittorrent.service
CapabilityBoundingSet = null;
PrivateDevices = true;
PrivateTmp = true;
PrivateUsers = true;
ProtectHome = true;
RestrictNamespaces = true;
SystemCallFilter = "@system-service";
};
};
systemd.services.qbittorrent-webui-proxy = {
wantedBy = [ "multi-user.target" ];
after = [ "wireguard-wg-qbittorrent.service" ];
partOf = [ "wireguard-wg-qbittorrent.service" ];
serviceConfig = {
PrivateNetwork = true;
NetworkNamespacePath = "/run/netns/qbittorrent";
Restart = "always";
ExecStart = "${pkgs.socat}/bin/socat UNIX-LISTEN:${cfg.homeDir}/webui.sock,fork,reuseaddr,mode=660,unlink-early TCP:127.0.0.1:${toString cfg.webuiPort}";
User = "qbittorrent";
Group = "nginx";
# systemd-analyze --no-pager security qbittorrent-webui-proxy.service
CapabilityBoundingSet = null;
PrivateDevices = true;
PrivateTmp = true;
PrivateUsers = true;
ProtectHome = true;
RestrictNamespaces = true;
SystemCallFilter = "@system-service";
};
};
systemd.services.qbittorrent_exporter = {
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
environment = {
QBITTORRENT_API_SOCKET = "${cfg.homeDir}/webui.sock";
QBITTORRENT_EXPORTER_LISTEN_ADDRESS = ":9561";
};
serviceConfig = {
ExecStart = "${pkgs.callPackage ./exporter { }}/bin/qbittorrent_exporter";
Restart = "always";
User = "qbittorrent";
Group = "qbittorrent";
# systemd-analyze --no-pager security qbittorrent_exporter.service
CapabilityBoundingSet = null;
PrivateDevices = true;
PrivateUsers = true;
ProtectHome = true;
RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
RestrictNamespaces = true;
SystemCallArchitectures = "native";
SystemCallFilter = "@system-service";
};
};
services.nginx.virtualHosts."${cfg.fqdn}" = {
enableACME = lib.mkDefault true;
forceSSL = lib.mkDefault true;
# treated as state
basicAuthFile = "${cfg.homeDir}/htpasswd";
locations = {
"/" = {
proxyPass = "http://unix:${cfg.homeDir}/webui.sock";
proxyWebsockets = true;
};
"/download/" = {
alias = "${cfg.downloadDir}/";
extraConfig = ''
autoindex on;
'';
};
"=/metrics" = {
proxyPass = "http://127.0.0.1:9561/metrics";
};
};
};
};
}