Files
panel/pkg/firewall
耗子andClaude Opus 5 d54f72e0ef fix: 恢复 shell 包的格式串检查并修正停机时的任务定性
go vet 的 printf 分析器有两个硬条件:包装器签名必须是 (format string,
args ...any),且函数体内不能给 format 形参赋值。pkg/shell 两条都不满足——
老实现的 shell = fmt.Sprintf(shell, args...) 踩了后者,上一轮抽出的
buildShell(shell string, args []any) 踩了前者。结果是 330 多个调用点的格式
串从来没被检查过,在 fe7cce62 上用探针验证过确实如此。

改掉签名与写法后检查生效,扫出的 23 处都不是真错配,只是「非常量格式串」。
其中 7 处本就在做格式化(路径用 + 拼好再传进去),还原成真正的格式串;其余
命令串是上游拼好的整条,为它们恢复 shell.Exec / ExecWithOutput / ExecWithPipe。

2fc68ed9 删掉的那个 Exec 删的是第二份实现:它自带 exec.CommandContext 与
ApplyEnv,没有 newCmd 的 setpgid、cmd.Cancel 和 WaitDelay,正是孙子进程杀不掉
的来源,合并掉是对的。现在恢复的只是 newCmd + runBuffered 的一行入口,没有可
漂移的行为,包内 ExecWithLog / ExecWithLogAppend 早就是这个命名档位。继续用
"%s" 透传还有个副作用:preCheckArg 按整值精确匹配单个元字符设计,整条命令送
进去过检必然恒真。

停机时正在跑的任务被记成 Failed 并跳过清理命令。systemd 默认 KillMode=
control-group,SIGTERM 发给整个 cgroup,任务的 bash 往往比运行器的 ctx 取消
先死,cmd.Wait() 返回时 taskCtx.Err() 还是 nil。根因是 runner 的 ctx 要经过
「信号 → quit 通道 → 主协程 select → runnerCancel」这条长链,改为直接挂在
signal.NotifyContext 上,取消与命令被杀几乎同时;定性前保留 200ms 宽限期兜住
剩余的调度差。关停顺序随之不再是决定性因素,上一轮为此做的调整可以撤掉。

CLI 此前用 context.TODO()。newCmd 统一 setpgid 之后 Ctrl+C 只打到前台进程组,
子进程照跑而 CLI 已经退出。改用 signal.NotifyContext,首次信号取消 ctx 整组
杀掉,再按一次恢复默认处理,可强退卡在不可取消段里的命令。

其余:

- 关停三段各 30 秒最坏叠到 90 秒,正好撞上 systemd 默认的 TimeoutStopSec,
  改为共用一个预算;runCancelShell 的清理超时同样从 60 秒收到 30 秒,否则
  停机时清理必被 Wait 放弃后的进程退出砍断
- nginx -t 与 apachectl configtest 的输出在 stderr,上一轮改成 out, _ := 只
  拿 stdout 永远是空串,补 2>&1
- apache 两处逐字相同的 reload 块抽成 reloadConfig;environment.go 的
  typ → 可执行文件映射不再与 IsInstalled 各存一份

已知未改:pkg/webserver 的 Dialect 已有 ConfigTest 与 reload,nginx/apache 仍
在 app 层手拼命令(两者的 apachectl 路径已经漂移,一个裸命令一个全路径),
caddy 与 openlitespeed 早已改用 d.Reload;收敛它会动到用户可见文案与 gotext
msgid,另行处理。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 17:57:44 +08:00
..