cicy-desktop 2.1.111 → 2.1.113

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cicy-desktop",
3
- "version": "2.1.111",
3
+ "version": "2.1.113",
4
4
  "description": "CiCy - AI-powered operating system browser",
5
5
  "main": "src/main.js",
6
6
  "bin": {
@@ -176,21 +176,32 @@ async function dockerEngineUp() {
176
176
  // dockerd can't drive in WSL2 → daemon fails to set up networking).
177
177
  // • run dockerd detached and wait for /var/run/docker.sock.
178
178
  async function startEngine() {
179
- try {
180
- await wslRun(
181
- "update-alternatives --set iptables /usr/sbin/iptables-legacy >/dev/null 2>&1; " +
182
- "update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy >/dev/null 2>&1; " +
183
- // A pre-baked rootfs (docker export after an inner dind dockerd) can ship a
184
- // STALE /var/run/docker.{pid,sock}: a fresh dockerd then refuses to start
185
- // ("pid file found, ensure docker is not running"). Clear them when dockerd
186
- // is NOT already running — that was the 烤制包「引擎没起来」failure.
187
- "if ! pgrep dockerd >/dev/null 2>&1; then rm -f /var/run/docker.pid /run/docker.pid /var/run/docker.sock /run/docker.sock; fi; " +
188
- "pgrep dockerd >/dev/null 2>&1 || (nohup dockerd >/var/log/cicy-dockerd.log 2>&1 &); " +
189
- // First boot of a freshly-imported distro: cold WSL2 VM + large pre-baked
190
- // /var/lib/docker give it longer than 20s.
191
- "for i in $(seq 1 40); do [ -S /var/run/docker.sock ] && docker version >/dev/null 2>&1 && break; sleep 1; done",
192
- { timeout: 60000 });
193
- } catch {}
179
+ // Up to 3 clean attempts: on a cold first boot dockerd can die mid-init (e.g.
180
+ // networking not ready yet) and only succeed on a fresh relaunch — that race
181
+ // was the main "一次装不上、要点几次重试" culprit. Each attempt clears stale
182
+ // runtime files, (re)launches dockerd, and waits for the socket; between
183
+ // attempts we hard-kill any half-dead daemon so the next launch is clean.
184
+ for (let attempt = 1; attempt <= 3; attempt++) {
185
+ try {
186
+ await wslRun(
187
+ "update-alternatives --set iptables /usr/sbin/iptables-legacy >/dev/null 2>&1; " +
188
+ "update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy >/dev/null 2>&1; " +
189
+ // A pre-baked rootfs (docker export after an inner dind dockerd) can ship a
190
+ // STALE /var/run/docker.{pid,sock}: a fresh dockerd then refuses to start
191
+ // ("pid file found, ensure docker is not running"). Clear them when dockerd
192
+ // is NOT already running — that was the 烤制包「引擎没起来」failure.
193
+ "if ! pgrep dockerd >/dev/null 2>&1; then rm -f /var/run/docker.pid /run/docker.pid /var/run/docker.sock /run/docker.sock; fi; " +
194
+ "pgrep dockerd >/dev/null 2>&1 || (nohup dockerd >/var/log/cicy-dockerd.log 2>&1 &); " +
195
+ // First boot of a freshly-imported distro: cold WSL2 VM + large pre-baked
196
+ // /var/lib/docker → give it longer than 20s.
197
+ "for i in $(seq 1 40); do [ -S /var/run/docker.sock ] && docker version >/dev/null 2>&1 && break; sleep 1; done",
198
+ { timeout: 60000 });
199
+ } catch {}
200
+ if (await dockerEngineUp()) return true;
201
+ // Failed: kill the half-dead daemon + clear runtime files for a clean retry.
202
+ try { await wslRun("pkill -9 dockerd 2>/dev/null; rm -f /var/run/docker.pid /run/docker.pid /var/run/docker.sock /run/docker.sock; sleep 1", { timeout: 15000 }); } catch {}
203
+ }
204
+ return false;
194
205
  }
195
206
 
196
207
  // Tail dockerd's log so a "引擎没起来" failure is diagnosable instead of blind.
@@ -229,11 +240,15 @@ const probeHealth = docker.probeHealth;
229
240
 
230
241
  // Start (or adopt) the container on :port inside the distro.
231
242
  //
232
- // cicy-code binds 127.0.0.1 inside the container (localhost-only by design), so
233
- // `-p 8009:8008` doesn't workdocker-proxy can't reach a loopback-bound app.
234
- // Instead use host networking + PORT=<port>: the app listens on <port> in the
235
- // distro's network namespace, and WSL2's localhost relay forwards it to Windows
236
- // 127.0.0.1:<port>. Verified: HEALTH 200 from Windows.
243
+ // We do NOT use --network host (it shares the distro's whole network namespace,
244
+ // exposing every container portsshd:22, cron, etc. and offers no
245
+ // isolation). Instead publish a single mapped port:
246
+ // CICY_PUBLIC=1 makes cicy-code bind 0.0.0.0:8008 INSIDE the container
247
+ // (it binds 127.0.0.1 by default, which docker-proxy can't reach).
248
+ // • -p 127.0.0.1:<port>:8008 pins the host side to loopback, so it's never
249
+ // network-exposed; the api_token gates access. WSL2's localhost relay then
250
+ // forwards the distro's 127.0.0.1:<port> to Windows 127.0.0.1:<port>.
251
+ // Only :<port> is published — sshd/cron stay inside the container's own netns.
237
252
  async function runContainer({ port = 8009, container = "cicy-code-docker", volume = "cicy-team", env = {} } = {}) {
238
253
  if (await probeHealth(port)) return { adopted: true };
239
254
  // Replace any stale same-named container.
@@ -242,7 +257,7 @@ async function runContainer({ port = 8009, container = "cicy-code-docker", volum
242
257
  .filter(([, v]) => v != null && v !== "")
243
258
  .map(([k, v]) => `-e ${k}='${String(v).replace(/'/g, "'\\''")}'`)
244
259
  .join(" ");
245
- const cmd = `docker run -d --name ${container} --restart unless-stopped --network host -e PORT=${port} -v ${volume}:/home/cicy ${envArgs} ${IMAGE}`;
260
+ const cmd = `docker run -d --name ${container} --restart unless-stopped -p 127.0.0.1:${port}:8008 -e CICY_PUBLIC=1 -v ${volume}:/home/cicy ${envArgs} ${IMAGE}`;
246
261
  await wslRun(cmd, { timeout: 60000 });
247
262
  return { started: true };
248
263
  }
@@ -251,7 +266,7 @@ async function runContainer({ port = 8009, container = "cicy-code-docker", volum
251
266
  // team registration — the host token is a different credential.
252
267
  async function readContainerToken(port = 8009, container = "cicy-code-docker") {
253
268
  try {
254
- // Host networking has no "publish" mapping, so look the container up by name.
269
+ // Look the container up by name and read the token from inside it.
255
270
  const { stdout } = await wslRun(`docker ps --filter "name=${container}" --format '{{.Names}}'`, { timeout: 10000 });
256
271
  const name = stdout.trim().split("\n")[0];
257
272
  if (!name) return "";
@@ -301,6 +316,12 @@ async function bootstrap(opts = {}) {
301
316
  async function _bootstrap({ onProgress, port = 8009, container = "cicy-code-docker", volume = "cicy-team", env = {} } = {}) {
302
317
  const emit = (ev) => { try { onProgress && onProgress(ev); } catch {} };
303
318
 
319
+ // 0) Fast path: already healthy → instant no-op (idempotent one-shot).
320
+ if (await probeHealth(port)) {
321
+ emit({ phase: "done", status: "done", message: "Docker cicy-code 已就绪 🎉" });
322
+ return { ok: true, container };
323
+ }
324
+
304
325
  // 1) WSL2 platform
305
326
  if (docker.wslMissing()) {
306
327
  const w = await docker.ensureWsl({ emit });
@@ -323,8 +344,8 @@ async function _bootstrap({ onProgress, port = 8009, container = "cicy-code-dock
323
344
  // 4) dockerd up (phase "container" = 启动服务)
324
345
  if (!(await dockerEngineUp())) {
325
346
  emit({ phase: "container", status: "running", message: "启动 Docker 引擎(首次较慢,请耐心)…" });
326
- await startEngine();
327
- const up = await docker.waitUntil(dockerEngineUp, { totalMs: 120000, everyMs: 3000 });
347
+ const started = await startEngine(); // 3 clean attempts internally
348
+ const up = started || await docker.waitUntil(dockerEngineUp, { totalMs: 120000, everyMs: 3000 });
328
349
  if (!up) {
329
350
  const log = await dockerdLogTail();
330
351
  emit({ phase: "container", status: "error", message: "Docker 引擎没起来——点「重试」" + (log ? `\n\ndockerd 日志(最后几行):\n${log}` : "") });
@@ -366,18 +387,27 @@ async function restart({ container = "cicy-code-docker", port = 8009 } = {}) {
366
387
  async function stop({ container = "cicy-code-docker" } = {}) {
367
388
  try { await wslRun(`docker stop ${container}`, { timeout: 30000 }); } catch {}
368
389
  }
390
+ // Unregister the dedicated distro (idempotent; no-op if absent). Used by upgrade
391
+ // to wipe a stale install before re-importing the latest pre-baked package.
392
+ function unregisterDistro() {
393
+ return new Promise((resolve) => {
394
+ execFile("wsl", ["--unregister", DISTRO], { timeout: 120000, windowsHide: true }, () => resolve());
395
+ });
396
+ }
397
+
398
+ // Upgrade = re-import the latest pre-baked 烤制包 (it carries the latest cicy-code
399
+ // image). DockerHub `docker pull` is unreliable in CN, and the standalone image
400
+ // `docker save` tarball was retired — so re-import (via the app's own resilient
401
+ // downloader, which copes with the flaky CN DNS that bare curl can't) is the
402
+ // only reliable CN update path. This RESETS the distro: the cicy-team volume is
403
+ // re-created and the instance re-seeds (new token) on next boot.
369
404
  async function upgrade({ onProgress, port = 8009, container = "cicy-code-docker", volume = "cicy-team", env = {} } = {}) {
370
405
  const emit = (ev) => { try { onProgress && onProgress(ev); } catch {} };
371
- if (!(await dockerEngineUp())) { await startEngine(); if (!(await dockerEngineUp())) { emit({ phase: "done", status: "error", message: "Docker 引擎未运行" }); return { ok: false, reason: "dockerd_not_up" }; } }
372
- let tarball;
373
- try { tarball = await docker.downloadImageTarball({ emit }); await loadImage(tarball, { emit }); emit({ phase: "image", status: "done", message: "镜像已更新" }); }
374
- catch (e) { emit({ phase: "done", status: "error", message: `升级失败:${e.message}` }); return { ok: false, reason: "image_failed" }; }
375
- emit({ phase: "container", status: "running", message: "用新镜像重建容器…" });
376
- try { await stop({ container }); await runContainer({ port, container, volume, env }); }
377
- catch (e) { emit({ phase: "done", status: "error", message: `容器启动失败:${e.message}` }); return { ok: false, reason: "container_start_failed" }; }
378
- const healthy = await docker.waitUntil(() => probeHealth(port), { totalMs: 120000, everyMs: 3000 });
379
- emit({ phase: "done", status: healthy ? "done" : "error", message: healthy ? "升级完成 🎉" : `启动了但 :${port} 还没响应` });
380
- return { ok: healthy };
406
+ emit({ phase: "install-docker", status: "running", message: "升级 = 拉取最新运行环境并重装(会重置容器数据)…" });
407
+ try { await stop({ container }); } catch {}
408
+ try { await unregisterDistro(); } catch {}
409
+ // Reuse the robust one-shot install flow (download import dockerd run).
410
+ return await _bootstrap({ onProgress, port, container, volume, env });
381
411
  }
382
412
 
383
413
  module.exports = {