@twinsearth/w2m-dsh-plugin 1.3.1 → 1.3.11
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/.claude-plugin/marketplace.json +3 -3
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +588 -0
- package/bin/w2m-maintenance.mjs +112 -0
- package/codex/mcp-servers.toml +1 -1
- package/deploy/maintenance/bootstrap.ps1 +30 -0
- package/deploy/maintenance/bootstrap.sh +59 -0
- package/deploy/server/w2m-maintenance.path +10 -0
- package/deploy/server/w2m-maintenance.service +20 -0
- package/deploy/windows/w2m-maintenance.xml +31 -0
- package/docs/COLLAB/COLLABORATION-MODEL.md +240 -0
- package/docs/COLLAB/EXECUTION-AUTH-MODEL.md +250 -0
- package/docs/COLLAB/M1-ACCEPTANCE-RUNBOOK.md +70 -0
- package/docs/COLLAB/M1-MAINTENANCE.md +313 -0
- package/docs/COLLAB/M2-TASK-GRANT.md +231 -0
- package/docs/COLLAB/M3-GIT-NATIVE-TASKS.md +291 -0
- package/docs/COLLAB/M4-PROJECT-ROOM.md +260 -0
- package/docs/COLLAB/M5-MULTI-CLIENT-PRIMITIVES.md +263 -0
- package/docs/COLLAB/M6-CROSS-NETWORK-RELIABILITY.md +231 -0
- package/docs/COLLAB/M7-METRICS-ACCEPTANCE.md +263 -0
- package/docs/COLLAB/TASK-MODEL.md +281 -0
- package/docs/MULTI-MACHINE-COLLABORATION-PLAN.md +186 -0
- package/docs/RELEASE-STABLE.md +193 -0
- package/docs/acceptance/1.3.10-beta.10/M1-LIVE-DIAG.json +27 -0
- package/docs/acceptance/1.3.10-beta.10/M1-LIVE-POSTUPDATE-DIAG.json +18 -0
- package/docs/acceptance/1.3.10-beta.10/M1-LIVE-RESTART.json +24 -0
- package/docs/acceptance/1.3.10-beta.10/M1-LIVE-UPDATE.json +24 -0
- package/docs/acceptance/1.3.11/M1-LIVE-DIAG.json +26 -0
- package/package.json +22 -4
- package/plugin.json +1 -1
- package/src/acceptance/evidence.mjs +79 -0
- package/src/agent/agent.mjs +64 -2
- package/src/agent/grant-gate.mjs +82 -0
- package/src/agent/maintenance-ferry.mjs +111 -0
- package/src/agent/outbox.mjs +138 -0
- package/src/agent/patch-artifact.mjs +53 -0
- package/src/agent/worktree.mjs +109 -0
- package/src/maintenance/actions.mjs +203 -0
- package/src/maintenance/grant.mjs +139 -0
- package/src/maintenance/operator-key.mjs +151 -0
- package/src/maintenance/protocol.mjs +204 -0
- package/src/maintenance/runner.mjs +189 -0
- package/src/plugin/tools.mjs +1 -1
- package/src/relay/blob-sessions.mjs +148 -0
- package/src/relay/merge-queue.mjs +137 -0
- package/src/relay/project-locks.mjs +179 -0
- package/src/relay/project-notifications.mjs +182 -0
- package/src/relay/project-reviews.mjs +161 -0
- package/src/relay/project-room.mjs +163 -0
- package/src/relay/reliability-metrics.mjs +180 -0
- package/src/relay/server.mjs +164 -1
- package/src/relay/state.mjs +128 -1
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
# 执行授权模型(Execution Authorization Model)设计规格 — v1.4.0-beta
|
|
2
|
+
|
|
3
|
+
> 状态:**模型级设计稿**。把 M1(运维通道)与 M2(任务级 grant)归纳成一个可复用的授权模型,并冻结线协议与失败语义。
|
|
4
|
+
> 版本:`1.4.0-beta.*`;实现按 M1(1.3.2)/ M2(1.3.3)分步交付,模型一致性验收在 1.4.0-beta 完成。
|
|
5
|
+
> 相关:`docs/COLLAB/M1-MAINTENANCE.md`、`docs/COLLAB/M2-TASK-GRANT.md`。
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. 问题陈述
|
|
10
|
+
|
|
11
|
+
现状只有一个执行授权来源:**机器本地静态 allow-list**。它带来三个无法回避的后果:
|
|
12
|
+
|
|
13
|
+
1. **远程运维不可能**:升级/重启/排障命令不在清单里,只能 SSH;清单空白的机器(TwinsEarth 实测)连 `node --version` 都拒绝,编队里"在线但不可用"。
|
|
14
|
+
2. **放宽清单是唯一出路,且不安全**:为了升级一次就把安装命令永久写进清单,等于给任何能调度任务的人一个长期入口。
|
|
15
|
+
3. **没有"谁授权了什么"的记录**:allow-list 是配置文件,不是事件;无法回答"这条命令是谁、在何时、为什么被允许"。
|
|
16
|
+
|
|
17
|
+
模型目标:引入**显式的、可审计的、带时限的执行授权**,与 allow-list 并存,且不改变"只执行 argv、不执行 shell 字符串"的红线。
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 2. 模型总览
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
┌────────────── 签发(控制面,操作员私钥)──────────────┐
|
|
25
|
+
│ grant / maintenance request(签名能力令牌) │
|
|
26
|
+
└──────────────────────────┬──────────────────────────┘
|
|
27
|
+
│ relay 传输(不解释内容)
|
|
28
|
+
▼
|
|
29
|
+
目标机 ┌────────────────── 授权判定 authorize(action, context) ──────────────────┐
|
|
30
|
+
│ 1. 硬红线检查(永远拒绝) │
|
|
31
|
+
│ 2. 能力校验:签名/主体/范围/时限/次数/重放/撤销 │
|
|
32
|
+
│ 3. 与 allow-list 组合:显式授权优先;无授权则看清单;都不中则拒绝 │
|
|
33
|
+
└──────────────────────────────┬───────────────────────────────────────┘
|
|
34
|
+
▼
|
|
35
|
+
执行面(二选一,权限不同)
|
|
36
|
+
A. agent 用户执行(argv)
|
|
37
|
+
B. root runner 执行(固定 action:update/restart/diag)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
核心概念:
|
|
41
|
+
|
|
42
|
+
| 概念 | 含义 |
|
|
43
|
+
|---|---|
|
|
44
|
+
| **Authority** | 操作员密钥(Ed25519),签名的唯一来源 |
|
|
45
|
+
| **Capability** | 签名令牌,表达"允许在哪些机器、执行什么、多久、几次" |
|
|
46
|
+
| **Subject** | 目标机器(machine_ids)/ 项目(后续 M3 的合并授权) |
|
|
47
|
+
| **Scope** | argv_hash + 执行约束(mode/cwd/write/timeout/env 白名单) |
|
|
48
|
+
| **Bounds** | TTL、max_uses、task 绑定、nonce、撤销 |
|
|
49
|
+
| **Decision** | 允许 / 拒绝(带统一错误码),拒绝默认、无副作用 |
|
|
50
|
+
| **Provenance** | 每次允许都记录来源:`allow-list` / `grant` / `maintenance`,进 ledger |
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## 3. 能力类型(本版本冻结两种)
|
|
55
|
+
|
|
56
|
+
### 3.1 能力 A:执行授权(grant)——agent 用户权限
|
|
57
|
+
|
|
58
|
+
- 主体:`machine_ids[]`
|
|
59
|
+
- 范围:`argv_hash`(sha256 of canonical argv)+ 可收紧的 `mode/cwd_rel/write/write_scope/timeout_ms`
|
|
60
|
+
- 边界:TTL ≤ 10 分钟、`max_uses`(按机器计)、可绑 `task_id`
|
|
61
|
+
- 执行者:agent(w2m 等非特权用户)
|
|
62
|
+
- 典型:空 allow-list 机器上执行 `node -e ...`、`npm --version`、诊断脚本
|
|
63
|
+
|
|
64
|
+
### 3.2 能力 B:维护请求(maintenance)——root/SYSTEM 权限
|
|
65
|
+
|
|
66
|
+
- 主体:`machine_id`(单机)
|
|
67
|
+
- 范围:固定 action 枚举 `update | restart | diag` 及其**白名单参数**(版本、sha256、URL 列表、目标单元)
|
|
68
|
+
- 边界:TTL ≤ 10 分钟、单次
|
|
69
|
+
- 执行者:root runner(独立单元,固定实现,解释 JSON 但不执行 shell)
|
|
70
|
+
- 典型:远程升级插件/relay、重启服务、收集诊断
|
|
71
|
+
|
|
72
|
+
> 两种能力共用同一签名载荷/规范化/密钥,但**权限不同、执行者不同、参数白名单不同**。这是模型的核心分离:让"跑一条命令"和"做一次系统维护"不共用危险通道。
|
|
73
|
+
|
|
74
|
+
### 3.3 未来能力(不在本版本)
|
|
75
|
+
|
|
76
|
+
- 项目级能力(合并队列的 merge-slot 授权)——由 M3 定义,沿用本模型(主体=project+branch,范围=merge 操作)。
|
|
77
|
+
- 角色委托(owner 签发子 key 给 maintainer)——v1.4.x 后续。
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## 4. 授权判定函数(冻结语义)
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
authorize(input) -> { allow: boolean, provenance, code?, detail? }
|
|
85
|
+
|
|
86
|
+
input = {
|
|
87
|
+
capability?: SignedCapability, // grant 或 maintenance,可能缺失
|
|
88
|
+
action: { kind: "exec"|"maintenance", argv?, constraints?, maintenance? },
|
|
89
|
+
context: { machine_id, task_id?, attempt, now, policy: { allow_list, red_lines, limits } },
|
|
90
|
+
state: { used_count, nonce_seen, revoked_set, pinned_operator_keys }
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
判定顺序(每一步失败都返回对应错误码,不执行、不落副作用):
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
1. 硬红线(任何来源都不能越过)
|
|
98
|
+
1.1 仅 argv 数组;禁止 shell 字符串/命令拼接
|
|
99
|
+
1.2 cwd 必须落在允许根内(项目目录/工作区),无路径穿越
|
|
100
|
+
1.3 禁止提权(agent 通道不能要求 root;maintenance 只走固定 action)
|
|
101
|
+
1.4 写入必须落在 write_scope 内;只读任务不得写
|
|
102
|
+
1.5 输出/超时/CPU/内存上限由机器策略决定,能力只能收紧
|
|
103
|
+
→ 违规:RED_LINE_VIOLATION
|
|
104
|
+
|
|
105
|
+
2. 有能力(capability 存在)
|
|
106
|
+
2.1 形状/大小/协议版本
|
|
107
|
+
2.2 验签(pinned operator key;未知 key_id → UNKNOWN_AUTHORITY)
|
|
108
|
+
2.3 主体匹配(machine_ids 含本机;maintenance 的 machine_id 必须相等)
|
|
109
|
+
2.4 范围匹配(argv_hash/参数逐项一致;约束不得比机器策略更宽)
|
|
110
|
+
2.5 时限与次数(issued_at ≤ now ≤ expires_at,TTL 上限;used_count < max_uses;task 绑定)
|
|
111
|
+
2.6 重放(nonce + capability_id 本地持久化)
|
|
112
|
+
2.7 撤销(relay 撤销集缓存窗口内命中)
|
|
113
|
+
→ 通过:allow,provenance = "grant" | "maintenance"
|
|
114
|
+
|
|
115
|
+
3. 无能力
|
|
116
|
+
3.1 allow-list 命中 → allow,provenance = "allow-list"
|
|
117
|
+
3.2 否则 → deny,code = NOT_AUTHORIZED(对 grant 场景为 GRANT_MISSING)
|
|
118
|
+
|
|
119
|
+
4. 记录
|
|
120
|
+
4.1 allow:ledger 记 provenance + capability_id + argv_hash + 结果
|
|
121
|
+
4.2 deny:ledger 记 code + 摘要(不记全量 argv 敏感内容,按红action策略脱敏)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
组合与优先级:
|
|
125
|
+
|
|
126
|
+
- **拒绝优先**:任一步拒绝即拒绝;能力不能覆盖硬红线。
|
|
127
|
+
- **能力优先于清单**(用于审计显式性),但结果相同;两者都允许时记录 `grant`(显式授权),并标记 `also_in_allow_list: true`。
|
|
128
|
+
- **清单是"常驻授权"**:其 provenance = `local-config`,不在 ledger 伪造操作员签名;模型上把 allow-list 视为"由机器 owner 预签发的长期能力",便于统一解释。
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## 5. 信任建立与密钥生命周期
|
|
133
|
+
|
|
134
|
+
### 5.1 操作员密钥
|
|
135
|
+
|
|
136
|
+
- 独立 Ed25519,与 operator token(调度凭据)**分离**:token 泄露不等于授权能力泄露,反之亦然。
|
|
137
|
+
- 私钥存放在控制端(DSH 配置/OS keychain);不进入 relay、不进入 agent。
|
|
138
|
+
- 公钥登记:控制端用 operator token 证明持有权 → relay 记录 `{key_id, public_key, created_at}`(ledger 事件)。
|
|
139
|
+
|
|
140
|
+
### 5.2 目标机信任(agent / root runner)
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
首选(推荐,M1/M2 终态):pairing-time pinning
|
|
144
|
+
pairing 响应携带 operator 公钥 → 机器在配对时固定(操作员在场,这是可信锚点)
|
|
145
|
+
轮换:新公钥必须由旧 pinned key 签名(rotation chain),agent 验链后更新
|
|
146
|
+
撤销:操作员登记 revoke;agent 同步后拒绝旧 key
|
|
147
|
+
|
|
148
|
+
beta 过渡(1.3.2/1.3.3 实现期):TOFU
|
|
149
|
+
首次验签时向 relay 拉取公钥并固定;文档明确"首次可被攻破的 relay 注入"
|
|
150
|
+
后续版本改为 pairing-time pinning 为默认,TOFU 仅作显式 fallback
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**已知限制(必须写进发布说明)**:TOFU 窗口内,被攻破的 relay 可注入伪造公钥。缓解:短 TTL、审计告警、尽快升级到 pinned 模式。
|
|
154
|
+
|
|
155
|
+
### 5.3 撤销
|
|
156
|
+
|
|
157
|
+
- relay 维护撤销集(capability_id、key_id);ledger 记录 `capability.revoked`。
|
|
158
|
+
- agent 缓存 30s;在 TTL 内撤销也生效(最多 30s 延迟)。
|
|
159
|
+
- 语义声明:撤销**不会**中断正在执行的动作,只阻止新的执行。
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## 6. 威胁模型(模型级)
|
|
164
|
+
|
|
165
|
+
| 威胁 | 能否得逞 | 依据/处置 |
|
|
166
|
+
|---|---|---|
|
|
167
|
+
| 恶意编队成员伪造指令 | ❌ | 无操作员私钥,签名不可伪造 |
|
|
168
|
+
| 被攻破的 relay 伪造授权 | ⚠️ TOFU 窗口内可注入公钥(文档声明);pinned 后不可 | 尽快 pinning;短 TTL;审计 |
|
|
169
|
+
| 被攻破的 relay 扣留/延迟指令 | ✅(可用性) | 授权不影响正确性;重试/审计可发现 |
|
|
170
|
+
| 被攻破的 agent | ❌ 提权 | agent 只能以自身用户执行;root 动作只走固定 maintenance action |
|
|
171
|
+
| 重放旧授权 | ❌ | nonce + capability_id 持久化 + task 绑定 + TTL |
|
|
172
|
+
| 改 argv 绕范围 | ❌ | argv_hash 校验 |
|
|
173
|
+
| 越界写文件 | ❌ | 硬红线 cwd/write_scope |
|
|
174
|
+
| 时钟回拨延长 TTL | ⚠️ | 服务端时间权威(relay);本地只做窗口判断;偏差容忍 ±60s |
|
|
175
|
+
| 操作员误授权 | ⚠️ | 展示完整 argv、TTL ≤10min、max_uses、撤销;高风险动作需二次确认 |
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## 7. 兼容与迁移
|
|
180
|
+
|
|
181
|
+
- **旧 relay**:忽略 grant/maintenance 字段 → 行为退回 allow-list;控制端检测后返回 `UNSUPPORTED_RELAY`。
|
|
182
|
+
- **旧 agent**:不认识字段 → allow-list 判定(维持 v1.3.1 行为),relay 可探测版本并提示 `UNSUPPORTED_AGENT`。
|
|
183
|
+
- **旧 allow-list**:模型上保留为常驻授权,行为不变;不需要迁移配置。
|
|
184
|
+
- **新安装**:默认仍是"空 allow-list + 无 pinned key",但配对时可选择 pin 操作员公钥(推荐);空清单机器不再"不可用"——只要操作员签一次能力即可。
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## 8. 验收(模型一致性,横跨 M1/M2)
|
|
189
|
+
|
|
190
|
+
### 正向(真实任务证据)
|
|
191
|
+
|
|
192
|
+
1. **空 allow-list + grant**:TwinsEarth 执行 `node -e "console.log(...)"`;ledger provenance=`grant`,记录 capability_id/argv_hash。
|
|
193
|
+
2. **空 allow-list + maintenance**:同机完成 `update`/`restart`/`diag`;provenance=`maintenance`,root runner 日志与 ledger 一致。
|
|
194
|
+
3. **普通 allow-list 命令**:provenance=`allow-list`,行为与 1.3.1 identical(对账对照)。
|
|
195
|
+
4. **撤销窗口**:签发→撤销→派发(≤30s 内)被拒;超过缓存窗口同样被拒。
|
|
196
|
+
5. **轮换链**:旧 key 签新 key → agent 更新 pinned;未签的换 key 被拒。
|
|
197
|
+
|
|
198
|
+
### 负向(模型矩阵,自动化)
|
|
199
|
+
|
|
200
|
+
```
|
|
201
|
+
硬红线:shell 字符串/路径穿越/提权/越界写 → RED_LINE_VIOLATION
|
|
202
|
+
能力:缺签/错签/未知 key/过期/未来/换机/换 argv/重放/超次数/撤销/任务绑定不符 →
|
|
203
|
+
NOT_AUTHORIZED / UNKNOWN_AUTHORITY / GRANT_* / MAINTENANCE_*
|
|
204
|
+
审计:拒绝不产生执行副作用;ledger 有 deny 记录
|
|
205
|
+
组合:能力拒绝时不回落 allow-list(防歧义);allow-list 命中但能力被篡改 → 拒绝(显式意图无效)
|
|
206
|
+
时钟:±61s 边界、issued_at 在未来、expires_at 恰好过期
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### 审计查询
|
|
210
|
+
|
|
211
|
+
- 给定 task_id → 能力来源、key_id、argv_hash、结果。
|
|
212
|
+
- 给定机器 → 当前 pinned key、最近能力使用、拒绝计数。
|
|
213
|
+
- 给定时间窗 → 所有 maintenance 动作与 grant 使用(导出 JSON)。
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## 9. 与三个模型的关系
|
|
218
|
+
|
|
219
|
+
| 模型 | 版本 | 依赖 | 说明 |
|
|
220
|
+
|---|---|---|---|
|
|
221
|
+
| 执行授权模型(本文) | v1.4.0-beta | 独立 | 定义"能否执行";M1/M2 是其两种实现 |
|
|
222
|
+
| 任务模型 | v1.4.1-beta | 本模型(合并/审查任务需授权) | 定义"执行什么、产出什么(worktree/patch/merge)" |
|
|
223
|
+
| 协作模型 | v1.4.2-beta | 本模型 + 任务模型 | 定义"谁和谁、在哪个项目/房间协作" |
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## 10. 交付物与版本映射
|
|
228
|
+
|
|
229
|
+
```
|
|
230
|
+
模型文档:docs/COLLAB/EXECUTION-AUTH-MODEL.md(本文)
|
|
231
|
+
实现拆分:
|
|
232
|
+
1.3.2-beta M1 运维通道(能力 B:固定 action + root runner)
|
|
233
|
+
1.3.3-beta M2 任务级 grant(能力 A:argv 授权 + agent 验签)
|
|
234
|
+
1.4.0-beta 模型一致性验收:统一错误码/审计查询/轮换链/撤销窗口/真实任务证据
|
|
235
|
+
(不新增功能;把 M1/M2 的行为对齐到本模型)
|
|
236
|
+
共享模块:
|
|
237
|
+
src/auth/operator-key.mjs 密钥生成/登记/轮换链/pin/撤销
|
|
238
|
+
src/auth/capability.mjs 两种能力的统一载荷、规范化、签发、验证
|
|
239
|
+
src/auth/authorize.mjs 判定函数(纯函数,输入输出可单测)
|
|
240
|
+
src/auth/audit.mjs 审计查询(ledger 上的投影)
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## 11. 开放问题(实现前确认)
|
|
244
|
+
|
|
245
|
+
1. **pinned vs TOFU 的默认**:1.3.2/1.3.3 实现期是否直接做 pairing-time pinning(更安全,改动稍大)?
|
|
246
|
+
2. **操作员密钥创建 UX**:`dsh` 命令生成并写入 OS keychain,还是插件面板生成?
|
|
247
|
+
3. **能力签发 UX**:一次一条命令(简单)还是"策略模板"(如"升级到某版本"一键签一组)?
|
|
248
|
+
4. **谁可以签**:仅 owner 操作员,还是允许 maintainer 使用预设策略?
|
|
249
|
+
5. **max TTL**:10 分钟是否合适(运维场景可能更长)?
|
|
250
|
+
6. **审计可见性**:ledger 里的 argv 是否永久保存明文(敏感参数)还是只存 hash + 前缀?
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# M1 real-machine acceptance runbook (v1.3.10-beta.2+)
|
|
2
|
+
|
|
3
|
+
> Goal: prove, on real machines, that a signed maintenance request updates/restarts/diagnoses a machine **without SSH and
|
|
4
|
+
> without widening the allow-list**. This needs one privileged bootstrap per machine; after that everything is remote.
|
|
5
|
+
|
|
6
|
+
## 0. Prerequisites
|
|
7
|
+
|
|
8
|
+
1. The machine already runs the plugin (any version >= 1.3.10-beta.2) and the agent is paired.
|
|
9
|
+
2. The relay runs a version that carries the maintenance fields (**v1.3.10-beta.2 or later**). A 1.3.1 relay silently drops them.
|
|
10
|
+
3. You have one-time root (Linux) / Administrator (Windows) access to run the bootstrap script.
|
|
11
|
+
|
|
12
|
+
## 1. Bootstrap (once per machine)
|
|
13
|
+
|
|
14
|
+
Linux:
|
|
15
|
+
|
|
16
|
+
~~~bash
|
|
17
|
+
# copy the package to /opt/w2m/app first (deploy-w2m.sh), then:
|
|
18
|
+
sudo bash /opt/w2m/app/deploy/maintenance/bootstrap.sh \
|
|
19
|
+
--app-dir /opt/w2m/app \
|
|
20
|
+
--machine-id <machine_id> \
|
|
21
|
+
--operator-key /tmp/operator.pub.pem
|
|
22
|
+
~~~
|
|
23
|
+
|
|
24
|
+
Windows (Administrator PowerShell):
|
|
25
|
+
|
|
26
|
+
~~~powershell
|
|
27
|
+
powershell -ExecutionPolicy Bypass -File C:\w2m\app\deploy\maintenance\bootstrap.ps1 \
|
|
28
|
+
-AppDir C:\w2m\app -MachineId <machine_id> -OperatorKey C:\w2m\operator.pub.pem
|
|
29
|
+
~~~
|
|
30
|
+
|
|
31
|
+
The bootstrap installs a SYSTEM path/task that executes only update/restart/diag, creates the queue directories the agent
|
|
32
|
+
writes to, and pins the operator public key. It never installs a shell.
|
|
33
|
+
|
|
34
|
+
## 2. Dispatch a signed request
|
|
35
|
+
|
|
36
|
+
Use scripts/m1-acceptance.mjs on the control side:
|
|
37
|
+
|
|
38
|
+
~~~powershell
|
|
39
|
+
$env:W2M_OPERATOR_TOKEN = '<operator token>'
|
|
40
|
+
$env:W2M_DEVICE_TOKEN = '<this control machine device token>'
|
|
41
|
+
node scripts/m1-acceptance.mjs --machine-id <target machine_id> --action diag
|
|
42
|
+
~~~
|
|
43
|
+
|
|
44
|
+
Expected: task ends done, evidence file written under docs/acceptance/<version>/, and the target machine's
|
|
45
|
+
/var/lib/w2m/maintenance/done/<request_id>.json holds the runner result.
|
|
46
|
+
|
|
47
|
+
## 3. Acceptance cases (record evidence for each)
|
|
48
|
+
|
|
49
|
+
| Case | Request | Expected |
|
|
50
|
+
|---|---|---|
|
|
51
|
+
| M1-DIAG | action=diag | done; result sections include version/services/journal/disk |
|
|
52
|
+
| M1-RESTART | action=restart targets=agent | done; machine heartbeats again |
|
|
53
|
+
| M1-UPDATE | action=update version=<next> sha=<asset sha> | done; package.json version changes; app.old kept |
|
|
54
|
+
| M1-UPDATE-ROLLBACK | same but broken sha/artifact | failed; previous tree still served (rollback) |
|
|
55
|
+
| N1-EXPIRED | request with expires_at in the past | refused MAINTENANCE_EXPIRED, nothing executed |
|
|
56
|
+
| N2-REPLAY | same request_id twice | second refused MAINTENANCE_REPLAY |
|
|
57
|
+
| N3-TAMPER | change a param after signing | refused MAINTENANCE_BAD_SIGNATURE |
|
|
58
|
+
| N4-WRONG-MACHINE | machine_id of another host | refused MAINTENANCE_WRONG_MACHINE |
|
|
59
|
+
|
|
60
|
+
## 4. What "green" means
|
|
61
|
+
|
|
62
|
+
- Every positive case has a result file whose status is ok and whose assertions match the table.
|
|
63
|
+
- Every negative case has a named refusal and **left the machine unchanged** (version, service state).
|
|
64
|
+
- The ledger contains maintenance_summary for each task and the evidence JSON records task_id + assertions.
|
|
65
|
+
|
|
66
|
+
## 5. Known limitations to state in the release note
|
|
67
|
+
|
|
68
|
+
- The bootstrap itself needs privilege; the channel removes SSH **after** it.
|
|
69
|
+
- TOFU key pinning: first key seen wins until rotated by a signed rotation. Pairing-time pinning is the planned hardening.
|
|
70
|
+
- No automatic rollback if the machine cannot be reached after restart; the operator re-dispatches restart (idempotent).
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
# M1 运维通道(v1.3.2-beta)设计规格
|
|
2
|
+
|
|
3
|
+
> 状态:**设计稿**。本文只定义"怎么做、边界在哪、如何验收",不含实现。
|
|
4
|
+
> 上游:`docs/MULTI-MACHINE-COLLABORATION-PLAN.md` 的 M1。
|
|
5
|
+
> 版本:`1.3.2-beta.*`(beta 默认走 GitHub prerelease,不入 npm)。
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. 目标与非目标
|
|
10
|
+
|
|
11
|
+
### 目标(本版本必须做到)
|
|
12
|
+
|
|
13
|
+
1. **远程升级**:操作员不登录机器,即可把编队里任意一台机器上的本项目插件 / relay / agent 升级到指定版本,并完成重启。
|
|
14
|
+
2. **远程重启**:按固定顺序重启本项目的 systemd 单元(Linux)或服务(Windows),不依赖 SSH。
|
|
15
|
+
3. **远程诊断**:收集一组**预先定死**的只读诊断信息(版本、服务状态、日志尾部、磁盘、端口),结果回传 relay。
|
|
16
|
+
4. **无需生前 allow-list**:目标机器即使 allow-list 为空,也能完成上述操作(这是本版本存在的理由)。
|
|
17
|
+
5. **可审计**:每次请求有签名、有发起者、有 hash、有结果,落在 relay 事件日志与机器本地日志里,事后可追。
|
|
18
|
+
|
|
19
|
+
### 非目标(明确不做,避免范围蔓延)
|
|
20
|
+
|
|
21
|
+
- 不做任意命令执行(那是 M2 的"任务级授权"领域)。
|
|
22
|
+
- 不做文件下发/上传(诊断结果走既有 blob 通道,不新增文件传输语义)。
|
|
23
|
+
- 不接管 DSH 自身的安装/升级(只处理**本插件**及其 relay/agent 服务;DSH 本体升级属于另一条线)。
|
|
24
|
+
- 不实现 Windows 之外的平台(含 macOS)在 1.3.2 的范围;Windows 用"计划任务 + 队列目录"的等价设计,macOS 放 1.3.3 之后(见 §10)。
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## 2. 拓扑与信任边界
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
操作员(控制端 DSH 插件)
|
|
32
|
+
│ 1. 签名 maintenance request(operator 私钥)
|
|
33
|
+
▼
|
|
34
|
+
relay(可信传输,但不可信为授权方)
|
|
35
|
+
│ 2. 作为普通 task 的附件下发(maintenance_request 字段),或本地投放
|
|
36
|
+
▼
|
|
37
|
+
机器上的 agent(非特权用户,如 w2m)
|
|
38
|
+
│ 3. 只做一件事:把已验证形状的 request 写成文件放进队列目录
|
|
39
|
+
▼
|
|
40
|
+
队列目录 /var/lib/w2m/maintenance/queue(root 可写,agent 只可写文件、不可执行)
|
|
41
|
+
│ 4. root/SYSTEM 权限的独立单元(path unit 或 timer)轮询
|
|
42
|
+
▼
|
|
43
|
+
维护执行器(固定 action:update / restart / diag)
|
|
44
|
+
│ 5. 验签 → 校验 action 参数 → 执行 → 写结果
|
|
45
|
+
▼
|
|
46
|
+
结果目录 /var/lib/w2m/maintenance/done
|
|
47
|
+
│ 6. agent 读取结果 → 作为 task 结果回报 relay
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**信任边界**:
|
|
51
|
+
|
|
52
|
+
| 主体 | 能做什么 | 不能做什么 |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| 操作员私钥 | 签发 maintenance request | 不直接接触目标机 shell |
|
|
55
|
+
| relay | 转发、存 hash、记账 | 不能伪造 request(无操作员私钥);不能改 action 参数(hash 绑定) |
|
|
56
|
+
| agent(w2m 用户) | 写 request 文件、读 done 文件 | 不能执行维护动作、不能改 root 单元、不能提权 |
|
|
57
|
+
| root 维护单元 | 执行白名单 action、验证签名 | 不解释用户输入、不跑任意 shell/脚本、不接受路径穿越 |
|
|
58
|
+
| 其他编队成员 | 看到任务/结果 | 无法代签、无法重放 |
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## 3. 请求协议(Request Envelope)
|
|
63
|
+
|
|
64
|
+
### 3.1 文件格式
|
|
65
|
+
|
|
66
|
+
请求文件为 UTF-8 JSON,**单行、≤ 64 KiB**,文件名 `<unix_ms>-<request_id>.json`:
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"protocol": "w2m-maintenance/v1",
|
|
71
|
+
"request_id": "01M6...26 字符 ULID",
|
|
72
|
+
"action": "update",
|
|
73
|
+
"params": {
|
|
74
|
+
"target": "plugin",
|
|
75
|
+
"version": "1.3.2-beta.1",
|
|
76
|
+
"artifact_sha256": "64 hex",
|
|
77
|
+
"artifact_urls": ["https://registry.npmjs.org/...tgz", "https://github.com/.../....tgz"],
|
|
78
|
+
"restart_after_update": true
|
|
79
|
+
},
|
|
80
|
+
"machine_id": "目标机器 machine_id(必须完全匹配)",
|
|
81
|
+
"nonce": "16-64 字符随机串",
|
|
82
|
+
"issued_at": "2026-10-21T13:00:00Z",
|
|
83
|
+
"expires_at": "2026-10-21T13:10:00Z",
|
|
84
|
+
"issued_by": "operator 名(仅审计展示,不参与授权)",
|
|
85
|
+
"signature": {
|
|
86
|
+
"alg": "ed25519",
|
|
87
|
+
"key_id": "operator 公钥指纹前 16 hex",
|
|
88
|
+
"value_base64": "签名值"
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 3.2 签名覆盖范围
|
|
94
|
+
|
|
95
|
+
签名对象是**规范化 JSON**(键按字典序、无空白、UTF-8),覆盖除 `signature` 外的所有字段:
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
canonical = JSON.stringify(sortKeys({protocol, request_id, action, params, machine_id, nonce, issued_at, expires_at, issued_by}))
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
规则:
|
|
102
|
+
|
|
103
|
+
- 校验方**重新规范化**,不信任文件里的字段顺序。
|
|
104
|
+
- `action` 与 `params` 都在签名内 → relay 或传输层改不了动作与参数。
|
|
105
|
+
- `machine_id` 在签名内 → 请求不能换机重放。
|
|
106
|
+
- `expires_at` 在签名内 → 操作员给的时限不可延长。
|
|
107
|
+
|
|
108
|
+
### 3.3 校验顺序(fail-closed,任何一步失败都拒绝并写结果)
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
1. 文件大小/形状/协议版本
|
|
112
|
+
2. 签名验证(操作员公钥,key_id 找公钥;未知 key_id 拒绝)
|
|
113
|
+
3. machine_id 与本地一致
|
|
114
|
+
4. nonce 未使用(本地已执行记录 + 结果目录去重;跨重启保留)
|
|
115
|
+
5. request_id 未执行过(幂等)
|
|
116
|
+
6. 时间:issued_at ≤ now ≤ expires_at,且 expires_at - issued_at ≤ 10 分钟
|
|
117
|
+
7. action ∈ {update, restart, diag},params 按 action 白名单逐字段校验
|
|
118
|
+
8. exec 前再次检查 artifact sha256 与 action 约束(TOCTOU 防护)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## 4. 动作规格(Action Semantics)
|
|
124
|
+
|
|
125
|
+
### 4.1 `update`
|
|
126
|
+
|
|
127
|
+
参数:`target ∈ {plugin, relay, agent}`、`version`、`artifact_sha256`、`artifact_urls[]`(最多 3 个,按顺序尝试)、`restart_after_update`。
|
|
128
|
+
|
|
129
|
+
步骤(全部固定、无 shell 拼接):
|
|
130
|
+
|
|
131
|
+
1. 下载 artifact 到临时文件(仅 https;每个 URL 超时 60s;总大小上限 200 MiB)。
|
|
132
|
+
2. 计算 sha256,必须与参数完全一致,否则删文件并失败(`UPDATE_HASH_MISMATCH`)。
|
|
133
|
+
3. **当前版本备份**:目录软链/时间戳副本(`/opt/w2m/app.old`),保留最近 1 份。
|
|
134
|
+
4. 解包到 staging 目录,校验包内 `package.json.version === version`(防"hash 对但版本错")。
|
|
135
|
+
5. 原子换入(rename),保留旧树。
|
|
136
|
+
6. 如 `restart_after_update=true` → 执行 restart 动作(同一套固定流程)。
|
|
137
|
+
7. **健康检查**:本机服务 `is-active` + relay `/healthz` 200 + 版本回报;失败则**自动回滚**到旧树并重启,结果中标记 `rolled_back: true`。
|
|
138
|
+
8. 写结果文件(成功/失败/回滚、耗时、版本、sha)。
|
|
139
|
+
|
|
140
|
+
### 4.2 `restart`
|
|
141
|
+
|
|
142
|
+
参数:`targets ∈ {rabbit, agent, all}`、`wait_healthy`(默认 true)。
|
|
143
|
+
|
|
144
|
+
固定顺序(Linux):
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
rabbit(relay)→ 等待 /healthz 200(≤60s)
|
|
148
|
+
agent(localside)→ 等待心跳上报(≤120s,经 relay 可观测)
|
|
149
|
+
stun 可选 → 等待端口监听
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
禁止:重启任意其他单元;禁止 `systemctl restart` 参数拼接。
|
|
153
|
+
|
|
154
|
+
### 4.3 `diag`
|
|
155
|
+
|
|
156
|
+
固定只读清单(输出上限每项 32 KiB,总计 ≤ 256 KiB):
|
|
157
|
+
|
|
158
|
+
- `version`:插件/relay/agent 的 package.json 版本、安装路径、文件 mtime
|
|
159
|
+
- `services`:各单元 `is-active`、`MainPID`、最近一次启动时间
|
|
160
|
+
- `journal`:每个单元最近 100 行(截断)
|
|
161
|
+
- `disk`:`df -h` 关键路径
|
|
162
|
+
- `ports`:本机监听中与 w2m 相关的端口
|
|
163
|
+
- `allowlist`:当前 allow-list(只报告,不改动)
|
|
164
|
+
- `relay`:本地缓存的 relay 可达性/healthz 摘要
|
|
165
|
+
|
|
166
|
+
结果作为文本 + JSON 写入结果目录,agent 以 blob 回报(超过内联阈值自动走 blob 通道)。
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## 5. 执行单元(Privileged Runner)
|
|
171
|
+
|
|
172
|
+
### 5.1 Linux(systemd)
|
|
173
|
+
|
|
174
|
+
两个文件,安装时由 deploy 脚本放置:
|
|
175
|
+
|
|
176
|
+
```
|
|
177
|
+
/etc/systemd/system/w2m-maintenance.path → 监听 queue 目录
|
|
178
|
+
/etc/systemd/system/w2m-maintenance.service → 执行 runner(Type=oneshot)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
约束:
|
|
182
|
+
|
|
183
|
+
- `User=root`(或专用 `w2m-maint` 用户 + 指定 capability),`NoNewPrivileges=yes` 仍可(不 exec shell)。
|
|
184
|
+
- `ProtectSystem=strict` + `ReadWritePaths=/opt/w2m /var/lib/w2m/maintenance`;`ProtectHome=yes`;`PrivateTmp=yes`;`RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX`;`SystemCallFilter` 白名单按需收紧。
|
|
185
|
+
- runner 只调用内部 JS 模块的固定函数(`update/restart/diag`),**不 exec 用户输入**;health check 用内置 HTTP client。
|
|
186
|
+
- 同一时刻只执行一个请求:`ExecCondition`/flock 串行化;执行期间 path unit 继续触发会被排队(目录里多个文件按 mtime 顺序处理)。
|
|
187
|
+
|
|
188
|
+
### 5.2 Windows(计划任务)
|
|
189
|
+
|
|
190
|
+
- 一个以 `SYSTEM` 运行的计划任务(或 Windows 服务),每分钟轮询 `%ProgramData%\w2m\maintenance\queue`。
|
|
191
|
+
- 同样的 JSON 协议;动作实现用 Node(包内固定模块);服务控制用 SCM API(不调用外部 shell)。
|
|
192
|
+
- 结果写 `done` 目录;agent 读结果回报。
|
|
193
|
+
|
|
194
|
+
### 5.3 目录权限
|
|
195
|
+
|
|
196
|
+
| 路径 | 属主 | 权限 |
|
|
197
|
+
|---|---|---|
|
|
198
|
+
| `queue/` | root | 0770 root:w2m(agent 可写文件,不可删/改他人文件) |
|
|
199
|
+
| `done/` | root | 0750 root:w2m |
|
|
200
|
+
| `state/`(已执行 nonce/request_id) | root | 0700 |
|
|
201
|
+
|
|
202
|
+
agent 写请求时使用 `O_CREAT|O_EXCL`,文件名含 request_id,避免覆盖;结果由 runner 以 root 写。
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## 6. 与 relay / agent 的集成(本版本的最小改动)
|
|
207
|
+
|
|
208
|
+
1. **agent**:收到 task 时若存在合法形状的 `maintenance_request` 字段(大小、JSON、协议版本),不走 allow-list,而是:
|
|
209
|
+
- 写入 queue 目录;
|
|
210
|
+
- 轮询 done 目录(≤120s);
|
|
211
|
+
- 把结果文件作为 task 结果回报(status ok/failed,附结果 blob)。
|
|
212
|
+
2. **relay**:透传 `maintenance_request` 字段(限 64 KiB),把 request_id 与 hash 写入 ledger(`maintenance.request` 事件);不解释内容。
|
|
213
|
+
3. **插件工具**(控制端):新增一个调用入口(名字在实现时确定)负责:生成 request → 签名 → 随 task 派发 → 等待结果;**不新增任意命令能力**。
|
|
214
|
+
4. **兼容性**:旧 relay 直接丢弃未知任务字段(不报错)→ 新控制端对旧 relay 返回明确错误 `MAINTENANCE_UNSUPPORTED_RELAY`,不静默失败。
|
|
215
|
+
|
|
216
|
+
> 说明:本版本操作员签名的验证在**目标机 root runner** 内完成;agent 只搬文件。这样即使 agent 被攻破,也无法伪造维护动作。
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## 7. 状态机与幂等
|
|
221
|
+
|
|
222
|
+
```
|
|
223
|
+
pending → claimed(runner 读取并加锁) → executing → done | failed | rolled_back
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
- `request_id` 幂等:done 目录已存在同名结果 → 直接返回既有结果,不重复执行。
|
|
227
|
+
- 重放:nonce + request_id 同时进入本地已执行集合(`state/`,跨重启保留),重复请求返回 `MAINTENANCE_REPLAY`。
|
|
228
|
+
- 超时:executing 超过 15 分钟由 runner 自终止并写 `failed`(`MAINTENANCE_TIMEOUT`)。
|
|
229
|
+
- 崩溃恢复:启动时扫描 `claimed/` 下的残留文件,转为 `failed` 并记录。
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## 8. 验收(本版本必须用真实任务触发)
|
|
234
|
+
|
|
235
|
+
### 8.1 功能验收(真实机器)
|
|
236
|
+
|
|
237
|
+
1. **空 allow-list 机器升级**:在一台 allow-list 为空的机器(验收对象:TwinsEarth / jp-shared)上,操作员签一次 `update` 请求 → 目标机完成插件升级 → 重启 → 版本回报 `1.3.2-beta.x`。全程不需要 SSH。
|
|
238
|
+
2. **relay 升级**:同样流程升级 relay 主机(验收对象:jp-shared),relay_id 变化、uptime 归零、`/healthz` 200、任务状态从持久化恢复。
|
|
239
|
+
3. **重启**:对 agent/relay 分别发 `restart`,验证固定顺序与健康等待。
|
|
240
|
+
4. **诊断**:发 `diag`,结果包含版本/服务/日志/磁盘,并作为 blob 可读取。
|
|
241
|
+
5. **审计**:ledger 中有 `maintenance.request`(request_id、hash、机器、结果状态),与机器本地日志一致。
|
|
242
|
+
|
|
243
|
+
### 8.2 负向验收(必须自动化,进单测/集成测试)
|
|
244
|
+
|
|
245
|
+
```
|
|
246
|
+
· 未签名 / 签名错误 / 未知 key_id → 拒绝
|
|
247
|
+
· 过期 / 未来 issued_at / TTL>10min → 拒绝
|
|
248
|
+
· machine_id 不匹配 → 拒绝
|
|
249
|
+
· 重放同一 request_id / nonce → 拒绝
|
|
250
|
+
· action 不在白名单 → 拒绝
|
|
251
|
+
· update: sha 不符 / 包内版本不符 / URL 非 https → 拒绝,不落盘执行
|
|
252
|
+
· restart: 目标越界(如 sshd)→ 拒绝
|
|
253
|
+
· diag: 输出超限 → 截断并标记
|
|
254
|
+
· relay 透传被篡改 params → 签名不符 → 拒绝
|
|
255
|
+
· agent 伪造 request 文件(无签名)→ 拒绝
|
|
256
|
+
· 执行失败 → 回滚成功且结果标记 rolled_back
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### 8.3 兼容与回归
|
|
260
|
+
|
|
261
|
+
- 旧版 agent + 新版 relay:不崩溃,返回 `MAINTENANCE_UNSUPPORTED_AGENT`。
|
|
262
|
+
- 新版 agent + 旧版 relay:任务里无字段,行为与 v1.3.1 一致。
|
|
263
|
+
- 无 DSH / 无 relay 的纯本地路径:手工把请求文件放入 queue 也能执行(验收脚本模拟)。
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## 9. 失败模式与回滚
|
|
268
|
+
|
|
269
|
+
| 失败 | 处置 |
|
|
270
|
+
|---|---|
|
|
271
|
+
| 下载失败/超时 | 重试下一个 URL(≤3),全失败 → failed,不改动现有安装 |
|
|
272
|
+
| sha 不符 | 删除临时文件,failed(不换树) |
|
|
273
|
+
| 换树后健康检查失败 | 自动回滚到 app.old + 重启,结果 `rolled_back` |
|
|
274
|
+
| 重启后 agent 不心跳 | relay 侧任务超时;控制端可再发一次 restart(幂等) |
|
|
275
|
+
| 请求文件损坏 | runner 拒绝并写 failed,不执行 |
|
|
276
|
+
| 队列目录被清空 | 本地 `state/` 仍能防重放;结果目录丢失只影响审计展示,不影响执行 |
|
|
277
|
+
| 操作员私钥泄露 | 轮换 operator key;旧 key_id 进撤销列表(request 校验时拒绝) |
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## 10. 开放问题(实现前需要确认)
|
|
282
|
+
|
|
283
|
+
1. **操作员签名密钥的形态**:复用现有 operator token 派生 Ed25519?还是引入独立的 operator key(推荐,职责分离)?密钥存 DSH 配置还是系统 keychain?
|
|
284
|
+
2. **Windows 优先级**:1.3.2-beta 是否先只做 Linux(TwinsEarth 是 Windows,需要确认它是否必须在本版本覆盖)?若必须,Windows runner 是否用计划任务(SYSTEM)即可。
|
|
285
|
+
3. **macOS**:1.3.3+ 再做,还是 1.3.2 一并设计(实现可延后)?
|
|
286
|
+
4. **升级目标范围**:本版本只升级"本插件包",还是也支持 relay 包(同一 tarball)与 DSH 本体?建议先只做本插件 + relay(同一包),DSH 本体单列。
|
|
287
|
+
5. **健康检查的判定**:relay `/healthz` 200 是否足够?是否要加"任务能派发成功"的端到端探针(更真实但更重)。
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## 11. 交付物清单(实现阶段)
|
|
292
|
+
|
|
293
|
+
```
|
|
294
|
+
src/maintenance/protocol.mjs request 形状/规范化/签名与验证(与 M2 共用)
|
|
295
|
+
src/maintenance/actions/update.mjs
|
|
296
|
+
src/maintenance/actions/restart.mjs
|
|
297
|
+
src/maintenance/actions/diag.mjs
|
|
298
|
+
src/maintenance/runner.mjs root 侧入口,固定 action 分派 + 状态机
|
|
299
|
+
src/agent/agent.mjs 新增 maintenance_request 搬运与结果回报(小改动)
|
|
300
|
+
src/relay/state.mjs + server.mjs 字段透传 + ledger 事件(小改动)
|
|
301
|
+
src/plugin/tools.mjs 控制端发起入口(签名→派发→等待)
|
|
302
|
+
deploy/server/w2m-maintenance.path / .service(Linux)
|
|
303
|
+
deploy/windows/w2m-maintenance.xml(Windows 计划任务)
|
|
304
|
+
test/maintenance-protocol.test.mjs / test/maintenance-runner.test.mjs
|
|
305
|
+
test/maintenance-e2e.test.mjs(本地模拟 relay + 真 root 不可行时的降级方案)
|
|
306
|
+
docs/COLLAB/M1-MAINTENANCE.md(本文)
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
## 12. 版本与发布
|
|
310
|
+
|
|
311
|
+
- `1.3.2-beta.1` 起;beta 政策:GitHub prerelease,不入 npm。
|
|
312
|
+
- 门禁:preflight 两遍 GREEN + M1 验收任务实跑(至少 Linux 一台 + 可选 Windows 一台)。
|
|
313
|
+
- 向下兼容:不改变现有 task/agent 协议,只新增可选字段与新文件;旧机器不受影响。
|