|
| 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