2026-02-09 00:15:31 +07:00
|
|
|
const { exec, spawn } = require("child_process");
|
2026-02-08 16:28:13 +07:00
|
|
|
const fs = require("fs");
|
|
|
|
|
const path = require("path");
|
2026-02-25 10:40:15 +07:00
|
|
|
const os = require("os");
|
2026-02-08 16:28:13 +07:00
|
|
|
|
|
|
|
|
const TARGET_HOST = "daily-cloudcode-pa.googleapis.com";
|
|
|
|
|
const IS_WIN = process.platform === "win32";
|
2026-02-25 10:40:15 +07:00
|
|
|
const IS_MAC = process.platform === "darwin";
|
2026-02-08 16:28:13 +07:00
|
|
|
const HOSTS_FILE = IS_WIN
|
|
|
|
|
? path.join(process.env.SystemRoot || "C:\\Windows", "System32", "drivers", "etc", "hosts")
|
|
|
|
|
: "/etc/hosts";
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Execute command with sudo password via stdin (macOS/Linux only)
|
|
|
|
|
*/
|
|
|
|
|
function execWithPassword(command, password) {
|
|
|
|
|
return new Promise((resolve, reject) => {
|
2026-02-09 00:15:31 +07:00
|
|
|
const child = spawn("sudo", ["-S", "sh", "-c", command], {
|
|
|
|
|
stdio: ["pipe", "pipe", "pipe"]
|
2026-02-08 16:28:13 +07:00
|
|
|
});
|
2026-02-09 00:15:31 +07:00
|
|
|
|
|
|
|
|
let stdout = "";
|
|
|
|
|
let stderr = "";
|
|
|
|
|
child.stdout.on("data", (d) => { stdout += d; });
|
|
|
|
|
child.stderr.on("data", (d) => { stderr += d; });
|
|
|
|
|
|
|
|
|
|
child.on("close", (code) => {
|
|
|
|
|
if (code === 0) resolve(stdout);
|
|
|
|
|
else reject(new Error(stderr || `Exit code ${code}`));
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-08 16:28:13 +07:00
|
|
|
child.stdin.write(`${password}\n`);
|
|
|
|
|
child.stdin.end();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Execute elevated command on Windows via PowerShell RunAs
|
|
|
|
|
*/
|
|
|
|
|
function execElevatedWindows(command) {
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
const psCommand = `Start-Process cmd -ArgumentList '/c','${command.replace(/'/g, "''")}' -Verb RunAs -Wait`;
|
|
|
|
|
exec(`powershell -Command "${psCommand}"`, (error, stdout, stderr) => {
|
|
|
|
|
if (error) {
|
|
|
|
|
reject(new Error(`Elevated command failed: ${error.message}\n${stderr}`));
|
|
|
|
|
} else {
|
|
|
|
|
resolve(stdout);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check if DNS entry already exists
|
|
|
|
|
*/
|
|
|
|
|
function checkDNSEntry() {
|
|
|
|
|
try {
|
|
|
|
|
const hostsContent = fs.readFileSync(HOSTS_FILE, "utf8");
|
|
|
|
|
return hostsContent.includes(TARGET_HOST);
|
|
|
|
|
} catch {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Add DNS entry to hosts file
|
|
|
|
|
*/
|
|
|
|
|
async function addDNSEntry(sudoPassword) {
|
|
|
|
|
if (checkDNSEntry()) {
|
|
|
|
|
console.log(`DNS entry for ${TARGET_HOST} already exists`);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const entry = `127.0.0.1 ${TARGET_HOST}`;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
if (IS_WIN) {
|
|
|
|
|
// Windows: use elevated echo >> hosts
|
|
|
|
|
await execElevatedWindows(`echo ${entry} >> "${HOSTS_FILE}"`);
|
|
|
|
|
} else {
|
2026-02-09 00:15:31 +07:00
|
|
|
await execWithPassword(`echo "${entry}" >> ${HOSTS_FILE}`, sudoPassword);
|
|
|
|
|
}
|
|
|
|
|
// Flush DNS cache
|
|
|
|
|
if (IS_WIN) {
|
|
|
|
|
await execElevatedWindows("ipconfig /flushdns");
|
2026-02-25 10:40:15 +07:00
|
|
|
} else if (IS_MAC) {
|
2026-02-09 00:15:31 +07:00
|
|
|
await execWithPassword("dscacheutil -flushcache && killall -HUP mDNSResponder", sudoPassword);
|
2026-02-25 10:40:15 +07:00
|
|
|
} else {
|
|
|
|
|
// Linux: try systemd-resolved, fall back silently
|
|
|
|
|
await execWithPassword("resolvectl flush-caches 2>/dev/null || true", sudoPassword);
|
2026-02-08 16:28:13 +07:00
|
|
|
}
|
|
|
|
|
console.log(`✅ Added DNS entry: ${entry}`);
|
|
|
|
|
} catch (error) {
|
2026-02-09 00:15:31 +07:00
|
|
|
const msg = error.message?.includes("incorrect password") ? "Wrong sudo password" : "Failed to add DNS entry";
|
|
|
|
|
throw new Error(msg);
|
2026-02-08 16:28:13 +07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Remove DNS entry from hosts file
|
|
|
|
|
*/
|
|
|
|
|
async function removeDNSEntry(sudoPassword) {
|
|
|
|
|
if (!checkDNSEntry()) {
|
|
|
|
|
console.log(`DNS entry for ${TARGET_HOST} does not exist`);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
if (IS_WIN) {
|
2026-02-25 10:40:15 +07:00
|
|
|
// Read in Node, filter, write to temp file, then elevated-copy over hosts
|
|
|
|
|
const content = fs.readFileSync(HOSTS_FILE, "utf8");
|
|
|
|
|
const filtered = content.split(/\r?\n/).filter(l => !l.includes(TARGET_HOST)).join("\r\n");
|
|
|
|
|
if (!filtered.trim() && content.trim()) {
|
|
|
|
|
throw new Error("Filtered hosts content is empty, aborting to prevent data loss");
|
|
|
|
|
}
|
|
|
|
|
const tmpFile = path.join(os.tmpdir(), "hosts_filtered.tmp");
|
|
|
|
|
fs.writeFileSync(tmpFile, filtered, "utf8");
|
|
|
|
|
// Use elevated cmd to copy temp file over hosts (safe: original untouched until copy succeeds)
|
|
|
|
|
const psCommand = `Start-Process cmd -ArgumentList '/c','copy /Y "${tmpFile}" "${HOSTS_FILE}"' -Verb RunAs -Wait`;
|
2026-02-08 16:28:13 +07:00
|
|
|
await new Promise((resolve, reject) => {
|
|
|
|
|
exec(`powershell -Command "${psCommand}"`, (error) => {
|
2026-02-25 10:40:15 +07:00
|
|
|
try { fs.unlinkSync(tmpFile); } catch { /* ignore */ }
|
2026-02-08 16:28:13 +07:00
|
|
|
if (error) reject(new Error(`Failed to remove DNS entry: ${error.message}`));
|
|
|
|
|
else resolve();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
} else {
|
2026-02-25 10:40:15 +07:00
|
|
|
// sed -i '' is macOS syntax; Linux uses sed -i without the empty string arg
|
|
|
|
|
const sedCmd = IS_MAC
|
|
|
|
|
? `sed -i '' '/${TARGET_HOST}/d' ${HOSTS_FILE}`
|
|
|
|
|
: `sed -i '/${TARGET_HOST}/d' ${HOSTS_FILE}`;
|
|
|
|
|
await execWithPassword(sedCmd, sudoPassword);
|
2026-02-09 00:15:31 +07:00
|
|
|
}
|
|
|
|
|
// Flush DNS cache
|
|
|
|
|
if (IS_WIN) {
|
|
|
|
|
await execElevatedWindows("ipconfig /flushdns");
|
2026-02-25 10:40:15 +07:00
|
|
|
} else if (IS_MAC) {
|
2026-02-09 00:15:31 +07:00
|
|
|
await execWithPassword("dscacheutil -flushcache && killall -HUP mDNSResponder", sudoPassword);
|
2026-02-25 10:40:15 +07:00
|
|
|
} else {
|
|
|
|
|
await execWithPassword("resolvectl flush-caches 2>/dev/null || true", sudoPassword);
|
2026-02-08 16:28:13 +07:00
|
|
|
}
|
|
|
|
|
console.log(`✅ Removed DNS entry for ${TARGET_HOST}`);
|
|
|
|
|
} catch (error) {
|
2026-02-09 00:15:31 +07:00
|
|
|
const msg = error.message?.includes("incorrect password") ? "Wrong sudo password" : "Failed to remove DNS entry";
|
|
|
|
|
throw new Error(msg);
|
2026-02-08 16:28:13 +07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
module.exports = { addDNSEntry, removeDNSEntry, execWithPassword, checkDNSEntry };
|