# nordvpn-client

Cross-platform NordVPN lifecycle skill for macOS and Linux.

## Overview

`nordvpn-client` is the operator-facing VPN control skill for OpenClaw. It can:

- detect whether the host is ready for NordVPN automation
- install or bootstrap the required backend
- validate auth
- connect to a target country or city
- verify the public exit location
- disconnect and restore normal local networking state

The skill uses different backends by platform:

- Linux: official `nordvpn` CLI
- macOS: NordLynx/WireGuard with `wireguard-go` and `wireguard-tools`

## Commands

```bash
node skills/nordvpn-client/scripts/nordvpn-client.js status
node skills/nordvpn-client/scripts/nordvpn-client.js install
node skills/nordvpn-client/scripts/nordvpn-client.js login
node skills/nordvpn-client/scripts/nordvpn-client.js verify
node skills/nordvpn-client/scripts/nordvpn-client.js verify --country "Italy"
node skills/nordvpn-client/scripts/nordvpn-client.js verify --country "Italy" --city "Milan"
node skills/nordvpn-client/scripts/nordvpn-client.js connect --country "Italy"
node skills/nordvpn-client/scripts/nordvpn-client.js connect --city "Tokyo"
node skills/nordvpn-client/scripts/nordvpn-client.js connect --country "Japan" --city "Tokyo"
node skills/nordvpn-client/scripts/nordvpn-client.js disconnect
node skills/nordvpn-client/scripts/nordvpn-client.js status --debug
```

## Credentials

Supported inputs:

- `NORDVPN_TOKEN`
- `NORDVPN_TOKEN_FILE`
- `NORDVPN_USERNAME`
- `NORDVPN_PASSWORD`
- `NORDVPN_PASSWORD_FILE`

Default OpenClaw credential paths:

- token: `~/.openclaw/workspace/.clawdbot/credentials/nordvpn/token.txt`
- password: `~/.openclaw/workspace/.clawdbot/credentials/nordvpn/password.txt`

Recommended setup on macOS is a token file with strict permissions:

```bash
mkdir -p ~/.openclaw/workspace/.clawdbot/credentials/nordvpn
chmod 700 ~/.openclaw/workspace/.clawdbot/credentials/nordvpn
printf '%s\n' '<your-nordvpn-token>' > ~/.openclaw/workspace/.clawdbot/credentials/nordvpn/token.txt
chmod 600 ~/.openclaw/workspace/.clawdbot/credentials/nordvpn/token.txt
```

Do not commit secrets into the repo or the skill docs.

## Platform Backends

### macOS

Current macOS backend:

- NordLynx/WireGuard
- `wireguard-go`
- `wireguard-tools`
- explicit macOS DNS management on eligible physical services:
  - `103.86.96.100`
  - `103.86.99.100`

Important behavior:

- `NordVPN.app` may remain installed, but the automated backend does not reuse app login state.
- the generated WireGuard config intentionally stays free of `DNS = ...` so `wg-quick` does not rewrite every macOS network service behind the skill’s back.
- during `connect`, the skill first proves the tunnel is stable with a bounded persistence gate that reuses the allowed helper `probe` action and a verified public exit.
- during `connect`, the skill snapshots current DNS/search-domain settings on eligible physical services and then applies NordVPN DNS only after that stable gate, one last liveness check, and a post-DNS system-hostname-resolution check succeed.
- during `disconnect`, or after a failed/stale teardown, the skill restores the saved DNS/search-domain snapshot.
- if persistence, exit verification, or post-DNS hostname resolution fails, the skill rolls back before treating the connect as successful and resumes Tailscale if it stopped it.
- when the skill intentionally stops Tailscale for a VPN session, it writes a short-lived suppression marker so host watchdogs do not immediately run `tailscale up` and fight the VPN route change.
- The skill automatically suspends Tailscale before connect if Tailscale is active.
- The skill resumes Tailscale after disconnect, or after a failed connect, if it stopped it.
- The Homebrew NordVPN app does not need to be uninstalled.

### Linux

Current Linux backend:

- official `nordvpn` CLI
- official NordVPN installer
- token login through `nordvpn login --token ...`

## Install / Bootstrap

### macOS

Bootstrap the automation backend:

```bash
node skills/nordvpn-client/scripts/nordvpn-client.js install
```

Equivalent Homebrew command:

```bash
brew install wireguard-go wireguard-tools
```

What `install` does on macOS:

- checks whether `wireguard-go` is present
- checks whether `wg` and `wg-quick` are present
- installs missing packages through Homebrew

### Linux

```bash
node skills/nordvpn-client/scripts/nordvpn-client.js install
```

What `install` does on Linux:

- downloads NordVPN’s official installer script
- runs it
- leaves subsequent login/connect to the official `nordvpn` CLI

## macOS sudoers Setup

Automated macOS connect/disconnect requires passwordless `sudo` for the helper script that invokes `wg-quick`.

Installed OpenClaw helper path:

```text
/Users/stefano/.openclaw/workspace/skills/nordvpn-client/scripts/nordvpn-wireguard-helper.sh
```

Edit sudoers safely:

```bash
sudo visudo
```

Add this exact rule:

```sudoers
stefano ALL=(root) NOPASSWD: /Users/stefano/.openclaw/workspace/skills/nordvpn-client/scripts/nordvpn-wireguard-helper.sh probe, /Users/stefano/.openclaw/workspace/skills/nordvpn-client/scripts/nordvpn-wireguard-helper.sh up, /Users/stefano/.openclaw/workspace/skills/nordvpn-client/scripts/nordvpn-wireguard-helper.sh down
```

Do not add extra helper actions just for persistence checks unless you are also updating host sudoers. The current implementation intentionally rides the persistence check on `probe` so the existing `probe/up/down` rule remains sufficient.

If you run the repo copy directly instead of the installed OpenClaw skill, adjust the helper path accordingly.

## Common Flows

### Status

```bash
node skills/nordvpn-client/scripts/nordvpn-client.js status
```

Use this first to answer:

- is the correct backend available?
- is the token visible?
- is `sudoReady` true?
- is the machine currently connected?

### Login

```bash
node skills/nordvpn-client/scripts/nordvpn-client.js login
```

On macOS this validates the token and populates the local auth cache. It does not connect the VPN.

### Connect

Country:

```bash
node skills/nordvpn-client/scripts/nordvpn-client.js connect --country "Germany"
```

City:

```bash
node skills/nordvpn-client/scripts/nordvpn-client.js connect --country "Japan" --city "Tokyo"
```

Expected macOS behavior:

- stop Tailscale if active
- select a NordVPN server for the target
- bring up the WireGuard tunnel
- prove persistence of the live `utun*` runtime via the helper `probe` path
- verify the public exit location
- run one final liveness check before applying NordVPN DNS
- return JSON describing the chosen server and final verified location

### Verify

```bash
node skills/nordvpn-client/scripts/nordvpn-client.js verify --country "Germany"
```

Use this after connect if you want an explicit location check without changing VPN state.

### Disconnect

```bash
node skills/nordvpn-client/scripts/nordvpn-client.js disconnect
```

Expected macOS behavior:

- attempt `wg-quick down` whenever there is active or residual NordVPN WireGuard state
- remove stale local NordVPN state files after teardown
- restore automatic DNS when the saved DNS snapshot is obviously just NordVPN-pinned leftovers
- resume Tailscale if the skill had suspended it

## Output Model

Normal JSON is redacted by default.

Redacted fields in normal mode:

- `cliPath`
- `appPath`
- `wireguard.configPath`
- `wireguard.helperPath`
- `wireguard.authCache.tokenSource`

Operational fields preserved in normal mode:

- `connected`
- `wireguard.active`
- `wireguard.endpoint`
- `requestedTarget`
- `verification`
- public IP and location

For deeper troubleshooting, use:

```bash
node skills/nordvpn-client/scripts/nordvpn-client.js status --debug
```

`--debug` keeps the internal local paths, helper-hardening diagnostics, and other low-level metadata in the JSON output.

If you also run local watchdogs such as `healthwatch.sh`, they should honor the NordVPN Tailscale suppression marker at `~/.nordvpn-client/tailscale-suppressed` and skip automatic `tailscale up` while the marker is fresh or the NordVPN WireGuard tunnel is active.

## Troubleshooting

### `Invalid authorization header`

Meaning:

- the token file was found
- the token value is not valid for NordVPN’s API

Actions:

1. generate a fresh NordVPN access token
2. replace the contents of `~/.openclaw/workspace/.clawdbot/credentials/nordvpn/token.txt`
3. run:

```bash
node skills/nordvpn-client/scripts/nordvpn-client.js login
```

### `sudoReady: false`

Meaning:

- the helper script is present
- the agent cannot run `wg-quick` non-interactively

Actions:

1. add the `visudo` rule shown above
2. rerun:

```bash
node skills/nordvpn-client/scripts/nordvpn-client.js status
```

Expected:

- `wireguard.sudoReady: true`

### WireGuard tools missing

Meaning:

- macOS backend is selected
- `wireguard-go`, `wg`, or `wg-quick` is missing

Actions:

```bash
node skills/nordvpn-client/scripts/nordvpn-client.js install
```

or:

```bash
brew install wireguard-go wireguard-tools
```

### Tailscale interaction

Expected behavior on macOS:

- Tailscale is suspended before the NordVPN connect
- Tailscale is resumed after disconnect or failed connect

If a connect succeeds but later traffic is wrong, check:

```bash
node skills/nordvpn-client/scripts/nordvpn-client.js status
/opt/homebrew/bin/tailscale status --json
```

Look for:

- `connected: true` and a foreign exit IP while NordVPN is up
- `connected: false` and Texas/Garland IP after disconnect

### Status says disconnected after a verified connect

This was a previous macOS false-negative path and is now normalized in the connect response.

Current expectation:

- if `connect` verifies the target location successfully
- the returned `state` snapshot should also show:
  - `connected: true`
  - `wireguard.active: true`

If that regresses, capture:

- `connect` JSON
- `verify` JSON
- `status --debug` JSON

### Disconnect says “no active connection” but traffic is still foreign

The current macOS disconnect path now treats residual WireGuard state as sufficient reason to attempt teardown.

Safe operator check:

```bash
node skills/nordvpn-client/scripts/nordvpn-client.js disconnect
node skills/nordvpn-client/scripts/nordvpn-client.js verify
```

Expected after a good disconnect:

- Texas/Garland public IP again
- `wireguard.configPath: null` in normal status output
- `wireguard.lastConnection: null`

If that regresses, capture:

- `disconnect` JSON
- `verify` JSON
- `status --debug` JSON

## Recommended Agent Workflow

For VPN-routed work:

1. `status`
2. `install` if backend tooling is missing
3. `login` if token validation has not happened yet
4. `connect --country ...` or `connect --country ... --city ...`
5. `verify`
6. run the follow-up skill such as `web-automation`
7. `disconnect`
8. `verify` again if you need proof the machine returned to the normal exit path
