fix: force mac nordvpn disconnect teardown

This commit is contained in:
Stefano Fiorini
2026-03-12 02:22:50 -05:00
parent b4c8d3fdb8
commit 647828aa78
2 changed files with 62 additions and 4 deletions

View File

@@ -365,6 +365,12 @@ function cleanupMacWireguardState(paths = {}) {
return { cleaned };
}
function shouldAttemptMacWireguardDisconnect(wireguardState) {
if (!wireguardState) return false;
if (wireguardState.active) return true;
return Boolean(wireguardState.configPath || wireguardState.endpoint || wireguardState.lastConnection);
}
function fetchJson(url, headers = {}) {
return new Promise((resolve) => {
const targetUrl = new URL(url);
@@ -1107,20 +1113,35 @@ async function disconnectNordvpn(installProbe) {
}
if (installProbe.platform === "darwin" && installProbe.wireguard && installProbe.wireguard.dependenciesReady) {
if (!installProbe.wireguard.active) {
const cleaned = cleanupMacWireguardState();
if (!shouldAttemptMacWireguardDisconnect(installProbe.wireguard)) {
const tailscale = await resumeMacTailscaleIfNeeded();
return {
backend: "wireguard",
changed: false,
stateCleaned: cleaned.cleaned,
tailscaleRestored: tailscale.restored,
message: "No active macOS WireGuard NordVPN connection found.",
};
}
const down = await runSudoWireguard(installProbe, "down");
if (!down.ok) {
throw new Error((down.stderr || down.stdout || down.error).trim() || "wg-quick down failed");
const message = (down.stderr || down.stdout || down.error).trim();
const normalized = message.toLowerCase();
if (
normalized.includes("is not a known interface") ||
normalized.includes("unable to access interface") ||
normalized.includes("not found")
) {
const cleaned = cleanupMacWireguardState();
const tailscale = await resumeMacTailscaleIfNeeded();
return {
backend: "wireguard",
changed: false,
stateCleaned: cleaned.cleaned,
tailscaleRestored: tailscale.restored,
message: "No active macOS WireGuard NordVPN connection found.",
};
}
throw new Error(message || "wg-quick down failed");
}
const cleaned = cleanupMacWireguardState();
const tailscale = await resumeMacTailscaleIfNeeded();

View File

@@ -21,6 +21,8 @@ module.exports = {
typeof getMacTailscalePath === "function" ? getMacTailscalePath : undefined,
isMacTailscaleActive:
typeof isMacTailscaleActive === "function" ? isMacTailscaleActive : undefined,
shouldAttemptMacWireguardDisconnect:
typeof shouldAttemptMacWireguardDisconnect === "function" ? shouldAttemptMacWireguardDisconnect : undefined,
detectMacWireguardActiveFromIfconfig:
typeof detectMacWireguardActiveFromIfconfig === "function" ? detectMacWireguardActiveFromIfconfig : undefined,
resolveHostnameWithFallback:
@@ -130,6 +132,41 @@ test("cleanupMacWireguardState removes stale config and last-connection files",
assert.equal(fs.existsSync(lastConnectionPath), false);
});
test("shouldAttemptMacWireguardDisconnect does not trust active=false when residual state exists", () => {
const { shouldAttemptMacWireguardDisconnect } = loadInternals();
assert.equal(typeof shouldAttemptMacWireguardDisconnect, "function");
assert.equal(
shouldAttemptMacWireguardDisconnect({
active: false,
configPath: "/Users/stefano/.nordvpn-client/wireguard/nordvpnctl.conf",
endpoint: null,
lastConnection: null,
}),
true
);
assert.equal(
shouldAttemptMacWireguardDisconnect({
active: false,
configPath: null,
endpoint: null,
lastConnection: { country: "Italy" },
}),
true
);
assert.equal(
shouldAttemptMacWireguardDisconnect({
active: false,
configPath: null,
endpoint: null,
lastConnection: null,
}),
false
);
});
test("isMacTailscaleActive treats Running backend as active", () => {
const { isMacTailscaleActive } = loadInternals();
assert.equal(typeof isMacTailscaleActive, "function");