mirror of
https://github.com/Nezumi-2711/9router.git
synced 2026-09-22 13:38:31 +00:00
feat: remove tunnel and tailscale feature
This commit is contained in:
+2
-53
@@ -150,54 +150,14 @@ function killByPidFile(pidFile) {
|
||||
} catch { }
|
||||
}
|
||||
|
||||
// Kill tunnel processes (cloudflared/tailscale) by their PID files
|
||||
function killTunnelByPidFile() {
|
||||
const tunnelDir = path.join(getAppDataDir(), "tunnel");
|
||||
killByPidFile(path.join(tunnelDir, "cloudflared.pid"));
|
||||
killByPidFile(path.join(tunnelDir, "tailscale.pid"));
|
||||
}
|
||||
|
||||
// Kill cloudflared whose --url targets this app's port (covers stale PID file case)
|
||||
function killCloudflaredByAppPort(appPort) {
|
||||
if (!appPort) return [];
|
||||
const portMatchers = [`localhost:${appPort}`, `127.0.0.1:${appPort}`];
|
||||
const pids = [];
|
||||
try {
|
||||
if (process.platform === "win32") {
|
||||
const psCmd = `powershell -NonInteractive -WindowStyle Hidden -Command "Get-WmiObject Win32_Process -Filter 'Name=\\"cloudflared.exe\\"' | Select-Object ProcessId,CommandLine | ConvertTo-Csv -NoTypeInformation"`;
|
||||
const output = execSync(psCmd, { encoding: "utf8", windowsHide: true, timeout: 5000 });
|
||||
const lines = output.split("\n").slice(1).filter(l => l.trim());
|
||||
lines.forEach(line => {
|
||||
if (portMatchers.some(m => line.includes(m))) {
|
||||
const match = line.match(/^"(\d+)"/);
|
||||
if (match && match[1]) pids.push(match[1]);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const output = execSync("ps -eo pid,command 2>/dev/null", { encoding: "utf8", timeout: 5000 });
|
||||
output.split("\n").forEach(line => {
|
||||
if (line.includes("cloudflared") && portMatchers.some(m => line.includes(m))) {
|
||||
const parts = line.trim().split(/\s+/);
|
||||
const pid = parts[0];
|
||||
if (pid && !isNaN(pid)) pids.push(pid);
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch { }
|
||||
return pids;
|
||||
}
|
||||
|
||||
// Kill all 9router processes
|
||||
function killAllAppProcesses(appPort) {
|
||||
return new Promise((resolve) => {
|
||||
try {
|
||||
// Background: MITM + tunnel/cloudflared run on separate ports/processes —
|
||||
// killing them doesn't free the app port, so don't block the critical path.
|
||||
// MITM runs on a separate process and does not block the critical path.
|
||||
// Server-side MITM manager has stale-lock recovery and starts deferred (~3s).
|
||||
setImmediate(() => {
|
||||
try { killProxyByPidFile(); } catch {}
|
||||
try { killTunnelByPidFile(); } catch {}
|
||||
try { killCloudflaredByAppPort(appPort); } catch {}
|
||||
});
|
||||
|
||||
const platform = process.platform;
|
||||
@@ -438,20 +398,11 @@ killAllAppProcesses(port)
|
||||
async function showInterfaceMenu() {
|
||||
const { selectMenu } = require("./src/cli/utils/input");
|
||||
const { clearScreen } = require("./src/cli/utils/display");
|
||||
const { getEndpoint } = require("./src/cli/utils/endpoint");
|
||||
|
||||
clearScreen();
|
||||
|
||||
const displayHost = getDisplayHost();
|
||||
|
||||
// Detect tunnel/local mode for server URL display
|
||||
let serverUrl;
|
||||
try {
|
||||
const { endpoint, tunnelEnabled } = await getEndpoint(port);
|
||||
serverUrl = tunnelEnabled ? endpoint.replace(/\/v1$/, "") : `http://${displayHost}:${port}`;
|
||||
} catch (e) {
|
||||
serverUrl = `http://${displayHost}:${port}`;
|
||||
}
|
||||
const serverUrl = `http://${displayHost}:${port}`;
|
||||
|
||||
const subtitle = `🚀 Server: \x1b[32m${serverUrl}\x1b[0m`;
|
||||
|
||||
@@ -529,8 +480,6 @@ function startServer() {
|
||||
} catch (e) { }
|
||||
// Kill MIT server (privileged process) via PID file
|
||||
killProxyByPidFile();
|
||||
// Kill cloudflared/tailscale via PID file (only this app's tunnel)
|
||||
killTunnelByPidFile();
|
||||
// Kill server process directly
|
||||
if (server.pid) {
|
||||
process.kill(server.pid, "SIGKILL");
|
||||
|
||||
@@ -430,34 +430,6 @@ async function validateProviderNode(data) {
|
||||
return makeRequest("POST", "/api/provider-nodes/validate", data);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TUNNEL API
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Get tunnel status
|
||||
* @returns {Promise<Object>} { success, data: { enabled, tunnelUrl, shortId, running } }
|
||||
*/
|
||||
async function getTunnelStatus() {
|
||||
return makeRequest("GET", "/api/tunnel/status");
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable tunnel
|
||||
* @returns {Promise<Object>} { success, data: { tunnelUrl, shortId } }
|
||||
*/
|
||||
async function enableTunnel() {
|
||||
return makeRequest("POST", "/api/tunnel/enable");
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable tunnel
|
||||
* @returns {Promise<Object>} { success, data: { success } }
|
||||
*/
|
||||
async function disableTunnel() {
|
||||
return makeRequest("POST", "/api/tunnel/disable");
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// EXPORTS
|
||||
// ============================================================================
|
||||
@@ -501,11 +473,6 @@ module.exports = {
|
||||
updateSettings,
|
||||
resetPassword,
|
||||
|
||||
// Tunnel
|
||||
getTunnelStatus,
|
||||
enableTunnel,
|
||||
disableTunnel,
|
||||
|
||||
// Models
|
||||
getModels,
|
||||
getAvailableModels,
|
||||
|
||||
@@ -16,7 +16,7 @@ const COLORS = {
|
||||
const DEFAULT_PASSWORD = "123456";
|
||||
|
||||
/**
|
||||
* Show settings menu (tunnel + RTK + reset password)
|
||||
* Show settings menu (RTK + reset password)
|
||||
* @param {Array<string>} breadcrumb - Breadcrumb path
|
||||
*/
|
||||
async function showSettingsMenu(breadcrumb = []) {
|
||||
@@ -26,15 +26,7 @@ async function showSettingsMenu(breadcrumb = []) {
|
||||
headerContent: async (data) => {
|
||||
const lines = [];
|
||||
|
||||
// Tunnel section
|
||||
const tunnel = data?.tunnel || {};
|
||||
if (tunnel.enabled && tunnel.publicUrl) {
|
||||
lines.push(` Endpoint: ${COLORS.green}${tunnel.publicUrl}/v1${COLORS.reset}`);
|
||||
lines.push(` Tunnel: ${COLORS.green}ON${COLORS.reset} ${COLORS.dim}(${tunnel.shortId})${COLORS.reset}`);
|
||||
} else {
|
||||
lines.push(` Endpoint: http://localhost:20128/v1`);
|
||||
lines.push(` Tunnel: ${COLORS.red}OFF${COLORS.reset} ${COLORS.dim}(local only)${COLORS.reset}`);
|
||||
}
|
||||
lines.push(" Endpoint: http://localhost:20128/v1");
|
||||
|
||||
// RTK section
|
||||
const rtkOn = data?.settings?.rtkEnabled !== false;
|
||||
@@ -50,24 +42,12 @@ async function showSettingsMenu(breadcrumb = []) {
|
||||
return lines.join("\n");
|
||||
},
|
||||
refresh: async () => {
|
||||
const [tunnelRes, settingsRes] = await Promise.all([
|
||||
api.getTunnelStatus(),
|
||||
api.getSettings()
|
||||
]);
|
||||
const settingsRes = await api.getSettings();
|
||||
return {
|
||||
tunnel: tunnelRes.success ? (tunnelRes.data || {}) : {},
|
||||
settings: settingsRes.success ? (settingsRes.data || {}) : {}
|
||||
};
|
||||
},
|
||||
items: [
|
||||
{
|
||||
label: "Tunnel ON",
|
||||
action: async () => { await enableTunnel(); return true; }
|
||||
},
|
||||
{
|
||||
label: "Tunnel OFF",
|
||||
action: async () => { await disableTunnel(); return true; }
|
||||
},
|
||||
{
|
||||
label: (d) => {
|
||||
const on = d?.settings?.rtkEnabled !== false;
|
||||
@@ -118,42 +98,6 @@ async function resetAuthMode() {
|
||||
await pause();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable tunnel via API
|
||||
*/
|
||||
async function enableTunnel() {
|
||||
showStatus("Creating tunnel...", "info");
|
||||
const result = await api.enableTunnel();
|
||||
|
||||
if (result.success) {
|
||||
const { publicUrl, shortId, alreadyRunning } = result.data || {};
|
||||
if (alreadyRunning) {
|
||||
showStatus(`Tunnel already running: ${publicUrl}`, "success");
|
||||
} else {
|
||||
showStatus(`Tunnel enabled: ${publicUrl} (${shortId})`, "success");
|
||||
}
|
||||
} else {
|
||||
showStatus(`Failed: ${result.error}`, "error");
|
||||
}
|
||||
|
||||
await pause();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable tunnel via API
|
||||
*/
|
||||
async function disableTunnel() {
|
||||
const result = await api.disableTunnel();
|
||||
|
||||
if (result.success) {
|
||||
showStatus("Tunnel disabled", "success");
|
||||
} else {
|
||||
showStatus(`Failed: ${result.error}`, "error");
|
||||
}
|
||||
|
||||
await pause();
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle RTK (Token Saver) via API
|
||||
* @param {boolean} currentlyOn
|
||||
|
||||
@@ -17,16 +17,8 @@ const COLORS = {
|
||||
let cachedHeader = "";
|
||||
let fetchingHeader = false;
|
||||
|
||||
function renderHeader(port, keys, tunnel) {
|
||||
const tunnelEnabled = tunnel && tunnel.enabled === true;
|
||||
const lines = [];
|
||||
if (tunnelEnabled && tunnel.publicUrl) {
|
||||
lines.push(`Endpoint: ${COLORS.green}${tunnel.publicUrl}/v1${COLORS.reset}`);
|
||||
lines.push(`Tunnel: ${COLORS.green}ON${COLORS.reset} ${COLORS.dim}(${tunnel.shortId})${COLORS.reset}`);
|
||||
} else {
|
||||
lines.push(`Endpoint: http://localhost:${port}/v1`);
|
||||
lines.push(`Tunnel: ${COLORS.red}OFF${COLORS.reset} ${COLORS.dim}(local only)${COLORS.reset}`);
|
||||
}
|
||||
function renderHeader(port, keys) {
|
||||
const lines = [`Endpoint: http://localhost:${port}/v1`];
|
||||
if (!keys || keys.length === 0) {
|
||||
lines.push(`Key: ${COLORS.dim}No API keys yet${COLORS.reset}`);
|
||||
} else {
|
||||
@@ -40,13 +32,9 @@ async function refreshHeaderBg(port) {
|
||||
if (fetchingHeader) return;
|
||||
fetchingHeader = true;
|
||||
try {
|
||||
const [keysResult, tunnelResult] = await Promise.all([
|
||||
api.getApiKeys(),
|
||||
api.getTunnelStatus()
|
||||
]);
|
||||
const keysResult = await api.getApiKeys();
|
||||
const keys = keysResult.success ? (keysResult.data.keys || []) : [];
|
||||
const tunnel = tunnelResult.success ? (tunnelResult.data || {}) : {};
|
||||
cachedHeader = renderHeader(port, keys, tunnel);
|
||||
cachedHeader = renderHeader(port, keys);
|
||||
} finally {
|
||||
fetchingHeader = false;
|
||||
}
|
||||
@@ -55,7 +43,7 @@ async function refreshHeaderBg(port) {
|
||||
function getHeader(port) {
|
||||
// Kick off background refresh; return cache (or placeholder on first call).
|
||||
refreshHeaderBg(port);
|
||||
return cachedHeader || `Endpoint: http://localhost:${port}/v1\nTunnel: ${COLORS.dim}...${COLORS.reset}\nKey: ${COLORS.dim}...${COLORS.reset}`;
|
||||
return cachedHeader || `Endpoint: http://localhost:${port}/v1\nKey: ${COLORS.dim}...${COLORS.reset}`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,32 +1,20 @@
|
||||
const api = require("../api/client");
|
||||
|
||||
const COLORS = {
|
||||
reset: "\x1b[0m",
|
||||
green: "\x1b[32m"
|
||||
};
|
||||
|
||||
/**
|
||||
* Get endpoint URL based on tunnel status
|
||||
* @param {number} port - Local server port
|
||||
* @returns {Promise<{endpoint: string, tunnelEnabled: boolean}>}
|
||||
*/
|
||||
async function getEndpoint(port) {
|
||||
const result = await api.getTunnelStatus();
|
||||
const tunnelEnabled = result.success && result.data?.enabled === true;
|
||||
const publicUrl = result.success ? result.data?.publicUrl : "";
|
||||
|
||||
const endpoint = tunnelEnabled && publicUrl ? `${publicUrl}/v1` : `http://localhost:${port}/v1`;
|
||||
return { endpoint, tunnelEnabled };
|
||||
}
|
||||
|
||||
/**
|
||||
* Get endpoint with color formatting
|
||||
* @param {number} port - Local server port
|
||||
* @returns {Promise<string>} Colored endpoint string
|
||||
*/
|
||||
async function getEndpointColored(port) {
|
||||
const { endpoint, tunnelEnabled } = await getEndpoint(port);
|
||||
return tunnelEnabled ? `${COLORS.green}${endpoint}${COLORS.reset}` : endpoint;
|
||||
}
|
||||
|
||||
module.exports = { getEndpoint, getEndpointColored };
|
||||
/**
|
||||
* Get the local gateway endpoint.
|
||||
* @param {number} port - Local server port
|
||||
* @returns {Promise<{endpoint: string}>}
|
||||
*/
|
||||
async function getEndpoint(port) {
|
||||
return { endpoint: `http://localhost:${port}/v1` };
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the local gateway endpoint for terminal output.
|
||||
* @param {number} port - Local server port
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
async function getEndpointColored(port) {
|
||||
const { endpoint } = await getEndpoint(port);
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
module.exports = { getEndpoint, getEndpointColored };
|
||||
Reference in New Issue
Block a user