cicy-desktop 2.1.140 → 2.1.142

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.
@@ -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-TR02tbIs.js"></script>
9
+ <script type="module" crossorigin src="./assets/index-DVdvubOy.js"></script>
10
10
  <link rel="stylesheet" crossorigin href="./assets/index-CKhSx1lp.css">
11
11
  </head>
12
12
  <body>
@@ -224,6 +224,16 @@ function register({ sidecarLogPath } = {}) {
224
224
  return { installed: false, dockerRunning: false, running: false, unknown: true, port: APP_PORT, platform: process.platform };
225
225
  });
226
226
 
227
+ // 「重试检测」: FORCE a fresh probe right now (appStatus only reads the cache, so
228
+ // clicking it changed nothing — the "点了没反应" bug). Returns the freshly-probed
229
+ // status and kicks the auto-start reconcile in the background. If WSL is wedged
230
+ // this resolves to unknown after the probe timeout, which the UI then surfaces.
231
+ ipcMain.handle("docker:app-redetect", async () => {
232
+ const s = await refreshDockerStatus();
233
+ reconcileDocker().catch(() => {});
234
+ return s;
235
+ });
236
+
227
237
  // Common run options for the :8009 instance: its own container/volume + the
228
238
  // LLM gateway env keyed by the 8008 team's token. (WSL: whole-home mount via
229
239
  // -v <volume>:/home/cicy inside wsl-docker.)
@@ -190,8 +190,10 @@ async function installDistro({ emit } = {}) {
190
190
  emit && emit({ phase: "container", status: "running", message: "重置运行环境(冷启动)…" });
191
191
  try { await wslTerminate(); } catch {}
192
192
 
193
- // 4) Free the ~444MB package now that the distro has everything.
194
- try { fs.unlinkSync(dest); } catch {}
193
+ // 4) KEEP the rootfs (deletion removed per 主人). Deleting it forced a fresh
194
+ // 447MB re-download on every reinstall / new user / clean retry — the main
195
+ // 「怎么这么慢」 pain. We keep it so reinstall reuses it (curlDownload skips a
196
+ // complete file). Users who want the disk back can delete the tarball manually.
195
197
  }
196
198
 
197
199
  // docker CLI present inside the distro?
@@ -230,51 +232,63 @@ async function dockerEngineUp() {
230
232
  // • iptables must use the LEGACY backend (Ubuntu defaults to nft, which
231
233
  // dockerd can't drive in WSL2 → daemon fails to set up networking).
232
234
  // • run dockerd detached and wait for /var/run/docker.sock.
235
+ // Launch dockerd FULLY DETACHED. This is the core fix for the all-day「WSL 卡死 /
236
+ // app 未响应」: the old launch `(nohup dockerd >log 2>&1 &)` left dockerd in the wsl
237
+ // session with stdin still on the wsl pipe, so `wsl.exe` would NOT exit — the
238
+ // launching call hung (up to its 150s timeout), and a hung wsl session wedges the
239
+ // whole WSL subsystem so every later `wsl` call blocks and the app freezes.
240
+ // setsid → new session (detached from the wsl console)
241
+ // </dev/null + >log 2>&1 → no shared stdio with the wsl pipe
242
+ // ⇒ the launch returns INSTANTLY and dockerd is fully orphaned. Short timeout: if
243
+ // THIS times out, WSL itself is already wedged (not dockerd's fault).
244
+ // The boot/autostart script the rootfs ships (run by wsl.conf [boot] + our logon
245
+ // task) used a NON-detached `nohup dockerd &`, which hangs the logon wsl.exe and
246
+ // wedges WSL on every reboot. Rewrite it to launch detached too. base64 so the
247
+ // multi-line script survives `bash -lc "…"` without any quote escaping.
248
+ const BOOT_SCRIPT_B64 = Buffer.from(
249
+ "#!/bin/sh\n" +
250
+ "update-alternatives --set iptables /usr/sbin/iptables-legacy 2>/dev/null || true\n" +
251
+ "update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy 2>/dev/null || true\n" +
252
+ "pgrep dockerd >/dev/null 2>&1 || setsid sh -c 'exec dockerd >/var/log/dockerd.log 2>&1 </dev/null' &\n"
253
+ ).toString("base64");
254
+
255
+ async function launchDockerd() {
256
+ await wslRun(
257
+ `echo ${BOOT_SCRIPT_B64} | base64 -d > /usr/local/sbin/start-dockerd.sh 2>/dev/null; chmod +x /usr/local/sbin/start-dockerd.sh 2>/dev/null; ` +
258
+ "update-alternatives --set iptables /usr/sbin/iptables-legacy >/dev/null 2>&1; " +
259
+ "update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy >/dev/null 2>&1; " +
260
+ // Stale /var/run/docker.{pid,sock} from the pre-baked rootfs make a fresh dockerd
261
+ // refuse to start; clear them when dockerd isn't already running.
262
+ "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; " +
263
+ "pgrep dockerd >/dev/null 2>&1 || setsid bash -c 'exec dockerd >/var/log/cicy-dockerd.log 2>&1 </dev/null' & " +
264
+ "true",
265
+ { timeout: 20000 });
266
+ }
267
+
233
268
  async function startEngine() {
234
- // Up to 3 clean attempts: on a cold first boot dockerd can die mid-init (e.g.
235
- // networking not ready yet) and only succeed on a fresh relaunch that race
236
- // was the main "一次装不上、要点几次重试" culprit. Each attempt clears stale
237
- // runtime files, (re)launches dockerd, and waits for the socket; between
238
- // attempts we hard-kill any half-dead daemon so the next launch is clean.
269
+ // Up to 3 attempts. Each: launch dockerd (detached, returns instantly) then POLL
270
+ // the socket in SHORT, SEPARATE wsl calls never hold the distro in one 120s call
271
+ // (that both masked wedges and blocked everything else). Cold first boot of a
272
+ // freshly-imported distro can take well over 40s, so poll up to 90s.
239
273
  for (let attempt = 1; attempt <= 3; attempt++) {
240
274
  const at0 = Date.now();
241
- let launchErr = null;
242
- log.info(`[startEngine] attempt ${attempt}/3 — (re)launch dockerd + wait for socket`);
243
- try {
244
- await wslRun(
245
- "update-alternatives --set iptables /usr/sbin/iptables-legacy >/dev/null 2>&1; " +
246
- "update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy >/dev/null 2>&1; " +
247
- // A pre-baked rootfs (docker export after an inner dind dockerd) can ship a
248
- // STALE /var/run/docker.{pid,sock}: a fresh dockerd then refuses to start
249
- // ("pid file found, ensure docker is not running"). Clear them when dockerd
250
- // is NOT already running — that was the 烤制包「引擎没起来」failure.
251
- "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; " +
252
- "pgrep dockerd >/dev/null 2>&1 || (nohup dockerd >/var/log/cicy-dockerd.log 2>&1 &); " +
253
- // First boot of a freshly-imported distro: cold WSL2 VM + large pre-baked
254
- // /var/lib/docker (restoring containers + overlay) can take well over 40s.
255
- // Wait up to 120s — the 40s window was too short on cold boot, which made
256
- // the next attempt pkill the STILL-INITIALIZING daemon and relaunch it
257
- // cold again (a restart loop → 「引擎没起来」even though dockerd was fine).
258
- "for i in $(seq 1 120); do [ -S /var/run/docker.sock ] && docker version >/dev/null 2>&1 && break; sleep 1; done",
259
- { timeout: 150000 });
260
- } catch (e) { launchErr = e; log.warn(`[startEngine] attempt ${attempt} launch/wait errored: ${e.message}`); }
261
- if (await dockerEngineUp()) { log.info(`[startEngine] ✓ dockerd up on attempt ${attempt} (${((Date.now() - at0) / 1000).toFixed(1)}s)`); return true; }
262
- log.warn(`[startEngine] attempt ${attempt} dockerd still not up after ${((Date.now() - at0) / 1000).toFixed(1)}s`);
263
- // Reset between attempts. If the launch itself ERRORED (timeout = WSL wedged /
264
- // unresponsive — the post-import failure where the command can't even get into
265
- // the distro), `wsl --terminate` so the next attempt cold-boots a CLEAN distro;
266
- // clearing pid files inside a wedged WSL does nothing. Otherwise dockerd just
267
- // died/is slow — clear stale runtime files only when it's actually gone.
268
- if (launchErr) {
269
- log.info(`[startEngine] launch errored → wsl --terminate for a clean cold boot`);
270
- try { await wslTerminate(); } catch {}
271
- } else {
272
- try {
273
- await wslRun(
274
- "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; sleep 1",
275
- { timeout: 15000 });
276
- } catch {}
275
+ let stuck = false;
276
+ log.info(`[startEngine] attempt ${attempt}/3 — launch dockerd (detached) + poll socket`);
277
+ try { await launchDockerd(); }
278
+ catch (e) { stuck = true; log.warn(`[startEngine] attempt ${attempt} launch errored — WSL stuck? ${e.message}`); }
279
+ if (!stuck) {
280
+ const deadline = Date.now() + 90000;
281
+ while (Date.now() < deadline) {
282
+ if (await dockerEngineUp()) { log.info(`[startEngine] ✓ dockerd up on attempt ${attempt} (${((Date.now() - at0) / 1000).toFixed(1)}s)`); return true; }
283
+ await new Promise((r) => setTimeout(r, 2000));
284
+ }
277
285
  }
286
+ log.warn(`[startEngine] attempt ${attempt} dockerd not up after ${((Date.now() - at0) / 1000).toFixed(1)}s`);
287
+ // Recover. A stuck launch = WSL wedged → `wsl --shutdown` (full VM reset — the
288
+ // ONLY thing that clears a real wedge; --terminate isn't enough). Otherwise
289
+ // dockerd just died/is slow → clear stale runtime files for a clean relaunch.
290
+ if (stuck) { log.info(`[startEngine] WSL stuck → wsl --shutdown`); try { await wslShutdown(); } catch {} }
291
+ else { try { await wslRun("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", { timeout: 15000 }); } catch {} }
278
292
  }
279
293
  log.error("[startEngine] ✗ dockerd NOT up after 3 attempts");
280
294
  return false;
@@ -1708,7 +1708,8 @@ function DockerInstallDrawerHost() {
1708
1708
  // Desktop and runs it (主人指令), streaming progress through the drawer above.
1709
1709
  function DockerCard({ dockerTeam, cloudTitle, onOpen, onRename, onRefresh }) {
1710
1710
  const [status, setStatus] = useState(null);
1711
- const [busy, setBusy] = useState(""); // "" | bootstrap | restart | stop | upgrade
1711
+ const [busy, setBusy] = useState(""); // "" | bootstrap | restart | stop | upgrade | probe
1712
+ const [probeNote, setProbeNote] = useState(""); // 「重试检测」后给用户的明确反馈(WSL 仍卡 → 提示重启)
1712
1713
  const [menuOpen, setMenuOpen] = useState(false);
1713
1714
  const [confirmRecreate, setConfirmRecreate] = useState(false); // 重建容器 in-app 确认弹窗(不用 native confirm)
1714
1715
  // Inline rename (mirrors LocalTeamCard): double-click the title to edit.
@@ -1731,8 +1732,11 @@ function DockerCard({ dockerTeam, cloudTitle, onOpen, onRename, onRefresh }) {
1731
1732
  const DOCKER_BLUE = "#2496ed";
1732
1733
 
1733
1734
  const checkStatus = useCallback(async () => {
1734
- try { setStatus(await window.cicy?.docker?.appStatus?.()); }
1735
- catch (e) { console.warn("[DockerCard]", e); }
1735
+ try {
1736
+ const s = await window.cicy?.docker?.appStatus?.();
1737
+ setStatus(s);
1738
+ if (s && !s.unknown) setProbeNote(""); // WSL came back → clear the stuck note
1739
+ } catch (e) { console.warn("[DockerCard]", e); }
1736
1740
  }, []);
1737
1741
 
1738
1742
  // Poll so the card reflects reality even when Docker changes outside the app
@@ -1874,6 +1878,8 @@ function DockerCard({ dockerTeam, cloudTitle, onOpen, onRename, onRefresh }) {
1874
1878
 
1875
1879
  const ctaLabel = busy === "open"
1876
1880
  ? tr("docker.opening", "打开中…")
1881
+ : busy === "probe"
1882
+ ? tr("docker.probing", "检测中…")
1877
1883
  : isBusy
1878
1884
  ? tr("docker.working", "处理中…")
1879
1885
  : running
@@ -1900,9 +1906,20 @@ function DockerCard({ dockerTeam, cloudTitle, onOpen, onRename, onRefresh }) {
1900
1906
  try { await onOpen?.(dockerTeam?.id); } finally { setBusy(""); }
1901
1907
  return;
1902
1908
  }
1903
- // WSL didn't answer the probe re-check instead of (re)installing. If WSL
1904
- // has since come up (e.g. autostart finished), this flips the card to 打开/启动.
1905
- if (unknown) { await checkStatus(); return; }
1909
+ // 「重试检测」: FORCE a fresh WSL probe (checkStatus only re-read the cache, so
1910
+ // clicking did nothing the「点了没反应」bug). Show a spinner so the click always
1911
+ // gives feedback; if WSL is STILL stuck after a real probe, say so clearly.
1912
+ if (unknown) {
1913
+ setBusy("probe");
1914
+ try {
1915
+ const s = await (window.cicy?.docker?.appRedetect?.() ?? window.cicy?.docker?.appStatus?.());
1916
+ if (s) setStatus(s);
1917
+ if (s?.unknown) setProbeNote(tr("docker.wslStillStuck", "WSL 仍无响应 —— 多半要重启 Windows 后再试"));
1918
+ else setProbeNote("");
1919
+ } catch (e) { setProbeNote(tr("docker.probeFailed", "检测失败,请重试")); }
1920
+ finally { setBusy(""); }
1921
+ return;
1922
+ }
1906
1923
  runBootstrap();
1907
1924
  };
1908
1925
 
@@ -2002,6 +2019,11 @@ function DockerCard({ dockerTeam, cloudTitle, onOpen, onRename, onRefresh }) {
2002
2019
  )}
2003
2020
  </div>
2004
2021
  <div className="bcard__meta"><span className="bcard__chip">Docker</span></div>
2022
+ {(probeNote || (!running && stateText)) && (
2023
+ <div data-id="DockerCard-statusline" style={{ marginTop: 4, fontSize: 11, lineHeight: 1.4, color: probeNote ? "#f0a020" : "rgba(255,255,255,0.55)" }}>
2024
+ {probeNote || stateText}
2025
+ </div>
2026
+ )}
2005
2027
  </div>
2006
2028
  <button
2007
2029
  type="button"