diff --git a/flake.lock b/flake.lock index ccf2649..59b63c9 100644 --- a/flake.lock +++ b/flake.lock @@ -90,13 +90,30 @@ "type": "github" } }, + "nixpkgs_2": { + "locked": { + "lastModified": 1709218635, + "narHash": "sha256-nytX/MkfqeTD4z7bMq4QRXcHxO9B3vRo9tM6fMtPFA8=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "068d4db604958d05d0b46c47f79b507d84dbc069", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-23.11", + "repo": "nixpkgs", + "type": "github" + } + }, "root": { "inputs": { "devshell": "devshell", "flake-parts": "flake-parts", "flake-root": "flake-root", "nixpkgs": "nixpkgs", - "treefmt-nix": "treefmt-nix" + "treefmt-nix": "treefmt-nix", + "vpnconfinement": "vpnconfinement" } }, "systems": { @@ -133,6 +150,24 @@ "repo": "treefmt-nix", "type": "github" } + }, + "vpnconfinement": { + "inputs": { + "nixpkgs": "nixpkgs_2" + }, + "locked": { + "lastModified": 1709159289, + "narHash": "sha256-66eFi/SgygAMOLLLkH5oqwBiI4iE5Bj/kBJmmMhX8fg=", + "owner": "Maroka-chan", + "repo": "VPN-Confinement", + "rev": "93804a1050d3699418f0f9472e9c5eca1aa8153d", + "type": "github" + }, + "original": { + "owner": "Maroka-chan", + "repo": "VPN-Confinement", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index a731e83..6cb5484 100644 --- a/flake.nix +++ b/flake.nix @@ -8,6 +8,10 @@ inputs = { nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; + vpnconfinement = { + url = "github:Maroka-chan/VPN-Confinement"; + inputs.nixpkgs.follows = "nixpkgs"; + }; flake-parts = { url = "github:hercules-ci/flake-parts"; @@ -25,15 +29,15 @@ }; }; - outputs = inputs @ {flake-parts, ...}: + outputs = inputs @ {flake-parts, vpnconfinement, nixpkgs, ...}: flake-parts.lib.mkFlake { inherit inputs; - } - rec { + } { imports = with inputs; [ flake-root.flakeModule treefmt-nix.flakeModule devshell.flakeModule + #vpnconfinement.nixosModules.default ]; systems = [ "x86_64-linux" @@ -41,7 +45,10 @@ flake = { nixosModules = rec { - nixarr = import ./nixarr; + #vpnconfinement = vpnconfinement.nixosModules.default; + nixarr = (import ./nixarr vpnconfinement); + #imports = [ vpnconfinement.nixosModules.default ]; + #nixarr.imports = [ vpnconfinement ]; default = nixarr; }; }; diff --git a/nixarr/ddns/default.nix b/nixarr/ddns/default.nix new file mode 100644 index 0000000..3a3c18d --- /dev/null +++ b/nixarr/ddns/default.nix @@ -0,0 +1,117 @@ +{ + pkgs, + config, + lib, + ... +}: +with lib; let + cfg = config.nixarr.ddns; +in { + options.nixarr.ddns = { + njalla = { + enable = mkOption { + type = types.bool; + default = false; + description = '' + **Required options:** + + - [`nixarr.ddns.njalla.keysFile`](#nixarr.ddns.njalla.keysfile) + + Whether or not to enable DDNS for a [Njalla](https://njal.la/) + domain. + ''; + }; + + keysFile = mkOption { + type = with types; nullOr path; + default = null; + description = '' + A path to a JSON-file containing key value pairs of domains and keys. + + To get the keys, create a dynamic njalla record. Upon creation + you should see something like the following command suggested: + + ```sh + curl "https://njal.la/update/?h=jellyfin.example.com&k=zeubesojOLgC2eJC&auto" + ``` + + Then the JSON-file you pass here should contain: + + ```json + { + "jellyfin.example.com": "zeubesojOLgC2eJC" + } + ``` + + You can, of course, add more key-value pairs than just one. + ''; + }; + }; + }; + + config = mkIf cfg.njalla.enable { + assertions = [ + { + assertion = cfg.njalla.enable -> cfg.njalla.keysFile != null; + message = '' + The nixarr.ddns.njalla.enable option requires the + nixarr.ddns.njalla.keysFile option to be set, but it was not. + ''; + } + ]; + + systemd.timers = mkIf cfg.njalla.enable { + ddnsNjalla = { + description = "Timer for setting the Njalla DDNS records"; + + timerConfig = { + OnBootSec = "30"; # Run 30 seconds after system boot + OnCalendar = "hourly"; + Persistent = true; # Run service immediately if last window was missed + RandomizedDelaySec = "5min"; # Run service OnCalendar +- 5min + }; + + wantedBy = ["multi-user.target"]; + }; + }; + + systemd.services = let + ddns-njalla = pkgs.writeShellApplication { + name = "ddns-njalla"; + + runtimeInputs = with pkgs; [ curl jq ]; + + # Thanks chatgpt... + text = '' + # Path to the JSON file + json_file="${cfg.njalla.keysFile}" + + # Convert the JSON object into a series of tab-separated key-value pairs using jq + # - `to_entries[]`: Convert the object into an array of key-value pairs. + # - `[.key, .value]`: For each pair, create an array containing the key and the value. + # - `@tsv`: Convert the array to a tab-separated string. + # The output will be a series of lines, each containing a key and a value separated by a tab. + jq_command='to_entries[] | [.key, .value] | @tsv' + + # Read the converted output line by line + # - `IFS=$'\t'`: Use the tab character as the field separator. + # - `read -r key val`: For each line, split it into `key` and `val` based on the tab separator. + while IFS=$'\t' read -r key val; do + # For each key-value pair, execute the curl command + # Replace `''${key}` and `''${val}` in the URL with the actual key and value. + curl -s "https://njal.la/update/?h=''${key}&k=''${val}&auto" + done < <(jq -r "$jq_command" "$json_file") + ''; + }; + in mkIf cfg.njalla.enable { + ddnsNjalla = { + description = "Sets the Njalla DDNS records"; + + serviceConfig = { + ExecStart = getExe ddns-njalla; + Type = "oneshot"; + }; + }; + }; + }; +} diff --git a/nixarr/default.nix b/nixarr/default.nix index f957c22..5cc263e 100644 --- a/nixarr/default.nix +++ b/nixarr/default.nix @@ -1,4 +1,4 @@ -{ +vpnconfinement: { config, lib, pkgs, @@ -8,7 +8,9 @@ with lib; let cfg = config.nixarr; in { imports = [ + vpnconfinement.nixosModules.default ./jellyfin + ./ddns ./radarr ./lidarr ./readarr @@ -70,46 +72,6 @@ in { ''; }; - ddns.njalla = { - enable = mkOption { - type = types.bool; - default = false; - description = '' - **Required options:** - - - [`nixarr.ddns.njalla.keysFile`](#nixarr.ddns.njalla.keysfile) - - Whether or not to enable DDNS for a [Njalla](https://njal.la/) - domain. - ''; - }; - - keysFile = mkOption { - type = with types; nullOr path; - default = null; - description = '' - A path to a JSON-file containing key value pairs of domains and keys. - - To get the keys, create a dynamic njalla record. Upon creation - you should see something like the following command suggested: - - ```sh - curl "https://njal.la/update/?h=jellyfin.example.com&k=zeubesojOLgC2eJC&auto" - ``` - - Then the JSON-file you pass here should contain: - - ```json - { - "jellyfin.example.com": "zeubesojOLgC2eJC" - } - ``` - - You can, of course, add more key-value pairs than just one. - ''; - }; - }; - vpn = { enable = mkOption { type = types.bool; @@ -128,16 +90,6 @@ in { description = "The path to the wireguard configuration file."; }; - dnsServers = mkOption { - type = with types; nullOr (listOf str); - default = null; - description = '' - Extra DNS servers for the VPN. If your wg config has a DNS field, - then this should not be necessary. - ''; - example = ["1.1.1.2"]; - }; - vpnTestService = { enable = mkEnableOption '' the vpn test service. Useful for testing DNS leaks or if the VPN @@ -145,10 +97,11 @@ in { ''; port = mkOption { - type = types.port; - default = 12300; + type = with types; nullOr port; + default = null; description = '' - The port that the vpn test service listens to. + The port that netcat listens to on the vpn test service. If set to + `null`, then netcat will not be started. ''; example = 58403; }; @@ -157,7 +110,7 @@ in { openTcpPorts = mkOption { type = with types; listOf port; default = []; - description = lib.mdDoc '' + description = '' What TCP ports to allow traffic from. You might need this if you're port forwarding on your VPN provider and you're setting up services not covered in by this module that uses the VPN. @@ -168,7 +121,7 @@ in { openUdpPorts = mkOption { type = with types; listOf port; default = []; - description = lib.mdDoc '' + description = '' What UDP ports to allow traffic from. You might need this if you're port forwarding on your VPN provider and you're setting up services not covered in by this module that uses the VPN. @@ -187,149 +140,101 @@ in { to be set, but it was not. ''; } - { - assertion = cfg.ddns.njalla.enable -> cfg.ddns.njalla.keysFile != null; - message = '' - The nixarr.ddns.njalla.enable option requires the - nixarr.ddns.njalla.keysFile option to be set, but it was not. - ''; - } ]; + # TODO: move this to modules, at least the "*Arrs"... users.groups = { - media.gid = 992; - prowlarr = {}; + media = {}; streamer = {}; torrenter = {}; }; - # TODO: This is BAD. But seems necessary when using containers. - # The prefered solution is to just remove containerization. - # Look at https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/misc/ids.nix - # See also issue: https://github.com/rasmus-kirk/nixarr/issues/1 users.users = { streamer = { isSystemUser = true; group = "streamer"; - uid = lib.mkForce 316; - }; - sonarr = { - isSystemUser = true; - group = "media"; - uid = lib.mkForce 274; - }; - radarr = { - isSystemUser = true; - group = "media"; - uid = lib.mkForce 275; - }; - lidarr = { - isSystemUser = true; - group = "media"; - uid = lib.mkForce 306; - }; - readarr = { - isSystemUser = true; - group = "media"; - uid = lib.mkForce 309; }; torrenter = { isSystemUser = true; group = "torrenter"; - uid = lib.mkForce 70; - }; - prowlarr = { - isSystemUser = true; - group = "prowlarr"; - uid = lib.mkForce 293; }; }; systemd.tmpfiles.rules = [ # Media dirs - "d '${cfg.mediaDir}' 0775 root media - -" - "d '${cfg.mediaDir}/library' 0775 streamer media - -" - "d '${cfg.mediaDir}/library/shows' 0775 streamer media - -" - "d '${cfg.mediaDir}/library/movies' 0775 streamer media - -" - "d '${cfg.mediaDir}/library/music' 0775 streamer media - -" - "d '${cfg.mediaDir}/library/books' 0775 streamer media - -" - "d '${cfg.mediaDir}/torrents' 0755 torrenter media - -" - "d '${cfg.mediaDir}/torrents/.incomplete' 0755 torrenter media - -" - "d '${cfg.mediaDir}/torrents/.watch' 0755 torrenter media - -" - "d '${cfg.mediaDir}/torrents/manual' 0755 torrenter media - -" - "d '${cfg.mediaDir}/torrents/liadarr' 0755 torrenter media - -" - "d '${cfg.mediaDir}/torrents/radarr' 0755 torrenter media - -" - "d '${cfg.mediaDir}/torrents/sonarr' 0755 torrenter media - -" - "d '${cfg.mediaDir}/torrents/readarr' 0755 torrenter media - -" + "d '${cfg.mediaDir}' 0775 root media - -" + "d '${cfg.mediaDir}/library' 0775 streamer media - -" + "d '${cfg.mediaDir}/library/shows' 0775 streamer media - -" + "d '${cfg.mediaDir}/library/movies' 0775 streamer media - -" + "d '${cfg.mediaDir}/library/music' 0775 streamer media - -" + "d '${cfg.mediaDir}/library/books' 0775 streamer media - -" + "d '${cfg.mediaDir}/torrents' 0755 torrenter media - -" + "d '${cfg.mediaDir}/torrents/.incomplete' 0755 torrenter media - -" + "d '${cfg.mediaDir}/torrents/.watch' 0755 torrenter media - -" + "d '${cfg.mediaDir}/torrents/manual' 0755 torrenter media - -" + "d '${cfg.mediaDir}/torrents/liadarr' 0755 torrenter media - -" + "d '${cfg.mediaDir}/torrents/radarr' 0755 torrenter media - -" + "d '${cfg.mediaDir}/torrents/sonarr' 0755 torrenter media - -" + "d '${cfg.mediaDir}/torrents/readarr' 0755 torrenter media - -" ]; - util-nixarr.vpnnamespace = { - enable = cfg.vpn.enable; + # TODO: wtf to do about openports + vpnnamespaces.wg = { + enable = cfg.vpn.enable ; accessibleFrom = [ "192.168.1.0/24" "127.0.0.1" ]; - dnsServers = cfg.vpn.dnsServers; - wireguardAddressPath = cfg.vpn.wgAddress; - wireguardConfigFile = if cfg.vpn.wgConf != null then cfg.vpn.wgConf else ""; - vpnTestService = { - enable = cfg.vpn.vpnTestService.enable; - port = cfg.vpn.vpnTestService.port; - }; - openTcpPorts = cfg.vpn.openTcpPorts; - openUdpPorts = cfg.vpn.openUdpPorts; + wireguardConfigFile = cfg.vpn.wgConf; }; - systemd.timers = mkIf cfg.ddns.njalla.enable { - ddnsNjalla = { - description = "Timer for setting the Njalla DDNS records"; - - timerConfig = { - OnBootSec = "30"; # Run 30 seconds after system boot - OnCalendar = "hourly"; - Persistent = true; # Run service immediately if last window was missed - RandomizedDelaySec = "5min"; # Run service OnCalendar +- 5min - }; - - wantedBy = ["multi-user.target"]; + # TODO: openports + systemd.services.vpn-test-service = { + enable = cfg.vpn.vpnTestService.enable; + vpnconfinement = { + enable = true; + vpnnamespace = "wg"; }; - }; - systemd.services = let - ddns-njalla = pkgs.writeShellApplication { - name = "ddns-njalla"; + script = let + vpn-test = pkgs.writeShellApplication { + name = "vpn-test"; - runtimeInputs = with pkgs; [ curl jq ]; + runtimeInputs = with pkgs; [util-linux unixtools.ping coreutils curl bash libressl netcat-gnu openresolv dig]; - # Thanks chatgpt... - text = '' - # Path to the JSON file - json_file="${cfg.ddns.njalla.keysFile}" + text = '' + cd "$(mktemp -d)" - # Convert the JSON object into a series of tab-separated key-value pairs using jq - # - `to_entries[]`: Convert the object into an array of key-value pairs. - # - `[.key, .value]`: For each pair, create an array containing the key and the value. - # - `@tsv`: Convert the array to a tab-separated string. - # The output will be a series of lines, each containing a key and a value separated by a tab. - jq_command='to_entries[] | [.key, .value] | @tsv' + # Print resolv.conf + echo "/etc/resolv.conf contains:" + cat /etc/resolv.conf - # Read the converted output line by line - # - `IFS=$'\t'`: Use the tab character as the field separator. - # - `read -r key val`: For each line, split it into `key` and `val` based on the tab separator. - while IFS=$'\t' read -r key val; do - # For each key-value pair, execute the curl command - # Replace `''${key}` and `''${val}` in the URL with the actual key and value. - curl -s "https://njal.la/update/?h=''${key}&k=''${val}&auto" - done < <(jq -r "$jq_command" "$json_file") - ''; - }; - in mkIf cfg.ddns.njalla.enable { - ddnsNjalla = { - description = "Sets the Njalla DDNS records"; + # Query resolvconf + echo "resolvconf output:" + resolvconf -l + echo "" - serviceConfig = { - ExecStart = getExe ddns-njalla; - Type = "oneshot"; + # Get ip + echo "Getting IP:" + curl -s ipinfo.io + + echo -ne "DNS leak test:" + curl -s https://raw.githubusercontent.com/macvk/dnsleaktest/b03ab54d574adbe322ca48cbcb0523be720ad38d/dnsleaktest.sh -o dnsleaktest.sh + chmod +x dnsleaktest.sh + ./dnsleaktest.sh + '' + (if cfg.vpn.vpnTestService.port != null then '' + echo "starting netcat on port ${builtins.toString cfg.vpn.vpnTestService.port}:" + nc -vnlp ${builtins.toString cfg.vpn.vpnTestService.port} + '' else ""); }; + in "${vpn-test}/bin/vpn-test"; + + bindsTo = ["netns@wg.service"]; + requires = ["network-online.target"]; + after = ["wg.service"]; + serviceConfig = { + #User = "torrenter"; + NetworkNamespacePath = "/var/run/netns/wg"; + BindReadOnlyPaths = ["/etc/netns/wg/resolv.conf:/etc/resolv.conf:norbind" "/data/test.file:/etc/test.file:norbind"]; }; }; }; diff --git a/nixarr/jellyfin/default.nix b/nixarr/jellyfin/default.nix index 7ee40b4..69834b8 100644 --- a/nixarr/jellyfin/default.nix +++ b/nixarr/jellyfin/default.nix @@ -7,7 +7,6 @@ let cfg = config.nixarr.jellyfin; defaultPort = 8096; nixarr = config.nixarr; - dnsServers = config.lib.vpn.dnsServers; in with lib; { options.nixarr.jellyfin = { enable = mkEnableOption "the Jellyfin service."; @@ -228,57 +227,16 @@ in with lib; { defaults.email = cfg.expose.https.acmeMail; }; - util-nixarr.vpnnamespace.portMappings = [ - ( - mkIf cfg.vpn.enable { - From = defaultPort; - To = defaultPort; - } - ) - ]; - - systemd.services."container@jellyfin" = mkIf cfg.vpn.enable { - requires = ["wg.service"]; + # Enable and specify VPN namespace to confine service in. + systemd.services.jellyfin.vpnconfinement = mkIf cfg.vpn.enable { + enable = true; + vpnnamespace = "wg"; }; - containers.jellyfin = mkIf cfg.vpn.enable { - autoStart = true; - ephemeral = true; - extraFlags = ["--network-namespace-path=/var/run/netns/wg"]; - - bindMounts = { - "${nixarr.mediaDir}/library".isReadOnly = false; - "${cfg.stateDir}".isReadOnly = false; - }; - - config = { - users.groups.streamer = { - gid = config.users.groups.streamer.gid; - }; - users.users.streamer = { - uid = lib.mkForce config.users.users.streamer.uid; - isSystemUser = true; - group = "streamer"; - }; - - # Use systemd-resolved inside the container - # Workaround for bug https://github.com/NixOS/nixpkgs/issues/162686 - networking.useHostResolvConf = lib.mkForce false; - services.resolved.enable = true; - networking.nameservers = dnsServers; - - services.jellyfin = { - enable = true; - user = "streamer"; - group = "streamer"; - logDir = "${cfg.stateDir}/log"; - cacheDir = "${cfg.stateDir}/cache"; - dataDir = "${cfg.stateDir}/data"; - configDir = "${cfg.stateDir}/config"; - }; - - system.stateVersion = "23.11"; - }; + # Port mappings + # TODO: openports if expose.vpn + vpnnamespaces.wg = mkIf cfg.vpn.enable { + portMappings = [{ From = defaultPort; To = defaultPort; }]; }; }; } diff --git a/nixarr/lidarr/default.nix b/nixarr/lidarr/default.nix index 41e5a6e..94d4b6e 100644 --- a/nixarr/lidarr/default.nix +++ b/nixarr/lidarr/default.nix @@ -5,7 +5,6 @@ }: with lib; let cfg = config.nixarr.lidarr; - dnsServers = config.lib.vpn.dnsServers; nixarr = config.nixarr; in { options.nixarr.lidarr = { @@ -50,53 +49,16 @@ in { dataDir = cfg.stateDir; }; - util-nixarr.vpnnamespace.portMappings = [ - ( - mkIf cfg.vpn.enable { - From = defaultPort; - To = defaultPort; - } - ) - ]; - - systemd.services."container@lidarr" = mkIf cfg.vpn.enable { - requires = ["wg.service"]; + # Enable and specify VPN namespace to confine service in. + systemd.services.lidarr.vpnconfinement = mkIf cfg.vpn.enable { + enable = true; + vpnnamespace = "wg"; }; - containers.lidarr = mkIf cfg.vpn.enable { - autoStart = true; - ephemeral = true; - extraFlags = ["--network-namespace-path=/var/run/netns/wg"]; - - bindMounts = { - "${nixarr.mediaDir}".isReadOnly = false; - "${cfg.stateDir}".isReadOnly = false; - }; - - config = { - users.groups.media = { - gid = config.users.groups.media.gid; - }; - users.users.lidarr = { - uid = lib.mkForce config.users.users.lidarr.uid; - isSystemUser = true; - group = "media"; - }; - - # Use systemd-resolved inside the container - # Workaround for bug https://github.com/NixOS/nixpkgs/issues/162686 - networking.useHostResolvConf = lib.mkForce false; - services.resolved.enable = true; - networking.nameservers = dnsServers; - - services.lidarr = { - enable = true; - group = "media"; - dataDir = "${cfg.stateDir}"; - }; - - system.stateVersion = "23.11"; - }; + # Port mappings + # TODO: openports + vpnnamespaces.wg = mkIf cfg.vpn.enable { + portMappings = [{ From = defaultPort; To = defaultPort; }]; }; services.nginx = mkIf cfg.vpn.enable { diff --git a/nixarr/openssh/default.nix b/nixarr/openssh/default.nix index d563747..3bb7415 100644 --- a/nixarr/openssh/default.nix +++ b/nixarr/openssh/default.nix @@ -75,19 +75,16 @@ in { '' ] else []; - util-nixarr.vpnnamespace = { - portMappings = builtins.map (x: { From = x; To = x; }) config.services.openssh.ports; - openUdpPorts = config.services.openssh.ports; - openTcpPorts = config.services.openssh.ports; + # Enable and specify VPN namespace to confine service in. + systemd.services.openssh.vpnconfinement = { + enable = true; + vpnnamespace = "wg"; }; - systemd.services.openssh = { - bindsTo = [ "netns@wg.service" ]; - requires = [ "network-online.target" ]; - after = [ "wg.service" ]; - serviceConfig = { - NetworkNamespacePath = "/var/run/netns/wg"; - }; + # Port mappings + # TODO: openports + vpnnamespaces.wg = { + portMappings = [{ From = defaultPort; To = defaultPort; }]; }; }; } diff --git a/nixarr/prowlarr/default.nix b/nixarr/prowlarr/default.nix index de90471..7d9bd70 100644 --- a/nixarr/prowlarr/default.nix +++ b/nixarr/prowlarr/default.nix @@ -6,7 +6,6 @@ }: with lib; let defaultPort = 9696; - dnsServers = config.lib.vpn.dnsServers; nixarr = config.nixarr; cfg = config.nixarr.prowlarr; in { @@ -49,51 +48,22 @@ in { "d '${cfg.stateDir}' 0700 prowlarr root - -" ]; - util-nixarr.services.prowlarr = mkIf (!cfg.vpn.enable) { + users.groups.prowlarr = {}; + + util-nixarr.services.prowlarr = { enable = true; dataDir = cfg.stateDir; }; - util-nixarr.vpnnamespace.portMappings = [ - ( - mkIf cfg.vpn.enable { - From = defaultPort; - To = defaultPort; - } - ) - ]; - - systemd.services."container@prowlarr" = mkIf cfg.vpn.enable { - requires = ["wg.service"]; + # Enable and specify VPN namespace to confine service in. + systemd.services.prowlarr.vpnconfinement = mkIf cfg.vpn.enable { + enable = true; + vpnnamespace = "wg"; }; - containers.prowlarr = mkIf cfg.vpn.enable { - autoStart = true; - ephemeral = true; - extraFlags = ["--network-namespace-path=/var/run/netns/wg"]; - bindMounts."${cfg.stateDir}".isReadOnly = false; - - config = { - users.groups.prowlarr = {}; - users.users.prowlarr = { - uid = lib.mkForce config.users.users.prowlarr.uid; - isSystemUser = true; - group = "prowlarr"; - }; - - # Use systemd-resolved inside the container - # Workaround for bug https://github.com/NixOS/nixpkgs/issues/162686 - networking.useHostResolvConf = lib.mkForce false; - services.resolved.enable = true; - networking.nameservers = dnsServers; - - util-nixarr.services.prowlarr = { - enable = true; - dataDir = cfg.stateDir; - }; - - system.stateVersion = "23.11"; - }; + # Port mappings + vpnnamespaces.wg = mkIf cfg.vpn.enable { + portMappings = [{ From = defaultPort; To = defaultPort; }]; }; services.nginx = mkIf cfg.vpn.enable { diff --git a/nixarr/radarr/default.nix b/nixarr/radarr/default.nix index 85aad5f..a20176a 100644 --- a/nixarr/radarr/default.nix +++ b/nixarr/radarr/default.nix @@ -8,7 +8,6 @@ with lib; let cfg = config.nixarr.radarr; defaultPort = 7878; nixarr = config.nixarr; - dnsServers = config.lib.vpn.dnsServers; in { options.nixarr.radarr = { enable = mkEnableOption "Enable the Radarr service."; @@ -45,60 +44,22 @@ in { "d '${cfg.stateDir}' 0700 radarr root - -" ]; - services.radarr = mkIf (!cfg.vpn.enable) { + services.radarr = { enable = cfg.enable; user = "radarr"; group = "media"; dataDir = cfg.stateDir; }; - util-nixarr.vpnnamespace.portMappings = [ - ( - mkIf cfg.vpn.enable { - From = defaultPort; - To = defaultPort; - } - ) - ]; - - systemd.services."container@radarr" = mkIf cfg.vpn.enable { - requires = ["wg.service"]; + # Enable and specify VPN namespace to confine service in. + systemd.services.radarr.vpnconfinement = mkIf cfg.vpn.enable { + enable = true; + vpnnamespace = "wg"; }; - containers.radarr = mkIf cfg.vpn.enable { - autoStart = true; - ephemeral = true; - extraFlags = ["--network-namespace-path=/var/run/netns/wg"]; - - bindMounts = { - "${nixarr.mediaDir}".isReadOnly = false; - "${cfg.stateDir}".isReadOnly = false; - }; - - config = { - users.groups.media = { - gid = config.users.groups.media.gid; - }; - users.users.radarr = { - uid = lib.mkForce config.users.users.radarr.uid; - isSystemUser = true; - group = "media"; - }; - - # Use systemd-resolved inside the container - # Workaround for bug https://github.com/NixOS/nixpkgs/issues/162686 - networking.useHostResolvConf = lib.mkForce false; - services.resolved.enable = true; - networking.nameservers = dnsServers; - - services.radarr = { - enable = true; - group = "media"; - dataDir = cfg.stateDir; - }; - - system.stateVersion = "23.11"; - }; + # Port mappings + vpnnamespaces.wg = mkIf cfg.vpn.enable { + portMappings = [{ From = defaultPort; To = defaultPort; }]; }; services.nginx = mkIf cfg.vpn.enable { diff --git a/nixarr/readarr/default.nix b/nixarr/readarr/default.nix index 5691a95..a79d4b6 100644 --- a/nixarr/readarr/default.nix +++ b/nixarr/readarr/default.nix @@ -6,7 +6,6 @@ with lib; let cfg = config.nixarr.readarr; nixarr = config.nixarr; - dnsServers = config.lib.vpn.dnsServers; in { options.nixarr.readarr = { enable = mkEnableOption "Enable the Readarr service"; @@ -50,53 +49,15 @@ in { dataDir = cfg.stateDir; }; - util-nixarr.vpnnamespace.portMappings = [ - ( - mkIf cfg.vpn.enable { - From = defaultPort; - To = defaultPort; - } - ) - ]; - - systemd.services."container@readarr" = mkIf cfg.vpn.enable { - requires = ["wg.service"]; + # Enable and specify VPN namespace to confine service in. + systemd.services.readarr.vpnconfinement = mkIf cfg.vpn.enable { + enable = true; + vpnnamespace = "wg"; }; - containers.readarr = mkIf cfg.vpn.enable { - autoStart = true; - ephemeral = true; - extraFlags = ["--network-namespace-path=/var/run/netns/wg"]; - - bindMounts = { - "${nixarr.mediaDir}".isReadOnly = false; - "${cfg.stateDir}".isReadOnly = false; - }; - - config = { - users.groups.media = { - gid = config.users.groups.media.gid; - }; - users.users.readarr = { - uid = lib.mkForce config.users.users.readarr.uid; - isSystemUser = true; - group = "media"; - }; - - # Use systemd-resolved inside the container - # Workaround for bug https://github.com/NixOS/nixpkgs/issues/162686 - networking.useHostResolvConf = lib.mkForce false; - services.resolved.enable = true; - networking.nameservers = dnsServers; - - services.readarr = { - enable = true; - group = "media"; - dataDir = "${cfg.stateDir}"; - }; - - system.stateVersion = "23.11"; - }; + # Port mappings + vpnnamespaces.wg = mkIf cfg.vpn.enable { + portMappings = [{ From = defaultPort; To = defaultPort; }]; }; services.nginx = mkIf cfg.vpn.enable { diff --git a/nixarr/sonarr/default.nix b/nixarr/sonarr/default.nix index 5c285a4..e74c861 100644 --- a/nixarr/sonarr/default.nix +++ b/nixarr/sonarr/default.nix @@ -8,7 +8,6 @@ with lib; let cfg = config.nixarr.sonarr; defaultPort = 8989; nixarr = config.nixarr; - dnsServers = config.lib.vpn.dnsServers; in { options.nixarr.sonarr = { enable = mkOption { @@ -49,60 +48,22 @@ in { "d '${cfg.stateDir}' 0700 sonarr root - -" ]; - services.sonarr = mkIf (!cfg.vpn.enable) { + services.sonarr = { enable = cfg.enable; user = "sonarr"; group = "media"; dataDir = cfg.stateDir; }; - util-nixarr.vpnnamespace.portMappings = [ - (mkIf cfg.vpn.enable { - From = defaultPort; - To = defaultPort; - }) - ]; - - systemd.services."container@sonarr" = mkIf cfg.vpn.enable { - requires = ["wg.service"]; + # Enable and specify VPN namespace to confine service in. + systemd.services.sonarr.vpnconfinement = mkIf cfg.vpn.enable { + enable = true; + vpnnamespace = "wg"; }; - containers.sonarr = mkIf cfg.vpn.enable { - autoStart = true; - ephemeral = true; - extraFlags = ["--network-namespace-path=/var/run/netns/wg"]; - - bindMounts = { - "${nixarr.mediaDir}".isReadOnly = false; - "${cfg.stateDir}".isReadOnly = false; - }; - - config = { - users.groups.media = { - gid = config.users.groups.media.gid; - }; - users.users.sonarr = { - uid = lib.mkForce config.users.users.sonarr.uid; - isSystemUser = true; - group = "media"; - }; - - # Use systemd-resolved inside the container - # Workaround for bug https://github.com/NixOS/nixpkgs/issues/162686 - networking.useHostResolvConf = lib.mkForce false; - services.resolved.enable = true; - networking.nameservers = dnsServers; - - users.groups.media = {}; - - services.sonarr = { - enable = cfg.enable; - group = "media"; - dataDir = cfg.stateDir; - }; - - system.stateVersion = "23.11"; - }; + # Port mappings + vpnnamespaces.wg = mkIf cfg.vpn.enable { + portMappings = [{ From = defaultPort; To = defaultPort; }]; }; services.nginx = mkIf cfg.vpn.enable { diff --git a/nixarr/transmission/cross-seed/default.nix b/nixarr/transmission/cross-seed/default.nix index 6ed2f10..b5278e0 100644 --- a/nixarr/transmission/cross-seed/default.nix +++ b/nixarr/transmission/cross-seed/default.nix @@ -80,6 +80,7 @@ in { users.users = mkIf (cfg.user == "cross-seed") { cross-seed = { + isSystemUser = true; group = cfg.group; }; }; diff --git a/nixarr/transmission/default.nix b/nixarr/transmission/default.nix index 5b63069..367bba9 100644 --- a/nixarr/transmission/default.nix +++ b/nixarr/transmission/default.nix @@ -7,7 +7,6 @@ with lib; let cfg = config.nixarr.transmission; nixarr = config.nixarr; - dnsServers = config.lib.vpn.dnsServers; cfg-cross-seed = config.nixarr.transmission.privateTrackers.cross-seed; transmissionCrossSeedScript = with builtins; pkgs.writeShellApplication { name = "mk-cross-seed-credentials"; @@ -198,7 +197,7 @@ in { ]; systemd.tmpfiles.rules = [ - "d '${cfg.stateDir}' 0700 torrenter root - -" + "d '${cfg.stateDir}' 0700 torrenter root - -" # This is fixes a bug in nixpks (https://github.com/NixOS/nixpkgs/issues/291883) "d '${cfg.stateDir}/.config/transmission-daemon' 0700 torrenter root - -" ] ++ ( @@ -230,7 +229,7 @@ in { )]; }; - services.transmission = mkIf (!cfg.vpn.enable) { + services.transmission = { enable = true; user = "torrenter"; group = "torrenter"; @@ -250,10 +249,11 @@ in { watch-dir-enabled = true; watch-dir = "${nixarr.mediaDir}/torrents/.watch"; - rpc-bind-address = "127.0.0.1"; + rpc-bind-address = if cfg.vpn.enable then "192.168.15.1" else "127.0.0.1"; rpc-port = cfg.uiPort; - rpc-whitelist-enabled = true; - rpc-whitelist = "192.168.15.1,127.0.0.1"; + # TODO: fix this for ssh tunneling... + rpc-whitelist-enabled = false; + rpc-whitelist = "192.168.15.1,127.0.0.1,192.168.1.*,192.168.0.*"; rpc-authentication-required = false; blocklist-enabled = true; @@ -269,8 +269,8 @@ in { anti-brute-force-enabled = true; anti-brute-force-threshold = 10; - script-torrent-done-enabled = true; - script-torrent-done-filename = getExe transmissionCrossSeedScript; + script-torrent-done-enabled = cfg-cross-seed.enable; + script-torrent-done-filename = if cfg-cross-seed.enable then transmissionCrossSeedScript else null; message-level = if cfg.messageLevel == "none" @@ -292,115 +292,18 @@ in { // cfg.extraSettings; }; - util-nixarr.vpnnamespace = mkIf cfg.vpn.enable { - portMappings = [ - { - From = cfg.uiPort; - To = cfg.uiPort; - } - ]; - openUdpPorts = [cfg.peerPort]; - openTcpPorts = [cfg.peerPort]; + # Enable and specify VPN namespace to confine service in. + systemd.services.transmission.vpnconfinement = mkIf cfg.vpn.enable { + enable = true; + vpnnamespace = "wg"; }; - systemd.services."container@transmission" = mkIf cfg.vpn.enable { - requires = ["wg.service"]; - }; - - containers.transmission = mkIf cfg.vpn.enable { - autoStart = true; - ephemeral = true; - extraFlags = ["--network-namespace-path=/var/run/netns/wg"]; - - bindMounts = { - "${nixarr.mediaDir}/torrents".isReadOnly = false; - "/var/lib/transmission" = { - hostPath = cfg.stateDir; - isReadOnly = false; - }; - }; - - config = { - users.groups.torrenter = { - gid = config.users.groups.torrenter.gid; - }; - users.users.torrenter = { - uid = lib.mkForce config.users.users.torrenter.uid; - isSystemUser = true; - group = "torrenter"; - }; - - # Use systemd-resolved inside the container - # Workaround for bug https://github.com/NixOS/nixpkgs/issues/162686 - networking.useHostResolvConf = lib.mkForce false; - services.resolved.enable = true; - networking.nameservers = dnsServers; - - systemd.services.transmission.serviceConfig = { - RootDirectoryStartOnly = lib.mkForce false; - RootDirectory = lib.mkForce ""; - }; - - services.transmission = { - enable = true; - user = "torrenter"; - group = "torrenter"; - webHome = - if cfg.flood.enable - then pkgs.flood-for-transmission - else null; - package = pkgs.transmission_4; - openRPCPort = true; - openPeerPorts = true; - settings = - { - download-dir = "${nixarr.mediaDir}/torrents"; - incomplete-dir-enabled = true; - incomplete-dir = "${nixarr.mediaDir}/torrents/.incomplete"; - watch-dir-enabled = true; - watch-dir = "${nixarr.mediaDir}/torrents/.watch"; - - rpc-bind-address = "192.168.15.1"; - rpc-port = cfg.uiPort; - rpc-whitelist-enabled = false; - rpc-whitelist = "192.168.15.1,127.0.0.1"; - rpc-authentication-required = false; - - blocklist-enabled = true; - blocklist-url = "https://github.com/Naunter/BT_BlockLists/raw/master/bt_blocklists.gz"; - - peer-port = cfg.peerPort; - dht-enabled = !cfg.privateTrackers.disableDhtPex; - pex-enabled = !cfg.privateTrackers.disableDhtPex; - utp-enabled = false; - encryption = 1; - port-forwarding-enabled = false; - - anti-brute-force-enabled = true; - anti-brute-force-threshold = 10; - - # 0 = None, 1 = Critical, 2 = Error, 3 = Warn, 4 = Info, 5 = Debug, 6 = Trace - message-level = 3; - } - // cfg.extraSettings; - }; - - environment.systemPackages = with pkgs; [ - curl - wget - util-linux - unixtools.ping - coreutils - curl - bash - libressl - netcat-gnu - openresolv - dig - ]; - - system.stateVersion = "23.11"; - }; + # Port mappings + # TODO: open peerPort + vpnnamespaces.wg = mkIf cfg.vpn.enable { + portMappings = [{ From = cfg.uiPort; To = cfg.uiPort; }]; + #openUdpPorts = [cfg.peerPort]; + #openTcpPorts = [cfg.peerPort]; }; services.nginx = mkIf cfg.vpn.enable { diff --git a/util/default.nix b/util/default.nix index c22cf7c..4c2a13e 100644 --- a/util/default.nix +++ b/util/default.nix @@ -1,6 +1,5 @@ { imports = [ ./upnp - ./vpnNamespace ]; } diff --git a/util/vpnNamespace/default.nix b/util/vpnNamespace/default.nix deleted file mode 100644 index 4ebec63..0000000 --- a/util/vpnNamespace/default.nix +++ /dev/null @@ -1,369 +0,0 @@ -# Thank you Maroka-chan <3 -{ - lib, - pkgs, - config, - ... -}: -with builtins; -with lib; let - cfg = config.util-nixarr.vpnnamespace; -in { - options.util-nixarr.vpnnamespace = { - enable = mkEnableOption '' - Whether to enable the VPN namespace. - - To access the namespace a veth pair is used to - connect the vpn namespace and the default namespace - through a linux bridge. One end of the pair is - connected to the linux bridge on the default namespace. - The other end is connected to the vpn namespace. - - Systemd services can be run within the namespace by - adding these options: - - bindsTo = [ "netns@wg.service" ]; - requires = [ "network-online.target" ]; - after = [ "wg.service" ]; - serviceConfig = { - NetworkNamespacePath = "/var/run/netns/wg"; - }; - ''; - - accessibleFrom = mkOption { - type = types.listOf types.str; - default = []; - description = lib.mdDoc '' - Subnets or specific addresses that the namespace should be accessible to. - ''; - example = [ - "10.0.2.0/24" - "192.168.1.27" - ]; - }; - - namespaceAddress = mkOption { - type = types.str; - default = "192.168.15.1"; - description = lib.mdDoc '' - The address of the veth interface connected to the vpn namespace. - - This is the address used to reach the vpn namespace from other - namespaces connected to the linux bridge. - ''; - }; - - bridgeAddress = mkOption { - type = types.str; - default = "192.168.15.5"; - description = lib.mdDoc '' - The address of the linux bridge on the default namespace. - - The linux bridge sits on the default namespace and - needs an address to make communication between the - default namespace and other namespaces on the - bridge possible. - ''; - }; - - wireguardAddressPath = mkOption { - type = types.path; - default = ""; - description = lib.mdDoc '' - The address for the wireguard interface. - It is a path to a file containing the address. - This is done so the whole wireguard config can be specified - in a secret file. - ''; - }; - - wireguardConfigFile = mkOption { - type = types.path; - default = "/etc/wireguard/wg0.conf"; - description = lib.mdDoc '' - Path to the wireguard config to use. - - Note that this _is_ a wg-quick config. - ''; - }; - - portMappings = mkOption { - type = with types; listOf (attrsOf port); - default = []; - description = lib.mdDoc '' - A list of pairs mapping a port from the host to a port in the namespace. - ''; - example = [ - { - From = 80; - To = 80; - } - ]; - }; - - dnsServers = mkOption { - type = with types; nullOr (listOf str); - default = []; - description = lib.mdDoc '' - DNS servers to append to any found in the wg-quick config file. - ''; - example = ["1.1.1.2"]; - }; - - openTcpPorts = mkOption { - type = with types; listOf port; - default = []; - description = lib.mdDoc '' - What TCP ports to allow incoming traffic from. You need this if - you're port forwarding on your VPN provider. - ''; - example = [46382 38473]; - }; - - openUdpPorts = mkOption { - type = with types; listOf port; - default = []; - description = lib.mdDoc '' - What UDP ports to allow incoming traffic from. You need this if - you're port forwarding on your VPN provider. - ''; - example = [46382 38473]; - }; - - vpnTestService = { - enable = mkEnableOption "Enable the vpn test service."; - - port = mkOption { - type = types.port; - default = [12300]; - description = lib.mdDoc '' - The port that the vpn test service listens to. - ''; - example = [58403]; - }; - }; - }; - - config = let - headMay = list: - if list == [] - then null - else head list; - # Checks if string is ipv4, from SO, hope it works well - # https://stackoverflow.com/questions/53497/regular-expression-that-matches-valid-ipv6-addresses - isIpv4 = address: let - pat = "((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])?/?[0-9]?[0-9]"; - regex = match pat address; - in - regex != null; - # Checks if string is ipv6, from SO, hope it works well - # https://stackoverflow.com/questions/53497/regular-expression-that-matches-valid-ipv6-addresses - isIpv6 = address: let - pat = "(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))"; - regex = match pat address; - in - regex != null; - isIp = ip: (isIpv4 ip || isIpv6 ip); - in - mkIf cfg.enable { - lib.vpn = { - dnsServers = let - lines = split "\n" (readFile cfg.wireguardConfigFile); - dnsLine = headMay (filter (x: typeOf x == "string" && match ".*DNS.*" x != null) lines); - in - if dnsLine == null - then [] - else let - ipsUnsplit = head (match "DNS ?=(.*)" dnsLine); - in - if ipsUnsplit == null - then [] - else let - ips = filter (x: typeOf x == "string") (split "," ipsUnsplit); - ipsNoSpaces = map (replaceStrings [" "] [""]) ips; - correctIps = filter isIp ipsNoSpaces; - in - assert (correctIps != []) || abort "There must be at least 1 DNS server set."; correctIps; - }; - - boot.kernel.sysctl."net.ipv4.ip_forward" = 1; - - systemd.services = { - "netns@" = { - description = "%I network namespace"; - before = ["network.target"]; - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - ExecStart = "${pkgs.iproute2}/bin/ip netns add %I"; - ExecStop = "${pkgs.iproute2}/bin/ip netns del %I"; - }; - }; - - wg = { - description = "wg network interface"; - bindsTo = ["netns@wg.service"]; - requires = ["network-online.target"]; - after = ["netns@wg.service"]; - wantedBy = ["multi-user.target"]; - - serviceConfig = let - lines = split "\n" (readFile cfg.wireguardConfigFile); - addrLine = headMay (filter (x: typeOf x == "string" && match ".*Address.*" x != null) lines); - in - if addrLine == null - then [] - else let - ipsUnsplit = head (match "Address ?=(.*)" addrLine); - in - if ipsUnsplit == null - then [] - else let - ips = filter (x: typeOf x == "string") (split "," ipsUnsplit); - ipsNoSpaces = map (replaceStrings [" "] [""]) ips; - wgIpv4Address = headMay (filter isIpv4 ipsNoSpaces); - - vpn-namespace = pkgs.writeShellApplication { - name = "vpn-namespace"; - - runtimeInputs = with pkgs; [iproute2 wireguard-tools iptables]; - - text = - '' - # Set up the wireguard interface - tmpdir=$(mktemp -d) - cat ${cfg.wireguardConfigFile} > "$tmpdir/wg.conf" - - # Get dns servers - grep "DNS =" "$tmpdir/wg.conf" | sed 's/DNS =//g' | sed 's/,/\n/g' | sed 's/ //g' | sed 's/^/nameserver: /g' > "$tmpdir/resolv.conf" - - ip link add wg0 type wireguard - ip link set wg0 netns wg - ip -n wg address add "${wgIpv4Address}" dev wg0 - ip netns exec wg wg setconf wg0 <(wg-quick strip "$tmpdir/wg.conf") - ip -n wg link set wg0 up - ip -n wg route add default dev wg0 - - # Start the loopback interface - ip -n wg link set dev lo up - - # Create a bridge - ip link add v-net-0 type bridge - ip addr add ${cfg.bridgeAddress}/24 dev v-net-0 - ip link set dev v-net-0 up - - # Set up veth pair to link namespace with host network - ip link add veth-vpn-br type veth peer name veth-vpn netns wg - ip link set veth-vpn-br master v-net-0 - - ip -n wg addr add ${cfg.namespaceAddress}/24 dev veth-vpn - ip -n wg link set dev veth-vpn up - - echo "setting dns" - # DNS test, see: - # https://www.man7.org/linux/man-pages/man8/wg-quick.8.html - # Absolutely no luck... - #echo "nameserver 1.1.1.1" | ip netns exec wg resolvconf -a wg0 -m 0 -x - - echo "Hello test" - '' - # Add routes to make the namespace accessible - + strings.concatMapStrings ( - x: - "ip -n wg route add ${x} via ${cfg.bridgeAddress}" + "\n" - ) - cfg.accessibleFrom - # Add prerouting rules - + strings.concatMapStrings ( - x: - "iptables -t nat -A PREROUTING -p tcp --dport ${builtins.toString x.From} -j DNAT --to-destination ${cfg.namespaceAddress}:${builtins.toString x.To}" - + "\n" - ) - cfg.portMappings - # Allow VPN TCP ports - + strings.concatMapStrings ( - x: - "ip netns exec wg iptables -I INPUT -p tcp --dport ${builtins.toString x} -j ACCEPT" - + "\n" - ) - cfg.openTcpPorts - # Allow VPN UDP ports - + strings.concatMapStrings ( - x: - "ip netns exec wg iptables -I INPUT -p udp --dport ${builtins.toString x} -j ACCEPT" - + "\n" - ) - cfg.openUdpPorts; - }; - in - assert (wgIpv4Address != null) || abort "No address found in config file."; { - Type = "oneshot"; - RemainAfterExit = true; - ExecStart = "${vpn-namespace}/bin/vpn-namespace"; - - ExecStopPost = with pkgs; - writers.writeBash "wg-down" ('' - ${iproute2}/bin/ip -n wg route del default dev wg0 - ${iproute2}/bin/ip -n wg link del wg0 - ${iproute2}/bin/ip -n wg link del veth-vpn - ${iproute2}/bin/ip link del v-net-0 - - # DNS test, see: - # https://www.man7.org/linux/man-pages/man8/wg-quick.8.html - #${iproute2}/bin/ip netns exec wg resolvconf -d wg0 - '' - # Delete prerouting rules - + strings.concatMapStrings (x: "${iptables}/bin/iptables -t nat -D PREROUTING -p tcp --dport ${builtins.toString x.From} -j DNAT --to-destination ${cfg.namespaceAddress}:${builtins.toString x.To}" + "\n") cfg.portMappings); - }; - }; - - vpn-test-service = { - enable = cfg.vpnTestService.enable; - - script = let - vpn-test = pkgs.writeShellApplication { - name = "vpn-test"; - - runtimeInputs = with pkgs; [util-linux unixtools.ping coreutils curl bash libressl netcat-gnu openresolv dig]; - - text = '' - cd "$(mktemp -d)" - - # Print resolv.conf - echo "/etc/resolv.conf contains:" - cat /etc/resolv.conf - - # Query resolvconf - echo "resolvconf output:" - resolvconf -l - echo "" - - # Get ip - echo "Getting IP:" - curl -s ipinfo.io - - cat /etc/test.file - - echo -ne "DNS leak test:" - curl -s https://raw.githubusercontent.com/macvk/dnsleaktest/b03ab54d574adbe322ca48cbcb0523be720ad38d/dnsleaktest.sh -o dnsleaktest.sh - chmod +x dnsleaktest.sh - ./dnsleaktest.sh - - echo "starting netcat on port ${builtins.toString cfg.vpnTestService.port}:" - nc -vnlp ${builtins.toString cfg.vpnTestService.port} - ''; - }; - in "${vpn-test}/bin/vpn-test"; - - bindsTo = ["netns@wg.service"]; - requires = ["network-online.target"]; - after = ["wg.service"]; - serviceConfig = { - User = "prowlarr"; - NetworkNamespacePath = "/var/run/netns/wg"; - BindReadOnlyPaths = ["/etc/netns/wg/resolv.conf:/etc/resolv.conf:norbind" "/data/test.file:/etc/test.file:norbind"]; - }; - }; - }; - }; -}