{ config, pkgs, ... }: let cfg = { stateDir = "/var/lib/qbittorrent"; downloadDir = "/var/videos/"; # TODO support other Media Types port = 8081; user = "qbittorrent"; }; in { imports = [ ./exporter.nix ]; age.secrets.mullvad.file = ../../../../secrets/nuc/mullvad.age; age.secrets.airvpn-private.file = ../../../../secrets/nuc/airvpn/private.age; age.secrets.airvpn-psk.file = ../../../../secrets/nuc/airvpn/psk.age; environment.etc."netns/torrent/resolv.conf".text = '' nameserver 9.9.9.9 ''; systemd.services."netns@" = { description = "%I network namespace"; before = [ "network.target" ]; serviceConfig = { Type = "oneshot"; RemainAfterExit = true; ExecStart = "${pkgs.iproute2}/bin/ip netns add %I"; ExecStartPost = "${pkgs.iproute2}/bin/ip netns exec %I ${pkgs.iproute2}/bin/ip link set dev lo up"; ExecStop = "${pkgs.iproute2}/bin/ip netns del %I"; }; }; systemd.services."qbittorrent-portforward@" = { description = "%I network namespace port mapping"; requires = [ "netns@%i.service" ]; after = [ "netns@%i.service" ]; # wantedBy = [ "multi-user.target" ]; serviceConfig = { Restart = "on-failure"; TimeoutStopSec = 300; ExecStart = ''${pkgs.socat}/bin/socat tcp-listen:${toString cfg.port},fork,reuseaddr exec:'${pkgs.iproute2}/bin/ip netns exec %I ${pkgs.socat}/bin/socat STDIO "tcp-connect:127.0.0.1:${toString cfg.port}"',nofork''; # ExecStart = [ # "${pkgs.socat}/bin/socat tcp-listen:%j,fork,reuseaddr" # ''${pkgs.iproute2}/bin/ip netns exec %I ${pkgs.socat}/bin/socat STDIO "tcp-connect:127.0.0.1:%j",nofork'' # ]; }; }; # scripted wireguard since systemd-networkd doesn't support netns yet networking.wireguard.useNetworkd = false; # networking.wireguard.interfaces."wg0-mullvad" = { # # Funny Mole # privateKeyFile = config.age.secrets.mullvad.path; # ips = [ "10.67.237.93/32" ]; # peers = [ # { # publicKey = "QEVIaIycN8p5twXCuZeQTEj9utozakw/MU8H6+/whls="; # allowedIPs = [ "0.0.0.0/0" ]; # endpoint = "138.199.34.129:51820"; # } # ]; # interfaceNamespace = "torrent"; # }; # systemd.services."wireguard-wg0-mullvad" = { # requires = [ "netns@torrent.service" ]; # }; networking.wireguard.interfaces."wg1-airvpn" = { privateKeyFile = config.age.secrets.airvpn-private.path; ips = [ " 10.146.65.170/32" "fd7d:76ee:e68f:a993:366:82ed:bc88:b04a/128" ]; peers = [ { publicKey = "PyLCXAQT8KkM4T+dUsOQfn+Ub3pGxfGlxkIApuig+hk="; presharedKeyFile = config.age.secrets.airvpn-psk.path; allowedIPs = [ "0.0.0.0/0" "::/0" ]; endpoint = "europe3.vpn.airdns.org:1637"; } ]; interfaceNamespace = "torrent"; }; systemd.services."wireguard-wg1-airvpn" = { requires = [ "netns@torrent.service" ]; }; users.users.${cfg.user} = { group = cfg.user; home = cfg.stateDir; isSystemUser = true; extraGroups = [ "media" ]; }; users.groups.${cfg.user} = { }; systemd.services."qbittorrent" = { description = "qBittorrent Service"; bindsTo = [ "netns@torrent.service" ]; after = [ "netns@torrent.service" ]; requires = [ "qbittorrent-portforward@torrent.service" ]; wantedBy = [ "multi-user.target" ]; serviceConfig = { User = cfg.user; Group = cfg.user; Restart = "always"; # PrivateNetwork = true; NetworkNamespacePath = "/var/run/netns/torrent"; ExecStart = "${pkgs.qbittorrent-nox}/bin/qbittorrent-nox --profile=${cfg.stateDir} --webui-port=${toString cfg.port}"; # Increase number of open file descriptors (default: 1024) # LimitNOFILE = 65536; # Avoid using nscd (leaks dns) InaccessiblePaths = [ "/run/nscd" ]; BindReadOnlyPaths = [ "/etc/netns/torrent/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"; }; }; networking.firewall.allowedTCPPorts = [ cfg.port ]; systemd.tmpfiles.rules = [ # ensure downloads directory is created, set permissions "d ${cfg.stateDir} - ${cfg.user} ${cfg.user} - -" "d ${cfg.stateDir}/qBittorrent - ${cfg.user} ${cfg.user} - -" ]; }