Compare commits

...

88 Commits

Author SHA1 Message Date
benedettadavico 2e7f1ed0f3 fix order 2025-11-17 16:27:17 +01:00
benedettadavico c2f588857f dns and icmp rules go to the top 2025-11-17 16:21:49 +01:00
benedettadavico 45598ce250 testing 2025-11-17 11:42:25 +01:00
serinko 4f991061dd fix nginx errors 2025-11-14 16:22:58 +01:00
serinko e5aef76256 non-interactive 2025-11-14 15:27:44 +01:00
serinko 6acc54d2bc syntax fix 2025-11-14 15:03:36 +01:00
serinko ab6e08dd13 fix logic of landing-page lookup 2025-11-14 14:27:38 +01:00
serinko e09066858c bump up version 2025-11-14 14:21:50 +01:00
RadekSabacky 842ce93a60 remove duplicate ufw rule 2025-11-14 13:24:20 +01:00
RadekSabacky ce26105986 typo 2025-11-14 13:24:05 +01:00
serinko cc95358385 add email to a fallback 2025-11-14 13:08:05 +01:00
serinko cc04a09ed7 remove redundant work 2025-11-14 12:58:03 +01:00
serinko ae47d53f0c enforce root 2025-11-14 12:49:10 +01:00
serinko e0ff09f323 enforce root 2025-11-14 12:44:57 +01:00
serinko 10707fd2c5 convention Y/n 2025-11-14 12:43:54 +01:00
serinko 9bdd2af14c enforce root 2025-11-14 12:42:05 +01:00
serinko 228ef8b158 add else 2025-11-14 12:40:14 +01:00
import this d820131d2c arg consistency
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-14 10:36:45 +00:00
import this 054715a600 robust error handling
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-14 10:36:23 +00:00
import this 3f560180b7 remove redundant
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-14 10:35:42 +00:00
import this f62dbbdae0 ensure idempotency for the iptable rules
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-14 10:33:23 +00:00
import this edecc4ba01 remove redundant detect interface
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-14 10:31:26 +00:00
serinko 58c0e289c2 syntax fix 2025-11-13 20:56:04 +01:00
serinko 6d8edc4bc7 replace y to Y and '' 2025-11-13 20:46:33 +01:00
serinko a44cdf1c7c flush nginx script anew 2025-11-13 20:37:35 +01:00
serinko 6b8a6283a4 fix nginx script 2025-11-13 20:27:43 +01:00
import this 94151965bb string to dict fix
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 17:55:09 +00:00
import this e8ca490db1 style
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 17:54:42 +00:00
import this fe7470ea44 address comment
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 17:54:17 +00:00
serinko 21d52244cb sync up with new tunnel manager 2025-11-13 18:49:53 +01:00
import this c0c58026a8 Merge pull request #6197 from nymtech/serinko/ip-tables-rewamp
one ring rules over all
2025-11-13 17:27:56 +00:00
import this 0fe863c889 delete to resolve merge conflict 2025-11-13 17:27:04 +00:00
import this 4e5d88f64c deleting to resolve merge confilict 2025-11-13 17:26:20 +00:00
serinko 1559f6a912 bugfix 2025-11-13 17:55:57 +01:00
serinko 766024be27 break into args 2025-11-13 17:51:39 +01:00
serinko 5ba181b118 break into args 2025-11-13 17:47:58 +01:00
import this 76fc9f4a90 syntax fix
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 16:23:38 +00:00
import this 8ca6af7c86 syntax fix
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 16:23:18 +00:00
import this 45e14a7fb1 address comments
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 16:22:57 +00:00
import this a38917cb9b address comments
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 16:22:34 +00:00
serinko cf8a399089 remove subshell 2025-11-13 17:07:20 +01:00
import this ba01820586 address comment
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 16:05:10 +00:00
import this 8c799b2976 address comment
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 16:04:34 +00:00
import this de4fb6291c address comments
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 16:03:50 +00:00
import this 81fd37e5c0 address comments
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 16:02:56 +00:00
serinko 219f3af967 remove subshell 2025-11-13 16:57:34 +01:00
import this aea7442525 add status
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 15:56:52 +00:00
import this 1525aed657 expand pattern to common naming conventions
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 15:56:18 +00:00
import this 943b5fa8bc address comment
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 15:51:01 +00:00
import this 71e0c025c6 address comment
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 15:50:42 +00:00
import this 239c6c701b address comment
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 15:50:19 +00:00
import this 91d0b7bdad address comment
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 15:49:49 +00:00
import this 99b28b2b6f address comment
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 15:49:32 +00:00
import this 5627ada57e address comment
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 15:49:02 +00:00
serinko 4e1228fff0 ensure cars passing in a shell 2025-11-13 16:43:39 +01:00
serinko 04be5624fa ensure cars passing in a shell 2025-11-13 16:41:42 +01:00
serinko a6fe1b1de7 fix logic to ensure to more robust 2025-11-13 16:32:40 +01:00
import this c5971d0e9d align space
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 15:18:54 +00:00
import this 06dd74ba34 address comments
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 15:18:38 +00:00
serinko c6a0256b03 remove wrong stdout 2025-11-13 16:13:25 +01:00
import this d04b61a88b spacing
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 15:10:01 +00:00
import this 70a119ac58 address comment
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 15:09:38 +00:00
import this e2fe3a60a6 address comment
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 15:01:23 +00:00
serinko 71301ee0cc sync ipv4 w ipv6 2025-11-13 15:08:34 +01:00
serinko aba6c9d4ac fix exit message 2025-11-13 15:04:17 +01:00
serinko c617bbb240 fix jq 2025-11-13 14:59:36 +01:00
serinko 1f8144eb11 add a safeguard 2025-11-13 14:47:02 +01:00
import this 694135c81b address comment
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 13:38:45 +00:00
import this e815f08505 address comment
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 13:38:15 +00:00
serinko f402da8e60 add new top manager tool 2025-11-13 14:28:38 +01:00
serinko 34a500d0a2 refactor completely 2025-11-13 14:26:49 +01:00
serinko ef52f25564 address comment 2025-11-13 11:20:13 +01:00
import this 010b013094 comment fix
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 10:17:04 +00:00
benedettadavico c503a5f0e8 few more tweaks 2025-11-13 10:21:37 +01:00
serinko 781afd3522 add arg 2025-11-12 17:36:29 +01:00
serinko 58083df0b0 fix QUIC helper script 2025-11-12 17:36:29 +01:00
benedettadavico 4e8d29d0c5 Merge remote-tracking branch 'origin/operators/tools-rewamp' into operators/tools-rewamp 2025-11-12 17:19:32 +01:00
benedettadavico 66797efa80 test new order of events.. 2025-11-12 17:18:59 +01:00
serinko 5a26fa262e add uplink override arg 2025-11-12 16:58:10 +01:00
serinko 73a34935ae trims 2025-11-12 16:29:52 +01:00
serinko a8086675d9 metadata port inside nymwg 2025-11-12 15:34:00 +01:00
serinko 0453345d65 address comments 2025-11-12 15:32:41 +01:00
serinko b56d9505e6 address comments 2025-11-12 15:31:08 +01:00
serinko bdacc72003 rm redundant 2025-11-12 15:22:26 +01:00
benedettadavico 9eca9efd82 fix direction and add test 2025-11-12 13:45:33 +01:00
serinko cc74d218fc rm redundant fn 2025-11-11 21:56:15 +01:00
serinko dea8a287e6 add arguments for env vars 2025-11-11 19:42:03 +01:00
serinko fc5d310935 add QUIC setup script to nym-node-cli 2025-11-11 15:04:09 +01:00
15 changed files with 1677 additions and 1002 deletions
@@ -375,7 +375,7 @@ operation check_nymtun_iptables completed successfully.
###### 5. Remove old and apply new rules for wireguad routing
```sh
/network_tunnel_manager.sh remove_duplicate_rules nymwg
./network_tunnel_manager.sh remove_duplicate_rules nymwg
./network_tunnel_manager.sh apply_iptables_rules_wg
```
-290
View File
@@ -1,290 +0,0 @@
#!/bin/bash
network_device=$(ip route show default | awk '/default/ {print $5}')
tunnel_interface="nymtun0"
wg_tunnel_interface="nymwg"
if ! dpkg -s iptables-persistent >/dev/null 2>&1; then
sudo apt-get update
sudo apt-get install -y iptables-persistent
else
echo "iptables-persistent is already installed."
fi
fetch_ipv6_address() {
local interface=$1
ipv6_global_address=$(ip -6 addr show "$interface" scope global | grep inet6 | awk '{print $2}' | head -n 1)
if [[ -z "$ipv6_global_address" ]]; then
echo "no globally routable IPv6 address found on $interface. Please configure IPv6 or check your network settings."
exit 1
else
echo "using IPv6 address: $ipv6_global_address"
fi
}
fetch_and_display_ipv6() {
ipv6_address=$(ip -6 addr show "$network_device" scope global | grep inet6 | awk '{print $2}')
if [[ -z "$ipv6_address" ]]; then
echo "no global IPv6 address found on $network_device."
else
echo "IPv6 address on $network_device: $ipv6_address"
fi
}
remove_duplicate_rules() {
local interface=$1
local script_name=$(basename "$0")
if [[ -z "$interface" ]]; then
echo "error: no interface specified. please enter the interface (nymwg or nymtun0):"
read -r interface
fi
if [[ "$interface" != "nymwg" && "$interface" != "nymtun0" ]]; then
echo "error: invalid interface '$interface'. allowed values are 'nymwg' or 'nymtun0'." >&2
exit 1
fi
echo "removing duplicate rules for $interface..."
iptables-save | grep "$interface" | while read -r line; do
sudo iptables -D ${line#-A } || echo "Failed to delete rule: $line"
done
ip6tables-save | grep "$interface" | while read -r line; do
sudo ip6tables -D ${line#-A } || echo "Failed to delete rule: $line"
done
echo "duplicates removed for $interface."
echo "!!-important-!! you need to now reapply the iptables rules for $interface."
if [ "$interface" == "nymwg" ]; then
echo "run: ./$script_name apply_iptables_rules_wg"
else
echo "run: ./$script_name apply_iptables_rules"
fi
}
adjust_ip_forwarding() {
ipv6_forwarding_setting="net.ipv6.conf.all.forwarding=1"
ipv4_forwarding_setting="net.ipv4.ip_forward=1"
# remove duplicate entries for these settings from the file
sudo sed -i "/^net.ipv6.conf.all.forwarding=/d" /etc/sysctl.conf
sudo sed -i "/^net.ipv4.ip_forward=/d" /etc/sysctl.conf
echo "$ipv6_forwarding_setting" | sudo tee -a /etc/sysctl.conf
echo "$ipv4_forwarding_setting" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p /etc/sysctl.conf
}
apply_iptables_rules() {
local interface=$1
echo "applying IPtables rules for $interface..."
sleep 2
sudo iptables -t nat -A POSTROUTING -o "$network_device" -j MASQUERADE
sudo iptables -A FORWARD -i "$interface" -o "$network_device" -j ACCEPT
sudo iptables -A FORWARD -i "$network_device" -o "$interface" -m state --state RELATED,ESTABLISHED -j ACCEPT
sudo ip6tables -t nat -A POSTROUTING -o "$network_device" -j MASQUERADE
sudo ip6tables -A FORWARD -i "$interface" -o "$network_device" -j ACCEPT
sudo ip6tables -A FORWARD -i "$network_device" -o "$interface" -m state --state RELATED,ESTABLISHED -j ACCEPT
sudo iptables-save | sudo tee /etc/iptables/rules.v4
sudo ip6tables-save | sudo tee /etc/iptables/rules.v6
}
check_tunnel_iptables() {
local interface=$1
echo "inspecting IPtables rules for $interface..."
echo "---------------------------------------"
echo "IPv4 rules:"
iptables -L FORWARD -v -n | awk -v dev="$interface" '/^Chain FORWARD/ || $0 ~ dev || $0 ~ "ufw-reject-forward"'
echo "---------------------------------------"
echo "IPv6 rules:"
ip6tables -L FORWARD -v -n | awk -v dev="$interface" '/^Chain FORWARD/ || $0 ~ dev || $0 ~ "ufw6-reject-forward"'
}
check_ipv6_ipv4_forwarding() {
result_ipv4=$(cat /proc/sys/net/ipv4/ip_forward)
result_ipv6=$(cat /proc/sys/net/ipv6/conf/all/forwarding)
echo "IPv4 forwarding is $([ "$result_ipv4" == "1" ] && echo "enabled" || echo "not enabled")."
echo "IPv6 forwarding is $([ "$result_ipv6" == "1" ] && echo "enabled" || echo "not enabled")."
}
check_ip_routing() {
echo "IPv4 routing table:"
ip route
echo "---------------------------------------"
echo "IPv6 routing table:"
ip -6 route
}
perform_pings() {
echo "performing IPv4 ping to google.com..."
ping -c 4 google.com
echo "---------------------------------------"
echo "performing IPv6 ping to google.com..."
ping6 -c 4 google.com
}
joke_through_tunnel() {
local interface=$1
local green="\033[0;32m"
local reset="\033[0m"
local red="\033[0;31m"
local yellow="\033[0;33m"
sleep 1
echo
echo -e "${yellow}checking tunnel connectivity and fetching a joke for $interface...${reset}"
echo -e "${yellow}if these test succeeds, it confirms your machine can reach the outside world via IPv4 and IPv6.${reset}"
echo -e "${yellow}however, probes and external clients may experience different connectivity to your nym-node.${reset}"
ipv4_address=$(ip addr show "$interface" | awk '/inet / {print $2}' | cut -d'/' -f1)
ipv6_address=$(ip addr show "$interface" | awk '/inet6 / && $2 !~ /^fe80/ {print $2}' | cut -d'/' -f1)
if [[ -z "$ipv4_address" && -z "$ipv6_address" ]]; then
echo -e "${red}no IP address found on $interface. unable to fetch a joke.${reset}"
echo -e "${red}please verify your tunnel configuration and ensure the interface is up.${reset}"
return 1
fi
if [[ -n "$ipv4_address" ]]; then
echo
echo -e "------------------------------------"
echo -e "detected IPv4 address: $ipv4_address"
echo -e "testing IPv4 connectivity..."
echo
if ping -c 1 -I "$ipv4_address" google.com >/dev/null 2>&1; then
echo -e "${green}IPv4 connectivity is working. fetching a joke...${reset}"
joke=$(curl -s -H "Accept: application/json" --interface "$ipv4_address" https://icanhazdadjoke.com/ | jq -r .joke)
[[ -n "$joke" && "$joke" != "null" ]] && echo -e "${green}IPv4 joke: $joke${reset}" || echo -e "failed to fetch a joke via IPv4."
else
echo -e "${red}IPv4 connectivity is not working for $interface. verify your routing and NAT settings.${reset}"
fi
else
echo -e "${red}no IPv4 address found on $interface. unable to fetch a joke via IPv4.${reset}"
fi
if [[ -n "$ipv6_address" ]]; then
echo
echo -e "------------------------------------"
echo -e "detected IPv6 address: $ipv6_address"
echo -e "testing IPv6 connectivity..."
echo
if ping6 -c 1 -I "$ipv6_address" google.com >/dev/null 2>&1; then
echo -e "${green}IPv6 connectivity is working. fetching a joke...${reset}"
joke=$(curl -s -H "Accept: application/json" --interface "$ipv6_address" https://icanhazdadjoke.com/ | jq -r .joke)
[[ -n "$joke" && "$joke" != "null" ]] && echo -e "${green}IPv6 joke: $joke${reset}" || echo -e "${red}failed to fetch a joke via IPv6.${reset}"
else
echo -e "${red}IPv6 connectivity is not working for $interface. verify your routing and NAT settings.${reset}"
fi
else
echo -e "${red}no IPv6 address found on $interface. unable to fetch a joke via IPv6.${reset}"
fi
echo -e "${green}joke fetching processes completed for $interface.${reset}"
echo -e "------------------------------------"
sleep 3
echo
echo
echo -e "${yellow}### connectivity testing recommendations ###${reset}"
echo -e "${yellow}- use the following command to test WebSocket connectivity from an external client:${reset}"
echo -e "${yellow} wscat -c wss://<your-ip-address/ hostname>:9001 ${reset}"
echo -e "${yellow}- test UDP connectivity on port 51822 (commonly used for nym wireguard) ${reset}"
echo -e "${yellow} from another machine, use tools like nc or socat to send UDP packets ${reset}"
echo -e "${yellow} echo 'test message' | nc -u <your-ip-address> 51822 ${reset}"
echo -e "${yellow}if connectivity issues persist, ensure port forwarding and firewall rules are correctly configured ${reset}"
echo
}
configure_dns_and_icmp_wg() {
echo "allowing icmp (ping)..."
sudo iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
sudo iptables -A OUTPUT -p icmp --icmp-type echo-reply -j ACCEPT
echo "allowing dns over udp (port 53)..."
sudo iptables -A INPUT -p udp --dport 53 -j ACCEPT
echo "allowing dns over tcp (port 53)..."
sudo iptables -A INPUT -p tcp --dport 53 -j ACCEPT
echo "saving iptables rules..."
sudo iptables-save >/etc/iptables/rules.v4
echo "dns and icmp configuration completed."
}
case "$1" in
fetch_ipv6_address_nym_tun)
fetch_ipv6_address "$tunnel_interface"
;;
fetch_and_display_ipv6)
fetch_and_display_ipv6
;;
apply_iptables_rules)
apply_iptables_rules "$tunnel_interface"
;;
apply_iptables_rules_wg)
apply_iptables_rules "$wg_tunnel_interface"
;;
check_nymtun_iptables)
check_tunnel_iptables "$tunnel_interface"
;;
check_nym_wg_tun)
check_tunnel_iptables "$wg_tunnel_interface"
;;
check_ipv6_ipv4_forwarding)
check_ipv6_ipv4_forwarding
;;
check_ip_routing)
check_ip_routing
;;
perform_pings)
perform_pings
;;
joke_through_the_mixnet)
joke_through_tunnel "$tunnel_interface"
;;
joke_through_wg_tunnel)
joke_through_tunnel "$wg_tunnel_interface"
;;
configure_dns_and_icmp_wg)
configure_dns_and_icmp_wg
;;
adjust_ip_forwarding)
adjust_ip_forwarding
;;
remove_duplicate_rules)
remove_duplicate_rules "$2"
;;
*)
echo "Usage: $0 [command]"
echo "Commands:"
echo " fetch_ipv6_address_nym_tun - Fetch IPv6 for nymtun0."
echo " fetch_and_display_ipv6 - Show IPv6 on default device."
echo " apply_iptables_rules - Apply IPtables rules for nymtun0."
echo " apply_iptables_rules_wg - Apply IPtables rules for nymwg."
echo " check_nymtun_iptables - Check IPtables for nymtun0."
echo " check_nym_wg_tun - Check IPtables for nymwg."
echo " check_ipv6_ipv4_forwarding - Check IPv4 and IPv6 forwarding."
echo " check_ip_routing - Display IP routing tables."
echo " perform_pings - Test IPv4 and IPv6 connectivity."
echo " joke_through_the_mixnet - Fetch a joke via nymtun0."
echo " joke_through_wg_tunnel - Fetch a joke via nymwg."
echo " configure_dns_and_icmp_wg - Allows icmp ping tests for probes alongside configuring dns"
echo " adjust_ip_forwarding - Enable IPV6 and IPV4 forwarding"
echo " remove_duplicate_rules <interface> - Remove duplicate iptables rules. Valid interfaces: nymwg, nymtun0"
exit 1
;;
esac
echo "operation $1 completed successfully."
File diff suppressed because it is too large Load Diff
+186 -82
View File
@@ -1,6 +1,6 @@
#!/usr/bin/python3
__version__ = "1.1.0"
__version__ = "1.2.0"
__default_branch__ = "develop"
import os
@@ -22,17 +22,25 @@ class NodeSetupCLI:
def __init__(self, args):
self.branch = args.dev
self.welcome_message = self.print_welcome_message()
self.mode = self.prompt_mode()
self.mode = self._get_or_prompt_mode(args)
self.prereqs_install_sh = self.fetch_script("nym-node-prereqs-install.sh")
self.env_vars_install_sh = self.fetch_script("setup-env-vars.sh")
self.node_install_sh = self.fetch_script("nym-node-install.sh")
self.service_config_sh = self.fetch_script("setup-systemd-service-file.sh")
self.start_node_systemd_service_sh = self.fetch_script("start-node-systemd-service.sh")
self.landing_page_html = self._check_gwx_mode() and self.fetch_script("landing-page.html")
self.nginx_proxy_wss_sh = self._check_gwx_mode() and self.fetch_script("nginx_proxy_wss_sh")
self.tunnel_manager_sh = self._check_gwx_mode() and self.fetch_script("network_tunnel_manager.sh")
self.wg_ip_tables_manager_sh = self._check_gwx_mode() and self.fetch_script("wireguard-exit-policy-manager.sh")
self.wg_ip_tables_test_sh = self._check_gwx_mode() and self.fetch_script("exit-policy-tests.sh")
self.is_gwx = self.mode == "exit-gateway"
if self.is_gwx:
self.landing_page_html = self.fetch_script("landing-page.html")
self.nginx_proxy_wss_sh = self.fetch_script("nginx_proxy_wss_sh")
self.tunnel_manager_sh = self.fetch_script("network_tunnel_manager.sh")
self.quic_bridge_deployment_sh = self.fetch_script("quic_bridge_deployment.sh")
else:
self.landing_page_html = None
self.nginx_proxy_wss_sh = None
self.tunnel_manager_sh = None
self.wg_ip_tables_manager_sh = None
self.wg_ip_tables_test_sh = None
self.quic_bridge_deployment_sh = None
def print_welcome_message(self):
"""Welcome user, warns for needed pre-reqs and asks for confimation"""
@@ -45,7 +53,7 @@ class NodeSetupCLI:
self.print_character("=", 41)
msg = \
"Before you begin, make sure that:\n"\
"1. You run this setup on Debian based Linux (ie Ubuntu)\n"\
"1. You run this setup on Debian based Linux (ie Ubuntu 22.04 LTS)\n"\
"2. You run this installation program from a root shell\n"\
"3. You meet minimal requirements: https://nym.com/docs/operators/nodes\n"\
"4. You accept Operators Terms & Conditions: https://nym.com/operators-validators-terms\n"\
@@ -59,43 +67,103 @@ class NodeSetupCLI:
else:
print("Without confirming the points above, we cannot continue.")
exit(1)
def ensure_env_values(self, args):
"""Collect env vars from args or prompt interactively, then save to env.sh."""
env_file = Path("env.sh")
fields = [
("hostname", "HOSTNAME", "Enter hostname (if you don't use a DNS, press enter): "),
("location", "LOCATION", "Enter node location (country code or name): "),
("email", "EMAIL", "Enter your email: "),
("moniker", "MONIKER", "Enter node public moniker (visible in explorer & NymVPN app): "),
("description", "DESCRIPTION", "Enter short node public description: "),
]
def prompt_mode(self):
"""Ask user to insert node functionality and save it in python and bash envs"""
existing = self._read_env_file(env_file)
updated = {}
for arg_name, key, prompt in fields:
cli_val = getattr(args, arg_name, None)
value = cli_val.strip() if cli_val else existing.get(key) or input(prompt).strip()
updated[key] = value
os.environ[key] = value
# autodetect PUBLIC_IP if not already set
if not os.environ.get("PUBLIC_IP"):
try:
ip = subprocess.run(["curl", "-fsS4", "https://ifconfig.me"],
capture_output=True, text=True, timeout=5)
if ip.returncode == 0 and ip.stdout.strip():
updated["PUBLIC_IP"] = ip.stdout.strip()
os.environ["PUBLIC_IP"] = ip.stdout.strip()
except subprocess.TimeoutExpired:
print("[WARN] Timeout expired while trying to fetch public IP with curl.")
except FileNotFoundError:
print("[WARN] 'curl' command not found. Please install curl or set PUBLIC_IP manually.")
except subprocess.CalledProcessError as e:
print(f"[WARN] Error while running curl to fetch public IP: {e}")
# write all collected variables to env.sh in one go
self._upsert_env_vars(updated, env_file)
print(f"[OK] Updated env.sh with {len(updated)} entries.")
def _upsert_env_vars(self, updates: dict, env_file: Path = Path("env.sh")):
existing = self._read_env_file(env_file)
existing.update(updates)
with env_file.open("w") as f:
for k, v in existing.items():
f.write(f'export {k}="{v}"\n')
os.environ.update(updates)
def _read_env_file(self, env_file: Path) -> dict:
env = {}
if env_file.exists():
for line in env_file.read_text().splitlines():
if line.startswith("export ") and "=" in line:
k, v = line.replace("export ", "", 1).split("=", 1)
env[k.strip()] = v.strip().strip('"')
return env
def _get_or_prompt_mode(self, args):
"""Resolve MODE from --mode, env.sh, os.environ, or prompt; persist to env.sh."""
env_file = Path("env.sh")
# CLI arg
mode = getattr(args, "mode", None)
if mode:
mode = mode.strip().lower()
self._upsert_env_vars({"MODE": mode})
print(f"Mode set to '{mode}' from CLI argument.")
return mode
# env.sh (replaces manual read)
existing = self._read_env_file(env_file)
mode = existing.get("MODE")
if mode:
os.environ["MODE"] = mode
return mode
# process env
if os.environ.get("MODE"):
return os.environ["MODE"]
# prompt
mode = input(
"\nEnter the mode you want to run nym-node in: "
"\n1. mixnode "
"\n2. entry-gateway "
"\n3. exit-gateway (works as entry-gateway as well) "
"\nPress 1, 2 or 3 and enter:\n"
).strip()
if mode in ("1", "mixnode"):
mode = "mixnode"
elif mode in ("2", "entry-gateway"):
mode = "entry-gateway"
elif mode in ("3", "exit-gateway"):
mode = "exit-gateway"
else:
print("Only numbers 1, 2 or 3 are accepted.")
"\nEnter node mode (mixnode / entry-gateway / exit-gateway): "
).strip().lower()
if mode not in ("mixnode", "entry-gateway", "exit-gateway"):
print("Invalid mode. Must be one of: mixnode, entry-gateway, exit-gateway.")
raise SystemExit(1)
# save mode for this Python instance
self.mode = mode
os.environ["MODE"] = mode
# persist to env.sh so other scripts can source it
env_file = Path("env.sh")
with env_file.open("a") as f:
f.write(f'export MODE="{mode}"\n')
# source env.sh so future bash subprocesses see it immediately
subprocess.run("source ./env.sh", shell=True, executable="/bin/bash")
self._upsert_env_vars({"MODE": mode})
print(f"Mode set to '{mode}' — stored in env.sh and sourced for immediate use.")
return mode
def fetch_script(self, script_name):
"""Fetches needed scripts according to a defined mode"""
# print header only the first time
@@ -119,16 +187,15 @@ class NodeSetupCLI:
github_raw_nymtech_nym_scripts_url = f"https://raw.githubusercontent.com/nymtech/nym/refs/heads/{self.branch}/scripts/"
scripts_urls = {
"nym-node-prereqs-install.sh": f"{github_raw_nymtech_nym_scripts_url}nym-node-setup/nym-node-prereqs-install.sh",
"setup-env-vars.sh": f"{github_raw_nymtech_nym_scripts_url}nym-node-setup/setup-env-vars.sh",
"nym-node-install.sh": f"{github_raw_nymtech_nym_scripts_url}nym-node-setup/nym-node-install.sh",
"setup-systemd-service-file.sh": f"{github_raw_nymtech_nym_scripts_url}nym-node-setup/setup-systemd-service-file.sh",
"start-node-systemd-service.sh": f"{github_raw_nymtech_nym_scripts_url}nym-node-setup/start-node-systemd-service.sh",
"nginx_proxy_wss_sh": f"{github_raw_nymtech_nym_scripts_url}nym-node-setup/setup-nginx-proxy-wss.sh",
"landing-page.html": f"{github_raw_nymtech_nym_scripts_url}nym-node-setup/landing-page.html",
"network_tunnel_manager.sh": f"https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/network_tunnel_manager.sh",
"wireguard-exit-policy-manager.sh": f"https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/wireguard-exit-policy/wireguard-exit-policy-manager.sh",
"exit-policy-tests.sh": f"https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/wireguard-exit-policy/exit-policy-tests.sh",
"network_tunnel_manager.sh": f"{github_raw_nymtech_nym_scripts_url}nym-node-setup/network-tunnel-manager.sh",
"quic_bridge_deployment.sh": f"{github_raw_nymtech_nym_scripts_url}nym-node-setup/quic_bridge_deployment.sh"
}
return scripts_urls[script_init_name]
def run_script(
@@ -200,62 +267,61 @@ class NodeSetupCLI:
def _check_gwx_mode(self):
"""Helper: Several fns run only for GWx - this fn checks this condition"""
if self.mode == "exit-gateway":
return True
else:
return False
return self.mode == "exit-gateway"
def check_wg_enabled(self):
"""Checks if Wireguard is enabled and if not, prompts user if they want to enable it, stores it to env.sh"""
def check_wg_enabled(self, args=None):
"""Determine if WireGuard is enabled; precedence: CLI > env > env.sh > prompt. Persist normalized value."""
env_file = os.path.join(os.getcwd(), "env.sh")
env_file = os.path.abspath(os.path.join(os.getcwd(), "env.sh"))
def norm(v):
return "true" if str(v).strip().lower() == "true" else "false"
def norm(v): # -> "true" or "false"
return "true" if str(v).strip().lower() in ("1", "true", "yes", "y") else "false"
val = None
# precedence: process env → env.sh → prompt
val = os.environ.get("WIREGUARD")
# CLI argument
if args and getattr(args, "wireguard", None) is not None:
val = norm(getattr(args, "wireguard"))
print(f"[INFO] WireGuard mode provided via CLI: {val}")
if val is None and os.path.isfile(env_file):
try:
with open(env_file, "r", encoding="utf-8") as f:
m = re.search(r'^\s*export\s+WIREGUARD\s*=\s*"?([^"\n]+)"?', f.read(), re.M)
if m:
val = m.group(1)
except Exception:
pass
# Environment variable
val = val or os.environ.get("WIREGUARD")
# env.sh file
if val is None:
envs = self._read_env_file(Path(env_file))
val = envs.get("WIREGUARD")
# Prompt
if val is None:
ans = input(
"\nWireGuard is not configured.\n"
"Nodes routing WireGuard can be listed as both entry and exit in the app.\n"
"Enable WireGuard support? (y/n): "
"Enable WireGuard support? (Y/n): "
).strip().lower()
val = "true" if ans in ("y", "yes") else "false"
val = "true" if ans in ("", "y", "yes") else "false"
val = norm(val)
os.environ["WIREGUARD"] = val
# persist to env.sh (replace or append)
# Persist to env.sh
try:
text = ""
if os.path.isfile(env_file):
with open(env_file, "r", encoding="utf-8") as f:
with open(env_file, encoding="utf-8") as f:
text = f.read()
if re.search(r'^\s*export\s+WIREGUARD\s*=.*$', text, re.M):
text = re.sub(r'^\s*export\s+WIREGUARD\s*=.*$', f'export WIREGUARD="{val}"', text, flags=re.M)
else:
if text and not text.endswith("\n"):
text += "\n"
text += f'export WIREGUARD="{val}"\n'
text = (text.rstrip("\n") + "\n" if text else "") + f'export WIREGUARD="{val}"\n'
with open(env_file, "w", encoding="utf-8") as f:
f.write(text)
print(f'WIREGUARD={val} saved to {env_file}')
except Exception as e:
except OSError as e:
print(f"Warning: could not write {env_file}: {e}")
return (val == "true")
return val == "true"
def run_bash_command(self, command, args=None, *, env=None, cwd=None, check=True):
"""
@@ -316,10 +382,17 @@ class NodeSetupCLI:
"Setting up Wireguard IP tables to match Nym exit policy for mixnet, stored at: https://nymtech.net/.wellknown/network-requester/exit-policy.txt"
"\nThis may take a while, follow the steps below and don't kill the process..."
)
self.run_script(self.wg_ip_tables_manager_sh, args=["install"])
self.run_script(self.wg_ip_tables_manager_sh, args=["status"])
self.run_script(self.wg_ip_tables_test_sh)
self.run_script(self.tunnel_manager_sh, args=["exit_policy_install"])
def quic_bridge_deploy(self):
"""Setup QUIC bridge and configuration using external script"""
print("\n* * * Installing and configuring QUIC bridges * * *")
answer = input("\nDo you want to install, setup and run QUIC bridge? (Y/n) ").strip().lower()
if answer in ("", "y", "yes"):
self.run_script(self.quic_bridge_deployment_sh, args=["full_bridge_setup"])
else:
print("Skipping QUIC bridge setup.")
def run_nym_node_as_service(self):
"""Starts /etc/systemd/system/nym-node.service based on prompt using external script"""
@@ -347,8 +420,8 @@ class NodeSetupCLI:
if is_active:
while True:
ans = input(f"{service} is already running. Restart it now? (y/n):\n").strip().lower()
if ans == "y":
ans = input(f"{service} is already running. Restart it now? (Y/n):\n").strip().lower()
if ans in ("", "Y", "y"):
self.run_script(self.start_node_systemd_service_sh, args=["restart-poll"], env=run_env)
return
elif ans == "n":
@@ -358,8 +431,8 @@ class NodeSetupCLI:
print("Invalid input. Please press 'y' or 'n' and press enter.")
else:
while True:
ans = input(f"{service} is not running. Start it now? (y/n):\n").strip().lower()
if ans == "y":
ans = input(f"{service} is not running. Start it now? (Y/n):\n").strip().lower()
if ans in ("", "Y", "y"):
self.run_script(self.start_node_systemd_service_sh, args=["start-poll"], env=run_env)
return
elif ans == "n":
@@ -510,8 +583,12 @@ class NodeSetupCLI:
def run_node_installation(self,args):
"""Main function called by argparser command install running full node install flow"""
self.ensure_env_values(args)
# Pass uplink override to all helper scripts if provided
if getattr(args, "uplink_dev", None):
os.environ["UPLINK_DEV"] = args.uplink_dev
os.environ["NETWORK_DEVICE"] = args.uplink_dev
self.run_script(self.prereqs_install_sh)
self.run_script(self.env_vars_install_sh)
self.run_script(self.node_install_sh)
self.run_script(self.service_config_sh)
self._check_gwx_mode() and self.run_script(self.nginx_proxy_wss_sh)
@@ -521,7 +598,7 @@ class NodeSetupCLI:
self.run_tunnel_manager_setup()
if self.check_wg_enabled():
self.setup_test_wg_ip_tables()
self.setup_test_wg_ip_tables()
self.quic_bridge_deploy()
@@ -537,7 +614,7 @@ class ArgParser:
version=f"nym-node-cli {__version__}"
)
parent.add_argument("-d", "--dev", metavar="BRANCH",
help="Define github branch",
help="Define github branch (default: develop)",
type=str,
default=argparse.SUPPRESS)
parent.add_argument("-v", "--verbose", action="store_true",
@@ -553,11 +630,38 @@ class ArgParser:
subparsers = parser.add_subparsers(dest="command", help="subcommands")
subparsers.required = True
p_install = subparsers.add_parser(
install_parser = subparsers.add_parser(
"install", parents=[parent],
help="Starts nym-node installation setup CLI",
aliases=["i", "I"], add_help=True
)
install_parser.add_argument(
"--mode",
choices=["mixnode", "entry-gateway", "exit-gateway"],
help="Node mode: 'mixnode', 'entry-gateway', or 'exit-gateway'",
)
install_parser.add_argument(
"--wireguard-enabled",
choices=["true", "false"],
help="WireGuard functionality switch: true / false"
)
install_parser.add_argument("--hostname", help="Node domain / hostname")
install_parser.add_argument("--location", help="Node location (country code or name)")
install_parser.add_argument("--email", help="Contact email for the node operator")
install_parser.add_argument("--moniker", help="Public moniker displayed in explorer & NymVPN app")
install_parser.add_argument("--description", help="Short public description of the node")
install_parser.add_argument("--public-ip", help="External IPv4 address (autodetected if omitted)")
install_parser.add_argument("--nym-node-binary", help="URL for nym-node binary (autodetected if omitted)")
install_parser.add_argument("--uplink-dev", help="Override uplink interface used for NAT/FORWARD (e.g., 'eth0'; autodetected if omitted)")
# generic fallback
install_parser.add_argument(
"--env",
action="append",
metavar="KEY=VALUE",
help="(Optional) Extra ENV VARS, e.g. --env CUSTOM_KEY=value",
)
args = parser.parse_args()
+26 -21
View File
@@ -34,8 +34,9 @@ check_existing_config() {
if [[ "${resp}" =~ ^([Rr][Ee][Ss][Ee][Tt])$ ]]; then
echo
read -r -p "We are going to remove the existing node with configuration $NODE_CONFIG_DIR and replace it with a fresh one, do you want to back up the old one first? (y/n) " backup_ans
if [[ "${backup_ans}" =~ ^[Yy]$ ]]; then
echo "We are going to remove the existing node with configuration $NODE_CONFIG_DIR and replace it with a fresh one."
read -r -p "back up the old one first? (Y/n) " backup_ans
if [[ -z "${backup_ans}" || "${backup_ans}" =~ ^[Yy]$ ]]; then
ts="$(date +%Y%m%d-%H%M%S)"
backup_dir="$HOME/.nym/backup/$(basename "$NODE_CONFIG_DIR")-$ts"
echo "Backing up to: $backup_dir"
@@ -181,24 +182,27 @@ fi
NYM_NODE="$HOME/nym-binaries/nym-node"
# if binary already exists, ask to overwrite; if yes, remove first
# if binary already exists, ask to overwrite; if yes, remove first; if no, skip download
if [[ -e "${NYM_NODE}" ]]; then
echo
echo -e "\n* * * A nym-node binary already exists at: ${NYM_NODE}"
read -r -p "Overwrite with the latest release? (y/n): " ow_ans
if [[ "${ow_ans}" =~ ^[Yy]$ ]]; then
echo "Removing existing binary to avoid 'text file busy'..."
rm -f "${NYM_NODE}"
else
echo "Keeping existing binary."
fi
fi
read -r -p "Overwrite with the latest release? (Y/n): " ow_ans
download_nym_node "$LATEST_TAG_URL" "$NYM_NODE"
if [[ -z "${ow_ans}" || "${ow_ans}" =~ ^[Yy]$ ]]; then
echo "Removing existing binary..."
rm -f "${NYM_NODE}"
download_nym_node "$LATEST_TAG_URL" "$NYM_NODE"
else
echo "Keeping existing binary. Skipping download."
fi
else
# binary does not exist → must download
download_nym_node "$LATEST_TAG_URL" "$NYM_NODE"
fi
echo -e "\n * * * Making binary executable * * *"
chmod +x "${NYM_NODE}"
echo "---------------------------------------------------"
echo "Nym node binary downloaded:"
"${NYM_NODE}" --version || true
@@ -225,17 +229,18 @@ WIREGUARD="${WIREGUARD:-}"
if [[ ( "$MODE" == "entry-gateway" || "$MODE" == "exit-gateway" ) && ( -n "${ASK_WG:-}" || -z "$WIREGUARD" ) ]]; then
echo
echo "Gateways can also route WireGuard in NymVPN."
echo "Enabling it means your node may be listed as both entry and exit in the app."
# show current default in the prompt if present
def_hint=""
[[ -n "${WIREGUARD}" ]] && def_hint=" [current: ${WIREGUARD}]"
read -r -p "Enable WireGuard support? (y/n)${def_hint}: " answer || true
case "${answer:-}" in
[Yy]* ) WIREGUARD="true" ;;
[Nn]* ) WIREGUARD="false" ;;
* ) : ;; # keep existing value if user just pressed enter
esac
read -r -p "Enable WireGuard support? (Y/n)${def_hint}: " answer || true
if [[ -z "${answer}" || "${answer}" =~ ^[Yy]$ ]]; then
WIREGUARD="true"
elif [[ "${answer}" =~ ^[Nn]$ ]]; then
WIREGUARD="false"
fi
fi
# final default only if still empty
WIREGUARD="${WIREGUARD:-false}"
@@ -1,6 +1,11 @@
#!/bin/bash
# update, upgrade & install dependencies
if [[ "$(id -u)" -ne 0 ]]; then
echo "This script must be run as root."
exit 1
fi
# update, upgrade and install dependencies
echo -e "\n* * * Installing needed prerequisities * * *"
apt update -y && apt --fix-broken install
@@ -8,11 +13,9 @@ apt upgrade
apt install apt ca-certificates jq curl wget ufw jq tmux pkg-config build-essential libssl-dev git ntp ntpdate neovim tree tmux tig nginx -y
apt install ufw --fix-missing
# enable & setup firewall
echo -e "\n* * * Setting up firewall using ufw * * * "
echo "Please enable the firewall in the next prompt for node proper routing!"
echo "Please enable the firewall in the next prompt for node proper routing."
echo
ufw enable
ufw allow 22/tcp # SSH - you're in control of these ports
@@ -24,6 +27,6 @@ ufw allow 8080/tcp # Nym specific - nym-node-api
ufw allow 9000/tcp # Nym Specific - clients port
ufw allow 9001/tcp # Nym specific - wss port
ufw allow 51822/udp # WireGuard
ufw allow 'Nginx Full' && \
ufw allow in on nymwg to any port 51830 proto tcp # bandwidth queries/topup - inside the tunnel
ufw reload && \
ufw status
@@ -47,7 +47,17 @@ NYM_ETC_BRIDGES="$NYM_ETC_DIR/bridges.toml"
NYM_ETC_CLIENT_PARAMS_DEFAULT="$NYM_ETC_DIR/client_bridge_params.json"
SERVICE_FILE="/etc/systemd/system/nym-bridge.service"
NET_DEV="$(ip route show default 2>/dev/null | awk '/default/ {print $5}' || true)"
NET_DEV="${UPLINK_DEV:-}"
if [[ -z "$NET_DEV" ]]; then
NET_DEV="$(ip -o route show default 2>/dev/null | awk '{print $5}' | head -n1)"
[[ -z "$NET_DEV" ]] && NET_DEV="$(ip -o route show default table all 2>/dev/null | awk '{print $5}' | head -n1)"
fi
if [[ -z "$NET_DEV" ]]; then
echo -e "${RED}Cannot determine uplink interface. Set UPLINK_DEV.${RESET}" | tee -a "$LOG_FILE"
exit 1
fi
echo "Using uplink device: $NET_DEV"
WG_IFACE="nymwg"
# Root check
@@ -65,6 +75,17 @@ err() { echo -e "${RED}$1${RESET}"; }
info() { echo -e "${CYAN}$1${RESET}"; }
press_enter() { read -r -p "$1"; }
# Disable pauses and interactive prompts for noninteractive mode
if [[ "${NONINTERACTIVE:-0}" == "1" ]]; then
# all pauses become no-ops
press_enter() { :; }
# silence any "enter path:" prompts
echo_prompt() { :; }
else
echo_prompt() { echo -n "$1"; }
fi
# Helper: detect dpkg dependency failure for libc6>=2.34
deb_depends_libc_too_old() {
local v
@@ -176,13 +197,31 @@ verify_bridge_prerequisites() {
}
adjust_ip_forwarding() {
title "Adjusting IP Forwarding"
sed -i '/^net\.ipv4\.ip_forward=/d' /etc/sysctl.conf
sed -i '/^net\.ipv6\.conf\.all\.forwarding=/d' /etc/sysctl.conf
echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
echo "net.ipv6.conf.all.forwarding=1" >> /etc/sysctl.conf
sysctl -p /etc/sysctl.conf
ok "IPv4/IPv6 forwarding enabled."
title "Checking IP forwarding"
local v4 v6
v4="$(cat /proc/sys/net/ipv4/ip_forward 2>/dev/null || echo 0)"
v6="$(cat /proc/sys/net/ipv6/conf/all/forwarding 2>/dev/null || echo 0)"
if [[ "$v4" == "1" ]]; then
ok "IPv4 forwarding is enabled."
else
warn "IPv4 forwarding is not enabled."
fi
if [[ "$v6" == "1" ]]; then
ok "IPv6 forwarding is enabled."
else
warn "IPv6 forwarding is not enabled."
fi
if [[ "$v4" != "1" || "$v6" != "1" ]]; then
echo
echo "To enable forwarding and routing consistently, run the network tunnel manager script as root."
echo "For example:"
echo " ./network-tunnel-manager.sh complete_networking_configuration"
echo "or:"
echo " ./network-tunnel-manager.sh adjust_ip_forwarding"
fi
}
# Install nym-bridge
@@ -377,6 +416,11 @@ User=root
ExecStart=$BRIDGE_BIN --config $NYM_ETC_BRIDGES
Restart=on-failure
RestartSec=5
LimitNOFILE=65535
ProtectSystem=full
ProtectHome=yes
PrivateTmp=yes
[Install]
WantedBy=multi-user.target
@@ -390,21 +434,40 @@ EOF
# IPTABLES & helpers
apply_bridge_iptables_rules() {
title "Applying iptables rules"
iptables -I INPUT -i "$WG_IFACE" -j ACCEPT || true
ip6tables -I INPUT -i "$WG_IFACE" -j ACCEPT || true
iptables -t nat -A POSTROUTING -o "$NET_DEV" -j MASQUERADE || true
ip6tables -t nat -A POSTROUTING -o "$NET_DEV" -j MASQUERADE || true
iptables-save > /etc/iptables/rules.v4
ip6tables-save > /etc/iptables/rules.v6
ok "iptables rules applied."
title "Checking iptables rules for bridge routing"
echo "Inspecting current iptables state for interface ${WG_IFACE} and uplink ${NET_DEV}."
echo
echo "IPv4 FORWARD:"
iptables -L FORWARD -n -v 2>/dev/null | sed -n '1,20p' || echo "iptables not available."
echo
echo "IPv4 NAT POSTROUTING:"
iptables -t nat -L POSTROUTING -n -v 2>/dev/null | sed -n '1,20p' || true
echo
echo "IPv6 FORWARD:"
ip6tables -L FORWARD -n -v 2>/dev/null | sed -n '1,20p' || true
echo
echo "IPv6 NAT POSTROUTING:"
ip6tables -t nat -L POSTROUTING -n -v 2>/dev/null | sed -n '1,20p' || true
echo
echo "This script no longer changes iptables. To configure routing and NAT for nym, use the network tunnel manager script."
echo "For example (run as root):"
echo " ./network-tunnel-manager.sh complete_networking_configuration"
}
configure_dns_and_icmp() {
title "Allow ICMP and DNS"
iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT || true
ip6tables -A INPUT -p ipv6-icmp -j ACCEPT || true
ok "ICMP and DNS rules applied."
title "Checking ICMP and DNS firewall rules"
echo "IPv4 INPUT rules related to ICMP and DNS:"
iptables -L INPUT -n -v 2>/dev/null | grep -E 'icmp|dpt:53' || echo "no matching IPv4 rules shown."
echo
echo "IPv6 INPUT rules related to ICMP and DNS:"
ip6tables -L INPUT -n -v 2>/dev/null | grep -E 'icmp|dpt:53' || echo "no matching IPv6 rules shown."
echo
echo "If ping or DNS are blocked for bridge traffic, adjust your firewall using the network tunnel manager script or your chosen firewall tool."
}
# Full interactive setup
@@ -429,6 +492,8 @@ full_bridge_setup() {
echo ""
echo "Step 2/6: Installing bridge binary..."
install_bridge_binary
echo "[Bridge Install] $(date '+%F %T') $( $BRIDGE_BIN --version 2>/dev/null || echo 'nym-bridge (unknown)')" \
>> /var/log/nym-bridge-version.log
press_enter "Press Enter to continue..."
echo ""
@@ -447,7 +512,7 @@ full_bridge_setup() {
press_enter "Press Enter to continue..."
echo ""
echo "Step 6/6: Configuring network rules (optional but recommended)..."
echo "Step 6/6: Checking network rules and forwarding status..."
adjust_ip_forwarding
apply_bridge_iptables_rules
configure_dns_and_icmp
-10
View File
@@ -39,16 +39,6 @@ while true; do
esac
done
# try to get the latest binary URL (non-fatal if missing)
LATEST_BINARY=$(
curl -fsSL https://github.com/nymtech/nym/releases/latest \
| grep -Eo 'href="/nymtech/nym/releases/download/[^"]+/nym-node"' \
| head -n1 \
| cut -d'"' -f2
)
if [[ -z "${LATEST_BINARY:-}" ]]; then
echo "WARNING: Could not determine latest nym-node binary URL right now. The installer will resolve it later."
fi
PUBLIC_IP=$(curl -fsS -4 https://ifconfig.me || true)
PUBLIC_IP=${PUBLIC_IP:-""}
+111 -276
View File
@@ -1,315 +1,136 @@
#!/usr/bin/env bash
set -euo pipefail
# load env (prefer absolute ENV_FILE injected by Python CLI; fallback to ./env.sh)
if [[ "$(id -u)" -ne 0 ]]; then
echo "This script must be run as root."
exit 1
fi
# load env
if [[ -n "${ENV_FILE:-}" && -f "${ENV_FILE}" ]]; then
set -a; . "${ENV_FILE}"; set +a
elif [[ -f "./env.sh" ]]; then
set -a; . ./env.sh; set +a
fi
: "${HOSTNAME:?HOSTNAME not set in env.sh}"
: "${EMAIL:?EMAIL not set in env.sh}"
: "${HOSTNAME:?HOSTNAME not set}"
: "${EMAIL:?EMAIL not set}"
export SYSTEMD_PAGER=""
export SYSTEMD_COLORS="0"
DEBIAN_FRONTEND=noninteractive
export DEBIAN_FRONTEND=noninteractive
# sanity check
if [[ "${HOSTNAME}" == "localhost" || "${HOSTNAME}" == "127.0.0.1" || "${HOSTNAME}" == "ubuntu" ]]; then
echo "ERROR: HOSTNAME cannot be 'localhost'. Use a public FQDN." >&2
exit 1
fi
echo -e "\n* * * Starting nginx configuration for landing page, reverse proxy and WSS * * *"
# set paths & ports vars
WEBROOT="/var/www/${HOSTNAME}"
LE_ACME_DIR="/var/www/letsencrypt"
SITES_AVAIL="/etc/nginx/sites-available"
SITES_EN="/etc/nginx/sites-enabled"
BASE_HTTP="${SITES_AVAIL}/${HOSTNAME}" # :80 vhost
BASE_HTTPS="${SITES_AVAIL}/${HOSTNAME}-ssl" # :443 vhost (well write it ourselves)
WSS_AVAIL="${SITES_AVAIL}/wss-config-nym"
BACKUP_DIR="/etc/nginx/sites-backups"
NYM_PORT_HTTP="${NYM_PORT_HTTP:-8080}"
NYM_PORT_WSS="${NYM_PORT_WSS:-9000}"
WSS_LISTEN_PORT="${WSS_LISTEN_PORT:-9001}"
HTTP_CONF="${SITES_AVAIL}/${HOSTNAME}"
WSS_CONF="${SITES_AVAIL}/wss-config-nym"
mkdir -p "${WEBROOT}" "${LE_ACME_DIR}" "${BACKUP_DIR}" "${SITES_AVAIL}" "${SITES_EN}"
echo
echo "* * * Starting nginx configuration for landing page, reverse proxy and WSS * * *"
# helpers
neat_backup() {
local file="$1"; [[ -f "$file" ]] || return 0
local sha_now; sha_now="$(sha256sum "$file" | awk '{print $1}')" || return 0
local tag; tag="$(basename "$file")"
local latest="${BACKUP_DIR}/${tag}.latest"
if [[ -f "$latest" ]]; then
local sha_prev; sha_prev="$(awk '{print $1}' "$latest")"
[[ "$sha_now" == "$sha_prev" ]] && return 0
fi
cp -a "$file" "${BACKUP_DIR}/${tag}.bak.$(date +%s)"
echo "$sha_now ${tag}" > "$latest"
ls -1t "${BACKUP_DIR}/${tag}.bak."* 2>/dev/null | tail -n +6 | xargs -r rm -f
}
###############################################################################
# step 1: ensure landing page exists (local fetch -> github -> template)
###############################################################################
ensure_enabled() {
local src="$1"; local name; name="$(basename "$src")"
ln -sf "$src" "${SITES_EN}/${name}"
}
mkdir -p "${WEBROOT}"
cert_ok() {
[[ -s "/etc/letsencrypt/live/${HOSTNAME}/fullchain.pem" && -s "/etc/letsencrypt/live/${HOSTNAME}/privkey.pem" ]]
}
SCRIPT_DIR="$(dirname "${ENV_FILE:-./env.sh}")"
LOCAL_FETCHED_PAGE="${SCRIPT_DIR}/landing-page.html"
fetch_landing_html() {
local url="https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/nym-node-setup/landing-page.html"
mkdir -p "${WEBROOT}"
if command -v curl >/dev/null 2>&1; then
curl -fsSL "$url" -o "${WEBROOT}/index.html" || true
else
wget -qO "${WEBROOT}/index.html" "$url" || true
fi
if [[ ! -s "${WEBROOT}/index.html" ]]; then
cat > "${WEBROOT}/index.html" <<'HTML'
if [[ -s "${LOCAL_FETCHED_PAGE}" ]]; then
cp "${LOCAL_FETCHED_PAGE}" "${WEBROOT}/index.html"
elif curl -fsSL \
https://raw.githubusercontent.com/nymtech/nym/develop/scripts/nym-node-setup/landing-page.html \
-o "${WEBROOT}/index.html"; then
:
else
cat > "${WEBROOT}/index.html" <<EOF
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Nym Exit Gateway</title>
<style>
body {
font-family: sans-serif;
text-align: center;
padding: 2em;
background-color: #111;
color: #0ff;
}
h1 {
margin-bottom: 0.5em;
}
</style>
</head>
<body>
<h1>Nym Exit Gateway</h1>
<p>This is a Nym Exit Gateway. The operator of this router has no access to any of the data routing through that due to encryption design.</p>
<html>
<head><title>nym node</title></head>
<body style="font-family:sans-serif;text-align:center;padding:2em;">
<h1>nym exit gateway</h1>
<p>this is a nym exit gateway.</p>
<p>Operator contact: <a href="mailto:${EMAIL}">${EMAIL}</a></p>
</body>
</html>
HTML
fi
}
EOF
fi
inject_email() {
local file="${WEBROOT}/index.html"
[[ -n "${EMAIL:-}" && -s "$file" ]] || return 0
# Escape characters that would break sed replacement
local esc_email
esc_email="$(printf '%s' "$EMAIL" | sed -e 's/[&/\]/\\&/g')"
# try to update existing meta (case-insensitive on the name attr)
if grep -qiE '<meta[^>]+name=["'"'"']contact:email["'"'"']' "$file"; then
sed -i -E \
"s|(<meta[^>]+name=[\"']contact:email[\"'][^>]*content=\")[^\"]*(\"[^>]*>)|\1${esc_email}\2|I" \
"$file" || true
return 0
fi
# insert before </head> if present (case-insensitive)
if grep -qi '</head>' "$file"; then
awk -v email="$EMAIL" '
BEGIN{IGNORECASE=1}
/<\/head>/ && !done {
print " <meta name=\"contact:email\" content=\"" email "\">"
done=1
}
{ print }
' "$file" > "${file}.tmp" && mv "${file}.tmp" "$file"
return 0
fi
# fallback: append at end
printf '\n<meta name="contact:email" content="%s">\n' "$EMAIL" >> "$file" || true
}
fetch_logo() {
local logo_url="https://raw.githubusercontent.com/nymtech/websites/refs/heads/main/www/nym.com/public/images/Nym_meta_Image.png?token=GHSAT0AAAAAACEERII7URYRTFACZ4F2OWZ42GMCPBQ"
mkdir -p "${WEBROOT}/images"
if [[ ! -s "${WEBROOT}/images/nym_logo.png" ]]; then
if command -v curl >/dev/null 2>&1; then
curl -fsSL "$logo_url" -o "${WEBROOT}/images/nym_logo.png" || true
else
wget -qO "${WEBROOT}/images/nym_logo.png" "$logo_url" || true
fi
fi
}
reload_nginx() { nginx -t && systemctl reload nginx; }
# landing page (idempotent)
fetch_landing_html
inject_email
fetch_logo
echo "Landing page at ${WEBROOT}/index.html"
# disable default and stale SSL configs
###############################################################################
# step 2: remove default site and old configs, restart nginx
###############################################################################
echo "Cleaning existing nginx configuration"
# remove default nginx site
[[ -L "${SITES_EN}/default" ]] && unlink "${SITES_EN}/default" || true
for f in "${SITES_EN}"/*; do
[[ -L "$f" ]] || continue
if grep -q "/etc/letsencrypt/live/localhost" "$f"; then
echo "Disabling vhost referencing localhost cert: $f"; unlink "$f"
fi
done
for f in "${SITES_EN}"/*; do
[[ -L "$f" ]] || continue
if grep -qE 'listen\s+.*443' "$f"; then
cert=$(awk '/ssl_certificate[ \t]+/ {print $2}' "$f" | tr -d ';' | head -n1)
key=$(awk '/ssl_certificate_key[ \t]+/ {print $2}' "$f" | tr -d ';' | head -n1)
if [[ -n "${cert:-}" && ! -s "$cert" ]] || [[ -n "${key:-}" && ! -s "$key" ]]; then
echo "Disabling SSL vhost with missing cert/key: $f"; unlink "$f"
fi
fi
done
# HTTP :80 vhost (ACME-safe, proxy to :8080)
neat_backup "${BASE_HTTP}"
cat > "${BASE_HTTP}" <<EOF
# optional: remove default available config if present
rm -f /etc/nginx/sites-available/default || true
# remove old vhosts for this domain
rm -f "${SITES_EN}/${HOSTNAME}" || true
rm -f "${SITES_EN}/${HOSTNAME}-ssl" || true
rm -f "${SITES_EN}/wss-config-nym" || true
rm -f "${HTTP_CONF}" || true
rm -f "${WSS_CONF}" || true
systemctl restart nginx || systemctl start nginx
###############################################################################
# step 3: create basic HTTP config like manual flow (80 -> 8080)
###############################################################################
cat > "${HTTP_CONF}" <<EOF
server {
listen 80;
listen [::]:80;
server_name ${HOSTNAME};
# ACME challenge path (HTTP only)
location ^~ /.well-known/acme-challenge/ {
root ${LE_ACME_DIR};
default_type "text/plain";
}
root ${WEBROOT};
index index.html;
location = /favicon.ico { return 204; access_log off; log_not_found off; }
location / {
try_files \$uri \$uri/ @app;
}
location @app {
proxy_pass http://127.0.0.1:${NYM_PORT_HTTP};
proxy_pass http://127.0.0.1:8080;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header Host \$host;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
}
}
EOF
ensure_enabled "${BASE_HTTP}"
reload_nginx
systemctl status nginx --no-pager | sed -n '1,6p' || true
# ACME preflight (informative)
echo -e "\n* * * ACME preflight checks * * *"
if ! curl -fsSL https://acme-v02.api.letsencrypt.org/directory >/dev/null; then
echo "WARNING: Can't reach Let's Encrypt directory. We'll still keep HTTP up." >&2
fi
THIS_IP="$(curl -fsS -4 https://ifconfig.me || true)"
DNS_IP="$(getent ahostsv4 "${HOSTNAME}" 2>/dev/null | awk '{print $1; exit}')"
echo "Public IPv4: ${THIS_IP:-unknown} DNS A(${HOSTNAME}): ${DNS_IP:-unresolved}"
if [[ -n "${THIS_IP:-}" && -n "${DNS_IP:-}" && "${THIS_IP}" != "${DNS_IP}" ]]; then
echo "WARNING: DNS for ${HOSTNAME} does not match this server's public IPv4."
fi
timedatectl show -p NTPSynchronized --value 2>/dev/null | grep -qi yes || timedatectl set-ntp true || true
ln -sf "${HTTP_CONF}" "${SITES_EN}/${HOSTNAME}"
# install certbot if missing
if ! command -v certbot >/dev/null 2>&1; then
if command -v snap >/dev/null 2>&1; then
snap install core || true; snap refresh core || true
snap install --classic certbot; ln -sf /snap/bin/certbot /usr/bin/certbot
else
apt-get update -y >/dev/null 2>&1 || true
apt-get install -y certbot >/dev/null 2>&1 || true
fi
fi
nginx -t
systemctl daemon-reload
systemctl restart nginx
# issue/renew via WEBROOT (no nginx auto-edit), non-fatal if it fails
STAGING_FLAG=""; [[ "${CERTBOT_STAGING:-0}" == "1" ]] && STAGING_FLAG="--staging" && echo "Using Let's Encrypt STAGING."
if ! cert_ok; then
certbot certonly --non-interactive --agree-tos -m "${EMAIL}" -d "${HOSTNAME}" \
--webroot -w "${LE_ACME_DIR}" ${STAGING_FLAG} || true
fi
###############################################################################
# step 4: install certbot and obtain certificate (letsencrypt)
###############################################################################
# create own 443 vhost (only if certs exist)
if cert_ok; then
neat_backup "${BASE_HTTPS}"
cat > "${BASE_HTTPS}" <<EOF
apt-get update -y >/dev/null 2>&1 || true
apt-get install -y certbot python3-certbot-nginx >/dev/null 2>&1 || true
echo "Requesting Let's Encrypt certificate for ${HOSTNAME}"
certbot --nginx --non-interactive --agree-tos --redirect --reuse-key \
-m "${EMAIL}" -d "${HOSTNAME}" || true
###############################################################################
# step 5: create WSS 9001 config using certbot-generated certs
###############################################################################
if [[ -s "/etc/letsencrypt/live/${HOSTNAME}/fullchain.pem" ]]; then
echo "Certificate detected, creating WSS config"
cat > "${WSS_CONF}" <<EOF
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name ${HOSTNAME};
listen 9001 ssl http2;
listen [::]:9001 ssl http2;
ssl_certificate /etc/letsencrypt/live/${HOSTNAME}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/${HOSTNAME}/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
root ${WEBROOT};
index index.html;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
location = /favicon.ico { return 204; access_log off; log_not_found off; }
location / {
try_files \$uri \$uri/ @app;
}
location @app {
proxy_pass http://127.0.0.1:${NYM_PORT_HTTP};
proxy_http_version 1.1;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header Host \$host;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
}
}
EOF
ensure_enabled "${BASE_HTTPS}"
# optional: redirect HTTP->HTTPS (keeps ACME path in HTTP too via separate small server)
neat_backup "${BASE_HTTP}"
cat > "${BASE_HTTP}" <<EOF
server {
listen 80;
listen [::]:80;
server_name ${HOSTNAME};
# Keep ACME reachable over HTTP:
location ^~ /.well-known/acme-challenge/ {
root ${LE_ACME_DIR};
default_type "text/plain";
}
# Redirect the rest to HTTPS
location / {
return 301 https://\$host\$request_uri;
}
}
EOF
ensure_enabled "${BASE_HTTP}"
reload_nginx
else
echo "NOTE: Cert not present yet; HTTPS (443) will not listen. Only HTTP (80) is active."
fi
# WSS TLS :9001 (only if certs exist)
if cert_ok; then
neat_backup "${WSS_AVAIL}"
cat > "${WSS_AVAIL}" <<EOF
server {
listen ${WSS_LISTEN_PORT} ssl http2;
listen [::]:${WSS_LISTEN_PORT} ssl http2;
server_name ${HOSTNAME};
ssl_certificate /etc/letsencrypt/live/${HOSTNAME}/fullchain.pem;
@@ -320,7 +141,11 @@ server {
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
location = /favicon.ico { return 204; access_log off; log_not_found off; }
location /favicon.ico {
return 204;
access_log off;
log_not_found off;
}
location / {
add_header 'Access-Control-Allow-Origin' '*' always;
@@ -333,20 +158,30 @@ server {
proxy_set_header Connection "Upgrade";
proxy_set_header X-Forwarded-For \$remote_addr;
proxy_pass http://127.0.0.1:${NYM_PORT_WSS};
proxy_pass http://localhost:9000;
proxy_intercept_errors on;
}
}
EOF
ensure_enabled "${WSS_AVAIL}"
reload_nginx
ln -sf "${WSS_CONF}" "${SITES_EN}/wss-config-nym"
nginx -t
systemctl daemon-reload
systemctl restart nginx
else
echo "Certificate missing, skipping WSS config"
fi
echo -e "\nDone."
if cert_ok; then
echo "HTTP : http://${HOSTNAME}/ (redirects to HTTPS)"
echo "TLS : https://${HOSTNAME}/ (served by nginx)"
echo "WSS : wss://${HOSTNAME}:${WSS_LISTEN_PORT}/ (served by nginx)"
###############################################################################
# step 6: summary
###############################################################################
echo "done."
echo "http : http://${HOSTNAME}"
if [[ -s "/etc/letsencrypt/live/${HOSTNAME}/fullchain.pem" ]]; then
echo "https : https://${HOSTNAME}"
echo "wss : wss://${HOSTNAME}:9001"
else
echo "Only HTTP is active (no cert yet). Re-run after DNS/ACME is ready to enable HTTPS + WSS."
echo "https not active yet (no cert)"
fi
@@ -83,8 +83,8 @@ fi
# interactive path (manual runs)
ensure_mode
read -rp "Service file not found. Create it now? (y/n): " create_ans
if [[ "${create_ans:-}" =~ ^[Yy]$ ]]; then
read -rp "Service file not found. Create it now? (Y/n): " create_ans
if [[ -z "${create_ans}" || "${create_ans}" =~ ^[Yy]$ ]]; then
create_service_file
else
echo "Not creating the service file."
@@ -1,240 +0,0 @@
#!/bin/bash
# Nym Exit Policy Verification Unit Tests
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[0;33m'
NC='\033[0m'
NYM_CHAIN="NYM-EXIT"
WG_INTERFACE="nymwg"
check_port_range_rules() {
local port_range="$1"
local protocol="${2:-tcp}"
local chain="${3:-$NYM_CHAIN}"
# Extract start and end ports
local start_port=$(echo "$port_range" | cut -d'-' -f1)
local end_port=$(echo "$port_range" | cut -d'-' -f2)
if iptables -t filter -C "$chain" -p "$protocol" --dport "$start_port:$end_port" -j ACCEPT 2>/dev/null; then
echo -e "${GREEN}✓ Rule exists: $chain $protocol port range $start_port:$end_port${NC}"
return 0
else
echo -e "${RED}✗ Rule missing: $chain $protocol port range $start_port:$end_port${NC}"
echo -e "${YELLOW}Dumping all rules in $chain:${NC}"
iptables -L "$chain" -n | grep "$protocol"
return 1
fi
}
# Test port range rules
test_port_range_rules() {
echo -e "${YELLOW}Testing Port Range Rules...${NC}"
# Select the essential port ranges for testing
local port_ranges=(
"20-21:tcp:FTP"
"80-81:tcp:HTTP"
"2082-2083:tcp:CPanel"
"5222-5223:tcp:XMPP"
"27000-27050:tcp:Steam (sampling)"
"989-990:tcp:FTP over TLS"
"5000-5005:tcp:RTP/VoIP"
"8087-8088:tcp:Simplify Media"
"8232-8233:tcp:Zcash"
"8332-8333:tcp:Bitcoin"
"18080-18081:tcp:Monero"
)
local total_failures=0
for range in "${port_ranges[@]}"; do
IFS=':' read -r port_range protocol service <<< "$range"
# Extract start and end ports
local start_port=$(echo "$port_range" | cut -d'-' -f1)
local end_port=$(echo "$port_range" | cut -d'-' -f2)
echo -e "${YELLOW}Testing $service $protocol port range $port_range${NC}"
if iptables -t filter -C "$NYM_CHAIN" -p "$protocol" --dport "$start_port:$end_port" -j ACCEPT 2>/dev/null; then
echo -e "${GREEN}✓ Rule exists: $NYM_CHAIN $protocol port range $start_port:$end_port${NC}"
else
echo -e "${RED}✗ Rule missing: $NYM_CHAIN $protocol port range $start_port:$end_port${NC}"
((total_failures++))
echo -e "${YELLOW}Existing rules for protocol $protocol:${NC}"
iptables -L "$NYM_CHAIN" -n | grep "$protocol"
fi
done
if [ $total_failures -eq 0 ]; then
return 0
else
return 1
fi
}
test_critical_services() {
echo -e "${YELLOW}Testing Critical Service Rules...${NC}"
local tcp_services=(
22 # SSH
53 # DNS
443 # HTTPS
853 # DNS over TLS
1194 # OpenVPN
)
local udp_services=(
53 # DNS
123 # NTP
1194 # OpenVPN
)
local failures=0
# Test TCP services
for port in "${tcp_services[@]}"; do
local rule_found=false
# First check for exact match
if iptables -t filter -C "$NYM_CHAIN" -p tcp --dport "$port" -j ACCEPT 2>/dev/null; then
echo -e "${GREEN}✓ Rule exists: NYM-EXIT tcp port $port${NC}"
rule_found=true
else
# If not found as exact port, search for it in port ranges
# This checks if the port is covered by any range rule
if iptables-save | grep -E "^-A $NYM_CHAIN.*tcp.*dpts:" | grep -qP "dpts:(\d+:)?$port(:|\d+)" || \
iptables-save | grep -E "^-A $NYM_CHAIN.*tcp.*dpts:" | grep -qP "dpts:$port:"; then
echo -e "${GREEN}✓ Rule exists: NYM-EXIT tcp port $port (covered by a range rule)${NC}"
rule_found=true
else
echo -e "${RED}✗ Rule missing: NYM-EXIT tcp port $port${NC}"
((failures++))
fi
fi
done
# Test UDP services - similar approach
for port in "${udp_services[@]}"; do
local rule_found=false
if iptables -t filter -C "$NYM_CHAIN" -p udp --dport "$port" -j ACCEPT 2>/dev/null; then
echo -e "${GREEN}✓ Rule exists: NYM-EXIT udp port $port${NC}"
rule_found=true
else
# If not found as exact port, search for it in port ranges
if iptables-save | grep -E "^-A $NYM_CHAIN.*udp.*dpts:" | grep -qP "dpts:(\d+:)?$port(:|\d+)" || \
iptables-save | grep -E "^-A $NYM_CHAIN.*udp.*dpts:" | grep -qP "dpts:$port:"; then
echo -e "${GREEN}✓ Rule exists: NYM-EXIT udp port $port (covered by a range rule)${NC}"
rule_found=true
else
echo -e "${RED}✗ Rule missing: NYM-EXIT udp port $port${NC}"
((failures++))
fi
fi
done
echo -e "${YELLOW}Relevant existing rules for HTTP (port 80):${NC}"
iptables-save | grep -E "$NYM_CHAIN.*tcp" | grep -E "(dpt|dpts):.*80"
return $failures
}
# Verify default reject rule exists
test_default_reject_rule() {
echo -e "${YELLOW}This test takes some time, do not quit the process${NC}"
echo
echo -e "${YELLOW}Testing Default Reject Rule...${NC}"
# Try different patterns to detect the reject rule
if iptables -L "$NYM_CHAIN" | grep -q "REJECT.*all.*anywhere.*anywhere.*reject-with"; then
echo -e "${GREEN}✓ Default REJECT rule exists${NC}"
return 0
elif iptables -L "$NYM_CHAIN" | grep -q "REJECT.*all -- .*everywhere.*everywhere"; then
echo -e "${GREEN}✓ Default REJECT rule exists${NC}"
return 0
elif iptables -L "$NYM_CHAIN" | grep -q "REJECT.*all.*0.0.0.0/0.*0.0.0.0/0"; then
echo -e "${GREEN}✓ Default REJECT rule exists${NC}"
return 0
elif iptables -n -L "$NYM_CHAIN" | grep -qE "REJECT.*all.*0.0.0.0/0.*0.0.0.0/0"; then
echo -e "${GREEN}✓ Default REJECT rule exists${NC}"
return 0
elif iptables -L "$NYM_CHAIN" | tail -1 | grep -q "REJECT"; then
echo -e "${GREEN}✓ Default REJECT rule exists at the end of chain${NC}"
return 0
else
echo -e "${RED}✗ Default REJECT rule missing${NC}"
# Display the last 3 rules in the chain for debugging
echo -e "${YELLOW}Last 3 rules in the chain:${NC}"
iptables -L "$NYM_CHAIN" | tail -3
return 1
fi
}
run_all_tests() {
local total_failures=0
local total_tests=0
local skip_default_reject=false
# Parse arguments
while [[ $# -gt 0 ]]; do
case "$1" in
--skip-default-reject)
skip_default_reject=true
shift
;;
*)
echo -e "${RED}Unknown argument: $1${NC}"
exit 1
;;
esac
done
local test_functions=(
"test_port_range_rules"
"test_critical_services"
)
if [ "$skip_default_reject" = false ]; then
test_functions+=("test_default_reject_rule")
fi
echo -e "${YELLOW}Running Nym Exit Policy Verification Tests...${NC}"
for test_func in "${test_functions[@]}"; do
((total_tests++))
$test_func
if [ $? -ne 0 ]; then
((total_failures++))
echo -e "${RED}Test $test_func FAILED${NC}"
else
echo -e "${GREEN}Test $test_func PASSED${NC}"
fi
done
echo -e "\n${YELLOW}Test Summary:${NC}"
echo -e "Total Tests: $total_tests"
echo -e "Failures: $total_failures"
if [ $total_failures -eq 0 ]; then
echo -e "${GREEN}All Tests Passed Successfully!${NC}"
exit 0
else
echo -e "${RED}Some Tests Failed. Please review the iptables configuration.${NC}"
exit 1
fi
}
if [[ $EUID -ne 0 ]]; then
echo -e "${RED}This script must be run as root${NC}"
exit 1
fi
# Run the tests
run_all_tests "$@"
@@ -0,0 +1,43 @@
#!/bin/bash
# Fix the FORWARD chain order so NYM-EXIT comes before ACCEPT
WG_INTERFACE="nymwg"
NETWORK_DEVICE=$(ip route show default | awk '/default/ {print $5}')
NYM_CHAIN="NYM-EXIT"
echo "Fixing FORWARD chain order..."
# Remove ALL ACCEPT rules for nymwg → network_device (there might be duplicates)
while iptables -D FORWARD -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j ACCEPT 2>/dev/null; do
echo "Removed ACCEPT rule"
done
# Remove NYM-EXIT hook if it exists (we'll re-add it)
iptables -D FORWARD -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN" 2>/dev/null || true
# Insert NYM-EXIT at position 1
iptables -I FORWARD 1 -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN"
echo "Added NYM-EXIT hook at position 1"
# Insert ACCEPT right after NYM-EXIT (position 2)
iptables -I FORWARD 2 -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j ACCEPT
echo "Added ACCEPT rule at position 2 (after NYM-EXIT)"
# Same for IPv6
while ip6tables -D FORWARD -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j ACCEPT 2>/dev/null; do
echo "Removed IPv6 ACCEPT rule"
done
ip6tables -D FORWARD -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN" 2>/dev/null || true
ip6tables -I FORWARD 1 -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN"
ip6tables -I FORWARD 2 -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j ACCEPT
echo "Fixed IPv6 rules"
echo ""
echo "=== New FORWARD chain order ==="
iptables -L FORWARD -n --line-numbers | head -10
echo ""
echo "=== Verify NYM-EXIT is before ACCEPT ==="
iptables -L FORWARD -n --line-numbers | grep -E "$WG_INTERFACE|$NYM_CHAIN" | head -5
@@ -0,0 +1,26 @@
#!/bin/bash
# Remove exit policy rules (use this if accidentally applied to entry gateway)
WG_INTERFACE="nymwg"
NETWORK_DEVICE=$(ip route show default | awk '/default/ {print $5}')
NYM_CHAIN="NYM-EXIT"
echo "Removing exit policy rules..."
# Remove NYM-EXIT hooks from FORWARD chain
iptables -D FORWARD -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN" 2>/dev/null || true
iptables -D FORWARD -o "$WG_INTERFACE" -j "$NYM_CHAIN" 2>/dev/null || true
ip6tables -D FORWARD -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN" 2>/dev/null || true
ip6tables -D FORWARD -o "$WG_INTERFACE" -j "$NYM_CHAIN" 2>/dev/null || true
# Flush and delete NYM-EXIT chains
iptables -F "$NYM_CHAIN" 2>/dev/null || true
iptables -X "$NYM_CHAIN" 2>/dev/null || true
ip6tables -F "$NYM_CHAIN" 2>/dev/null || true
ip6tables -X "$NYM_CHAIN" 2>/dev/null || true
echo "✓ Exit policy rules removed"
echo ""
echo "=== Current FORWARD chain (first 10 rules) ==="
iptables -L FORWARD -n --line-numbers | head -10
@@ -1,40 +0,0 @@
#!/bin/bash
validate_exit_policy() {
echo "=== Nym Exit Policy Blocking Validation ==="
# Check iptables rules
echo "Checking iptables NYM-EXIT chain:"
sudo iptables -L NYM-EXIT -v -n
# Test IP ranges and individual IPs
test_ips=(
"5.188.10.0/24" # Blocked network range
"31.132.36.50" # Specific blocked IP
"37.9.42.100" # Another blocked IP
)
for target in "${test_ips[@]}"; do
echo -e "\n\e[33mTesting blocking for $target\e[0m"
# Multiple connection test methods
methods=(
"ping -c 4 -W 2"
"curl -m 5 http://$target"
"nc -z -w 5 $target 80"
"telnet $target 80"
)
for method in "${methods[@]}"; do
echo -n "Testing with $method: "
if sudo timeout 5 $method >/dev/null 2>&1; then
echo -e "\e[31mFAILED: Connection succeeded (Blocking ineffective)\e[0m"
else
echo -e "\e[32mBLOCKED\e[0m"
fi
done
done
}
# Run the test
validate_exit_policy
@@ -11,7 +11,7 @@
# - Groups rules logically for easier management
# - Integrates with existing Nym node configuration
#
# Usage: ./nym-exit-policy.sh [command]
# Usage: ./wireguard-exit-policy-manager.sh [command]
set -e
@@ -38,13 +38,13 @@ add_port_rules() {
local end_port=$(echo "$port" | cut -d'-' -f2)
if ! $chain -C "$NYM_CHAIN" -p "$protocol" --dport "$start_port:$end_port" -j ACCEPT 2>/dev/null; then
$chain -A "$NYM_CHAIN" -p "$protocol" --dport "$start_port:$end_port" -j ACCEPT
$chain -I "$NYM_CHAIN" -p "$protocol" --dport "$start_port:$end_port" -j ACCEPT
echo -e " ${GREEN}Added: $NYM_CHAIN $protocol port range $start_port:$end_port${NC}"
fi
else
# Single port handling
if ! $chain -C "$NYM_CHAIN" -p "$protocol" --dport "$port" -j ACCEPT 2>/dev/null; then
$chain -A "$NYM_CHAIN" -p "$protocol" --dport "$port" -j ACCEPT
$chain -I "$NYM_CHAIN" -p "$protocol" --dport "$port" -j ACCEPT
echo -e " ${GREEN}Added: $NYM_CHAIN $protocol port $port${NC}"
fi
fi
@@ -117,15 +117,15 @@ create_nym_chain() {
fi
# Link it to the FORWARD chain if not already linked
if ! iptables -C FORWARD -o "$WG_INTERFACE" -j "$NYM_CHAIN" 2>/dev/null; then
if ! iptables -C FORWARD -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN" 2>/dev/null; then
echo -e "${YELLOW}Linking $NYM_CHAIN to FORWARD chain...${NC}"
iptables -A FORWARD -o "$WG_INTERFACE" -j "$NYM_CHAIN"
iptables -I FORWARD 1 -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN"
fi
# Link IPv6 chain
if ! ip6tables -C FORWARD -o "$WG_INTERFACE" -j "$NYM_CHAIN" 2>/dev/null; then
if ! ip6tables -C FORWARD -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN" 2>/dev/null; then
echo -e "${YELLOW}Linking $NYM_CHAIN to IPv6 FORWARD chain...${NC}"
ip6tables -A FORWARD -o "$WG_INTERFACE" -j "$NYM_CHAIN"
ip6tables -I FORWARD 1 -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN"
fi
}
@@ -282,6 +282,21 @@ add_default_reject_rule() {
apply_port_allowlist() {
echo -e "${YELLOW}Applying allowed ports...${NC}"
# Insert DNS rules at the very beginning of NYM-EXIT chain, this ensures DNS queries are never blocked by REJECT rules
echo -e "${YELLOW}Ensuring DNS is at the beginning of $NYM_CHAIN...${NC}"
iptables -I "$NYM_CHAIN" 1 -p udp --dport 53 -j ACCEPT
iptables -I "$NYM_CHAIN" 2 -p tcp --dport 53 -j ACCEPT
ip6tables -I "$NYM_CHAIN" 1 -p udp --dport 53 -j ACCEPT
ip6tables -I "$NYM_CHAIN" 2 -p tcp --dport 53 -j ACCEPT
# ICMP is needed because the probe tests DNS by pinging hostnames
iptables -I "$NYM_CHAIN" 3 -p icmp --icmp-type echo-request -j ACCEPT
iptables -I "$NYM_CHAIN" 4 -p icmp --icmp-type echo-reply -j ACCEPT
ip6tables -I "$NYM_CHAIN" 3 -p ipv6-icmp -j ACCEPT
echo -e "${GREEN}✓ DNS and ICMP rules inserted at beginning of $NYM_CHAIN${NC}"
# Dictionary of services and their ports
declare -A PORT_MAPPINGS=(
["FTP"]="20-21"
@@ -313,6 +328,7 @@ apply_port_allowlist() {
["IMAPOverTLS"]="993"
["POP3OverTLS"]="995"
["OpenVPN"]="1194"
["WireGuardPeer"]="51820-51822"
["QTServerAdmin"]="1220"
["PKTKRB"]="1293"
["MSSQL"]="1433"
@@ -357,17 +373,18 @@ apply_port_allowlist() {
["Lightning"]="9735"
["NDMP"]="10000"
["OpenPGP"]="11371"
["Monero"]="18080-18081"
["MoneroRPC"]="18089"
["Monero"]="18080-18081"
["MoneroRPC"]="18089"
["GoogleVoice"]="19294"
["EnsimControlPanel"]="19638"
["DarkFiTor"]="25551"
["DarkFiTor"]="25551"
["Minecraft"]="25565"
["DarkFi"]="26661"
["DarkFi"]="26661"
["Steam"]="27000-27050"
["ElectrumSSL"]="50002"
["MOSH"]="60000-61000"
["Mumble"]="64738"
["Metadata"]="51830"
)
# Add TCP and UDP rules for each allowed port
@@ -382,8 +399,6 @@ apply_port_allowlist() {
add_port_rules ip6tables "$port" "udp"
done
add_default_reject_rule
echo -e "${GREEN}Port allowlist applied successfully.${NC}"
}
@@ -418,7 +433,9 @@ clear_rules() {
# Remove the chain from FORWARD if it exists
iptables -D FORWARD -o "$WG_INTERFACE" -j "$NYM_CHAIN" 2>/dev/null || true
iptables -D FORWARD -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN" 2>/dev/null || true
ip6tables -D FORWARD -o "$WG_INTERFACE" -j "$NYM_CHAIN" 2>/dev/null || true
ip6tables -D FORWARD -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j "$NYM_CHAIN" 2>/dev/null || true
# Delete the chains
iptables -X "$NYM_CHAIN" 2>/dev/null || true
@@ -640,8 +657,11 @@ main() {
create_nym_chain
setup_nat_rules
configure_dns_and_icmp
apply_spamhaus_blocklist
# Apply allowlist first (so DNS and other allowed ports are not blocked)
apply_port_allowlist
# Then apply blocklist (aka the REJECT rules)
apply_spamhaus_blocklist
add_default_reject_rule
save_rules
echo -e "${GREEN}Nym exit policy installed successfully.${NC}"
;;