whistle.easy-proxy 1.2.1 → 1.3.0
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/lib/cert-check.js +135 -28
- package/lib/cli/commands/proxy.js +8 -0
- package/lib/cli/commands/start.js +9 -0
- package/package.json +1 -1
package/lib/cert-check.js
CHANGED
|
@@ -12,6 +12,7 @@ const fs = require('node:fs');
|
|
|
12
12
|
const path = require('node:path');
|
|
13
13
|
const os = require('node:os');
|
|
14
14
|
const { execSync } = require('node:child_process');
|
|
15
|
+
const { getEffectiveBypass } = require('./cli/proxy-config');
|
|
15
16
|
|
|
16
17
|
const HOME = os.homedir();
|
|
17
18
|
const IS_MACOS = os.platform() === 'darwin';
|
|
@@ -237,6 +238,56 @@ var APP_CA_VARS = [
|
|
|
237
238
|
['NODE_EXTRA_CA_CERTS', 'Node.js'],
|
|
238
239
|
];
|
|
239
240
|
|
|
241
|
+
// whistle 代理端口(与 rule-api.js 保持一致,W2_PORT 覆盖)
|
|
242
|
+
var PROXY_PORT = process.env.W2_PORT || '8899';
|
|
243
|
+
|
|
244
|
+
// 代理开关 shell 文件(setproxy/unsetproxy 函数所在),由 ~/.zshrc source
|
|
245
|
+
var SHELL_ENV_FILE = path.join(HOME, '.easy-proxy', 'shell-env.sh');
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* 把 easy-proxy 的 bypass 域名列表转换为 NO_PROXY 值。
|
|
249
|
+
* 两边语法不同:
|
|
250
|
+
* - bypass 用 macOS 风格通配(*.bilibili.com、10.*.*.*、*china*)
|
|
251
|
+
* - NO_PROXY 只认后缀匹配(.bilibili.com)和 CIDR(10.0.0.0/8)
|
|
252
|
+
* 转换规则:
|
|
253
|
+
* *.x.com → .x.com(NO_PROXY 的 .x 语义即匹配所有子域)
|
|
254
|
+
* a.b.*.* → CIDR(私网段,查表映射;查不到则丢弃)
|
|
255
|
+
* 纯域名/IP → 原样保留
|
|
256
|
+
* 含 * 但不是上述形态(如 *china* 中缀通配)→ NO_PROXY 无法表达,丢弃
|
|
257
|
+
* @param {string[]} bypassList
|
|
258
|
+
* @returns {string}
|
|
259
|
+
*/
|
|
260
|
+
function bypassToNoProxy(bypassList) {
|
|
261
|
+
// bypass 里的私网通配 → NO_PROXY 的 CIDR 写法(含网段聚合,10.16~31 手动聚合成 /12)
|
|
262
|
+
var CIDR_MAP = {
|
|
263
|
+
'10.*.*.*': '10.0.0.0/8',
|
|
264
|
+
'192.168.*.*': '192.168.0.0/16',
|
|
265
|
+
'127.*.*.*': '127.0.0.0/8',
|
|
266
|
+
};
|
|
267
|
+
for (var i = 16; i <= 31; i++) CIDR_MAP['172.' + i + '.*.*'] = '172.16.0.0/12';
|
|
268
|
+
|
|
269
|
+
var seen = { localhost: true };
|
|
270
|
+
var out = ['localhost'];
|
|
271
|
+
bypassList.forEach(function (d) {
|
|
272
|
+
var v;
|
|
273
|
+
if (CIDR_MAP[d]) {
|
|
274
|
+
v = CIDR_MAP[d];
|
|
275
|
+
} else if (d.indexOf('*') === -1) {
|
|
276
|
+
// 纯域名或 IP,原样保留
|
|
277
|
+
v = d;
|
|
278
|
+
} else if (d.slice(0, 2) === '*.' && d.indexOf('*', 2) === -1) {
|
|
279
|
+
// *.x.com 形态(仅前导通配)→ 后缀匹配
|
|
280
|
+
v = d.slice(1);
|
|
281
|
+
} else {
|
|
282
|
+
// 中缀/多段通配(*china*、*.ghproxy.* 等)NO_PROXY 表达不了,丢弃
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
if (seen[v]) return;
|
|
286
|
+
seen[v] = true;
|
|
287
|
+
out.push(v);
|
|
288
|
+
});
|
|
289
|
+
return out.join(',');
|
|
290
|
+
}
|
|
240
291
|
function printAppCaHints(certPath) {
|
|
241
292
|
var needs = APP_CA_VARS.filter(function (n) {
|
|
242
293
|
return !process.env[n[0]];
|
|
@@ -254,41 +305,39 @@ function printAppCaHints(certPath) {
|
|
|
254
305
|
}
|
|
255
306
|
|
|
256
307
|
/**
|
|
257
|
-
*
|
|
258
|
-
*
|
|
259
|
-
*
|
|
260
|
-
* @returns {{added: string[], conflicts: string[]}|null}
|
|
308
|
+
* 向 ~/.zshrc 追加 source shell-env.sh 的一行(幂等)。
|
|
309
|
+
* CA 与代理变量全部移入 shell-env.sh 维护,zshrc 只留这一个入口;
|
|
310
|
+
* 旧版写入的 export HTTP_PROXY 等历史块提示冲突(值可能与当前 whistle 端口不符)。
|
|
311
|
+
* @returns {{added: string[], conflicts: string[]}|null} 新增与冲突项,失败返回 null
|
|
261
312
|
*/
|
|
262
|
-
function writeShellExports(
|
|
313
|
+
function writeShellExports() {
|
|
263
314
|
if (IS_WINDOWS) return null;
|
|
264
315
|
var zshrc = path.join(HOME, '.zshrc');
|
|
265
316
|
try {
|
|
266
317
|
var content = fs.existsSync(zshrc) ? fs.readFileSync(zshrc, 'utf-8') : '';
|
|
267
318
|
var added = [];
|
|
268
319
|
var conflicts = [];
|
|
269
|
-
//
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
320
|
+
// source 行:不存在才追加
|
|
321
|
+
var sourceLine = '[ -f "' + SHELL_ENV_FILE + '" ] && source "' + SHELL_ENV_FILE + '"';
|
|
322
|
+
if (content.indexOf(SHELL_ENV_FILE) === -1) {
|
|
323
|
+
added.push(sourceLine);
|
|
324
|
+
// 旧版把 CA/代理变量直接写进 zshrc,现全部由 shell-env.sh 接管,提示清理
|
|
325
|
+
[/^\s*(export\s+)?HTTPS_PROXY=/m, /^\s*(export\s+)?REQUESTS_CA_BUNDLE=/m].forEach(
|
|
326
|
+
function (re) {
|
|
327
|
+
if (re.test(content)) conflicts.push('zshrc 残留旧版 export(现由 shell-env.sh 接管)');
|
|
328
|
+
}
|
|
329
|
+
);
|
|
330
|
+
}
|
|
275
331
|
if (added.length) {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
'\n# easy-proxy:
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
added.forEach(function (n) {
|
|
283
|
-
block += ' export ' + n[0] + '=' + merged + ' # ' + n[1] + '\n';
|
|
284
|
-
});
|
|
285
|
-
block += 'fi\n';
|
|
286
|
-
fs.appendFileSync(zshrc, block);
|
|
332
|
+
fs.appendFileSync(
|
|
333
|
+
zshrc,
|
|
334
|
+
'\n# easy-proxy: CLI 环境变量与代理开关(由 easy-proxy start 维护)\n' +
|
|
335
|
+
added.join('\n') +
|
|
336
|
+
'\n'
|
|
337
|
+
);
|
|
287
338
|
}
|
|
288
339
|
return {
|
|
289
|
-
added: added
|
|
290
|
-
return n[0];
|
|
291
|
-
}),
|
|
340
|
+
added: added,
|
|
292
341
|
conflicts: conflicts,
|
|
293
342
|
};
|
|
294
343
|
} catch (_e) {
|
|
@@ -296,6 +345,53 @@ function writeShellExports(merged) {
|
|
|
296
345
|
}
|
|
297
346
|
}
|
|
298
347
|
|
|
348
|
+
/**
|
|
349
|
+
* 生成/更新 shell 环境文件(~/.easy-proxy/shell-env.sh),由 ~/.zshrc source。
|
|
350
|
+
* 内容:CA 三件套 export + setproxy/unsetproxy 代理开关函数。
|
|
351
|
+
* NO_PROXY 动态取 bypass 配置转换值(bypass 变更后重写本文件即可刷新)。
|
|
352
|
+
* 不自动 export 代理变量:是否走代理由用户在终端里 setproxy/unsetproxy 决定。
|
|
353
|
+
* @param {string} merged - 合并 CA 文件路径(可能因证书重签变化,每次都重写)
|
|
354
|
+
*/
|
|
355
|
+
function writeProxyEnv(merged) {
|
|
356
|
+
if (IS_WINDOWS) return;
|
|
357
|
+
var proxyUrl = 'http://127.0.0.1:' + PROXY_PORT;
|
|
358
|
+
var noProxy = bypassToNoProxy(getEffectiveBypass());
|
|
359
|
+
var content = [
|
|
360
|
+
'# easy-proxy: CLI 环境变量与代理开关(由 easy-proxy 维护,请勿手动编辑)',
|
|
361
|
+
'# CA 变量带 -f 守卫:合并文件被清理时自动跳过,不影响其余变量',
|
|
362
|
+
merged
|
|
363
|
+
? [
|
|
364
|
+
'if [ -f "' + merged + '" ]; then',
|
|
365
|
+
' export REQUESTS_CA_BUNDLE=' + merged + ' # Python requests/httpx',
|
|
366
|
+
' export SSL_CERT_FILE=' + merged + ' # Python 原生 ssl/urllib',
|
|
367
|
+
' export NODE_EXTRA_CA_CERTS=' + merged + ' # Node.js',
|
|
368
|
+
'fi',
|
|
369
|
+
].join('\n')
|
|
370
|
+
: '# (证书未生成,CA 变量未写入)',
|
|
371
|
+
'# 先清掉同名 alias(用户旧配置可能是 alias 形式,zsh 中 alias 会阻止同名函数定义)',
|
|
372
|
+
'unalias setproxy unsetproxy 2>/dev/null',
|
|
373
|
+
'setproxy() {',
|
|
374
|
+
' export HTTP_PROXY=' + proxyUrl,
|
|
375
|
+
' export HTTPS_PROXY=' + proxyUrl,
|
|
376
|
+
' export NO_PROXY=' + noProxy,
|
|
377
|
+
' echo "CLI 代理已开启: ' + proxyUrl + '"',
|
|
378
|
+
'}',
|
|
379
|
+
'unsetproxy() {',
|
|
380
|
+
' unset HTTP_PROXY HTTPS_PROXY NO_PROXY',
|
|
381
|
+
' echo "CLI 代理已关闭"',
|
|
382
|
+
'}',
|
|
383
|
+
'',
|
|
384
|
+
]
|
|
385
|
+
.filter(Boolean)
|
|
386
|
+
.join('\n');
|
|
387
|
+
try {
|
|
388
|
+
fs.mkdirSync(path.dirname(SHELL_ENV_FILE), { recursive: true });
|
|
389
|
+
fs.writeFileSync(SHELL_ENV_FILE, content);
|
|
390
|
+
} catch (e) {
|
|
391
|
+
console.warn('写入代理开关文件失败:', e.message);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
299
395
|
// ANSI 颜色(与 bin CLI 一致)
|
|
300
396
|
var c = {
|
|
301
397
|
reset: '\x1b[0m',
|
|
@@ -357,14 +453,23 @@ async function checkCertStatus(compact, autoInstall) {
|
|
|
357
453
|
var trusted = checkTrusted(info.subject, certPath);
|
|
358
454
|
var dateStr = info.notAfter.toISOString().slice(0, 10);
|
|
359
455
|
|
|
360
|
-
// 自动管理模式(start):未信任则用 whistle 内置安装器装进系统信任库 + 写
|
|
456
|
+
// 自动管理模式(start):未信任则用 whistle 内置安装器装进系统信任库 + 写 shell 环境文件
|
|
361
457
|
if (autoInstall && !trusted) {
|
|
362
458
|
try {
|
|
363
459
|
var installRootCA = require('whistle/bin/ca');
|
|
364
460
|
installRootCA(certPath);
|
|
365
461
|
var merged = ensureMergedCaBundle(certPath);
|
|
366
|
-
if (merged
|
|
367
|
-
|
|
462
|
+
if (merged) {
|
|
463
|
+
writeProxyEnv(merged);
|
|
464
|
+
var shellResult = writeShellExports();
|
|
465
|
+
if (shellResult && shellResult.added.length) {
|
|
466
|
+
console.log(
|
|
467
|
+
`${c.green}✔${c.reset} 根证书已安装,环境变量入口已写入 ~/.zshrc(新终端生效)`
|
|
468
|
+
);
|
|
469
|
+
console.log(
|
|
470
|
+
`${c.gray} 终端执行 setproxy 可开启 CLI 代理(pnpm/npm 等不读系统代理)${c.reset}`
|
|
471
|
+
);
|
|
472
|
+
}
|
|
368
473
|
}
|
|
369
474
|
trusted = checkTrusted(info.subject, certPath);
|
|
370
475
|
} catch (e) {
|
|
@@ -432,5 +537,7 @@ module.exports = {
|
|
|
432
537
|
fixCertCompliance,
|
|
433
538
|
ensureMergedCaBundle,
|
|
434
539
|
writeShellExports,
|
|
540
|
+
writeProxyEnv,
|
|
541
|
+
bypassToNoProxy,
|
|
435
542
|
checkTrusted,
|
|
436
543
|
};
|
|
@@ -19,6 +19,10 @@ function enableSystemProxy() {
|
|
|
19
19
|
info(`开启系统代理(bypass: ${bypass.length} 个域名)...`);
|
|
20
20
|
execSync(`"${localW2Path}" proxy -x "${bypassArg}"`, { stdio: 'inherit' });
|
|
21
21
|
ok('系统代理已开启');
|
|
22
|
+
// 刷新 shell-env.sh(CA 路径 + NO_PROXY ← bypass 最新值),终端 setproxy 取到的就是新列表
|
|
23
|
+
const certCheck = require('../../cert-check');
|
|
24
|
+
const certFile = certCheck.findCertFile();
|
|
25
|
+
certCheck.writeProxyEnv(certFile ? certCheck.ensureMergedCaBundle(certFile) : null);
|
|
22
26
|
}
|
|
23
27
|
|
|
24
28
|
function disableSystemProxy() {
|
|
@@ -253,6 +257,10 @@ function registerProxy(program) {
|
|
|
253
257
|
|
|
254
258
|
proxyConfig.saveConfig({ bypassDomains: newList });
|
|
255
259
|
ok(`已保存跳过列表(${newList.length} 个域名)`);
|
|
260
|
+
// 同步刷新 shell-env.sh 的 NO_PROXY
|
|
261
|
+
const certCheck = require('../../cert-check');
|
|
262
|
+
const certFile = certCheck.findCertFile();
|
|
263
|
+
certCheck.writeProxyEnv(certFile ? certCheck.ensureMergedCaBundle(certFile) : null);
|
|
256
264
|
info('重新开启系统代理后生效: easy-proxy proxy on');
|
|
257
265
|
});
|
|
258
266
|
}
|
|
@@ -34,6 +34,15 @@ function registerStart(program) {
|
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
if (newCertPath) {
|
|
37
|
+
// 幂等补齐:shell-env.sh(CA 变量 + 代理函数)与 zshrc 的 source 行。
|
|
38
|
+
// 每次都重写 shell-env.sh —— 证书重签后 CA 路径会变,bypass 变更后 NO_PROXY 要刷新
|
|
39
|
+
const {
|
|
40
|
+
writeProxyEnv,
|
|
41
|
+
writeShellExports,
|
|
42
|
+
ensureMergedCaBundle: ensure,
|
|
43
|
+
} = require('../../cert-check');
|
|
44
|
+
writeProxyEnv(ensure(newCertPath));
|
|
45
|
+
writeShellExports();
|
|
37
46
|
await checkCertStatus(true, true);
|
|
38
47
|
} else {
|
|
39
48
|
info('证书: 未找到(whistle 生成超时,下次启动自动重试)');
|
package/package.json
CHANGED