feat: remove tunnel and tailscale feature

This commit is contained in:
2026-07-18 19:46:52 +07:00
parent 3ebfab2684
commit 6bb03d85f7
60 changed files with 1172 additions and 4254 deletions
+2 -53
View File
@@ -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");
-33
View File
@@ -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,
+3 -59
View File
@@ -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
+5 -17
View File
@@ -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}`;
}
/**
+20 -32
View File
@@ -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 };