ServerBox 自定义命令完全指南:在服务器详情页运行任意 Shell 命令
2026/9/16 15:03:42 网站建设 项目流程

ServerBox 自定义命令完全指南:在服务器详情页运行任意 Shell 命令

【免费下载链接】flutter_server_boxServerBox - server status & toolbox项目地址: https://gitcode.com/GitHub_Trending/fl/flutter_server_box

ServerBox(本仓库 flutter_server_box)允许你为每台服务器添加一组自定义 Shell 命令,命令的输出会随状态数据一起定期刷新,直接显示在服务器详情页上。本文围绕 docs/src/content/docs/zh/advanced/custom-commands.md 展开,结合 App 端编辑器、Monitor agent 的 Rust 实现与生成的状态脚本,讲解存储机制、编辑流程、编写规范、安全边界与旧格式迁移,帮助你安全地把这台服务器变成可编程的状态面板。

存储位置:命令是服务器上的文件,而不是 App 里的配置

自定义命令的存储设计是整个功能的基石。每条命令对应服务器上的一个文件,位于~/.config/server_box/custom_cmds目录(Windows 上为$env:USERPROFILE\.config\server_box\custom_cmds)。这是唯一的存储位置:App 不会在本地另存命令,打开编辑器时从服务器读取目录内容,保存时直接写回服务器。

该路径的 Unix 与 Windows 两种表达式统一定义在 crates/sbm_parser/src/script.rs:

pub const CUSTOM_CMD_DIR_UNIX: &str = "$HOME/.config/server_box/custom_cmds"; pub const CUSTOM_CMD_DIR_WINDOWS: &str = "(Join-Path $env:USERPROFILE '.config\\server_box\\custom_cmds')";

选择「文件」而非「脚本内嵌文本」有一个关键工程原因,见同文件注释:自定义命令是数据(文件),不是脚本代码的一部分。这样「用户手滑打错一个命令」只会破坏那一条命令,而不会破坏整张状态页;同时用户配置永远不会改变脚本本身的字节内容,脚本只是运行期读取这个目录。

由此带来的两个直接后果,你需要事先清楚:

  • 编辑时服务器必须可访问。服务器不可达时,编辑器无法读取或写回,界面会提示无法保存(对应 Dart 编辑器注释「rather than collecting edits that would have nowhere to go」)。
  • App 与 Monitor agent 共用同一组命令。Monitor agent 的网页面板编辑的也是这个目录,生成的状态脚本也从这里读取,因此两边天然共享、无需任何同步步骤。

编辑命令:App 端操作流程

  1. 打开服务器编辑页,选择自定义命令 → 编辑
  2. 在编辑器中添加、重命名、修改或拖动命令调整顺序。
  3. 点击保存。

每条命令由名称Shell 命令两部分组成,名称会作为服务器详情页上对应输出的标签。

命令顺序会被保存,并决定状态页的显示顺序,因此编辑器使用可拖动列表(ReorderableListView),而不是普通的键值对表单。编辑器实现位于 lib/view/page/server/custom_cmds.dart,其加载与保存分别通过ShellFuncManager.readCustomCmds/installCustomCmds走 FFI 调用 Rust 生成脚本,再经 SSH 在远端执行,逻辑在 lib/data/model/app/scripts/shell_func.dart。

App 端的校验规则(lib/view/page/server/custom_cmds.dart)与 Monitor 后端(见下文)保持一致:

  • 名称与命令内容都不能为空;
  • 名称长度上限64 个字符(名称会经 base64url 编码后写入文件名,编码会膨胀约 1/3,64 字符是 255 字节文件名预算下的安全值);
  • 名称不允许重复(一个名称对应一个文件,重名会导致后者静默覆盖前者);
  • 名称不允许首尾空白。

磁盘上的真实形态:排序前缀与编码名称

命令在服务器上不是以「命令原文」作文件名存储的。生成脚本时,Rust 侧custom_cmd_file_name(crates/sbm_parser/src/script.rs)将每条命令写成:

NNNNN_<base64url(名称)>
  • 5 位数字前缀用于排序:命令间隔为CUSTOM_CMD_ORDER_STEP = 100(稀疏编号),这样拖动命令调整顺序时只需重命名个别文件,不必重编号所有文件——这在文件位于 SSH 连接另一端时尤其重要。
  • 名称经过 base64url 编码,因此命令可以叫任意名字,且名称中的任何字符都不会进入 Shell 上下文(杜绝注入)。
  • Windows 平台文件额外带.ps1扩展名(&运算符无法执行无扩展名文件),扩展名不属于编码名称的一部分。
  • 目录中不属于本系统的杂散文件会被静默跳过,不会让编辑器崩溃。

特殊名称:server_card_top_right

名称为server_card_top_right的命令不会与其他自定义命令一起显示,其输出会显示在首页服务器卡片的右上角

其消费逻辑在 lib/view/page/server/tab/utils.dart:App 从状态中取出status.customCmds['server_card_top_right'],并取输出的最后一行val.split('\n').lastOrNull)作为单行字符串展示;若该命令不存在,则依次回退到温度(优先preferTempDev指定设备,否则首个温度传感器)与 uptime,以|拼接。

因此编写这条命令时,请确保其最终输出为一行短文本——它会被放在卡片右上角,多行输出中只有最后一行可见。

编写建议与示例

文档给出了四类实战写法,全部可以直接使用:

使用绝对路径(避免 PATH 差异导致找不到命令):

/usr/local/bin/my-script.sh

支持 Shell 管道(命令经/bin/sh执行,管道、重定向、awk 等标准工具均可用):

ps aux | sort -rk 3 | head -5

格式化输出(让展示更精炼):

uptime | awk -F'load average:' '{print $2}'

限制输出量(输出会随状态数据一起传输与存储):

tail -20 /var/log/syslog

性能约束上,文档明确:最好在一秒内完成,因为命令会在每次状态刷新时执行。

底层执行机制:读目录、跑文件、限量、限时

要理解「命令怎么被执行」,需要看生成脚本中自定义命令段的实现(Unix 分支在 crates/sbm_parser/src/script.rs,Windows 分支在 crates/sbm_parser/src/script.rs)。核心设计是:脚本本身不内嵌任何命令文本,而是在SbStatus函数中遍历目录并按文件名排序逐个执行文件:

  • 文件按NNNNN_前缀自然排序,即用户编排的执行顺序;脚本完全不解析内容即可得到顺序。
  • sh "$f"方式运行(而非直接执行文件):无需设置可执行位,且在noexec挂载点上也能工作。
  • 每条命令的段标记为SrvBoxCusCmdSep.b64.<base64url(名称)>,输出经 base64 包裹后再进入状态流,命令输出中即使恰好打印了SrvBoxSep.cpu这类内置标记,也不会污染内置状态段。
  • 执行时限 5 秒:优先使用timeout 5 sh "$f";无timeout命令时退化为后台进程 + 进程树清理(kill_tree/setsid分组),先 TERM 再 KILL。
  • 输出上限 64 KiBCUSTOM_CMD_MAX_OUTPUT_BYTES,见 crates/sbm_parser/src/script.rs):输出先写入mktemp临时文件,用ulimit -f限制文件大小,再用head -c 65536截取后 base64 输出,避免子进程持续占用状态管道。
  • 自定义命令只注入SbStatus函数(普通状态轮询),不进入SbStatusExt扩展段——这与文档「随状态数据一起定期刷新」的描述一致。

解析端(crates/sbm_parser/src/script.rs)将输出按段拆分为「键 → 输出」的有序列表;自定义命令的键被命名空间为SrvBoxCusCmdSep.<名称>,保证一条叫cpudisk的自定义命令不会覆盖内置同名状态段。若命令输出了伪造的内置标记,解析时保留先出现的真实内置段,防止被篡改覆盖。

安全性:命令以谁的权限运行?

自定义命令是「在服务器上安排定时执行的代码」,因此安全模型必须讲清楚:

命令以 App 连接服务器时使用的身份运行:SSH 连接使用 SSH 用户,Monitor agent 连接使用运行 agent 的用户。任何以该身份可达的资源和权限,命令同样可达。

在 Monitor 服务器上,权限门控更严格(monitor/src/api/custom_cmds.rs):

  • 读取GET /api/v1/custom-cmds)只需面板登录即可——它只是披露命令内容,不涉及机器执行;
  • 写入PUT /api/v1/custom-cmds)要求 agent 具备full_access权限:agent 已开启终端访问,且请求通过安全传输(TLS),或明确配置了allow_insecure。每次写入都会校验一次(UI 提示只是提示,不是安全边界),并写入审计日志(仅记录命令名称、不记录命令正文,防止用户凭据进入日志)。

向该目录添加一个文件,等同于安排代码以 agent 用户身份在每次刷新时执行——这是full_access门控的理由,它与 Shell 和/exec需要同一份授权,而不是一个更弱的第二授权。

写入的原子性与可靠性(monitor/src/monitoring/custom_cmds.rs)同样值得一提:整批命令先写入custom_cmds.new临时目录,加排他锁后把旧目录改名为custom_cmds.old,再原子替换;若中途崩溃,下次写入前会自动恢复旧目录(recover_interrupted_replace)。App 端经 SSH 的安装脚本(crates/sbm_parser/src/script.rs)也采用同样的「写新目录 → 改名替换」策略,且命令正文以 base64 传输,避免 heredoc 被用户文本意外终止。

请务必遵守两条红线

  • 避免使用会修改系统状态的命令(如rmshutdown、写文件等);删除某条命令的方式是在编辑器中删除它——它会随整目录替换而消失,不需要也不可能在命令里「补一个清理动作」。
  • 不要在命令中写入密码、token 或其他凭据。命令文件以明文存储于服务器~/.config/server_box/custom_cmds,且可能被 Audit 日志、状态输出等路径间接暴露。

从旧格式迁移

早期版本将自定义命令保存为服务器设置中的JSON 对象。当前版本的行为(文档明确 + 源码印证):

  • 编辑服务器其他配置时,App 会暂时保留这些旧条目;
  • 首次连接时,将其迁移到服务器上的custom_cmds目录;
  • 当前版本不再写入旧格式

迁移状态判定依赖脚本输出的SrvBoxCusCmdDir/SrvBoxCusCmdDirEnd/SrvBoxCusCmdDirMissing标记(crates/sbm_parser/src/script.rs):目录不存在(Missing,从未安装过)与目录存在但为空(End,用户删光了所有命令)是两种必须区分的情况——前者触发迁移,后者不触发。

测试与一致性保障

仓库为这一功能提供了多层测试:

  • 目录读写与故障恢复单测(monitor/src/monitoring/custom_cmds.rs):覆盖写入后按用户编排顺序读回(而非字母序)、杂散文件被跳过、替换删除已移除命令、中断后的目录恢复、重名/空名/超长名校验、并发替换最终只剩一份完整目录。
  • 脚本兼容性测试(crates/sbm_parser/tests/script_compat.rs):校验 Rust 生成脚本与历史 Dart 构建器字节级一致,并核对 Shell 表达式与本地路径指向同一目录。
  • Monitor API 集成测试(monitor/tests/custom_cmds_api.rs):验证GET/PUT /api/v1/custom-cmds的鉴权与full_access门控。

小结

ServerBox 自定义命令的核心理念是**「以文件为存储、以目录为协议」**:App 编辑器、Monitor 网页面板与生成的状态脚本三方都围绕~/.config/server_box/custom_cmds这一份数据工作,没有副本、没有同步、没有格式分叉。你只需记住四个要点:命令是服务器上的文件(编辑需服务器在线)、顺序即显示顺序(可拖动调整)、server_card_top_right可定制首页卡片右上角、命令以连接身份定时执行(务必保持简单、只读、无凭据)。

【免费下载链接】flutter_server_boxServerBox - server status & toolbox项目地址: https://gitcode.com/GitHub_Trending/fl/flutter_server_box

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询