dsh-selfupdater 0.4.22 → 0.4.24

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.
Files changed (2) hide show
  1. package/lib/updater.mjs +34 -5
  2. package/package.json +1 -1
package/lib/updater.mjs CHANGED
@@ -376,19 +376,46 @@ function launchRunnerChain() {
376
376
  env: { ...process.env, TRIM_APPDEST: appDir },
377
377
  });
378
378
  child.unref();
379
+ // 用新 runner 的 PID 覆盖飞牛守护的 PID 文件:守护探活恢复正常认知,
380
+ // 后续自愈/状态轮询不会因 PID 文件指向死进程而拉起竞争实例。
381
+ writeSupervisorPidFile(child.pid);
379
382
  log(`已拉起 runner 链 (pid=${child.pid})`);
380
383
  }
381
384
 
382
385
  /**
383
- * 健康检查:轮询本地端口直到有任意 HTTP 响应(服务能应答即算活着,
384
- * 不苛求 200 —— 登录页/404 都证明 Web 服务已起来)。
386
+ * 飞牛守护 PID 文件握手(0.4.23 新增,修复守护竞态):
387
+ * 宿主为升级退出后,守护的 PID 文件指向死进程,升级窗口(1~2 分钟)内
388
+ * 任何一次守护自愈/状态轮询/手动启停都会按"服务未运行"拉起竞争实例,
389
+ * 与本脚本的下载/换装/拉起互相踩踏(12:01~12:04 实测踩坑)。
390
+ * 对策:升级期间把本脚本 PID 写入 PID 文件 —— 守护 kill -0 探活认为
391
+ * "应用仍在运行",不会另起实例;结束后由 launchRunnerChain 用新 runner
392
+ * PID 覆盖。本脚本若中途死亡,PID 失效,守护自然兜底拉起,行为闭环。
393
+ */
394
+ const supervisorPidFile = process.env.TRIM_PKGVAR
395
+ ? join(process.env.TRIM_PKGVAR, `${process.env.TRIM_APPNAME || 'deepseek-harness'}.pid`)
396
+ : null;
397
+
398
+ function writeSupervisorPidFile(pid) {
399
+ if (!supervisorPidFile) return;
400
+ try {
401
+ writeFileSync(supervisorPidFile, String(pid));
402
+ log(`已写入守护 PID 文件: ${supervisorPidFile} <- ${pid}`);
403
+ } catch { /* 非飞牛环境或权限不足时静默跳过 */ }
404
+ }
405
+
406
+ /**
407
+ * 健康检查:轮询本地端口,判定服务是否真正就绪。
408
+ * 0.4.23:从"有任何响应即健康"收紧为要求 res.ok,防错误页中间层误判。
409
+ * 0.4.24:本机实测 alpha.4 起 Web 端点启用了 token 鉴权,未带 token 访问
410
+ * `/` 返回 401 —— 这是真实后端的应答(服务已就绪),必须放行;
411
+ * 代理错误页通常是 502/503,仍会被正确拒绝。
385
412
  */
386
413
  async function waitHealthy(timeoutMs) {
387
414
  const deadline = Date.now() + timeoutMs;
388
415
  while (Date.now() < deadline) {
389
416
  try {
390
- await fetch(`http://127.0.0.1:${port}/`, { signal: AbortSignal.timeout(3000) });
391
- return true;
417
+ const res = await fetch(`http://127.0.0.1:${port}/`, { signal: AbortSignal.timeout(3000) });
418
+ if (res.ok || res.status === 401 || res.status === 403) return true;
392
419
  } catch { /* 未就绪继续等 */ }
393
420
  await sleep(2000);
394
421
  }
@@ -420,6 +447,8 @@ async function rollback() {
420
447
 
421
448
  async function main() {
422
449
  mkdirSync(dshStateDir, { recursive: true });
450
+ // 升级窗口内接管守护 PID 文件,防止飞牛守护拉起竞争实例(见上方握手注释)
451
+ writeSupervisorPidFile(process.pid);
423
452
  const current = currentDshVersion();
424
453
 
425
454
  // 锁文件握手:插件本体 POST /perform 会预写锁文件(by=plugin)作为并发
@@ -462,7 +491,7 @@ async function main() {
462
491
  if (await waitHealthy(90000)) {
463
492
  // 成功:清理 staging;bak 目录保留作为手动救砖的最后手段。
464
493
  rmSync(stagingDir, { recursive: true, force: true });
465
- setState('done', `升级完成:${current} -> ${target}`, { finishedAt: new Date().toISOString() });
494
+ setState('done', `升级完成:${current} -> ${target}。请检查插件市场的插件是否已适配新版 DSH。`, { finishedAt: new Date().toISOString() });
466
495
  } else {
467
496
  await rollback();
468
497
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-selfupdater",
3
- "version": "0.4.22",
3
+ "version": "0.4.24",
4
4
  "description": "Self-update plugin for DeepSeek Harness: DSH core upgrades via detached swap script; plugin self-update installs in-place without killing the host and prompts for restart. DSH 主程序与已装插件的一站式在线更新插件。",
5
5
  "license": "MIT",
6
6
  "type": "module",