Skip to content

Commit b65ad55

Browse files
committed
2 parents 6d6426d + 42cb046 commit b65ad55

23 files changed

Lines changed: 2865 additions & 13 deletions

docs/superpowers/plans/2026-04-26-bash-bin-build-systemd-manager-enhancements.md

Lines changed: 1299 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 320 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
1+
# Bash Bin Build 与 systemd-service-manager 增强设计
2+
3+
## Summary
4+
5+
本设计补齐 Bash 工具的统一构建入口,并增强 `systemd-service-manager` 的可观察性与失败恢复能力。
6+
7+
核心方向是保持职责边界清晰:`Manage-BinScripts.ps1` 继续只负责 `.ps1` / `.py``bin` shim 同步;Bash 工具通过新增的 `scripts/bash/build.sh` 统一构建;根目录 `install.ps1` 在安装流程中调用这个 Bash 构建入口。`systemd-service-manager` 本身继续保留自己的模块化源码和局部 `build.sh`,由统一入口调度。
8+
9+
## Context
10+
11+
当前仓库已经存在三类 `bin` 产物来源:
12+
13+
- `Manage-BinScripts.ps1` 扫描 `.ps1` / `.py`,生成 PowerShell shim。
14+
- `scripts/node` 通过 Node 构建流程生成 `bin/rule-loader` 等包装器。
15+
- `scripts/bash/systemd-service-manager/build.sh` 独立构建 `bin/systemd-service-manager`
16+
- `scripts/bash/aliyun-oss-put.sh` 这类单文件 Bash 脚本目前没有统一复制到 `bin` 的流程。
17+
18+
这种结构能工作,但 Bash 构建没有统一入口,根安装流程也没有显式刷新 Bash 工具。用户还指出 `systemd-service-manager list` 目前只显示名称,不方便判断服务实际运行命令;同时希望补充重试能力。
19+
20+
## Goals
21+
22+
- 新增 `scripts/bash/build.sh`,作为 Bash 工具的统一构建入口。
23+
-`install.ps1` 调用 `scripts/bash/build.sh`,安装时自动刷新 Bash `bin` 产物。
24+
- Bash 构建支持并行执行,并可限制并发数,默认根据 CPU 核心数推导。
25+
- Bash 构建清单支持两类目标:目录内 `build.sh` 构建型目标,以及单文件 `.sh` 复制型目标。
26+
- `systemd-service-manager list` 输出更多配置摘要,包括命令、目标、调度、scope 与重启策略。
27+
- `systemd-service-manager list --json` 输出结构化数据,便于脚本消费与测试断言。
28+
- `systemd-service-manager restart` 作为一等生命周期命令纳入文档、help 与测试覆盖。
29+
- `systemd-service-manager` 增加明确的 retry 配置模型,避免服务重启和 timer task 重试语义混乱。
30+
- 更新相关 README 与模板,让用户知道新字段和构建入口。
31+
32+
## Non-Goals
33+
34+
- 不把 Bash 构建逻辑塞进 `Manage-BinScripts.ps1`
35+
- 不在本轮统一重构 PowerShell、Node、Bash 三类构建为同一个跨语言构建器。
36+
- 不实现全局插件注册表或动态扫描所有目录下的任意 `build.sh`
37+
- 不改变已有 `systemd-service-manager install/start/stop/restart/status/logs` 的命令语义。
38+
- 不默认启用 timer task retry,避免改变现有定时任务失败行为。
39+
40+
## Chosen Approach
41+
42+
采用“Bash 独立统一构建入口 + systemd 管理器小步增强”的方案。
43+
44+
`scripts/bash/build.sh` 维护一个显式构建清单。第一版纳入 `scripts/bash/systemd-service-manager/build.sh` 和单文件脚本 `scripts/bash/aliyun-oss-put.sh`。后续 Bash 工具可以按需加入。显式清单比目录全量扫描更可控,能避免误执行示例、临时目录或未完成工具里的 `build.sh`
45+
46+
`install.ps1` 在同步 PowerShell/Python shim 之后调用 Bash 构建入口,再继续 Node 构建和 PATH 配置。这样安装流程从用户视角仍是一条命令,但内部职责没有混在一起。
47+
48+
## Bash Build Entry
49+
50+
新增文件:
51+
52+
```text
53+
scripts/bash/build.sh
54+
```
55+
56+
第一版构建清单:
57+
58+
```text
59+
build:systemd-service-manager:scripts/bash/systemd-service-manager/build.sh
60+
copy:aliyun-oss-put:scripts/bash/aliyun-oss-put.sh
61+
```
62+
63+
构建目标分为两类:
64+
65+
- `build`:目标目录或工具自带 `build.sh`。统一入口直接调用该脚本,由子构建负责生成自己的 `bin` 产物。
66+
- `copy`:单文件 `.sh`。统一入口把源文件复制到 `bin/<name>`,默认去掉 `.sh` 扩展,并执行 `chmod +x`
67+
68+
`copy` 目标保留源文件内容,不生成包装器。这样单文件脚本的 shebang、注释、参数解析和相对当前工作目录行为都保持原样。
69+
70+
命令形态:
71+
72+
```bash
73+
scripts/bash/build.sh
74+
scripts/bash/build.sh --jobs 2
75+
scripts/bash/build.sh --list
76+
scripts/bash/build.sh --only systemd-service-manager
77+
```
78+
79+
参数含义:
80+
81+
- `--jobs <n>`:限制并发构建数,必须是大于 0 的整数。
82+
- `--list`:只列出可构建工具,不执行构建。
83+
- `--only <name>`:只构建指定工具,便于本地调试。
84+
85+
环境变量:
86+
87+
- `BASH_BUILD_JOBS`:未传 `--jobs` 时作为并发数覆盖值。
88+
89+
默认并发数:
90+
91+
```text
92+
jobs = max(1, min(cpu_count, task_count))
93+
```
94+
95+
CPU 核心数探测顺序:
96+
97+
1. Linux 优先使用 `nproc`
98+
2. macOS / BSD 优先使用 `getconf _NPROCESSORS_ONLN`
99+
3. 失败时回退到 `1`
100+
101+
并行执行策略:
102+
103+
- 每个构建任务后台执行。
104+
- 每个任务输出写入独立临时日志。
105+
- 调度器最多同时运行 `jobs` 个任务。
106+
- 所有任务结束后统一打印成功/失败摘要。
107+
- 任一任务失败时,`scripts/bash/build.sh` 返回非零退出码。
108+
109+
## Build Logging
110+
111+
`scripts/bash/build.sh` 的日志要明确展示“输入是什么、解析成什么、实际做了什么”。并行构建时子任务输出会进入独立日志文件,主进程负责打印稳定摘要,避免多任务输出交织。
112+
113+
启动日志必须包含:
114+
115+
- 原始参数,例如 `args=--jobs 2 --only systemd-service-manager`
116+
- 项目根目录、`bin` 输出目录、临时日志目录。
117+
- 解析后的模式:`list=true/false``only=<name|all>`
118+
- 并发来源:`jobs=<n>`,并标明来自 `--jobs``BASH_BUILD_JOBS` 或 CPU 自动推导。
119+
- 本次选中的目标数量与目标清单。
120+
121+
`--list` 输出必须包含每个目标的:
122+
123+
- `name`
124+
- `type`,即 `build``copy`
125+
- `source`
126+
- `output`,copy 目标为 `bin/<name>`,build 目标为子构建自管产物。
127+
128+
每个任务的日志摘要必须包含:
129+
130+
- `START <name>`:目标类型、源路径、预期动作。
131+
- `ACTION <name>``run build.sh``copy source -> bin/<name>`
132+
- `DONE <name>`:退出码、耗时、关键产物路径。
133+
- `FAIL <name>`:退出码、耗时、子任务日志路径。
134+
135+
最终摘要必须包含:
136+
137+
- 总任务数、成功数、失败数、跳过数。
138+
- 成功任务名称。
139+
- 失败任务名称与日志路径。
140+
- 总耗时。
141+
142+
日志格式以稳定前缀为主,例如 `[bash-build][info]``[bash-build][error]`。时间戳可以有,但测试不依赖时间戳。
143+
144+
单文件复制策略:
145+
146+
- 源文件必须是普通文件,并以 `.sh` 结尾。
147+
- 输出文件默认是 `bin/<name>`,其中 `<name>` 来自清单,不从路径临时推导。
148+
- 输出文件覆盖旧产物,确保安装时总能拿到最新版本。
149+
- Unix 平台对输出文件执行 `chmod 0755`
150+
151+
## Install Integration
152+
153+
`install.ps1` 新增 `Install-BashScripts` 函数,负责调用:
154+
155+
```powershell
156+
bash ./scripts/bash/build.sh
157+
```
158+
159+
如果当前环境没有 `bash`
160+
161+
- Windows 原生环境打印 warning,不中断安装。
162+
- Linux/macOS 环境打印 error,并将 Bash 构建视为失败。
163+
164+
安装顺序调整为:
165+
166+
1. 配置项目根目录 PATH。
167+
2. 执行 `Manage-BinScripts.ps1 -Action sync -Force`
168+
3. 执行 `scripts/bash/build.sh`
169+
4. 构建 `scripts/node` 工具。
170+
5. 配置 `bin` PATH。
171+
6. 执行 nbstripout、AutoHotkey、Shell 配置等后续步骤。
172+
173+
## systemd-service-manager List
174+
175+
`list` 默认输出从“仅名称”升级为可扫描摘要。
176+
177+
建议文本输出:
178+
179+
```text
180+
Services
181+
- api | scope=system | restart=always/3s | command=/usr/bin/env bash -lc 'node server.js'
182+
183+
Timers
184+
- cleanup | scope=system | schedule=0 3 * * * | target=task | command=/usr/bin/find /tmp/myapp -type f -mtime +7 -delete
185+
- restart-api | scope=system | schedule=@daily | target=service:api | action=restart
186+
```
187+
188+
`list --json` 输出数组结构:
189+
190+
```json
191+
[
192+
{
193+
"type": "service",
194+
"name": "api",
195+
"scope": "system",
196+
"command": "/usr/bin/env bash -lc 'node server.js'",
197+
"restart": "always",
198+
"restartSec": "3s"
199+
}
200+
]
201+
```
202+
203+
设计取舍:
204+
205+
- 默认文本输出面向人读,保持紧凑。
206+
- JSON 输出面向脚本,字段稳定;缺失值统一保留字段并置为 `null`,方便消费者处理。
207+
- `SSM_DEBUG_DUMP_CONFIG=1` 的测试辅助路径保留,不和正式 `--json` 混用。
208+
209+
## systemd-service-manager Restart
210+
211+
`restart` 必须作为一等生命周期命令保留并验证。
212+
213+
要求:
214+
215+
- 顶层 help 展示 `restart`
216+
- README 示例包含 `systemd-service-manager restart api --project /path/to/app`
217+
- 源码入口与构建产物都能执行 `restart`
218+
- `restart <name>` 在 service 与 timer 中只命中一个对象时允许自动推断类型。
219+
- `restart service <name>` / `restart timer <name>` 支持显式指定类型。
220+
- `restart` 的 system scope 写操作沿用现有 sudo 自动提权规则。
221+
222+
如果实现中已经存在 `restart`,本轮只补齐文档与回归测试;如果发现分发、构建产物或帮助文本遗漏,则按上述要求补齐。
223+
224+
## Retry Model
225+
226+
Service 重试继续使用 systemd 原生字段:
227+
228+
- `RESTART`
229+
- `RESTART_SEC`
230+
231+
Timer task 新增可选字段:
232+
233+
- `RETRY_ATTEMPTS`
234+
- `RETRY_DELAY_SEC`
235+
236+
行为规则:
237+
238+
- 默认不启用 timer task retry。
239+
-`TARGET_TYPE=task``RETRY_ATTEMPTS` 大于 `1` 时,渲染出的 task service 使用轻量 Bash wrapper 执行 `COMMAND`
240+
- wrapper 在命令失败时等待 `RETRY_DELAY_SEC` 秒后重试。
241+
- 所有尝试失败后返回最后一次命令的退出码。
242+
- `TARGET_TYPE=service` 的 timer 不支持 `RETRY_ATTEMPTS`,因为它只是触发 `systemctl restart/start/stop`,失败恢复应交给目标 service 的 `Restart=` 或人工处理。
243+
244+
默认值:
245+
246+
- `RETRY_ATTEMPTS` 未设置时视为 `1`
247+
- `RETRY_DELAY_SEC` 未设置时视为 `5`
248+
249+
## Error Handling
250+
251+
Bash 构建入口:
252+
253+
- 构建脚本不存在时报错并返回非零。
254+
- `copy` 目标源文件不存在、不是 `.sh` 或复制失败时报错并返回非零。
255+
- `--jobs``BASH_BUILD_JOBS` 非法时报错并返回非零。
256+
- 参数解析失败时打印收到的原始参数和支持的用法。
257+
- 任一子构建失败时打印失败工具名、退出码与日志路径。
258+
- 构建全部成功时打印产物摘要。
259+
260+
`install.ps1`
261+
262+
- 调用 Bash 构建失败时输出清晰错误。
263+
- Windows 无 `bash` 时只 warning,因为不是所有 Windows 安装都需要 Bash 工具。
264+
- Linux/macOS 无 `bash` 或 Bash 构建失败时返回失败状态,避免用户以为 `bin/systemd-service-manager` 已刷新。
265+
266+
`systemd-service-manager`
267+
268+
- `list` 遇到单个无效配置时整体失败,避免展示半可信列表。
269+
- retry 字段非法时在解析阶段失败。
270+
- timer service target 上配置 retry 时失败并提示该字段只适用于 `TARGET_TYPE=task`
271+
272+
## Testing
273+
274+
需要覆盖以下测试:
275+
276+
- `scripts/bash/build.sh --list` 能列出 `systemd-service-manager`
277+
- `scripts/bash/build.sh --list` 输出目标的 `name``type``source``output`
278+
- `scripts/bash/build.sh --jobs 1` 能构建 `bin/systemd-service-manager`
279+
- `scripts/bash/build.sh --jobs 1` 日志包含原始参数、解析后的 jobs、目标清单、任务动作与最终摘要。
280+
- `scripts/bash/build.sh --only aliyun-oss-put` 能把 `scripts/bash/aliyun-oss-put.sh` 复制为可执行的 `bin/aliyun-oss-put`
281+
- `copy` 目标日志包含 `copy source -> bin/<name>`
282+
- 多个 fake build 任务时,并发数不超过 `--jobs`
283+
- 子构建失败时统一入口返回非零,并打印失败摘要。
284+
- `install.ps1` 能调用 Bash 构建入口;缺少 `bash` 的平台分支用 mock 或最小断言覆盖。
285+
- `systemd-service-manager list` 输出包含 service command、timer command / target 与 schedule。
286+
- `systemd-service-manager list --json` 输出可解析 JSON。
287+
- `systemd-service-manager restart` 在源码入口和构建产物中都有 help、类型推断与显式类型路径覆盖。
288+
- service retry 字段仍渲染为 `Restart=` / `RestartSec=`
289+
- timer task retry 字段生成 wrapper,并在字段非法时失败。
290+
291+
按仓库规则,本次实现涉及 `scripts/bash/**``install.ps1``tests/**/*.ps1` 与 systemd-manager Vitest,因此完成后需要执行:
292+
293+
```bash
294+
pnpm run qa:systemd-service-manager
295+
pnpm qa
296+
```
297+
298+
如果改动触及 PowerShell 测试或 `install.ps1` 行为测试,还需要执行:
299+
300+
```bash
301+
pnpm test:pwsh:all
302+
```
303+
304+
## Rollout
305+
306+
第一阶段:
307+
308+
- 新增 `scripts/bash/build.sh`
309+
- 接入 `install.ps1`
310+
- 补充 Bash 构建入口测试与文档。
311+
-`scripts/bash/aliyun-oss-put.sh` 作为单文件 copy 目标纳入 `bin` 产物刷新。
312+
313+
第二阶段:
314+
315+
- 增强 `systemd-service-manager list``list --json`
316+
- 补齐 `systemd-service-manager restart` 的文档、help 与回归测试。
317+
- 增加 retry 配置解析与渲染。
318+
- 更新模板与 README。
319+
320+
这两个阶段可以在同一个实现计划中完成,但代码提交时应尽量按职责拆分,便于回滚与 review。

0 commit comments

Comments
 (0)