nym-node-setup: plumb HOST_SSH_PORT through tunnel manager, CLI, and env setup (#6633)

* network-tunnel-manager: make SSH port configurable

* Rename SSH_PORT to HOST_SSH_PORT.

* setup: plumb HOST_SSH_PORT through env and CLI

* setup-env-vars: persist HOST_SSH_PORT in env.sh

---------

Co-authored-by: p17o <p17o>
This commit is contained in:
p17o
2026-04-30 09:10:21 +02:00
committed by GitHub
parent cabbeaf1bf
commit 84a4924e77
3 changed files with 104 additions and 28 deletions
@@ -1,5 +1,5 @@
#!/bin/bash
# nym tunnel and wireguard exit policy manager
# nym host network firewall, tunnel and wireguard exit policy manager
# run this script as root
set -euo pipefail
@@ -556,6 +556,34 @@ apply_smtps_465_rate_limit() {
###############################################################################
NETWORK_FIREWALL_COMMENT="NYM-NETWORK-FW"
HOST_SSH_PORT="${HOST_SSH_PORT:-22}"
NETWORK_FIREWALL_TCP_PORTS=("$HOST_SSH_PORT" 80 443 1789 1790 8080 9000 9001 41264)
NETWORK_FIREWALL_UDP_PORTS=(4443 51822 51264)
NETWORK_FIREWALL_WG_TCP_PORT=51830
validate_port_value() {
local name="$1"
local value="$2"
if ! [[ "$value" =~ ^[0-9]+$ ]] || (( 10#$value < 1 || 10#$value > 65535 )); then
error "invalid ${name}='${value}'. expected integer 1-65535"
exit 1
fi
}
validate_port_value "HOST_SSH_PORT" "$HOST_SSH_PORT"
build_network_firewall_status_pattern() {
local patterns=("$NETWORK_FIREWALL_COMMENT")
local port
for port in "${NETWORK_FIREWALL_TCP_PORTS[@]}" "${NETWORK_FIREWALL_UDP_PORTS[@]}" "$NETWORK_FIREWALL_WG_TCP_PORT"; do
patterns+=("dpt:${port}")
done
local IFS='|'
printf '%s' "${patterns[*]}"
}
delete_managed_input_rules() {
local cmd="$1"
@@ -593,22 +621,19 @@ configure_network_firewall() {
delete_managed_input_rules iptables
delete_managed_input_rules ip6tables
local tcp_ports=(22 80 443 1789 1790 8080 9000 9001 41264)
local udp_ports=(4443 51822 51264)
local port
for port in "${tcp_ports[@]}"; do
for port in "${NETWORK_FIREWALL_TCP_PORTS[@]}"; do
add_input_port_rule iptables "$port" tcp
add_input_port_rule ip6tables "$port" tcp
done
for port in "${udp_ports[@]}"; do
for port in "${NETWORK_FIREWALL_UDP_PORTS[@]}"; do
add_input_port_rule iptables "$port" udp
add_input_port_rule ip6tables "$port" udp
done
add_input_port_rule iptables 51830 tcp "$WG_INTERFACE"
add_input_port_rule ip6tables 51830 tcp "$WG_INTERFACE"
add_input_port_rule iptables "$NETWORK_FIREWALL_WG_TCP_PORT" tcp "$WG_INTERFACE"
add_input_port_rule ip6tables "$NETWORK_FIREWALL_WG_TCP_PORT" tcp "$WG_INTERFACE"
save_iptables_rules
ok "host network firewall configuration completed"
@@ -616,23 +641,25 @@ configure_network_firewall() {
show_network_firewall_status() {
info "managed host network firewall rules"
local status_pattern
status_pattern="$(build_network_firewall_status_pattern)"
echo
info "ipv4 input rules:"
iptables -L INPUT -n -v --line-numbers | grep -E "($NETWORK_FIREWALL_COMMENT|dpt:22|dpt:80|dpt:443|dpt:1789|dpt:1790|dpt:8080|dpt:9000|dpt:9001|dpt:51822|dpt:51830|dpt:41264|dpt:51264)" || true
iptables -L INPUT -n -v --line-numbers | grep -E "(${status_pattern})" || true
echo
info "ipv6 input rules:"
ip6tables -L INPUT -n -v --line-numbers | grep -E "($NETWORK_FIREWALL_COMMENT|dpt:22|dpt:80|dpt:443|dpt:1789|dpt:1790|dpt:8080|dpt:9000|dpt:9001|dpt:51822|dpt:51830|dpt:41264|dpt:51264)" || true
ip6tables -L INPUT -n -v --line-numbers | grep -E "(${status_pattern})" || true
}
test_network_firewall_rules() {
info "testing host network firewall rules"
local failures=0
local tcp_ports=(22 80 443 1789 1790 8080 9000 9001 41264)
local udp_ports=(4443 51822 51264)
local port
for port in "${tcp_ports[@]}"; do
for port in "${NETWORK_FIREWALL_TCP_PORTS[@]}"; do
if iptables -C INPUT -p tcp --dport "$port" -m conntrack --ctstate NEW -m comment --comment "$NETWORK_FIREWALL_COMMENT" -j ACCEPT 2>/dev/null; then
ok "ipv4 tcp port $port allowed"
else
@@ -648,7 +675,7 @@ test_network_firewall_rules() {
fi
done
for port in "${udp_ports[@]}"; do
for port in "${NETWORK_FIREWALL_UDP_PORTS[@]}"; do
if iptables -C INPUT -p udp --dport "$port" -m conntrack --ctstate NEW -m comment --comment "$NETWORK_FIREWALL_COMMENT" -j ACCEPT 2>/dev/null; then
ok "ipv4 udp port $port allowed"
else
@@ -664,17 +691,17 @@ test_network_firewall_rules() {
fi
done
if iptables -C INPUT -i "$WG_INTERFACE" -p tcp --dport 51830 -m conntrack --ctstate NEW -m comment --comment "$NETWORK_FIREWALL_COMMENT" -j ACCEPT 2>/dev/null; then
ok "ipv4 tcp port 51830 allowed on $WG_INTERFACE"
if iptables -C INPUT -i "$WG_INTERFACE" -p tcp --dport "$NETWORK_FIREWALL_WG_TCP_PORT" -m conntrack --ctstate NEW -m comment --comment "$NETWORK_FIREWALL_COMMENT" -j ACCEPT 2>/dev/null; then
ok "ipv4 tcp port $NETWORK_FIREWALL_WG_TCP_PORT allowed on $WG_INTERFACE"
else
error "ipv4 tcp port 51830 missing on $WG_INTERFACE"
error "ipv4 tcp port $NETWORK_FIREWALL_WG_TCP_PORT missing on $WG_INTERFACE"
((failures++))
fi
if ip6tables -C INPUT -i "$WG_INTERFACE" -p tcp --dport 51830 -m conntrack --ctstate NEW -m comment --comment "$NETWORK_FIREWALL_COMMENT" -j ACCEPT 2>/dev/null; then
ok "ipv6 tcp port 51830 allowed on $WG_INTERFACE"
if ip6tables -C INPUT -i "$WG_INTERFACE" -p tcp --dport "$NETWORK_FIREWALL_WG_TCP_PORT" -m conntrack --ctstate NEW -m comment --comment "$NETWORK_FIREWALL_COMMENT" -j ACCEPT 2>/dev/null; then
ok "ipv6 tcp port $NETWORK_FIREWALL_WG_TCP_PORT allowed on $WG_INTERFACE"
else
error "ipv6 tcp port 51830 missing on $WG_INTERFACE"
error "ipv6 tcp port $NETWORK_FIREWALL_WG_TCP_PORT missing on $WG_INTERFACE"
((failures++))
fi
@@ -1695,6 +1722,7 @@ environment overrides:
NETWORK_DEVICE_V6 Auto-detected IPv6 uplink (e.g., eth2). Optional; if unset, IPv6-specific setup is skipped.
TUNNEL_INTERFACE Default: nymtun0. Requires root privileges (sudo) to manage.
WG_INTERFACE Default: nymwg - Must match your WireGuard interface name.
HOST_SSH_PORT Default: 22. Set manually if you connect to your host's SSH daemon through another port.
EOF
status=0
+52 -8
View File
@@ -68,23 +68,60 @@ class NodeSetupCLI:
print("Without confirming the points above, we cannot continue.")
exit(1)
def _coerce_ssh_port(self, value) -> str:
sval = str(value).strip() if value is not None else ""
if not sval:
sval = "22"
if not sval.isdigit():
print(f"Invalid SSH port: {sval!r}. Expected integer 1..65535.")
raise SystemExit(1)
port = int(sval, 10)
if not 1 <= port <= 65535:
print(f"Invalid SSH port: {port}. Expected integer 1..65535.")
raise SystemExit(1)
return str(port)
def _resolve_field(args, existing, arg_name, env_key, prompt, *, default=None, validator=None):
cli_val = getattr(args, arg_name, None)
if cli_val is not None:
value = str(cli_val).strip()
elif existing.get(env_key):
value = str(existing[env_key]).strip()
else:
entered = input(prompt).strip()
value = entered if entered else (default if default is not None else "")
if validator:
value = validator(value)
return value
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: "),
("hostname", "HOSTNAME", "Enter hostname (if you don't use a DNS, press enter): ", None, None),
("location", "LOCATION", "Enter node location (country code or name): ", None, None),
("email", "EMAIL", "Enter your email: ", None, None),
("moniker", "MONIKER", "Enter node public moniker (visible in explorer & NymVPN app): ", None, None),
("description", "DESCRIPTION", "Enter short node public description: ", None, None),
("host_ssh_port", "HOST_SSH_PORT", "Enter host SSH port (press enter for default port 22): ", "22", self._coerce_ssh_port),
]
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()
for arg_name, key, prompt, default, validator in fields:
value = self._resolve_field(
args,
existing,
arg_name,
key,
prompt,
default=default,
validator=validator,
)
updated[key] = value
os.environ[key] = value
@@ -639,6 +676,13 @@ class ArgParser:
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(
"--host-ssh-port",
type=int,
help="Host SSH port to allow in the firewall (default: 22)",
)
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)")
+4
View File
@@ -19,6 +19,8 @@ while true; do
read -rp "Enter your email: " EMAIL
read -rp "Enter node public moniker (visible in the explorer and NymVPN app): " MONIKER
read -rp "Enter node public description: " DESCRIPTION
read -rp "Enter host SSH port (press enter for default port 22): " HOST_SSH_PORT
HOST_SSH_PORT="${HOST_SSH_PORT:-22}"
# show summary table
echo -e "\nPlease confirm the values you entered:"
@@ -28,6 +30,7 @@ while true; do
printf "%-20s %s\n" "EMAIL:" "$EMAIL"
printf "%-20s %s\n" "MONIKER:" "$MONIKER"
printf "%-20s %s\n" "DESCRIPTION:" "$DESCRIPTION"
printf "%-20s %s\n" "HOST_SSH_PORT:" "$HOST_SSH_PORT"
echo "---------------------------------------"
read -rp "Are these correct? (y/n): " CONFIRM
@@ -52,6 +55,7 @@ PUBLIC_IP=${PUBLIC_IP:-""}
echo "export MONIKER=\"${MONIKER}\""
echo "export DESCRIPTION=\"${DESCRIPTION}\""
echo "export PUBLIC_IP=\"${PUBLIC_IP}\""
echo "export HOST_SSH_PORT=\"${HOST_SSH_PORT}\""
} > env.sh
echo -e "\nVariables saved to ./env.sh"