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 +1 -1
- package/src/sidecar/wsl-docker.js +67 -19
package/package.json
CHANGED
|
@@ -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 }, () =>
|
|
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
|
|
410
|
-
|
|
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
|
|