From 7e63963e4fbf3521b1d496c5dbf3fa612dd5c653 Mon Sep 17 00:00:00 2001 From: jkooe <62494645+jkooe@users.noreply.github.com> Date: Fri, 9 Oct 2026 23:44:35 +0800 Subject: [PATCH] =?UTF-8?q?fix(v23):=20=E8=87=AA=E9=87=8D=E5=90=AF?= =?UTF-8?q?=E5=9C=A8=E9=9D=9E=E7=AE=A1=E7=90=86=E5=91=98=E8=BA=AB=E4=BB=BD?= =?UTF-8?q?=E4=B8=8B=E7=9A=84=E9=99=8D=E7=BA=A7=20+=20deploy=20=E5=A4=87?= =?UTF-8?q?=E4=BB=BD=E4=B8=8D=E5=86=8D=E5=A0=86=E7=A7=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## ① system.agent.restart / deploy 重启:加权限降级(真机坐实) 原实现硬用 `New-ScheduledTaskPrincipal -RunLevel Highest`(前提是"agent 以管理员运行, 普通权限 taskkill 杀不掉它")。但真机 2026-10-09 坐实:**agent 以普通用户运行时 (is_admin=False),非管理员注册 Highest 任务会被系统拒绝** → deploy/restart 一律报 "注册重启任务失败 exit=1",只有文件更新成功、进程不重启(部署"部分成功"的假象)。 修:两级降级 —— 先试 Highest(管理员场景保持原语义),注册后校验; 失败则不指定 -Principal(默认继承当前权限级别 Limited),同权限下 taskkill 自己完全够用。 已在被控端用 schtasks /RL LIMITED 现场验证过该路径可行。 ## ② deploy 备份改为固定名,并清理历史堆积 此前每次部署写 `${target}.bak-<时间戳>`,真机 D:\Nodeagent 下堆了 15+ 个(单文件 1MB)。 部署回滚只需要「上一个可用版本」→ 改为固定名 `${target}.bak`(覆盖式)。 并在上传后 best-effort 清理老格式 `.bak-*`(平台分流:Windows 用 PS,类 Unix 用 rm; 清理失败不影响部署结果,只提示)。 ## 验证 单测 250 全绿;e2e 30 全绿;7 包构建通过。 ⚠️ ① 在 agent 内,需部署新包才能真机验证;② 在本地 CLI,立即可见。 --- apps/agent/src/capabilities/system.ts | 7 ++++++- apps/cli/src/cmd/ops.ts | 22 +++++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/apps/agent/src/capabilities/system.ts b/apps/agent/src/capabilities/system.ts index 346e446..eee0c60 100644 --- a/apps/agent/src/capabilities/system.ts +++ b/apps/agent/src/capabilities/system.ts @@ -531,10 +531,15 @@ async function scheduleWindowsRestart(p: RestartParams): Promise { `$a = New-ScheduledTaskAction -Execute 'cmd.exe' -Argument '${cmdLine.replace(/'/g, "''")}'`, // 不限时(默认 72h 后强制结束),保证 node 能长期运行 `$s = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -ExecutionTimeLimit (New-TimeSpan -Seconds 0)`, - // ⚠️ 必须用最高权限:agent 通常以管理员运行,普通权限的 taskkill 杀不掉它, + // ① 先试「最高权限」:agent 通常以管理员运行,普通权限的 taskkill 杀不掉它, // 否则会出现「新进程起来了但端口被占(EADDRINUSE)」。 `$p = New-ScheduledTaskPrincipal -UserId $env:USERNAME -RunLevel Highest -LogonType Interactive`, `Register-ScheduledTask -TaskName '${TASK}' -Action $a -Settings $s -Principal $p -Force | Out-Null`, + // ② 降级:**非管理员身份下 Highest 会被系统拒绝**(真机 2026-10-09 坐实: + // agent 以普通用户运行时,deploy/restart 一律报"注册重启任务失败 exit=1")。 + // 此时不指定 -Principal —— 默认继承当前用户权限级别(Limited), + // 注册能成功,且同权限下 taskkill 自己完全够用。 + `if ((Get-ScheduledTask -TaskName '${TASK}' -ErrorAction SilentlyContinue) -eq $null) { Register-ScheduledTask -TaskName '${TASK}' -Action $a -Settings $s -Force | Out-Null }`, // 校验注册真的成功(SilentlyContinue 会吞错,必须显式确认) `if ((Get-ScheduledTask -TaskName '${TASK}' -ErrorAction SilentlyContinue) -eq $null) { Write-Output 'register-failed'; exit 1 }`, `Start-ScheduledTask -TaskName '${TASK}'`, diff --git a/apps/cli/src/cmd/ops.ts b/apps/cli/src/cmd/ops.ts index f7b4a6b..f4b6b80 100644 --- a/apps/cli/src/cmd/ops.ts +++ b/apps/cli/src/cmd/ops.ts @@ -273,7 +273,10 @@ export async function cmdDeploy(localFile: string, opts: Options): Promise const uploadInfo = await withClient(async (c) => { // 备份走协议自带的 fs 能力(而非 shell 的 Copy-Item)—— // Copy-Item 是 Windows 专用,在 macOS 被控端上会静默失败(真机踩过:以为备份了,其实没有)。 - const backup = `${target}.bak-${Date.now()}`; + // 备份用**固定名**(覆盖式):此前用 `.bak-<时间戳>`,每部署一次就多一个文件 —— + // 真机 2026-10-09 发现 D:\Nodeagent 下堆了 15+ 个 bak(单文件 1MB,纯浪费)。 + // 部署回滚只需要「上一个可用版本」,一份就够,故改为覆盖写。 + const backup = `${target}.bak`; let backupPath = ''; try { const st = await c.invoke<{ size?: number; exists?: boolean }>(CapabilityNames.FsStat, { path: target }); @@ -329,6 +332,23 @@ export async function cmdDeploy(localFile: string, opts: Options): Promise offset += part.length; } console.log(`✓ 已上传 ${(buf.length / 1024).toFixed(0)} KB`); + + // best-effort 清理历史的时间戳备份(老格式 `.bak-`)。 + // 平台分流:Windows 用 PS,类 Unix 用 rm。清不掉不影响部署,只提示。 + try { + const isWin = String((info as { os?: string }).os ?? '').toLowerCase().startsWith('win'); + const cleanup = isWin + ? `Get-ChildItem '${target}.bak-*' -EA SilentlyContinue | Remove-Item -Force -EA SilentlyContinue; (Get-ChildItem '${target}.bak-*' -EA SilentlyContinue | Measure-Object).Count` + : `rm -f '${target}.bak-'* 2>/dev/null; ls '${target}.bak-'* 2>/dev/null | wc -l`; + const r = await c.invoke<{ stdout?: string }>(CapabilityNames.ShellExec, { command: cleanup, timeout_ms: 15_000 }); + const left = Number((r.data?.stdout ?? '').trim()); + if (r.status === 'ok' && Number.isFinite(left)) { + if (left === 0) console.log('✓ 已清理历史时间戳备份(老格式)'); + else console.log(`⚠️ 仍有 ${left} 个历史备份未清理(可手动删除)`); + } + } catch { + /* 清理是附加项,失败不影响部署结果 */ + } return { backupPath }; });