Skip to content
Merged
Changes from 2 commits
Commits
Show all changes
143 commits
Select commit Hold shift + click to select a range
fc5d310
add QUIC setup script to nym-node-cli
serinko Nov 11, 2025
dea8a28
add arguments for env vars
serinko Nov 11, 2025
cc74d21
rm redundant fn
serinko Nov 11, 2025
9eca9ef
fix direction and add test
benedettadavico Nov 12, 2025
bdacc72
rm redundant
serinko Nov 12, 2025
b56d950
address comments
serinko Nov 12, 2025
0453345
address comments
serinko Nov 12, 2025
a808667
metadata port inside nymwg
serinko Nov 12, 2025
73a3493
trims
serinko Nov 12, 2025
5a26fa2
add uplink override arg
serinko Nov 12, 2025
66797ef
test new order of events..
benedettadavico Nov 12, 2025
4e8d29d
Merge remote-tracking branch 'origin/operators/tools-rewamp' into ope…
benedettadavico Nov 12, 2025
58083df
fix QUIC helper script
serinko Nov 12, 2025
781afd3
add arg
serinko Nov 12, 2025
c503a5f
few more tweaks
benedettadavico Nov 13, 2025
010b013
comment fix
serinko Nov 13, 2025
ef52f25
address comment
serinko Nov 13, 2025
34a500d
refactor completely
serinko Nov 13, 2025
f402da8
add new top manager tool
serinko Nov 13, 2025
e815f08
address comment
serinko Nov 13, 2025
694135c
address comment
serinko Nov 13, 2025
1f8144e
add a safeguard
serinko Nov 13, 2025
c617bbb
fix jq
serinko Nov 13, 2025
aba6c9d
fix exit message
serinko Nov 13, 2025
71301ee
sync ipv4 w ipv6
serinko Nov 13, 2025
e2fe3a6
address comment
serinko Nov 13, 2025
70a119a
address comment
serinko Nov 13, 2025
d04b61a
spacing
serinko Nov 13, 2025
c6a0256
remove wrong stdout
serinko Nov 13, 2025
06dd74b
address comments
serinko Nov 13, 2025
c5971d0
align space
serinko Nov 13, 2025
a6fe1b1
fix logic to ensure to more robust
serinko Nov 13, 2025
04be562
ensure cars passing in a shell
serinko Nov 13, 2025
4e1228f
ensure cars passing in a shell
serinko Nov 13, 2025
5627ada
address comment
serinko Nov 13, 2025
99b28b2
address comment
serinko Nov 13, 2025
91d0b7b
address comment
serinko Nov 13, 2025
239c6c7
address comment
serinko Nov 13, 2025
71e0c02
address comment
serinko Nov 13, 2025
943b5fa
address comment
serinko Nov 13, 2025
1525aed
expand pattern to common naming conventions
serinko Nov 13, 2025
aea7442
add status
serinko Nov 13, 2025
219f3af
remove subshell
serinko Nov 13, 2025
81fd37e
address comments
serinko Nov 13, 2025
de4fb62
address comments
serinko Nov 13, 2025
8c799b2
address comment
serinko Nov 13, 2025
ba01820
address comment
serinko Nov 13, 2025
cf8a399
remove subshell
serinko Nov 13, 2025
a38917c
address comments
serinko Nov 13, 2025
45e14a7
address comments
serinko Nov 13, 2025
8ca6af7
syntax fix
serinko Nov 13, 2025
76fc9f4
syntax fix
serinko Nov 13, 2025
5ba181b
break into args
serinko Nov 13, 2025
766024b
break into args
serinko Nov 13, 2025
1559f6a
bugfix
serinko Nov 13, 2025
4e5d88f
deleting to resolve merge confilict
serinko Nov 13, 2025
0fe863c
delete to resolve merge conflict
serinko Nov 13, 2025
c0c5802
Merge pull request #6197 from nymtech/serinko/ip-tables-rewamp
serinko Nov 13, 2025
21d5224
sync up with new tunnel manager
serinko Nov 13, 2025
fe7470e
address comment
serinko Nov 13, 2025
e8ca490
style
serinko Nov 13, 2025
9415196
string to dict fix
serinko Nov 13, 2025
6b8a628
fix nginx script
serinko Nov 13, 2025
a44cdf1
flush nginx script anew
serinko Nov 13, 2025
6d8edc4
replace y to Y and ''
serinko Nov 13, 2025
58c0e28
syntax fix
serinko Nov 13, 2025
edecc4b
remove redundant detect interface
serinko Nov 14, 2025
f62dbbd
ensure idempotency for the iptable rules
serinko Nov 14, 2025
3f56018
remove redundant
serinko Nov 14, 2025
054715a
robust error handling
serinko Nov 14, 2025
d820131
arg consistency
serinko Nov 14, 2025
228ef8b
add else
serinko Nov 14, 2025
9bdd2af
enforce root
serinko Nov 14, 2025
10707fd
convention Y/n
serinko Nov 14, 2025
e0ff09f
enforce root
serinko Nov 14, 2025
ae47d53
enforce root
serinko Nov 14, 2025
cc04a09
remove redundant work
serinko Nov 14, 2025
cc95358
add email to a fallback
serinko Nov 14, 2025
ce26105
typo
windy-ux Nov 14, 2025
842ce93
remove duplicate ufw rule
windy-ux Nov 14, 2025
e090668
bump up version
serinko Nov 14, 2025
ab6e08d
fix logic of landing-page lookup
serinko Nov 14, 2025
6acc54d
syntax fix
serinko Nov 14, 2025
e5aef76
non-interactive
serinko Nov 14, 2025
4f99106
fix nginx errors
serinko Nov 14, 2025
ef25480
fix
benedettadavico Nov 17, 2025
82a9563
add a checker script
benedettadavico Nov 18, 2025
b742ace
add firewall check to the main script
benedettadavico Nov 18, 2025
bdc0f50
/ move ensure_jq where needed
windy-ux Nov 18, 2025
06c0c36
+ add output for no rules were deduplicated
windy-ux Nov 18, 2025
2e05986
Revert "/ move ensure_jq where needed"
windy-ux Nov 18, 2025
2933732
Revert "+ add output for no rules were deduplicated"
windy-ux Nov 18, 2025
bfcb4c7
/ fix test_default_reject_rule
windy-ux Nov 19, 2025
c23e139
+ COLORS test_default_reject_rule
windy-ux Nov 19, 2025
4736f1e
/ fix login in exit_policy_run_tests
windy-ux Nov 19, 2025
95ee3a7
+ colors test_forward_chain_hook & complete_networking_configuration
windy-ux Nov 19, 2025
40a7a87
+ colorl jq install
windy-ux Nov 19, 2025
8de37eb
/ move ensure_jq where needed
windy-ux Nov 19, 2025
5496cce
/ move color definition
windy-ux Nov 19, 2025
22db132
@ merge fix test_default_reject_rule
windy-ux Nov 19, 2025
9c5847d
@ fix failing exit_policy_run_tests
windy-ux Nov 19, 2025
568268d
+ color exit_policy_run_tests
windy-ux Nov 19, 2025
7a339d4
+ color everywhere
windy-ux Nov 19, 2025
1b9af19
update routing configuration steps and make components
serinko Nov 19, 2025
45a1074
remove redundant
serinko Nov 19, 2025
37e3a10
fix routing test
serinko Nov 19, 2025
b4544c2
wg exit policy setup
serinko Nov 20, 2025
78fb779
write wg exit policy testing steps
serinko Nov 20, 2025
dcfd0f7
debug trace ticks
serinko Nov 20, 2025
47c6006
ready to merge back
serinko Nov 20, 2025
4fdbcb0
Merge pull request #6218 from nymtech/docs/tools-rewamp - [DOCs/opera…
serinko Nov 20, 2025
4e7b471
/ test failed echo text
windy-ux Nov 20, 2025
6c01c9f
Merge 'origin/fixing-order'
windy-ux Nov 20, 2025
752c791
+ colors for check the firewall setup
windy-ux Nov 20, 2025
18d271f
+ colors test_exit_policy_connectivity
windy-ux Nov 20, 2025
7b96adf
/ refactor help section
windy-ux Nov 20, 2025
9b07619
/ refactor help section
windy-ux Nov 20, 2025
3825d5f
Merge branch 'local/radek_benny_merge' into radek/network_scripts_edit
windy-ux Nov 20, 2025
50d7689
end status of help
windy-ux Nov 20, 2025
f9e2311
end status of help
windy-ux Nov 20, 2025
bb5b434
+ colors show_exit_policy_status
windy-ux Nov 20, 2025
a488a1b
/ color fixes
windy-ux Nov 20, 2025
ef7974f
/ otpimize create_nym_chain
windy-ux Nov 20, 2025
76993a9
/ colors
windy-ux Nov 20, 2025
6d1d9d5
Merge branch 'operators/tools-rewamp' into radek/network_scripts_edit
serinko Nov 21, 2025
89dc865
Merge pull request #6217 from nymtech/radek/network_scripts_edit
serinko Nov 21, 2025
2cc59aa
Merge branch 'develop' into operators/tools-rewamp
serinko Nov 21, 2025
6170ca2
Update time-now.md
serinko Nov 21, 2025
6d63ba1
menu fix
serinko Nov 21, 2025
52f98de
simplify
serinko Nov 21, 2025
28dc7ca
add logging and logfile
serinko Nov 24, 2025
68eae18
fix coloring and trap
serinko Nov 24, 2025
c13b4aa
fix coloring and trap
serinko Nov 24, 2025
f1be6ae
@ rename $cmd -> item in exit_policy_install_deps
windy-ux Nov 24, 2025
2d37c33
tweak docs commands
serinko Nov 24, 2025
26f4dd8
add another test
benedettadavico Nov 24, 2025
42c051d
add default output test
benedettadavico Nov 24, 2025
de0ae68
docs: specify command desc
serinko Nov 24, 2025
00d0ae0
docs: add noninteractive mode for quic setup
serinko Nov 24, 2025
8c3a797
@ fix perform_pings
windy-ux Nov 24, 2025
f12a554
Merge remote-tracking branch 'origin/operators/tools-rewamp' into ope…
windy-ux Nov 24, 2025
a293d6d
full_tunnel_setup to nym_tunnel_setup
serinko Nov 24, 2025
e0c74c5
formatting fix ... LFG
serinko Nov 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
222 changes: 162 additions & 60 deletions scripts/nym-node-setup/nym-node-cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,28 @@ 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.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.wg_ip_tables_manager_sh = self.fetch_script("wireguard-exit-policy-manager.sh")
self.wg_ip_tables_test_sh = self.fetch_script("exit-policy-tests.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"""
Expand All @@ -45,7 +56,7 @@ def print_welcome_message(self):
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"\
Expand All @@ -59,43 +70,99 @@ def print_welcome_message(self):
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"""
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.")
raise SystemExit(1)
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 Exception:
pass

# 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.")

# save mode for this Python instance
self.mode = mode
os.environ["MODE"] = mode

# persist to env.sh so other scripts can source it


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")
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")
# CLI arg
mode = getattr(args, "mode", None)
if mode:
mode = mode.strip().lower()
self._upsert_env_var("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 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)

self._upsert_env_var("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
Expand All @@ -119,7 +186,7 @@ def _return_script_url(self, script_init_name):
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",
#"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",
Expand All @@ -128,7 +195,9 @@ def _return_script_url(self, script_init_name):
"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",
"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(
Expand Down Expand Up @@ -205,27 +274,30 @@ def _check_gwx_mode(self):
else:
return False

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() in ("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"
Expand All @@ -237,25 +309,24 @@ def norm(v): # -> "true" or "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):
"""
Expand Down Expand Up @@ -320,6 +391,10 @@ def setup_test_wg_ip_tables(self):
self.run_script(self.wg_ip_tables_manager_sh, args=["status"])
self.run_script(self.wg_ip_tables_test_sh)

def quic_bridge_deploy(self):
"""Setup QUIC bridge and configuration using external script"""
self.run_script(self.quic_bridge_deployment_sh, args=["full_bridge_setup"], env=run_env)
return

def run_nym_node_as_service(self):
"""Starts /etc/systemd/system/nym-node.service based on prompt using external script"""
Expand Down Expand Up @@ -510,6 +585,7 @@ def _env_with_envfile(self) -> dict:

def run_node_installation(self,args):
"""Main function called by argparser command install running full node install flow"""
self.ensure_env_values(args)
self.run_script(self.prereqs_install_sh)
self.run_script(self.env_vars_install_sh)
self.run_script(self.node_install_sh)
Expand All @@ -521,7 +597,7 @@ def run_node_installation(self,args):
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()



Expand Down Expand Up @@ -558,6 +634,32 @@ def parser_main(self):
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",
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)")

# 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()

Expand Down
Loading