feat: add nordvpn wireguard sudo helper

This commit is contained in:
Stefano Fiorini
2026-03-12 00:34:04 -05:00
parent 045cf6aad2
commit a8a285b356
4 changed files with 44 additions and 8 deletions

View File

@@ -35,7 +35,7 @@ node skills/nordvpn-client/scripts/nordvpn-client.js disconnect
- `NORDVPN_TOKEN` or `NORDVPN_TOKEN_FILE` - `NORDVPN_TOKEN` or `NORDVPN_TOKEN_FILE`
- `wireguard-go` - `wireguard-go`
- `wireguard-tools` - `wireguard-tools`
- non-interactive `sudo` for `wg-quick` - non-interactive `sudo` for `~/.openclaw/workspace/skills/nordvpn-client/scripts/nordvpn-wireguard-helper.sh`
- `NordVPN.app` may stay installed but is only the manual fallback - `NordVPN.app` may stay installed but is only the manual fallback
- the app login is not reused by the automated WireGuard backend - the app login is not reused by the automated WireGuard backend

View File

@@ -96,6 +96,6 @@ For an automated macOS flow:
- `NORDVPN_TOKEN` or `NORDVPN_TOKEN_FILE` - `NORDVPN_TOKEN` or `NORDVPN_TOKEN_FILE`
- `wireguard-go` - `wireguard-go`
- `wireguard-tools` - `wireguard-tools`
- non-interactive `sudo` for `wg-quick` - non-interactive `sudo` for `~/.openclaw/workspace/skills/nordvpn-client/scripts/nordvpn-wireguard-helper.sh`
- `NordVPN.app` login on macOS is not reused by the WireGuard backend. - `NordVPN.app` login on macOS is not reused by the WireGuard backend.
- The Homebrew `nordvpn` app does not need to be uninstalled. It can coexist with the WireGuard backend. - The Homebrew `nordvpn` app does not need to be uninstalled. It can coexist with the WireGuard backend.

View File

@@ -22,6 +22,15 @@ const OPENCLAW_NORDVPN_CREDENTIALS_DIR = path.join(
); );
const DEFAULT_TOKEN_FILE = path.join(OPENCLAW_NORDVPN_CREDENTIALS_DIR, "token.txt"); const DEFAULT_TOKEN_FILE = path.join(OPENCLAW_NORDVPN_CREDENTIALS_DIR, "token.txt");
const DEFAULT_PASSWORD_FILE = path.join(OPENCLAW_NORDVPN_CREDENTIALS_DIR, "password.txt"); const DEFAULT_PASSWORD_FILE = path.join(OPENCLAW_NORDVPN_CREDENTIALS_DIR, "password.txt");
const MAC_WG_HELPER_PATH = path.join(
os.homedir(),
".openclaw",
"workspace",
"skills",
"nordvpn-client",
"scripts",
"nordvpn-wireguard-helper.sh"
);
const DEFAULT_DNS_IPV4 = "103.86.96.100"; const DEFAULT_DNS_IPV4 = "103.86.96.100";
const DEFAULT_DNS_IPV6 = "2400:bb40:4444::100"; const DEFAULT_DNS_IPV6 = "2400:bb40:4444::100";
const CLIENT_IPV4 = "10.5.0.2"; const CLIENT_IPV4 = "10.5.0.2";
@@ -274,7 +283,8 @@ async function probeMacWireguard() {
const wgPath = commandExists("wg"); const wgPath = commandExists("wg");
const wgQuickPath = commandExists("wg-quick"); const wgQuickPath = commandExists("wg-quick");
const wireguardGoPath = commandExists("wireguard-go"); const wireguardGoPath = commandExists("wireguard-go");
const sudoProbe = await runExec("sudo", ["-n", "true"]); const helperPath = fileExists(MAC_WG_HELPER_PATH) ? MAC_WG_HELPER_PATH : null;
const sudoProbe = helperPath ? await runExec("sudo", ["-n", helperPath, "probe"]) : { ok: false };
let active = false; let active = false;
let showRaw = ""; let showRaw = "";
let endpoint = ""; let endpoint = "";
@@ -293,6 +303,7 @@ async function probeMacWireguard() {
wgPath: wgPath || null, wgPath: wgPath || null,
wgQuickPath: wgQuickPath || null, wgQuickPath: wgQuickPath || null,
wireguardGoPath: wireguardGoPath || null, wireguardGoPath: wireguardGoPath || null,
helperPath,
dependenciesReady: Boolean(wgPath && wgQuickPath && wireguardGoPath), dependenciesReady: Boolean(wgPath && wgQuickPath && wireguardGoPath),
sudoReady: sudoProbe.ok, sudoReady: sudoProbe.ok,
interfaceName: MAC_WG_INTERFACE, interfaceName: MAC_WG_INTERFACE,
@@ -390,7 +401,7 @@ function buildStateSummary(installProbe, ipInfo) {
recommendedAction = installProbe.tokenAvailable recommendedAction = installProbe.tokenAvailable
? installProbe.wireguard.sudoReady ? installProbe.wireguard.sudoReady
? "Use token-based WireGuard automation on macOS." ? "Use token-based WireGuard automation on macOS."
: "WireGuard tooling and token are available, but connect/disconnect require non-interactive sudo for wg-quick. Authorize sudo for wg-quick, then rerun login/connect." : `WireGuard tooling and token are available, but connect/disconnect require non-interactive sudo for ${MAC_WG_HELPER_PATH}. Allow that helper in sudoers, then rerun login/connect.`
: `Set NORDVPN_TOKEN, NORDVPN_TOKEN_FILE, or place the token at ${DEFAULT_TOKEN_FILE} for automated macOS NordLynx/WireGuard connects.`; : `Set NORDVPN_TOKEN, NORDVPN_TOKEN_FILE, or place the token at ${DEFAULT_TOKEN_FILE} for automated macOS NordLynx/WireGuard connects.`;
} else if (installProbe.platform === "darwin" && installProbe.appInstalled) { } else if (installProbe.platform === "darwin" && installProbe.appInstalled) {
controlMode = "app-manual"; controlMode = "app-manual";
@@ -436,6 +447,7 @@ function buildStateSummary(installProbe, ipInfo) {
wgPath: installProbe.wireguard.wgPath, wgPath: installProbe.wireguard.wgPath,
wgQuickPath: installProbe.wireguard.wgQuickPath, wgQuickPath: installProbe.wireguard.wgQuickPath,
wireguardGoPath: installProbe.wireguard.wireguardGoPath, wireguardGoPath: installProbe.wireguard.wireguardGoPath,
helperPath: installProbe.wireguard.helperPath,
authCache: installProbe.wireguard.authCache, authCache: installProbe.wireguard.authCache,
lastConnection: installProbe.wireguard.lastConnection, lastConnection: installProbe.wireguard.lastConnection,
} }
@@ -769,12 +781,12 @@ async function connectViaCli(cliPath, target) {
} }
async function runSudoWireguard(installProbe, action) { async function runSudoWireguard(installProbe, action) {
const wgQuickPath = installProbe.wireguard && installProbe.wireguard.wgQuickPath; const helperPath = installProbe.wireguard && installProbe.wireguard.helperPath;
if (!wgQuickPath) throw new Error("wg-quick is not installed."); if (!helperPath) throw new Error(`WireGuard helper is missing at ${MAC_WG_HELPER_PATH}.`);
if (!installProbe.wireguard.sudoReady) { if (!installProbe.wireguard.sudoReady) {
throw new Error("Non-interactive sudo is required for macOS WireGuard connect/disconnect. Authorize sudo first, then retry."); throw new Error(`Non-interactive sudo is required for macOS WireGuard connect/disconnect. Allow sudo for ${MAC_WG_HELPER_PATH}, then rerun login/connect.`);
} }
return runExec("sudo", ["-n", "env", `PATH=${process.env.PATH || ""}`, wgQuickPath, action, WG_CONFIG_PATH]); return runExec("sudo", ["-n", helperPath, action]);
} }
async function connectViaMacWireguard(installProbe, target) { async function connectViaMacWireguard(installProbe, target) {

View File

@@ -0,0 +1,24 @@
#!/bin/sh
set -eu
ACTION="${1:-}"
case "$ACTION" in
probe|up|down)
;;
*)
echo "Usage: nordvpn-wireguard-helper.sh [probe|up|down]" >&2
exit 2
;;
esac
WG_QUICK="/opt/homebrew/bin/wg-quick"
WG_CONFIG="/Users/stefano/.nordvpn-client/wireguard/nordvpnctl.conf"
PATH="/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin"
export PATH
if [ "$ACTION" = "probe" ]; then
test -x "$WG_QUICK"
exit 0
fi
exec "$WG_QUICK" "$ACTION" "$WG_CONFIG"