Latest Threat Research:SANDWORM_MODE: Shai-Hulud-Style npm Worm Hijacks CI Workflows and Poisons AI Toolchains.Details
Socket
Book a DemoInstallSign in
Socket

9remote

Package Overview
Dependencies
Maintainers
1
Versions
33
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

9remote - npm Package Compare versions

Comparing version
0.1.25
to
0.1.26
+273
utils/cloudflared.old.js
import fs from "fs";
import path from "path";
import https from "https";
import os from "os";
import { execSync, spawn } from "child_process";
const BIN_DIR = path.join(os.homedir(), ".9remote", "bin");
const BINARY_NAME = "cloudflared";
const IS_WINDOWS = os.platform() === "win32";
const BIN_NAME = IS_WINDOWS ? `${BINARY_NAME}.exe` : BINARY_NAME;
const BIN_PATH = path.join(BIN_DIR, BIN_NAME);
const PID_FILE = path.join(os.homedir(), ".9remote", "cloudflared.pid");
// Track intentional shutdown to suppress exit logs
let isIntentionalShutdown = false;
// Auto-restart configuration
const MAX_RESTART_ATTEMPTS = 5;
const RESTART_WINDOW_MS = 60000; // 1 minute
let restartTimes = [];
let restartCallback = null;
const GITHUB_BASE_URL = "https://github.com/cloudflare/cloudflared/releases/latest/download";
/**
* Platform mappings for cloudflared
*/
const PLATFORM_MAPPINGS = {
darwin: {
x64: "cloudflared-darwin-amd64.tgz",
arm64: "cloudflared-darwin-amd64.tgz"
},
win32: {
x64: "cloudflared-windows-amd64.exe"
},
linux: {
x64: "cloudflared-linux-amd64",
arm64: "cloudflared-linux-arm64"
}
};
/**
* Get download URL
*/
function getDownloadUrl() {
const platform = os.platform();
const arch = os.arch();
const platformMapping = PLATFORM_MAPPINGS[platform];
if (!platformMapping) {
throw new Error(`Unsupported platform: ${platform}`);
}
const binaryName = platformMapping[arch];
if (!binaryName) {
throw new Error(`Unsupported architecture: ${arch} for platform ${platform}`);
}
return `${GITHUB_BASE_URL}/${binaryName}`;
}
/**
* Download file from URL
*/
async function downloadFile(url, dest) {
return new Promise((resolve, reject) => {
const file = fs.createWriteStream(dest);
https.get(url, (response) => {
if ([301, 302].includes(response.statusCode)) {
file.close();
fs.unlinkSync(dest);
downloadFile(response.headers.location, dest).then(resolve).catch(reject);
return;
}
if (response.statusCode !== 200) {
file.close();
fs.unlinkSync(dest);
reject(new Error(`Download failed with status ${response.statusCode}`));
return;
}
response.pipe(file);
file.on("finish", () => {
file.close(() => resolve(dest));
});
file.on("error", (err) => {
file.close();
fs.unlinkSync(dest);
reject(err);
});
}).on("error", (err) => {
file.close();
if (fs.existsSync(dest)) fs.unlinkSync(dest);
reject(err);
});
});
}
/**
* Ensure cloudflared binary exists
*/
export async function ensureCloudflared() {
if (!fs.existsSync(BIN_DIR)) {
fs.mkdirSync(BIN_DIR, { recursive: true });
}
if (fs.existsSync(BIN_PATH)) {
if (!IS_WINDOWS) {
fs.chmodSync(BIN_PATH, "755");
}
return BIN_PATH;
}
console.log("📥 Downloading tunnel binary...");
const url = getDownloadUrl();
const isArchive = url.endsWith(".tgz");
const downloadDest = isArchive ? path.join(BIN_DIR, "cloudflared.tgz") : BIN_PATH;
try {
await downloadFile(url, downloadDest);
if (isArchive) {
console.log("✅ Extracting...");
execSync(`tar -xzf "${downloadDest}" -C "${BIN_DIR}"`, { stdio: "pipe" });
fs.unlinkSync(downloadDest);
}
if (!IS_WINDOWS) {
fs.chmodSync(BIN_PATH, "755");
}
console.log("✅ cloudflared ready");
return BIN_PATH;
} catch (error) {
console.error("❌ Failed to download cloudflared:", error.message);
throw error;
}
}
// Log patterns to filter cloudflared output
const LOG_IGNORE = [
"INF Starting tunnel",
"INF Version",
"GOOS:",
"Settings:",
"Autoupdate frequency",
"Generated Connector",
"Initial protocol",
"ICMP proxy",
"Created ICMP",
"Starting metrics server",
"curve preferences",
"Updated to new configuration"
];
/**
* Spawn cloudflared tunnel
* @param {string} tunnelToken
* @param {Function} onRestart - Callback when tunnel needs restart
* @returns {ChildProcess}
*/
export async function spawnCloudflared(tunnelToken, onRestart = null) {
const binaryPath = await ensureCloudflared();
// Store restart callback
if (onRestart) {
restartCallback = onRestart;
}
const child = spawn(binaryPath, ["tunnel", "run", "--token", tunnelToken], {
detached: false,
stdio: ["ignore", "pipe", "pipe"]
});
let connectionCount = 0;
const handleLog = (data) => {
const msg = data.toString().trim();
// Skip ignored messages
if (LOG_IGNORE.some(pattern => msg.includes(pattern))) {
return;
}
// Skip errors during intentional shutdown
if (isIntentionalShutdown) {
return;
}
// Show connection status briefly
if (msg.includes("Registered tunnel connection")) {
connectionCount++;
if (connectionCount <= 4) {
process.stdout.write(`\r ✔ Connection ${connectionCount}/4 established`);
if (connectionCount === 4) {
process.stdout.write("\n");
}
}
return;
}
// Show errors
if (msg.includes("ERR") || msg.includes("error") || msg.includes("failed")) {
console.error(`[cloudflared] ${msg}`);
}
};
child.stdout.on("data", handleLog);
child.stderr.on("data", handleLog);
child.on("error", (error) => {
console.error("❌ cloudflared error:", error);
});
child.on("exit", (code) => {
// Only log unexpected exits
if (!isIntentionalShutdown && code !== 0 && code !== null) {
console.log(`cloudflared exited with code ${code}`);
// Auto-restart logic
if (restartCallback) {
const now = Date.now();
restartTimes.push(now);
// Remove old restart times outside window
restartTimes = restartTimes.filter(t => t > now - RESTART_WINDOW_MS);
if (restartTimes.length <= MAX_RESTART_ATTEMPTS) {
console.log(`🔄 Restarting tunnel... (attempt ${restartTimes.length}/${MAX_RESTART_ATTEMPTS})`);
setTimeout(() => {
restartCallback(tunnelToken);
}, 2000);
} else {
console.log(`❌ Too many tunnel restarts (${MAX_RESTART_ATTEMPTS} in ${RESTART_WINDOW_MS / 1000}s). Giving up.`);
}
}
}
});
// Save PID
fs.writeFileSync(PID_FILE, child.pid.toString());
return child;
}
/**
* Kill cloudflared process
*/
export function killCloudflared() {
try {
if (fs.existsSync(PID_FILE)) {
isIntentionalShutdown = true;
const pid = parseInt(fs.readFileSync(PID_FILE, "utf8"));
process.kill(pid);
fs.unlinkSync(PID_FILE);
}
} catch (error) {
// Silently ignore errors
}
}
/**
* Reset restart counter
*/
export function resetRestartCounter() {
restartTimes = [];
restartCallback = null;
}
+99
-17

@@ -26,3 +26,3 @@ #!/usr/bin/env node

const SERVER_PORT = 2208;
const MAX_RESTART_ATTEMPTS = 3;
const MAX_RESTART_ATTEMPTS = 10;
const RESTART_WINDOW_MS = 60000; // 1 minute

@@ -189,3 +189,3 @@

*/
function startServerWithRestart(onReady) {
function startServerWithRestart(onReady, onServerCrash) {
const restartTimes = [];

@@ -201,2 +201,3 @@ let currentProcess = null;

isFirstStart = false;
} else {
}

@@ -219,5 +220,8 @@

});
currentProcess.on("exit", (code, signal) => {
if (isShuttingDown) return;
if (isShuttingDown) {
return;
}

@@ -243,3 +247,10 @@ // Check if it's a crash (non-zero exit code or unexpected signal)

console.log(chalk.yellow(`🔄 Restarting server... (attempt ${restartTimes.length}/${MAX_RESTART_ATTEMPTS})`));
console.log(ORANGE_DIM("⚠️ [DEBUG] NOTE: Tunnel connection may be stale - will restart tunnel"));
// ✅ Callback để restart cloudflared
if (onServerCrash) {
console.log(chalk.yellow("✅ Restarting tunnel connection..."));
onServerCrash();
}
// Wait a bit before restart

@@ -249,2 +260,3 @@ setTimeout(() => {

}, 1000);
} else {
}

@@ -278,19 +290,16 @@ });

*/
let exitHandlerRegistered = false;
function setupExitHandler(serverManager, tunnelProcess, apiKey) {
if (exitHandlerRegistered) return;
exitHandlerRegistered = true;
process.on("SIGINT", async () => {
console.log(chalk.yellow("\n\n🛑 Stopping server..."));
serverManager.shutdown();
tunnelProcess.kill();
resetRestartCounter();
clearState();
// Cleanup tunnel on worker
try {
await fetch(`${WORKER_URL}/api/tunnel/delete`, {
method: "DELETE",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ apiKey })
});
} catch { }
clearState();
console.log(chalk.green("✅ Server stopped"));

@@ -316,3 +325,4 @@ process.exit(0);

return response.json();
const data = await response.json();
return data;
}

@@ -329,2 +339,4 @@

killCloudflared();
// Wait for process to be killed
await new Promise(resolve => setTimeout(resolve, 1000));
} catch { }

@@ -334,3 +346,3 @@

try {
await fetch(`${WORKER_URL}/api/session/create`, {
const sessionResponse = await fetch(`${WORKER_URL}/api/session/create`, {
method: "POST",

@@ -340,2 +352,11 @@ headers: { "Content-Type": "application/json" },

});
if (!sessionResponse.ok) {
const text = await sessionResponse.text();
console.log(chalk.red(`❌ Failed to create session: ${sessionResponse.status} ${sessionResponse.statusText}`));
console.log(chalk.yellow(`Response: ${text.substring(0, 200)}`));
return null;
}
const sessionData = await sessionResponse.json();
} catch (error) {

@@ -347,3 +368,61 @@ console.log(chalk.red(`❌ Failed to create session: ${error.message}`));

// Start server with auto-restart
const serverManager = startServerWithRestart();
const serverManager = startServerWithRestart(null, async () => {
// Callback khi server crash - đợi server ready rồi gửi SIGHUP
if (!tunnelProcess) {
return;
}
// Wait for server to be ready
const maxWait = 60000; // 60s
const checkInterval = 1000; // 1s
const maxRetries = Math.floor(maxWait / checkInterval);
let serverReady = false;
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch(`http://localhost:${SERVER_PORT}/api/health`, {
method: "GET",
timeout: 2000
});
if (response.ok) {
const data = await response.json();
if (data.status === "ok") {
serverReady = true;
console.log(chalk.green(`✅ Server ready after ${i + 1}s`));
break;
}
}
} catch (err) {
// Server not ready yet
}
if (i % 5 === 0) {
}
await new Promise(resolve => setTimeout(resolve, checkInterval));
}
if (!serverReady) {
console.log(chalk.red("❌ Server not ready after 60s - skipping tunnel reconnect"));
return;
}
// Server ready - send SIGHUP to cloudflared to reconnect
try {
process.kill(tunnelProcess.pid, "SIGHUP");
console.log(chalk.green("✅ SIGHUP sent - cloudflared should reconnect"));
} catch (err) {
// Fallback: kill and restart
try {
tunnelProcess.kill();
await new Promise(resolve => setTimeout(resolve, 1000));
tunnelProcess = await startTunnel(token);
console.log(chalk.green("✅ Tunnel restarted"));
} catch (restartErr) {
console.log(chalk.red(`❌ Failed to restart tunnel: ${restartErr.message}`));
}
}
});

@@ -396,2 +475,3 @@ // Wait for server to start

}

@@ -431,5 +511,7 @@ // Wait for tunnel to be ready

break;
} else {
}
}
} catch { }
} catch (err) {
}

@@ -436,0 +518,0 @@ const spinners = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];

+1
-1
{
"name": "9remote",
"version": "0.1.25",
"version": "0.1.26",
"type": "module",

@@ -5,0 +5,0 @@ "description": "Remote terminal access from anywhere",

@@ -18,3 +18,3 @@ import fs from "fs";

// Auto-restart configuration
const MAX_RESTART_ATTEMPTS = 3;
const MAX_RESTART_ATTEMPTS = 5;
const RESTART_WINDOW_MS = 60000; // 1 minute

@@ -181,2 +181,7 @@ let restartTimes = [];

// Reset intentional shutdown flag immediately after spawn
// This ensures auto-restart works if cloudflared crashes
isIntentionalShutdown = false;
console.log(`✅ Cloudflared spawned with PID: ${child.pid}`);
let connectionCount = 0;

@@ -210,5 +215,3 @@

// Show errors
if (msg.includes("ERR") || msg.includes("error") || msg.includes("failed")) {
console.error(`[cloudflared] ${msg}`);
}
console.log(`[cloudflared] ${msg}`);
};

@@ -223,6 +226,8 @@

child.on("exit", (code) => {
// Only log unexpected exits
if (!isIntentionalShutdown && code !== 0 && code !== null) {
console.log(`cloudflared exited with code ${code}`);
child.on("exit", (code, signal) => {
console.log(`⚠️ Cloudflared process exited (code: ${code}, signal: ${signal}, intentional: ${isIntentionalShutdown})`);
// Restart on ANY unexpected exit (including code 0 if not intentional)
if (!isIntentionalShutdown) {
console.log(`⚠️ Cloudflared unexpected exit detected - will restart`);

@@ -240,2 +245,3 @@ // Auto-restart logic

setTimeout(() => {
console.log(`🔄 Executing tunnel restart...`);
restartCallback(tunnelToken);

@@ -246,3 +252,7 @@ }, 2000);

}
} else {
console.log(`⚠️ No restart callback registered`);
}
} else {
console.log(`ℹ️ Cloudflared exit ignored (intentional shutdown)`);
}

@@ -265,7 +275,9 @@ });

const pid = parseInt(fs.readFileSync(PID_FILE, "utf8"));
console.log(`🔄 Killing cloudflared process PID: ${pid}`);
process.kill(pid);
fs.unlinkSync(PID_FILE);
console.log(`✅ Cloudflared killed`);
}
} catch (error) {
// Silently ignore errors
console.log(`⚠️ Error killing cloudflared: ${error.message}`);
}

@@ -272,0 +284,0 @@ }

Sorry, the diff of this file is too big to display