fix(v22): 自更新在 Windows 上必须**就地覆写**而非 rename(真机暴露的平台差异)
## 现象(真机 192.168.1.100 上拉取式更新) `✗ E_EXECUTION_FAILED: 替换脚本失败` —— 下载与哈希校验都过了,挂在最后一步「替换」。 ## 根因 Windows **不允许重命名/替换正被运行进程打开的文件**(EPERM/EBUSY),而 node.exe 正持有 agent.mjs。POSIX 可以 unlink 打开中的文件,所以本地(macOS)测不出来。 这也解释了为什么推送式 `deploy` 一直没问题:它走**就地覆写**,而覆写打开中的文件 在 Windows 上是允许的(只有删除/重命名被禁)。 ## 修复 - 改为 `writeFileSync(scriptPath, buf)` 就地覆写(先备份 + 已校验的暂存内容) - **写后校验**:重算入口文件 sha256,与刚校验过的下载比对;不一致立即从备份回滚 - 清理暂存文件;错误信息带上 backup_path / staged_path 便于人工兜底 ## 顺带的真机结论(都已在 192.168.1.100 上验证) - **被控端访问不了 github.com**(`fetch failed`)→ 国内网络现实;改用局域网/自建镜像即可 (本轮用 `http://192.168.1.100:8081/...` 拉取成功 ✓)→ 故 URL 参数化是必需的,不是花活 - **拉取式更新在 Windows 上可用**(内容确实变化:构建哈希 4833b0b5 → 216328d5 ✓, 重启后复核通过 ✓) - **Windows 音频能力真机跑通**(Core Audio COM):🔇 静音 · 音量 77%,可读回真实状态 - **证书指纹钉住**:普通 agent 更新不影响钉住(证书未变 → 仍一致 ✓);只有证书重建才触发 `E_CERT_MISMATCH` —— 符合设计意图
This commit is contained in:
1 parent
4106ada876
commit
a56d67c012
1 file changed
+25
-2
@@ -167,15 +167,38 @@ export async function agentUpdate(args: Args): Promise<unknown> {
|
||||
detail: err instanceof Error ? err.message : String(err),
|
||||
});
|
||||
}
|
||||
// ⚠️ 必须**就地覆写**,不能用 rename 覆盖 —— Windows 不允许重命名/替换「正被运行进程
|
||||
// 打开的文件」(EPERM/EBUSY),而 node.exe 正持有 agent.mjs。真机踩过:rename 必失败。
|
||||
// 覆写打开中的文件是允许的(只有删除/重命名被禁),这也正是推送式 deploy 能工作的原因。
|
||||
try {
|
||||
renameSync(staged, scriptPath);
|
||||
writeFileSync(scriptPath, buf);
|
||||
} catch (err) {
|
||||
throw new CapabilityError(ErrorCodes.EXECUTION_FAILED, '替换脚本失败', {
|
||||
throw new CapabilityError(ErrorCodes.EXECUTION_FAILED, '写入脚本失败(已保留备份,可回滚)', {
|
||||
detail: err instanceof Error ? err.message : String(err),
|
||||
backup_path: backup,
|
||||
staged_path: staged,
|
||||
});
|
||||
}
|
||||
// 写后校验:内容必须与刚校验过的下载一致,否则立即回滚(防止截断/半写)
|
||||
const written = createHash('sha256').update(readFileSync(scriptPath)).digest('hex');
|
||||
if (written !== got) {
|
||||
try {
|
||||
copyFileSync(backup, scriptPath); // 回滚
|
||||
} catch {
|
||||
/* 回滚失败时靠 backup_path 人工兜底 */
|
||||
}
|
||||
throw new CapabilityError(ErrorCodes.EXECUTION_FAILED, '写入后校验不一致,已自动回滚', {
|
||||
expected: got.slice(0, 12),
|
||||
actual: written.slice(0, 12),
|
||||
backup_path: backup,
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
unlinkSync(staged); // 暂存文件已并入入口文件,及时清理
|
||||
} catch {
|
||||
/* 清理失败不影响正确性 */
|
||||
}
|
||||
|
||||
audit({
|
||||
type: 'agent.update',
|
||||
|
||||
Reference in new issue
Block a user