{ config, flake, pkgs, ... }: let inherit (flake.config.people) user0; inherit (flake.config.services) instances; serviceCfg = instances.torrent; host = instances.torrent.domains.url0; dns0 = instances.web.dns.provider0; dns0Path = "dns/${dns0}"; torrentPort = 51820; in { microvm.vms.${serviceCfg.name} = { autostart = true; config = { system.stateVersion = "25.05"; networking = { wg-quick.interfaces = { wg0 = { address = [ "10.2.0.2/32" ]; dns = [ "10.2.0.1" ]; privateKeyFile = "/run/secrets/wireguard-pass"; peers = [ { publicKey = "QPfiwJQmt5VLEOh1ufLbi1lj6LUnwQY0tgDSh3pWx1k="; endpoint = "185.111.110.1:${builtins.toString torrentPort}"; allowedIPs = [ "0.0.0.0/0" "::/0" ]; persistentKeepalive = 25; } ]; }; }; firewall = { enable = true; allowedTCPPorts = [ 22 torrentPort serviceCfg.ports.port0 ]; allowedUDPPorts = [ torrentPort ]; }; dhcpcd.enable = false; useNetworkd = true; }; # imports = [ # ./rqbit.nix # ]; services = { qbittorrent = { enable = true; webuiPort = serviceCfg.ports.port0; torrentingPort = torrentPort; openFirewall = true; serverConfig = { LegalNotice.Accepted = true; BitTorrent = { Session = { Interface = "wg0"; InterfaceName = "wg0"; MaxConnections = -1; Port = torrentPort; MaxConnectionsPerTorrent = -1; MaxUploads = -1; MaxActiveDownloads = 999; MaxActiveUploads = 999; MaxActiveTorrents = 999; }; }; Preferences = { WebUI = { Username = "user"; # generate new passwords with this: # https://codeberg.org/feathecutie/qbittorrent_password Password_PBKDF2 = "@ByteArray(1bJKXLVSLU6kgCHbCS2lDg==:BmyrMaod6dbJqEe7Ud/JgKAxRMqzsAuEjHcTvLzIBgc5rc5Z7J2X9mbH0cDEAhXqc+O3gQxrckt8S2Gf+zlO9w==)"; }; General = { Locale = "en"; }; Downloads = { SavePath = "${serviceCfg.varPaths.path0}/downloads"; TempPathEnabled = false; PreAllocation = false; }; }; }; }; openssh = { enable = true; settings.PasswordAuthentication = false; }; }; users.users.root.openssh.authorizedKeys.keys = flake.config.people.users.${user0}.sshKeys; systemd = { network = { enable = true; networks."10-enp" = { matchConfig.Name = "enp0s5"; addresses = [ { Address = "${serviceCfg.interface.ip}/24"; } ]; gateway = [ serviceCfg.interface.gate ]; }; }; tmpfiles.rules = [ "d ${serviceCfg.varPaths.path0} 755 ${serviceCfg.name} ${serviceCfg.name} -" "d ${serviceCfg.varPaths.path0}/downloads 755 ${serviceCfg.name} ${serviceCfg.name} -" ]; services = { qbittorrent = { after = [ "wg-quick-wg0.service" ]; requires = [ "wg-quick-wg0.service" ]; }; natpmp-portforward = { description = "NAT-PMP Port Forwarding for VPN"; after = [ "wg-quick-wg0.service" "qbittorrent.service" ]; requires = [ "wg-quick-wg0.service" "qbittorrent.service" ]; wantedBy = [ "multi-user.target" ]; serviceConfig = { Type = "simple"; Restart = "always"; RestartSec = "10s"; }; script = '' PASSWORD=$(cat /run/secrets/qbittorrent-pass) # Wait for qBittorrent to be ready echo "Waiting for qBittorrent to start..." sleep 10 # Enable IP forwarding echo 1 > /proc/sys/net/ipv4/ip_forward while true; do echo "Requesting port forwarding from VPN..." # Get the forwarded port for UDP UDP_OUTPUT=$(${pkgs.libnatpmp}/bin/natpmpc -a 1 0 udp 60 -g 10.2.0.1 2>&1) UDP_PORT=$(echo "$UDP_OUTPUT" | ${pkgs.gnugrep}/bin/grep "Mapped public port" | ${pkgs.gawk}/bin/awk '{print $4}' | head -1) # Get the forwarded port for TCP TCP_OUTPUT=$(${pkgs.libnatpmp}/bin/natpmpc -a 1 0 tcp 60 -g 10.2.0.1 2>&1) TCP_PORT=$(echo "$TCP_OUTPUT" | ${pkgs.gnugrep}/bin/grep "Mapped public port" | ${pkgs.gawk}/bin/awk '{print $4}' | head -1) if [ -n "$UDP_PORT" ] && [ -n "$TCP_PORT" ]; then echo "Port forwarding successful: UDP=$UDP_PORT, TCP=$TCP_PORT" # Clear old PREROUTING rules to avoid duplicates ${pkgs.iptables}/bin/iptables -t nat -D PREROUTING -i enp0s5 -p tcp -j DNAT --to-destination 10.2.0.2 2>/dev/null || true ${pkgs.iptables}/bin/iptables -t nat -D PREROUTING -i enp0s5 -p udp -j DNAT --to-destination 10.2.0.2 2>/dev/null || true # Forward traffic from enp0s5 (VM's main interface) to wg0 (WireGuard interface) ${pkgs.iptables}/bin/iptables -t nat -A PREROUTING -i enp0s5 -p tcp --dport "$TCP_PORT" -j DNAT --to-destination 10.2.0.2:$TCP_PORT ${pkgs.iptables}/bin/iptables -t nat -A PREROUTING -i enp0s5 -p udp --dport "$UDP_PORT" -j DNAT --to-destination 10.2.0.2:$UDP_PORT # Enable masquerading for responses ${pkgs.iptables}/bin/iptables -t nat -C POSTROUTING -o wg0 -j MASQUERADE 2>/dev/null || \ ${pkgs.iptables}/bin/iptables -t nat -A POSTROUTING -o wg0 -j MASQUERADE # Allow forwarding between interfaces ${pkgs.iptables}/bin/iptables -C FORWARD -i enp0s5 -o wg0 -j ACCEPT 2>/dev/null || \ ${pkgs.iptables}/bin/iptables -A FORWARD -i enp0s5 -o wg0 -j ACCEPT ${pkgs.iptables}/bin/iptables -C FORWARD -i wg0 -o enp0s5 -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || \ ${pkgs.iptables}/bin/iptables -A FORWARD -i wg0 -o enp0s5 -m state --state RELATED,ESTABLISHED -j ACCEPT echo "Firewall forwarding rules updated for ports: UDP=$UDP_PORT, TCP=$TCP_PORT" # Open the ports in the firewall dynamically (INPUT chain) ${pkgs.iptables}/bin/iptables -C INPUT -p udp --dport "$UDP_PORT" -j ACCEPT 2>/dev/null || \ ${pkgs.iptables}/bin/iptables -A INPUT -p udp --dport "$UDP_PORT" -j ACCEPT ${pkgs.iptables}/bin/iptables -C INPUT -p tcp --dport "$TCP_PORT" -j ACCEPT 2>/dev/null || \ ${pkgs.iptables}/bin/iptables -A INPUT -p tcp --dport "$TCP_PORT" -j ACCEPT echo "Firewall INPUT rules updated for ports: UDP=$UDP_PORT, TCP=$TCP_PORT" # Update qBittorrent listening port via API echo "Logging into qBittorrent API..." COOKIE=$(${pkgs.curl}/bin/curl -s -i \ --header "Referer: http://localhost:${toString serviceCfg.ports.port0}" \ --data "username=user&password=$PASSWORD" \ "http://localhost:${toString serviceCfg.ports.port0}/api/v2/auth/login" | \ ${pkgs.gnugrep}/bin/grep -i "set-cookie" | ${pkgs.gawk}/bin/awk -F'SID=|;' '{print $2}') if [ -n "$COOKIE" ]; then echo "Authentication successful, updating port..." ${pkgs.curl}/bin/curl -s \ --cookie "SID=$COOKIE" \ --data "json={\"listen_port\":$UDP_PORT}" \ "http://localhost:${toString serviceCfg.ports.port0}/api/v2/app/setPreferences" echo "Updated qBittorrent listening port to $UDP_PORT" else echo "WARNING: Failed to authenticate with qBittorrent API" fi else echo "ERROR: Failed to get forwarded ports" echo "UDP output: $UDP_OUTPUT" echo "TCP output: $TCP_OUTPUT" exit 1 fi sleep 45 done ''; }; }; }; microvm = { vcpu = 4; mem = 1024 * 1; hypervisor = "qemu"; interfaces = [ { type = "tap"; id = serviceCfg.interface.id; mac = serviceCfg.interface.mac; } { type = "user"; id = serviceCfg.interface.idUser; mac = serviceCfg.interface.macUser; } ]; forwardPorts = [ { from = "host"; host.port = serviceCfg.interface.ssh; guest.port = 22; } ]; shares = [ { source = "/nix/store"; mountPoint = "/nix/.ro-store"; tag = "ro-store"; proto = "virtiofs"; } { mountPoint = serviceCfg.varPaths.path0; proto = "virtiofs"; source = serviceCfg.mntPaths.path0; tag = "${serviceCfg.name}_data"; } { mountPoint = "/run/secrets"; proto = "virtiofs"; source = "/run/secrets/torrent"; tag = "host_secrets"; } ]; }; environment.systemPackages = builtins.attrValues { inherit (pkgs) bottom gawk iptables libnatpmp speedtest-go wireguard-tools ; }; }; }; services = { caddy = { virtualHosts = { "${host}" = { extraConfig = '' basic_auth { {$CADDY_AUTH_USER} {$CADDY_AUTH_PASSWORD_HASH} } reverse_proxy ${serviceCfg.interface.ip}:${toString serviceCfg.ports.port0} tls ${serviceCfg.ssl.cert} ${serviceCfg.ssl.key} encode zstd gzip ''; }; }; }; }; sops.secrets = { "caddy/share-auth" = { owner = "caddy"; group = "caddy"; mode = "0400"; }; "torrent/wireguard-pass" = { owner = "root"; mode = "0400"; }; "torrent/qbittorrent-pass" = { owner = "root"; mode = "0400"; }; }; security.acme.certs."${host}" = { dnsProvider = dns0; environmentFile = config.sops.secrets.${dns0Path}.path; group = "caddy"; }; users.users.caddy.extraGroups = [ "acme" ]; systemd = { services.caddy = { serviceConfig = { EnvironmentFile = config.sops.secrets."caddy/share-auth".path; }; }; tmpfiles.rules = [ "d ${serviceCfg.mntPaths.path0} 0755 microvm wheel - -" "d ${serviceCfg.secretPaths.path0}/caddy 755 caddy caddy -" "d /var/log/caddy 755 caddy caddy -" ]; }; networking.firewall = { allowedTCPPorts = [ 38834 torrentPort ]; allowedUDPPorts = [ 38834 torrentPort ]; }; }