@thangnm.nip/arouter 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/app-path-routes-manifest.json +9 -9
- package/.next/standalone/.next/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/(dashboard)/mitm/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(dashboard)/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +2 -2
- package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +3 -3
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/api/cli-tools/antigravity-mitm/alias/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/cli-tools/antigravity-mitm/console-logs/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/cli-tools/antigravity-mitm/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/cli-tools/antigravity-mitm/settings/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/cli-tools/claude-settings/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/cli-tools/codex-settings/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/cli-tools/copilot-settings/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/cli-tools/droid-settings/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/cli-tools/openclaw-settings/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/cli-tools/opencode-settings/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/keys/[id]/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/keys/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/locale/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/models/alias/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/models/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/providers/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/settings/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/version/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +4 -4
- package/.next/standalone/.next/server/app/index.segments/!KGRhc2hib2FyZCk/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/index.segments/!KGRhc2hib2FyZCk.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/mitm.html +1 -1
- package/.next/standalone/.next/server/app/mitm.rsc +5 -5
- package/.next/standalone/.next/server/app/mitm.segments/!KGRhc2hib2FyZCk/mitm/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/mitm.segments/!KGRhc2hib2FyZCk/mitm.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/mitm.segments/!KGRhc2hib2FyZCk.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/mitm.segments/_full.segment.rsc +5 -5
- package/.next/standalone/.next/server/app/mitm.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/mitm.segments/_index.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/mitm.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app-paths-manifest.json +9 -9
- package/.next/standalone/.next/server/chunks/411.js +1 -1
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +2 -2
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/static/chunks/{702-67d57e0cdf85676e.js → 702-0864796c00fc702e.js} +1 -1
- package/.next/standalone/node_modules/node-forge/LICENSE +331 -0
- package/.next/standalone/node_modules/node-forge/README.md +2071 -0
- package/.next/standalone/node_modules/node-forge/dist/forge.all.min.js +2 -0
- package/.next/standalone/node_modules/node-forge/dist/forge.all.min.js.map +1 -0
- package/.next/standalone/node_modules/node-forge/dist/forge.min.js +2 -0
- package/.next/standalone/node_modules/node-forge/dist/forge.min.js.map +1 -0
- package/.next/standalone/node_modules/node-forge/dist/prime.worker.min.js +2 -0
- package/.next/standalone/node_modules/node-forge/dist/prime.worker.min.js.map +1 -0
- package/.next/standalone/node_modules/node-forge/flash/swf/SocketPool.swf +0 -0
- package/.next/standalone/node_modules/node-forge/lib/aes.js +1091 -0
- package/.next/standalone/node_modules/node-forge/lib/aesCipherSuites.js +282 -0
- package/.next/standalone/node_modules/node-forge/lib/asn1-validator.js +91 -0
- package/.next/standalone/node_modules/node-forge/lib/asn1.js +1503 -0
- package/.next/standalone/node_modules/node-forge/lib/baseN.js +186 -0
- package/.next/standalone/node_modules/node-forge/lib/cipher.js +230 -0
- package/.next/standalone/node_modules/node-forge/lib/cipherModes.js +999 -0
- package/.next/standalone/node_modules/node-forge/lib/des.js +496 -0
- package/.next/standalone/node_modules/node-forge/lib/ed25519.js +1072 -0
- package/.next/standalone/node_modules/node-forge/lib/forge.js +13 -0
- package/.next/standalone/node_modules/node-forge/lib/form.js +149 -0
- package/.next/standalone/node_modules/node-forge/lib/hmac.js +146 -0
- package/.next/standalone/node_modules/node-forge/lib/http.js +1346 -0
- package/.next/standalone/node_modules/node-forge/lib/index.all.js +16 -0
- package/.next/standalone/node_modules/node-forge/lib/index.js +33 -0
- package/.next/standalone/node_modules/node-forge/lib/jsbn.js +1264 -0
- package/.next/standalone/node_modules/node-forge/lib/kem.js +168 -0
- package/.next/standalone/node_modules/node-forge/lib/log.js +319 -0
- package/.next/standalone/node_modules/node-forge/lib/md.all.js +13 -0
- package/.next/standalone/node_modules/node-forge/lib/md.js +11 -0
- package/.next/standalone/node_modules/node-forge/lib/md5.js +289 -0
- package/.next/standalone/node_modules/node-forge/lib/mgf.js +12 -0
- package/.next/standalone/node_modules/node-forge/lib/mgf1.js +57 -0
- package/.next/standalone/node_modules/node-forge/lib/oids.js +179 -0
- package/.next/standalone/node_modules/node-forge/lib/pbe.js +1023 -0
- package/.next/standalone/node_modules/node-forge/lib/pbkdf2.js +211 -0
- package/.next/standalone/node_modules/node-forge/lib/pem.js +237 -0
- package/.next/standalone/node_modules/node-forge/lib/pkcs1.js +276 -0
- package/.next/standalone/node_modules/node-forge/lib/pkcs12.js +1078 -0
- package/.next/standalone/node_modules/node-forge/lib/pkcs7.js +1260 -0
- package/.next/standalone/node_modules/node-forge/lib/pkcs7asn1.js +410 -0
- package/.next/standalone/node_modules/node-forge/lib/pki.js +102 -0
- package/.next/standalone/node_modules/node-forge/lib/prime.js +297 -0
- package/.next/standalone/node_modules/node-forge/lib/prime.worker.js +168 -0
- package/.next/standalone/node_modules/node-forge/lib/prng.js +419 -0
- package/.next/standalone/node_modules/node-forge/lib/pss.js +241 -0
- package/.next/standalone/node_modules/node-forge/lib/random.js +191 -0
- package/.next/standalone/node_modules/node-forge/lib/rc2.js +410 -0
- package/.next/standalone/node_modules/node-forge/lib/rsa.js +1949 -0
- package/.next/standalone/node_modules/node-forge/lib/sha1.js +319 -0
- package/.next/standalone/node_modules/node-forge/lib/sha256.js +327 -0
- package/.next/standalone/node_modules/node-forge/lib/sha512.js +561 -0
- package/.next/standalone/node_modules/node-forge/lib/socket.js +287 -0
- package/.next/standalone/node_modules/node-forge/lib/ssh.js +236 -0
- package/.next/standalone/node_modules/node-forge/lib/tls.js +4282 -0
- package/.next/standalone/node_modules/node-forge/lib/tlssocket.js +249 -0
- package/.next/standalone/node_modules/node-forge/lib/util.js +2652 -0
- package/.next/standalone/node_modules/node-forge/lib/x509.js +3242 -0
- package/.next/standalone/node_modules/node-forge/lib/xhr.js +738 -0
- package/.next/standalone/node_modules/node-forge/package.json +123 -0
- package/.next/standalone/node_modules/node-machine-id/.babelrc +26 -0
- package/.next/standalone/node_modules/node-machine-id/.eslintignore +2 -0
- package/.next/standalone/node_modules/node-machine-id/.eslintrc +140 -0
- package/.next/standalone/node_modules/node-machine-id/.flowconfig +9 -0
- package/.next/standalone/node_modules/node-machine-id/LICENSE +21 -0
- package/.next/standalone/node_modules/node-machine-id/README.md +84 -0
- package/.next/standalone/node_modules/node-machine-id/dist/index.js +1 -0
- package/.next/standalone/node_modules/node-machine-id/index.js +80 -0
- package/.next/standalone/node_modules/node-machine-id/package.json +74 -0
- package/.next/standalone/node_modules/node-machine-id/tests/index.js +45 -0
- package/.next/standalone/node_modules/node-machine-id/types/index.d.ts +17 -0
- package/.next/standalone/node_modules/node-machine-id/webpack.config.babel.js +59 -0
- package/.next/standalone/package.json +1 -1
- package/.next/standalone/src/mitm/cert/generate.js +32 -0
- package/.next/standalone/src/mitm/cert/install.js +176 -0
- package/.next/standalone/src/mitm/cert/rootCA.js +173 -0
- package/.next/standalone/src/mitm/dns/dnsConfig.js +216 -0
- package/.next/standalone/src/mitm/logger.js +8 -0
- package/.next/standalone/src/mitm/manager.js +603 -0
- package/.next/standalone/src/mitm/paths.js +16 -0
- package/package.json +1 -1
- /package/.next/standalone/.next/static/{oQBK39Yf5Nkr7-_ESoUO2 → DkVxg6bby4SUixuq1vdO8}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{oQBK39Yf5Nkr7-_ESoUO2 → DkVxg6bby4SUixuq1vdO8}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module based on OS native UUID/GUID which used for internal needs.
|
|
3
|
+
*/
|
|
4
|
+
declare module 'node-machine-id' {
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* This function gets the OS native UUID/GUID synchronously, hashed by default.
|
|
8
|
+
* @param {boolean} [original=false] - If true return original value of machine id, otherwise return hashed value (sha - 256)
|
|
9
|
+
*/
|
|
10
|
+
function machineIdSync(original?: boolean): string;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* This function gets the OS native UUID/GUID asynchronously (recommended), hashed by default.
|
|
14
|
+
* @param {boolean} [original=false] - If true return original value of machine id, otherwise return hashed value (sha - 256)
|
|
15
|
+
*/
|
|
16
|
+
function machineId(original?: boolean): Promise<string>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import webpack from 'webpack';
|
|
3
|
+
|
|
4
|
+
let nodeModules = fs.readdirSync('./node_modules')
|
|
5
|
+
.filter((module) => {
|
|
6
|
+
return module !== '.bin';
|
|
7
|
+
})
|
|
8
|
+
.reduce((prev, module) => {
|
|
9
|
+
return Object.assign(prev, {[module]: 'commonjs ' + module});
|
|
10
|
+
}, {});
|
|
11
|
+
|
|
12
|
+
export default {
|
|
13
|
+
entry: ['./index.js'],
|
|
14
|
+
output: {
|
|
15
|
+
path: './dist',
|
|
16
|
+
filename: 'index.js',
|
|
17
|
+
library: 'electron-machine-id',
|
|
18
|
+
libraryTarget: 'umd'
|
|
19
|
+
},
|
|
20
|
+
target: 'electron',
|
|
21
|
+
module: {
|
|
22
|
+
loaders: [
|
|
23
|
+
{
|
|
24
|
+
test: /\.js$/,
|
|
25
|
+
exclude: /node_modules/,
|
|
26
|
+
loader: 'babel',
|
|
27
|
+
query: {
|
|
28
|
+
cacheDirectory: true
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
test: /\.json$/,
|
|
33
|
+
loader: 'json'
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
},
|
|
37
|
+
plugins: [
|
|
38
|
+
new webpack.IgnorePlugin(/node_modules/),
|
|
39
|
+
new webpack.optimize.OccurrenceOrderPlugin(),
|
|
40
|
+
new webpack.optimize.DedupePlugin(),
|
|
41
|
+
new webpack.optimize.UglifyJsPlugin({
|
|
42
|
+
compress: { warnings: false },
|
|
43
|
+
output: { comments: false },
|
|
44
|
+
})
|
|
45
|
+
],
|
|
46
|
+
node: {
|
|
47
|
+
//do not include polyfills...
|
|
48
|
+
//http://webpack.github.io/docs/configuration.html#node
|
|
49
|
+
console: false,
|
|
50
|
+
process: false,
|
|
51
|
+
child_process: false,
|
|
52
|
+
global: false,
|
|
53
|
+
buffer: false,
|
|
54
|
+
crypto: false,
|
|
55
|
+
__filename: false,
|
|
56
|
+
__dirname: false
|
|
57
|
+
},
|
|
58
|
+
externals: nodeModules
|
|
59
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const path = require("path");
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const { MITM_DIR } = require("../paths");
|
|
4
|
+
const { generateRootCA, loadRootCA, generateLeafCert } = require("./rootCA");
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Generate Root CA certificate (one-time setup)
|
|
8
|
+
* This replaces the old static wildcard cert approach
|
|
9
|
+
*/
|
|
10
|
+
async function generateCert() {
|
|
11
|
+
return await generateRootCA();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get certificate for a specific domain (dynamic generation)
|
|
16
|
+
* Used by SNICallback in server.js
|
|
17
|
+
*/
|
|
18
|
+
function getCertForDomain(domain) {
|
|
19
|
+
try {
|
|
20
|
+
const rootCA = loadRootCA();
|
|
21
|
+
const leafCert = generateLeafCert(domain, rootCA);
|
|
22
|
+
return {
|
|
23
|
+
key: leafCert.key,
|
|
24
|
+
cert: leafCert.cert
|
|
25
|
+
};
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error(`Failed to generate cert for ${domain}:`, error.message);
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports = { generateCert, getCertForDomain };
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const crypto = require("crypto");
|
|
3
|
+
const { exec } = require("child_process");
|
|
4
|
+
const { execWithPassword } = require("../dns/dnsConfig.js");
|
|
5
|
+
const { log, err } = require("../logger");
|
|
6
|
+
|
|
7
|
+
const IS_WIN = process.platform === "win32";
|
|
8
|
+
const IS_MAC = process.platform === "darwin";
|
|
9
|
+
const LINUX_CERT_DIR = "/usr/local/share/ca-certificates";
|
|
10
|
+
|
|
11
|
+
// Get SHA1 fingerprint from cert file using Node.js crypto
|
|
12
|
+
function getCertFingerprint(certPath) {
|
|
13
|
+
const pem = fs.readFileSync(certPath, "utf-8");
|
|
14
|
+
const der = Buffer.from(pem.replace(/-----[^-]+-----/g, "").replace(/\s/g, ""), "base64");
|
|
15
|
+
return crypto.createHash("sha1").update(der).digest("hex").toUpperCase().match(/.{2}/g).join(":");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Check if certificate is already installed in system store
|
|
20
|
+
*/
|
|
21
|
+
async function checkCertInstalled(certPath) {
|
|
22
|
+
if (IS_WIN) return checkCertInstalledWindows(certPath);
|
|
23
|
+
if (IS_MAC) return checkCertInstalledMac(certPath);
|
|
24
|
+
return checkCertInstalledLinux();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function checkCertInstalledMac(certPath) {
|
|
28
|
+
return new Promise((resolve) => {
|
|
29
|
+
try {
|
|
30
|
+
const fingerprint = getCertFingerprint(certPath).replace(/:/g, "");
|
|
31
|
+
// security verify-cert returns 0 only if cert is trusted by system policy
|
|
32
|
+
exec(`security verify-cert -c "${certPath}" -p ssl -k /Library/Keychains/System.keychain 2>/dev/null`, (error) => {
|
|
33
|
+
if (!error) return resolve(true);
|
|
34
|
+
// Fallback: check if fingerprint appears in System keychain with trust
|
|
35
|
+
exec(`security dump-trust-settings -d 2>/dev/null | grep -i "${fingerprint}"`, (err2, stdout2) => {
|
|
36
|
+
resolve(!err2 && !!stdout2?.trim());
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
} catch {
|
|
40
|
+
resolve(false);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function checkCertInstalledWindows(certPath) {
|
|
46
|
+
return new Promise((resolve) => {
|
|
47
|
+
// Check Root store for our Root CA by common name
|
|
48
|
+
exec("certutil -store Root \"arouter MITM Root CA\"", { windowsHide: true }, (error) => {
|
|
49
|
+
resolve(!error);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Install SSL certificate to system trust store
|
|
56
|
+
*/
|
|
57
|
+
async function installCert(sudoPassword, certPath) {
|
|
58
|
+
if (!fs.existsSync(certPath)) {
|
|
59
|
+
throw new Error(`Certificate file not found: ${certPath}`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const isInstalled = await checkCertInstalled(certPath);
|
|
63
|
+
if (isInstalled) {
|
|
64
|
+
log("🔐 Cert: already trusted ✅");
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (IS_WIN) {
|
|
69
|
+
await installCertWindows(certPath);
|
|
70
|
+
} else if (IS_MAC) {
|
|
71
|
+
await installCertMac(sudoPassword, certPath);
|
|
72
|
+
} else {
|
|
73
|
+
await installCertLinux(sudoPassword, certPath);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function installCertMac(sudoPassword, certPath) {
|
|
78
|
+
// Remove all old certs with same name first to avoid duplicate/stale cert conflict
|
|
79
|
+
const deleteOld = `security delete-certificate -c "arouter MITM Root CA" /Library/Keychains/System.keychain 2>/dev/null || true`;
|
|
80
|
+
const install = `security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain "${certPath}"`;
|
|
81
|
+
try {
|
|
82
|
+
await execWithPassword(`${deleteOld} && ${install}`, sudoPassword);
|
|
83
|
+
log("🔐 Cert: ✅ installed to system keychain");
|
|
84
|
+
} catch (error) {
|
|
85
|
+
const msg = error.message?.includes("canceled") ? "User canceled authorization" : "Certificate install failed";
|
|
86
|
+
throw new Error(msg);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function installCertWindows(certPath) {
|
|
91
|
+
// Process already has admin rights — run certutil directly, no UAC needed
|
|
92
|
+
return new Promise((resolve, reject) => {
|
|
93
|
+
exec(
|
|
94
|
+
`certutil -addstore Root "${certPath}"`,
|
|
95
|
+
{ windowsHide: true },
|
|
96
|
+
(error) => {
|
|
97
|
+
if (error) reject(new Error(`Failed to install certificate: ${error.message}`));
|
|
98
|
+
else { log("🔐 Cert: ✅ installed to Windows Root store"); resolve(); }
|
|
99
|
+
}
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Uninstall SSL certificate from system store
|
|
106
|
+
*/
|
|
107
|
+
async function uninstallCert(sudoPassword, certPath) {
|
|
108
|
+
const isInstalled = await checkCertInstalled(certPath);
|
|
109
|
+
if (!isInstalled) {
|
|
110
|
+
log("🔐 Cert: not found in system store");
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (IS_WIN) {
|
|
115
|
+
await uninstallCertWindows();
|
|
116
|
+
} else if (IS_MAC) {
|
|
117
|
+
await uninstallCertMac(sudoPassword, certPath);
|
|
118
|
+
} else {
|
|
119
|
+
await uninstallCertLinux(sudoPassword);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function uninstallCertMac(sudoPassword, certPath) {
|
|
124
|
+
const fingerprint = getCertFingerprint(certPath).replace(/:/g, "");
|
|
125
|
+
const command = `security delete-certificate -Z "${fingerprint}" /Library/Keychains/System.keychain`;
|
|
126
|
+
try {
|
|
127
|
+
await execWithPassword(command, sudoPassword);
|
|
128
|
+
log("🔐 Cert: ✅ uninstalled from system keychain");
|
|
129
|
+
} catch (err) {
|
|
130
|
+
throw new Error("Failed to uninstall certificate");
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function uninstallCertWindows() {
|
|
135
|
+
// Process already has admin rights — run certutil directly, no UAC needed
|
|
136
|
+
return new Promise((resolve, reject) => {
|
|
137
|
+
exec(
|
|
138
|
+
`certutil -delstore Root "arouter MITM Root CA"`,
|
|
139
|
+
{ windowsHide: true },
|
|
140
|
+
(error) => {
|
|
141
|
+
if (error) reject(new Error(`Failed to uninstall certificate: ${error.message}`));
|
|
142
|
+
else { log("🔐 Cert: ✅ uninstalled from Windows Root store"); resolve(); }
|
|
143
|
+
}
|
|
144
|
+
);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function checkCertInstalledLinux() {
|
|
149
|
+
const certFile = `${LINUX_CERT_DIR}/arouter-root-ca.crt`;
|
|
150
|
+
return Promise.resolve(fs.existsSync(certFile));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function installCertLinux(sudoPassword, certPath) {
|
|
154
|
+
const destFile = `${LINUX_CERT_DIR}/arouter-root-ca.crt`;
|
|
155
|
+
// Try update-ca-certificates (Debian/Ubuntu), fallback to update-ca-trust (Fedora/RHEL)
|
|
156
|
+
const cmd = `cp "${certPath}" "${destFile}" && (update-ca-certificates 2>/dev/null || update-ca-trust 2>/dev/null || true)`;
|
|
157
|
+
try {
|
|
158
|
+
await execWithPassword(cmd, sudoPassword);
|
|
159
|
+
log("🔐 Cert: ✅ installed to Linux trust store");
|
|
160
|
+
} catch (error) {
|
|
161
|
+
throw new Error("Certificate install failed");
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async function uninstallCertLinux(sudoPassword) {
|
|
166
|
+
const destFile = `${LINUX_CERT_DIR}/arouter-root-ca.crt`;
|
|
167
|
+
const cmd = `rm -f "${destFile}" && (update-ca-certificates 2>/dev/null || update-ca-trust 2>/dev/null || true)`;
|
|
168
|
+
try {
|
|
169
|
+
await execWithPassword(cmd, sudoPassword);
|
|
170
|
+
log("🔐 Cert: ✅ uninstalled from Linux trust store");
|
|
171
|
+
} catch (error) {
|
|
172
|
+
throw new Error("Failed to uninstall certificate");
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
module.exports = { installCert, uninstallCert, checkCertInstalled };
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
const path = require("path");
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const forge = require("node-forge");
|
|
4
|
+
const { MITM_DIR } = require("../paths");
|
|
5
|
+
|
|
6
|
+
const ROOT_CA_KEY_PATH = path.join(MITM_DIR, "rootCA.key");
|
|
7
|
+
const ROOT_CA_CERT_PATH = path.join(MITM_DIR, "rootCA.crt");
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Check if cert file is expired or expiring within 30 days
|
|
11
|
+
*/
|
|
12
|
+
function isCertExpired(certPath) {
|
|
13
|
+
try {
|
|
14
|
+
const cert = forge.pki.certificateFromPem(fs.readFileSync(certPath, "utf8"));
|
|
15
|
+
const expiryThreshold = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000);
|
|
16
|
+
return cert.validity.notAfter < expiryThreshold;
|
|
17
|
+
} catch {
|
|
18
|
+
return true; // treat unreadable cert as expired
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Generate Root CA certificate (only once, auto-regenerate if expired)
|
|
24
|
+
* This Root CA will sign all dynamic leaf certificates
|
|
25
|
+
*/
|
|
26
|
+
async function generateRootCA() {
|
|
27
|
+
const exists = fs.existsSync(ROOT_CA_KEY_PATH) && fs.existsSync(ROOT_CA_CERT_PATH);
|
|
28
|
+
if (exists && !isCertExpired(ROOT_CA_CERT_PATH)) {
|
|
29
|
+
console.log("✅ Root CA already exists");
|
|
30
|
+
return { key: ROOT_CA_KEY_PATH, cert: ROOT_CA_CERT_PATH };
|
|
31
|
+
}
|
|
32
|
+
if (exists) {
|
|
33
|
+
console.log("🔐 Root CA expired or expiring soon — regenerating...");
|
|
34
|
+
try { fs.unlinkSync(ROOT_CA_KEY_PATH); } catch { /* ignore */ }
|
|
35
|
+
try { fs.unlinkSync(ROOT_CA_CERT_PATH); } catch { /* ignore */ }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!fs.existsSync(MITM_DIR)) {
|
|
39
|
+
fs.mkdirSync(MITM_DIR, { recursive: true });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
console.log("🔐 Generating Root CA certificate...");
|
|
43
|
+
|
|
44
|
+
// Generate RSA key pair
|
|
45
|
+
const keys = forge.pki.rsa.generateKeyPair(2048);
|
|
46
|
+
|
|
47
|
+
// Create Root CA certificate
|
|
48
|
+
const cert = forge.pki.createCertificate();
|
|
49
|
+
cert.publicKey = keys.publicKey;
|
|
50
|
+
cert.serialNumber = "01";
|
|
51
|
+
cert.validity.notBefore = new Date();
|
|
52
|
+
cert.validity.notAfter = new Date();
|
|
53
|
+
cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 10);
|
|
54
|
+
|
|
55
|
+
const attrs = [
|
|
56
|
+
{ name: "commonName", value: "arouter MITM Root CA" },
|
|
57
|
+
{ name: "organizationName", value: "arouter" },
|
|
58
|
+
{ name: "countryName", value: "US" }
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
cert.setSubject(attrs);
|
|
62
|
+
cert.setIssuer(attrs); // Self-signed
|
|
63
|
+
|
|
64
|
+
cert.setExtensions([
|
|
65
|
+
{
|
|
66
|
+
name: "basicConstraints",
|
|
67
|
+
cA: true,
|
|
68
|
+
critical: true
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: "keyUsage",
|
|
72
|
+
keyCertSign: true,
|
|
73
|
+
cRLSign: true,
|
|
74
|
+
critical: true
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: "subjectKeyIdentifier"
|
|
78
|
+
}
|
|
79
|
+
]);
|
|
80
|
+
|
|
81
|
+
// Self-sign the certificate
|
|
82
|
+
cert.sign(keys.privateKey, forge.md.sha256.create());
|
|
83
|
+
|
|
84
|
+
// Save to disk
|
|
85
|
+
const privateKeyPem = forge.pki.privateKeyToPem(keys.privateKey);
|
|
86
|
+
const certPem = forge.pki.certificateToPem(cert);
|
|
87
|
+
|
|
88
|
+
fs.writeFileSync(ROOT_CA_KEY_PATH, privateKeyPem);
|
|
89
|
+
fs.writeFileSync(ROOT_CA_CERT_PATH, certPem);
|
|
90
|
+
|
|
91
|
+
console.log("✅ Root CA generated successfully");
|
|
92
|
+
return { key: ROOT_CA_KEY_PATH, cert: ROOT_CA_CERT_PATH };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Load Root CA from disk
|
|
97
|
+
*/
|
|
98
|
+
function loadRootCA() {
|
|
99
|
+
if (!fs.existsSync(ROOT_CA_KEY_PATH) || !fs.existsSync(ROOT_CA_CERT_PATH)) {
|
|
100
|
+
throw new Error("Root CA not found. Generate it first.");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const keyPem = fs.readFileSync(ROOT_CA_KEY_PATH, "utf8");
|
|
104
|
+
const certPem = fs.readFileSync(ROOT_CA_CERT_PATH, "utf8");
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
key: forge.pki.privateKeyFromPem(keyPem),
|
|
108
|
+
cert: forge.pki.certificateFromPem(certPem)
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Generate leaf certificate for a specific domain, signed by Root CA
|
|
114
|
+
*/
|
|
115
|
+
function generateLeafCert(domain, rootCA) {
|
|
116
|
+
// Generate key pair for leaf cert
|
|
117
|
+
const keys = forge.pki.rsa.generateKeyPair(2048);
|
|
118
|
+
|
|
119
|
+
// Create leaf certificate
|
|
120
|
+
const cert = forge.pki.createCertificate();
|
|
121
|
+
cert.publicKey = keys.publicKey;
|
|
122
|
+
cert.serialNumber = Math.floor(Math.random() * 1000000).toString();
|
|
123
|
+
cert.validity.notBefore = new Date();
|
|
124
|
+
cert.validity.notAfter = new Date();
|
|
125
|
+
cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1);
|
|
126
|
+
|
|
127
|
+
cert.setSubject([
|
|
128
|
+
{ name: "commonName", value: domain }
|
|
129
|
+
]);
|
|
130
|
+
|
|
131
|
+
cert.setIssuer(rootCA.cert.subject.attributes);
|
|
132
|
+
|
|
133
|
+
cert.setExtensions([
|
|
134
|
+
{
|
|
135
|
+
name: "basicConstraints",
|
|
136
|
+
cA: false
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
name: "keyUsage",
|
|
140
|
+
digitalSignature: true,
|
|
141
|
+
keyEncipherment: true
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
name: "extKeyUsage",
|
|
145
|
+
serverAuth: true,
|
|
146
|
+
clientAuth: true
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
name: "subjectAltName",
|
|
150
|
+
altNames: [
|
|
151
|
+
{ type: 2, value: domain }, // DNS
|
|
152
|
+
{ type: 2, value: `*.${domain}` } // Wildcard
|
|
153
|
+
]
|
|
154
|
+
}
|
|
155
|
+
]);
|
|
156
|
+
|
|
157
|
+
// Sign with Root CA
|
|
158
|
+
cert.sign(rootCA.key, forge.md.sha256.create());
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
key: forge.pki.privateKeyToPem(keys.privateKey),
|
|
162
|
+
cert: forge.pki.certificateToPem(cert)
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
module.exports = {
|
|
167
|
+
generateRootCA,
|
|
168
|
+
loadRootCA,
|
|
169
|
+
generateLeafCert,
|
|
170
|
+
isCertExpired,
|
|
171
|
+
ROOT_CA_CERT_PATH,
|
|
172
|
+
ROOT_CA_KEY_PATH
|
|
173
|
+
};
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
const { exec, spawn } = require("child_process");
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const os = require("os");
|
|
5
|
+
const { log, err } = require("../logger");
|
|
6
|
+
|
|
7
|
+
// Per-tool DNS hosts mapping
|
|
8
|
+
const TOOL_HOSTS = {
|
|
9
|
+
antigravity: ["daily-cloudcode-pa.googleapis.com", "cloudcode-pa.googleapis.com"],
|
|
10
|
+
copilot: ["api.individual.githubcopilot.com"],
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const IS_WIN = process.platform === "win32";
|
|
14
|
+
const IS_MAC = process.platform === "darwin";
|
|
15
|
+
const HOSTS_FILE = IS_WIN
|
|
16
|
+
? path.join(process.env.SystemRoot || "C:\\Windows", "System32", "drivers", "etc", "hosts")
|
|
17
|
+
: "/etc/hosts";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Execute elevated PowerShell script on Windows via Start-Process -Verb RunAs.
|
|
21
|
+
* Only UAC consent dialog appears, no CMD/PS window popup.
|
|
22
|
+
*/
|
|
23
|
+
function executeElevatedPowerShell(psScriptPath, timeoutMs = 30000) {
|
|
24
|
+
const flagFile = path.join(os.tmpdir(), `ps_done_${Date.now()}.flag`);
|
|
25
|
+
const psSQ = (s) => s.replace(/'/g, "''");
|
|
26
|
+
|
|
27
|
+
let psContent = fs.readFileSync(psScriptPath, "utf8");
|
|
28
|
+
psContent += `\nSet-Content -Path '${psSQ(flagFile)}' -Value 'done' -Encoding UTF8\n`;
|
|
29
|
+
fs.writeFileSync(psScriptPath, psContent, "utf8");
|
|
30
|
+
|
|
31
|
+
const outerCmd = `Start-Process powershell -ArgumentList '-NoProfile','-ExecutionPolicy','Bypass','-WindowStyle','Hidden','-File','${psSQ(psScriptPath)}' -Verb RunAs -WindowStyle Hidden`;
|
|
32
|
+
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
let settled = false;
|
|
35
|
+
const settle = (fn, arg) => { if (!settled) { settled = true; fn(arg); } };
|
|
36
|
+
|
|
37
|
+
exec(
|
|
38
|
+
`powershell -NoProfile -NonInteractive -WindowStyle Hidden -Command "${outerCmd}"`,
|
|
39
|
+
{ windowsHide: true },
|
|
40
|
+
() => {}
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const deadline = Date.now() + timeoutMs;
|
|
44
|
+
const poll = () => {
|
|
45
|
+
if (settled) return;
|
|
46
|
+
if (fs.existsSync(flagFile)) {
|
|
47
|
+
try { fs.unlinkSync(flagFile); fs.unlinkSync(psScriptPath); } catch { /* ignore */ }
|
|
48
|
+
return settle(resolve);
|
|
49
|
+
}
|
|
50
|
+
if (Date.now() > deadline) {
|
|
51
|
+
try { fs.unlinkSync(psScriptPath); } catch { /* ignore */ }
|
|
52
|
+
return settle(reject, new Error("Timed out waiting for UAC confirmation"));
|
|
53
|
+
}
|
|
54
|
+
setTimeout(poll, 500);
|
|
55
|
+
};
|
|
56
|
+
setTimeout(poll, 300);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Execute command with sudo password via stdin (macOS/Linux only)
|
|
62
|
+
*/
|
|
63
|
+
function execWithPassword(command, password) {
|
|
64
|
+
return new Promise((resolve, reject) => {
|
|
65
|
+
const child = spawn("sudo", ["-S", "sh", "-c", command], {
|
|
66
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
let stdout = "";
|
|
70
|
+
let stderr = "";
|
|
71
|
+
child.stdout.on("data", (d) => { stdout += d; });
|
|
72
|
+
child.stderr.on("data", (d) => { stderr += d; });
|
|
73
|
+
|
|
74
|
+
child.on("close", (code) => {
|
|
75
|
+
if (code === 0) resolve(stdout);
|
|
76
|
+
else reject(new Error(stderr || `Exit code ${code}`));
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
child.stdin.write(`${password}\n`);
|
|
80
|
+
child.stdin.end();
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Flush DNS cache (macOS/Linux)
|
|
86
|
+
*/
|
|
87
|
+
async function flushDNS(sudoPassword) {
|
|
88
|
+
if (IS_WIN) return; // Windows flushes inline via ipconfig
|
|
89
|
+
if (IS_MAC) {
|
|
90
|
+
await execWithPassword("dscacheutil -flushcache && killall -HUP mDNSResponder", sudoPassword);
|
|
91
|
+
} else {
|
|
92
|
+
await execWithPassword("resolvectl flush-caches 2>/dev/null || true", sudoPassword);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Check if DNS entry exists for a specific host
|
|
98
|
+
*/
|
|
99
|
+
function checkDNSEntry(host = null) {
|
|
100
|
+
try {
|
|
101
|
+
const hostsContent = fs.readFileSync(HOSTS_FILE, "utf8");
|
|
102
|
+
if (host) return hostsContent.includes(host);
|
|
103
|
+
// Legacy: check all antigravity hosts (backward compat)
|
|
104
|
+
return TOOL_HOSTS.antigravity.every(h => hostsContent.includes(h));
|
|
105
|
+
} catch {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Check DNS status per tool — returns { [tool]: boolean }
|
|
112
|
+
*/
|
|
113
|
+
function checkAllDNSStatus() {
|
|
114
|
+
try {
|
|
115
|
+
const hostsContent = fs.readFileSync(HOSTS_FILE, "utf8");
|
|
116
|
+
const result = {};
|
|
117
|
+
for (const [tool, hosts] of Object.entries(TOOL_HOSTS)) {
|
|
118
|
+
result[tool] = hosts.every(h => hostsContent.includes(h));
|
|
119
|
+
}
|
|
120
|
+
return result;
|
|
121
|
+
} catch {
|
|
122
|
+
return Object.fromEntries(Object.keys(TOOL_HOSTS).map(t => [t, false]));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Add DNS entries for a specific tool
|
|
128
|
+
*/
|
|
129
|
+
async function addDNSEntry(tool, sudoPassword) {
|
|
130
|
+
const hosts = TOOL_HOSTS[tool];
|
|
131
|
+
if (!hosts) throw new Error(`Unknown tool: ${tool}`);
|
|
132
|
+
|
|
133
|
+
const entriesToAdd = hosts.filter(h => !checkDNSEntry(h));
|
|
134
|
+
if (entriesToAdd.length === 0) {
|
|
135
|
+
log(`🌐 DNS ${tool}: already active`);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const entries = entriesToAdd.map(h => `127.0.0.1 ${h}`).join("\n");
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
if (IS_WIN) {
|
|
143
|
+
// Process already has admin rights — edit hosts file directly
|
|
144
|
+
const toAppend = entriesToAdd.map(h => `127.0.0.1 ${h}`).join("\r\n") + "\r\n";
|
|
145
|
+
fs.appendFileSync(HOSTS_FILE, toAppend, "utf8");
|
|
146
|
+
require("child_process").execSync("ipconfig /flushdns", { windowsHide: true });
|
|
147
|
+
} else {
|
|
148
|
+
await execWithPassword(`echo "${entries}" >> ${HOSTS_FILE}`, sudoPassword);
|
|
149
|
+
await flushDNS(sudoPassword);
|
|
150
|
+
}
|
|
151
|
+
log(`🌐 DNS ${tool}: ✅ added ${entriesToAdd.join(", ")}`);
|
|
152
|
+
} catch (error) {
|
|
153
|
+
const msg = error.message?.includes("incorrect password") ? "Wrong sudo password" : "Failed to add DNS entry";
|
|
154
|
+
throw new Error(msg);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Remove DNS entries for a specific tool
|
|
160
|
+
*/
|
|
161
|
+
async function removeDNSEntry(tool, sudoPassword) {
|
|
162
|
+
const hosts = TOOL_HOSTS[tool];
|
|
163
|
+
if (!hosts) throw new Error(`Unknown tool: ${tool}`);
|
|
164
|
+
|
|
165
|
+
const entriesToRemove = hosts.filter(h => checkDNSEntry(h));
|
|
166
|
+
if (entriesToRemove.length === 0) {
|
|
167
|
+
log(`🌐 DNS ${tool}: already inactive`);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
if (IS_WIN) {
|
|
173
|
+
// Process already has admin rights — edit hosts file directly
|
|
174
|
+
const content = fs.readFileSync(HOSTS_FILE, "utf8");
|
|
175
|
+
const filtered = content.split(/\r?\n/).filter(l => !entriesToRemove.some(h => l.includes(h))).join("\r\n");
|
|
176
|
+
fs.writeFileSync(HOSTS_FILE, filtered, "utf8");
|
|
177
|
+
require("child_process").execSync("ipconfig /flushdns", { windowsHide: true });
|
|
178
|
+
} else {
|
|
179
|
+
for (const host of entriesToRemove) {
|
|
180
|
+
const sedCmd = IS_MAC
|
|
181
|
+
? `sed -i '' '/${host}/d' ${HOSTS_FILE}`
|
|
182
|
+
: `sed -i '/${host}/d' ${HOSTS_FILE}`;
|
|
183
|
+
await execWithPassword(sedCmd, sudoPassword);
|
|
184
|
+
}
|
|
185
|
+
await flushDNS(sudoPassword);
|
|
186
|
+
}
|
|
187
|
+
log(`🌐 DNS ${tool}: ✅ removed ${entriesToRemove.join(", ")}`);
|
|
188
|
+
} catch (error) {
|
|
189
|
+
const msg = error.message?.includes("incorrect password") ? "Wrong sudo password" : "Failed to remove DNS entry";
|
|
190
|
+
throw new Error(msg);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Remove ALL tool DNS entries (used when stopping server)
|
|
196
|
+
*/
|
|
197
|
+
async function removeAllDNSEntries(sudoPassword) {
|
|
198
|
+
for (const tool of Object.keys(TOOL_HOSTS)) {
|
|
199
|
+
try {
|
|
200
|
+
await removeDNSEntry(tool, sudoPassword);
|
|
201
|
+
} catch (e) {
|
|
202
|
+
err(`DNS ${tool}: failed to remove — ${e.message}`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
module.exports = {
|
|
208
|
+
TOOL_HOSTS,
|
|
209
|
+
addDNSEntry,
|
|
210
|
+
removeDNSEntry,
|
|
211
|
+
removeAllDNSEntries,
|
|
212
|
+
execWithPassword,
|
|
213
|
+
executeElevatedPowerShell,
|
|
214
|
+
checkDNSEntry,
|
|
215
|
+
checkAllDNSStatus,
|
|
216
|
+
};
|