cicy-desktop 2.1.159 → 2.1.161
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 +1 -1
- package/src/backends/homepage-preload.js +3 -3
- package/src/backends/homepage-react/assets/{index-CKQp3xSO.js → index-BQDe_2_G.js} +6 -6
- package/src/backends/homepage-react/index.html +1 -1
- package/src/backends/local-teams.js +8 -8
- package/src/backends/sidecar-ipc.js +43 -69
- package/src/i18n/locales/en.json +1 -1
- package/src/i18n/locales/fr.json +1 -1
- package/src/i18n/locales/ja.json +1 -1
- package/src/i18n/locales/zh-CN.json +1 -1
- package/src/main.js +3 -5
- package/src/sidecar/cicy-code.js +3 -7
- package/src/sidecar/colima-docker.js +13 -13
- package/src/sidecar/docker.js +1 -1
- package/src/sidecar/wsl-docker.js +18 -18
- package/workers/render/src/App.jsx +15 -15
|
@@ -6,7 +6,7 @@
|
|
|
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-
|
|
9
|
+
<script type="module" crossorigin src="./assets/index-BQDe_2_G.js"></script>
|
|
10
10
|
<link rel="stylesheet" crossorigin href="./assets/index-Cuc3w0O-.css">
|
|
11
11
|
</head>
|
|
12
12
|
<body>
|
|
@@ -224,7 +224,7 @@ async function openTeam(id, opts = {}) {
|
|
|
224
224
|
if (!node) return { ok: false, error: "team not found" };
|
|
225
225
|
const baseUrl = (node.base_url || "").replace(/\/$/, "");
|
|
226
226
|
if (!baseUrl) return { ok: false, error: "no base_url" };
|
|
227
|
-
// opts.token (a LIVE-read token, e.g. the :
|
|
227
|
+
// opts.token (a LIVE-read token, e.g. the :8008 container's own) takes
|
|
228
228
|
// precedence over any stored token — the Docker team stores none.
|
|
229
229
|
const token = (opts && opts.token) || node.api_token || "";
|
|
230
230
|
const url = token ? `${baseUrl}/?token=${encodeURIComponent(token)}` : baseUrl;
|
|
@@ -439,7 +439,7 @@ async function syncNameToCloud(id) {
|
|
|
439
439
|
// cloud_team_id 被覆盖、又和 8008 串名。按 is_docker 标记 OR 端口(docker app port)
|
|
440
440
|
// 跳过(端口判断不依赖标记的写入时机,更稳)。
|
|
441
441
|
{
|
|
442
|
-
const DOCKER_PORT = String(process.env.CICY_DOCKER_APP_PORT ||
|
|
442
|
+
const DOCKER_PORT = String(process.env.CICY_DOCKER_APP_PORT || 8008);
|
|
443
443
|
let isDockerNode = !!node.is_docker;
|
|
444
444
|
try { if (new URL(node.base_url).port === DOCKER_PORT) isDockerNode = true; } catch {}
|
|
445
445
|
if (isDockerNode) return;
|
|
@@ -524,10 +524,10 @@ async function addTeam(spec) {
|
|
|
524
524
|
// pass it, leaving the swap URL with no `?token=` and stranding the user
|
|
525
525
|
// at a login screen. Auto-fill from local global.json (top-level api_token)
|
|
526
526
|
// so the common case "Just Works", even when spec.api_token is empty.
|
|
527
|
-
// skipTokenAutofill: the :
|
|
528
|
-
// is read LIVE from the container on every open (主人: teams.json 不存
|
|
527
|
+
// skipTokenAutofill: the :8008 Docker team must NEVER store a token — its token
|
|
528
|
+
// is read LIVE from the container on every open (主人: teams.json 不存 8008 的
|
|
529
529
|
// token / docker 的 token 是实时拿的). Without this guard the auto-fill below
|
|
530
|
-
// back-fills the HOST 8008 token, which
|
|
530
|
+
// back-fills the HOST 8008 token, which 8008 rejects → endless login screen.
|
|
531
531
|
if (!spec.api_token && !spec.skipTokenAutofill) {
|
|
532
532
|
try {
|
|
533
533
|
const host = new URL(baseUrl).hostname;
|
|
@@ -567,19 +567,19 @@ async function addTeam(spec) {
|
|
|
567
567
|
// addTeam fires syncNameToCloud(id) below (fire-and-forget). If is_docker isn't
|
|
568
568
|
// already on the node, that first sync sees a plain local team, device-registers
|
|
569
569
|
// it, and the cloud hands back THIS DEVICE's shared team (= the 8008 team) — so
|
|
570
|
-
// :
|
|
570
|
+
// :8008 and :8008 end up on one cloud_team_id and renaming one renames both
|
|
571
571
|
// ("串名"). Marking is_docker here (explicit spec OR by the docker app port, which
|
|
572
572
|
// doesn't depend on the caller setting a flag) closes that window for ALL creation
|
|
573
573
|
// paths (sidecar registerAppTeam, a cloud deeplink, a manual add). cloud_team_id
|
|
574
574
|
// (the node's OWN independent team) is written the same atomic way when known.
|
|
575
|
-
const DOCKER_PORT = String(process.env.CICY_DOCKER_APP_PORT ||
|
|
575
|
+
const DOCKER_PORT = String(process.env.CICY_DOCKER_APP_PORT || 8008);
|
|
576
576
|
const isDockerNode = !!spec.is_docker || (port != null && String(port) === DOCKER_PORT);
|
|
577
577
|
|
|
578
578
|
const now = new Date().toISOString();
|
|
579
579
|
const patch = {
|
|
580
580
|
name: spec.name !== undefined ? String(spec.name || unnamedName()) : undefined,
|
|
581
581
|
base_url: baseUrl,
|
|
582
|
-
// skipTokenAutofill → force-clear any stored token (Docker :
|
|
582
|
+
// skipTokenAutofill → force-clear any stored token (Docker :8008 reads live).
|
|
583
583
|
api_token: spec.skipTokenAutofill ? "" : (spec.api_token !== undefined ? String(spec.api_token || "") : undefined),
|
|
584
584
|
install_source: spec.install_source ?? undefined,
|
|
585
585
|
install_os: spec.install_os ?? undefined,
|
|
@@ -33,30 +33,16 @@ const APP_DOCKER_SUPPORTED = process.platform === "win32" || process.platform ==
|
|
|
33
33
|
|
|
34
34
|
const PORT = Number(process.env.CICY_CODE_PORT || 8008);
|
|
35
35
|
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
// entire container state survives recreation (主人: "把整个 docker 挂出来").
|
|
42
|
-
// macOS is DOCKER-ONLY (主人指令: native 退役 — native 跑在宿主机无隔离会动用户数据).
|
|
43
|
-
// So on darwin the docker cicy-code IS the PRIMARY on :8008 (the slot the rest of the app
|
|
44
|
-
// already talks to; native no longer spawns there — see src/sidecar/cicy-code.js). The
|
|
45
|
-
// existing daemon/reconcile/ensureDockerTeam/appOpts machinery below just retargets to 8008
|
|
46
|
-
// — independent cloud team key, named-volume isolation, auto-start all come for free.
|
|
47
|
-
// win32 keeps the Docker-版 as an optional 2nd instance on :8009 alongside native :8008 (next).
|
|
48
|
-
const APP_PORT = Number(process.env.CICY_DOCKER_APP_PORT || (process.platform === "darwin" ? 8008 : 8009));
|
|
49
|
-
// container / volume 名都带上 port —— 一台机可以跑多个 docker(不同端口),各自
|
|
50
|
-
// 独立容器 + 独立 volume(数据隔离)+ 独立云端 team。docker-teams.json 也按 volume
|
|
51
|
-
// (含 port)区分。
|
|
36
|
+
// docker-only(主人: native 退役):cicy-code 只在 docker 容器里跑,:8008 就是它 —— 全
|
|
37
|
+
// app 都跟 :8008 说话。darwin=Colima / win32=WSL2,数据在命名卷里隔离(不动用户文件)。
|
|
38
|
+
// homepage 的 docker 卡负责首次安装(缺 colima/wsl 时引导装)+ 状态。一台机可跑多个
|
|
39
|
+
// docker(不同端口),各自独立容器 + 卷 + 云端 team(teams.json 里各自的 node 条目)。
|
|
40
|
+
const APP_PORT = Number(process.env.CICY_DOCKER_APP_PORT || 8008);
|
|
52
41
|
const APP_CONTAINER = process.env.CICY_DOCKER_APP_CONTAINER || `cicy-code-docker-${APP_PORT}`;
|
|
53
42
|
const APP_VOLUME = process.env.CICY_DOCKER_APP_VOLUME || `cicy-team-${APP_PORT}`;
|
|
54
43
|
const APP_MOUNT = process.env.CICY_DOCKER_APP_MOUNT || "/home/cicy";
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
// global.json providers (NOT the api_token, which is only the local access
|
|
58
|
-
// credential). 8008 is up by the time the Docker card is used, so the key is
|
|
59
|
-
// ready — we just read it and pass it to the container. Same key ⇒ same billing.
|
|
44
|
+
// 容器的 LLM 网关 key:优先 ensureDockerTeam 缓存的本 docker team 的 apiKey,回退本机
|
|
45
|
+
// global.json 里的 gateway key(readLocalGatewayKey),保证容器始终有 LLM key 可用。
|
|
60
46
|
const GATEWAY_ENDPOINT = process.env.CICY_AI_GATEWAY_LLM_ENDPOINT || "https://gateway.cicy-ai.com";
|
|
61
47
|
function readLocalGatewayKey() {
|
|
62
48
|
try {
|
|
@@ -71,48 +57,36 @@ function readLocalGatewayKey() {
|
|
|
71
57
|
} catch { return ""; }
|
|
72
58
|
}
|
|
73
59
|
|
|
74
|
-
// ── Docker
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
// docker
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
// 账单 + 改名(PATCH /api/teams/:id,和私有云卡同一套)。
|
|
60
|
+
// ── Docker 云端 team(统一到 teams.json)─────────────────────────────────────
|
|
61
|
+
// docker-only(主人): native 退役,docker cicy-code 就是 :8008 本地团队。一机多 docker
|
|
62
|
+
// = 不同端口 = teams.json 里不同 node 条目(127-0-0-1-<port>),各自 cloud_team_id。
|
|
63
|
+
// 不再单独存 docker-teams.json —— teamId 统一落在 teams.json 的 node.cloud_team_id 上
|
|
64
|
+
// (由 registerAppTeam 的 addTeam 传 cloud_team_id 写盘,和 node 同一次 writeNodes)。
|
|
65
|
+
// 这里只:读已有 teamId / 没有就 POST /api/teams 建一个 + 取网关 key,缓存给 appOpts。
|
|
81
66
|
const cc = (() => { try { return require("../cloud/cloud-client"); } catch { return null; } })();
|
|
82
|
-
const
|
|
83
|
-
function
|
|
84
|
-
function writeDockerTeams(obj) { try { fs.mkdirSync(path.dirname(DOCKER_TEAMS_FILE), { recursive: true }); fs.writeFileSync(DOCKER_TEAMS_FILE, JSON.stringify(obj, null, 2)); } catch {} }
|
|
67
|
+
const TEAMS_JSON = path.join(os.homedir(), "cicy-ai", "db", "teams.json");
|
|
68
|
+
function readTeamsJson() { try { return JSON.parse(fs.readFileSync(TEAMS_JSON, "utf8")) || {}; } catch { return {}; } }
|
|
85
69
|
let dockerTeamReg = null; // { teamId, title, apiKey } — 缓存,appOpts 读它
|
|
86
|
-
|
|
87
|
-
// 「Docker 团队」(那是第二实例 :8009 用的)。docker-only 下它是唯一/主团队。
|
|
88
|
-
const APP_TEAM_TITLE = (APP_PORT === PORT) ? "本地团队" : "Docker 团队";
|
|
70
|
+
const APP_TEAM_TITLE = "本地团队"; // 只有一个 cicy-code(:8008)= 本地团队
|
|
89
71
|
|
|
90
|
-
//
|
|
72
|
+
// 确保本 docker(按 node = 127-0-0-1-<APP_PORT>,支持一机多 docker)有云端 team;返回
|
|
73
|
+
// { teamId, title, apiKey } 或 null。READ-ONLY teams.json —— teamId 的落盘走
|
|
74
|
+
// registerAppTeam 的 addTeam,避免和 local-teams 的 writeNodes 抢写。
|
|
91
75
|
async function ensureDockerTeam() {
|
|
92
|
-
if (!cc || !cc.loginToken || !cc.loginToken()) return null; // 未登录 → appOpts
|
|
76
|
+
if (!cc || !cc.loginToken || !cc.loginToken()) return null; // 未登录 → appOpts 回退本地 key
|
|
93
77
|
try {
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
78
|
+
const nodeKey = `127-0-0-1-${APP_PORT}`; // 本 docker 在 teams.json 的 node key
|
|
79
|
+
const node = readTeamsJson()[nodeKey];
|
|
80
|
+
const teamId = node && node.cloud_team_id;
|
|
81
|
+
if (!teamId) {
|
|
97
82
|
const created = await cc.createTeam({ title: APP_TEAM_TITLE, kind: "cloud" });
|
|
98
83
|
if (!created || !created.ok) return null;
|
|
99
|
-
|
|
100
|
-
// 回退成 owner/device 名,卡片就显示错了。PATCH 一下盖掉。
|
|
101
|
-
try { await cc.renameTeam(created.teamId, APP_TEAM_TITLE); } catch {}
|
|
102
|
-
store[APP_VOLUME] = { teamId: created.teamId, title: APP_TEAM_TITLE, titleForced: true };
|
|
103
|
-
writeDockerTeams(store);
|
|
84
|
+
try { await cc.renameTeam(created.teamId, APP_TEAM_TITLE); } catch {} // 盖掉云端 owner/device 回退名
|
|
104
85
|
dockerTeamReg = { teamId: created.teamId, title: APP_TEAM_TITLE, apiKey: created.apiKey };
|
|
105
|
-
return dockerTeamReg;
|
|
86
|
+
return dockerTeamReg; // teamId 由 registerAppTeam.addTeam(cloud_team_id) 写进 teams.json
|
|
106
87
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if (!rec.titleForced) {
|
|
110
|
-
try { await cc.renameTeam(rec.teamId, APP_TEAM_TITLE); } catch {}
|
|
111
|
-
rec.title = APP_TEAM_TITLE; rec.titleForced = true;
|
|
112
|
-
store[APP_VOLUME] = rec; writeDockerTeams(store);
|
|
113
|
-
}
|
|
114
|
-
const apiKey = await cc.getTeamApiKey(rec.teamId);
|
|
115
|
-
dockerTeamReg = { teamId: rec.teamId, title: rec.title, apiKey };
|
|
88
|
+
const apiKey = await cc.getTeamApiKey(teamId);
|
|
89
|
+
dockerTeamReg = { teamId, title: (node.name || APP_TEAM_TITLE), apiKey };
|
|
116
90
|
return dockerTeamReg;
|
|
117
91
|
} catch (e) { return null; }
|
|
118
92
|
}
|
|
@@ -146,12 +120,12 @@ function register({ sidecarLogPath } = {}) {
|
|
|
146
120
|
_dockerDaemonBusy = true;
|
|
147
121
|
try {
|
|
148
122
|
const s = await refreshDockerStatus();
|
|
149
|
-
// 没启动的给我启动: distro installed but :
|
|
123
|
+
// 没启动的给我启动: distro installed but :8008 not healthy (and WSL not unknown)
|
|
150
124
|
// → bring it up. bootstrap is idempotent: it skips done steps and just runs
|
|
151
125
|
// startEngine + the container. Skip when not installed (would silently pull
|
|
152
126
|
// the 444MB rootfs) or unknown (WSL not answering — let the next tick retry).
|
|
153
127
|
if (s.installed && !s.running && !s.unknown) {
|
|
154
|
-
log.info("[docker-daemon] installed but :
|
|
128
|
+
log.info("[docker-daemon] installed but :8008 down → auto-starting (bootstrap idempotent)");
|
|
155
129
|
try { await appDocker.bootstrap(appOpts()); } catch (e) { log.warn(`[docker-daemon] auto-start failed: ${e.message}`); }
|
|
156
130
|
await refreshDockerStatus();
|
|
157
131
|
}
|
|
@@ -220,8 +194,8 @@ function register({ sidecarLogPath } = {}) {
|
|
|
220
194
|
}
|
|
221
195
|
});
|
|
222
196
|
|
|
223
|
-
// ---- Docker-版 cicy-code on :
|
|
224
|
-
// Card states (主人: 状态分清楚): running(:
|
|
197
|
+
// ---- Docker-版 cicy-code on :8008 — WSL2 + Ubuntu + Docker Engine (方案 A) ----
|
|
198
|
+
// Card states (主人: 状态分清楚): running(:8008 healthy)→打开 / dockerRunning
|
|
225
199
|
// (engine up)→启动 / installed(Ubuntu present)→启动 Docker / else→下载安装.
|
|
226
200
|
ipcMain.handle("docker:app-status", () => {
|
|
227
201
|
// NON-BLOCKING: return what the background daemon already computed (memory →
|
|
@@ -243,7 +217,7 @@ function register({ sidecarLogPath } = {}) {
|
|
|
243
217
|
return s;
|
|
244
218
|
});
|
|
245
219
|
|
|
246
|
-
// Common run options for the :
|
|
220
|
+
// Common run options for the :8008 instance: its own container/volume + the
|
|
247
221
|
// LLM gateway env keyed by the 8008 team's token. (WSL: whole-home mount via
|
|
248
222
|
// -v <volume>:/home/cicy inside wsl-docker.)
|
|
249
223
|
const appOpts = () => {
|
|
@@ -254,24 +228,24 @@ function register({ sidecarLogPath } = {}) {
|
|
|
254
228
|
if (gwKey) env.CICY_AI_GATEWAY_LLM_API_KEY = gwKey;
|
|
255
229
|
return { port: APP_PORT, container: APP_CONTAINER, volume: APP_VOLUME, env };
|
|
256
230
|
};
|
|
257
|
-
// Register the running :
|
|
231
|
+
// Register the running :8008 instance as a (custom) team so the card's "打开"
|
|
258
232
|
// reuses the token-injected open/reload flow. addTeam dedups by host:port.
|
|
259
|
-
// Upsert the :
|
|
233
|
+
// Upsert the :8008 team with the CONTAINER's OWN live token. Critical: never
|
|
260
234
|
// fall back to the host 8008 token (addTeam auto-fills global.json on an empty
|
|
261
|
-
// api_token — that's the host credential, which
|
|
235
|
+
// api_token — that's the host credential, which 8008 rejects → login screen).
|
|
262
236
|
// Returns the team id, or {ok:false} when the container token can't be read.
|
|
263
|
-
// Register the :
|
|
237
|
+
// Register the :8008 team WITHOUT a token. 主人: teams.json 不存 8008 的 token;
|
|
264
238
|
// docker 的 token 是实时拿的. skipTokenAutofill stops addTeam from back-filling
|
|
265
|
-
// the HOST 8008 token (the bug that made
|
|
239
|
+
// the HOST 8008 token (the bug that made 8008 verify with 8008's token → login).
|
|
266
240
|
const registerAppTeam = async () => {
|
|
267
241
|
const lt = require("./local-teams");
|
|
268
242
|
await ensureDockerTeam(); // 确保独立云端 team 存在 + 拿到 teamId/title
|
|
269
|
-
const title = (dockerTeamReg && dockerTeamReg.title) ||
|
|
243
|
+
const title = (dockerTeamReg && dockerTeamReg.title) || APP_TEAM_TITLE;
|
|
270
244
|
// is_docker + cloud_team_id are passed INTO addTeam so they land in the SAME
|
|
271
245
|
// writeNodes as the node — before addTeam's fire-and-forget syncNameToCloud
|
|
272
|
-
// runs. That's what stops the freshly-created :
|
|
246
|
+
// runs. That's what stops the freshly-created :8008 node from device-registering
|
|
273
247
|
// into THIS device's shared (8008) team and 串名. (addTeam also self-detects
|
|
274
|
-
// is_docker by the :
|
|
248
|
+
// is_docker by the :8008 port, so this is belt-and-suspenders.)
|
|
275
249
|
const r = await lt.addTeam({
|
|
276
250
|
base_url: `http://127.0.0.1:${APP_PORT}`,
|
|
277
251
|
name: title,
|
|
@@ -305,7 +279,7 @@ function register({ sidecarLogPath } = {}) {
|
|
|
305
279
|
});
|
|
306
280
|
|
|
307
281
|
// One-click bootstrap (方案 A): ensure WSL2 → Ubuntu → Docker Engine → load
|
|
308
|
-
// image → start :
|
|
282
|
+
// image → start :8008 container → health. Streams phase/progress on
|
|
309
283
|
// 'docker:app-progress'. Idempotent + resumable → the modal's 重试 just re-runs.
|
|
310
284
|
ipcMain.handle("docker:app-bootstrap", async (e) => {
|
|
311
285
|
if (!APP_DOCKER_SUPPORTED) return { ok: false, error: "Docker cicy-code 仅支持 Windows / macOS" };
|
|
@@ -363,7 +337,7 @@ function register({ sidecarLogPath } = {}) {
|
|
|
363
337
|
catch (e) { return { ok: false, error: e.message }; }
|
|
364
338
|
});
|
|
365
339
|
|
|
366
|
-
// ⋯ menu → 升级: re-pull the latest R2 image, re-create the :
|
|
340
|
+
// ⋯ menu → 升级: re-pull the latest R2 image, re-create the :8008 container.
|
|
367
341
|
ipcMain.handle("docker:app-upgrade", async (e) => {
|
|
368
342
|
if (!APP_DOCKER_SUPPORTED) return { ok: false, error: "Docker cicy-code 仅支持 Windows / macOS" };
|
|
369
343
|
try {
|
package/src/i18n/locales/en.json
CHANGED
|
@@ -126,7 +126,7 @@
|
|
|
126
126
|
"title": "Docker cicy-code",
|
|
127
127
|
"install": "Install",
|
|
128
128
|
"start": "Start",
|
|
129
|
-
"running": "Running · :
|
|
129
|
+
"running": "Running · :8008",
|
|
130
130
|
"notRunning": "Stopped · click Start",
|
|
131
131
|
"notInstalled": "Not installed · click Install",
|
|
132
132
|
"working": "Working…",
|
package/src/i18n/locales/fr.json
CHANGED
|
@@ -125,7 +125,7 @@
|
|
|
125
125
|
"title": "Docker cicy-code",
|
|
126
126
|
"install": "Installer",
|
|
127
127
|
"start": "Démarrer",
|
|
128
|
-
"running": "En cours · :
|
|
128
|
+
"running": "En cours · :8008",
|
|
129
129
|
"notRunning": "Arrêté · cliquez sur Démarrer",
|
|
130
130
|
"notInstalled": "Non installé · cliquez pour installer",
|
|
131
131
|
"working": "En cours…",
|
package/src/i18n/locales/ja.json
CHANGED
package/src/main.js
CHANGED
|
@@ -817,11 +817,9 @@ X-GNOME-Autostart-enabled=true
|
|
|
817
817
|
let _sidecarWatchdogTimer = null;
|
|
818
818
|
function startSidecarWatchdog({ intervalMs = 30_000 } = {}) {
|
|
819
819
|
if (_sidecarWatchdogTimer) return;
|
|
820
|
-
//
|
|
821
|
-
//
|
|
822
|
-
|
|
823
|
-
// would just spam "sidecar unreachable → restart" every tick. Skip it entirely on darwin.
|
|
824
|
-
if (process.platform === "darwin") return;
|
|
820
|
+
// 主人: native 退役,全平台只有 docker。:8008 由 docker daemon(sidecar-ipc reconcile)
|
|
821
|
+
// + 容器 --restart 保活,没有 native 要 watchdog 去检查/重启。直接不跑。
|
|
822
|
+
return;
|
|
825
823
|
let consecutiveFailures = 0;
|
|
826
824
|
let restartInFlight = false;
|
|
827
825
|
|
package/src/sidecar/cicy-code.js
CHANGED
|
@@ -100,13 +100,9 @@ async function startFromRuntime({ logPath, port }) {
|
|
|
100
100
|
async function start({ logPath, port = DEFAULT_PORT, force = false, version = null } = {}) {
|
|
101
101
|
if (child && !force) return child;
|
|
102
102
|
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
|
|
106
|
-
// primary on :8008, brought up + kept alive by the docker daemon in src/backends/
|
|
107
|
-
// sidecar-ipc.js (APP_PORT=8008 on darwin). probeExisting()/watchdog still work — they
|
|
108
|
-
// just observe the container's :8008. Returning null here = "no native child".
|
|
109
|
-
if (process.platform === "darwin") return null;
|
|
103
|
+
// 主人: native 退役,全平台 docker-only。cicy-code 只在 docker 容器里跑(:8008,命名卷
|
|
104
|
+
// 隔离,sidecar-ipc 的 docker daemon 负责拉起 + 保活)。这里不再起任何 native。
|
|
105
|
+
return null;
|
|
110
106
|
|
|
111
107
|
if (!force && await probeExisting(port)) {
|
|
112
108
|
console.log(`[cicy-code-sidecar] existing instance on :${port}, reusing`);
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// Docker 引擎」的方案,在 Mac 上就是 Colima:一个 Apache-2.0、纯 CLI、无 GUI、
|
|
5
5
|
// 无授权、无 root 的轻量 Linux VM(基于 Lima),里面跑标准 Docker 引擎。Lima 会
|
|
6
6
|
// 把 VM 内监听 127.0.0.1 的端口自动转发到宿主 127.0.0.1,所以 VM 里发布在
|
|
7
|
-
// :
|
|
7
|
+
// :8008 的容器,Mac 上 127.0.0.1:8008 直接可达——和 WSL2 的 localhost 转发等价。
|
|
8
8
|
//
|
|
9
9
|
// 与 wsl-docker.js 完全同接口(bootstrap/status/restart/stop/dockerRestart/
|
|
10
10
|
// recreate/update/upgrade/runContainer/readContainerToken),由 sidecar-ipc.js
|
|
@@ -220,7 +220,7 @@ const probeHealth = docker.probeHealth;
|
|
|
220
220
|
// • Apple Silicon 加 --platform linux/amd64(rosetta 跑 x86 容器);
|
|
221
221
|
// • /home/cicy 必须用 docker **named volume**,不能用 host bind-mount!真机实测:
|
|
222
222
|
// bind-mount 会用空的宿主目录**遮住镜像里预装的 /home/cicy**(cicy-code 装在那),
|
|
223
|
-
// entrypoint 找不到就试图全局 npm 重装 → EACCES 崩溃,:
|
|
223
|
+
// entrypoint 找不到就试图全局 npm 重装 → EACCES 崩溃,:8008 起不来。named volume
|
|
224
224
|
// 首次挂载会**从镜像内容预填充**,容器才看得到预装的 cicy-code(和 WSL 一致)。
|
|
225
225
|
// Shared folder bind: the CURRENT macOS user's ~/Desktop/Share ↔
|
|
226
226
|
// /home/cicy/cicy-ai/Share in the container. Auto-created (with a readme) on every
|
|
@@ -255,7 +255,7 @@ function shareMountArg() {
|
|
|
255
255
|
} catch { return ""; }
|
|
256
256
|
}
|
|
257
257
|
|
|
258
|
-
async function runContainer({ port =
|
|
258
|
+
async function runContainer({ port = 8008, container = "cicy-code-docker-8008", volume = "cicy-team-8008", env = {} } = {}) {
|
|
259
259
|
if (await probeHealth(port)) return { adopted: true };
|
|
260
260
|
try { await dk(`rm -f ${container}`, { timeout: 20000 }); } catch {} // 替换同名残留容器
|
|
261
261
|
const envArgs = Object.entries(env || {})
|
|
@@ -273,7 +273,7 @@ async function runContainer({ port = 8009, container = "cicy-code-docker-8009",
|
|
|
273
273
|
// 该卷的 _data/cicy-ai/global.json(最稳,避开 busy 容器的慢 exec);② 退回 docker
|
|
274
274
|
// exec。重试到 entrypoint 把 global.json 写出来为止;真读不到返回 ""(调用方不得拿
|
|
275
275
|
// 错/宿主 token 去开,会卡登录)。
|
|
276
|
-
async function readContainerToken(port =
|
|
276
|
+
async function readContainerToken(port = 8008, container = "cicy-code-docker-8008", volume = "cicy-team-8008") {
|
|
277
277
|
for (let attempt = 1; attempt <= 5; attempt++) {
|
|
278
278
|
try { const { stdout } = await sh(`colima ssh -p ${PROFILE} -- sudo cat /var/lib/docker/volumes/${volume}/_data/cicy-ai/global.json 2>/dev/null`, { timeout: 10000 });
|
|
279
279
|
const m = String(stdout).match(/"api_token"\s*:\s*"(cicy_[A-Za-z0-9]+)"/);
|
|
@@ -324,7 +324,7 @@ async function ensureDesktopShortcut(_volume, _port) { /* no-op on darwin: named
|
|
|
324
324
|
// 与 wsl status 同形 { wsl, distro, engineUp, running },供 sidecar-ipc 复用:
|
|
325
325
|
// wsl → 平台/依赖就绪(brew+colima+docker CLI 都在)
|
|
326
326
|
// distro → VM(profile)已创建
|
|
327
|
-
async function status(port =
|
|
327
|
+
async function status(port = 8008) {
|
|
328
328
|
const deps = (await colimaInstalled()) && (await dockerCliInstalled());
|
|
329
329
|
const vm = deps && (await vmExists());
|
|
330
330
|
const up = vm && (await engineUp());
|
|
@@ -344,7 +344,7 @@ async function bootstrap(opts = {}) {
|
|
|
344
344
|
return _bootstrapInFlight;
|
|
345
345
|
}
|
|
346
346
|
|
|
347
|
-
async function _bootstrap({ onProgress, port =
|
|
347
|
+
async function _bootstrap({ onProgress, port = 8008, container = "cicy-code-docker-8008", volume = "cicy-team-8008", env = {} } = {}) {
|
|
348
348
|
const emit = (ev) => { try { onProgress && onProgress(ev); } catch {} };
|
|
349
349
|
|
|
350
350
|
// 0) 快路径:已健康 → 秒返回(幂等)。
|
|
@@ -396,7 +396,7 @@ async function _bootstrap({ onProgress, port = 8009, container = "cicy-code-dock
|
|
|
396
396
|
// ---- 生命周期(卡片 ⋯ 菜单)--------------------------------------------
|
|
397
397
|
|
|
398
398
|
// 仅重启容器内的 cicy-code 进程(supervisor),cron/sshd 等不动;退回整容器重启。
|
|
399
|
-
async function restart({ container = "cicy-code-docker-
|
|
399
|
+
async function restart({ container = "cicy-code-docker-8008", port = 8008, volume = "cicy-team-8008" } = {}) {
|
|
400
400
|
await startVM({});
|
|
401
401
|
try { await dk(`exec ${container} supervisorctl -c /etc/supervisor/supervisord.conf restart cicy-code`, { timeout: 30000 }); }
|
|
402
402
|
catch { try { await dk(`restart ${container}`, { timeout: 60000 }); } catch {} }
|
|
@@ -406,7 +406,7 @@ async function restart({ container = "cicy-code-docker-8009", port = 8009, volum
|
|
|
406
406
|
}
|
|
407
407
|
|
|
408
408
|
// 原地更新 cicy-code(镜像内的 cicy-code-update.sh,不重建容器)。
|
|
409
|
-
async function update({ onProgress, container = "cicy-code-docker-
|
|
409
|
+
async function update({ onProgress, container = "cicy-code-docker-8008", port = 8008 } = {}) {
|
|
410
410
|
const emit = (ev) => { try { onProgress && onProgress(ev); } catch {} };
|
|
411
411
|
await startVM({});
|
|
412
412
|
emit({ phase: "image", status: "running", message: "更新 cicy-code(拉取最新版)…" });
|
|
@@ -415,23 +415,23 @@ async function update({ onProgress, container = "cicy-code-docker-8009", port =
|
|
|
415
415
|
{ emit, phase: "image", timeout: 300000 });
|
|
416
416
|
} catch (e) { emit({ phase: "done", status: "error", message: `更新失败:${e.message}(试试「升级」重装)` }); return { ok: false, reason: "update_failed" }; }
|
|
417
417
|
const healthy = await docker.waitUntil(() => probeHealth(port), { totalMs: 120000, everyMs: 3000 });
|
|
418
|
-
emit({ phase: "done", status: healthy ? "done" : "error", message: healthy ? "cicy-code 已更新到最新 🎉" : "更新了但 :
|
|
418
|
+
emit({ phase: "done", status: healthy ? "done" : "error", message: healthy ? "cicy-code 已更新到最新 🎉" : "更新了但 :8008 还没响应——稍等或点重试" });
|
|
419
419
|
return { ok: healthy };
|
|
420
420
|
}
|
|
421
421
|
|
|
422
|
-
async function stop({ container = "cicy-code-docker-
|
|
422
|
+
async function stop({ container = "cicy-code-docker-8008" } = {}) {
|
|
423
423
|
try { await dk(`stop ${container}`, { timeout: 30000 }); } catch {}
|
|
424
424
|
}
|
|
425
425
|
|
|
426
426
|
// docker restart 整个容器(entrypoint 重跑、重读 volume global.json)。
|
|
427
|
-
async function dockerRestart({ container = "cicy-code-docker-
|
|
427
|
+
async function dockerRestart({ container = "cicy-code-docker-8008" } = {}) {
|
|
428
428
|
await dk(`restart ${container}`, { timeout: 45000 });
|
|
429
429
|
return true;
|
|
430
430
|
}
|
|
431
431
|
|
|
432
432
|
// 重建:强删占该端口的任何容器 + 目标容器,再 docker run(用新 env,如新 docker team
|
|
433
433
|
// 网关 key)。保留 bind-mount 宿主目录(数据/api_token 不丢)。破坏性 → 调用方要 confirm。
|
|
434
|
-
async function recreate({ port =
|
|
434
|
+
async function recreate({ port = 8008, container = "cicy-code-docker-8008", volume = "cicy-team-8008", env = {} } = {}) {
|
|
435
435
|
try { await dk(`ps -aq --filter publish=${port} | xargs -r docker --context ${CTX} rm -f 2>/dev/null; docker --context ${CTX} rm -f ${container} 2>/dev/null; true`, { timeout: 30000 }); } catch {}
|
|
436
436
|
const r = await runContainer({ port, container, volume, env });
|
|
437
437
|
try { await ensureDesktopShortcut(volume, port); } catch {}
|
|
@@ -439,7 +439,7 @@ async function recreate({ port = 8009, container = "cicy-code-docker-8009", volu
|
|
|
439
439
|
}
|
|
440
440
|
|
|
441
441
|
// 升级 = 删 VM 重装(重置;cicy-team 数据随之重置,实例重新 seed 出新 token)。
|
|
442
|
-
async function upgrade({ onProgress, port =
|
|
442
|
+
async function upgrade({ onProgress, port = 8008, container = "cicy-code-docker-8008", volume = "cicy-team-8008", env = {} } = {}) {
|
|
443
443
|
const emit = (ev) => { try { onProgress && onProgress(ev); } catch {} };
|
|
444
444
|
emit({ phase: "install-docker", status: "running", message: "升级 = 重装运行环境(会重置容器数据)…" });
|
|
445
445
|
try { await sh(`colima delete -f -p ${PROFILE} 2>/dev/null; true`, { timeout: 120000 }); } catch {}
|
package/src/sidecar/docker.js
CHANGED
|
@@ -441,7 +441,7 @@ function desktopDir() {
|
|
|
441
441
|
// Start the container. Returns a sidecar child token { docker:true, container,
|
|
442
442
|
// id } or null when Docker isn't ready (homepage guides the user to install
|
|
443
443
|
// Docker Desktop). `container`/`volume` are parameterized so a SECOND instance
|
|
444
|
-
// (the Docker-版 cicy-code on :
|
|
444
|
+
// (the Docker-版 cicy-code on :8008) can run alongside the native local one
|
|
445
445
|
// without a name/volume collision.
|
|
446
446
|
async function start({ port = 8008, container = CONTAINER, volume = VOLUME, mountTarget = "/home/cicy/cicy-ai", env = {} } = {}) {
|
|
447
447
|
// Something already serves a healthy cicy-code on :port (a legacy-named
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
// (Apache-2.0, no licensing) INSIDE a WSL2 Ubuntu distro and drive it with
|
|
5
5
|
// deterministic Linux commands — no UAC click-through, no whale-icon wait, no
|
|
6
6
|
// leftover-staging / PATH issues. WSL2 forwards localhost, so a container
|
|
7
|
-
// published on :
|
|
7
|
+
// published on :8008 in Ubuntu is reachable at 127.0.0.1:8008 on Windows.
|
|
8
8
|
//
|
|
9
9
|
// Flow: ensure WSL2 → ensure Ubuntu distro → apt install docker.io → start
|
|
10
10
|
// dockerd → docker load (image tarball from ~/Downloads via /mnt/c) → docker run
|
|
11
|
-
// → health-probe :
|
|
11
|
+
// → health-probe :8008 from Windows. Every step checks-then-acts and is
|
|
12
12
|
// idempotent, so 重试 resumes.
|
|
13
13
|
|
|
14
14
|
const { execFile, execFileSync, spawn } = require("child_process");
|
|
@@ -411,7 +411,7 @@ function shareMountArg() {
|
|
|
411
411
|
} catch (e) { log.warn(`[wsl-docker] Share mount setup failed: ${e.message}`); return ""; }
|
|
412
412
|
}
|
|
413
413
|
|
|
414
|
-
async function runContainer({ port =
|
|
414
|
+
async function runContainer({ port = 8008, container = "cicy-code-docker", volume = "cicy-team-8008", env = {} } = {}) {
|
|
415
415
|
// 每次容器"启动"(含已在跑被 adopt)都确保桌面快捷方式存在 —— 不存在就建,坏了就修。
|
|
416
416
|
if (await probeHealth(port)) { ensureDesktopShortcut(volume, port).catch(() => {}); return { adopted: true }; }
|
|
417
417
|
// Replace any stale same-named container.
|
|
@@ -423,7 +423,7 @@ async function runContainer({ port = 8009, container = "cicy-code-docker", volum
|
|
|
423
423
|
// --dns: WSL2's auto resolv.conf points the distro at the host NAT gateway
|
|
424
424
|
// (172.x.x.1), which docker's default DNS forwarding does NOT reach from inside a
|
|
425
425
|
// bridge container → every lookup is EAI_AGAIN and cicy-code's startup `npm i`
|
|
426
|
-
// crash-loops the container (:
|
|
426
|
+
// crash-loops the container (:8008 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
428
|
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}`;
|
|
429
429
|
await wslRun(cmd, { timeout: 60000 });
|
|
@@ -432,11 +432,11 @@ async function runContainer({ port = 8009, container = "cicy-code-docker", volum
|
|
|
432
432
|
}
|
|
433
433
|
|
|
434
434
|
// Read the container's OWN api_token (its volume-persisted global.json). This is
|
|
435
|
-
// the ONLY correct credential for :
|
|
436
|
-
//
|
|
435
|
+
// the ONLY correct credential for :8008 — the host's 8008 token is different and
|
|
436
|
+
// 8008 rejects it. Retries because right after start the entrypoint may not have
|
|
437
437
|
// written global.json yet; returns "" only if it truly can't be read (callers
|
|
438
438
|
// must then NOT open with a wrong/host token — that strands the user at login).
|
|
439
|
-
async function readContainerToken(port =
|
|
439
|
+
async function readContainerToken(port = 8008, container = "cicy-code-docker", volume = "cicy-team-8008") {
|
|
440
440
|
for (let attempt = 1; attempt <= 5; attempt++) {
|
|
441
441
|
// 1) Fast + reliable: read the volume-backed global.json straight from the
|
|
442
442
|
// distro fs. `docker exec` into a just-loaded/busy container is slow and
|
|
@@ -459,7 +459,7 @@ async function readContainerToken(port = 8009, container = "cicy-code-docker", v
|
|
|
459
459
|
}
|
|
460
460
|
|
|
461
461
|
// Register a Windows logon task that starts dockerd in our distro on every
|
|
462
|
-
// logon — old inbox WSL ignores wsl.conf [boot], so without this :
|
|
462
|
+
// logon — old inbox WSL ignores wsl.conf [boot], so without this :8008 is dead
|
|
463
463
|
// after a Windows reboot until the user clicks 启动. The container's
|
|
464
464
|
// --restart unless-stopped then brings cicy-code back automatically. Idempotent
|
|
465
465
|
// (start-dockerd.sh is `pgrep dockerd || dockerd`); /f overwrites a stale task.
|
|
@@ -477,7 +477,7 @@ function ensureAutostart() {
|
|
|
477
477
|
}
|
|
478
478
|
|
|
479
479
|
// Drop a desktop shortcut (folder icon) to the container's /home/cicy — i.e. the
|
|
480
|
-
// cicy-team volume on the distro — so the user can browse :
|
|
480
|
+
// cicy-team volume on the distro — so the user can browse :8008's files from
|
|
481
481
|
// Windows Explorer. \\wsl$\<distro>\… is the UNC view of the WSL filesystem.
|
|
482
482
|
// Idempotent: CreateShortcut overwrites. Best-effort (errors swallowed).
|
|
483
483
|
// PowerShell single-quoted literal. PowerShell does NOT treat backslash as an
|
|
@@ -487,7 +487,7 @@ function ensureAutostart() {
|
|
|
487
487
|
// backslashes) which Explorer can't open. That was the broken "WSL 快捷方式".
|
|
488
488
|
function psSingle(s) { return "'" + String(s).replace(/'/g, "''") + "'"; }
|
|
489
489
|
|
|
490
|
-
function ensureDesktopShortcut(volume = "cicy-team-
|
|
490
|
+
function ensureDesktopShortcut(volume = "cicy-team-8008", port = 8008) {
|
|
491
491
|
if (process.platform !== "win32") return Promise.resolve();
|
|
492
492
|
return new Promise((res) => {
|
|
493
493
|
// 快捷方式名带 port —— 多个 docker(不同端口)各自一个桌面文件夹快捷方式。
|
|
@@ -511,7 +511,7 @@ function ensureDesktopShortcut(volume = "cicy-team-8009", port = 8009) {
|
|
|
511
511
|
}
|
|
512
512
|
|
|
513
513
|
// Composite status for the card.
|
|
514
|
-
async function status(port =
|
|
514
|
+
async function status(port = 8008) {
|
|
515
515
|
// `unknown` = WSL didn't answer a probe (stuck/booting). The homepage uses it
|
|
516
516
|
// to show 「检测中/WSL 无响应·重试」instead of falsely showing 「下载安装」.
|
|
517
517
|
const miss = await docker.wslMissing(); // true | false | null(unknown)
|
|
@@ -542,7 +542,7 @@ async function bootstrap(opts = {}) {
|
|
|
542
542
|
return _bootstrapInFlight;
|
|
543
543
|
}
|
|
544
544
|
|
|
545
|
-
async function _bootstrap({ onProgress, port =
|
|
545
|
+
async function _bootstrap({ onProgress, port = 8008, container = "cicy-code-docker", volume = "cicy-team", env = {} } = {}) {
|
|
546
546
|
const emit = (ev) => { try { onProgress && onProgress(ev); } catch {} };
|
|
547
547
|
|
|
548
548
|
// Structured, PERSISTED trace of the whole run (electron-log → main.log) so a
|
|
@@ -650,7 +650,7 @@ async function _bootstrap({ onProgress, port = 8009, container = "cicy-code-dock
|
|
|
650
650
|
// Restart ONLY cicy-code via supervisor — cron / sshd / user daemons keep
|
|
651
651
|
// running (that's the whole point of the supervisor layout). Falls back to a
|
|
652
652
|
// full container restart on the pre-supervisor image.
|
|
653
|
-
async function restart({ container = "cicy-code-docker", port =
|
|
653
|
+
async function restart({ container = "cicy-code-docker", port = 8008, volume = "cicy-team-8008" } = {}) {
|
|
654
654
|
await startEngine();
|
|
655
655
|
try {
|
|
656
656
|
await wslRun(`docker exec ${container} supervisorctl -c /etc/supervisor/supervisord.conf restart cicy-code`, { timeout: 30000 });
|
|
@@ -666,7 +666,7 @@ async function restart({ container = "cicy-code-docker", port = 8009, volume = "
|
|
|
666
666
|
// which installs the latest version side-by-side, repoints the symlink, and
|
|
667
667
|
// `supervisorctl restart cicy-code` — no container recreate, daemons untouched.
|
|
668
668
|
// Streamed to the drawer so the user sees the npm pull + restart.
|
|
669
|
-
async function update({ onProgress, container = "cicy-code-docker", port =
|
|
669
|
+
async function update({ onProgress, container = "cicy-code-docker", port = 8008 } = {}) {
|
|
670
670
|
const emit = (ev) => { try { onProgress && onProgress(ev); } catch {} };
|
|
671
671
|
await startEngine();
|
|
672
672
|
emit({ phase: "image", status: "running", message: "更新 cicy-code(拉取最新版)…" });
|
|
@@ -678,7 +678,7 @@ async function update({ onProgress, container = "cicy-code-docker", port = 8009
|
|
|
678
678
|
return { ok: false, reason: "update_failed" };
|
|
679
679
|
}
|
|
680
680
|
const healthy = await docker.waitUntil(() => probeHealth(port), { totalMs: 120000, everyMs: 3000 });
|
|
681
|
-
emit({ phase: "done", status: healthy ? "done" : "error", message: healthy ? "cicy-code 已更新到最新 🎉" : "更新了但 :
|
|
681
|
+
emit({ phase: "done", status: healthy ? "done" : "error", message: healthy ? "cicy-code 已更新到最新 🎉" : "更新了但 :8008 还没响应——稍等或点重试" });
|
|
682
682
|
return { ok: healthy };
|
|
683
683
|
}
|
|
684
684
|
async function stop({ container = "cicy-code-docker" } = {}) {
|
|
@@ -688,7 +688,7 @@ async function stop({ container = "cicy-code-docker" } = {}) {
|
|
|
688
688
|
// docker restart 整个容器(stop+start 同一个容器)—— 区别于 restart()(supervisorctl
|
|
689
689
|
// 重启容器内的 cicy-code 进程)和重建(rm+run)。容器重启后 entrypoint 重跑,会重读
|
|
690
690
|
// volume global.json,所以若先把新 key 写进 volume,这个就能让 cicy-code 用上新 key。
|
|
691
|
-
async function dockerRestart({ container = "cicy-code-docker-
|
|
691
|
+
async function dockerRestart({ container = "cicy-code-docker-8008" } = {}) {
|
|
692
692
|
await wslRun(`docker restart ${container}`, { timeout: 45000 });
|
|
693
693
|
return true;
|
|
694
694
|
}
|
|
@@ -696,7 +696,7 @@ async function dockerRestart({ container = "cicy-code-docker-8009" } = {}) {
|
|
|
696
696
|
// 重建容器:docker rm -f 旧容器 + docker run 新容器(用新 env,如新的 docker team 网关
|
|
697
697
|
// key)。**保留 volume**(数据/api_token/deviceId 不丢),只是换掉容器本身 + env。
|
|
698
698
|
// 破坏性(短暂中断 + 换 key)→ 调用方要 confirm。
|
|
699
|
-
async function recreate({ port =
|
|
699
|
+
async function recreate({ port = 8008, container = "cicy-code-docker-8008", volume = "cicy-team-8008", env = {} } = {}) {
|
|
700
700
|
// 强删占用该端口的**任何**容器(含老名字 cicy-code-docker)+ 目标容器 —— 否则
|
|
701
701
|
// runContainer 开头的 probeHealth 看到旧容器还健康会 adopt 它、不重建,key 就换不了。
|
|
702
702
|
try { await wslRun(`docker ps -aq --filter publish=${port} | xargs -r docker rm -f 2>/dev/null; docker rm -f ${container} 2>/dev/null; true`, { timeout: 30000 }); } catch {}
|
|
@@ -718,7 +718,7 @@ function unregisterDistro() {
|
|
|
718
718
|
// downloader, which copes with the flaky CN DNS that bare curl can't) is the
|
|
719
719
|
// only reliable CN update path. This RESETS the distro: the cicy-team volume is
|
|
720
720
|
// re-created and the instance re-seeds (new token) on next boot.
|
|
721
|
-
async function upgrade({ onProgress, port =
|
|
721
|
+
async function upgrade({ onProgress, port = 8008, container = "cicy-code-docker", volume = "cicy-team", env = {} } = {}) {
|
|
722
722
|
const emit = (ev) => { try { onProgress && onProgress(ev); } catch {} };
|
|
723
723
|
emit({ phase: "install-docker", status: "running", message: "升级 = 拉取最新运行环境并重装(会重置容器数据)…" });
|
|
724
724
|
try { await stop({ container }); } catch {}
|