@gehennawu/dsh-service 1.9.9 → 2.0.1
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/README.en.md +37 -6
- package/README.md +37 -6
- package/backup-integrity.js +3 -1
- package/client.js +1 -1
- package/index.js +59 -16
- package/package.json +3 -3
package/index.js
CHANGED
|
@@ -3039,9 +3039,11 @@ async function repairPermissions(ctx, dshHome, plans, planId) {
|
|
|
3039
3039
|
|
|
3040
3040
|
// 运行环境检查(v0.17):managed/declared → ok;疑似手动启动 → warning 但带 advisory 标记
|
|
3041
3041
|
//(黄色行内提示,不参与 overall 聚合、不点亮标签 ⚠ 与顶部提醒——用户复核口径);unknown →
|
|
3042
|
-
// info。detail
|
|
3042
|
+
// info。detail 是客户端映射词典的令牌。桌面端(Electron)由应用自身托管重启,是明确的 ok
|
|
3043
|
+
// 事实,不再落 unknown 的含糊提示,且优先于 manual 判定。
|
|
3043
3044
|
function runtimeEnvCheck(runtimeEnv) {
|
|
3044
3045
|
if (runtimeEnv === undefined || runtimeEnv === null) return null
|
|
3046
|
+
if (runtimeEnv.electronShell === true) return { id: 'runtime-env', status: 'ok', detail: 'desktop' }
|
|
3045
3047
|
if (runtimeEnv.manualStartLikely === true) return { id: 'runtime-env', status: 'warning', detail: 'manual', advisory: true }
|
|
3046
3048
|
if (runtimeEnv.supervisorKind === 'declared') return { id: 'runtime-env', status: 'ok', detail: 'declared' }
|
|
3047
3049
|
if (typeof runtimeEnv.supervisorKind === 'string' && runtimeEnv.supervisorKind.length > 0) {
|
|
@@ -3256,7 +3258,9 @@ function collectActiveWork(ctx) {
|
|
|
3256
3258
|
// manualStartLikely=true —— 无管理器且 stdin/stdout 双 TTY(终端直启特征),升级后宿主
|
|
3257
3259
|
// 不自动退出,客户端改示手动重启指引;
|
|
3258
3260
|
// 其余 —— unknown(输出重定向、NSSM/WinSW 等包装器),维持现状不折腾。
|
|
3259
|
-
// 探测不到的包装器可用 DSH_SERVICE_RUNTIME_ENV=managed|manual 由用户显式声明。
|
|
3261
|
+
// 探测不到的包装器可用 DSH_SERVICE_RUNTIME_ENV=managed|manual|desktop 由用户显式声明。
|
|
3262
|
+
// 第四态 electronShell=true 是正交的平台事实(DSH 桌面端),三态取值不变:桌面端既没有
|
|
3263
|
+
// supervisor 标记、stdio 也非 TTY,本会落 unknown,靠这一位才能改走桌面端重启引导。
|
|
3260
3264
|
const RUNTIME_SUPERVISOR_ENV = [
|
|
3261
3265
|
['pm2', ['pm_id', 'PM2_HOME', 'pm_uptime']],
|
|
3262
3266
|
['systemd', ['INVOCATION_ID', 'JOURNAL_STREAM', 'NOTIFY_SOCKET']],
|
|
@@ -3264,9 +3268,21 @@ const RUNTIME_SUPERVISOR_ENV = [
|
|
|
3264
3268
|
['kubernetes', ['KUBERNETES_SERVICE_HOST']],
|
|
3265
3269
|
]
|
|
3266
3270
|
|
|
3271
|
+
// 桌面端(DSH Desktop)判据:桌面端的 Host 是 Electron 以 RunAsNode 承载的子进程,该进程里
|
|
3272
|
+
// `process.versions.electron` 有值(2026-09-28 Windows 真机实测:electron 44.0.0 / node 24.18.1,
|
|
3273
|
+
// 同进程 ELECTRON_RUN_AS_NODE=1,stdin/stdout 均非 TTY)。只认版本号字段:它是进程自身的运行时
|
|
3274
|
+
// 事实,不像 env 那样会随父进程继承给普通子进程,也不会对 iframe/嵌入帧误判(`'dshDesktop' in
|
|
3275
|
+
// globalThis` 在嵌入帧里同样成立,不能当判据)。非 Electron 的桌面封装可显式声明
|
|
3276
|
+
// DSH_SERVICE_RUNTIME_ENV=desktop。
|
|
3277
|
+
function isElectronRuntime(versions) {
|
|
3278
|
+
return versions !== null && typeof versions === 'object'
|
|
3279
|
+
&& typeof versions.electron === 'string' && versions.electron.length > 0
|
|
3280
|
+
}
|
|
3281
|
+
|
|
3267
3282
|
function detectRuntimeEnv(options = {}) {
|
|
3268
3283
|
const env = options.env ?? process.env
|
|
3269
3284
|
const platform = options.platform ?? process.platform
|
|
3285
|
+
const versions = options.versions ?? process.versions
|
|
3270
3286
|
const stdinIsTTY = options.stdinIsTTY ?? (process.stdin ? process.stdin.isTTY === true : false)
|
|
3271
3287
|
const stdoutIsTTY = options.stdoutIsTTY ?? (process.stdout ? process.stdout.isTTY === true : false)
|
|
3272
3288
|
// 默认探测器只在未被注入时执行;win32 直接短路(/.dockerenv 与 /proc 不存在),单测全部显式传原语。
|
|
@@ -3274,25 +3290,39 @@ function detectRuntimeEnv(options = {}) {
|
|
|
3274
3290
|
const cgroupText = options.cgroupText ?? (platform === 'win32' ? '' : (() => { try { return readFileSync('/proc/1/cgroup', 'utf8') } catch (_) { return '' } })())
|
|
3275
3291
|
|
|
3276
3292
|
const forced = typeof env.DSH_SERVICE_RUNTIME_ENV === 'string' ? env.DSH_SERVICE_RUNTIME_ENV.trim().toLowerCase() : ''
|
|
3277
|
-
|
|
3278
|
-
|
|
3293
|
+
const electronShell = forced === 'desktop' || isElectronRuntime(versions)
|
|
3294
|
+
// 桌面端标记只在成立时出现在返回对象里:其余平台的原返回逐字不变,既有三态用例与下游
|
|
3295
|
+
// deepEqual 断言不受影响。判据只喂给「重启 / 升级 / 诊断」的用户可见行为,不作安全门槛。
|
|
3296
|
+
const shell = (result) => (electronShell ? { ...result, electronShell: true } : result)
|
|
3297
|
+
|
|
3298
|
+
if (forced === 'manual') return shell({ platform, supervisorKind: null, manualStartLikely: true })
|
|
3299
|
+
if (forced === 'managed') return shell({ platform, supervisorKind: 'declared', manualStartLikely: false })
|
|
3300
|
+
// 显式声明桌面端:不再往下看 supervisor 标记与 TTY(桌面端那些信号都不代表会被拉起)。
|
|
3301
|
+
if (forced === 'desktop') return shell({ platform, supervisorKind: null, manualStartLikely: false })
|
|
3279
3302
|
|
|
3280
3303
|
for (const [kind, keys] of RUNTIME_SUPERVISOR_ENV) {
|
|
3281
3304
|
if (keys.some((key) => env[key] !== undefined && env[key] !== '')) {
|
|
3282
|
-
return { platform, supervisorKind: kind, manualStartLikely: false }
|
|
3305
|
+
return shell({ platform, supervisorKind: kind, manualStartLikely: false })
|
|
3283
3306
|
}
|
|
3284
3307
|
}
|
|
3285
3308
|
// /.dockerenv 与 /proc 只在 POSIX 内核上存在;win32 原生进程不做这两个探测。
|
|
3286
3309
|
if (platform !== 'win32') {
|
|
3287
|
-
if (dockerEnvExists === true) return { platform, supervisorKind: 'docker', manualStartLikely: false }
|
|
3288
|
-
if (/docker/i.test(cgroupText)) return { platform, supervisorKind: 'docker', manualStartLikely: false }
|
|
3289
|
-
if (/containerd|kubepods|lxc|podman/i.test(cgroupText)) return { platform, supervisorKind: 'container', manualStartLikely: false }
|
|
3310
|
+
if (dockerEnvExists === true) return shell({ platform, supervisorKind: 'docker', manualStartLikely: false })
|
|
3311
|
+
if (/docker/i.test(cgroupText)) return shell({ platform, supervisorKind: 'docker', manualStartLikely: false })
|
|
3312
|
+
if (/containerd|kubepods|lxc|podman/i.test(cgroupText)) return shell({ platform, supervisorKind: 'container', manualStartLikely: false })
|
|
3290
3313
|
}
|
|
3291
|
-
if (stdinIsTTY === true && stdoutIsTTY === true) return { platform, supervisorKind: null, manualStartLikely: true }
|
|
3292
|
-
return { platform, supervisorKind: null, manualStartLikely: false }
|
|
3314
|
+
if (stdinIsTTY === true && stdoutIsTTY === true) return shell({ platform, supervisorKind: null, manualStartLikely: true })
|
|
3315
|
+
return shell({ platform, supervisorKind: null, manualStartLikely: false })
|
|
3293
3316
|
}
|
|
3294
3317
|
|
|
3295
|
-
|
|
3318
|
+
// 桌面端不退出:壳把 Host 的任何非 0 退出判为崩溃,写 crash report 并弹原生「启动失败」对话框
|
|
3319
|
+
// (退出 / 重启 / 禁用第三方插件),没有自动重拉,且官方没有任何可编程重启 RPC。因此桌面端一律
|
|
3320
|
+
// 不 exit(42),把「已是最新状态、需要重启」这一事实交给调用方转述(客户端改示桌面端重启指引)。
|
|
3321
|
+
// 返回值:scheduled=false + desktopManaged=true 时调用方不得再启动恢复轮询(不会出现新实例)。
|
|
3322
|
+
function scheduleRestart(ctx, runtimeEnv) {
|
|
3323
|
+
if (runtimeEnv !== undefined && runtimeEnv !== null && runtimeEnv.electronShell === true) {
|
|
3324
|
+
return { scheduled: false, desktopManaged: true }
|
|
3325
|
+
}
|
|
3296
3326
|
const doExit = () => {
|
|
3297
3327
|
try {
|
|
3298
3328
|
// 退出码 42 交给 Docker/systemd/pm2 的重启策略处理。
|
|
@@ -3302,8 +3332,9 @@ function scheduleRestart(ctx) {
|
|
|
3302
3332
|
}
|
|
3303
3333
|
}
|
|
3304
3334
|
const timer = ctx.get('timer')
|
|
3305
|
-
if (timer !== undefined)
|
|
3306
|
-
|
|
3335
|
+
if (timer !== undefined) timer.timeout(doExit, 500)
|
|
3336
|
+
else doExit()
|
|
3337
|
+
return { scheduled: true, desktopManaged: false }
|
|
3307
3338
|
}
|
|
3308
3339
|
|
|
3309
3340
|
// ─── 技能管理(v0.22):扫描 / frontmatter 手术 / AI 补全 ─────────────────────
|
|
@@ -5209,7 +5240,7 @@ function apply(ctx, featureConfig) {
|
|
|
5209
5240
|
isEnabled: () => featureEnabled('backupMaintenance'),
|
|
5210
5241
|
runtimeEnv,
|
|
5211
5242
|
previousInstanceId: instanceId,
|
|
5212
|
-
scheduleRestart: () => scheduleRestart(ctx),
|
|
5243
|
+
scheduleRestart: () => scheduleRestart(ctx, runtimeEnv),
|
|
5213
5244
|
})
|
|
5214
5245
|
// 会话管理(v0.35):删除两段式计划(planId → {id, path, bytes}),TTL 过期自动驱逐。
|
|
5215
5246
|
const sessionDeletePlans = new Map()
|
|
@@ -5701,7 +5732,11 @@ function apply(ctx, featureConfig) {
|
|
|
5701
5732
|
if (invocation.rawInput.trim() !== '') return { kind: 'error', text: '/restart does not accept arguments.' }
|
|
5702
5733
|
const activity = collectActiveWork(ctx)
|
|
5703
5734
|
if (activity.hasActive) return { kind: 'error', text: `Restart refused: ${activity.items.length} active item(s) detected. Use the Service Control restart tab to review them.` }
|
|
5704
|
-
scheduleRestart
|
|
5735
|
+
// 桌面端不退出(见 scheduleRestart):命令反馈必须说清「要重启的是应用」,否则用户
|
|
5736
|
+
// 会以为宿主进程会自己回来。
|
|
5737
|
+
if (scheduleRestart(ctx, runtimeEnv).desktopManaged) {
|
|
5738
|
+
return { kind: 'success', text: 'DSH Desktop manages this process: quit and reopen the app to restart it.' }
|
|
5739
|
+
}
|
|
5705
5740
|
return { kind: 'success', text: 'Restart scheduled. The DSH Web process will exit in 0.5 seconds.' }
|
|
5706
5741
|
},
|
|
5707
5742
|
}), 'dsh-service restart command')
|
|
@@ -5885,6 +5920,11 @@ function apply(ctx, featureConfig) {
|
|
|
5885
5920
|
|
|
5886
5921
|
} },
|
|
5887
5922
|
'upgrade': { audit: true, handle: async (payload, rpcEndpoint) => {
|
|
5923
|
+
// 桌面端短路(先于任何 subprocess):桌面端升级动作要 spawn `dsh plugin --profile desktop add`,
|
|
5924
|
+
// 而 bundled runtime 的 bin 不进 Host PATH,且官方明确 CLI 不能启动/改写 profiles/desktop
|
|
5925
|
+
// (该 profile 由 Electron 独占)⇒ 必然失败。官方通道是桌面端自带的插件管理页,直接告诉
|
|
5926
|
+
// 用户走那条路,不产生任何 subprocess 调用。
|
|
5927
|
+
if (runtimeEnv.electronShell === true) return rpcFailure(new Error('desktop-managed-upgrade'))
|
|
5888
5928
|
try {
|
|
5889
5929
|
const value = await upgradePlugin(ctx, dshHome, runtimeEnv)
|
|
5890
5930
|
// 升级落地即作废更新缓存:下一次 check-update 要按新的磁盘版本重算「是否有新版本」,
|
|
@@ -5967,12 +6007,15 @@ function apply(ctx, featureConfig) {
|
|
|
5967
6007
|
return { ok: false, error: 'active-work', value: activity }
|
|
5968
6008
|
}
|
|
5969
6009
|
|
|
5970
|
-
scheduleRestart(ctx)
|
|
6010
|
+
const restart = scheduleRestart(ctx, runtimeEnv)
|
|
5971
6011
|
return {
|
|
5972
6012
|
ok: true,
|
|
5973
6013
|
value: {
|
|
5974
6014
|
// 只下发客户端真正消费的实例 id;重启反馈文案由客户端词典渲染(双语约束)。
|
|
5975
6015
|
instanceId,
|
|
6016
|
+
// 桌面端不退出(见 scheduleRestart):显式告知客户端「不会有新实例」,客户端据此
|
|
6017
|
+
// 改示桌面端重启指引、不启动恢复轮询(与升级落地那条 requiresManualRestart 同语义)。
|
|
6018
|
+
...(restart.desktopManaged ? { requiresManualRestart: true } : {}),
|
|
5976
6019
|
},
|
|
5977
6020
|
}
|
|
5978
6021
|
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gehennawu/dsh-service",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "DSH Web 运维面板:安全重启、健康监控、备份和 Linux 权限维护。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"build:client": "node scripts/build-client.mjs",
|
|
8
8
|
"check:client": "node scripts/check-client-source.mjs",
|
|
9
9
|
"pretest": "npm run build:client",
|
|
10
|
-
"test": "node --test",
|
|
10
|
+
"test": "node --test test/*.test.js",
|
|
11
11
|
"prepack": "npm run build:client"
|
|
12
12
|
},
|
|
13
13
|
"main": "./index.js",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"homepage": "https://github.com/gehennawu/dsh-service#readme",
|
|
54
54
|
"engines": {
|
|
55
55
|
"node": ">=22",
|
|
56
|
-
"dsh": ">=0.1.1-rc.2 <0.
|
|
56
|
+
"dsh": ">=0.1.1-rc.2 <0.2.0-rc.3"
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
59
|
"@deepseek-ai/schemastery": "^3.18.3"
|