cicy-desktop 2.1.122 → 2.1.123

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.122",
3
+ "version": "2.1.123",
4
4
  "description": "CiCy - AI-powered operating system browser",
5
5
  "main": "src/main.js",
6
6
  "bin": {
@@ -16,6 +16,7 @@ const path = require("path");
16
16
  const os = require("os");
17
17
  const fs = require("fs");
18
18
  const docker = require("./docker"); // shared: downloads, waitUntil, probeHealth, launchElevated, ensureWsl…
19
+ const log = require("electron-log"); // persisted main.log — bootstrap timing/failures land here
19
20
 
20
21
  // Dedicated distro name — NEVER reuse/clobber a user's own "Ubuntu" distro.
21
22
  const DISTRO = process.env.CICY_WSL_DISTRO || "cicy-code-wsl";
@@ -182,6 +183,8 @@ async function startEngine() {
182
183
  // runtime files, (re)launches dockerd, and waits for the socket; between
183
184
  // attempts we hard-kill any half-dead daemon so the next launch is clean.
184
185
  for (let attempt = 1; attempt <= 3; attempt++) {
186
+ const at0 = Date.now();
187
+ log.info(`[startEngine] attempt ${attempt}/3 — (re)launch dockerd + wait for socket`);
185
188
  try {
186
189
  await wslRun(
187
190
  "update-alternatives --set iptables /usr/sbin/iptables-legacy >/dev/null 2>&1; " +
@@ -199,8 +202,9 @@ async function startEngine() {
199
202
  // cold again (a restart loop → 「引擎没起来」even though dockerd was fine).
200
203
  "for i in $(seq 1 120); do [ -S /var/run/docker.sock ] && docker version >/dev/null 2>&1 && break; sleep 1; done",
201
204
  { timeout: 150000 });
202
- } catch {}
203
- if (await dockerEngineUp()) return true;
205
+ } catch (e) { log.warn(`[startEngine] attempt ${attempt} launch/wait errored: ${e.message}`); }
206
+ if (await dockerEngineUp()) { log.info(`[startEngine] ✓ dockerd up on attempt ${attempt} (${((Date.now() - at0) / 1000).toFixed(1)}s)`); return true; }
207
+ log.warn(`[startEngine] attempt ${attempt} dockerd still not up after ${((Date.now() - at0) / 1000).toFixed(1)}s`);
204
208
  // Not up after the wait. Only hard-reset when dockerd actually DIED (crash) —
205
209
  // if it's still alive it's just slow/mid-init, so leave it and let the next
206
210
  // attempt's wait loop keep polling instead of killing a healthy daemon.
@@ -210,6 +214,7 @@ async function startEngine() {
210
214
  { timeout: 15000 });
211
215
  } catch {}
212
216
  }
217
+ log.error("[startEngine] ✗ dockerd NOT up after 3 attempts");
213
218
  return false;
214
219
  }
215
220
 
@@ -310,7 +315,11 @@ function ensureAutostart() {
310
315
  return new Promise((res) => {
311
316
  const tr = `wsl.exe -d ${DISTRO} -u root -e /usr/local/sbin/start-dockerd.sh`;
312
317
  execFile("schtasks", ["/create", "/tn", "cicy-docker-autostart", "/tr", tr, "/sc", "onlogon", "/rl", "HIGHEST", "/f"],
313
- { windowsHide: true }, () => res());
318
+ { windowsHide: true }, (err, _stdout, stderr) => {
319
+ if (err) log.warn(`[ensureAutostart] schtasks create FAILED (dockerd won't auto-start after reboot): ${err.message}${stderr ? ` / ${String(stderr).trim()}` : ""}`);
320
+ else log.info("[ensureAutostart] logon task 'cicy-docker-autostart' registered → dockerd starts on next logon");
321
+ res();
322
+ });
314
323
  });
315
324
  }
316
325
 
@@ -375,65 +384,104 @@ async function bootstrap(opts = {}) {
375
384
  async function _bootstrap({ onProgress, port = 8009, container = "cicy-code-docker", volume = "cicy-team", env = {} } = {}) {
376
385
  const emit = (ev) => { try { onProgress && onProgress(ev); } catch {} };
377
386
 
387
+ // Structured, PERSISTED trace of the whole run (electron-log → main.log) so a
388
+ // failed "重启后起不来" is diagnosable AFTER the fact instead of staring at the
389
+ // ephemeral progress modal. Each step logs ▶start / ✓done(+duration, or "skip"
390
+ // when already satisfied) / ✗fail(+reason+duration); the final line gives the
391
+ // total time and the SLOWEST step, plus a [name,ms] breakdown of every step.
392
+ const BT0 = Date.now();
393
+ const secs = (t) => `${((Date.now() - t) / 1000).toFixed(1)}s`;
394
+ const steps = [];
395
+ let _pt = BT0, _pname = "";
396
+ const begin = (name) => { _pname = name; _pt = Date.now(); log.info(`[bootstrap] ▶ ${name}`); };
397
+ const done = (skip) => { steps.push([_pname, Date.now() - _pt]); log.info(`[bootstrap] ✓ ${_pname}${skip ? " (skip)" : ""} ${secs(_pt)}`); };
398
+ const fail = (reason, extra) => { steps.push([`${_pname}:FAIL`, Date.now() - _pt]); log.error(`[bootstrap] ✗ ${_pname} reason=${reason} ${secs(_pt)}${extra ? `\n${extra}` : ""}`); };
399
+ const finish = (ok, reason) => {
400
+ const slow = steps.slice().sort((a, b) => b[1] - a[1])[0];
401
+ log.info(`[bootstrap] ${ok ? "DONE ✓" : `ABORT ✗ reason=${reason}`} total=${secs(BT0)} slowest=${slow ? `${slow[0]} ${(slow[1] / 1000).toFixed(1)}s` : "n/a"} steps=${JSON.stringify(steps.map(([n, d]) => [n, Math.round(d)]))}`);
402
+ };
403
+ log.info(`[bootstrap] START port=${port} container=${container} volume=${volume}`);
404
+
378
405
  // 0) Fast path: already healthy → instant no-op (idempotent one-shot).
406
+ begin("probe-healthy");
379
407
  if (await probeHealth(port)) {
408
+ done();
380
409
  emit({ phase: "done", status: "done", message: "Docker cicy-code 已就绪 🎉" });
410
+ finish(true);
381
411
  return { ok: true, container };
382
412
  }
413
+ done();
383
414
 
384
415
  // 1) WSL2 platform
416
+ begin("ensure-wsl");
385
417
  if (docker.wslMissing()) {
386
418
  const w = await docker.ensureWsl({ emit });
387
- if (w.needsReboot) { emit({ phase: "done", status: "reboot", message: "WSL2 正在安装——请【重启 Windows】后回来点「重试」继续。" }); return { ok: false, reason: "wsl_reboot_required" }; }
388
- }
419
+ if (w.needsReboot) { fail("wsl_reboot_required"); emit({ phase: "done", status: "reboot", message: "WSL2 正在安装——请【重启 Windows】后回来点「重试」继续。" }); finish(false, "wsl_reboot_required"); return { ok: false, reason: "wsl_reboot_required" }; }
420
+ done();
421
+ } else done(true);
389
422
 
390
423
  // 2) Ubuntu distro
424
+ begin("ensure-distro");
391
425
  if (!distroInstalled()) {
392
- try { await installDistro({ emit }); } catch (e) { emit({ phase: "install-docker", status: "error", message: `Ubuntu 安装失败:${e.message}(点重试)` }); return { ok: false, reason: "distro_install_failed" }; }
426
+ try { await installDistro({ emit }); } catch (e) { fail("distro_install_failed", e.message); emit({ phase: "install-docker", status: "error", message: `Ubuntu 安装失败:${e.message}(点重试)` }); finish(false, "distro_install_failed"); return { ok: false, reason: "distro_install_failed" }; }
393
427
  const t0 = Date.now();
394
428
  const ok = await docker.waitUntil(() => distroInstalled(), { totalMs: 600000, everyMs: 5000, onTick: () => emit({ phase: "install-docker", status: "running", message: `正在下载/注册 Ubuntu…(已 ${Math.round((Date.now() - t0) / 1000)}s,首次较慢请耐心)` }) });
395
- if (!ok) { emit({ phase: "install-docker", status: "error", message: "Ubuntu 还没装好——稍等或点「重试」" }); return { ok: false, reason: "distro_not_ready" }; }
396
- }
429
+ if (!ok) { fail("distro_not_ready"); emit({ phase: "install-docker", status: "error", message: "Ubuntu 还没装好——稍等或点「重试」" }); finish(false, "distro_not_ready"); return { ok: false, reason: "distro_not_ready" }; }
430
+ done();
431
+ } else done(true);
397
432
 
398
433
  // 3) Docker Engine inside Ubuntu
434
+ begin("install-docker-engine");
399
435
  if (!(await dockerInstalled())) {
400
- try { await installDockerEngine({ emit }); } catch (e) { emit({ phase: "install-docker", status: "error", message: `Docker 安装失败:${e.message}(点重试)` }); return { ok: false, reason: "docker_install_failed" }; }
401
- }
436
+ try { await installDockerEngine({ emit }); } catch (e) { fail("docker_install_failed", e.message); emit({ phase: "install-docker", status: "error", message: `Docker 安装失败:${e.message}(点重试)` }); finish(false, "docker_install_failed"); return { ok: false, reason: "docker_install_failed" }; }
437
+ done();
438
+ } else done(true);
402
439
 
403
440
  // 4) dockerd up (phase "container" = 启动服务)
441
+ begin("start-dockerd");
404
442
  if (!(await dockerEngineUp())) {
405
443
  emit({ phase: "container", status: "running", message: "启动 Docker 引擎(首次较慢,请耐心)…" });
406
444
  const started = await startEngine(); // 3 clean attempts internally
407
445
  const up = started || await docker.waitUntil(dockerEngineUp, { totalMs: 120000, everyMs: 3000 });
408
446
  if (!up) {
409
- const log = await dockerdLogTail();
410
- emit({ phase: "container", status: "error", message: "Docker 引擎没起来——点「重试」" + (log ? `\n\ndockerd 日志(最后几行):\n${log}` : "") });
447
+ const dlog = await dockerdLogTail();
448
+ fail("dockerd_not_up", dlog ? `dockerd log:\n${dlog}` : "");
449
+ emit({ phase: "container", status: "error", message: "Docker 引擎没起来——点「重试」" + (dlog ? `\n\ndockerd 日志(最后几行):\n${dlog}` : "") });
450
+ finish(false, "dockerd_not_up");
411
451
  return { ok: false, reason: "dockerd_not_up" };
412
452
  }
413
- }
453
+ done();
454
+ } else done(true);
414
455
 
415
456
  // 5) Base image — pre-baked into the package, so this normally just confirms.
416
457
  // The download-tarball path is a fallback for a non-pre-baked rootfs.
458
+ begin("ensure-image");
417
459
  if (!(await imagePresent())) {
418
460
  let tarball;
419
461
  try { tarball = await docker.downloadImageTarball({ emit }); }
420
- catch (e) { emit({ phase: "image", status: "error", message: `镜像下载失败:${e.message}(点重试续传)` }); return { ok: false, reason: "image_download_failed" }; }
462
+ catch (e) { fail("image_download_failed", e.message); emit({ phase: "image", status: "error", message: `镜像下载失败:${e.message}(点重试续传)` }); finish(false, "image_download_failed"); return { ok: false, reason: "image_download_failed" }; }
421
463
  try { await loadImage(tarball, { emit }); }
422
- catch (e) { emit({ phase: "image", status: "error", message: `镜像导入失败:${e.message}(点重试)` }); return { ok: false, reason: "image_load_failed" }; }
423
- }
464
+ catch (e) { fail("image_load_failed", e.message); emit({ phase: "image", status: "error", message: `镜像导入失败:${e.message}(点重试)` }); finish(false, "image_load_failed"); return { ok: false, reason: "image_load_failed" }; }
465
+ done();
466
+ } else done(true);
424
467
 
425
468
  // 6) Container (phase "container" = 启动服务)
469
+ begin("run-container");
426
470
  if (!(await probeHealth(port))) {
427
471
  emit({ phase: "container", status: "running", message: "启动 cicy-code 服务…" });
428
472
  try { await runContainer({ port, container, volume, env }); }
429
- catch (e) { emit({ phase: "container", status: "error", message: `服务启动失败:${e.message}(点重试)` }); return { ok: false, reason: "container_start_failed" }; }
430
- }
473
+ catch (e) { fail("container_start_failed", e.message); emit({ phase: "container", status: "error", message: `服务启动失败:${e.message}(点重试)` }); finish(false, "container_start_failed"); return { ok: false, reason: "container_start_failed" }; }
474
+ done();
475
+ } else done(true);
431
476
 
432
477
  // 7) Health — the ONLY path to ok:true.
478
+ begin("wait-health");
433
479
  emit({ phase: "container", status: "running", message: "等待 cicy-code 就绪…" });
434
480
  const healthy = await docker.waitUntil(() => probeHealth(port), { totalMs: 120000, everyMs: 3000 });
435
- if (healthy) { await ensureAutostart(); await ensureDesktopShortcut(volume, port); } // survive reboot + desktop shortcut
481
+ if (healthy) { done(); await ensureAutostart(); await ensureDesktopShortcut(volume, port); } // survive reboot + desktop shortcut
482
+ else fail("health_timeout");
436
483
  emit({ phase: healthy ? "done" : "container", status: healthy ? "done" : "error", message: healthy ? "Docker cicy-code 已就绪 🎉" : `服务起来了但 :${port} 还没响应——稍等或点「重试」` });
484
+ finish(healthy, healthy ? null : "health_timeout");
437
485
  return { ok: healthy, container };
438
486
  }
439
487