cicy-desktop 2.1.107 → 2.1.108
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/docker.js +42 -6
- package/src/sidecar/wsl-docker.js +35 -6
package/package.json
CHANGED
package/src/sidecar/docker.js
CHANGED
|
@@ -504,15 +504,51 @@ function wslMissing() {
|
|
|
504
504
|
}
|
|
505
505
|
}
|
|
506
506
|
|
|
507
|
+
// Read-only, works without elevation. True iff a Windows optional feature
|
|
508
|
+
// reports State : Enabled.
|
|
509
|
+
function featureEnabled(feature) {
|
|
510
|
+
return new Promise((resolve) => {
|
|
511
|
+
execFile("dism", ["/english", "/online", "/get-featureinfo", `/featurename:${feature}`],
|
|
512
|
+
{ timeout: 30000, windowsHide: true },
|
|
513
|
+
(_e, out) => resolve(/State\s*:\s*Enabled/i.test(String(out || ""))));
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// Enable ONE Windows optional feature, elevated, and WAIT until it actually
|
|
518
|
+
// reports Enabled. The old `wsl --install` was fire-and-forget — it could
|
|
519
|
+
// silently no-op, leaving the card stuck on "reboot required" forever. Here we
|
|
520
|
+
// drive DISM and verify, streaming every step to the install drawer.
|
|
521
|
+
async function dismEnableFeature(feature, label, { emit } = {}) {
|
|
522
|
+
if (await featureEnabled(feature)) {
|
|
523
|
+
emit && emit({ phase: "install-docker", status: "running", message: `${label}:已启用,跳过` });
|
|
524
|
+
return true;
|
|
525
|
+
}
|
|
526
|
+
emit && emit({ phase: "install-docker", status: "running", message: `${label}:正在启用(约 1–2 分钟,请稍候)…` });
|
|
527
|
+
await launchElevated("dism", ["/online", "/enable-feature", `/featurename:${feature}`, "/all", "/norestart"], { emit }).catch(() => {});
|
|
528
|
+
// launchElevated fires the elevated task and returns immediately; poll the
|
|
529
|
+
// real feature state (DISM exits 3010 = success + reboot-required).
|
|
530
|
+
const ok = await waitUntil(() => featureEnabled(feature), { totalMs: 240000, everyMs: 5000 });
|
|
531
|
+
emit && emit({ phase: "install-docker", status: ok ? "running" : "error", message: ok ? `${label}:已启用 ✓` : `${label}:未能确认启用(点「重试」)` });
|
|
532
|
+
return ok;
|
|
533
|
+
}
|
|
534
|
+
|
|
507
535
|
// Ensure the WSL2 backend exists; install it (elevated) if missing. Returns
|
|
508
|
-
// { ok } when present,
|
|
509
|
-
//
|
|
536
|
+
// { ok } when already present, { needsReboot } after the two required Windows
|
|
537
|
+
// features are verified-enabled (a Windows reboot is then needed before Docker
|
|
538
|
+
// can use WSL2), or { failed } if a feature couldn't be enabled.
|
|
510
539
|
async function ensureWsl({ emit } = {}) {
|
|
511
540
|
if (!wslMissing()) return { ok: true };
|
|
512
|
-
emit && emit({ phase: "install-docker", status: "running", message: "Docker 需要 WSL2
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
541
|
+
emit && emit({ phase: "install-docker", status: "running", message: "Docker 需要 WSL2 后端,开始启用所需的 Windows 功能…" });
|
|
542
|
+
const a = await dismEnableFeature("Microsoft-Windows-Subsystem-Linux", "启用 WSL 功能 1/2 · Linux 子系统", { emit });
|
|
543
|
+
const b = await dismEnableFeature("VirtualMachinePlatform", "启用 WSL 功能 2/2 · 虚拟机平台", { emit });
|
|
544
|
+
if (!a || !b) {
|
|
545
|
+
emit && emit({ phase: "done", status: "error", message: "WSL 功能未能全部启用——请点「重试」" });
|
|
546
|
+
return { ok: false, needsReboot: false, failed: true };
|
|
547
|
+
}
|
|
548
|
+
// Best-effort: also pull the WSL2 kernel/plumbing (no-op until reboot on some
|
|
549
|
+
// builds; harmless if it errors).
|
|
550
|
+
await launchElevated("wsl", ["--install", "--no-distribution"], { emit }).catch(() => {});
|
|
551
|
+
emit && emit({ phase: "install-docker", status: "running", message: "WSL2 功能已启用 ✓,需【重启 Windows】后回来点「重试」继续。" });
|
|
516
552
|
return { ok: false, needsReboot: true };
|
|
517
553
|
}
|
|
518
554
|
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// → health-probe :8009 from Windows. Every step checks-then-acts and is
|
|
12
12
|
// idempotent, so 重试 resumes.
|
|
13
13
|
|
|
14
|
-
const { execFile, execFileSync } = require("child_process");
|
|
14
|
+
const { execFile, execFileSync, spawn } = require("child_process");
|
|
15
15
|
const path = require("path");
|
|
16
16
|
const docker = require("./docker"); // shared: downloads, waitUntil, probeHealth, launchElevated, ensureWsl…
|
|
17
17
|
|
|
@@ -32,6 +32,34 @@ function wslRun(cmd, { timeout = 60000, distro = DISTRO } = {}) {
|
|
|
32
32
|
});
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
// Like wslRun, but STREAMS each output line to the install drawer so the user
|
|
36
|
+
// (and the customer) watch the real install proceed — apt fetching/unpacking,
|
|
37
|
+
// `docker load` layers — instead of staring at a frozen spinner. Throttled so
|
|
38
|
+
// rapid output doesn't flood the log; resolves { stdout } with the full tail.
|
|
39
|
+
function wslRunStream(cmd, { emit, phase = "install-docker", timeout = 900000, distro = DISTRO } = {}) {
|
|
40
|
+
return new Promise((resolve, reject) => {
|
|
41
|
+
const child = spawn("wsl", ["-d", distro, "-u", "root", "--", "bash", "-lc", cmd], { windowsHide: true });
|
|
42
|
+
let buf = "", tail = "", last = 0;
|
|
43
|
+
const pump = (chunk) => {
|
|
44
|
+
buf += chunk.toString("utf8");
|
|
45
|
+
let nl;
|
|
46
|
+
while ((nl = buf.indexOf("\n")) >= 0) {
|
|
47
|
+
const line = buf.slice(0, nl).replace(/\r$/, "").trim();
|
|
48
|
+
buf = buf.slice(nl + 1);
|
|
49
|
+
if (!line) continue;
|
|
50
|
+
tail += line + "\n";
|
|
51
|
+
const now = Date.now();
|
|
52
|
+
if (emit && now - last > 350) { last = now; emit({ phase, status: "running", message: line.slice(0, 200) }); }
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
child.stdout.on("data", pump);
|
|
56
|
+
child.stderr.on("data", pump);
|
|
57
|
+
const timer = setTimeout(() => { try { child.kill("SIGKILL"); } catch {} reject(Object.assign(new Error("timeout"), { stdout: tail })); }, timeout);
|
|
58
|
+
child.on("error", (e) => { clearTimeout(timer); reject(e); });
|
|
59
|
+
child.on("close", (code) => { clearTimeout(timer); code === 0 ? resolve({ stdout: tail }) : reject(Object.assign(new Error(`exit ${code}`), { stdout: tail })); });
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
35
63
|
// Is `DISTRO` registered? `wsl -l -q` lists installed distros (UTF-16LE).
|
|
36
64
|
function distroInstalled(distro = DISTRO) {
|
|
37
65
|
if (process.platform !== "win32") return false;
|
|
@@ -66,8 +94,8 @@ async function dockerInstalled() {
|
|
|
66
94
|
|
|
67
95
|
// Install Docker Engine (docker.io) inside the distro.
|
|
68
96
|
async function installDockerEngine({ emit } = {}) {
|
|
69
|
-
emit && emit({ phase: "install-docker", status: "running", message: "在 Ubuntu 里安装 Docker(apt
|
|
70
|
-
await
|
|
97
|
+
emit && emit({ phase: "install-docker", status: "running", message: "在 Ubuntu 里安装 Docker(apt,几分钟,下面是实时进度)…" });
|
|
98
|
+
await wslRunStream("apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y docker.io 2>&1", { emit, phase: "install-docker", timeout: 900000 });
|
|
71
99
|
}
|
|
72
100
|
|
|
73
101
|
// dockerd reachable inside the distro?
|
|
@@ -96,9 +124,9 @@ function toWslPath(winPath) {
|
|
|
96
124
|
|
|
97
125
|
// docker load the (Windows-side) tarball into the distro's Docker + retag.
|
|
98
126
|
async function loadImage(winTarballPath, { emit } = {}) {
|
|
99
|
-
emit && emit({ phase: "image", status: "loading", message: "正在导入镜像到 Docker
|
|
127
|
+
emit && emit({ phase: "image", status: "loading", message: "正在导入镜像到 Docker(较大,约 1-3 分钟,下面是实时进度)…" });
|
|
100
128
|
const p = toWslPath(winTarballPath);
|
|
101
|
-
const { stdout } = await
|
|
129
|
+
const { stdout } = await wslRunStream(`docker load -i "${p}"`, { emit, phase: "image", timeout: 600000 });
|
|
102
130
|
const m = String(stdout).match(/Loaded image:\s*(\S+)/i);
|
|
103
131
|
if (m && m[1] !== IMAGE) { try { await wslRun(`docker tag ${m[1]} ${IMAGE}`, { timeout: 15000 }); } catch {} }
|
|
104
132
|
}
|
|
@@ -155,7 +183,8 @@ async function bootstrap({ onProgress, port = 8009, container = "cicy-code-docke
|
|
|
155
183
|
// 2) Ubuntu distro
|
|
156
184
|
if (!distroInstalled()) {
|
|
157
185
|
try { await installDistro({ emit }); } catch (e) { emit({ phase: "done", status: "error", message: `Ubuntu 安装失败:${e.message}(点重试)` }); return { ok: false, reason: "distro_install_failed" }; }
|
|
158
|
-
const
|
|
186
|
+
const t0 = Date.now();
|
|
187
|
+
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,首次较慢请耐心)` }) });
|
|
159
188
|
if (!ok) { emit({ phase: "done", status: "error", message: "Ubuntu 还没装好——稍等或点「重试」" }); return { ok: false, reason: "distro_not_ready" }; }
|
|
160
189
|
}
|
|
161
190
|
|