Compare commits
88 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2e7f1ed0f3 | |||
| c2f588857f | |||
| 45598ce250 | |||
| 4f991061dd | |||
| e5aef76256 | |||
| 6acc54d2bc | |||
| ab6e08dd13 | |||
| e09066858c | |||
| 842ce93a60 | |||
| ce26105986 | |||
| cc95358385 | |||
| cc04a09ed7 | |||
| ae47d53f0c | |||
| e0ff09f323 | |||
| 10707fd2c5 | |||
| 9bdd2af14c | |||
| 228ef8b158 | |||
| d820131d2c | |||
| 054715a600 | |||
| 3f560180b7 | |||
| f62dbbdae0 | |||
| edecc4ba01 | |||
| 58c0e289c2 | |||
| 6d8edc4bc7 | |||
| a44cdf1c7c | |||
| 6b8a6283a4 | |||
| 94151965bb | |||
| e8ca490db1 | |||
| fe7470ea44 | |||
| 21d52244cb | |||
| c0c58026a8 | |||
| 0fe863c889 | |||
| 4e5d88f64c | |||
| 1559f6a912 | |||
| 766024be27 | |||
| 5ba181b118 | |||
| 76fc9f4a90 | |||
| 8ca6af7c86 | |||
| 45e14a7fb1 | |||
| a38917cb9b | |||
| cf8a399089 | |||
| ba01820586 | |||
| 8c799b2976 | |||
| de4fb6291c | |||
| 81fd37e5c0 | |||
| 219f3af967 | |||
| aea7442525 | |||
| 1525aed657 | |||
| 943b5fa8bc | |||
| 71e0c025c6 | |||
| 239c6c701b | |||
| 91d0b7bdad | |||
| 99b28b2b6f | |||
| 5627ada57e | |||
| 4e1228fff0 | |||
| 04be5624fa | |||
| a6fe1b1de7 | |||
| c5971d0e9d | |||
| 06dd74ba34 | |||
| c6a0256b03 | |||
| d04b61a88b | |||
| 70a119ac58 | |||
| e2fe3a60a6 | |||
| 71301ee0cc | |||
| aba6c9d4ac | |||
| c617bbb240 | |||
| 1f8144eb11 | |||
| 694135c81b | |||
| e815f08505 | |||
| f402da8e60 | |||
| 34a500d0a2 | |||
| ef52f25564 | |||
| 010b013094 | |||
| c503a5f0e8 | |||
| 781afd3522 | |||
| 58083df0b0 | |||
| 4e8d29d0c5 | |||
| 66797efa80 | |||
| 5a26fa262e | |||
| 73a34935ae | |||
| a8086675d9 | |||
| 0453345d65 | |||
| b56d9505e6 | |||
| bdacc72003 | |||
| 9eca9efd82 | |||
| cc74d218fc | |||
| dea8a287e6 | |||
| fc5d310935 |
@@ -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
|
||||
```
|
||||
|
||||
@@ -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
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:-""}
|
||||
|
||||
@@ -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 (we’ll 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}"
|
||||
;;
|
||||
|
||||
Reference in New Issue
Block a user