Nix configurations for my homelab

add modules for protonvpn and qbittorrent

yemou.pink b3d1e6be 5c0bfc42

verified
Changed files
+185 -3
lutea
modules
scripts
secrets
+2
lutea/config.nix
···
../modules/network-info.nix
../modules/nix.nix
../modules/printing.nix
+
../modules/qbittorrent.nix
../modules/remote-builder.nix
../modules/river.nix
../modules/smartd.nix
../modules/tools.nix
../modules/typst.nix
../modules/virtualbox.nix
+
../modules/vpn.nix
];
sops = {
+69
modules/qbittorrent.nix
···
+
{
+
config,
+
lib,
+
pkgs,
+
...
+
}:
+
{
+
environment.persistence."/data/persistent".directories = [
+
{
+
directory = "/var/lib/qBittorrent";
+
mode = "0700";
+
user = config.services.qbittorrent.user;
+
group = config.services.qbittorrent.group;
+
}
+
];
+
+
# TODO: Make sure that the qbittorrent service only starts if the torrent interface is up
+
+
systemd.services.protonvpn-qbittorrent-natpmp = {
+
description = "Get a port and provide it to qBittorrent";
+
requires = [
+
"network-online.target"
+
"qbittorrent.service"
+
];
+
wantedBy = [ "multi-user.target" ];
+
serviceConfig = {
+
ExecStart = "${
+
pkgs.writeShellApplication {
+
name = "protonvpn-natpmp";
+
runtimeInputs = with pkgs; [
+
curl
+
gnugrep
+
jq
+
libnatpmp
+
];
+
text = builtins.readFile ../scripts/protonvpn-natpmp.sh;
+
}
+
}/bin/protonvpn-natpmp";
+
Restart = "on-failure";
+
};
+
};
+
+
services.qbittorrent = {
+
enable = true;
+
webuiPort = 8082;
+
serverConfig = {
+
LegalNotice.Accepted = true;
+
BitTorrent.Session = {
+
Interface = "vpnt";
+
InterfaceName = "vpnt";
+
TorrentContentLayout = "Subfolder";
+
};
+
Network.PortForwardingEnabled = false;
+
Preferences = {
+
General.StatusbarExternalIPDisplayed = true;
+
WebUI = lib.mkMerge [
+
(lib.mkIf (config.networking.hostName == "lutea") { LocalHostAuth = false; })
+
(lib.mkIf (config.networking.hostName == "lily") {
+
AuthSubnetWhitelistEnable = true;
+
AuthSubnetWhitelist = [
+
config.garden.lutea.ipv4-local
+
config.garden.lutea.netbird-ip
+
];
+
})
+
];
+
};
+
};
+
};
+
}
+68
modules/vpn.nix
···
+
{ config, ... }:
+
{
+
sops.secrets = {
+
"protonvpn-torrent/private-key" = {
+
sopsFile = ../secrets/lilu.yaml;
+
owner = "systemd-network";
+
group = "systemd-network";
+
};
+
"protonvpn-torrent/public-key" = {
+
sopsFile = ../secrets/lilu.yaml;
+
owner = "systemd-network";
+
group = "systemd-network";
+
};
+
};
+
+
systemd.network = {
+
networks."50-vpn-torrent" = {
+
matchConfig.Name = "vpnt";
+
address = [
+
"2a07:b944::2:2/128"
+
"10.2.0.2/32"
+
];
+
dns = [
+
"2a07:b944::2:1"
+
"10.2.0.1"
+
];
+
routes = [
+
{ Destination = "2a07:b944::2:1"; }
+
{ Destination = "10.2.0.1"; }
+
{
+
Destination = "::/0";
+
Table = 10;
+
}
+
{
+
Destination = "0.0.0.0/0";
+
Table = 10;
+
}
+
];
+
routingPolicyRules = [
+
{
+
From = "2a07:b944::2:2";
+
Table = 10;
+
}
+
{
+
From = "10.2.0.2";
+
Table = 10;
+
}
+
];
+
};
+
netdevs."50-vpn-torrent" = {
+
netdevConfig = {
+
Kind = "wireguard";
+
Name = "vpnt";
+
};
+
wireguardConfig.PrivateKeyFile = config.sops.secrets."protonvpn-torrent/private-key".path;
+
wireguardPeers = [
+
{
+
PublicKeyFile = config.sops.secrets."protonvpn-torrent/public-key".path;
+
Endpoint = "31.13.189.226:51820";
+
AllowedIPs = [
+
"0.0.0.0/0"
+
"::/0"
+
];
+
}
+
];
+
};
+
};
+
}
+40
scripts/protonvpn-natpmp.sh
···
+
#!/bin/sh
+
+
QBITTORRENT_URL="http://localhost:8082"
+
+
while true
+
do
+
tcp_output=$(natpmpc -g 10.2.0.1 -a 0 1 tcp 60 | grep Mapped) || {
+
printf '%s\n' "Error: failed to get TCP port" >&2
+
exit 1
+
}
+
+
udp_output=$(natpmpc -g 10.2.0.1 -a 0 1 udp 60 | grep Mapped) || {
+
printf '%s\n' "Error: failed to get UDP port" >&2
+
exit 1
+
}
+
+
read -r _ _ _ tcp_port _ <<-EOF
+
$tcp_output
+
EOF
+
+
read -r _ _ _ udp_port _ <<-EOF
+
$udp_output
+
EOF
+
+
# NOTE: The port numbers should be the same i'm pretty sure but this is a little sanity check
+
[ "$tcp_port" -eq "$udp_port" ] || {
+
printf '%s\n' "Warning: TCP and UDP ports aren't the same" >&2
+
}
+
+
current_port=$(curl -sf ${QBITTORRENT_URL}/api/v2/app/preferences | jq .listen_port)
+
+
[ "$current_port" -eq "$udp_port" ] || {
+
printf '%s\n' "Port changed from $current_port -> $udp_port"
+
curl -sfd "json={\"listen_port\":$udp_port}" "${QBITTORRENT_URL}/api/v2/app/setPreferences"
+
}
+
+
printf '%s\n' "Current Port: $udp_port"
+
+
sleep 30
+
done
+6 -3
secrets/lilu.yaml
···
y6d-smtp:
user: ENC[AES256_GCM,data:IZK759k1/F6v,iv:Aj92dOU58OU1zCcCsKeaHzsvWePRo6s8sE5mMMwM4DM=,tag:1V12iaPqjroNBQfaJHlP5Q==,type:str]
pass: ENC[AES256_GCM,data:q6bhty/EUUYIV+VQ9ZLHNjODOqA=,iv:aJ2+ToXQGLmZtO06ZXBwa6OGt7qil/mSbBG4VI6muRU=,tag:zn4mzLC7+qh40lP07ZEzPQ==,type:str]
+
protonvpn-torrent:
+
public-key: ENC[AES256_GCM,data:sCLj4u46lr/ImHyFsgwXcw1UxlTfYYT3W6qKcs8NjISW8t9oNwAPQF71VaE=,iv:6edmr6kB0fIXSFlHaujZnE8Ug3M7n9rXIFQVBvYXwRs=,tag:Qovr/4CLdSL78p+E8G2fiw==,type:str]
+
private-key: ENC[AES256_GCM,data:cNapKRzpeSJ2c972e8tTRAPwNx0RyHCf39YIUDisAIhYSPN9/zLON5iv4EU=,iv:JlDgAt2nM5YS0xGadfPvRb6c4hs0gX5KgZmBYzhMlfI=,tag:66faJtuinyfZsbVKfcUeFA==,type:str]
sops:
age:
- recipient: age1amaa55e7nusv904a9ucfvtnjlw4srtet42suehey6u3yc4t2xc5sdldepj
···
cm43OGNYd1ZnbEM0NjVYY3ZOdi94Sk0Kn8jz57CaoCE3ceFv1TNsYdqW83sqxYiy
4X21omXCeqpRG5DC2QyAJQE/93lBhsHKIMCraNMaOycPlVQYdyTviA==
-----END AGE ENCRYPTED FILE-----
-
lastmodified: "2025-08-05T22:10:59Z"
-
mac: ENC[AES256_GCM,data:Bqymx8fJKDcpr8GfP3fK+PPNivhPCyBx6yiybje0MOwMP4Qrc06YeYyktvV0z0MvPqOf31FMXMYbwYjuWOgUPKCsFC9QGLcshAnD9qG7LxX+5PaonFCk0LAUW5NABluGSkTViM6wCywqoSUB9BC8xnw8kSrMO7yXzJghIr6rusw=,iv:BmOKCn+p14ZKSAsn+nDQYWlPZsJFJEjGMyKuz9d9IY4=,tag:CpVMaMywwLVQYKFMx5l6WA==,type:str]
+
lastmodified: "2025-10-09T11:59:47Z"
+
mac: ENC[AES256_GCM,data:utvW8XNWMuBrtKOfxLFFnXnTM8H/hYFO2pgKnNb+UZF6j36HnfcYb25hUiKLdQ791804LE3mmgR+evtcEl2YFV/8TDfwZTvlVKf6LDJtuYpcrlQYIIWrf6z6EjDwReZOW7my+PmgCNGTZ19mdtgXwSYQOGKX/PyUFEPxTwDCY8A=,iv:oGjRrn9f4aVRnlzIPfa1YthUrXXe7xRntcMjxsheOUI=,tag:p0Dira33uF1Tx2Rx+hiUZQ==,type:str]
unencrypted_suffix: _unencrypted
-
version: 3.10.2
+
version: 3.11.0