Compare commits

...

97 Commits

Author SHA1 Message Date
RadekSabacky 1cfb0a2d4a / move uwf rules into iptables 2025-11-20 14:28:44 +01:00
import this 4fdbcb051a Merge pull request #6218 from nymtech/docs/tools-rewamp - [DOCs/operators]: Tools rewamp documentation 2025-11-20 13:03:48 +00:00
serinko 47c6006bb7 ready to merge back 2025-11-20 13:52:41 +01:00
serinko dcfd0f77ad debug trace ticks 2025-11-20 13:39:11 +01:00
serinko 78fb779010 write wg exit policy testing steps 2025-11-20 13:33:06 +01:00
serinko b4544c2b48 wg exit policy setup 2025-11-20 13:17:40 +01:00
serinko 37e3a101b1 fix routing test 2025-11-19 17:41:35 +01:00
serinko 45a1074377 remove redundant 2025-11-19 17:35:33 +01:00
serinko 1b9af19e20 update routing configuration steps and make components 2025-11-19 17:14:44 +01:00
RadekSabacky bdc0f5022d / move ensure_jq where needed 2025-11-18 16:38:01 +01:00
serinko 4f991061dd fix nginx errors 2025-11-14 16:22:58 +01:00
serinko e5aef76256 non-interactive 2025-11-14 15:27:44 +01:00
serinko 6acc54d2bc syntax fix 2025-11-14 15:03:36 +01:00
serinko ab6e08dd13 fix logic of landing-page lookup 2025-11-14 14:27:38 +01:00
serinko e09066858c bump up version 2025-11-14 14:21:50 +01:00
RadekSabacky 842ce93a60 remove duplicate ufw rule 2025-11-14 13:24:20 +01:00
RadekSabacky ce26105986 typo 2025-11-14 13:24:05 +01:00
serinko cc95358385 add email to a fallback 2025-11-14 13:08:05 +01:00
serinko cc04a09ed7 remove redundant work 2025-11-14 12:58:03 +01:00
serinko ae47d53f0c enforce root 2025-11-14 12:49:10 +01:00
serinko e0ff09f323 enforce root 2025-11-14 12:44:57 +01:00
serinko 10707fd2c5 convention Y/n 2025-11-14 12:43:54 +01:00
serinko 9bdd2af14c enforce root 2025-11-14 12:42:05 +01:00
serinko 228ef8b158 add else 2025-11-14 12:40:14 +01:00
import this d820131d2c arg consistency
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-14 10:36:45 +00:00
import this 054715a600 robust error handling
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-14 10:36:23 +00:00
import this 3f560180b7 remove redundant
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-14 10:35:42 +00:00
import this f62dbbdae0 ensure idempotency for the iptable rules
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-14 10:33:23 +00:00
import this edecc4ba01 remove redundant detect interface
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-14 10:31:26 +00:00
serinko 58c0e289c2 syntax fix 2025-11-13 20:56:04 +01:00
serinko 6d8edc4bc7 replace y to Y and '' 2025-11-13 20:46:33 +01:00
serinko a44cdf1c7c flush nginx script anew 2025-11-13 20:37:35 +01:00
serinko 6b8a6283a4 fix nginx script 2025-11-13 20:27:43 +01:00
import this 94151965bb string to dict fix
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 17:55:09 +00:00
import this e8ca490db1 style
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 17:54:42 +00:00
import this fe7470ea44 address comment
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 17:54:17 +00:00
serinko 21d52244cb sync up with new tunnel manager 2025-11-13 18:49:53 +01:00
import this c0c58026a8 Merge pull request #6197 from nymtech/serinko/ip-tables-rewamp
one ring rules over all
2025-11-13 17:27:56 +00:00
import this 0fe863c889 delete to resolve merge conflict 2025-11-13 17:27:04 +00:00
import this 4e5d88f64c deleting to resolve merge confilict 2025-11-13 17:26:20 +00:00
serinko 1559f6a912 bugfix 2025-11-13 17:55:57 +01:00
serinko 766024be27 break into args 2025-11-13 17:51:39 +01:00
serinko 5ba181b118 break into args 2025-11-13 17:47:58 +01:00
import this 76fc9f4a90 syntax fix
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 16:23:38 +00:00
import this 8ca6af7c86 syntax fix
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 16:23:18 +00:00
import this 45e14a7fb1 address comments
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 16:22:57 +00:00
import this a38917cb9b address comments
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 16:22:34 +00:00
serinko cf8a399089 remove subshell 2025-11-13 17:07:20 +01:00
import this ba01820586 address comment
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 16:05:10 +00:00
import this 8c799b2976 address comment
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 16:04:34 +00:00
import this de4fb6291c address comments
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 16:03:50 +00:00
import this 81fd37e5c0 address comments
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 16:02:56 +00:00
serinko 219f3af967 remove subshell 2025-11-13 16:57:34 +01:00
import this aea7442525 add status
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 15:56:52 +00:00
import this 1525aed657 expand pattern to common naming conventions
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 15:56:18 +00:00
import this 943b5fa8bc address comment
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 15:51:01 +00:00
import this 71e0c025c6 address comment
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 15:50:42 +00:00
import this 239c6c701b address comment
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 15:50:19 +00:00
import this 91d0b7bdad address comment
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 15:49:49 +00:00
import this 99b28b2b6f address comment
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 15:49:32 +00:00
import this 5627ada57e address comment
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 15:49:02 +00:00
serinko 4e1228fff0 ensure cars passing in a shell 2025-11-13 16:43:39 +01:00
serinko 04be5624fa ensure cars passing in a shell 2025-11-13 16:41:42 +01:00
serinko a6fe1b1de7 fix logic to ensure to more robust 2025-11-13 16:32:40 +01:00
import this c5971d0e9d align space
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 15:18:54 +00:00
import this 06dd74ba34 address comments
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 15:18:38 +00:00
serinko c6a0256b03 remove wrong stdout 2025-11-13 16:13:25 +01:00
import this d04b61a88b spacing
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 15:10:01 +00:00
import this 70a119ac58 address comment
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 15:09:38 +00:00
import this e2fe3a60a6 address comment
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 15:01:23 +00:00
serinko 71301ee0cc sync ipv4 w ipv6 2025-11-13 15:08:34 +01:00
serinko aba6c9d4ac fix exit message 2025-11-13 15:04:17 +01:00
serinko c617bbb240 fix jq 2025-11-13 14:59:36 +01:00
serinko 1f8144eb11 add a safeguard 2025-11-13 14:47:02 +01:00
import this 694135c81b address comment
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 13:38:45 +00:00
import this e815f08505 address comment
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 13:38:15 +00:00
serinko f402da8e60 add new top manager tool 2025-11-13 14:28:38 +01:00
serinko 34a500d0a2 refactor completely 2025-11-13 14:26:49 +01:00
serinko ef52f25564 address comment 2025-11-13 11:20:13 +01:00
import this 010b013094 comment fix
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-13 10:17:04 +00:00
benedettadavico c503a5f0e8 few more tweaks 2025-11-13 10:21:37 +01:00
serinko 781afd3522 add arg 2025-11-12 17:36:29 +01:00
serinko 58083df0b0 fix QUIC helper script 2025-11-12 17:36:29 +01:00
benedettadavico 4e8d29d0c5 Merge remote-tracking branch 'origin/operators/tools-rewamp' into operators/tools-rewamp 2025-11-12 17:19:32 +01:00
benedettadavico 66797efa80 test new order of events.. 2025-11-12 17:18:59 +01:00
serinko 5a26fa262e add uplink override arg 2025-11-12 16:58:10 +01:00
serinko 73a34935ae trims 2025-11-12 16:29:52 +01:00
serinko a8086675d9 metadata port inside nymwg 2025-11-12 15:34:00 +01:00
serinko 0453345d65 address comments 2025-11-12 15:32:41 +01:00
serinko b56d9505e6 address comments 2025-11-12 15:31:08 +01:00
serinko bdacc72003 rm redundant 2025-11-12 15:22:26 +01:00
benedettadavico 9eca9efd82 fix direction and add test 2025-11-12 13:45:33 +01:00
serinko cc74d218fc rm redundant fn 2025-11-11 21:56:15 +01:00
serinko dea8a287e6 add arguments for env vars 2025-11-11 19:42:03 +01:00
serinko fc5d310935 add QUIC setup script to nym-node-cli 2025-11-11 15:04:09 +01:00
mfahampshire 4848d081d0 Max/tweak ts sdk actions (#6185)
* add taskset to wasm release build commands

* bump taskset cores
2025-11-11 10:19:55 +00:00
mfahampshire b3452ede77 add wss to prod csp (#6183)
* add wss to csp
2025-11-10 20:48:02 +00:00
26 changed files with 2032 additions and 2007 deletions
+2 -2
View File
@@ -23,7 +23,7 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Setup yarn
run: npm install -g yarn
@@ -54,6 +54,6 @@ jobs:
- name: Lint
run: yarn lint
- name: Typecheck with tsc
run: yarn tsc
@@ -0,0 +1,266 @@
import { Callout } from 'nextra/components';
import { Tabs } from 'nextra/components';
import { Steps } from 'nextra/components';
import { AccordionTemplate } from 'components/accordion-template.tsx';
export const ManagerIPOutput = () => (
<div>
Correct <code>./network-tunnel-manager.sh fetch_and_display_ipv6</code> output
</div>
);
export const ManagerTablesOutput = () => (
<div>
Correct <code>./network-tunnel-manager.sh check_nymtun_iptables</code> output
</div>
);
export const ShowTun = () => (
<div>
Correct <code>ip addr show nymtun0</code> output
</div>
);
<Callout>
We recommend operators to configure their `nym-node` with the full routing configuration.
However, most of the time the packets sent through the Mixnet are IPv4 based. The IPv6 packets are still pretty rare and therefore it's not mandatory from operational point of view to have this configuration implemented if you running only `mixnode` mode.
If you preparing to run a `nym-node` with all modes enabled in the future, this setup is required.
</Callout>
<Callout type="warning" emoji="⚠️">
Networking configuration across different ISPs and various operation systems does not have a generic solution. If the provided configuration setup doesn't solve your problem check out [IPv6 troubleshooting](/operators/troubleshooting/vps-isp.mdx#ipv6-troubleshooting) page. Be aware that you may have to do more research, customised adjustments or contact your ISP to change settings for your VPS.
</ Callout>
**Network Tunnel Manager ([`network-tunnel-manager.sh`](https://github.com/nymtech/nym/blob/develop/scripts/network_tunnel_manager.sh), NTM) is currently the one tool hadling the configuration of `nym-node` hosting server, according to the required design (node's [functionality](/operators/nodes/nym-node/setup#functionality-mode), WireGuard setup etc).**
**NTM cand administrate these areas:**
* IPv4 and IPv6 routing to the internet
* The `nymtun0` interface (Mixnet / 5-hop): dynamically managed by the `exit-gateway` service. When the service is stopped, `nymtun0` disappears, and when started, `nymtun0` is recreated.
* The `nymwg` interface (WG / 2-hop): used for creating a secure wireguard tunnel as part of the Nym Network configuration.
* `iptables` rules specific to `nymwg` to ensure proper routing and forwarding through the wireguard tunnel. The `nymwg` interface needs to be correctly configured and active for the related commands to function properly. This includes applying or removing iptables rules and running connectivity tests through the `nymwg` tunnel.
* WireGuard exit policy: Mixnet uses a common [exit policy](https://nymtech.net/.wellknown/network-requester/exit-policy.txt), to apply the same for WG, the operators need to set that one up on their server using `iptables` rules.
* Testing and validating all above
**Before starting with the following configuration, make sure you have the [latest `nym-node` binary](https://github.com/nymtech/nym/releases) installed and your [VPS setup](../preliminary-steps/vps-setup.mdx) finished properly!**
<Callout type="warning" emoji="⚠️">
**Run the following steps as root!**
</ Callout>
**Choose configuration command according your setup**
<div>
<Tabs items={[
<strong>New <code>nym-node</ code> full configuration</strong>,
<strong>Existing <code>nym-node</ code> full configuration</strong>,
<strong>Step-by-step or Pprtial configuration</strong>
]} defaultIndex={0}>
<Tabs.Tab>
This design is meant for operators setting up a new node on a fresh machine and it will result with a complete server readiness for routing as Entry Gateway and Exit Gateway in both Mixnet and WireGuard mode.
<Steps>
###### 1. Download `network-tunnel-manager.sh`, make executable and run with `--help` command:
```sh
curl -L https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/nym-node-setup/network-tunnel-manager.sh -o network-tunnel-manager.sh && \
chmod +x network-tunnel-manager.sh && \
./network-tunnel-manager.sh --help
```
###### 2. Make sure your `nym-node` service is up and running and bonded
- **If you setting up a new node and not upgrading an existing one, keep it running and [bond](/operators/nodes/nym-node/bonding.mdx) your node now! Then come back here and follow the rest of the configuration.**
###### 3. Execute complete network configuration:
```sh
./network-tunnel-manager.sh complete_networking_setup
```
</ Steps>
</Tabs.Tab>
<Tabs.Tab>
This is meant for operators configuring an existing and bonded node and it will result with a complete server readiness for routing as Entry Gateway and Exit Gateway in both Mixnet and WireGuard mode.
<Steps>
###### 1. Download `network-tunnel-manager.sh`, make executable and run with `--help` command:
```sh
curl -L https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/nym-node-setup/network-tunnel-manager.sh -o network-tunnel-manager.sh && \
chmod +x network-tunnel-manager.sh && \
./network-tunnel-manager.sh --help
```
###### 2. Execute complete network configuration:
```sh
./network-tunnel-manager.sh complete_networking_setup
```
</ Steps>
</Tabs.Tab>
<Tabs.Tab>
<Steps>
This design is meant for operators who want to do their server configuration step by step or choose only some parts of the setup.
###### 1. Download `network-tunnel-manager.sh`, make executable and run with `--help` command:
```sh
curl -L https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/nym-node-setup/network-tunnel-manager.sh -o network-tunnel-manager.sh && \
chmod +x network-tunnel-manager.sh && \
./network-tunnel-manager.sh --help
```
###### 2. Make sure your `nym-node` service is up and running and bonded
- **If you setting up a new node and not upgrading an existing one, keep it running and [bond](/operators/nodes/nym-node/bonding.mdx) your node now! Then come back here and follow the rest of the configuration.**
###### 3. Choose steps according your need
> You should be certain in your selection when configuring only various parts of the server.
###### Setup IP tables rules
- Delete IP tables rules for IPv4 and IPv6 and apply new ones:
```sh
./network-tunnel-manager.sh remove_duplicate_rules nymtun0
./network-tunnel-manager.sh apply_iptables_rules
```
- The process may prompt you if you want to save current IPv4 and IPv6 rules, choose yes.
![](/images/operators/ip_table_prompt.png)
- At this point you should see a `global ipv6` address.
```sh
./network-tunnel-manager.sh fetch_and_display_ipv6
```
<br />
<AccordionTemplate name={<ManagerTablesOutput/>}>
```sh
iptables-persistent is already installed.
Using IPv6 address: 2001:db8:a160::1/112 #the address will be different for you
operation fetch_ipv6_address_nym_tun completed successfully.
```
</AccordionTemplate>
###### Check Nymtun IP tables:
```sh
./network-tunnel-manager.sh check_nymtun_iptables
```
- If there's no process running it wouldn't return anything.
- In case you see `nymtun0` but not active, this is probably because you are setting up a new (never bonded) node and not upgrading an existing one.
<br />
<AccordionTemplate name={<ManagerIPOutput/>}>
```sh
iptables-persistent is already installed.
network Device: eth0
---------------------------------------
inspecting IPv4 firewall rules...
Chain FORWARD (policy DROP 0 packets, 0 bytes)
0 0 ufw-reject-forward all -- * * 0.0.0.0/0 0.0.0.0/0
0 0 ACCEPT all -- nymtun0 eth0 0.0.0.0/0 0.0.0.0/0
0 0 ACCEPT all -- eth0 nymtun0 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
0 0 ACCEPT all -- nymtun0 eth0 0.0.0.0/0 0.0.0.0/0
0 0 ACCEPT all -- eth0 nymtun0 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
0 0 ACCEPT all -- nymtun0 eth0 0.0.0.0/0 0.0.0.0/0
0 0 ACCEPT all -- eth0 nymtun0 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
---------------------------------------
inspecting IPv6 firewall rules...
Chain FORWARD (policy DROP 0 packets, 0 bytes)
0 0 ufw6-reject-forward all * * ::/0 ::/0
0 0 ACCEPT all eth0 nymtun0 ::/0 ::/0 state RELATED,ESTABLISHED
0 0 ACCEPT all nymtun0 eth0 ::/0 ::/0
0 0 ACCEPT all eth0 nymtun0 ::/0 ::/0 state RELATED,ESTABLISHED
0 0 ACCEPT all nymtun0 eth0 ::/0 ::/0
0 0 ACCEPT all eth0 nymtun0 ::/0 ::/0 state RELATED,ESTABLISHED
0 0 ACCEPT all nymtun0 eth0 ::/0 ::/0
operation check_nymtun_iptables completed successfully.
```
</AccordionTemplate>
###### Remove old and apply new rules for wireguad routing
```sh
../network-tunnel-manager.sh remove_duplicate_rules nymwg
./network-tunnel-manager.sh apply_iptables_rules_wg
```
###### Apply rules to configure DNS routing and allow ICMP piung test for node probing (network testing)
```sh
./network-tunnel-manager.sh configure_dns_and_icmp_wg
```
###### Adjust and validate IP forwarding
```sh
./network-tunnel-manager.sh adjust_ip_forwarding
./network-tunnel-manager.sh check_ipv6_ipv4_forwarding
```
###### Check `nymtun0` interface and test routing configuration
```sh
ip addr show nymtun0
```
<br />
<AccordionTemplate name={<ShowTun/>}>
```sh
# your addresses will be different
8: nymtun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1420 qdisc fq_codel state UNKNOWN group default qlen 500
link/none
inet 10.0.0.1/16 scope global nymtun0
valid_lft forever preferred_lft forever
inet6 fc00::1/112 scope global
valid_lft forever preferred_lft forever
inet6 fe80::ad08:d167:5700:8c7c/64 scope link stable-privacy
valid_lft forever preferred_lft forever`
```
</AccordionTemplate>
- Validate your IPv6 and IPv4 networking by running a joke test via Mixnet:
```sh
./network-tunnel-manager.sh joke_through_the_mixnet
```
- Validate your tunneling by running a joke test via WG:
```sh
../network-tunnel-manager.sh joke_through_wg_tunnel
```
###### Enable wireguard
Now you can run your node with the `--wireguard-enabled true` flag or add it to your [systemd service config](#systemd). Restart your `nym-node` or [systemd](#2-following-steps-for-nym-nodes-running-as-systemd-service) service (recommended):
```sh
systemctl daemon-reload && service nym-node restart
```
- Optionally, you can check if the node is running correctly by monitoring the service logs:
```sh
journalctl -u nym-node.service -f -n 100
```
</ Steps>
</Tabs.Tab>
</Tabs>
</div>
<Callout type="info" emoji="️">
Note that the functionality the node runs in is decided by [arguments on the node itself / in node's `config.toml`](/operators/nodes/nym-node/setup#functionality-mode), this tool only prepares the server.
</ Callout>
Make sure that you get the validation of all connectivity. If there are still any problems, please refer to [troubleshooting section](/operators/troubleshooting/vps-isp.mdx#incorrect-gateway-network-check).
@@ -0,0 +1,68 @@
import { Callout } from 'nextra/components';
import { Tabs } from 'nextra/components';
import { Steps } from 'nextra/components';
import { AccordionTemplate } from 'components/accordion-template.tsx';
import ExitPolicyInstallOutput from 'components/operators/snippets/wg-exit-policy-install-output.mdx';
import ExitPolicyStatusOutput from 'components/operators/snippets/wg-exit-policy-status-output.mdx';
<Callout type="info" emoji="️">
**In case you had used `network-tunnel-manager.sh` with the command `complete_networking_setup`, your WireGuard exit policy is already setup. You can test it in the next chapter.**
</ Callout>
Nym Node running as Exit Gateway has contains multiple modules, one of them is Nym Network Requester(NR), routing TCP traffic to the internet. To make sure that the node is not just an open proxy, NR checks agains [Nym exit policy](https://nymtech.net/.wellknown/network-requester/exit-policy.txt) following these conditions (in this exact order):
1. Do we explicitly block those IP addresses regardless of ports?
2. Do we allow those specific ports regardless of IPs?
3. Do block EVERYTHING else!
The exit policy is same for all NRs, the content is shaped by an offchain governance of Nym Node operators on our [forum](https://forum.nym.com/t/poll-a-new-nym-exit-policy-for-exit-gateways-and-the-nym-mixnet-is-inbound/464).
There is a caveat though. NR is only routing TCP streams and therefore any other type of routing than Mixnet is *not* filtered thorugh the exit policy. To ensure that Nym Nodes follow the same exit policy when routing IP packets through WireGuard and don't act as open proxies, the operators have to set up these rules via IP tables rules.
**For all routing configuration we provide one tool [`network-tunnel-manager.sh` (NTM)](https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/wireguard-exit-policy/wireguard-exit-policy-manager.sh). This tool manages WireGuard exit policy as well.**
In case you haven't run `network-tunnel-manager.sh` with the command `complete_networking_setup` you need to use NTM for WireGuard exit policy configuration.
**Folow these steps**
<Callout type="warning" emoji="⚠️">
**Run the following steps as root!**
</ Callout>
<Steps>
###### 1. Download `network-tunnel-manager.sh`, make executable and run with `--help` command:
```sh
curl -L https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/nym-node-setup/network-tunnel-manager.sh -o network-tunnel-manager.sh && \
chmod +x network-tunnel-manager.sh && \
./network-tunnel-manager.sh --help
```
###### 2. Install exit policy
- Clear old rules and configure new ones:
```sh
./network-tunnel-manager.sh exit_policy_clear
./network-tunnel-manager.sh exit_policy_install
```
- The output should look like this:
<AccordionTemplate name="Cosole output">
<ExitPolicyInstallOutput />
</ AccordionTemplate>
###### 3. Check status of your configuration
```sh
./network-tunnel-manager.sh exit_policy_status
```
- The output should look like this:
<AccordionTemplate name="Cosole output">
<ExitPolicyStatusOutput />
</ AccordionTemplate>
</ Steps>
Now your WireGuard routing (2-hop) should have same rotuing permissions like [Nym exit policy](https://nymtech.net/.wellknown/network-requester/exit-policy.txt) used on 5-hop (Mixnet) mode of NymVPN.
@@ -0,0 +1,39 @@
import { Tabs } from 'nextra/components';
import { Steps } from 'nextra/components';
import ExitPolicyTestServer from 'components/operators/snippets/wg-exit-policy-testing-from-server.mdx';
import ExitPolicyTestOutside from 'components/operators/snippets/wg-exit-policy-testing-from-outside.mdx';
**For all routing configuration we provide one tool [`network-tunnel-manager.sh` (NTM)](https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/wireguard-exit-policy/wireguard-exit-policy-manager.sh). This tool manages WireGuard tests all configurations, including WireGuard exit policy as well.**
You can use NTM to validate the application of the IP tables routes on your `nym-node` by checking it from the server side as well as from the outside.
<div>
<Tabs items={[
<strong>From the server</strong>,
<strong>From the outside - using NymVPN</strong>
]} defaultIndex={0}>
<Tabs.Tab><ExitPolicyTestServer /></Tabs.Tab>
<Tabs.Tab><ExitPolicyTestOutside /></Tabs.Tab>
</Tabs>
</div>
If all works , your node has successfully implemented WireGuard exit policy with the same routing permissions like [Nym exit policy](https://nymtech.net/.wellknown/network-requester/exit-policy.txt) used on 5-hop (Mixnet) for TCP routing.
@@ -1,9 +1,29 @@
import { Steps } from 'nextra/components';
Here are a few ways you can ensre your WireGuard exit policy is working correctly from the outside.
<Steps>
###### 1. Using NymVPN
- Connect to NymVPN and use your node as an Exit Gateway in dVPN (2-hop) mode
- While connected to NymVPN, navigate to [`portquiz.net:12345`](http://portquiz.net:12345) and see if you can load the page
- It shouldn't load, but if you navigate to some of the accepted ports, like[`portquiz.net:443`](http://portquiz.net:443) it should all work
###### 2. Testing from your local terminal
- Install these dependencies on your local machine:
```shell
sudo apt install tcpdump
sudo tcpdump -i nymwg -n
```
- Connect to [NymVPN](https://nym.com) and select your node as an Exit Gateway (after running the exit policy [manager script](#wireguard-exit-policy-configuration))
- Connect to [NymVPN](https://nym.com) and select your node as an Exit Gateway
- Run the `tcpdump` command before registering
- Have the output of the `echo $BLOCKED_IP` from your node and try to go into your browser or a registered client and try to connect - It will not resolve.
- Have the output of the `echo $BLOCKED_IP` from your node and try to go into your browser or a registered client and try to connect - It should not resolve
</ Steps>
@@ -1,11 +1,39 @@
Run this command to define variable `BLOCKED_IP` and try to `ping` it:
import { Steps } from 'nextra/components';
import { AccordionTemplate } from 'components/accordion-template.tsx';
import ExitPolicyTestOutput from 'components/operators/snippets/wg-exit-policy-test-output.mdx';
<Steps>
###### 1. Make sure to have the latest NTM:
```sh
curl -L https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/nym-node-setup/network-tunnel-manager.sh -o network-tunnel-manager.sh && \
chmod +x network-tunnel-manager.sh && \
./network-tunnel-manager.sh --help
```
###### 2. Run tests with NTM
```sh
./network-tunnel-manager.sh exit_policy_test_connectivity
./network-tunnel-manager.sh exit_policy_tests
```
- The output should look like this:
<AccordionTemplate name="Cosole output">
<ExitPolicyTestOutput />
</ AccordionTemplate>
###### 3. Optionally you can do some manual sanity checks
- Run this command to define variable `BLOCKED_IP` and try to `ping` it:
```shell
BLOCKED_IP=$(grep "ExitPolicy reject" /etc/nym/exit-policy.txt | head -1 | sed -E 's/ExitPolicy reject ([^:]+):.*/\1/' | sed 's/\/.*$//')
ping -c 3 $BLOCKED_IP
```
You should see `100% packet loss` as an outcome.
- You should see `100% packet loss` as an outcome.
```shell
telnet $BLOCKED_IP 80
```
You should see `telnet: Unable to connect to remote host: Connection timed out`.
- You should see `telnet: Unable to connect to remote host: Connection timed out`.
</ Steps>
@@ -1 +1 @@
Monday, November 10th 2025, 13:30:27 UTC
Thursday, November 20th 2025, 12:33:19 UTC
+8 -8
View File
@@ -1108,22 +1108,22 @@ const config = {
form-action 'self';
frame-ancestors 'none';
upgrade-insecure-requests;
connect-src 'self' https://github.com *.vercel.app *.nymtech.net *.nymvpn.com *.nymte.ch *.nyx.network *.nym.com https://nym.com nymvpn.com https://nymvpn.com *.nymtech.cc;
connect-src 'self' wss: https://github.com *.vercel.app *.nymtech.net *.nymvpn.com *.nymte.ch *.nyx.network *.nym.com https://nym.com nymvpn.com https://nymvpn.com *.nymtech.cc;
frame-src 'self' https://vercel.live *.vercel.app *.nym.com https://nym.com;
worker-src 'self' blob: https://vercel.live *.vercel.app *.nym.com https://nym.com;
`;
return [
{
source: '/(.*)',
source: "/(.*)",
headers: [
{
key: 'Content-Security-Policy',
key: "Content-Security-Policy",
value: csp.replace(/\s{2,}/g, " ").trim(),
}
]
}
]
}
},
],
},
];
},
};
module.exports = config;
@@ -3,34 +3,11 @@ import { Tabs } from 'nextra/components';
import { VarInfo } from 'components/variable-info.tsx';
import { Steps } from 'nextra/components';
import { AccordionTemplate } from 'components/accordion-template.tsx';
import ExitPolicyInstallOutput from 'components/operators/snippets/wg-exit-policy-install-output.mdx';
import ExitPolicyStatusOutput from 'components/operators/snippets/wg-exit-policy-status-output.mdx';
import ExitPolicyTestOutput from 'components/operators/snippets/wg-exit-policy-test-output.mdx';
import ExitPolicyTestServer from 'components/operators/snippets/wg-exit-policy-testing-from-server.mdx';
import ExitPolicyTestOutside from 'components/operators/snippets/wg-exit-policy-testing-from-outside.mdx';
import WGExitPolicyConf from 'components/operators/snippets/wg-exit-policy-conf.mdx';
import WGExitPolicyTest from 'components/operators/snippets/wg-exit-policy-test.mdx';
import RoutingConf from 'components/operators/snippets/routing-conf.mdx';
import QuicDeploymentSteps from 'components/operators/snippets/quic-bridge-deployment-script-setup.mdx';
export const ManagerIPOutput = () => (
<div>
Correct <code>./network_tunnel_manager.sh fetch_and_display_ipv6</code> output
</div>
);
export const ManagerTablesOutput = () => (
<div>
Correct <code>./network_tunnel_manager.sh check_nymtun_iptables</code> output
</div>
);
export const ShowTun = () => (
<div>
Correct <code>ip addr show nymtun0</code> output
</div>
);
# Nym Node Configuration
<VarInfo />
@@ -222,9 +199,11 @@ This lets your operating system know it's ok to reload the service configuration
</Steps>
## Connectivity Test and Configuration
## Routing Configuration
During our ongoing testing events we found out, that after introducing IP Packet Router (IPR) and [Nym exit policy](https://nymtech.net/.wellknown/network-requester/exit-policy.txt) on embedded Network Requester (NR) by default, only a fragment of Gateways routes correctly through IPv4 and IPv6. We built a useful monitor to check out your Gateway (`nym-node --mode exit-gateway`) at [harbourmaster.nymtech.net](https://harbourmaster.nymtech.net/).
<RoutingConf />
### Quick IPv6 Check
IPv6 routing is not only a case for gateways. Imagine a rare occasion when you run a `mixnode` without IPv6 enabled and a client will sent IPv6 packets through the Mixnet through such route:
```ascii
@@ -232,19 +211,6 @@ IPv6 routing is not only a case for gateways. Imagine a rare occasion when you r
```
In this (unusual) case your `mixnode` will not be able to route the packets. The node will drop the packets and its performance would go down. For that reason it's beneficial to have IPv6 enabled when running a `mixnode` functionality.
<Callout>
We recommend operators to configure their `nym-node` with the full routing configuration.
However, most of the time the packets sent through the Mixnet are IPv4 based. The IPv6 packets are still pretty rare and therefore it's not mandatory from operational point of view to have this configuration implemented if you running only `mixnode` mode.
If you preparing to run a `nym-node` with all modes enabled in the future, this setup is required.
</Callout>
<Callout type="info" emoji="️">
For everyone participating in Delegation Program or Service Grant program, this setup is a requirement!
</Callout>
### Quick IPv6 Check
You can always check IPv6 address and connectivity by using some of these methods:
<br />
@@ -273,267 +239,13 @@ telnet -6 ipv6.telnetmyip.com
Make sure to keep your IPv4 address enabled while setting up IPv6, as the majority of routing goes through that one!
</Callout>
### Routing Configuration
While we're working on Rust implementation to have these settings as a part of the binary build, to solve these connectivity requirements in the meantime we wrote a script [`network_tunnel_manager.sh`](https://github.com/nymtech/nym/blob/develop/scripts/network_tunnel_manager.sh) to support operators to configure their servers and address all the connectivity requirements.
Networking configuration across different ISPs and various operation systems does not have a generic solution. If the provided configuration setup doesn't solve your problem check out [IPv6 troubleshooting](../../troubleshooting/vps-isp.mdx#ipv6-troubleshooting) page. Be aware that you may have to do more research, customised adjustments or contact your ISP to change settings for your VPS.
The `nymtun0` interface is dynamically managed by the `exit-gateway` service. When the service is stopped, `nymtun0` disappears, and when started, `nymtun0` is recreated.
The `nymwg` interface is used for creating a secure wireguard tunnel as part of the Nym Network configuration. Similar to `nymtun0`, the script manages iptables rules specific to `nymwg` to ensure proper routing and forwarding through the wireguard tunnel. The `nymwg` interface needs to be correctly configured and active for the related commands to function properly. This includes applying or removing iptables rules and running connectivity tests through the `nymwg` tunnel.
The script should be used in a context where `nym-node` is running to fully utilise its capabilities, particularly for fetching IPv6 addresses or applying network rules that depend on the `nymtun0` and `nymwg` interfaces and to establish a WireGuard tunnel.
**Before starting with the following configuration, make sure you have the [latest `nym-node` binary](https://github.com/nymtech/nym/releases) installed and your [VPS setup](../preliminary-steps/vps-setup.mdx) finished properly!**
<Steps>
###### 1. Download `network_tunnel_manager.sh`, make executable and run:
```sh
curl -L https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/network_tunnel_manager.sh -o network_tunnel_manager.sh && \
chmod +x network_tunnel_manager.sh && \
./network_tunnel_manager.sh
```
###### 2. Make sure your `nym-node` service is up and running and bond
- **If you setting up a new node and not upgrading an existing one, keep it running and [bond](bonding.mdx) your node now**. Then come back here and follow the rest of the configuration.
<Callout type="warning" emoji="⚠️">
**Run the following steps as root or with `sudo` prefix!**
</Callout>
###### 3. Setup IP tables rules
- Delete IP tables rules for IPv4 and IPv6 and apply new ones:
```sh
./network_tunnel_manager.sh remove_duplicate_rules nymtun0
./network_tunnel_manager.sh apply_iptables_rules
```
- The process may prompt you if you want to save current IPv4 and IPv6 rules, choose yes.
![](/images/operators/ip_table_prompt.png)
- At this point you should see a `global ipv6` address.
```sh
./network_tunnel_manager.sh fetch_and_display_ipv6
```
<br />
<AccordionTemplate name={<ManagerTablesOutput/>}>
```sh
iptables-persistent is already installed.
Using IPv6 address: 2001:db8:a160::1/112 #the address will be different for you
operation fetch_ipv6_address_nym_tun completed successfully.
```
</AccordionTemplate>
###### 4. Check Nymtun IP tables:
```sh
./network_tunnel_manager.sh check_nymtun_iptables
```
- If there's no process running it wouldn't return anything.
- In case you see `nymtun0` but not active, this is probably because you are setting up a new (never bonded) node and not upgrading an existing one.
<br />
<AccordionTemplate name={<ManagerIPOutput/>}>
```sh
iptables-persistent is already installed.
network Device: eth0
---------------------------------------
inspecting IPv4 firewall rules...
Chain FORWARD (policy DROP 0 packets, 0 bytes)
0 0 ufw-reject-forward all -- * * 0.0.0.0/0 0.0.0.0/0
0 0 ACCEPT all -- nymtun0 eth0 0.0.0.0/0 0.0.0.0/0
0 0 ACCEPT all -- eth0 nymtun0 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
0 0 ACCEPT all -- nymtun0 eth0 0.0.0.0/0 0.0.0.0/0
0 0 ACCEPT all -- eth0 nymtun0 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
0 0 ACCEPT all -- nymtun0 eth0 0.0.0.0/0 0.0.0.0/0
0 0 ACCEPT all -- eth0 nymtun0 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED
---------------------------------------
inspecting IPv6 firewall rules...
Chain FORWARD (policy DROP 0 packets, 0 bytes)
0 0 ufw6-reject-forward all * * ::/0 ::/0
0 0 ACCEPT all eth0 nymtun0 ::/0 ::/0 state RELATED,ESTABLISHED
0 0 ACCEPT all nymtun0 eth0 ::/0 ::/0
0 0 ACCEPT all eth0 nymtun0 ::/0 ::/0 state RELATED,ESTABLISHED
0 0 ACCEPT all nymtun0 eth0 ::/0 ::/0
0 0 ACCEPT all eth0 nymtun0 ::/0 ::/0 state RELATED,ESTABLISHED
0 0 ACCEPT all nymtun0 eth0 ::/0 ::/0
operation check_nymtun_iptables completed successfully.
```
</AccordionTemplate>
###### 5. Remove old and apply new rules for wireguad routing
```sh
/network_tunnel_manager.sh remove_duplicate_rules nymwg
./network_tunnel_manager.sh apply_iptables_rules_wg
```
###### 6. Apply rules to configure DNS routing and allow ICMP piung test for node probing (network testing)
```sh
./network_tunnel_manager.sh configure_dns_and_icmp_wg
```
###### 7. Adjust and validate IP forwarding
```sh
./network_tunnel_manager.sh adjust_ip_forwarding
./network_tunnel_manager.sh check_ipv6_ipv4_forwarding
```
###### 8. Check `nymtun0` interface and test routing configuration
```sh
ip addr show nymtun0
```
<br />
<AccordionTemplate name={<ShowTun/>}>
```sh
# your addresses will be different
8: nymtun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1420 qdisc fq_codel state UNKNOWN group default qlen 500
link/none
inet 10.0.0.1/16 scope global nymtun0
valid_lft forever preferred_lft forever
inet6 fc00::1/112 scope global
valid_lft forever preferred_lft forever
inet6 fe80::ad08:d167:5700:8c7c/64 scope link stable-privacy
valid_lft forever preferred_lft forever`
```
</AccordionTemplate>
- Validate your IPv6 and IPv4 networking by running a joke test via Mixnet:
```sh
./network_tunnel_manager.sh joke_through_the_mixnet
```
- Validate your tunneling by running a joke test via WG:
```sh
./network_tunnel_manager.sh joke_through_wg_tunnel
```
- **Note:** WireGuard will return only IPv4 joke, not IPv6. WG IPv6 is under development. Running IPR joke through the mixnet with `./network_tunnel_manager.sh joke_through_the_mixnet` should work with both IPv4 and IPv6!
###### 9. Enable wireguard
Now you can run your node with the `--wireguard-enabled true` flag or add it to your [systemd service config](#systemd). Restart your `nym-node` or [systemd](#2-following-steps-for-nym-nodes-running-as-systemd-service) service (recommended):
```sh
systemctl daemon-reload && service nym-node restart
```
- Optionally, you can check if the node is running correctly by monitoring the service logs:
```sh
journalctl -u nym-node.service -f -n 100
```
</Steps>
Make sure that you get the validation of all connectivity. If there are still any problems, please refer to [troubleshooting section](../../troubleshooting/vps-isp.mdx#incorrect-gateway-network-check).
## Wireguard Exit Policy Configuration
Nym Node running as Exit Gateway has contains multiple modules, one of them is Nym Network Requester(NR), routing TCP traffic to the internet. To make sure that the node is not just an open proxy, NR checks agains [Nym exit policy](https://nymtech.net/.wellknown/network-requester/exit-policy.txt) following these conditions (in this exact order):
1. Do we explicitly block those IP addresses regardless of ports?
2. Do we allow those specific ports regardless of IPs?
3. Do block EVERYTHING else!
The exit policy is same for all NRs, the content is shaped by an offchain governance of Nym Node operators on our [forum](https://forum.nym.com/t/poll-a-new-nym-exit-policy-for-exit-gateways-and-the-nym-mixnet-is-inbound/464).
There is a caveat though. NR is only routing TCP streams and therefore any other type of routing is *not* filtered thorugh the exit policy. To ensure that Nym Nodes follow the same exit policy when routing IP packets through wireguard and don't act as open proxies, the operators have to set up these rules via IP tables rules.
**Follow these steps, using a [setup script](https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/wireguard-exit-policy/wireguard-exit-policy-manager.sh) and [testing scripts](https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/wireguard-exit-policy/exit-policy-tests.sh) written by Nym quality assurance team, to setup exit policy for wireguard:**
<Steps>
###### 1. Download the scripts and make executable
- SSH to your node
- Create a folder `~/nym-binaries` and navigate there
```sh
mkdir $HOME/nym-binaries
cd $HOME/nym-binaries
```
- Download the scripts
```sh
wget https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/wireguard-exit-policy/wireguard-exit-policy-manager.sh
wget https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/wireguard-exit-policy/exit-policy-tests.sh
```
- Make executable
```sh
chmod +x wireguard-exit-policy-manager.sh exit-policy-tests.sh
```
###### 2. Install `wireguard-exit-policy-manager.sh`
```sh
./wireguard-exit-policy-manager.sh install
```
- The output should look like this:
<AccordionTemplate name="Cosole output">
<ExitPolicyInstallOutput />
</ AccordionTemplate>
###### 3. Run `wireguard-exit-policy-manager.sh`
```sh
./wireguard-exit-policy-manager.sh status
```
- The output should look like this:
<AccordionTemplate name="Cosole output">
<ExitPolicyStatusOutput />
</ AccordionTemplate>
###### 4. Test with `exit-policy-tests.sh`
```sh
./exit-policy-tests.sh
```
- The output should look like this:
<AccordionTemplate name="Cosole output">
<ExitPolicyTestOutput />
</ AccordionTemplate>
###### 5. In case of problems, you can clear the exit policy rule
```sh
./wireguard-exit-policy-manager.sh clear
./wireguard-exit-policy-manager.sh status
```
</ Steps>
Now your wireguart routing should have same rotuing permissions like [Nym exit policy](https://nymtech.net/.wellknown/network-requester/exit-policy.txt) used on 5-hop (Mixnet) mode of NymVPN.
<WGExitPolicyConf />
### Testing Wireguard Exit Policy
You can validate the application of the IP tables routes on your `nym-node` by checking it from the server side as well as from the outside.
<div>
<Tabs items={[
<strong>From the server</strong>,
<strong>From the outside - using NymVPN</strong>
]} defaultIndex={0}>
<Tabs.Tab><ExitPolicyTestServer /></Tabs.Tab>
<Tabs.Tab><ExitPolicyTestOutside /></Tabs.Tab>
</Tabs>
</div>
Your node has successfully implemented wireguard exit policy with the same routing permissions like [Nym exit policy](https://nymtech.net/.wellknown/network-requester/exit-policy.txt) used on 5-hop (Mixnet).
<WGExitPolicyTest />
## QUIC Transport Bridge Deployment
-290
View File
@@ -1,290 +0,0 @@
#!/bin/bash
network_device=$(ip route show default | awk '/default/ {print $5}')
tunnel_interface="nymtun0"
wg_tunnel_interface="nymwg"
if ! dpkg -s iptables-persistent >/dev/null 2>&1; then
sudo apt-get update
sudo apt-get install -y iptables-persistent
else
echo "iptables-persistent is already installed."
fi
fetch_ipv6_address() {
local interface=$1
ipv6_global_address=$(ip -6 addr show "$interface" scope global | grep inet6 | awk '{print $2}' | head -n 1)
if [[ -z "$ipv6_global_address" ]]; then
echo "no globally routable IPv6 address found on $interface. Please configure IPv6 or check your network settings."
exit 1
else
echo "using IPv6 address: $ipv6_global_address"
fi
}
fetch_and_display_ipv6() {
ipv6_address=$(ip -6 addr show "$network_device" scope global | grep inet6 | awk '{print $2}')
if [[ -z "$ipv6_address" ]]; then
echo "no global IPv6 address found on $network_device."
else
echo "IPv6 address on $network_device: $ipv6_address"
fi
}
remove_duplicate_rules() {
local interface=$1
local script_name=$(basename "$0")
if [[ -z "$interface" ]]; then
echo "error: no interface specified. please enter the interface (nymwg or nymtun0):"
read -r interface
fi
if [[ "$interface" != "nymwg" && "$interface" != "nymtun0" ]]; then
echo "error: invalid interface '$interface'. allowed values are 'nymwg' or 'nymtun0'." >&2
exit 1
fi
echo "removing duplicate rules for $interface..."
iptables-save | grep "$interface" | while read -r line; do
sudo iptables -D ${line#-A } || echo "Failed to delete rule: $line"
done
ip6tables-save | grep "$interface" | while read -r line; do
sudo ip6tables -D ${line#-A } || echo "Failed to delete rule: $line"
done
echo "duplicates removed for $interface."
echo "!!-important-!! you need to now reapply the iptables rules for $interface."
if [ "$interface" == "nymwg" ]; then
echo "run: ./$script_name apply_iptables_rules_wg"
else
echo "run: ./$script_name apply_iptables_rules"
fi
}
adjust_ip_forwarding() {
ipv6_forwarding_setting="net.ipv6.conf.all.forwarding=1"
ipv4_forwarding_setting="net.ipv4.ip_forward=1"
# remove duplicate entries for these settings from the file
sudo sed -i "/^net.ipv6.conf.all.forwarding=/d" /etc/sysctl.conf
sudo sed -i "/^net.ipv4.ip_forward=/d" /etc/sysctl.conf
echo "$ipv6_forwarding_setting" | sudo tee -a /etc/sysctl.conf
echo "$ipv4_forwarding_setting" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p /etc/sysctl.conf
}
apply_iptables_rules() {
local interface=$1
echo "applying IPtables rules for $interface..."
sleep 2
sudo iptables -t nat -A POSTROUTING -o "$network_device" -j MASQUERADE
sudo iptables -A FORWARD -i "$interface" -o "$network_device" -j ACCEPT
sudo iptables -A FORWARD -i "$network_device" -o "$interface" -m state --state RELATED,ESTABLISHED -j ACCEPT
sudo ip6tables -t nat -A POSTROUTING -o "$network_device" -j MASQUERADE
sudo ip6tables -A FORWARD -i "$interface" -o "$network_device" -j ACCEPT
sudo ip6tables -A FORWARD -i "$network_device" -o "$interface" -m state --state RELATED,ESTABLISHED -j ACCEPT
sudo iptables-save | sudo tee /etc/iptables/rules.v4
sudo ip6tables-save | sudo tee /etc/iptables/rules.v6
}
check_tunnel_iptables() {
local interface=$1
echo "inspecting IPtables rules for $interface..."
echo "---------------------------------------"
echo "IPv4 rules:"
iptables -L FORWARD -v -n | awk -v dev="$interface" '/^Chain FORWARD/ || $0 ~ dev || $0 ~ "ufw-reject-forward"'
echo "---------------------------------------"
echo "IPv6 rules:"
ip6tables -L FORWARD -v -n | awk -v dev="$interface" '/^Chain FORWARD/ || $0 ~ dev || $0 ~ "ufw6-reject-forward"'
}
check_ipv6_ipv4_forwarding() {
result_ipv4=$(cat /proc/sys/net/ipv4/ip_forward)
result_ipv6=$(cat /proc/sys/net/ipv6/conf/all/forwarding)
echo "IPv4 forwarding is $([ "$result_ipv4" == "1" ] && echo "enabled" || echo "not enabled")."
echo "IPv6 forwarding is $([ "$result_ipv6" == "1" ] && echo "enabled" || echo "not enabled")."
}
check_ip_routing() {
echo "IPv4 routing table:"
ip route
echo "---------------------------------------"
echo "IPv6 routing table:"
ip -6 route
}
perform_pings() {
echo "performing IPv4 ping to google.com..."
ping -c 4 google.com
echo "---------------------------------------"
echo "performing IPv6 ping to google.com..."
ping6 -c 4 google.com
}
joke_through_tunnel() {
local interface=$1
local green="\033[0;32m"
local reset="\033[0m"
local red="\033[0;31m"
local yellow="\033[0;33m"
sleep 1
echo
echo -e "${yellow}checking tunnel connectivity and fetching a joke for $interface...${reset}"
echo -e "${yellow}if these test succeeds, it confirms your machine can reach the outside world via IPv4 and IPv6.${reset}"
echo -e "${yellow}however, probes and external clients may experience different connectivity to your nym-node.${reset}"
ipv4_address=$(ip addr show "$interface" | awk '/inet / {print $2}' | cut -d'/' -f1)
ipv6_address=$(ip addr show "$interface" | awk '/inet6 / && $2 !~ /^fe80/ {print $2}' | cut -d'/' -f1)
if [[ -z "$ipv4_address" && -z "$ipv6_address" ]]; then
echo -e "${red}no IP address found on $interface. unable to fetch a joke.${reset}"
echo -e "${red}please verify your tunnel configuration and ensure the interface is up.${reset}"
return 1
fi
if [[ -n "$ipv4_address" ]]; then
echo
echo -e "------------------------------------"
echo -e "detected IPv4 address: $ipv4_address"
echo -e "testing IPv4 connectivity..."
echo
if ping -c 1 -I "$ipv4_address" google.com >/dev/null 2>&1; then
echo -e "${green}IPv4 connectivity is working. fetching a joke...${reset}"
joke=$(curl -s -H "Accept: application/json" --interface "$ipv4_address" https://icanhazdadjoke.com/ | jq -r .joke)
[[ -n "$joke" && "$joke" != "null" ]] && echo -e "${green}IPv4 joke: $joke${reset}" || echo -e "failed to fetch a joke via IPv4."
else
echo -e "${red}IPv4 connectivity is not working for $interface. verify your routing and NAT settings.${reset}"
fi
else
echo -e "${red}no IPv4 address found on $interface. unable to fetch a joke via IPv4.${reset}"
fi
if [[ -n "$ipv6_address" ]]; then
echo
echo -e "------------------------------------"
echo -e "detected IPv6 address: $ipv6_address"
echo -e "testing IPv6 connectivity..."
echo
if ping6 -c 1 -I "$ipv6_address" google.com >/dev/null 2>&1; then
echo -e "${green}IPv6 connectivity is working. fetching a joke...${reset}"
joke=$(curl -s -H "Accept: application/json" --interface "$ipv6_address" https://icanhazdadjoke.com/ | jq -r .joke)
[[ -n "$joke" && "$joke" != "null" ]] && echo -e "${green}IPv6 joke: $joke${reset}" || echo -e "${red}failed to fetch a joke via IPv6.${reset}"
else
echo -e "${red}IPv6 connectivity is not working for $interface. verify your routing and NAT settings.${reset}"
fi
else
echo -e "${red}no IPv6 address found on $interface. unable to fetch a joke via IPv6.${reset}"
fi
echo -e "${green}joke fetching processes completed for $interface.${reset}"
echo -e "------------------------------------"
sleep 3
echo
echo
echo -e "${yellow}### connectivity testing recommendations ###${reset}"
echo -e "${yellow}- use the following command to test WebSocket connectivity from an external client:${reset}"
echo -e "${yellow} wscat -c wss://<your-ip-address/ hostname>:9001 ${reset}"
echo -e "${yellow}- test UDP connectivity on port 51822 (commonly used for nym wireguard) ${reset}"
echo -e "${yellow} from another machine, use tools like nc or socat to send UDP packets ${reset}"
echo -e "${yellow} echo 'test message' | nc -u <your-ip-address> 51822 ${reset}"
echo -e "${yellow}if connectivity issues persist, ensure port forwarding and firewall rules are correctly configured ${reset}"
echo
}
configure_dns_and_icmp_wg() {
echo "allowing icmp (ping)..."
sudo iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
sudo iptables -A OUTPUT -p icmp --icmp-type echo-reply -j ACCEPT
echo "allowing dns over udp (port 53)..."
sudo iptables -A INPUT -p udp --dport 53 -j ACCEPT
echo "allowing dns over tcp (port 53)..."
sudo iptables -A INPUT -p tcp --dport 53 -j ACCEPT
echo "saving iptables rules..."
sudo iptables-save >/etc/iptables/rules.v4
echo "dns and icmp configuration completed."
}
case "$1" in
fetch_ipv6_address_nym_tun)
fetch_ipv6_address "$tunnel_interface"
;;
fetch_and_display_ipv6)
fetch_and_display_ipv6
;;
apply_iptables_rules)
apply_iptables_rules "$tunnel_interface"
;;
apply_iptables_rules_wg)
apply_iptables_rules "$wg_tunnel_interface"
;;
check_nymtun_iptables)
check_tunnel_iptables "$tunnel_interface"
;;
check_nym_wg_tun)
check_tunnel_iptables "$wg_tunnel_interface"
;;
check_ipv6_ipv4_forwarding)
check_ipv6_ipv4_forwarding
;;
check_ip_routing)
check_ip_routing
;;
perform_pings)
perform_pings
;;
joke_through_the_mixnet)
joke_through_tunnel "$tunnel_interface"
;;
joke_through_wg_tunnel)
joke_through_tunnel "$wg_tunnel_interface"
;;
configure_dns_and_icmp_wg)
configure_dns_and_icmp_wg
;;
adjust_ip_forwarding)
adjust_ip_forwarding
;;
remove_duplicate_rules)
remove_duplicate_rules "$2"
;;
*)
echo "Usage: $0 [command]"
echo "Commands:"
echo " fetch_ipv6_address_nym_tun - Fetch IPv6 for nymtun0."
echo " fetch_and_display_ipv6 - Show IPv6 on default device."
echo " apply_iptables_rules - Apply IPtables rules for nymtun0."
echo " apply_iptables_rules_wg - Apply IPtables rules for nymwg."
echo " check_nymtun_iptables - Check IPtables for nymtun0."
echo " check_nym_wg_tun - Check IPtables for nymwg."
echo " check_ipv6_ipv4_forwarding - Check IPv4 and IPv6 forwarding."
echo " check_ip_routing - Display IP routing tables."
echo " perform_pings - Test IPv4 and IPv6 connectivity."
echo " joke_through_the_mixnet - Fetch a joke via nymtun0."
echo " joke_through_wg_tunnel - Fetch a joke via nymwg."
echo " configure_dns_and_icmp_wg - Allows icmp ping tests for probes alongside configuring dns"
echo " adjust_ip_forwarding - Enable IPV6 and IPV4 forwarding"
echo " remove_duplicate_rules <interface> - Remove duplicate iptables rules. Valid interfaces: nymwg, nymtun0"
exit 1
;;
esac
echo "operation $1 completed successfully."
File diff suppressed because it is too large Load Diff
+186 -82
View File
@@ -1,6 +1,6 @@
#!/usr/bin/python3
__version__ = "1.1.0"
__version__ = "1.2.0"
__default_branch__ = "develop"
import os
@@ -22,17 +22,25 @@ class NodeSetupCLI:
def __init__(self, args):
self.branch = args.dev
self.welcome_message = self.print_welcome_message()
self.mode = self.prompt_mode()
self.mode = self._get_or_prompt_mode(args)
self.prereqs_install_sh = self.fetch_script("nym-node-prereqs-install.sh")
self.env_vars_install_sh = self.fetch_script("setup-env-vars.sh")
self.node_install_sh = self.fetch_script("nym-node-install.sh")
self.service_config_sh = self.fetch_script("setup-systemd-service-file.sh")
self.start_node_systemd_service_sh = self.fetch_script("start-node-systemd-service.sh")
self.landing_page_html = self._check_gwx_mode() and self.fetch_script("landing-page.html")
self.nginx_proxy_wss_sh = self._check_gwx_mode() and self.fetch_script("nginx_proxy_wss_sh")
self.tunnel_manager_sh = self._check_gwx_mode() and self.fetch_script("network_tunnel_manager.sh")
self.wg_ip_tables_manager_sh = self._check_gwx_mode() and self.fetch_script("wireguard-exit-policy-manager.sh")
self.wg_ip_tables_test_sh = self._check_gwx_mode() and self.fetch_script("exit-policy-tests.sh")
self.is_gwx = self.mode == "exit-gateway"
if self.is_gwx:
self.landing_page_html = self.fetch_script("landing-page.html")
self.nginx_proxy_wss_sh = self.fetch_script("nginx_proxy_wss_sh")
self.tunnel_manager_sh = self.fetch_script("network_tunnel_manager.sh")
self.quic_bridge_deployment_sh = self.fetch_script("quic_bridge_deployment.sh")
else:
self.landing_page_html = None
self.nginx_proxy_wss_sh = None
self.tunnel_manager_sh = None
self.wg_ip_tables_manager_sh = None
self.wg_ip_tables_test_sh = None
self.quic_bridge_deployment_sh = None
def print_welcome_message(self):
"""Welcome user, warns for needed pre-reqs and asks for confimation"""
@@ -45,7 +53,7 @@ class NodeSetupCLI:
self.print_character("=", 41)
msg = \
"Before you begin, make sure that:\n"\
"1. You run this setup on Debian based Linux (ie Ubuntu)\n"\
"1. You run this setup on Debian based Linux (ie Ubuntu 22.04 LTS)\n"\
"2. You run this installation program from a root shell\n"\
"3. You meet minimal requirements: https://nym.com/docs/operators/nodes\n"\
"4. You accept Operators Terms & Conditions: https://nym.com/operators-validators-terms\n"\
@@ -59,43 +67,103 @@ class NodeSetupCLI:
else:
print("Without confirming the points above, we cannot continue.")
exit(1)
def ensure_env_values(self, args):
"""Collect env vars from args or prompt interactively, then save to env.sh."""
env_file = Path("env.sh")
fields = [
("hostname", "HOSTNAME", "Enter hostname (if you don't use a DNS, press enter): "),
("location", "LOCATION", "Enter node location (country code or name): "),
("email", "EMAIL", "Enter your email: "),
("moniker", "MONIKER", "Enter node public moniker (visible in explorer & NymVPN app): "),
("description", "DESCRIPTION", "Enter short node public description: "),
]
def prompt_mode(self):
"""Ask user to insert node functionality and save it in python and bash envs"""
existing = self._read_env_file(env_file)
updated = {}
for arg_name, key, prompt in fields:
cli_val = getattr(args, arg_name, None)
value = cli_val.strip() if cli_val else existing.get(key) or input(prompt).strip()
updated[key] = value
os.environ[key] = value
# autodetect PUBLIC_IP if not already set
if not os.environ.get("PUBLIC_IP"):
try:
ip = subprocess.run(["curl", "-fsS4", "https://ifconfig.me"],
capture_output=True, text=True, timeout=5)
if ip.returncode == 0 and ip.stdout.strip():
updated["PUBLIC_IP"] = ip.stdout.strip()
os.environ["PUBLIC_IP"] = ip.stdout.strip()
except subprocess.TimeoutExpired:
print("[WARN] Timeout expired while trying to fetch public IP with curl.")
except FileNotFoundError:
print("[WARN] 'curl' command not found. Please install curl or set PUBLIC_IP manually.")
except subprocess.CalledProcessError as e:
print(f"[WARN] Error while running curl to fetch public IP: {e}")
# write all collected variables to env.sh in one go
self._upsert_env_vars(updated, env_file)
print(f"[OK] Updated env.sh with {len(updated)} entries.")
def _upsert_env_vars(self, updates: dict, env_file: Path = Path("env.sh")):
existing = self._read_env_file(env_file)
existing.update(updates)
with env_file.open("w") as f:
for k, v in existing.items():
f.write(f'export {k}="{v}"\n')
os.environ.update(updates)
def _read_env_file(self, env_file: Path) -> dict:
env = {}
if env_file.exists():
for line in env_file.read_text().splitlines():
if line.startswith("export ") and "=" in line:
k, v = line.replace("export ", "", 1).split("=", 1)
env[k.strip()] = v.strip().strip('"')
return env
def _get_or_prompt_mode(self, args):
"""Resolve MODE from --mode, env.sh, os.environ, or prompt; persist to env.sh."""
env_file = Path("env.sh")
# CLI arg
mode = getattr(args, "mode", None)
if mode:
mode = mode.strip().lower()
self._upsert_env_vars({"MODE": mode})
print(f"Mode set to '{mode}' from CLI argument.")
return mode
# env.sh (replaces manual read)
existing = self._read_env_file(env_file)
mode = existing.get("MODE")
if mode:
os.environ["MODE"] = mode
return mode
# process env
if os.environ.get("MODE"):
return os.environ["MODE"]
# prompt
mode = input(
"\nEnter the mode you want to run nym-node in: "
"\n1. mixnode "
"\n2. entry-gateway "
"\n3. exit-gateway (works as entry-gateway as well) "
"\nPress 1, 2 or 3 and enter:\n"
).strip()
if mode in ("1", "mixnode"):
mode = "mixnode"
elif mode in ("2", "entry-gateway"):
mode = "entry-gateway"
elif mode in ("3", "exit-gateway"):
mode = "exit-gateway"
else:
print("Only numbers 1, 2 or 3 are accepted.")
"\nEnter node mode (mixnode / entry-gateway / exit-gateway): "
).strip().lower()
if mode not in ("mixnode", "entry-gateway", "exit-gateway"):
print("Invalid mode. Must be one of: mixnode, entry-gateway, exit-gateway.")
raise SystemExit(1)
# save mode for this Python instance
self.mode = mode
os.environ["MODE"] = mode
# persist to env.sh so other scripts can source it
env_file = Path("env.sh")
with env_file.open("a") as f:
f.write(f'export MODE="{mode}"\n')
# source env.sh so future bash subprocesses see it immediately
subprocess.run("source ./env.sh", shell=True, executable="/bin/bash")
self._upsert_env_vars({"MODE": mode})
print(f"Mode set to '{mode}' — stored in env.sh and sourced for immediate use.")
return mode
def fetch_script(self, script_name):
"""Fetches needed scripts according to a defined mode"""
# print header only the first time
@@ -119,16 +187,15 @@ class NodeSetupCLI:
github_raw_nymtech_nym_scripts_url = f"https://raw.githubusercontent.com/nymtech/nym/refs/heads/{self.branch}/scripts/"
scripts_urls = {
"nym-node-prereqs-install.sh": f"{github_raw_nymtech_nym_scripts_url}nym-node-setup/nym-node-prereqs-install.sh",
"setup-env-vars.sh": f"{github_raw_nymtech_nym_scripts_url}nym-node-setup/setup-env-vars.sh",
"nym-node-install.sh": f"{github_raw_nymtech_nym_scripts_url}nym-node-setup/nym-node-install.sh",
"setup-systemd-service-file.sh": f"{github_raw_nymtech_nym_scripts_url}nym-node-setup/setup-systemd-service-file.sh",
"start-node-systemd-service.sh": f"{github_raw_nymtech_nym_scripts_url}nym-node-setup/start-node-systemd-service.sh",
"nginx_proxy_wss_sh": f"{github_raw_nymtech_nym_scripts_url}nym-node-setup/setup-nginx-proxy-wss.sh",
"landing-page.html": f"{github_raw_nymtech_nym_scripts_url}nym-node-setup/landing-page.html",
"network_tunnel_manager.sh": f"https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/network_tunnel_manager.sh",
"wireguard-exit-policy-manager.sh": f"https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/wireguard-exit-policy/wireguard-exit-policy-manager.sh",
"exit-policy-tests.sh": f"https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/wireguard-exit-policy/exit-policy-tests.sh",
"network_tunnel_manager.sh": f"{github_raw_nymtech_nym_scripts_url}nym-node-setup/network-tunnel-manager.sh",
"quic_bridge_deployment.sh": f"{github_raw_nymtech_nym_scripts_url}nym-node-setup/quic_bridge_deployment.sh"
}
return scripts_urls[script_init_name]
def run_script(
@@ -200,62 +267,61 @@ class NodeSetupCLI:
def _check_gwx_mode(self):
"""Helper: Several fns run only for GWx - this fn checks this condition"""
if self.mode == "exit-gateway":
return True
else:
return False
return self.mode == "exit-gateway"
def check_wg_enabled(self):
"""Checks if Wireguard is enabled and if not, prompts user if they want to enable it, stores it to env.sh"""
def check_wg_enabled(self, args=None):
"""Determine if WireGuard is enabled; precedence: CLI > env > env.sh > prompt. Persist normalized value."""
env_file = os.path.join(os.getcwd(), "env.sh")
env_file = os.path.abspath(os.path.join(os.getcwd(), "env.sh"))
def norm(v):
return "true" if str(v).strip().lower() == "true" else "false"
def norm(v): # -> "true" or "false"
return "true" if str(v).strip().lower() in ("1", "true", "yes", "y") else "false"
val = None
# precedence: process env → env.sh → prompt
val = os.environ.get("WIREGUARD")
# CLI argument
if args and getattr(args, "wireguard", None) is not None:
val = norm(getattr(args, "wireguard"))
print(f"[INFO] WireGuard mode provided via CLI: {val}")
if val is None and os.path.isfile(env_file):
try:
with open(env_file, "r", encoding="utf-8") as f:
m = re.search(r'^\s*export\s+WIREGUARD\s*=\s*"?([^"\n]+)"?', f.read(), re.M)
if m:
val = m.group(1)
except Exception:
pass
# Environment variable
val = val or os.environ.get("WIREGUARD")
# env.sh file
if val is None:
envs = self._read_env_file(Path(env_file))
val = envs.get("WIREGUARD")
# Prompt
if val is None:
ans = input(
"\nWireGuard is not configured.\n"
"Nodes routing WireGuard can be listed as both entry and exit in the app.\n"
"Enable WireGuard support? (y/n): "
"Enable WireGuard support? (Y/n): "
).strip().lower()
val = "true" if ans in ("y", "yes") else "false"
val = "true" if ans in ("", "y", "yes") else "false"
val = norm(val)
os.environ["WIREGUARD"] = val
# persist to env.sh (replace or append)
# Persist to env.sh
try:
text = ""
if os.path.isfile(env_file):
with open(env_file, "r", encoding="utf-8") as f:
with open(env_file, encoding="utf-8") as f:
text = f.read()
if re.search(r'^\s*export\s+WIREGUARD\s*=.*$', text, re.M):
text = re.sub(r'^\s*export\s+WIREGUARD\s*=.*$', f'export WIREGUARD="{val}"', text, flags=re.M)
else:
if text and not text.endswith("\n"):
text += "\n"
text += f'export WIREGUARD="{val}"\n'
text = (text.rstrip("\n") + "\n" if text else "") + f'export WIREGUARD="{val}"\n'
with open(env_file, "w", encoding="utf-8") as f:
f.write(text)
print(f'WIREGUARD={val} saved to {env_file}')
except Exception as e:
except OSError as e:
print(f"Warning: could not write {env_file}: {e}")
return (val == "true")
return val == "true"
def run_bash_command(self, command, args=None, *, env=None, cwd=None, check=True):
"""
@@ -316,10 +382,17 @@ class NodeSetupCLI:
"Setting up Wireguard IP tables to match Nym exit policy for mixnet, stored at: https://nymtech.net/.wellknown/network-requester/exit-policy.txt"
"\nThis may take a while, follow the steps below and don't kill the process..."
)
self.run_script(self.wg_ip_tables_manager_sh, args=["install"])
self.run_script(self.wg_ip_tables_manager_sh, args=["status"])
self.run_script(self.wg_ip_tables_test_sh)
self.run_script(self.tunnel_manager_sh, args=["exit_policy_install"])
def quic_bridge_deploy(self):
"""Setup QUIC bridge and configuration using external script"""
print("\n* * * Installing and configuring QUIC bridges * * *")
answer = input("\nDo you want to install, setup and run QUIC bridge? (Y/n) ").strip().lower()
if answer in ("", "y", "yes"):
self.run_script(self.quic_bridge_deployment_sh, args=["full_bridge_setup"])
else:
print("Skipping QUIC bridge setup.")
def run_nym_node_as_service(self):
"""Starts /etc/systemd/system/nym-node.service based on prompt using external script"""
@@ -347,8 +420,8 @@ class NodeSetupCLI:
if is_active:
while True:
ans = input(f"{service} is already running. Restart it now? (y/n):\n").strip().lower()
if ans == "y":
ans = input(f"{service} is already running. Restart it now? (Y/n):\n").strip().lower()
if ans in ("", "Y", "y"):
self.run_script(self.start_node_systemd_service_sh, args=["restart-poll"], env=run_env)
return
elif ans == "n":
@@ -358,8 +431,8 @@ class NodeSetupCLI:
print("Invalid input. Please press 'y' or 'n' and press enter.")
else:
while True:
ans = input(f"{service} is not running. Start it now? (y/n):\n").strip().lower()
if ans == "y":
ans = input(f"{service} is not running. Start it now? (Y/n):\n").strip().lower()
if ans in ("", "Y", "y"):
self.run_script(self.start_node_systemd_service_sh, args=["start-poll"], env=run_env)
return
elif ans == "n":
@@ -510,8 +583,12 @@ class NodeSetupCLI:
def run_node_installation(self,args):
"""Main function called by argparser command install running full node install flow"""
self.ensure_env_values(args)
# Pass uplink override to all helper scripts if provided
if getattr(args, "uplink_dev", None):
os.environ["UPLINK_DEV"] = args.uplink_dev
os.environ["NETWORK_DEVICE"] = args.uplink_dev
self.run_script(self.prereqs_install_sh)
self.run_script(self.env_vars_install_sh)
self.run_script(self.node_install_sh)
self.run_script(self.service_config_sh)
self._check_gwx_mode() and self.run_script(self.nginx_proxy_wss_sh)
@@ -521,7 +598,7 @@ class NodeSetupCLI:
self.run_tunnel_manager_setup()
if self.check_wg_enabled():
self.setup_test_wg_ip_tables()
self.setup_test_wg_ip_tables()
self.quic_bridge_deploy()
@@ -537,7 +614,7 @@ class ArgParser:
version=f"nym-node-cli {__version__}"
)
parent.add_argument("-d", "--dev", metavar="BRANCH",
help="Define github branch",
help="Define github branch (default: develop)",
type=str,
default=argparse.SUPPRESS)
parent.add_argument("-v", "--verbose", action="store_true",
@@ -553,11 +630,38 @@ class ArgParser:
subparsers = parser.add_subparsers(dest="command", help="subcommands")
subparsers.required = True
p_install = subparsers.add_parser(
install_parser = subparsers.add_parser(
"install", parents=[parent],
help="Starts nym-node installation setup CLI",
aliases=["i", "I"], add_help=True
)
install_parser.add_argument(
"--mode",
choices=["mixnode", "entry-gateway", "exit-gateway"],
help="Node mode: 'mixnode', 'entry-gateway', or 'exit-gateway'",
)
install_parser.add_argument(
"--wireguard-enabled",
choices=["true", "false"],
help="WireGuard functionality switch: true / false"
)
install_parser.add_argument("--hostname", help="Node domain / hostname")
install_parser.add_argument("--location", help="Node location (country code or name)")
install_parser.add_argument("--email", help="Contact email for the node operator")
install_parser.add_argument("--moniker", help="Public moniker displayed in explorer & NymVPN app")
install_parser.add_argument("--description", help="Short public description of the node")
install_parser.add_argument("--public-ip", help="External IPv4 address (autodetected if omitted)")
install_parser.add_argument("--nym-node-binary", help="URL for nym-node binary (autodetected if omitted)")
install_parser.add_argument("--uplink-dev", help="Override uplink interface used for NAT/FORWARD (e.g., 'eth0'; autodetected if omitted)")
# generic fallback
install_parser.add_argument(
"--env",
action="append",
metavar="KEY=VALUE",
help="(Optional) Extra ENV VARS, e.g. --env CUSTOM_KEY=value",
)
args = parser.parse_args()
+26 -21
View File
@@ -34,8 +34,9 @@ check_existing_config() {
if [[ "${resp}" =~ ^([Rr][Ee][Ss][Ee][Tt])$ ]]; then
echo
read -r -p "We are going to remove the existing node with configuration $NODE_CONFIG_DIR and replace it with a fresh one, do you want to back up the old one first? (y/n) " backup_ans
if [[ "${backup_ans}" =~ ^[Yy]$ ]]; then
echo "We are going to remove the existing node with configuration $NODE_CONFIG_DIR and replace it with a fresh one."
read -r -p "back up the old one first? (Y/n) " backup_ans
if [[ -z "${backup_ans}" || "${backup_ans}" =~ ^[Yy]$ ]]; then
ts="$(date +%Y%m%d-%H%M%S)"
backup_dir="$HOME/.nym/backup/$(basename "$NODE_CONFIG_DIR")-$ts"
echo "Backing up to: $backup_dir"
@@ -181,24 +182,27 @@ fi
NYM_NODE="$HOME/nym-binaries/nym-node"
# if binary already exists, ask to overwrite; if yes, remove first
# if binary already exists, ask to overwrite; if yes, remove first; if no, skip download
if [[ -e "${NYM_NODE}" ]]; then
echo
echo -e "\n* * * A nym-node binary already exists at: ${NYM_NODE}"
read -r -p "Overwrite with the latest release? (y/n): " ow_ans
if [[ "${ow_ans}" =~ ^[Yy]$ ]]; then
echo "Removing existing binary to avoid 'text file busy'..."
rm -f "${NYM_NODE}"
else
echo "Keeping existing binary."
fi
fi
read -r -p "Overwrite with the latest release? (Y/n): " ow_ans
download_nym_node "$LATEST_TAG_URL" "$NYM_NODE"
if [[ -z "${ow_ans}" || "${ow_ans}" =~ ^[Yy]$ ]]; then
echo "Removing existing binary..."
rm -f "${NYM_NODE}"
download_nym_node "$LATEST_TAG_URL" "$NYM_NODE"
else
echo "Keeping existing binary. Skipping download."
fi
else
# binary does not exist → must download
download_nym_node "$LATEST_TAG_URL" "$NYM_NODE"
fi
echo -e "\n * * * Making binary executable * * *"
chmod +x "${NYM_NODE}"
echo "---------------------------------------------------"
echo "Nym node binary downloaded:"
"${NYM_NODE}" --version || true
@@ -225,17 +229,18 @@ WIREGUARD="${WIREGUARD:-}"
if [[ ( "$MODE" == "entry-gateway" || "$MODE" == "exit-gateway" ) && ( -n "${ASK_WG:-}" || -z "$WIREGUARD" ) ]]; then
echo
echo "Gateways can also route WireGuard in NymVPN."
echo "Enabling it means your node may be listed as both entry and exit in the app."
# show current default in the prompt if present
def_hint=""
[[ -n "${WIREGUARD}" ]] && def_hint=" [current: ${WIREGUARD}]"
read -r -p "Enable WireGuard support? (y/n)${def_hint}: " answer || true
case "${answer:-}" in
[Yy]* ) WIREGUARD="true" ;;
[Nn]* ) WIREGUARD="false" ;;
* ) : ;; # keep existing value if user just pressed enter
esac
read -r -p "Enable WireGuard support? (Y/n)${def_hint}: " answer || true
if [[ -z "${answer}" || "${answer}" =~ ^[Yy]$ ]]; then
WIREGUARD="true"
elif [[ "${answer}" =~ ^[Nn]$ ]]; then
WIREGUARD="false"
fi
fi
# final default only if still empty
WIREGUARD="${WIREGUARD:-false}"
@@ -1,29 +1,13 @@
#!/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
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
ufw enable
ufw allow 22/tcp # SSH - you're in control of these ports
ufw allow 80/tcp # HTTP
ufw allow 443/tcp # HTTPS
ufw allow 1789/tcp # Nym specific
ufw allow 1790/tcp # Nym specific
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 reload && \
ufw status
@@ -47,7 +47,17 @@ NYM_ETC_BRIDGES="$NYM_ETC_DIR/bridges.toml"
NYM_ETC_CLIENT_PARAMS_DEFAULT="$NYM_ETC_DIR/client_bridge_params.json"
SERVICE_FILE="/etc/systemd/system/nym-bridge.service"
NET_DEV="$(ip route show default 2>/dev/null | awk '/default/ {print $5}' || true)"
NET_DEV="${UPLINK_DEV:-}"
if [[ -z "$NET_DEV" ]]; then
NET_DEV="$(ip -o route show default 2>/dev/null | awk '{print $5}' | head -n1)"
[[ -z "$NET_DEV" ]] && NET_DEV="$(ip -o route show default table all 2>/dev/null | awk '{print $5}' | head -n1)"
fi
if [[ -z "$NET_DEV" ]]; then
echo -e "${RED}Cannot determine uplink interface. Set UPLINK_DEV.${RESET}" | tee -a "$LOG_FILE"
exit 1
fi
echo "Using uplink device: $NET_DEV"
WG_IFACE="nymwg"
# Root check
@@ -65,6 +75,17 @@ err() { echo -e "${RED}$1${RESET}"; }
info() { echo -e "${CYAN}$1${RESET}"; }
press_enter() { read -r -p "$1"; }
# Disable pauses and interactive prompts for noninteractive mode
if [[ "${NONINTERACTIVE:-0}" == "1" ]]; then
# all pauses become no-ops
press_enter() { :; }
# silence any "enter path:" prompts
echo_prompt() { :; }
else
echo_prompt() { echo -n "$1"; }
fi
# Helper: detect dpkg dependency failure for libc6>=2.34
deb_depends_libc_too_old() {
local v
@@ -176,13 +197,31 @@ verify_bridge_prerequisites() {
}
adjust_ip_forwarding() {
title "Adjusting IP Forwarding"
sed -i '/^net\.ipv4\.ip_forward=/d' /etc/sysctl.conf
sed -i '/^net\.ipv6\.conf\.all\.forwarding=/d' /etc/sysctl.conf
echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
echo "net.ipv6.conf.all.forwarding=1" >> /etc/sysctl.conf
sysctl -p /etc/sysctl.conf
ok "IPv4/IPv6 forwarding enabled."
title "Checking IP forwarding"
local v4 v6
v4="$(cat /proc/sys/net/ipv4/ip_forward 2>/dev/null || echo 0)"
v6="$(cat /proc/sys/net/ipv6/conf/all/forwarding 2>/dev/null || echo 0)"
if [[ "$v4" == "1" ]]; then
ok "IPv4 forwarding is enabled."
else
warn "IPv4 forwarding is not enabled."
fi
if [[ "$v6" == "1" ]]; then
ok "IPv6 forwarding is enabled."
else
warn "IPv6 forwarding is not enabled."
fi
if [[ "$v4" != "1" || "$v6" != "1" ]]; then
echo
echo "To enable forwarding and routing consistently, run the network tunnel manager script as root."
echo "For example:"
echo " ./network-tunnel-manager.sh complete_networking_configuration"
echo "or:"
echo " ./network-tunnel-manager.sh adjust_ip_forwarding"
fi
}
# Install nym-bridge
@@ -377,6 +416,11 @@ User=root
ExecStart=$BRIDGE_BIN --config $NYM_ETC_BRIDGES
Restart=on-failure
RestartSec=5
LimitNOFILE=65535
ProtectSystem=full
ProtectHome=yes
PrivateTmp=yes
[Install]
WantedBy=multi-user.target
@@ -390,21 +434,40 @@ EOF
# IPTABLES & helpers
apply_bridge_iptables_rules() {
title "Applying iptables rules"
iptables -I INPUT -i "$WG_IFACE" -j ACCEPT || true
ip6tables -I INPUT -i "$WG_IFACE" -j ACCEPT || true
iptables -t nat -A POSTROUTING -o "$NET_DEV" -j MASQUERADE || true
ip6tables -t nat -A POSTROUTING -o "$NET_DEV" -j MASQUERADE || true
iptables-save > /etc/iptables/rules.v4
ip6tables-save > /etc/iptables/rules.v6
ok "iptables rules applied."
title "Checking iptables rules for bridge routing"
echo "Inspecting current iptables state for interface ${WG_IFACE} and uplink ${NET_DEV}."
echo
echo "IPv4 FORWARD:"
iptables -L FORWARD -n -v 2>/dev/null | sed -n '1,20p' || echo "iptables not available."
echo
echo "IPv4 NAT POSTROUTING:"
iptables -t nat -L POSTROUTING -n -v 2>/dev/null | sed -n '1,20p' || true
echo
echo "IPv6 FORWARD:"
ip6tables -L FORWARD -n -v 2>/dev/null | sed -n '1,20p' || true
echo
echo "IPv6 NAT POSTROUTING:"
ip6tables -t nat -L POSTROUTING -n -v 2>/dev/null | sed -n '1,20p' || true
echo
echo "This script no longer changes iptables. To configure routing and NAT for nym, use the network tunnel manager script."
echo "For example (run as root):"
echo " ./network-tunnel-manager.sh complete_networking_configuration"
}
configure_dns_and_icmp() {
title "Allow ICMP and DNS"
iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT || true
ip6tables -A INPUT -p ipv6-icmp -j ACCEPT || true
ok "ICMP and DNS rules applied."
title "Checking ICMP and DNS firewall rules"
echo "IPv4 INPUT rules related to ICMP and DNS:"
iptables -L INPUT -n -v 2>/dev/null | grep -E 'icmp|dpt:53' || echo "no matching IPv4 rules shown."
echo
echo "IPv6 INPUT rules related to ICMP and DNS:"
ip6tables -L INPUT -n -v 2>/dev/null | grep -E 'icmp|dpt:53' || echo "no matching IPv6 rules shown."
echo
echo "If ping or DNS are blocked for bridge traffic, adjust your firewall using the network tunnel manager script or your chosen firewall tool."
}
# Full interactive setup
@@ -429,6 +492,8 @@ full_bridge_setup() {
echo ""
echo "Step 2/6: Installing bridge binary..."
install_bridge_binary
echo "[Bridge Install] $(date '+%F %T') $( $BRIDGE_BIN --version 2>/dev/null || echo 'nym-bridge (unknown)')" \
>> /var/log/nym-bridge-version.log
press_enter "Press Enter to continue..."
echo ""
@@ -447,7 +512,7 @@ full_bridge_setup() {
press_enter "Press Enter to continue..."
echo ""
echo "Step 6/6: Configuring network rules (optional but recommended)..."
echo "Step 6/6: Checking network rules and forwarding status..."
adjust_ip_forwarding
apply_bridge_iptables_rules
configure_dns_and_icmp
-10
View File
@@ -39,16 +39,6 @@ while true; do
esac
done
# try to get the latest binary URL (non-fatal if missing)
LATEST_BINARY=$(
curl -fsSL https://github.com/nymtech/nym/releases/latest \
| grep -Eo 'href="/nymtech/nym/releases/download/[^"]+/nym-node"' \
| head -n1 \
| cut -d'"' -f2
)
if [[ -z "${LATEST_BINARY:-}" ]]; then
echo "WARNING: Could not determine latest nym-node binary URL right now. The installer will resolve it later."
fi
PUBLIC_IP=$(curl -fsS -4 https://ifconfig.me || true)
PUBLIC_IP=${PUBLIC_IP:-""}
+111 -276
View File
@@ -1,315 +1,136 @@
#!/usr/bin/env bash
set -euo pipefail
# load env (prefer absolute ENV_FILE injected by Python CLI; fallback to ./env.sh)
if [[ "$(id -u)" -ne 0 ]]; then
echo "This script must be run as root."
exit 1
fi
# load env
if [[ -n "${ENV_FILE:-}" && -f "${ENV_FILE}" ]]; then
set -a; . "${ENV_FILE}"; set +a
elif [[ -f "./env.sh" ]]; then
set -a; . ./env.sh; set +a
fi
: "${HOSTNAME:?HOSTNAME not set in env.sh}"
: "${EMAIL:?EMAIL not set in env.sh}"
: "${HOSTNAME:?HOSTNAME not set}"
: "${EMAIL:?EMAIL not set}"
export SYSTEMD_PAGER=""
export SYSTEMD_COLORS="0"
DEBIAN_FRONTEND=noninteractive
export DEBIAN_FRONTEND=noninteractive
# sanity check
if [[ "${HOSTNAME}" == "localhost" || "${HOSTNAME}" == "127.0.0.1" || "${HOSTNAME}" == "ubuntu" ]]; then
echo "ERROR: HOSTNAME cannot be 'localhost'. Use a public FQDN." >&2
exit 1
fi
echo -e "\n* * * Starting nginx configuration for landing page, reverse proxy and WSS * * *"
# set paths & ports vars
WEBROOT="/var/www/${HOSTNAME}"
LE_ACME_DIR="/var/www/letsencrypt"
SITES_AVAIL="/etc/nginx/sites-available"
SITES_EN="/etc/nginx/sites-enabled"
BASE_HTTP="${SITES_AVAIL}/${HOSTNAME}" # :80 vhost
BASE_HTTPS="${SITES_AVAIL}/${HOSTNAME}-ssl" # :443 vhost (well write it ourselves)
WSS_AVAIL="${SITES_AVAIL}/wss-config-nym"
BACKUP_DIR="/etc/nginx/sites-backups"
NYM_PORT_HTTP="${NYM_PORT_HTTP:-8080}"
NYM_PORT_WSS="${NYM_PORT_WSS:-9000}"
WSS_LISTEN_PORT="${WSS_LISTEN_PORT:-9001}"
HTTP_CONF="${SITES_AVAIL}/${HOSTNAME}"
WSS_CONF="${SITES_AVAIL}/wss-config-nym"
mkdir -p "${WEBROOT}" "${LE_ACME_DIR}" "${BACKUP_DIR}" "${SITES_AVAIL}" "${SITES_EN}"
echo
echo "* * * Starting nginx configuration for landing page, reverse proxy and WSS * * *"
# helpers
neat_backup() {
local file="$1"; [[ -f "$file" ]] || return 0
local sha_now; sha_now="$(sha256sum "$file" | awk '{print $1}')" || return 0
local tag; tag="$(basename "$file")"
local latest="${BACKUP_DIR}/${tag}.latest"
if [[ -f "$latest" ]]; then
local sha_prev; sha_prev="$(awk '{print $1}' "$latest")"
[[ "$sha_now" == "$sha_prev" ]] && return 0
fi
cp -a "$file" "${BACKUP_DIR}/${tag}.bak.$(date +%s)"
echo "$sha_now ${tag}" > "$latest"
ls -1t "${BACKUP_DIR}/${tag}.bak."* 2>/dev/null | tail -n +6 | xargs -r rm -f
}
###############################################################################
# step 1: ensure landing page exists (local fetch -> github -> template)
###############################################################################
ensure_enabled() {
local src="$1"; local name; name="$(basename "$src")"
ln -sf "$src" "${SITES_EN}/${name}"
}
mkdir -p "${WEBROOT}"
cert_ok() {
[[ -s "/etc/letsencrypt/live/${HOSTNAME}/fullchain.pem" && -s "/etc/letsencrypt/live/${HOSTNAME}/privkey.pem" ]]
}
SCRIPT_DIR="$(dirname "${ENV_FILE:-./env.sh}")"
LOCAL_FETCHED_PAGE="${SCRIPT_DIR}/landing-page.html"
fetch_landing_html() {
local url="https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/nym-node-setup/landing-page.html"
mkdir -p "${WEBROOT}"
if command -v curl >/dev/null 2>&1; then
curl -fsSL "$url" -o "${WEBROOT}/index.html" || true
else
wget -qO "${WEBROOT}/index.html" "$url" || true
fi
if [[ ! -s "${WEBROOT}/index.html" ]]; then
cat > "${WEBROOT}/index.html" <<'HTML'
if [[ -s "${LOCAL_FETCHED_PAGE}" ]]; then
cp "${LOCAL_FETCHED_PAGE}" "${WEBROOT}/index.html"
elif curl -fsSL \
https://raw.githubusercontent.com/nymtech/nym/develop/scripts/nym-node-setup/landing-page.html \
-o "${WEBROOT}/index.html"; then
:
else
cat > "${WEBROOT}/index.html" <<EOF
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Nym Exit Gateway</title>
<style>
body {
font-family: sans-serif;
text-align: center;
padding: 2em;
background-color: #111;
color: #0ff;
}
h1 {
margin-bottom: 0.5em;
}
</style>
</head>
<body>
<h1>Nym Exit Gateway</h1>
<p>This is a Nym Exit Gateway. The operator of this router has no access to any of the data routing through that due to encryption design.</p>
<html>
<head><title>nym node</title></head>
<body style="font-family:sans-serif;text-align:center;padding:2em;">
<h1>nym exit gateway</h1>
<p>this is a nym exit gateway.</p>
<p>Operator contact: <a href="mailto:${EMAIL}">${EMAIL}</a></p>
</body>
</html>
HTML
fi
}
EOF
fi
inject_email() {
local file="${WEBROOT}/index.html"
[[ -n "${EMAIL:-}" && -s "$file" ]] || return 0
# Escape characters that would break sed replacement
local esc_email
esc_email="$(printf '%s' "$EMAIL" | sed -e 's/[&/\]/\\&/g')"
# try to update existing meta (case-insensitive on the name attr)
if grep -qiE '<meta[^>]+name=["'"'"']contact:email["'"'"']' "$file"; then
sed -i -E \
"s|(<meta[^>]+name=[\"']contact:email[\"'][^>]*content=\")[^\"]*(\"[^>]*>)|\1${esc_email}\2|I" \
"$file" || true
return 0
fi
# insert before </head> if present (case-insensitive)
if grep -qi '</head>' "$file"; then
awk -v email="$EMAIL" '
BEGIN{IGNORECASE=1}
/<\/head>/ && !done {
print " <meta name=\"contact:email\" content=\"" email "\">"
done=1
}
{ print }
' "$file" > "${file}.tmp" && mv "${file}.tmp" "$file"
return 0
fi
# fallback: append at end
printf '\n<meta name="contact:email" content="%s">\n' "$EMAIL" >> "$file" || true
}
fetch_logo() {
local logo_url="https://raw.githubusercontent.com/nymtech/websites/refs/heads/main/www/nym.com/public/images/Nym_meta_Image.png?token=GHSAT0AAAAAACEERII7URYRTFACZ4F2OWZ42GMCPBQ"
mkdir -p "${WEBROOT}/images"
if [[ ! -s "${WEBROOT}/images/nym_logo.png" ]]; then
if command -v curl >/dev/null 2>&1; then
curl -fsSL "$logo_url" -o "${WEBROOT}/images/nym_logo.png" || true
else
wget -qO "${WEBROOT}/images/nym_logo.png" "$logo_url" || true
fi
fi
}
reload_nginx() { nginx -t && systemctl reload nginx; }
# landing page (idempotent)
fetch_landing_html
inject_email
fetch_logo
echo "Landing page at ${WEBROOT}/index.html"
# disable default and stale SSL configs
###############################################################################
# step 2: remove default site and old configs, restart nginx
###############################################################################
echo "Cleaning existing nginx configuration"
# remove default nginx site
[[ -L "${SITES_EN}/default" ]] && unlink "${SITES_EN}/default" || true
for f in "${SITES_EN}"/*; do
[[ -L "$f" ]] || continue
if grep -q "/etc/letsencrypt/live/localhost" "$f"; then
echo "Disabling vhost referencing localhost cert: $f"; unlink "$f"
fi
done
for f in "${SITES_EN}"/*; do
[[ -L "$f" ]] || continue
if grep -qE 'listen\s+.*443' "$f"; then
cert=$(awk '/ssl_certificate[ \t]+/ {print $2}' "$f" | tr -d ';' | head -n1)
key=$(awk '/ssl_certificate_key[ \t]+/ {print $2}' "$f" | tr -d ';' | head -n1)
if [[ -n "${cert:-}" && ! -s "$cert" ]] || [[ -n "${key:-}" && ! -s "$key" ]]; then
echo "Disabling SSL vhost with missing cert/key: $f"; unlink "$f"
fi
fi
done
# HTTP :80 vhost (ACME-safe, proxy to :8080)
neat_backup "${BASE_HTTP}"
cat > "${BASE_HTTP}" <<EOF
# optional: remove default available config if present
rm -f /etc/nginx/sites-available/default || true
# remove old vhosts for this domain
rm -f "${SITES_EN}/${HOSTNAME}" || true
rm -f "${SITES_EN}/${HOSTNAME}-ssl" || true
rm -f "${SITES_EN}/wss-config-nym" || true
rm -f "${HTTP_CONF}" || true
rm -f "${WSS_CONF}" || true
systemctl restart nginx || systemctl start nginx
###############################################################################
# step 3: create basic HTTP config like manual flow (80 -> 8080)
###############################################################################
cat > "${HTTP_CONF}" <<EOF
server {
listen 80;
listen [::]:80;
server_name ${HOSTNAME};
# ACME challenge path (HTTP only)
location ^~ /.well-known/acme-challenge/ {
root ${LE_ACME_DIR};
default_type "text/plain";
}
root ${WEBROOT};
index index.html;
location = /favicon.ico { return 204; access_log off; log_not_found off; }
location / {
try_files \$uri \$uri/ @app;
}
location @app {
proxy_pass http://127.0.0.1:${NYM_PORT_HTTP};
proxy_pass http://127.0.0.1:8080;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header Host \$host;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
}
}
EOF
ensure_enabled "${BASE_HTTP}"
reload_nginx
systemctl status nginx --no-pager | sed -n '1,6p' || true
# ACME preflight (informative)
echo -e "\n* * * ACME preflight checks * * *"
if ! curl -fsSL https://acme-v02.api.letsencrypt.org/directory >/dev/null; then
echo "WARNING: Can't reach Let's Encrypt directory. We'll still keep HTTP up." >&2
fi
THIS_IP="$(curl -fsS -4 https://ifconfig.me || true)"
DNS_IP="$(getent ahostsv4 "${HOSTNAME}" 2>/dev/null | awk '{print $1; exit}')"
echo "Public IPv4: ${THIS_IP:-unknown} DNS A(${HOSTNAME}): ${DNS_IP:-unresolved}"
if [[ -n "${THIS_IP:-}" && -n "${DNS_IP:-}" && "${THIS_IP}" != "${DNS_IP}" ]]; then
echo "WARNING: DNS for ${HOSTNAME} does not match this server's public IPv4."
fi
timedatectl show -p NTPSynchronized --value 2>/dev/null | grep -qi yes || timedatectl set-ntp true || true
ln -sf "${HTTP_CONF}" "${SITES_EN}/${HOSTNAME}"
# install certbot if missing
if ! command -v certbot >/dev/null 2>&1; then
if command -v snap >/dev/null 2>&1; then
snap install core || true; snap refresh core || true
snap install --classic certbot; ln -sf /snap/bin/certbot /usr/bin/certbot
else
apt-get update -y >/dev/null 2>&1 || true
apt-get install -y certbot >/dev/null 2>&1 || true
fi
fi
nginx -t
systemctl daemon-reload
systemctl restart nginx
# issue/renew via WEBROOT (no nginx auto-edit), non-fatal if it fails
STAGING_FLAG=""; [[ "${CERTBOT_STAGING:-0}" == "1" ]] && STAGING_FLAG="--staging" && echo "Using Let's Encrypt STAGING."
if ! cert_ok; then
certbot certonly --non-interactive --agree-tos -m "${EMAIL}" -d "${HOSTNAME}" \
--webroot -w "${LE_ACME_DIR}" ${STAGING_FLAG} || true
fi
###############################################################################
# step 4: install certbot and obtain certificate (letsencrypt)
###############################################################################
# create own 443 vhost (only if certs exist)
if cert_ok; then
neat_backup "${BASE_HTTPS}"
cat > "${BASE_HTTPS}" <<EOF
apt-get update -y >/dev/null 2>&1 || true
apt-get install -y certbot python3-certbot-nginx >/dev/null 2>&1 || true
echo "Requesting Let's Encrypt certificate for ${HOSTNAME}"
certbot --nginx --non-interactive --agree-tos --redirect --reuse-key \
-m "${EMAIL}" -d "${HOSTNAME}" || true
###############################################################################
# step 5: create WSS 9001 config using certbot-generated certs
###############################################################################
if [[ -s "/etc/letsencrypt/live/${HOSTNAME}/fullchain.pem" ]]; then
echo "Certificate detected, creating WSS config"
cat > "${WSS_CONF}" <<EOF
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name ${HOSTNAME};
listen 9001 ssl http2;
listen [::]:9001 ssl http2;
ssl_certificate /etc/letsencrypt/live/${HOSTNAME}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/${HOSTNAME}/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
root ${WEBROOT};
index index.html;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
location = /favicon.ico { return 204; access_log off; log_not_found off; }
location / {
try_files \$uri \$uri/ @app;
}
location @app {
proxy_pass http://127.0.0.1:${NYM_PORT_HTTP};
proxy_http_version 1.1;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header Host \$host;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
}
}
EOF
ensure_enabled "${BASE_HTTPS}"
# optional: redirect HTTP->HTTPS (keeps ACME path in HTTP too via separate small server)
neat_backup "${BASE_HTTP}"
cat > "${BASE_HTTP}" <<EOF
server {
listen 80;
listen [::]:80;
server_name ${HOSTNAME};
# Keep ACME reachable over HTTP:
location ^~ /.well-known/acme-challenge/ {
root ${LE_ACME_DIR};
default_type "text/plain";
}
# Redirect the rest to HTTPS
location / {
return 301 https://\$host\$request_uri;
}
}
EOF
ensure_enabled "${BASE_HTTP}"
reload_nginx
else
echo "NOTE: Cert not present yet; HTTPS (443) will not listen. Only HTTP (80) is active."
fi
# WSS TLS :9001 (only if certs exist)
if cert_ok; then
neat_backup "${WSS_AVAIL}"
cat > "${WSS_AVAIL}" <<EOF
server {
listen ${WSS_LISTEN_PORT} ssl http2;
listen [::]:${WSS_LISTEN_PORT} ssl http2;
server_name ${HOSTNAME};
ssl_certificate /etc/letsencrypt/live/${HOSTNAME}/fullchain.pem;
@@ -320,7 +141,11 @@ server {
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
location = /favicon.ico { return 204; access_log off; log_not_found off; }
location /favicon.ico {
return 204;
access_log off;
log_not_found off;
}
location / {
add_header 'Access-Control-Allow-Origin' '*' always;
@@ -333,20 +158,30 @@ server {
proxy_set_header Connection "Upgrade";
proxy_set_header X-Forwarded-For \$remote_addr;
proxy_pass http://127.0.0.1:${NYM_PORT_WSS};
proxy_pass http://localhost:9000;
proxy_intercept_errors on;
}
}
EOF
ensure_enabled "${WSS_AVAIL}"
reload_nginx
ln -sf "${WSS_CONF}" "${SITES_EN}/wss-config-nym"
nginx -t
systemctl daemon-reload
systemctl restart nginx
else
echo "Certificate missing, skipping WSS config"
fi
echo -e "\nDone."
if cert_ok; then
echo "HTTP : http://${HOSTNAME}/ (redirects to HTTPS)"
echo "TLS : https://${HOSTNAME}/ (served by nginx)"
echo "WSS : wss://${HOSTNAME}:${WSS_LISTEN_PORT}/ (served by nginx)"
###############################################################################
# step 6: summary
###############################################################################
echo "done."
echo "http : http://${HOSTNAME}"
if [[ -s "/etc/letsencrypt/live/${HOSTNAME}/fullchain.pem" ]]; then
echo "https : https://${HOSTNAME}"
echo "wss : wss://${HOSTNAME}:9001"
else
echo "Only HTTP is active (no cert yet). Re-run after DNS/ACME is ready to enable HTTPS + WSS."
echo "https not active yet (no cert)"
fi
@@ -83,8 +83,8 @@ fi
# interactive path (manual runs)
ensure_mode
read -rp "Service file not found. Create it now? (y/n): " create_ans
if [[ "${create_ans:-}" =~ ^[Yy]$ ]]; then
read -rp "Service file not found. Create it now? (Y/n): " create_ans
if [[ -z "${create_ans}" || "${create_ans}" =~ ^[Yy]$ ]]; then
create_service_file
else
echo "Not creating the service file."
@@ -1,240 +0,0 @@
#!/bin/bash
# Nym Exit Policy Verification Unit Tests
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[0;33m'
NC='\033[0m'
NYM_CHAIN="NYM-EXIT"
WG_INTERFACE="nymwg"
check_port_range_rules() {
local port_range="$1"
local protocol="${2:-tcp}"
local chain="${3:-$NYM_CHAIN}"
# Extract start and end ports
local start_port=$(echo "$port_range" | cut -d'-' -f1)
local end_port=$(echo "$port_range" | cut -d'-' -f2)
if iptables -t filter -C "$chain" -p "$protocol" --dport "$start_port:$end_port" -j ACCEPT 2>/dev/null; then
echo -e "${GREEN}✓ Rule exists: $chain $protocol port range $start_port:$end_port${NC}"
return 0
else
echo -e "${RED}✗ Rule missing: $chain $protocol port range $start_port:$end_port${NC}"
echo -e "${YELLOW}Dumping all rules in $chain:${NC}"
iptables -L "$chain" -n | grep "$protocol"
return 1
fi
}
# Test port range rules
test_port_range_rules() {
echo -e "${YELLOW}Testing Port Range Rules...${NC}"
# Select the essential port ranges for testing
local port_ranges=(
"20-21:tcp:FTP"
"80-81:tcp:HTTP"
"2082-2083:tcp:CPanel"
"5222-5223:tcp:XMPP"
"27000-27050:tcp:Steam (sampling)"
"989-990:tcp:FTP over TLS"
"5000-5005:tcp:RTP/VoIP"
"8087-8088:tcp:Simplify Media"
"8232-8233:tcp:Zcash"
"8332-8333:tcp:Bitcoin"
"18080-18081:tcp:Monero"
)
local total_failures=0
for range in "${port_ranges[@]}"; do
IFS=':' read -r port_range protocol service <<< "$range"
# Extract start and end ports
local start_port=$(echo "$port_range" | cut -d'-' -f1)
local end_port=$(echo "$port_range" | cut -d'-' -f2)
echo -e "${YELLOW}Testing $service $protocol port range $port_range${NC}"
if iptables -t filter -C "$NYM_CHAIN" -p "$protocol" --dport "$start_port:$end_port" -j ACCEPT 2>/dev/null; then
echo -e "${GREEN}✓ Rule exists: $NYM_CHAIN $protocol port range $start_port:$end_port${NC}"
else
echo -e "${RED}✗ Rule missing: $NYM_CHAIN $protocol port range $start_port:$end_port${NC}"
((total_failures++))
echo -e "${YELLOW}Existing rules for protocol $protocol:${NC}"
iptables -L "$NYM_CHAIN" -n | grep "$protocol"
fi
done
if [ $total_failures -eq 0 ]; then
return 0
else
return 1
fi
}
test_critical_services() {
echo -e "${YELLOW}Testing Critical Service Rules...${NC}"
local tcp_services=(
22 # SSH
53 # DNS
443 # HTTPS
853 # DNS over TLS
1194 # OpenVPN
)
local udp_services=(
53 # DNS
123 # NTP
1194 # OpenVPN
)
local failures=0
# Test TCP services
for port in "${tcp_services[@]}"; do
local rule_found=false
# First check for exact match
if iptables -t filter -C "$NYM_CHAIN" -p tcp --dport "$port" -j ACCEPT 2>/dev/null; then
echo -e "${GREEN}✓ Rule exists: NYM-EXIT tcp port $port${NC}"
rule_found=true
else
# If not found as exact port, search for it in port ranges
# This checks if the port is covered by any range rule
if iptables-save | grep -E "^-A $NYM_CHAIN.*tcp.*dpts:" | grep -qP "dpts:(\d+:)?$port(:|\d+)" || \
iptables-save | grep -E "^-A $NYM_CHAIN.*tcp.*dpts:" | grep -qP "dpts:$port:"; then
echo -e "${GREEN}✓ Rule exists: NYM-EXIT tcp port $port (covered by a range rule)${NC}"
rule_found=true
else
echo -e "${RED}✗ Rule missing: NYM-EXIT tcp port $port${NC}"
((failures++))
fi
fi
done
# Test UDP services - similar approach
for port in "${udp_services[@]}"; do
local rule_found=false
if iptables -t filter -C "$NYM_CHAIN" -p udp --dport "$port" -j ACCEPT 2>/dev/null; then
echo -e "${GREEN}✓ Rule exists: NYM-EXIT udp port $port${NC}"
rule_found=true
else
# If not found as exact port, search for it in port ranges
if iptables-save | grep -E "^-A $NYM_CHAIN.*udp.*dpts:" | grep -qP "dpts:(\d+:)?$port(:|\d+)" || \
iptables-save | grep -E "^-A $NYM_CHAIN.*udp.*dpts:" | grep -qP "dpts:$port:"; then
echo -e "${GREEN}✓ Rule exists: NYM-EXIT udp port $port (covered by a range rule)${NC}"
rule_found=true
else
echo -e "${RED}✗ Rule missing: NYM-EXIT udp port $port${NC}"
((failures++))
fi
fi
done
echo -e "${YELLOW}Relevant existing rules for HTTP (port 80):${NC}"
iptables-save | grep -E "$NYM_CHAIN.*tcp" | grep -E "(dpt|dpts):.*80"
return $failures
}
# Verify default reject rule exists
test_default_reject_rule() {
echo -e "${YELLOW}This test takes some time, do not quit the process${NC}"
echo
echo -e "${YELLOW}Testing Default Reject Rule...${NC}"
# Try different patterns to detect the reject rule
if iptables -L "$NYM_CHAIN" | grep -q "REJECT.*all.*anywhere.*anywhere.*reject-with"; then
echo -e "${GREEN}✓ Default REJECT rule exists${NC}"
return 0
elif iptables -L "$NYM_CHAIN" | grep -q "REJECT.*all -- .*everywhere.*everywhere"; then
echo -e "${GREEN}✓ Default REJECT rule exists${NC}"
return 0
elif iptables -L "$NYM_CHAIN" | grep -q "REJECT.*all.*0.0.0.0/0.*0.0.0.0/0"; then
echo -e "${GREEN}✓ Default REJECT rule exists${NC}"
return 0
elif iptables -n -L "$NYM_CHAIN" | grep -qE "REJECT.*all.*0.0.0.0/0.*0.0.0.0/0"; then
echo -e "${GREEN}✓ Default REJECT rule exists${NC}"
return 0
elif iptables -L "$NYM_CHAIN" | tail -1 | grep -q "REJECT"; then
echo -e "${GREEN}✓ Default REJECT rule exists at the end of chain${NC}"
return 0
else
echo -e "${RED}✗ Default REJECT rule missing${NC}"
# Display the last 3 rules in the chain for debugging
echo -e "${YELLOW}Last 3 rules in the chain:${NC}"
iptables -L "$NYM_CHAIN" | tail -3
return 1
fi
}
run_all_tests() {
local total_failures=0
local total_tests=0
local skip_default_reject=false
# Parse arguments
while [[ $# -gt 0 ]]; do
case "$1" in
--skip-default-reject)
skip_default_reject=true
shift
;;
*)
echo -e "${RED}Unknown argument: $1${NC}"
exit 1
;;
esac
done
local test_functions=(
"test_port_range_rules"
"test_critical_services"
)
if [ "$skip_default_reject" = false ]; then
test_functions+=("test_default_reject_rule")
fi
echo -e "${YELLOW}Running Nym Exit Policy Verification Tests...${NC}"
for test_func in "${test_functions[@]}"; do
((total_tests++))
$test_func
if [ $? -ne 0 ]; then
((total_failures++))
echo -e "${RED}Test $test_func FAILED${NC}"
else
echo -e "${GREEN}Test $test_func PASSED${NC}"
fi
done
echo -e "\n${YELLOW}Test Summary:${NC}"
echo -e "Total Tests: $total_tests"
echo -e "Failures: $total_failures"
if [ $total_failures -eq 0 ]; then
echo -e "${GREEN}All Tests Passed Successfully!${NC}"
exit 0
else
echo -e "${RED}Some Tests Failed. Please review the iptables configuration.${NC}"
exit 1
fi
}
if [[ $EUID -ne 0 ]]; then
echo -e "${RED}This script must be run as root${NC}"
exit 1
fi
# Run the tests
run_all_tests "$@"
@@ -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
@@ -1,679 +0,0 @@
#!/bin/bash
#
# Nym Wireguard Exit Policy Manager
# Version: 1.0.0
#
# This script manages iptables rules for Nym Wireguard exit policies
# Features:
# - Implements the Nym exit policy from official documentation
# - Makes rules persistent across reboots
# - Provides commands to inspect and manage rules
# - Groups rules logically for easier management
# - Integrates with existing Nym node configuration
#
# Usage: ./nym-exit-policy.sh [command]
set -e
NETWORK_DEVICE=$(ip route show default | awk '/default/ {print $5}')
WG_INTERFACE="nymwg"
NYM_CHAIN="NYM-EXIT"
POLICY_FILE="/etc/nym/exit-policy.txt"
EXIT_POLICY_LOCATION="https://nymtech.net/.wellknown/network-requester/exit-policy.txt"
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
NC='\033[0m'
add_port_rules() {
local chain="$1"
local port="$2"
local protocol="${3:-tcp}"
# Check if the port contains a range
if [[ "$port" == *"-"* ]]; then
# Port range handling - add as a single rule with a range
local start_port=$(echo "$port" | cut -d'-' -f1)
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
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
echo -e " ${GREEN}Added: $NYM_CHAIN $protocol port $port${NC}"
fi
fi
}
install_dependencies() {
if ! dpkg -s iptables-persistent >/dev/null 2>&1; then
echo -e "${YELLOW}Installing iptables-persistent...${NC}"
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y iptables-persistent
echo -e "${GREEN}iptables-persistent installed.${NC}"
else
echo -e "${GREEN}iptables-persistent is already installed.${NC}"
fi
# Check for other required dependencies
for cmd in iptables ip6tables ip grep sed awk wget curl; do
if ! command -v "$cmd" &>/dev/null; then
echo -e "${YELLOW}Installing $cmd...${NC}"
apt-get install -y "$cmd"
fi
done
}
configure_ip_forwarding() {
echo -e "${YELLOW}Configuring IP forwarding...${NC}"
# Remove any existing forwarding settings to avoid duplicates
sed -i "/^net.ipv6.conf.all.forwarding=/d" /etc/sysctl.conf
sed -i "/^net.ipv4.ip_forward=/d" /etc/sysctl.conf
# Add forwarding settings
echo "net.ipv6.conf.all.forwarding=1" | tee -a /etc/sysctl.conf
echo "net.ipv4.ip_forward=1" | tee -a /etc/sysctl.conf
# Apply changes
sysctl -p /etc/sysctl.conf
# Verify settings
ipv4_forwarding=$(cat /proc/sys/net/ipv4/ip_forward)
ipv6_forwarding=$(cat /proc/sys/net/ipv6/conf/all/forwarding)
if [ "$ipv4_forwarding" == "1" ] && [ "$ipv6_forwarding" == "1" ]; then
echo -e "${GREEN}IP forwarding configured successfully.${NC}"
else
echo -e "${RED}Failed to configure IP forwarding.${NC}"
exit 1
fi
}
create_nym_chain() {
echo -e "${YELLOW}Creating Nym exit policy chain...${NC}"
# Check if the chain already exists
if iptables -L "$NYM_CHAIN" &>/dev/null; then
echo -e "${YELLOW}Chain $NYM_CHAIN already exists. Flushing it...${NC}"
iptables -F "$NYM_CHAIN"
else
echo -e "${YELLOW}Creating chain $NYM_CHAIN...${NC}"
iptables -N "$NYM_CHAIN"
fi
# Do the same for IPv6
if ip6tables -L "$NYM_CHAIN" &>/dev/null; then
echo -e "${YELLOW}Chain $NYM_CHAIN already exists in ip6tables. Flushing it...${NC}"
ip6tables -F "$NYM_CHAIN"
else
echo -e "${YELLOW}Creating chain $NYM_CHAIN in ip6tables...${NC}"
ip6tables -N "$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
echo -e "${YELLOW}Linking $NYM_CHAIN to FORWARD chain...${NC}"
iptables -A FORWARD -o "$WG_INTERFACE" -j "$NYM_CHAIN"
fi
# Link IPv6 chain
if ! ip6tables -C FORWARD -o "$WG_INTERFACE" -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"
fi
}
setup_nat_rules() {
echo -e "${YELLOW}Setting up NAT rules...${NC}"
# Check if NAT rule for IPv4 exists
if ! iptables -t nat -C POSTROUTING -o "$NETWORK_DEVICE" -j MASQUERADE 2>/dev/null; then
iptables -t nat -A POSTROUTING -o "$NETWORK_DEVICE" -j MASQUERADE
echo -e "${GREEN}Added IPv4 NAT rule.${NC}"
else
echo -e "${GREEN}IPv4 NAT rule already exists.${NC}"
fi
# Check if NAT rule for IPv6 exists
if ! ip6tables -t nat -C POSTROUTING -o "$NETWORK_DEVICE" -j MASQUERADE 2>/dev/null; then
ip6tables -t nat -A POSTROUTING -o "$NETWORK_DEVICE" -j MASQUERADE
echo -e "${GREEN}Added IPv6 NAT rule.${NC}"
else
echo -e "${GREEN}IPv6 NAT rule already exists.${NC}"
fi
# Setup forwarding rules for Wireguard interface
if ! iptables -C FORWARD -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j ACCEPT 2>/dev/null; then
iptables -A FORWARD -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j ACCEPT
echo -e "${GREEN}Added IPv4 forwarding rule (WG → Internet).${NC}"
fi
if ! iptables -C FORWARD -i "$NETWORK_DEVICE" -o "$WG_INTERFACE" -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null; then
iptables -A FORWARD -i "$NETWORK_DEVICE" -o "$WG_INTERFACE" -m state --state RELATED,ESTABLISHED -j ACCEPT
echo -e "${GREEN}Added IPv4 forwarding rule (Internet → WG for established connections).${NC}"
fi
# IPv6 forwarding rules
if ! ip6tables -C FORWARD -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j ACCEPT 2>/dev/null; then
ip6tables -A FORWARD -i "$WG_INTERFACE" -o "$NETWORK_DEVICE" -j ACCEPT
echo -e "${GREEN}Added IPv6 forwarding rule (WG → Internet).${NC}"
fi
if ! ip6tables -C FORWARD -i "$NETWORK_DEVICE" -o "$WG_INTERFACE" -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null; then
ip6tables -A FORWARD -i "$NETWORK_DEVICE" -o "$WG_INTERFACE" -m state --state RELATED,ESTABLISHED -j ACCEPT
echo -e "${GREEN}Added IPv6 forwarding rule (Internet → WG for established connections).${NC}"
fi
}
configure_dns_and_icmp() {
echo -e "${YELLOW}Configuring DNS and ICMP rules...${NC}"
# ICMP rules for ping
if ! iptables -C INPUT -p icmp --icmp-type echo-request -j ACCEPT 2>/dev/null; then
iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
echo -e "${GREEN}Added IPv4 ICMP rule (allow ping requests).${NC}"
fi
if ! iptables -C OUTPUT -p icmp --icmp-type echo-reply -j ACCEPT 2>/dev/null; then
iptables -A OUTPUT -p icmp --icmp-type echo-reply -j ACCEPT
echo -e "${GREEN}Added IPv4 ICMP rule (allow ping replies).${NC}"
fi
# ICMPv6 rules for ping6
if ! ip6tables -C INPUT -p ipv6-icmp -j ACCEPT 2>/dev/null; then
ip6tables -A INPUT -p ipv6-icmp -j ACCEPT
echo -e "${GREEN}Added IPv6 ICMP rule (allow ping6).${NC}"
fi
# DNS rules
if ! iptables -C INPUT -p udp --dport 53 -j ACCEPT 2>/dev/null; then
iptables -A INPUT -p udp --dport 53 -j ACCEPT
echo -e "${GREEN}Added IPv4 DNS rule (UDP).${NC}"
fi
if ! iptables -C INPUT -p tcp --dport 53 -j ACCEPT 2>/dev/null; then
iptables -A INPUT -p tcp --dport 53 -j ACCEPT
echo -e "${GREEN}Added IPv4 DNS rule (TCP).${NC}"
fi
# IPv6 DNS rules
if ! ip6tables -C INPUT -p udp --dport 53 -j ACCEPT 2>/dev/null; then
ip6tables -A INPUT -p udp --dport 53 -j ACCEPT
echo -e "${GREEN}Added IPv6 DNS rule (UDP).${NC}"
fi
if ! ip6tables -C INPUT -p tcp --dport 53 -j ACCEPT 2>/dev/null; then
ip6tables -A INPUT -p tcp --dport 53 -j ACCEPT
echo -e "${GREEN}Added IPv6 DNS rule (TCP).${NC}"
fi
}
# Apply Spamhaus blocklist from the Nym exit policy
apply_spamhaus_blocklist() {
echo -e "${YELLOW}Applying Spamhaus blocklist...${NC}"
# Create directory if not exists
mkdir -p "$(dirname "$POLICY_FILE")"
# Try to download the policy file
echo -e "${YELLOW}Downloading exit policy from $EXIT_POLICY_LOCATION${NC}"
if ! wget -q "$EXIT_POLICY_LOCATION" -O "$POLICY_FILE" 2>/dev/null; then
echo -e "${RED}Failed to download exit policy. Using minimal blocklist.${NC}"
# Create a minimal policy file with a few entries
cat >"$POLICY_FILE" <<EOF
ExitPolicy reject 5.188.10.0/23:*
ExitPolicy reject 31.132.36.0/22:*
ExitPolicy reject 37.9.42.0/24:*
ExitPolicy reject 45.43.128.0/18:*
ExitPolicy reject *:*
EOF
fi
# Count and process rules
total_rules=$(grep -c "^ExitPolicy reject" "$POLICY_FILE" | grep -v "\*:\*")
echo -e "${YELLOW}Processing $total_rules blocklist rules...${NC}"
# Extract and apply IP block rules
grep "^ExitPolicy reject" "$POLICY_FILE" | grep -v "\*:\*" |
while read -r line; do
# Extract IP range
ip_range=$(echo "$line" | sed -E 's/ExitPolicy reject ([^:]+):.*/\1/')
# Apply rule if it's a valid IP range
if [[ -n "$ip_range" ]]; then
# Skip if the rule already exists
if ! iptables -C "$NYM_CHAIN" -d "$ip_range" -j REJECT 2>/dev/null; then
iptables -A "$NYM_CHAIN" -d "$ip_range" -j REJECT
fi
# Apply IPv6 rules for IPv6 addresses
if [[ "$ip_range" == *":"* ]] && ! ip6tables -C "$NYM_CHAIN" -d "$ip_range" -j REJECT 2>/dev/null; then
ip6tables -A "$NYM_CHAIN" -d "$ip_range" -j REJECT
fi
fi
done
echo -e "${GREEN}Blocklist applied successfully.${NC}"
}
add_default_reject_rule() {
echo -e "${YELLOW}Adding default reject rule...${NC}"
# First remove any existing plain reject rules (without specific destinations)
iptables -D "$NYM_CHAIN" -j REJECT 2>/dev/null || true
iptables -D "$NYM_CHAIN" -j REJECT --reject-with icmp-port-unreachable 2>/dev/null || true
ip6tables -D "$NYM_CHAIN" -j REJECT 2>/dev/null || true
ip6tables -D "$NYM_CHAIN" -j REJECT --reject-with icmp6-port-unreachable 2>/dev/null || true
# Add the default catch-all reject rule (must be the last rule in the chain)
iptables -A "$NYM_CHAIN" -j REJECT --reject-with icmp-port-unreachable
ip6tables -A "$NYM_CHAIN" -j REJECT --reject-with icmp6-port-unreachable
echo -e "${GREEN}Default reject rule added successfully.${NC}"
}
apply_port_allowlist() {
echo -e "${YELLOW}Applying allowed ports...${NC}"
# Dictionary of services and their ports
declare -A PORT_MAPPINGS=(
["FTP"]="20-21"
["SSH"]="22"
["WHOIS"]="43"
["DNS"]="53"
["Finger"]="79"
["HTTP"]="80-81"
["Kerberos"]="88"
["POP3"]="110"
["NTP"]="123"
["IMAP"]="143"
["IMAP3"]="220"
["LDAP"]="389"
["HTTPS"]="443"
["SMBWindowsFileShare"]="445"
["Kpasswd"]="464"
["RTSP"]="554"
["LDAPS"]="636"
["SILC"]="706"
["KerberosAdmin"]="749"
["DNSOverTLS"]="853"
["Rsync"]="873"
["VMware"]="902-904"
["RemoteHTTPS"]="981"
["FTPOverTLS"]="989-990"
["NetnewsAdmin"]="991"
["TelnetOverTLS"]="992"
["IMAPOverTLS"]="993"
["POP3OverTLS"]="995"
["OpenVPN"]="1194"
["QTServerAdmin"]="1220"
["PKTKRB"]="1293"
["MSSQL"]="1433"
["VLSILicenseManager"]="1500"
["OracleDB"]="1521"
["Sametime"]="1533"
["GroupWise"]="1677"
["PPTP"]="1723"
["RTSPAlt"]="1755"
["MSNP"]="1863"
["NFS"]="2049"
["CPanel"]="2082-2083"
["GNUnet"]="2086-2087"
["NBX"]="2095-2096"
["Zephyr"]="2102-2104"
["XboxLive"]="3074"
["MySQL"]="3306"
["SVN"]="3690"
["RWHOIS"]="4321"
["Virtuozzo"]="4643"
["RTPVOIP"]="5000-5005"
["MMCC"]="5050"
["ICQ"]="5190"
["XMPP"]="5222-5223"
["AndroidMarket"]="5228"
["PostgreSQL"]="5432"
["MongoDBDefault"]="27017"
["Electrum"]="8082"
["SimplifyMedia"]="8087-8088"
["Zcash"]="8232-8233"
["Bitcoin"]="8332-8333"
["HTTPSALT"]="8443"
["TeamSpeak"]="8767"
["MQTTS"]="8883"
["HTTPProxy"]="8888"
["TorORPort"]="9001"
["TorDirPort"]="9030"
["Tari"]="9053"
["Gaming"]="9339"
["Git"]="9418"
["HTTPSALT2"]="9443"
["Lightning"]="9735"
["NDMP"]="10000"
["OpenPGP"]="11371"
["Monero"]="18080-18081"
["MoneroRPC"]="18089"
["GoogleVoice"]="19294"
["EnsimControlPanel"]="19638"
["DarkFiTor"]="25551"
["Minecraft"]="25565"
["DarkFi"]="26661"
["Steam"]="27000-27050"
["ElectrumSSL"]="50002"
["MOSH"]="60000-61000"
["Mumble"]="64738"
)
# Add TCP and UDP rules for each allowed port
for service in "${!PORT_MAPPINGS[@]}"; do
port="${PORT_MAPPINGS[$service]}"
echo -e "${YELLOW}Adding rules for $service (Port: $port)${NC}"
# Add both TCP and UDP rules for all services
add_port_rules iptables "$port" "tcp"
add_port_rules ip6tables "$port" "tcp"
add_port_rules iptables "$port" "udp"
add_port_rules ip6tables "$port" "udp"
done
add_default_reject_rule
echo -e "${GREEN}Port allowlist applied successfully.${NC}"
}
safe_iptables_rule_remove() {
local chain="$1"
local table="${2:-filter}"
local interface="$3"
# Remove rule if it exists
if iptables -t "$table" -C "$chain" -o "$interface" -j "$NYM_CHAIN" 2>/dev/null; then
iptables -t "$table" -D "$chain" -o "$interface" -j "$NYM_CHAIN"
fi
}
safe_ip6tables_rule_remove() {
local chain="$1"
local table="${2:-filter}"
local interface="$3"
# Remove rule if it exists
if ip6tables -t "$table" -C "$chain" -o "$interface" -j "$NYM_CHAIN" 2>/dev/null; then
ip6tables -t "$table" -D "$chain" -o "$interface" -j "$NYM_CHAIN"
fi
}
clear_rules() {
echo -e "${YELLOW}Clearing Nym exit policy rules...${NC}"
# Flush all rules in the NYM-EXIT chain
iptables -F "$NYM_CHAIN" 2>/dev/null || true
ip6tables -F "$NYM_CHAIN" 2>/dev/null || true
# Remove the chain from FORWARD if it exists
iptables -D FORWARD -o "$WG_INTERFACE" -j "$NYM_CHAIN" 2>/dev/null || true
ip6tables -D FORWARD -o "$WG_INTERFACE" -j "$NYM_CHAIN" 2>/dev/null || true
# Delete the chains
iptables -X "$NYM_CHAIN" 2>/dev/null || true
ip6tables -X "$NYM_CHAIN" 2>/dev/null || true
echo -e "${GREEN}Nym exit policy rules cleared successfully.${NC}"
}
remove_duplicate_rules() {
local interface="$1"
if [[ -z "$interface" ]]; then
echo -e "${RED}Error: No interface specified. Usage: $0 remove-duplicates <interface>${NC}" >&2
exit 1
fi
echo -e "${YELLOW}Detecting and removing duplicate rules for $interface...${NC}"
# Verbose duplicate rule detection for IPv4
echo -e "${YELLOW}Checking IPv4 duplicate rules:${NC}"
iptables-save | grep -E "(-A FORWARD|-A $NYM_CHAIN)" | grep "$interface" | sort | uniq -d && {
echo -e "${RED}Duplicate IPv4 rules found! Removing...${NC}"
# Remove duplicates by saving unique rules
iptables-save | grep -E "(-A FORWARD|-A $NYM_CHAIN)" | grep "$interface" | sort | uniq | while read -r rule; do
# Carefully remove duplicates
full_rule=$(echo "$rule" | sed 's/^-A/iptables -D/')
eval "$full_rule" 2>/dev/null
done
} || echo -e "${GREEN}No duplicate IPv4 rules found.${NC}"
# Verbose duplicate rule detection for IPv6
echo -e "${YELLOW}Checking IPv6 duplicate rules:${NC}"
ip6tables-save | grep -E "(-A FORWARD|-A $NYM_CHAIN)" | grep "$interface" | sort | uniq -d && {
echo -e "${RED}Duplicate IPv6 rules found! Removing...${NC}"
# Remove duplicates by saving unique rules
ip6tables-save | grep -E "(-A FORWARD|-A $NYM_CHAIN)" | grep "$interface" | sort | uniq | while read -r rule; do
# Carefully remove duplicates
full_rule=$(echo "$rule" | sed 's/^-A/ip6tables -D/')
eval "$full_rule" 2>/dev/null
done
} || echo -e "${GREEN}No duplicate IPv6 rules found.${NC}"
# Additional verification
echo -e "\n${YELLOW}Rule verification:${NC}"
echo "IPv4 Rules:"
iptables -L FORWARD -v -n | grep "$interface"
echo "IPv6 Rules:"
ip6tables -L FORWARD -v -n | grep "$interface"
echo -e "${GREEN}Duplicate rule removal process completed.${NC}"
}
save_rules() {
echo -e "${YELLOW}Saving iptables rules to make them persistent...${NC}"
if [ -d "/etc/iptables" ]; then
# For Debian/Ubuntu with iptables-persistent
iptables-save >/etc/iptables/rules.v4
ip6tables-save >/etc/iptables/rules.v6
echo -e "${GREEN}Rules saved to /etc/iptables/rules.v4 and /etc/iptables/rules.v6${NC}"
else
# Fallback method
iptables-save >/etc/iptables.rules
ip6tables-save >/etc/ip6tables.rules
echo -e "${GREEN}Rules saved to /etc/iptables.rules and /etc/ip6tables.rules${NC}"
# Add loading script to rc.local if it doesn't exist
if [ ! -f "/etc/network/if-pre-up.d/iptables" ]; then
cat >/etc/network/if-pre-up.d/iptables <<EOF
#!/bin/sh
iptables-restore < /etc/iptables.rules
ip6tables-restore < /etc/ip6tables.rules
EOF
chmod +x /etc/network/if-pre-up.d/iptables
echo -e "${GREEN}Created pre-up script to load rules at boot${NC}"
fi
fi
}
show_status() {
echo -e "${YELLOW}Nym Exit Policy Status:${NC}"
echo -e "${YELLOW}----------------------${NC}"
# Network information
echo -e "${GREEN}Network Device:${NC} $NETWORK_DEVICE"
echo -e "${GREEN}Wireguard Interface:${NC} $WG_INTERFACE"
# Interface check
if ! ip link show "$WG_INTERFACE" &>/dev/null; then
echo -e "${RED}WARNING: Wireguard interface $WG_INTERFACE not found!${NC}"
return 1
fi
# Interface details
echo -e "\n${YELLOW}Interface Details:${NC}"
ip link show "$WG_INTERFACE"
# IP Addresses
echo -e "\n${YELLOW}IP Addresses:${NC}"
ip -4 addr show dev "$WG_INTERFACE"
ip -6 addr show dev "$WG_INTERFACE"
# Iptables Chain Status
echo -e "\n${YELLOW}Iptables Chains:${NC}"
{
echo "IPv4 Chain:"
iptables -L "$NYM_CHAIN" -n -v
echo -e "\nIPv6 Chain:"
ip6tables -L "$NYM_CHAIN" -n -v
} || echo "One or both chains not found"
# Forwarding Status
echo -e "\n${YELLOW}IP Forwarding:${NC}"
echo "IPv4: $(cat /proc/sys/net/ipv4/ip_forward)"
echo "IPv6: $(cat /proc/sys/net/ipv6/conf/all/forwarding)"
}
test_connectivity() {
echo -e "${YELLOW}Testing connectivity through $WG_INTERFACE...${NC}"
# More comprehensive interface check
interface_info=$(ip link show "$WG_INTERFACE" 2>/dev/null)
if [ -z "$interface_info" ]; then
echo -e "${RED}Interface $WG_INTERFACE not found!${NC}"
return 1
fi
# Check for multiple possible interface states
if ! echo "$interface_info" | grep -qE "state (UP|UNKNOWN|DORMANT)"; then
echo -e "${RED}Interface $WG_INTERFACE is not in an active state!${NC}"
echo "$interface_info"
return 1
fi
# Detailed interface information
echo -e "${GREEN}Interface Details:${NC}"
echo "$interface_info"
# Get IP addresses with more robust method
ipv4_address=$(ip -4 addr show dev "$WG_INTERFACE" | grep -oP '(?<=inet\s)\d+\.\d+\.\d+\.\d+/\d+' | cut -d'/' -f1 | head -n1)
ipv6_address=$(ip -6 addr show dev "$WG_INTERFACE" scope global | grep -oP '(?<=inet6\s)[0-9a-f:]+/\d+' | cut -d'/' -f1 | head -n1)
echo -e "${GREEN}IPv4 Address:${NC} ${ipv4_address:-Not found}"
echo -e "${GREEN}IPv6 Address:${NC} ${ipv6_address:-Not found}"
# Connectivity tests
if [[ -n "$ipv4_address" ]]; then
echo -e "${YELLOW}Testing IPv4 connectivity from $ipv4_address...${NC}"
# Ping test
if timeout 5 ping -c 3 -I "$ipv4_address" 8.8.8.8 >/dev/null 2>&1; then
echo -e "${GREEN}IPv4 connectivity to 8.8.8.8: Success${NC}"
else
echo -e "${RED}IPv4 connectivity to 8.8.8.8: Failed${NC}"
fi
# DNS resolution test
if timeout 5 ping -c 3 -I "$ipv4_address" google.com >/dev/null 2>&1; then
echo -e "${GREEN}IPv4 DNS resolution: Success${NC}"
else
echo -e "${RED}IPv4 DNS resolution: Failed${NC}"
fi
# HTTP(S) connectivity test
if command -v curl &>/dev/null; then
if timeout 5 curl -s --interface "$ipv4_address" -o /dev/null -w "%{http_code}" https://www.google.com | grep -q "200"; then
echo -e "${GREEN}IPv4 HTTPS connectivity: Success${NC}"
else
echo -e "${RED}IPv4 HTTPS connectivity: Failed${NC}"
fi
fi
else
echo -e "${RED}No IPv4 address configured on $WG_INTERFACE${NC}"
fi
# Similar tests for IPv6 if available
if [[ -n "$ipv6_address" ]]; then
echo -e "${YELLOW}Testing IPv6 connectivity from $ipv6_address...${NC}"
if timeout 5 ping6 -c 3 -I "$ipv6_address" 2001:4860:4860::8888 >/dev/null 2>&1; then
echo -e "${GREEN}IPv6 connectivity to Google DNS: Success${NC}"
else
echo -e "${RED}IPv6 connectivity to Google DNS: Failed${NC}"
fi
if timeout 5 ping6 -c 3 -I "$ipv6_address" google.com >/dev/null 2>&1; then
echo -e "${GREEN}IPv6 DNS resolution: Success${NC}"
else
echo -e "${RED}IPv6 DNS resolution: Failed${NC}"
fi
if command -v curl &>/dev/null; then
if timeout 5 curl -s --interface "$ipv6_address" -o /dev/null -w "%{http_code}" https://www.google.com | grep -q "200"; then
echo -e "${GREEN}IPv6 HTTPS connectivity: Success${NC}"
else
echo -e "${RED}IPv6 HTTPS connectivity: Failed${NC}"
fi
fi
else
echo -e "${YELLOW}No IPv6 address configured on $WG_INTERFACE${NC}"
fi
echo -e "${GREEN}Connectivity tests completed.${NC}"
}
main() {
# Check for root privileges
if [ "$(id -u)" -ne 0 ]; then
echo -e "${RED}This script must be run as root${NC}" >&2
exit 1
fi
# Parse command-line arguments
case "$1" in
install)
install_dependencies
configure_ip_forwarding
create_nym_chain
setup_nat_rules
configure_dns_and_icmp
apply_spamhaus_blocklist
apply_port_allowlist
save_rules
echo -e "${GREEN}Nym exit policy installed successfully.${NC}"
;;
status)
show_status
;;
test)
test_connectivity
;;
clear)
clear_rules
echo -e "${GREEN}Nym exit policy rules cleared.${NC}"
;;
remove-duplicates)
remove_duplicate_rules "$2"
;;
help | --help | -h)
echo "Usage: $0 [command]"
echo ""
echo "Commands:"
echo " install Install and configure Nym exit policy"
echo " status Show current Nym exit policy status"
echo " test Test connectivity through Wireguard interface"
echo " clear Remove all Nym exit policy rules"
echo " remove-duplicates <interface> Remove duplicate iptables rules for an interface"
echo " help Show this help message"
;;
*)
echo -e "${RED}Invalid command. Use '$0 help' for usage information.${NC}" >&2
exit 1
;;
esac
}
main "$@"
+4 -4
View File
@@ -4,15 +4,15 @@ all: build build-node
build: test build-wasm
build-wasm:
wasm-pack build --scope nymproject --target web --out-dir ../../dist/wasm/client
wasm-opt -Oz -o ../../dist/wasm/client/nym_client_wasm_bg.wasm ../../dist/wasm/client/nym_client_wasm_bg.wasm
taskset -c 0-11 wasm-pack build --scope nymproject --target web --out-dir ../../dist/wasm/client
taskset -c 0-11 wasm-opt -Oz -o ../../dist/wasm/client/nym_client_wasm_bg.wasm ../../dist/wasm/client/nym_client_wasm_bg.wasm
build-debug-dev:
wasm-pack build --debug --scope nymproject --target no-modules
build-rust-node:
wasm-pack build --scope nymproject --target nodejs --out-dir ../../dist/node/wasm/client
wasm-opt -Oz -o ../../dist/node/wasm/client/nym_client_wasm_bg.wasm ../../dist/node/wasm/client/nym_client_wasm_bg.wasm
taskset -c 0-11 wasm-pack build --scope nymproject --target nodejs --out-dir ../../dist/node/wasm/client
taskset -c 0-11 wasm-opt -Oz -o ../../dist/node/wasm/client/nym_client_wasm_bg.wasm ../../dist/node/wasm/client/nym_client_wasm_bg.wasm
build-package-json-node:
node build-node.mjs
+2 -2
View File
@@ -1,5 +1,5 @@
all: build-full
build-full:
wasm-pack build --all-features --scope nymproject --target web --out-dir ../../dist/wasm/full-nym-wasm
wasm-opt -Oz -o ../../dist/wasm/full-nym-wasm/nym_wasm_sdk_bg.wasm ../../dist/wasm/full-nym-wasm/nym_wasm_sdk_bg.wasm
taskset -c 0-11 wasm-pack build --all-features --scope nymproject --target web --out-dir ../../dist/wasm/full-nym-wasm
taskset -c 0-11 wasm-opt -Oz -o ../../dist/wasm/full-nym-wasm/nym_wasm_sdk_bg.wasm ../../dist/wasm/full-nym-wasm/nym_wasm_sdk_bg.wasm
+3 -3
View File
@@ -9,8 +9,8 @@ build-go-opt:
$(MAKE) -C go-mix-conn build-go-opt
build-rust:
wasm-pack build --scope nymproject --target web --out-dir ../../dist/wasm/mix-fetch
wasm-opt -Oz -o ../../dist/wasm/mix-fetch/mix_fetch_wasm_bg.wasm ../../dist/wasm/mix-fetch/mix_fetch_wasm_bg.wasm
taskset -c 0-11 wasm-pack build --scope nymproject --target web --out-dir ../../dist/wasm/mix-fetch
taskset -c 0-11 wasm-opt -Oz -o ../../dist/wasm/mix-fetch/mix_fetch_wasm_bg.wasm ../../dist/wasm/mix-fetch/mix_fetch_wasm_bg.wasm
build-rust-debug:
wasm-pack build --debug --scope nymproject --target no-modules
@@ -23,4 +23,4 @@ check-fmt-go:
check-fmt-rust:
cargo fmt --check
cargo clippy --target wasm32-unknown-unknown -- -Dwarnings
cargo clippy --target wasm32-unknown-unknown -- -Dwarnings