cicy-desktop 2.1.295 → 2.1.297

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.
@@ -119,6 +119,19 @@ jobs:
119
119
  if (-not $exe) { Write-Error "no exe found in dist"; exit 1 }
120
120
  $base = "oss://cicy-1372193042-cn/releases"
121
121
 
122
+ # 0) WSL2 内核裸文件(一次性,已存在就跳过):精简版 Windows 没有 msiexec 装不了
123
+ # wsl_update_x64.msi,桌面端改为直接下这个文件放到 System32\lxss\tools\kernel。
124
+ $kernelKey = "$base/wsl-kernel/kernel"
125
+ & $oss stat "$kernelKey" 2>$null | Out-Null
126
+ if ($LASTEXITCODE -ne 0) {
127
+ Write-Host "Publishing raw WSL2 kernel -> $kernelKey"
128
+ Invoke-WebRequest -Uri "https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi" -OutFile wsl_update_x64.msi
129
+ $kdir = Join-Path $PWD "wsl-kernel-extract"
130
+ Start-Process msiexec.exe -ArgumentList "/a","`"$PWD\wsl_update_x64.msi`"","/qn","TARGETDIR=$kdir" -Wait
131
+ $kfile = Get-ChildItem $kdir -Recurse -File | Where-Object { $_.Name -eq "kernel" } | Select-Object -First 1
132
+ if ($kfile) { & $oss cp "$($kfile.FullName)" "$kernelKey" -f --acl public-read } else { Write-Warning "kernel not found in msi extract" }
133
+ } else { Write-Host "WSL2 kernel already on OSS, skip" }
134
+
122
135
  # 1) 永久版本化副本(unique key — 不会被后续发版覆盖)
123
136
  $verKey = "$base/cicy-desktop-$version.exe"
124
137
  Write-Host "Uploading versioned -> $verKey"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cicy-desktop",
3
- "version": "2.1.295",
3
+ "version": "2.1.297",
4
4
  "description": "CiCy - AI-powered operating system browser",
5
5
  "main": "src/main.js",
6
6
  "bin": {
@@ -148,7 +148,7 @@ function register({ sidecarLogPath } = {}) {
148
148
  let _autoBootstrapRetryAt = 0; // 硬失败后的自动重试时间点(退避,不是永久暂停:用户不会自己修)
149
149
  // 退避:子进程被拦(安全软件开机后一段时间会自己放行)2 分钟一试;其余 15 分钟一试。
150
150
  const AUTO_RETRY_MS = { spawn_blocked: 2 * 60 * 1000, default: 15 * 60 * 1000 };
151
- const HARD_BOOTSTRAP_REASONS = new Set(["spawn_blocked", "virtualization_disabled", "wsl_enable_failed", "wsl_reboot_required"]);
151
+ const HARD_BOOTSTRAP_REASONS = new Set(["spawn_blocked", "virtualization_disabled", "wsl_enable_failed", "wsl_kernel_missing", "wsl_reboot_required"]);
152
152
  function recordBootstrapResult(result, err) {
153
153
  if (err) {
154
154
  const code = err && err.code;
@@ -667,6 +667,20 @@ function featureEnabled(feature) {
667
667
  });
668
668
  }
669
669
 
670
+ const WSL_KERNEL_MSI_URL = process.env.CICY_WSL_KERNEL_URL || "https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi";
671
+ // 裸内核文件(由发布工作流从 MSI 解出后放到 OSS):精简版 Windows 没有 msiexec 时直接放到位。
672
+ const WSL_KERNEL_RAW_URL = process.env.CICY_WSL_KERNEL_RAW_URL || (require("./mirrors").OSS_RELEASES_BASE + "/wsl-kernel/kernel");
673
+ // WSL2 kernel present? The inbox (feature-based) WSL keeps it at System32\lxss\tools\kernel;
674
+ // the Store WSL bundles its own, in which case the file is absent but `wsl --status`
675
+ // reports a kernel version — callers treat "functional + version" as present too.
676
+ function wslKernelPresent() {
677
+ if (process.platform !== "win32") return true;
678
+ const sysroot = process.env.SystemRoot || process.env.windir || "C:\\Windows";
679
+ try { if (fs.existsSync(path.join(sysroot, "System32", "lxss", "tools", "kernel"))) return true; } catch {}
680
+ try { if (fs.existsSync(path.join(process.env.LOCALAPPDATA || "", "Microsoft", "WindowsApps", "wsl.exe")) && fs.existsSync(path.join(process.env.ProgramFiles || "C:\\Program Files", "WSL", "wsl.exe"))) return true; } catch {}
681
+ return false;
682
+ }
683
+
670
684
  // Functional WSL probe that needs no elevation: `wsl -l -v` on a machine with
671
685
  // the features enabled (and rebooted) either lists distros or says "no installed
672
686
  // distributions"; with the features off it prints the "--install" help text.
@@ -719,17 +733,26 @@ function elevatedWslSetup({ emit, need = {} } = {}) {
719
733
  `$R = "${resultFile.replace(/"/g, '""')}"`,
720
734
  'function Say($m) { Add-Content -Path $R -Value ("[" + (Get-Date -Format "HH:mm:ss") + "] " + $m) }',
721
735
  'function St($n) { try { (Get-CimInstance Win32_OptionalFeature -Filter ("Name=\'" + $n + "\'")).InstallState } catch { 0 } }',
722
- 'function En($n) { dism /online /enable-feature /featurename:$n /all /norestart | Out-Null; $c = $LASTEXITCODE; Say ("enable " + $n + " exit=" + $c); return $c }',
736
+ '$S32 = Join-Path $env:SystemRoot "System32"; $DISM = Join-Path $S32 "dism.exe"; $SFC = Join-Path $S32 "sfc.exe"; $MSIEXEC = Join-Path $S32 "msiexec.exe"',
737
+ 'function En($n) { & $DISM /online /enable-feature /featurename:$n /all /norestart | Out-Null; $c = $LASTEXITCODE; Say ("enable " + $n + " exit=" + $c); return $c }',
723
738
  'Say "start admin=$(([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(\'Administrators\'))"',
724
739
  '$feats = @("Microsoft-Windows-Subsystem-Linux","VirtualMachinePlatform")',
725
740
  '$bad = @(); foreach ($f in $feats) { if ((St $f) -ne 1) { $c = En $f; if ($c -ne 0 -and $c -ne 3010) { $bad += $f } } }',
726
741
  'if ($bad.Count -gt 0) {',
727
742
  ' Say "repair: DISM /RestoreHealth (component store refused: $($bad -join \',\')) — this can take 10-30 min"',
728
- ' dism /online /cleanup-image /restorehealth /norestart | Out-Null; Say ("restorehealth exit=" + $LASTEXITCODE)',
729
- ' sfc /scannow | Out-Null; Say ("sfc exit=" + $LASTEXITCODE)',
743
+ ' & $DISM /online /cleanup-image /restorehealth /norestart | Out-Null; Say ("restorehealth exit=" + $LASTEXITCODE)',
744
+ ' & $SFC /scannow | Out-Null; Say ("sfc exit=" + $LASTEXITCODE)',
730
745
  ' foreach ($f in $bad) { $c = En $f }',
731
746
  '}',
732
- `if (Test-Path "${msi.replace(/"/g, '""')}") { Start-Process msiexec.exe -ArgumentList "/i","${msi.replace(/"/g, '""')}","/qn","/norestart" -Wait; Say "kernel msi installed" }`,
747
+ `$KDIR = Join-Path $S32 "lxss\\tools"; $KFILE = Join-Path $KDIR "kernel"`,
748
+ `if (-not (Test-Path $KFILE)) {`,
749
+ ` if ((Test-Path $MSIEXEC) -and (Test-Path "${msi.replace(/"/g, '""')}")) { $p = Start-Process $MSIEXEC -ArgumentList "/i","\`"${msi.replace(/"/g, '""')}\`"","/qn","/norestart" -Wait -PassThru; Say ("kernel msi exit=" + $p.ExitCode) }`,
750
+ ` if (-not (Test-Path $KFILE)) {`,
751
+ ` Say "msiexec missing or failed — downloading the raw WSL2 kernel file"`,
752
+ ` New-Item -ItemType Directory -Force $KDIR | Out-Null`,
753
+ ` try { Invoke-WebRequest -UseBasicParsing -Uri "${WSL_KERNEL_RAW_URL}" -OutFile $KFILE; Say ("kernel file " + (Get-Item $KFILE).Length + " bytes") } catch { Say ("kernel download failed: " + $_.Exception.Message) }`,
754
+ ` }`,
755
+ `}`,
733
756
  'Say ("final subsystem=" + (St "Microsoft-Windows-Subsystem-Linux") + " vmPlatform=" + (St "VirtualMachinePlatform"))',
734
757
  'Say "DONE"',
735
758
  ].join("\r\n");
@@ -819,10 +842,27 @@ async function ensureWsl({ emit } = {}) {
819
842
  if (state.ready) {
820
843
  // 功能都已启用、wsl.exe 也在,但 WSL 本身还不能用(`wsl -l -v` 只打印帮助)= 启用后还没
821
844
  // 重启。别去 --import(必失败),直接进自动重启流程。
822
- if (await wslFunctional()) return { ok: true };
823
- log.warn("[bootstrap] ensure-wsl: features enabled but WSL not functional yet (pending reboot)");
824
- emit && emit({ phase: "install-docker", status: "running", message: "WSL 功能已启用但尚未生效,需要重启 Windows…" });
825
- return { ok: false, needsReboot: true };
845
+ if (!(await wslFunctional())) {
846
+ log.warn("[bootstrap] ensure-wsl: features enabled but WSL not functional yet (pending reboot)");
847
+ emit && emit({ phase: "install-docker", status: "running", message: "WSL 功能已启用但尚未生效,需要重启 Windows…" });
848
+ return { ok: false, needsReboot: true };
849
+ }
850
+ // WSL2 内核(lxss\tools\kernel)缺失时 --import 也必失败。先把 MSI 下到 Downloads,再用
851
+ // 同一个提权脚本装(功能已启用的话脚本只做装内核这一件事)。
852
+ if (!wslKernelPresent()) {
853
+ const msi = path.join(os.homedir(), "Downloads", "wsl_update_x64.msi");
854
+ try { await ensureDownloaded(WSL_KERNEL_MSI_URL, msi, null, { emit, phase: "install-docker", label: "下载 WSL2 内核" }); }
855
+ catch (e) { log.warn(`[bootstrap] kernel msi download failed: ${e.message}`); }
856
+ emit && emit({ phase: "install-docker", status: "running", message: "WSL2 内核未安装,开始安装(会弹一次 UAC,请点「是」)…" });
857
+ const r = await elevatedWslSetup({ emit, need: {} });
858
+ if (!wslKernelPresent()) {
859
+ const message = `WSL2 内核未能安装${r.detail ? `(${r.detail})` : "(UAC 未确认或 MSI 安装失败)"}——会自动重试。`;
860
+ log.error(`[bootstrap] ✗ ensure-wsl reason=wsl_kernel_missing ${r.detail || ""}`);
861
+ emit && emit({ phase: "done", status: "error", message });
862
+ return { ok: false, needsReboot: false, failed: true, reason: "wsl_kernel_missing", message };
863
+ }
864
+ }
865
+ return { ok: true };
826
866
  }
827
867
 
828
868
  emit && emit({ phase: "install-docker", status: "running", message: "Docker 需要 WSL2 后端,开始启用所需的 Windows 功能(会弹一次 UAC,请点「是」)…" });
@@ -939,7 +979,7 @@ module.exports = {
939
979
  start, stop, stopContainer, restart, checkStatus, loadImage, loadImageFromTarball,
940
980
  downloadImageTarball, imagePresent, dockerOk, installDocker,
941
981
  bootstrap, probeHealth, readContainerToken, dockerDesktopExe, desktopDir, downloadsDir, imageTarballPath,
942
- launchElevated, elevatedWslSetup, spawnProbe, wslFunctional, wslExe, wslMissing, ensureWsl, virtualizationStatus,
982
+ launchElevated, elevatedWslSetup, spawnProbe, wslFunctional, wslKernelPresent, wslExe, wslMissing, ensureWsl, virtualizationStatus,
943
983
  // platform-agnostic download/retry primitives, reused by native.js
944
984
  ensureDownloaded, curlDownload, withRetry, waitUntil, run, headSize,
945
985
  // image freshness (修「重建仍用旧镜像」—— 校验 OSS ETag 变了才重下重载)
@@ -67,8 +67,22 @@ test("a missing wsl.exe (ENOENT, features enabled but not yet rebooted) counts a
67
67
 
68
68
  test("features enabled but WSL still the pre-reboot stub → needsReboot; auto-reboot capped at 2", () => {
69
69
  const d = read("src/sidecar/docker.js");
70
- assert.match(d, /if \(await wslFunctional\(\)\) return \{ ok: true \};/);
70
+ assert.match(d, /if \(!\(await wslFunctional\(\)\)\) \{/);
71
71
  assert.match(d, /\[Argument\\\]/);
72
72
  const ipc = read("src/backends/sidecar-ipc.js");
73
73
  assert.match(ipc, /rebootCount\(\) >= 2/);
74
74
  });
75
+
76
+ test("missing WSL2 kernel is installed through the same single elevation", () => {
77
+ const d = read("src/sidecar/docker.js");
78
+ assert.match(d, /function wslKernelPresent\(\)/);
79
+ assert.match(d, /if \(!wslKernelPresent\(\)\) \{[^]*elevatedWslSetup\(\{ emit, need: \{\} \}\)/);
80
+ assert.match(d, /reason: "wsl_kernel_missing"/);
81
+ });
82
+
83
+ test("kernel install falls back to the raw kernel file when msiexec is absent", () => {
84
+ const d = read("src/sidecar/docker.js");
85
+ assert.match(d, /WSL_KERNEL_RAW_URL/);
86
+ assert.match(d, /msiexec missing or failed/);
87
+ assert.match(read(".github/workflows/windows-exe-release.yml"), /wsl-kernel\/kernel/);
88
+ });