cicy-desktop 2.1.178 → 2.1.180
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/package.json +6 -6
- package/src/backends/homepage-preload.js +1 -0
- package/src/backends/homepage-react/assets/{index-CGcN1St7.css → index-CyUwy5Um.css} +1 -1
- package/src/backends/homepage-react/assets/index-DhqT69Bv.js +377 -0
- package/src/backends/homepage-react/index.html +2 -2
- package/src/backends/sidecar-ipc.js +39 -1
- package/src/main.js +22 -9
- package/src/sidecar/colima-docker.js +12 -5
- package/src/sidecar/host-mihomo.js +150 -0
- package/src/sidecar/wsl-docker.js +10 -4
- package/workers/render/src/App.css +6 -0
- package/workers/render/src/App.jsx +22 -0
- package/src/backends/homepage-react/assets/index-CH3x7Ecg.js +0 -377
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
<link rel="icon" type="image/svg+xml" href="./favicon.svg" />
|
|
7
7
|
<link rel="icon" type="image/png" sizes="256x256" href="./favicon-256.png" />
|
|
8
8
|
<title>CiCy Desktop</title>
|
|
9
|
-
<script type="module" crossorigin src="./assets/index-
|
|
10
|
-
<link rel="stylesheet" crossorigin href="./assets/index-
|
|
9
|
+
<script type="module" crossorigin src="./assets/index-DhqT69Bv.js"></script>
|
|
10
|
+
<link rel="stylesheet" crossorigin href="./assets/index-CyUwy5Um.css">
|
|
11
11
|
</head>
|
|
12
12
|
<body>
|
|
13
13
|
<div id="root"></div>
|
|
@@ -24,6 +24,7 @@ const sidecar = require("../sidecar/cicy-code");
|
|
|
24
24
|
const docker = require("../sidecar/docker");
|
|
25
25
|
const wslDocker = require("../sidecar/wsl-docker"); // Docker-版 via WSL2+Ubuntu (方案 A, win32)
|
|
26
26
|
const colimaDocker = require("../sidecar/colima-docker"); // Docker-版 via Colima (Lima VM, darwin)
|
|
27
|
+
const hostMihomo = require("../sidecar/host-mihomo"); // 宿主侧 mihomo —— 给系统 Chrome 的 per-profile 代理
|
|
27
28
|
// 按平台分发 Docker-版 cicy-code 的运行层:darwin → Colima,win32 → WSL2。两者同接口
|
|
28
29
|
// (bootstrap/status/restart/stop/dockerRestart/recreate/update/upgrade/runContainer/
|
|
29
30
|
// readContainerToken),所以下面的 handler 共用一份逻辑,只换底层模块。
|
|
@@ -61,6 +62,9 @@ const cc = (() => { try { return require("../cloud/cloud-client"); } catch { ret
|
|
|
61
62
|
const DOCKER_TEAMS_FILE = path.join(os.homedir(), "cicy-ai", "db", "docker-teams.json");
|
|
62
63
|
function readDockerTeams() { try { return JSON.parse(fs.readFileSync(DOCKER_TEAMS_FILE, "utf8")) || {}; } catch { return {}; } }
|
|
63
64
|
function writeDockerTeams(obj) { try { fs.mkdirSync(path.dirname(DOCKER_TEAMS_FILE), { recursive: true }); fs.writeFileSync(DOCKER_TEAMS_FILE, JSON.stringify(obj, null, 2)); } catch {} }
|
|
65
|
+
// 「Chrome 代理」开关:在宿主起一个 mihomo(host-mihomo)服务系统 Chrome 的 per-profile 代理,
|
|
66
|
+
// 配置从容器里 cp 出来。按 volume 存 docker-teams.json[volume].chromeProxy。
|
|
67
|
+
function chromeProxyEnabled() { try { return !!(readDockerTeams()[APP_VOLUME] || {}).chromeProxy; } catch { return false; } }
|
|
64
68
|
let dockerTeamReg = null; // { teamId, title, apiKey } — 缓存,appOpts 读它
|
|
65
69
|
|
|
66
70
|
// docker 团队的权威来源 = cloud(w-10122 #197):POST /api/team/docker/register 按
|
|
@@ -105,7 +109,7 @@ function register({ sidecarLogPath } = {}) {
|
|
|
105
109
|
// 容器里 cicy-code 的版本(DockerCard 底部显示):running 时读 :APP_PORT/api/health。
|
|
106
110
|
let ver = null;
|
|
107
111
|
if (s.running) { try { ver = await require("../sidecar/version").running(APP_PORT); } catch {} }
|
|
108
|
-
_dockerStatusCache = { installed: !!s.distro, dockerRunning: !!s.engineUp, running: !!s.running, unknown: !!s.unknown, version: ver, port: APP_PORT, platform: process.platform, ts: Date.now() };
|
|
112
|
+
_dockerStatusCache = { installed: !!s.distro, dockerRunning: !!s.engineUp, running: !!s.running, unknown: !!s.unknown, version: ver, port: APP_PORT, platform: process.platform, chromeProxy: chromeProxyEnabled(), chromeProxyRunning: hostMihomo.running(), ts: Date.now() };
|
|
109
113
|
} catch (e) {
|
|
110
114
|
_dockerStatusCache = { installed: false, dockerRunning: false, running: false, unknown: true, port: APP_PORT, platform: process.platform, error: e.message, ts: Date.now() };
|
|
111
115
|
}
|
|
@@ -140,6 +144,8 @@ function register({ sidecarLogPath } = {}) {
|
|
|
140
144
|
await refreshDockerStatus();
|
|
141
145
|
}
|
|
142
146
|
} catch (e) { log.warn(`[docker-daemon] key self-heal failed: ${e.message}`); }
|
|
147
|
+
// Chrome 代理:开关开着但宿主 mihomo 没跑 → 拉起(从容器同步配置,幂等)。
|
|
148
|
+
try { await maybeStartChromeProxy(); } catch (e) { log.warn(`[chrome-proxy] auto-start failed: ${e.message}`); }
|
|
143
149
|
}
|
|
144
150
|
} finally { _dockerDaemonBusy = false; }
|
|
145
151
|
return _dockerStatusCache;
|
|
@@ -149,6 +155,18 @@ function register({ sidecarLogPath } = {}) {
|
|
|
149
155
|
setInterval(() => { reconcileDocker().catch(() => {}); }, 60000); // keep fresh + self-heal
|
|
150
156
|
}
|
|
151
157
|
|
|
158
|
+
// 宿主 Chrome 代理:开关开着才做。从容器 cp 出 mihomo 配置 → host-mihomo 在宿主起/续。
|
|
159
|
+
// 已在跑也同步一次配置(容器侧节点可能被云端更新),变了才重启。幂等。
|
|
160
|
+
async function maybeStartChromeProxy() {
|
|
161
|
+
if (!chromeProxyEnabled() || !APP_DOCKER_SUPPORTED) return;
|
|
162
|
+
const yaml = await appDocker.readMihomoConfig(APP_CONTAINER);
|
|
163
|
+
if (hostMihomo.binPresent() && hostMihomo.running()) {
|
|
164
|
+
if (hostMihomo.writeConfig(yaml)) hostMihomo.start({ force: true });
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
await hostMihomo.enable({ containerYaml: yaml });
|
|
168
|
+
}
|
|
169
|
+
|
|
152
170
|
ipcMain.handle("sidecar:status", async () => {
|
|
153
171
|
const running = await sidecar.probeExisting(PORT);
|
|
154
172
|
return { running };
|
|
@@ -342,6 +360,26 @@ function register({ sidecarLogPath } = {}) {
|
|
|
342
360
|
} catch (err) { return { ok: false, error: err.message }; }
|
|
343
361
|
});
|
|
344
362
|
|
|
363
|
+
// ⋯ menu 开关 → 「Chrome 代理」:在宿主起一个 mihomo 服务系统 Chrome 的 per-profile 代理
|
|
364
|
+
// (127.0.0.1:2000N)。配置从容器里 cp 出来(含云端下发的真实节点),DNS 关掉、控制口错开。
|
|
365
|
+
// 容器不再 publish 20001-32(那条路在 colima/WSL 下根本不通)。开关存 docker-teams.json[volume]。
|
|
366
|
+
ipcMain.handle("docker:app-chrome-proxy", async (e, on) => {
|
|
367
|
+
if (!APP_DOCKER_SUPPORTED) return { ok: false, error: "Chrome 代理仅支持 Windows / macOS" };
|
|
368
|
+
try {
|
|
369
|
+
const store = readDockerTeams();
|
|
370
|
+
store[APP_VOLUME] = { ...(store[APP_VOLUME] || {}), chromeProxy: !!on };
|
|
371
|
+
writeDockerTeams(store);
|
|
372
|
+
if (on) {
|
|
373
|
+
const containerYaml = await appDocker.readMihomoConfig(APP_CONTAINER);
|
|
374
|
+
await hostMihomo.enable({ containerYaml, emit: (ev) => { try { e.sender.send("docker:app-progress", ev); } catch {} } });
|
|
375
|
+
} else {
|
|
376
|
+
hostMihomo.stop();
|
|
377
|
+
}
|
|
378
|
+
await refreshDockerStatus().catch(() => {});
|
|
379
|
+
return { ok: true, chromeProxy: !!on, running: hostMihomo.running() };
|
|
380
|
+
} catch (err) { return { ok: false, error: err.message }; }
|
|
381
|
+
});
|
|
382
|
+
|
|
345
383
|
// ⋯ menu → 更新 cicy-code: pull the latest cicy-code into the container +
|
|
346
384
|
// restart it (no container recreate). Streams progress to the drawer.
|
|
347
385
|
ipcMain.handle("docker:app-update", async (e) => {
|
package/src/main.js
CHANGED
|
@@ -1377,25 +1377,38 @@ function cleanup() {
|
|
|
1377
1377
|
log.info("[Cleanup] shutting down child services");
|
|
1378
1378
|
try { cicyCodeSidecar.stop(); } catch (e) { /* best-effort */ }
|
|
1379
1379
|
|
|
1380
|
-
//
|
|
1381
|
-
//
|
|
1380
|
+
// Reap leftover NATIVE host helpers from the pre-docker-only era (a stray
|
|
1381
|
+
// cicy-code.exe / ttyd / gotty / code-server). 主人: quitting CiCy Desktop must
|
|
1382
|
+
// NOT touch Docker — in docker-only these all run INSIDE the container, and the
|
|
1383
|
+
// Docker engine (colima VM on mac / WSL distro+dockerd on win) is a separate
|
|
1384
|
+
// background service that survives the app + the container is --restart
|
|
1385
|
+
// unless-stopped. So we ONLY kill exact native host targets, never the engine.
|
|
1382
1386
|
try {
|
|
1383
1387
|
const { execSync } = require("child_process");
|
|
1384
1388
|
const targets = ["cicy-code", "ttyd", "gotty", "code-server"];
|
|
1385
1389
|
|
|
1386
1390
|
if (process.platform === "win32") {
|
|
1387
|
-
// Windows: taskkill /F /IM <name>.exe
|
|
1391
|
+
// Windows: taskkill /F /IM <name>.exe — EXACT image name, no wildcards, so it
|
|
1392
|
+
// can only hit a native cicy-code.exe (retired), never wsl.exe / dockerd /
|
|
1393
|
+
// vmmem / the WSL distro. We never `wsl --terminate/--shutdown` on quit.
|
|
1388
1394
|
for (const t of targets) {
|
|
1389
1395
|
try { execSync(`taskkill /F /IM ${t}.exe`, { stdio: "ignore", windowsHide: true }); } catch {}
|
|
1390
|
-
try { execSync(`taskkill /F /IM ${t}`, { stdio: "ignore", windowsHide: true }); } catch {}
|
|
1391
1396
|
}
|
|
1392
|
-
// Windows has no tmux by default; nothing to do.
|
|
1393
1397
|
} else {
|
|
1394
|
-
// macOS / Linux: pkill matches by command line.
|
|
1395
|
-
|
|
1396
|
-
|
|
1398
|
+
// macOS / Linux: pkill matches by FULL command line. ⚠️ docker-only: cicy-code
|
|
1399
|
+
// / ttyd / gotty / code-server all run INSIDE the container, NOT on the host —
|
|
1400
|
+
// there is normally nothing here to kill. CRITICAL: never `pkill -f cicy-code`
|
|
1401
|
+
// plainly — the colima profile is named `cicy-code`, so the Lima VM hostagent
|
|
1402
|
+
// runs as `limactl ... colima-cicy-code` and a bare match KILLS THE VM on every
|
|
1403
|
+
// quit → next launch sees Docker "down" and re-bootstraps ("重新 install" bug).
|
|
1404
|
+
// Anchor cicy-code to the native --desktop sidecar form (retired) only.
|
|
1405
|
+
const unixPats = ["cicy-code --desktop", "ttyd", "gotty", "code-server"];
|
|
1406
|
+
for (const p of unixPats) {
|
|
1407
|
+
try { execSync(`pkill -f '${p}'`, { stdio: "ignore" }); } catch {}
|
|
1397
1408
|
}
|
|
1398
|
-
|
|
1409
|
+
// Host tmux only (native path, retired); the container's tmux is in its own
|
|
1410
|
+
// namespace and unaffected. (No -f cicy-code match here either.)
|
|
1411
|
+
try { execSync(`pkill -f 'tmux.*cicy'`, { stdio: "ignore" }); } catch {}
|
|
1399
1412
|
}
|
|
1400
1413
|
} catch (e) {
|
|
1401
1414
|
log.warn(`[Cleanup] kill children failed: ${e.message}`);
|
|
@@ -325,12 +325,11 @@ async function runContainer({ port = 8009, container = "cicy-code-docker-8009",
|
|
|
325
325
|
.filter(([, v]) => v != null && v !== "")
|
|
326
326
|
.map(([k, v]) => `-e ${k}='${String(v).replace(/'/g, "'\\''")}'`)
|
|
327
327
|
.join(" ");
|
|
328
|
-
//
|
|
329
|
-
//
|
|
330
|
-
//
|
|
331
|
-
const CHROME_PROXY_PORTS = process.env.CICY_CHROME_PROXY_PORTS || "20001-20032";
|
|
328
|
+
// 主人方案: Chrome 的 per-profile 代理(127.0.0.1:2000N)改由「宿主 mihomo」(host-mihomo.js)
|
|
329
|
+
// 服务,不再从容器 publish 20001-32 —— colima/Lima 转发那个端口段始终到不了容器里只绑
|
|
330
|
+
// 127.0.0.1 的监听(Chrome 一直 ERR_EMPTY_RESPONSE)。容器只暴露 cicy-code 的 :8009。
|
|
332
331
|
const mk = (s) => `run -d --name ${container} --restart unless-stopped ${PLATFORM_FLAG} ` +
|
|
333
|
-
`-p 127.0.0.1:${port}:8008 -
|
|
332
|
+
`-p 127.0.0.1:${port}:8008 -e CICY_PUBLIC=1 -v ${volume}:/home/cicy ${s} ${envArgs} ${IMAGE}`;
|
|
334
333
|
const share = shareMountArg();
|
|
335
334
|
try {
|
|
336
335
|
await dk(mk(share), { timeout: 90000 });
|
|
@@ -565,7 +564,15 @@ async function authorizeHostSsh({ container = "cicy-code-docker-8009" } = {}) {
|
|
|
565
564
|
return { ok: true, user, added: add.length, verified, detail };
|
|
566
565
|
}
|
|
567
566
|
|
|
567
|
+
// 读容器里 cicy-code 生成的 mihomo.yaml —— host-mihomo 用它在宿主重建 Chrome 代理配置
|
|
568
|
+
// (含云端下发的真实上游节点)。
|
|
569
|
+
async function readMihomoConfig(container = "cicy-code-docker-8009") {
|
|
570
|
+
const { stdout } = await dk(`exec ${container} cat /home/cicy/cicy-ai/db/mihomo.yaml`, { timeout: 15000 });
|
|
571
|
+
return String(stdout || "");
|
|
572
|
+
}
|
|
573
|
+
|
|
568
574
|
module.exports = {
|
|
569
575
|
bootstrap, status, restart, stop, dockerRestart, recreate, update, upgrade, runContainer, readContainerToken,
|
|
570
576
|
vmExists, colimaInstalled, dockerCliInstalled, engineUp, imagePresent, probeHealth, hasGatewayKey, authorizeHostSsh,
|
|
577
|
+
readMihomoConfig,
|
|
571
578
|
};
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
// Host-side mihomo for the Chrome per-profile proxy.
|
|
2
|
+
//
|
|
3
|
+
// docker-only put mihomo INSIDE the cicy-code container. But the system Chrome
|
|
4
|
+
// runs on the HOST (mac/win/linux), and its per-profile proxy points at
|
|
5
|
+
// 127.0.0.1:2000N — which a container CANNOT reliably serve through colima/WSL
|
|
6
|
+
// port-forwarding (the per-profile listeners bind 127.0.0.1 inside the container;
|
|
7
|
+
// publishing the 20001-32 range through colima/Lima never reached them → Chrome
|
|
8
|
+
// got ERR_EMPTY_RESPONSE / connection-closed). 主人方案: run a SECOND mihomo on
|
|
9
|
+
// the HOST, reusing the SAME proxy nodes/rules — we copy the container's
|
|
10
|
+
// mihomo.yaml out (the cloud provisions it with the real upstream nodes), strip
|
|
11
|
+
// it down to just what Chrome needs (listeners + proxies + groups + rules), and
|
|
12
|
+
// run it natively so Chrome reaches 127.0.0.1:2000N directly. No container port
|
|
13
|
+
// publish involved (runContainer drops the -p 20001-32 range).
|
|
14
|
+
//
|
|
15
|
+
// Binary is pulled from OSS (CN-fast) — github/gh-proxy was too slow on real
|
|
16
|
+
// Macs. Same version the container uses (cicy-ai/cicy-mihomo v1.10.4).
|
|
17
|
+
|
|
18
|
+
const { spawn, execFile } = require("child_process");
|
|
19
|
+
const fs = require("fs");
|
|
20
|
+
const os = require("os");
|
|
21
|
+
const path = require("path");
|
|
22
|
+
const https = require("https");
|
|
23
|
+
const yaml = require("js-yaml");
|
|
24
|
+
|
|
25
|
+
const VER = (process.env.CICY_MIHOMO_VERSION || "v1.10.4").replace(/^v?/, "v");
|
|
26
|
+
const OSS_BASE = process.env.CICY_OSS_BASE || "https://cicy-1372193042-cn.oss-cn-shanghai.aliyuncs.com";
|
|
27
|
+
const IS_WIN = process.platform === "win32";
|
|
28
|
+
const EXT = IS_WIN ? ".exe" : "";
|
|
29
|
+
|
|
30
|
+
function osStr() { return process.platform === "darwin" ? "darwin" : IS_WIN ? "windows" : "linux"; }
|
|
31
|
+
function archStr() { return process.arch === "arm64" ? "arm64" : "amd64"; }
|
|
32
|
+
// OSS mirror of cicy-ai/cicy-mihomo releases (see mihomo/<ver>/mihomo-<os>-<arch>).
|
|
33
|
+
function assetUrl() { return process.env.CICY_MIHOMO_RELEASE_URL || `${OSS_BASE}/mihomo/${VER}/mihomo-${osStr()}-${archStr()}${EXT}`; }
|
|
34
|
+
|
|
35
|
+
const RT_DIR = path.join(os.homedir(), "cicy-ai", "runtime", "mihomo", VER.replace(/^v/, ""));
|
|
36
|
+
function binPath() { return path.join(RT_DIR, "mihomo" + EXT); }
|
|
37
|
+
const HOST_CONFIG = path.join(os.homedir(), "cicy-ai", "db", "mihomo-host.yaml");
|
|
38
|
+
const HOST_LOG = path.join(os.homedir(), "cicy-ai", "db", "mihomo-host.log");
|
|
39
|
+
const PID_FILE = path.join(os.homedir(), "cicy-ai", "db", "mihomo-host.pid");
|
|
40
|
+
|
|
41
|
+
// Host instance's own control/mixed ports — kept OFF the container's (9001/19001)
|
|
42
|
+
// so the two mihomos never clash even if both somehow share a loopback view.
|
|
43
|
+
const HOST_MIXED = Number(process.env.CICY_HOST_MIHOMO_MIXED || 9011);
|
|
44
|
+
const HOST_CTRL = Number(process.env.CICY_HOST_MIHOMO_CTRL || 19011);
|
|
45
|
+
|
|
46
|
+
function binPresent() { try { return fs.statSync(binPath()).size > 1_000_000; } catch { return false; } }
|
|
47
|
+
|
|
48
|
+
function download(url, dest, redirects = 0) {
|
|
49
|
+
return new Promise((resolve, reject) => {
|
|
50
|
+
if (redirects > 5) return reject(new Error("too many redirects"));
|
|
51
|
+
const f = fs.createWriteStream(dest);
|
|
52
|
+
const req = https.get(url, (res) => {
|
|
53
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
54
|
+
f.close(); try { fs.unlinkSync(dest); } catch {}
|
|
55
|
+
return download(res.headers.location, dest, redirects + 1).then(resolve, reject);
|
|
56
|
+
}
|
|
57
|
+
if (res.statusCode !== 200) { f.close(); try { fs.unlinkSync(dest); } catch {} return reject(new Error(`HTTP ${res.statusCode} for ${url}`)); }
|
|
58
|
+
res.pipe(f);
|
|
59
|
+
f.on("finish", () => f.close(() => resolve()));
|
|
60
|
+
f.on("error", (e) => { try { fs.unlinkSync(dest); } catch {} reject(e); });
|
|
61
|
+
});
|
|
62
|
+
req.on("error", (e) => { try { fs.unlinkSync(dest); } catch {} reject(e); });
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Pull the mihomo binary from OSS into the runtime store (idempotent).
|
|
67
|
+
async function ensureBinary({ emit } = {}) {
|
|
68
|
+
if (binPresent()) return binPath();
|
|
69
|
+
fs.mkdirSync(RT_DIR, { recursive: true });
|
|
70
|
+
emit && emit({ phase: "chrome-proxy", status: "running", message: "下载 mihomo(Chrome 代理,从 OSS)…" });
|
|
71
|
+
await download(assetUrl(), binPath());
|
|
72
|
+
if (!IS_WIN) { try { fs.chmodSync(binPath(), 0o755); } catch {} }
|
|
73
|
+
if (!binPresent()) throw new Error("mihomo 下载后校验失败(文件过小)");
|
|
74
|
+
return binPath();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Build the HOST config from the container's mihomo.yaml: keep only what Chrome
|
|
78
|
+
// needs (listeners + proxies + proxy-groups + rules), disable DNS (no :53 bind on
|
|
79
|
+
// the host — needs root) and pin our own control/mixed ports. The per-profile
|
|
80
|
+
// listeners already bind 127.0.0.1, exactly what host Chrome connects to.
|
|
81
|
+
function buildHostConfig(containerYaml) {
|
|
82
|
+
let c = {};
|
|
83
|
+
try { c = yaml.load(containerYaml) || {}; } catch (e) { throw new Error(`容器 mihomo 配置解析失败: ${e.message}`); }
|
|
84
|
+
const host = {
|
|
85
|
+
"mixed-port": HOST_MIXED,
|
|
86
|
+
"allow-lan": false,
|
|
87
|
+
"log-level": "warning",
|
|
88
|
+
"external-controller": `127.0.0.1:${HOST_CTRL}`,
|
|
89
|
+
secret: "",
|
|
90
|
+
dns: { enable: false },
|
|
91
|
+
listeners: Array.isArray(c.listeners) ? c.listeners : [],
|
|
92
|
+
proxies: Array.isArray(c.proxies) ? c.proxies : [],
|
|
93
|
+
"proxy-groups": Array.isArray(c["proxy-groups"]) ? c["proxy-groups"] : [],
|
|
94
|
+
rules: Array.isArray(c.rules) ? c.rules : ["MATCH,DIRECT"],
|
|
95
|
+
};
|
|
96
|
+
return yaml.dump(host, { lineWidth: -1 });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Write the adapted config; returns true if it changed (caller restarts on change).
|
|
100
|
+
function writeConfig(containerYaml) {
|
|
101
|
+
const next = buildHostConfig(containerYaml);
|
|
102
|
+
let prev = ""; try { prev = fs.readFileSync(HOST_CONFIG, "utf8"); } catch {}
|
|
103
|
+
fs.mkdirSync(path.dirname(HOST_CONFIG), { recursive: true });
|
|
104
|
+
if (prev === next) return false;
|
|
105
|
+
fs.writeFileSync(HOST_CONFIG, next);
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function readPid() { try { return Number(fs.readFileSync(PID_FILE, "utf8").trim()) || 0; } catch { return 0; } }
|
|
110
|
+
function alive(pid) { if (!pid) return false; try { process.kill(pid, 0); return true; } catch { return false; } }
|
|
111
|
+
function running() { return alive(readPid()); }
|
|
112
|
+
|
|
113
|
+
function stop() {
|
|
114
|
+
const pid = readPid();
|
|
115
|
+
if (alive(pid)) { try { process.kill(pid, "SIGKILL"); } catch {} }
|
|
116
|
+
try { fs.unlinkSync(PID_FILE); } catch {}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Spawn the host mihomo detached so it survives this process. Idempotent: a live
|
|
120
|
+
// instance with the current config is left alone unless force.
|
|
121
|
+
function start({ force = false } = {}) {
|
|
122
|
+
if (!force && running()) return { started: false, adopted: true };
|
|
123
|
+
stop();
|
|
124
|
+
if (!binPresent()) throw new Error("mihomo 二进制不存在(先 ensureBinary)");
|
|
125
|
+
if (!fs.existsSync(HOST_CONFIG)) throw new Error("host 配置不存在(先 writeConfig)");
|
|
126
|
+
const out = fs.openSync(HOST_LOG, "a");
|
|
127
|
+
const child = spawn(binPath(), ["-f", HOST_CONFIG], {
|
|
128
|
+
detached: true, stdio: ["ignore", out, out], windowsHide: true,
|
|
129
|
+
});
|
|
130
|
+
child.unref();
|
|
131
|
+
try { fs.writeFileSync(PID_FILE, String(child.pid)); } catch {}
|
|
132
|
+
return { started: true, pid: child.pid };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Full enable: ensure binary → copy+adapt container config → (re)start.
|
|
136
|
+
// containerYaml is fetched by the caller (sidecar-ipc) via appDocker.
|
|
137
|
+
async function enable({ containerYaml, emit } = {}) {
|
|
138
|
+
await ensureBinary({ emit });
|
|
139
|
+
if (!containerYaml) throw new Error("没有拿到容器 mihomo 配置");
|
|
140
|
+
const changed = writeConfig(containerYaml);
|
|
141
|
+
const res = start({ force: changed });
|
|
142
|
+
emit && emit({ phase: "chrome-proxy", status: "running", message: "Chrome 代理(host mihomo)已就绪" });
|
|
143
|
+
return { ok: true, ...res };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
module.exports = {
|
|
147
|
+
VER, assetUrl, binPath, binPresent, ensureBinary,
|
|
148
|
+
buildHostConfig, writeConfig, start, stop, running, enable,
|
|
149
|
+
HOST_CONFIG, HOST_LOG,
|
|
150
|
+
};
|
|
@@ -425,10 +425,9 @@ async function runContainer({ port = 8009, container = "cicy-code-docker", volum
|
|
|
425
425
|
// bridge container → every lookup is EAI_AGAIN and cicy-code's startup `npm i`
|
|
426
426
|
// crash-loops the container (:8009 never comes up). Pin public resolvers: Aliyun
|
|
427
427
|
// 223.5.5.5 (CN-fast) first, Google 8.8.8.8 as the overseas fallback.
|
|
428
|
-
//
|
|
429
|
-
//
|
|
430
|
-
const
|
|
431
|
-
const cmd = `docker run -d --name ${container} --restart unless-stopped --dns 223.5.5.5 --dns 8.8.8.8 -p 127.0.0.1:${port}:8008 -p 127.0.0.1:${CHROME_PROXY_PORTS}:${CHROME_PROXY_PORTS} -e CICY_PUBLIC=1 -v ${volume}:/home/cicy ${shareMountArg()} ${envArgs} ${IMAGE}`;
|
|
428
|
+
// 主人方案: Chrome 的 per-profile 代理改由「宿主 mihomo」(host-mihomo.js)服务,不再从容器
|
|
429
|
+
// publish 20001-32(WSL/colima 转发那段口到不了容器里只绑 127.0.0.1 的监听)。容器只暴露 :8009。
|
|
430
|
+
const cmd = `docker run -d --name ${container} --restart unless-stopped --dns 223.5.5.5 --dns 8.8.8.8 -p 127.0.0.1:${port}:8008 -e CICY_PUBLIC=1 -v ${volume}:/home/cicy ${shareMountArg()} ${envArgs} ${IMAGE}`;
|
|
432
431
|
await wslRun(cmd, { timeout: 60000 });
|
|
433
432
|
ensureDesktopShortcut(volume, port).catch(() => {});
|
|
434
433
|
return { started: true };
|
|
@@ -736,7 +735,14 @@ async function hasGatewayKey(container = "cicy-code-docker") {
|
|
|
736
735
|
catch { return false; }
|
|
737
736
|
}
|
|
738
737
|
|
|
738
|
+
// 读容器里 cicy-code 生成的 mihomo.yaml(host-mihomo 用它在 Windows 宿主重建 Chrome 代理配置)。
|
|
739
|
+
async function readMihomoConfig(container = "cicy-code-docker-8009") {
|
|
740
|
+
const { stdout } = await wslRun(`docker exec ${container} cat /home/cicy/cicy-ai/db/mihomo.yaml`, { timeout: 15000 });
|
|
741
|
+
return String(stdout || "");
|
|
742
|
+
}
|
|
743
|
+
|
|
739
744
|
module.exports = {
|
|
740
745
|
bootstrap, status, restart, stop, dockerRestart, recreate, update, upgrade, runContainer, readContainerToken,
|
|
741
746
|
distroInstalled, dockerInstalled, dockerEngineUp, imagePresent, probeHealth, wslRun, hasGatewayKey,
|
|
747
|
+
readMihomoConfig,
|
|
742
748
|
};
|
|
@@ -841,6 +841,12 @@ body {
|
|
|
841
841
|
.bcard__menu-item.is-danger:hover { background: rgba(239,68,68,.16); color: #fff; }
|
|
842
842
|
/* Plain divider line in the DockerCard ⋯ menu (cicy-code ops 上 / Docker 容器 ops 下). */
|
|
843
843
|
.bcard__menu-sep { height: 1px; margin: 5px 10px; background: rgba(255,255,255,.08); }
|
|
844
|
+
/* Switch-style menu item (Chrome 代理): label 左 + 小开关右. */
|
|
845
|
+
.bcard__menu-item--switch { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
|
|
846
|
+
.bcard__switch { flex: none; width: 30px; height: 17px; border-radius: 999px; background: rgba(255,255,255,.16); position: relative; transition: background .15s; }
|
|
847
|
+
.bcard__switch::after { content: ""; position: absolute; top: 2px; left: 2px; width: 13px; height: 13px; border-radius: 50%; background: #fff; transition: transform .15s; }
|
|
848
|
+
.bcard__switch.is-on { background: var(--accent-text, #4f8cff); }
|
|
849
|
+
.bcard__switch.is-on::after { transform: translateX(13px); }
|
|
844
850
|
/* Portaled to document.body to escape .bcard's overflow:hidden (which clipped the
|
|
845
851
|
dropdown). Inline top/left position it under the kebab; here just a high
|
|
846
852
|
stacking order (below toast/drawer) + word-wrapping items so nothing overflows. */
|
|
@@ -1910,6 +1910,19 @@ function DockerCard({ dockerTeam, cloudTitle, onOpen, onRename, onRefresh }) {
|
|
|
1910
1910
|
} finally { setBusy(""); checkStatus(); }
|
|
1911
1911
|
}, [checkStatus]);
|
|
1912
1912
|
|
|
1913
|
+
// 「Chrome 代理」开关:在宿主起一个 mihomo 给系统 Chrome 的 per-profile 代理(从容器 cp 配置)。
|
|
1914
|
+
const toggleChromeProxy = useCallback(async (on) => {
|
|
1915
|
+
setMenuOpen(false); setBusy("chromeproxy");
|
|
1916
|
+
toast.show({ id: "docker-op", message: on ? tr("docker.chromeProxyOn", "启用 Chrome 代理(宿主 mihomo)…") : tr("docker.chromeProxyOff", "关闭 Chrome 代理…"), status: "running" });
|
|
1917
|
+
try {
|
|
1918
|
+
const r = await window.cicy.docker.appChromeProxy(on);
|
|
1919
|
+
if (r?.ok) toast.show({ id: "docker-op", message: on ? tr("docker.chromeProxyDone", "Chrome 代理已启用 ✅") : tr("docker.chromeProxyUndone", "Chrome 代理已关闭"), status: "done", ttl: 3000 });
|
|
1920
|
+
else toast.show({ id: "docker-op", message: (r?.error || tr("docker.opFailed", "操作失败")), status: "error", ttl: 6000 });
|
|
1921
|
+
} catch (e) {
|
|
1922
|
+
toast.show({ id: "docker-op", message: e.message, status: "error", ttl: 6000 });
|
|
1923
|
+
} finally { setBusy(""); checkStatus(); }
|
|
1924
|
+
}, [checkStatus]);
|
|
1925
|
+
|
|
1913
1926
|
// Render on Windows (WSL2) and macOS (Colima) — the two platforms the
|
|
1914
1927
|
// Docker-版 backend supports. window.cicy.platform is sync, so we can decide
|
|
1915
1928
|
// immediately without waiting on the async appStatus probe.
|
|
@@ -2048,6 +2061,15 @@ function DockerCard({ dockerTeam, cloudTitle, onOpen, onRename, onRefresh }) {
|
|
|
2048
2061
|
{tr("docker.authorizeHost", "授权容器访问 Mac")}
|
|
2049
2062
|
</button>
|
|
2050
2063
|
)}
|
|
2064
|
+
{/* Chrome 代理:宿主 mihomo 服务系统 Chrome 的 per-profile 代理(mac/win 都有 host Chrome)*/}
|
|
2065
|
+
<button type="button" data-id="DockerCard-chrome-proxy"
|
|
2066
|
+
className="bcard__menu-item bcard__menu-item--switch"
|
|
2067
|
+
role="menuitemcheckbox" aria-checked={!!status?.chromeProxy}
|
|
2068
|
+
title={tr("docker.chromeProxyHint", "在本机起一个 mihomo,给系统 Chrome 的分身代理(127.0.0.1:2000N)。配置从容器同步")}
|
|
2069
|
+
onClick={() => toggleChromeProxy(!status?.chromeProxy)}>
|
|
2070
|
+
<span>{tr("docker.chromeProxy", "Chrome 代理")}</span>
|
|
2071
|
+
<span className={`bcard__switch${status?.chromeProxy ? " is-on" : ""}`} data-id="DockerCard-chrome-proxy-switch" aria-hidden />
|
|
2072
|
+
</button>
|
|
2051
2073
|
<button type="button" data-id="DockerCard-docker-restart" className="bcard__menu-item"
|
|
2052
2074
|
onClick={() => runOp("restart", () => window.cicy.docker.appDockerRestart(), tr("docker.dockerRestarted", "已重启 Docker 容器"))}>
|
|
2053
2075
|
{tr("docker.dockerRestart", "重启 Docker")}
|