diff --git a/modules/services/proton-wireguard.nix b/modules/services/proton-wireguard.nix index 57d5664..1563ea0 100644 --- a/modules/services/proton-wireguard.nix +++ b/modules/services/proton-wireguard.nix @@ -10,36 +10,46 @@ inherit (lib) getExe getExe'; interface = "proton0"; - containerIPv4Subnet = "10.89.50.0/24"; gateway = "10.2.0.1"; + routingTable = 51820; + + containerIPv4Subnet = "10.89.50.0/24"; + containerIPv4Gateway = "10.89.50.1"; + + ip = getExe' pkgs.iproute2 "ip"; + secret = config.sops.placeholder; + privateIPv4Subnets = [ "10.0.0.0/8" "172.16.0.0/12" "192.168.0.0/16" ]; - routingTable = 51820; - secret = config.sops.placeholder; - ip = getExe' pkgs.iproute2 "ip"; + + routingTableString = toString routingTable; + + privateSubnetPostUpRules = builtins.concatStringsSep "\n" (map (subnet: '' + ${ip} -4 rule del from ${containerIPv4Subnet} to ${subnet} table main priority 900 2>/dev/null || true + ${ip} -4 rule add from ${containerIPv4Subnet} to ${subnet} table main priority 900 + '') + privateIPv4Subnets); + + privateSubnetPreDownRules = builtins.concatStringsSep "\n" (map (subnet: '' + ${ip} -4 rule del from ${containerIPv4Subnet} to ${subnet} table main priority 900 2>/dev/null || true + '') + privateIPv4Subnets); postUp = '' ${ip} -4 route replace ${gateway} dev ${interface} - ${ip} -4 route replace default dev ${interface} table ${toString routingTable} - ${builtins.concatStringsSep "\n" (map (subnet: '' - ${ip} -4 rule del from ${containerIPv4Subnet} to ${subnet} table main priority 900 2>/dev/null || true - ${ip} -4 rule add from ${containerIPv4Subnet} to ${subnet} table main priority 900 - '') - privateIPv4Subnets)} - ${ip} -4 rule del from ${containerIPv4Subnet} table ${toString routingTable} priority 1000 2>/dev/null || true - ${ip} -4 rule add from ${containerIPv4Subnet} table ${toString routingTable} priority 1000 + ${ip} -4 route replace default dev ${interface} table ${routingTableString} + ${privateSubnetPostUpRules} + ${ip} -4 rule del from ${containerIPv4Subnet} table ${routingTableString} priority 1000 2>/dev/null || true + ${ip} -4 rule add from ${containerIPv4Subnet} table ${routingTableString} priority 1000 ''; preDown = '' - ${ip} -4 rule del from ${containerIPv4Subnet} table ${toString routingTable} priority 1000 2>/dev/null || true - ${builtins.concatStringsSep "\n" (map (subnet: '' - ${ip} -4 rule del from ${containerIPv4Subnet} to ${subnet} table main priority 900 2>/dev/null || true - '') - privateIPv4Subnets)} - ${ip} -4 route del default dev ${interface} table ${toString routingTable} 2>/dev/null || true + ${ip} -4 rule del from ${containerIPv4Subnet} table ${routingTableString} priority 1000 2>/dev/null || true + ${privateSubnetPreDownRules} + ${ip} -4 route del default dev ${interface} table ${routingTableString} 2>/dev/null || true ${ip} -4 route del ${gateway} dev ${interface} 2>/dev/null || true ''; in { @@ -51,6 +61,8 @@ "wireguard/proton/dns" = {}; "wireguard/proton/endpoint" = {}; "wireguard/proton/private_key" = {}; + "wireguard/proton/proxy/password" = {}; + "wireguard/proton/proxy/user" = {}; "wireguard/proton/public_key" = {}; qbittorrent_proxy_path = {}; # keep-sorted end @@ -64,7 +76,7 @@ Address = ${secret."wireguard/proton/address"} DNS = ${secret."wireguard/proton/dns"} MTU = 1420 - Table = ${toString routingTable} + Table = ${routingTableString} [Peer] PublicKey = ${secret."wireguard/proton/public_key"} @@ -76,31 +88,57 @@ }; systemd.services."wg-quick-${interface}" = { - # keep-sorted start - after = ["sops-install-secrets.service"]; wants = ["sops-install-secrets.service"]; - # keep-sorted end + after = ["sops-install-secrets.service"]; }; systemd.services.proton-port-forward = { + description = "Maintain Proton VPN port forwarding"; + after = [ "nftables.service" "sops-install-secrets.service" "wg-quick-${interface}.service" ]; - description = "Maintain Proton VPN port forwarding"; + wantedBy = ["multi-user.target"]; + wants = [ + "sops-install-secrets.service" + "wg-quick-${interface}.service" + ]; + environment = { + # keep-sorted start CONTAINER_IPV4_SUBNET = containerIPv4Subnet; PRIVATE_IPV4_SUBNETS = builtins.concatStringsSep " " privateIPv4Subnets; PROTON_GATEWAY = gateway; QBITTORRENT_CONTAINER = "qbittorrent"; QBITTORRENT_NETWORK = "vpn"; - ROUTING_TABLE = toString routingTable; QUI_CONTAINER = "qui"; QUI_NETWORK = "torrent"; QUI_PORT = "7476"; + ROUTING_TABLE = routingTableString; WIREGUARD_INTERFACE = interface; + # keep-sorted end }; + + serviceConfig = { + ExecStart = getExe pkgs.proton-port-forward; + LoadCredential = [ + "qui_qbittorrent_proxy_path:${config.sops.secrets.qbittorrent_proxy_path.path}" + ]; + Restart = "always"; + RestartSec = "5s"; + }; + }; + + systemd.services.proton-indexer-proxy = { + description = "HTTP proxy for selected Prowlarr indexers over Proton VPN"; + + after = [ + "nftables.service" + "sops-install-secrets.service" + "wg-quick-${interface}.service" + ]; wantedBy = ["multi-user.target"]; wants = [ "sops-install-secrets.service" @@ -108,17 +146,50 @@ ]; serviceConfig = { - ExecStart = getExe pkgs.proton-port-forward; - LoadCredential = ["qui_qbittorrent_proxy_path:${config.sops.secrets.qbittorrent_proxy_path.path}"]; + DynamicUser = true; + ExecStart = pkgs.writeShellScript "proton-indexer-proxy" '' + set -eu + + config_file="$RUNTIME_DIRECTORY/tinyproxy.conf" + password_file="$CREDENTIALS_DIRECTORY/proxy_password" + user_file="$CREDENTIALS_DIRECTORY/proxy_user" + + cat > "$config_file" <> "$config_file" + fi + + exec ${getExe pkgs.tinyproxy} -d -c "$config_file" + ''; + LoadCredential = [ + "proxy_password:${config.sops.secrets."wireguard/proton/proxy/password".path}" + "proxy_user:${config.sops.secrets."wireguard/proton/proxy/user".path}" + ]; Restart = "always"; RestartSec = "5s"; + RuntimeDirectory = "proton-indexer-proxy"; }; }; networking = { - # keep-sorted start block=yes newline_separated=yes firewall.checkReversePath = "loose"; + firewall.extraInputRules = '' + iifname "podman*" tcp dport 8888 accept + ''; + nftables = { enable = true; ruleset = '' @@ -142,18 +213,10 @@ }; wg-quick.interfaces.${interface} = { - # keep-sorted start autostart = true; configFile = config.sops.templates."${interface}.conf".path; - inherit - # keep-sorted start - postUp - preDown - # keep-sorted end - ; - # keep-sorted end + inherit postUp preDown; }; - # keep-sorted end }; }; } diff --git a/secrets.yaml b/secrets.yaml index 4807799..55856d5 100644 --- a/secrets.yaml +++ b/secrets.yaml @@ -69,6 +69,9 @@ wireguard: public_key: ENC[AES256_GCM,data:NgGtMiobS32NvEHc9c1Ngk75H9HbEvPKkxMxwdSTIlyAL5HGEfSV+n02hdw=,iv:1Ci6jBjcfYZZ86qtrPtx4f4W1ynzTkeF7M7g7HyyRY8=,tag:0OghdJoTf0gx/0HjArTIGg==,type:str] allowed_ips: ENC[AES256_GCM,data:9kOkZno3oiOKoykVoDBB,iv:IpBDkg8HbB1pzpn0mpuXkIgrZz8Sf30BGvyIRSEaqW8=,tag:wIytxSUSNHUFrmVeGGJQtQ==,type:str] endpoint: ENC[AES256_GCM,data:dyF/WM2Xlz05HnJgkYSNKWhS6Q==,iv:Qv/Q3ef+eKPQ4+Gf9BwrCp7cj6mQiyCAnk1iMXHPbrs=,tag:0CjXKCxR0vY7wV5uFPEZqQ==,type:str] + proxy: + user: ENC[AES256_GCM,data:yofDX/0hqzg=,iv:yRuzKAMbj7bV6Lb0NuJs4Z9dOgfdy1DE6mdcpu76IEI=,tag:UPGWhdV8gMjVx3RXb26pKg==,type:str] + password: ENC[AES256_GCM,data:4W/zuCqb5IUOvA6n9Ufl8JMVEOomax5V+R1uvuPwxUejQ/5BSPGT6w==,iv:VbAPuaUZ2OLXGOFlt8A/E2M4cfyNKfNz1ORW9MatXdg=,tag:IQNpjhucvKTKb53viz932A==,type:str] euclid: private_key: ENC[AES256_GCM,data:dGh0qAiYgloX6EoV0Q72UP/faOpeoPqXUaxkN8lKq6KQbxycRqBHapPo+5s=,iv:jb/sliGbNK2r7yWoaL7u6RTUnbMTH6+wFJgq/LCxucE=,tag:mdfz5/aRkozRB8lRRPtd2Q==,type:str] gotify: @@ -114,8 +117,8 @@ sops: w2ToACYVigKMSlqPNG9bKcK75XI/iAONG48DQm1Nse3yB9/q3jwd+Q== -----END AGE ENCRYPTED FILE----- recipient: age1yl52c6aemw9anmfuqayamvsnkvlu6fmy0kk3gzvrraexxd9pr90qvs8pe5 - lastmodified: "2026-06-13T20:19:37Z" - mac: ENC[AES256_GCM,data:H+QA1jT/roe0D10P30dpgyYCD0XgJgBvYsvwieh6svxI1xEoFu0ckWxLMSmjiESCP2uFkmyeKNRV0exiiwTAG7NDLsqV5i3z/swMwvbGY38ZdIDnOE0Gs3M2TdcOOldmNmxCfhrwuLCGW69DNU7HFrTcqpAsL9lp3LeabpVQlCs=,iv:6vushk/VM+9EIijROmhz4nWZ/At6EGejEnbSJsNBcuk=,tag:GMlo349urMUJpowxpoNZfA==,type:str] + lastmodified: "2026-06-14T00:25:36Z" + mac: ENC[AES256_GCM,data:6gEJwTAedQTIZ/T7cA+xkpx8a/Hdo6DJj07+QtX0XdLuRB/TexK0IdGlgFRnnqrRTqZgszvPYhA5H5+O1PfqTIlcTTPAPSB3SEGhXhBOQMTbxmEtItNxuj+Gr0N0xW/IltSP4N0egk8LJpOT6fjca5cZzbuQlSsh2KmMKfXn+G4=,iv:7eAbvguXMmF8O5Xo4pfIcER30G7UQYIu6Nwb92hnnvc=,tag:0QOpw7qs1r1L46ZwSrewFQ==,type:str] pgp: - created_at: "2026-06-02T01:50:38Z" enc: |-