diff --git a/docs/nordvpn-client.md b/docs/nordvpn-client.md index 9409b40..2a08833 100644 --- a/docs/nordvpn-client.md +++ b/docs/nordvpn-client.md @@ -42,7 +42,7 @@ node skills/nordvpn-client/scripts/nordvpn-client.js disconnect Quick start: 1. `node skills/nordvpn-client/scripts/nordvpn-client.js install` -2. set `NORDVPN_TOKEN` or `NORDVPN_TOKEN_FILE` +2. put your token in `~/.openclaw/workspace/.clawdbot/credentials/nordvpn/token.txt` or set `NORDVPN_TOKEN` / `NORDVPN_TOKEN_FILE` 3. `node skills/nordvpn-client/scripts/nordvpn-client.js login` 4. `node skills/nordvpn-client/scripts/nordvpn-client.js connect --country "Italy"` or `--city "Milan"` 5. `node skills/nordvpn-client/scripts/nordvpn-client.js verify` @@ -65,6 +65,11 @@ Supported env vars: Do not put secrets in the skill docs or repo. +Default OpenClaw credential paths: + +- token: `~/.openclaw/workspace/.clawdbot/credentials/nordvpn/token.txt` +- password: `~/.openclaw/workspace/.clawdbot/credentials/nordvpn/password.txt` + ## Verification model `status`, `verify`, and `connect` emit JSON suitable for agent use: diff --git a/skills/nordvpn-client/SKILL.md b/skills/nordvpn-client/SKILL.md index ee45c97..eeb3a19 100644 --- a/skills/nordvpn-client/SKILL.md +++ b/skills/nordvpn-client/SKILL.md @@ -59,6 +59,11 @@ Optional credential file env vars: - `NORDVPN_TOKEN_FILE` - `NORDVPN_PASSWORD_FILE` +Default OpenClaw credential paths: + +- token: `~/.openclaw/workspace/.clawdbot/credentials/nordvpn/token.txt` +- password: `~/.openclaw/workspace/.clawdbot/credentials/nordvpn/password.txt` + ## Verification Behavior `status`, `verify`, and `connect` report machine-readable JSON including: @@ -79,7 +84,7 @@ Use `verify` when you want an explicit post-connect location check without chang For an automated macOS flow: 1. `node scripts/nordvpn-client.js install` -2. set `NORDVPN_TOKEN` or `NORDVPN_TOKEN_FILE` +2. put your token in `~/.openclaw/workspace/.clawdbot/credentials/nordvpn/token.txt` or set `NORDVPN_TOKEN` / `NORDVPN_TOKEN_FILE` 3. `node scripts/nordvpn-client.js login` 4. `node scripts/nordvpn-client.js connect --country "Italy"` or `--city "Milan"` 5. `node scripts/nordvpn-client.js verify` diff --git a/skills/nordvpn-client/scripts/nordvpn-client.js b/skills/nordvpn-client/scripts/nordvpn-client.js index 0007330..6e19afe 100644 --- a/skills/nordvpn-client/scripts/nordvpn-client.js +++ b/skills/nordvpn-client/scripts/nordvpn-client.js @@ -12,6 +12,16 @@ const WG_STATE_DIR = path.join(STATE_DIR, "wireguard"); const WG_CONFIG_PATH = path.join(WG_STATE_DIR, `${MAC_WG_INTERFACE}.conf`); const AUTH_CACHE_PATH = path.join(STATE_DIR, "auth.json"); const LAST_CONNECTION_PATH = path.join(STATE_DIR, "last-connection.json"); +const OPENCLAW_NORDVPN_CREDENTIALS_DIR = path.join( + os.homedir(), + ".openclaw", + "workspace", + ".clawdbot", + "credentials", + "nordvpn" +); +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_DNS_IPV4 = "103.86.96.100"; const DEFAULT_DNS_IPV6 = "2400:bb40:4444::100"; const CLIENT_IPV4 = "10.5.0.2"; @@ -39,9 +49,11 @@ function usage() { env: [ "NORDVPN_TOKEN", "NORDVPN_TOKEN_FILE", + `default token file: ${DEFAULT_TOKEN_FILE}`, "NORDVPN_USERNAME", "NORDVPN_PASSWORD", "NORDVPN_PASSWORD_FILE", + `default password file: ${DEFAULT_PASSWORD_FILE}`, ], }; } @@ -75,13 +87,22 @@ function detectPlatform() { function readSecret(envName, fileEnvName) { if (process.env[envName]) return process.env[envName]; - const filePath = process.env[fileEnvName]; - if (!filePath) return ""; - try { - return fs.readFileSync(filePath, "utf8").trim(); - } catch { - return ""; + + const candidates = []; + if (process.env[fileEnvName]) candidates.push(process.env[fileEnvName]); + if (envName === "NORDVPN_TOKEN") candidates.push(DEFAULT_TOKEN_FILE); + if (envName === "NORDVPN_PASSWORD") candidates.push(DEFAULT_PASSWORD_FILE); + + for (const candidate of candidates) { + try { + const value = fs.readFileSync(candidate, "utf8").trim(); + if (value) return value; + } catch { + // Continue. + } } + + return ""; } function normalizeLocation(value) { @@ -370,7 +391,7 @@ function buildStateSummary(installProbe, ipInfo) { ? 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." - : "Set NORDVPN_TOKEN or NORDVPN_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) { controlMode = "app-manual"; automaticControl = false; @@ -378,7 +399,7 @@ function buildStateSummary(installProbe, ipInfo) { connectMode = "app-manual"; recommendedAction = installProbe.tokenAvailable ? "NordVPN.app is installed, but automated macOS connects also require wireguard-go and wireguard-tools. Run 'node scripts/nordvpn-client.js install' to install them with Homebrew." - : "NordVPN.app is installed. For automated macOS connects, run 'node scripts/nordvpn-client.js install' to install wireguard-go and wireguard-tools with Homebrew, then set NORDVPN_TOKEN or NORDVPN_TOKEN_FILE and run login."; + : `NordVPN.app is installed. For automated macOS connects, run 'node scripts/nordvpn-client.js install' to install wireguard-go and wireguard-tools with Homebrew, then set NORDVPN_TOKEN, NORDVPN_TOKEN_FILE, or place the token at ${DEFAULT_TOKEN_FILE} and run login.`; } else if (installProbe.platform === "darwin" && installProbe.brewPath) { controlMode = "wireguard-bootstrap"; automaticControl = false; @@ -759,7 +780,7 @@ async function runSudoWireguard(installProbe, action) { async function connectViaMacWireguard(installProbe, target) { const token = readSecret("NORDVPN_TOKEN", "NORDVPN_TOKEN_FILE"); if (!token) { - throw new Error("macOS NordLynx/WireGuard automation requires NORDVPN_TOKEN or NORDVPN_TOKEN_FILE."); + throw new Error(`macOS NordLynx/WireGuard automation requires NORDVPN_TOKEN, NORDVPN_TOKEN_FILE, or a token at ${DEFAULT_TOKEN_FILE}.`); } if (!installProbe.wireguard || !installProbe.wireguard.dependenciesReady) { throw new Error("wireguard-go and wireguard-tools are required on macOS. Run install first."); @@ -912,7 +933,13 @@ async function loginNordvpn(installProbe) { backend: "wireguard", validatedAt: new Date().toISOString(), hasNordlynxPrivateKey: Boolean(credentials.nordlynx_private_key), - tokenSource: process.env.NORDVPN_TOKEN ? "env:NORDVPN_TOKEN" : process.env.NORDVPN_TOKEN_FILE ? "file:NORDVPN_TOKEN_FILE" : "unknown", + tokenSource: process.env.NORDVPN_TOKEN + ? "env:NORDVPN_TOKEN" + : process.env.NORDVPN_TOKEN_FILE + ? "file:NORDVPN_TOKEN_FILE" + : fileExists(DEFAULT_TOKEN_FILE) + ? `default:${DEFAULT_TOKEN_FILE}` + : "unknown", }; writeJsonFile(AUTH_CACHE_PATH, cache); return { @@ -932,7 +959,7 @@ async function loginNordvpn(installProbe) { backend: "app-manual", manualActionRequired: true, message: - "Opened NordVPN.app. Complete login in the app/browser flow, or set NORDVPN_TOKEN / NORDVPN_TOKEN_FILE to use the automated macOS WireGuard backend.", + `Opened NordVPN.app. Complete login in the app/browser flow, or set NORDVPN_TOKEN, NORDVPN_TOKEN_FILE, or place the token at ${DEFAULT_TOKEN_FILE} to use the automated macOS WireGuard backend.`, }; } @@ -1007,7 +1034,7 @@ async function main() { } else if (platform === "darwin" && installProbe.appInstalled) { connectResult = await connectViaMacApp(target); } else if (platform === "darwin" && !installProbe.tokenAvailable) { - throw new Error("macOS automated NordLynx/WireGuard connects require NORDVPN_TOKEN or NORDVPN_TOKEN_FILE."); + throw new Error(`macOS automated NordLynx/WireGuard connects require NORDVPN_TOKEN, NORDVPN_TOKEN_FILE, or a token at ${DEFAULT_TOKEN_FILE}.`); } else if (platform === "darwin") { throw new Error("No usable macOS NordVPN backend is ready. Run install to bootstrap WireGuard tooling."); } else if (!installProbe.installed) {