Files
u-claw/portable/lib/resolve-no-proxy.mjs
hfshfg 3439db32ad feat(portable): 内网/局域网可用性工具包 + 启动绕代理 (v2.1.10)
便携版在内网/离线/受控浏览器环境下"配不上、用不了、查不清"的三个痛点工具化,
Win + Mac 双端,纯 Node 零依赖脚本,中文提示由 node 打印(.bat 保持纯 ASCII,
.command 保持 LF),三端真机验证(本机 Win / Mac mini / 客户机 pc-7512)。

新增工具:
- lib/intranet-check.mjs (+ Windows-IntranetFix.bat / Mac-IntranetFix.command)
  一键体检: 代理env + 直连可达 + 真发一条对话,分清"网络不通 vs 配置错"。
- lib/setup-local-model.mjs (+ Windows-LocalModel.bat / Mac-LocalModel.command)
  纯命令行配 Ollama / newapi 并当场实测,绕开打不开的 Control UI;写前自动备份+merge。
- lib/resolve-no-proxy.mjs 接入 Windows-Start.bat / Mac-Start.command:
  把配置里的模型 host 写进 NO_PROXY,避免系统代理劫持内网模型请求
  (OpenClaw 自身只 bypass loopback CDP,不管模型 host)。
- OpenClaw-Doctor.bat / Mac-OpenClaw-Doctor.command: 官方 doctor 的隔离进阶入口
  (只读、非交互;doctor 实跑慢且 TTY-only,故不进客户一键流程)。

测试: tests/windows-launchers.test.mjs 新增回归
- 客户面 .bat 必须纯 ASCII (UTF-8 中文会被 GBK cmd 读乱,报 usebackq 不是命令)
- macOS .command 必须 LF-only (CRLF 触发 bad interpreter: /bin/bash^M)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 18:53:21 +08:00

88 lines
3.6 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// resolve-no-proxy.mjs — 让"内网/自建模型地址"绕开系统代理
//
// 背景(内网环境最大的坑):
// 很多公司/机房的机器设置了 HTTP_PROXY / HTTPS_PROXY 环境变量(为了上外网)。
// OpenClaw 启动时若检测到这两个变量,会 setGlobalDispatcher(new EnvHttpProxyAgent())
// 于是"所有" fetch——包括调用用户自己填的模型 baseUrl——都被塞进公司代理。
// 当模型部署在内网(如 http://10.x / 192.168.x / 某机房 IP代理够不着那台机器
// 请求直接失败。表现互联网能连公网模型、reasonix/copilot 也能连内网,唯独本程序连不上。
// 见 openclaw dist/auth-profiles-*.js 的 ensureGlobalUndiciEnvProxyDispatcher()。
//
// 方案undici 的 EnvHttpProxyAgent 认 NO_PROXY。把用户配置里所有模型 baseUrl 的主机名
// IP 或域名)+ 本机回环地址,统一写进 NO_PROXY让这些地址"直连不走代理"。
// 纯增量、绝对安全:自建/内网模型本就不该走代理;没设代理时 NO_PROXY 也无副作用。
//
// 设计原则:静默失败。任何一步出错就不输出,启动照常(只是少了这层保护)。
//
// CLI 用法(供 .bat / .command source
// node resolve-no-proxy.mjs <CONFIG_PATH>
// 输出(无代理需要保护时不输出任何内容):
// UCLAW_NO_PROXY=localhost,127.0.0.1,::1,15.151.114.142,...
import { readFileSync } from 'node:fs';
// 始终直连的本机地址。
const ALWAYS = ['localhost', '127.0.0.1', '::1'];
// 从一个 baseUrl 字符串里抽出主机名IP 或域名)。容错:解析不了就忽略。
function hostOf(raw) {
if (typeof raw !== 'string') return null;
const trimmed = raw.trim();
if (!trimmed) return null;
try {
// 补协议URL() 才能解析 "host:port/v1" 这种缺协议的写法。
const withScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed) ? trimmed : `http://${trimmed}`;
const host = new URL(withScheme).hostname; // 自动去掉 IPv6 的方括号
return host || null;
} catch {
return null;
}
}
// 递归收集对象里所有 baseUrl 字段的主机名providers 可能嵌套/命名各异,宽松收集最稳)。
function collectHosts(node, out) {
if (!node || typeof node !== 'object') return;
if (Array.isArray(node)) {
for (const item of node) collectHosts(item, out);
return;
}
for (const [key, value] of Object.entries(node)) {
if (key === 'baseUrl' || key === 'baseURL') {
const h = hostOf(value);
if (h) out.add(h);
} else if (value && typeof value === 'object') {
collectHosts(value, out);
}
}
}
function main() {
const configPath = process.argv[2] || process.env.OPENCLAW_CONFIG_PATH;
if (!configPath) return;
let config;
try {
config = JSON.parse(readFileSync(configPath, 'utf8'));
} catch {
return; // 配置不存在/坏了:不输出,启动照常
}
const hosts = new Set();
collectHosts(config?.models, hosts);
// 合并已有的 NO_PROXY避免覆盖用户/系统已有设置。
const existing = (process.env.NO_PROXY || process.env.no_proxy || '')
.split(',')
.map((s) => s.trim())
.filter(Boolean);
const merged = Array.from(new Set([...existing, ...ALWAYS, ...hosts]));
// 没有任何模型主机、又没有已有 NO_PROXY 时,只剩本机回环,输出也无害——但为简洁,
// 仅当收集到了真实模型主机时才输出(本机回环本就不会被代理误伤到业务)。
if (hosts.size === 0 && existing.length === 0) return;
process.stdout.write(`UCLAW_NO_PROXY=${merged.join(',')}\n`);
}
main();