cicy-desktop 2.1.367 → 2.1.369
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/.github/workflows/build-qemu-package.yml +89 -0
- package/package.json +8 -6
- package/src/backends/homepage-react/assets/index-BMeWzitz.js +490 -0
- package/src/backends/homepage-react/index.html +1 -1
- package/src/backends/hub-client.js +1 -1
- package/src/backends/local-teams.js +1 -1
- package/src/backends/sidecar-ipc.js +9 -3
- package/src/i18n/locales/en.json +2 -2
- package/src/i18n/locales/fr.json +2 -2
- package/src/i18n/locales/ja.json +2 -2
- package/src/i18n/locales/zh-CN.json +3 -3
- package/src/main.js +3 -0
- package/src/sidecar/app-docker.js +9 -0
- package/src/sidecar/docker.js +14 -4
- package/src/sidecar/linux-backend.js +55 -0
- package/src/sidecar/qemu-docker.js +521 -0
- package/src/sidecar/qemu-vm.cjs +397 -0
- package/test/linux-backend.test.js +64 -0
- package/workers/render/src/App.jsx +155 -48
- package/src/backends/homepage-react/assets/index-BWZkvmvO.js +0 -679
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
setTimeout(check, 5000);
|
|
40
40
|
})();
|
|
41
41
|
</script>
|
|
42
|
-
<script type="module" crossorigin src="./assets/index-
|
|
42
|
+
<script type="module" crossorigin src="./assets/index-BMeWzitz.js"></script>
|
|
43
43
|
<link rel="stylesheet" crossorigin href="./assets/index-CVk0uc2g.css">
|
|
44
44
|
</head>
|
|
45
45
|
<body>
|
|
@@ -143,7 +143,7 @@ async function sidecarToken() {
|
|
|
143
143
|
if (!tok && process.platform === "win32") {
|
|
144
144
|
try {
|
|
145
145
|
tok = String(
|
|
146
|
-
(await require("../sidecar/
|
|
146
|
+
(await require("../sidecar/app-docker").readContainerToken(SIDECAR_PORT)) || ""
|
|
147
147
|
).trim();
|
|
148
148
|
} catch (e) {
|
|
149
149
|
log.warn(`[hub] sidecar token: ${e.message}`);
|
|
@@ -318,7 +318,7 @@ async function openTeam(id, opts = {}) {
|
|
|
318
318
|
let token = (opts && opts.token) || "";
|
|
319
319
|
if (!token) {
|
|
320
320
|
if (isWinDockerTeam) {
|
|
321
|
-
try { token = String(await require("../sidecar/
|
|
321
|
+
try { token = String(await require("../sidecar/app-docker").readContainerToken(localPort) || "").trim(); } catch (e) { log.warn(`[local-teams] open ${id}: container token read failed: ${e.message}`); }
|
|
322
322
|
if (!token) return { ok: false, error: "container_token_unavailable", hint: "cicy-code 容器还没就绪(读不到容器内的 token)。稍等几秒或用卡片菜单「重启」。" };
|
|
323
323
|
} else if (isLocalUrl) {
|
|
324
324
|
token = readGlobal()?.api_token || node.api_token || "";
|
|
@@ -34,7 +34,8 @@ const hostMihomo = require("../sidecar/host-mihomo"); // 宿主侧 mihomo ——
|
|
|
34
34
|
// 按平台分发 Docker-版 cicy-code 的运行层:darwin → Colima,win32 → WSL2。两者同接口
|
|
35
35
|
// (bootstrap/status/restart/stop/dockerRestart/recreate/update/upgrade/runContainer/
|
|
36
36
|
// readContainerToken),所以下面的 handler 共用一份逻辑,只换底层模块。
|
|
37
|
-
|
|
37
|
+
// win32 上再分:老机器 WSL2,新机器 QEMU(不靠 WSL,:8009)—— 见 sidecar/linux-backend.js。
|
|
38
|
+
const appDocker = require("../sidecar/app-docker");
|
|
38
39
|
// Docker-版 cicy-code 只剩 Windows(WSL2)。(2026-06 回调): macOS 改回 native cicy-code
|
|
39
40
|
// (:8008,colima 在 16G mac 上压垮内存被 jetsam 杀)→ darwin 不再走 docker,这里不放行,
|
|
40
41
|
// 所有 docker:app-* handler / reconcile / chrome-proxy 在 mac 上全部 no-op,DockerCard 不显示。
|
|
@@ -115,7 +116,7 @@ async function ensureDockerTeam() {
|
|
|
115
116
|
return null;
|
|
116
117
|
};
|
|
117
118
|
try {
|
|
118
|
-
const r = await cc.registerDockerTeam({ port: APP_PORT });
|
|
119
|
+
const r = await cc.registerDockerTeam({ port: APP_PORT, title: "本机团队" });
|
|
119
120
|
if (r && r.ok && r.apiKey) {
|
|
120
121
|
dockerTeamReg = { teamId: r.teamId, title: r.title || `Docker :${APP_PORT}`, apiKey: r.apiKey };
|
|
121
122
|
try { const store = readDockerTeams(); store[APP_VOLUME] = { ...(store[APP_VOLUME] || {}), teamId: r.teamId, title: dockerTeamReg.title, apiKey: r.apiKey }; writeDockerTeams(store); } catch {}
|
|
@@ -551,7 +552,9 @@ function register({ sidecarLogPath } = {}) {
|
|
|
551
552
|
const registerAppTeam = async () => {
|
|
552
553
|
const lt = require("./local-teams");
|
|
553
554
|
await ensureDockerTeam(); // 确保独立云端 team 存在 + 拿到 teamId/title
|
|
554
|
-
|
|
555
|
+
// 默认名「本机团队」(2026-10-21);云端还是系统缺省名(老的 Docker 团队 / Docker :<port>)时也换成它。
|
|
556
|
+
const t0 = dockerTeamReg && dockerTeamReg.title;
|
|
557
|
+
const title = (!t0 || /^\s*docker\s*(团队|team)?\s*(:\s*\d+)?\s*$/i.test(t0)) ? "本机团队" : t0;
|
|
555
558
|
// is_docker + cloud_team_id are passed INTO addTeam so they land in the SAME
|
|
556
559
|
// writeNodes as the node — before addTeam's fire-and-forget syncNameToCloud
|
|
557
560
|
// runs. That's what stops the freshly-created :8008 node from device-registering
|
|
@@ -582,6 +585,9 @@ function register({ sidecarLogPath } = {}) {
|
|
|
582
585
|
// 否则 → WSL 卷 \\wsl$\<distro>\…\volumes\<vol>\_data(= 容器 /home/cicy)。
|
|
583
586
|
ipcMain.handle("docker:open-dir", async (_e, which) => {
|
|
584
587
|
if (!APP_DOCKER_SUPPORTED) return { ok: false, error: "unsupported" };
|
|
588
|
+
// QEMU 后端没有宿主共享目录(Windows 上的 QEMU 不支持 virtio-9p/virtiofs):C:\projects 没挂进容器,
|
|
589
|
+
// 卷在虚拟机磁盘里,资源管理器打不开。如实说,别打开一个和容器无关的目录。
|
|
590
|
+
if (appDocker.backend === "qemu") return { ok: false, error: "qemu_no_host_share", hint: "QEMU 版容器的文件在虚拟机里,请在 cicy-code 里或首页 Debian 终端里访问" };
|
|
585
591
|
try {
|
|
586
592
|
const { shell } = require("electron");
|
|
587
593
|
let p;
|
package/src/i18n/locales/en.json
CHANGED
|
@@ -236,14 +236,14 @@
|
|
|
236
236
|
"menu": "Port settings",
|
|
237
237
|
"title": "Port settings",
|
|
238
238
|
"sub": "Extra ports (besides :8008) published so Windows can reach in-container services",
|
|
239
|
-
"mainFixed": ":
|
|
239
|
+
"mainFixed": ":{{p}} · cicy-code (fixed)",
|
|
240
240
|
"add": "+ Add port",
|
|
241
241
|
"ph": "Port 1-65535",
|
|
242
242
|
"save": "Save & rebuild",
|
|
243
243
|
"saving": "Saving…",
|
|
244
244
|
"cancel": "Cancel",
|
|
245
245
|
"hint": "Saving rebuilds the container (volume data kept) with a brief interruption",
|
|
246
|
-
"invalid": "Invalid port (1-65535, not
|
|
246
|
+
"invalid": "Invalid port (1-65535, not {{p}}, no duplicates)",
|
|
247
247
|
"none": "No extra ports",
|
|
248
248
|
"remove": "Remove"
|
|
249
249
|
},
|
package/src/i18n/locales/fr.json
CHANGED
|
@@ -236,14 +236,14 @@
|
|
|
236
236
|
"menu": "Réglage des ports",
|
|
237
237
|
"title": "Réglage des ports",
|
|
238
238
|
"sub": "Ports supplémentaires (hors :8008) publiés pour que Windows atteigne les services du conteneur",
|
|
239
|
-
"mainFixed": ":
|
|
239
|
+
"mainFixed": ":{{p}} · cicy-code (fixe)",
|
|
240
240
|
"add": "+ Ajouter un port",
|
|
241
241
|
"ph": "Port 1-65535",
|
|
242
242
|
"save": "Enregistrer et reconstruire",
|
|
243
243
|
"saving": "Enregistrement…",
|
|
244
244
|
"cancel": "Annuler",
|
|
245
245
|
"hint": "L'enregistrement reconstruit le conteneur (données du volume conservées), brève interruption",
|
|
246
|
-
"invalid": "Port invalide (1-65535, pas
|
|
246
|
+
"invalid": "Port invalide (1-65535, pas {{p}}, sans doublon)",
|
|
247
247
|
"none": "Aucun port supplémentaire",
|
|
248
248
|
"remove": "Retirer"
|
|
249
249
|
},
|
package/src/i18n/locales/ja.json
CHANGED
|
@@ -236,14 +236,14 @@
|
|
|
236
236
|
"menu": "ポート設定",
|
|
237
237
|
"title": "ポート設定",
|
|
238
238
|
"sub": ":8008 以外に公開し、Windows からコンテナ内サービスへ直接到達できるポート",
|
|
239
|
-
"mainFixed": ":
|
|
239
|
+
"mainFixed": ":{{p}} · cicy-code(固定)",
|
|
240
240
|
"add": "+ ポートを追加",
|
|
241
241
|
"ph": "ポート 1-65535",
|
|
242
242
|
"save": "保存して再構築",
|
|
243
243
|
"saving": "保存中…",
|
|
244
244
|
"cancel": "キャンセル",
|
|
245
245
|
"hint": "保存するとコンテナを再構築します(ボリュームのデータは保持、短時間中断)",
|
|
246
|
-
"invalid": "無効なポート(1-65535、
|
|
246
|
+
"invalid": "無効なポート(1-65535、{{p}} 不可、重複不可)",
|
|
247
247
|
"none": "追加ポートなし",
|
|
248
248
|
"remove": "削除"
|
|
249
249
|
},
|
|
@@ -209,7 +209,7 @@
|
|
|
209
209
|
"recreateOk": "确定重建",
|
|
210
210
|
"runningWslBroken": "运行中 · ⚠ WSL 管理异常(能打开;更新/重启需修复 WSL)",
|
|
211
211
|
"openFailed": "打开失败",
|
|
212
|
-
"openTitle": "
|
|
212
|
+
"openTitle": "打开本机团队",
|
|
213
213
|
"repairWsl": "修复 WSL",
|
|
214
214
|
"wslBrokenRepair": "WSL 管理异常 · 点「修复 WSL」重装",
|
|
215
215
|
"openWslDir": "打开 WSL 目录",
|
|
@@ -236,14 +236,14 @@
|
|
|
236
236
|
"menu": "端口设置",
|
|
237
237
|
"title": "端口设置",
|
|
238
238
|
"sub": "除 :8008 外,额外发布、可从 Windows 直达容器内服务的端口",
|
|
239
|
-
"mainFixed": ":
|
|
239
|
+
"mainFixed": ":{{p}} · cicy-code(固定)",
|
|
240
240
|
"add": "+ 添加端口",
|
|
241
241
|
"ph": "端口号 1-65535",
|
|
242
242
|
"save": "保存并重建",
|
|
243
243
|
"saving": "保存中…",
|
|
244
244
|
"cancel": "取消",
|
|
245
245
|
"hint": "保存会重建容器(volume 数据保留),会有短暂中断",
|
|
246
|
-
"invalid": "有端口无效(1-65535,不能是
|
|
246
|
+
"invalid": "有端口无效(1-65535,不能是 {{p}},不能重复)",
|
|
247
247
|
"none": "暂无额外端口",
|
|
248
248
|
"remove": "移除"
|
|
249
249
|
},
|
package/src/main.js
CHANGED
|
@@ -6,6 +6,9 @@ const { default: contextMenu } = require("electron-context-menu");
|
|
|
6
6
|
const fs = require("fs");
|
|
7
7
|
const os = require("os");
|
|
8
8
|
const path = require("path");
|
|
9
|
+
// 必须最先:决定 Windows 上 Docker-版 cicy-code 走 WSL(老机器)还是 QEMU(新机器 :8009),
|
|
10
|
+
// 并在任何模块读 CICY_DOCKER_APP_PORT 之前把端口定下来。见 sidecar/linux-backend.js。
|
|
11
|
+
require("./sidecar/linux-backend").init();
|
|
9
12
|
const cicyCodeSidecar = require("./sidecar/cicy-code");
|
|
10
13
|
const backendsIPC = require("./backends/ipc");
|
|
11
14
|
const { openHomepage } = require("./backends/homepage-window");
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Copyright 2026 CiCy AI
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
// Docker-版 cicy-code 的运行层(同接口):darwin → Colima,win32 → WSL2(老机器)或 QEMU(新机器)。
|
|
5
|
+
// 选哪个见 linux-backend.js。调用方一律经这里拿,别再直接 require("./wsl-docker")。
|
|
6
|
+
const backend = require("./linux-backend").backend();
|
|
7
|
+
module.exports = process.platform === "darwin" ? require("./colima-docker")
|
|
8
|
+
: backend === "qemu" ? require("./qemu-docker")
|
|
9
|
+
: require("./wsl-docker");
|
package/src/sidecar/docker.js
CHANGED
|
@@ -219,7 +219,16 @@ function headETag(url, hops = 5) {
|
|
|
219
219
|
function imageEtagPath() { return path.join(downloadsDir(), "cicy-code-latest.etag"); }
|
|
220
220
|
function readLoadedImageEtag() { try { return fs.readFileSync(imageEtagPath(), "utf8").trim(); } catch { return ""; } }
|
|
221
221
|
function writeLoadedImageEtag(v) { try { fs.writeFileSync(imageEtagPath(), String(v || "")); } catch {} }
|
|
222
|
-
|
|
222
|
+
// 国内网络到 R2 公开域名(*.de5.net)会被重置;dl.cicy-ai.com 是同一个桶经 Cloudflare Worker 的只读镜像
|
|
223
|
+
// (仓库 ~/projects/cicy-dl-mirror,ETag/Range 原样透传)。R2 HEAD 不通时一律改走它。
|
|
224
|
+
const R2_MIRROR_TARBALL = process.env.CICY_DOCKER_MIRROR_URL || "https://dl.cicy-ai.com/images/cicy-code-latest.tar.gz";
|
|
225
|
+
async function remoteImageEtag() { return (await headETag(R2_TARBALL)) || headETag(R2_MIRROR_TARBALL); }
|
|
226
|
+
// 先 HEAD 一下 R2(15s 超时),通就用 R2,不通直接换镜像 —— 别让 curl 在被重置的 R2 上重试 8 轮(每轮几十秒)。
|
|
227
|
+
async function pickTarballSource() {
|
|
228
|
+
if ((await headSize(R2_TARBALL)) > 0) return { url: R2_TARBALL, alt: R2_MIRROR_TARBALL };
|
|
229
|
+
if ((await headSize(R2_MIRROR_TARBALL)) > 0) return { url: R2_MIRROR_TARBALL, alt: null };
|
|
230
|
+
return { url: R2_TARBALL, alt: R2_MIRROR_TARBALL };
|
|
231
|
+
}
|
|
223
232
|
// Drop the cached tarball so a stale copy on disk can't be reused (downloadImageTarball
|
|
224
233
|
// skips a complete file by size, which a same-size new image would wrongly satisfy).
|
|
225
234
|
function clearImageTarball() { try { fs.unlinkSync(imageTarballPath()); } catch {} }
|
|
@@ -403,22 +412,23 @@ function curlDownload(url, dest, { emit, phase = "image", label = "下载镜像"
|
|
|
403
412
|
// (much faster here); falls back to the node downloader if curl is unavailable.
|
|
404
413
|
async function downloadImageTarball({ emit } = {}) {
|
|
405
414
|
const dest = imageTarballPath();
|
|
415
|
+
const src = await pickTarballSource(); // R2 不通(国内)→ dl.cicy-ai.com 镜像
|
|
406
416
|
// REUSE a complete tarball already on disk (staged into ~/Downloads, or a prior
|
|
407
417
|
// run) BEFORE curlDownload touches it — curlDownload doesn't skip a complete file
|
|
408
418
|
// (and a failed curl truncates it). headSize uses node http so it works even when
|
|
409
419
|
// curl can't resolve the host. Skips the ~500MB re-download per new user.
|
|
410
420
|
try {
|
|
411
|
-
const expected = await headSize(
|
|
421
|
+
const expected = await headSize(src.url);
|
|
412
422
|
const have = fs.statSync(dest).size;
|
|
413
423
|
if (expected > 0 && have === expected) {
|
|
414
424
|
emit && emit({ phase: "image", status: "skip", message: "镜像:已有完整包,跳过下载", progress: 100, received: have, total: expected });
|
|
415
425
|
return dest;
|
|
416
426
|
}
|
|
417
427
|
} catch {}
|
|
418
|
-
try { await curlDownload(
|
|
428
|
+
try { await curlDownload(src.url, dest, { emit, phase: "image", label: "下载镜像" }); }
|
|
419
429
|
catch (e) {
|
|
420
430
|
emit && emit({ phase: "image", status: "running", message: `curl 下载失败(${e.message}),改用内置下载…` });
|
|
421
|
-
await ensureDownloaded(
|
|
431
|
+
await ensureDownloaded(src.url, dest, src.alt, { emit, phase: "image", label: "下载镜像" });
|
|
422
432
|
}
|
|
423
433
|
return dest;
|
|
424
434
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Copyright 2026 CiCy AI
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
// Windows 上 Docker-版 cicy-code 跑在哪:WSL2(老机器)还是 QEMU(新机器,不再用 WSL)。
|
|
5
|
+
//
|
|
6
|
+
// 规则:2026-10-21 起新机器走 QEMU、端口 8009;2026-10-22 用户定「所有的机器全部用 QEMU」——
|
|
7
|
+
// 新判定一律 qemu(不再看机器上有没有 WSL)。已经落盘的判定不自动改:老机器的 wsl 由人逐台迁
|
|
8
|
+
// (停 WSL 容器 → 改 linux-backend.json → 重启 desktop),避免一升级就整队断服。
|
|
9
|
+
// 判定结果写进 ~/cicy-ai/db/linux-backend.json。CICY_LINUX_BACKEND=wsl|qemu 可强制覆盖。
|
|
10
|
+
//
|
|
11
|
+
// 必须在 main.js 最早处 init():sidecar-ipc 等模块在 require 时就读 CICY_DOCKER_APP_PORT。
|
|
12
|
+
|
|
13
|
+
const fs = require("fs");
|
|
14
|
+
const os = require("os");
|
|
15
|
+
const path = require("path");
|
|
16
|
+
const { execFileSync } = require("child_process");
|
|
17
|
+
|
|
18
|
+
const FILE = path.join(os.homedir(), "cicy-ai", "db", "linux-backend.json");
|
|
19
|
+
const DISTRO = process.env.CICY_WSL_DISTRO || "cicy-code-wsl";
|
|
20
|
+
const QEMU_PORT = 8009;
|
|
21
|
+
let _backend = null;
|
|
22
|
+
|
|
23
|
+
// 老机器的证据:WSL 注册表里有我们的发行版,或者之前算过的 docker 状态说装过。只读,不碰 wsl.exe。
|
|
24
|
+
function hasWslInstall() {
|
|
25
|
+
try {
|
|
26
|
+
execFileSync("reg", ["query", "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Lxss", "/s", "/f", DISTRO, "/d", "/e"], { windowsHide: true, timeout: 8000, stdio: "ignore" });
|
|
27
|
+
return true;
|
|
28
|
+
} catch {}
|
|
29
|
+
try {
|
|
30
|
+
const j = JSON.parse(fs.readFileSync(path.join(os.homedir(), "cicy-ai", "db", "docker-status.json"), "utf8"));
|
|
31
|
+
if (j && (j.installed || j.running) && Number(j.port || 8008) === 8008) return true;
|
|
32
|
+
} catch {}
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function decide() {
|
|
37
|
+
if (process.platform !== "win32") return "native";
|
|
38
|
+
const forced = String(process.env.CICY_LINUX_BACKEND || "").toLowerCase();
|
|
39
|
+
if (forced === "wsl" || forced === "qemu") return forced;
|
|
40
|
+
try { const j = JSON.parse(fs.readFileSync(FILE, "utf8")); if (j && (j.backend === "wsl" || j.backend === "qemu")) return j.backend; } catch {}
|
|
41
|
+
const b = "qemu";
|
|
42
|
+
try { fs.mkdirSync(path.dirname(FILE), { recursive: true }); fs.writeFileSync(FILE, JSON.stringify({ backend: b, decidedAt: new Date().toISOString(), hadWsl: hasWslInstall() }, null, 2)); } catch {}
|
|
43
|
+
return b;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function init() {
|
|
47
|
+
if (_backend) return _backend;
|
|
48
|
+
_backend = decide();
|
|
49
|
+
if (_backend === "qemu" && !process.env.CICY_DOCKER_APP_PORT) process.env.CICY_DOCKER_APP_PORT = String(QEMU_PORT);
|
|
50
|
+
if (_backend === "qemu" && !process.env.CICY_SIDECAR_PORT) process.env.CICY_SIDECAR_PORT = process.env.CICY_DOCKER_APP_PORT;
|
|
51
|
+
return _backend;
|
|
52
|
+
}
|
|
53
|
+
function backend() { return _backend || init(); }
|
|
54
|
+
|
|
55
|
+
module.exports = { init, backend, QEMU_PORT, hasWslInstall };
|