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`
- `wireguard-go`
- `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
- 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`
- `wireguard-go`
- `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.
- 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_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_IPV6 = "2400:bb40:4444::100";
const CLIENT_IPV4 = "10.5.0.2";
@@ -274,7 +283,8 @@ async function probeMacWireguard() {
const wgPath = commandExists("wg");
const wgQuickPath = commandExists("wg-quick");
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 showRaw = "";
let endpoint = "";
@@ -293,6 +303,7 @@ async function probeMacWireguard() {
wgPath: wgPath || null,
wgQuickPath: wgQuickPath || null,
wireguardGoPath: wireguardGoPath || null,
helperPath,
dependenciesReady: Boolean(wgPath && wgQuickPath && wireguardGoPath),
sudoReady: sudoProbe.ok,
interfaceName: MAC_WG_INTERFACE,
@@ -390,7 +401,7 @@ function buildStateSummary(installProbe, ipInfo) {
recommendedAction = installProbe.tokenAvailable
? installProbe.wireguard.sudoReady
? "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.`;
} else if (installProbe.platform === "darwin" && installProbe.appInstalled) {
controlMode = "app-manual";
@@ -436,6 +447,7 @@ function buildStateSummary(installProbe, ipInfo) {
wgPath: installProbe.wireguard.wgPath,
wgQuickPath: installProbe.wireguard.wgQuickPath,
wireguardGoPath: installProbe.wireguard.wireguardGoPath,
helperPath: installProbe.wireguard.helperPath,
authCache: installProbe.wireguard.authCache,
lastConnection: installProbe.wireguard.lastConnection,
}
@@ -769,12 +781,12 @@ async function connectViaCli(cliPath, target) {
}
async function runSudoWireguard(installProbe, action) {
const wgQuickPath = installProbe.wireguard && installProbe.wireguard.wgQuickPath;
if (!wgQuickPath) throw new Error("wg-quick is not installed.");
const helperPath = installProbe.wireguard && installProbe.wireguard.helperPath;
if (!helperPath) throw new Error(`WireGuard helper is missing at ${MAC_WG_HELPER_PATH}.`);
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) {

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"