☰
宝塔面板实战食谱:基于 bt-panel 技能的前后端部署、运维与故障排查全指南
2026/10/12 2:03:21 网站建设 项目流程

【免费下载链接】ccg-workflow

多模型协作工作流引擎 — /ccg:go 一个命令,AI 自动分析意图、选择策略、编排 Codex + Gemini + Claude 协作执行

项目地址:https://gitcode.com/gh_mirrors/cc/ccg-workflow
点击查看免费下载

导读

本文是 ccg-workflow 仓库中bt-panel技能(位于 templates/skills/bt-panel)的实战操作手册,完整收录其 references/recipes.md 中沉淀的真实运维场景:纯前端静态站更新、Node.js/PHP 后端部署、单文件热修复、数据库迁移、远程诊断与基于自动备份的回滚。读完本文,你将能够只凭一个宝塔面板地址 + 32 位 API 密钥,在无需 SSH、无需 rsync的前提下完成从部署、重启到回滚的完整线上运维闭环,并能针对签名失败、IP 白名单、字段名版本差异等高频故障快速定位修复。

bt-panel是 ccg-workflow 随 v3.5.1 引入的独立技能(见 README.zh-CN.md),同批技能还包括seo-page-builder、adsense-site-auditor。它由三个可执行文件组成:通用 API 客户端 bt_client.py、一键部署编排器 bt_deploy.py,以及站点别名配置模板 sites.example.json。本文所有命令均以这三个文件为运行基础,并会结合源码解释其底层行为,让你不仅会用,还知道为什么。


一、前置:工具链与凭据约定

在套用下文任何一条"食谱"之前,先确认工具链就位。仓库中的bt-panel技能安装后位于~/.claude/skills/ccg/bt-panel/,目录结构如下(见 SKILL.md):

~/.claude/skills/ccg/bt-panel/ ├── SKILL.md # 技能指南 ├── bt_client.py # 核心 API 客户端 + 通用 CLI(test/sites/ls/cat/put/exec/sql …) ├── bt_deploy.py # 一键部署器(tar→upload→extract→sql→restart→cleanup) ├── sites.example.json # 站点别名配置示例(复制为 sites.json 使用) └── references/ ├── api-reference.md # 宝塔 v11 API 接口速查(踩过的坑都在这) └── recipes.md # 实战食谱:更新前端 / PHP / Node / 回滚 …(本文主体)

仓库测试 skills-hygiene.test.ts 专门断言bt_client.py、bt_deploy.py、sites.example.json三个文件随技能一同发布,也就是说这套食谱不是纸上谈兵——脚本与文档一起被打包分发。

凭据的三种提供方式

# 方式 1:环境变量(一次性) export BT_URL="https://panel.example.com:8888" export BT_KEY="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" # 方式 2:CLI 参数 python3 bt_client.py --panel https://1.2.3.4:8888 --key XXX test # 方式 3:sites.json 别名(长期使用推荐,--alias <name>)

从源码看,build_client()(bt_client.py)的取值优先级是:--panel/--key显式参数 > 环境变量BT_URL/BT_PANEL/BT_KEY/BT_API_KEY>--alias对应站点配置;bt_deploy.py同理(bt_deploy.py)。load_site_alias()(bt_client.py)依次查找三个候选文件:$BT_SITES_JSON环境变量指定路径、~/.claude/skills/bt-panel/sites.json、~/.bt-sites.json,先到先用。

⚠️ 安全提醒:API 密钥等同于服务器 root 权限,sites.json必须进入.gitignore,绝不可提交到 git 跟踪的文件中(recipes.md 的"最佳实践"一节有具体操作,下文详述)。

连通性自检

python3 bt_client.py test # 返回里附带变体探测结果: # "_skill_detected": {"os": "linux", "variant": "bt", "major": 11, "shell": "bash"}

test()在 bt_client.py 中实现:它调用/system?action=GetSystemTotal并触发panel_info探测,返回面板版本、系统信息与_skill_detected字段。该探测结果决定后续所有接口的选择策略,这也是食谱中大量命令能"一套脚本通吃宝塔 v7–v11 / aaPanel / Windows 面板"的底层原因。


二、七大实战场景:从部署到回滚的完整命令模板

场景 1:更新一个纯前端静态站(Vue/React dist)

特点:无状态、需要完整替换、无服务重启、可以清空目标目录。

直接命令行方式:

python3 bt_deploy.py \ --local ./dist \ --remote /www/wwwroot/h5.example.com \ --clean \ --owner www:www \ --exclude '*.map'

或做成 alias 长期复用:

"h5-frontend": { "panel": "https://1.2.3.4:8888", "key": "xxxx", "local": "~/projects/myapp-h5/dist", "remote": "/www/wwwroot/h5.example.com", "clean": true, "exclude": ["*.map"] }
python3 bt_deploy.py --alias h5-frontend

源码剖析:--clean在部署时先执行find {remote} -mindepth 1 -delete清空目标目录但保留目录本身(bt_deploy.py),配合tar -xzf ... --strip-components=1解压到目标目录(bt_deploy.py),最后按--owner(默认www:www)执行chown -R修正属主(bt_deploy.py)。静态站没有进程需要重启,因此不需要restart字段——这是它与后端场景最大的区别。

场景 2:更新 Node.js 后端(pm2 管理)

python3 bt_deploy.py \ --alias myapp-api \ --backup \ --restart 'cd /www/wwwroot/myapp-api && npm install --production && pm2 restart myapp-api'

alias 配置:

"myapp-api": { "panel": "https://1.2.3.4:8888", "key": "xxxx", "local": "~/projects/myapp-api", "remote": "/www/wwwroot/myapp-api", "database": "myapp_db", "backup": true, "exclude": ["node_modules", ".git", ".env.local", "*.log"], "restart": [ "cd /www/wwwroot/myapp-api && npm install --production --silent", "cd /www/wwwroot/myapp-api && pm2 restart myapp-api || pm2 start ecosystem.config.js" ] }

关键点解读:

  • exclude排除node_modules、.git、.env.local、*.log:本地依赖与敏感环境变量不进 tar 包,线上依赖通过restart里的npm install --production现场安装;
  • restart可以是字符串或字符串数组,Deployer.restart_service()(bt_deploy.py)按顺序逐条执行,pm2 restart myapp-api || pm2 start ecosystem.config.js的写法保证"进程在就重启、不在就按配置拉起";
  • backup: true会在部署前对远端目录做 tar.gz 备份,这是场景 7 回滚的基础;
  • 默认排除清单在 merge_cfg() 中兜底:node_modules、.git、.DS_Store、*.log、*.pyc、__pycache__,即使你不写exclude也不会把依赖和日志打包。

场景 3:更新 PHP 站(FastAdmin/ThinkPHP)

"jys-backend": { "panel": "https://1.2.3.4:8888", "key": "xxxx", "local": "~/projects/mysite-php", "remote": "/www/wwwroot/mysite-php", "database": "jys", "backup": true, "exclude": ["vendor", ".git", "runtime/*", "*.log", ".idea"], "restart": [ "cd /www/wwwroot/mysite-php && find runtime/temp -name '*.php' -delete 2>/dev/null; true", "systemctl reload nginx" ] }

关键点解读:

  • PHP 框架(FastAdmin/ThinkPHP 等)的runtime目录是编译缓存,部署后清理临时文件并用; true保证即便 find 无匹配也不中断命令链;
  • systemctl reload nginx让 PHP 改动即时生效,PHP-FPM 版本不同时也可按需改为systemctl restart php-fpm-74(参考 sites.example.json 中demo-php的写法);
  • database: "jys"配合--backup会在部署时对数据库做备份(详见场景 5)。

场景 4:只改一两个配置文件(热修复)

无需重部署,直接put或write:

# 覆盖单文件(本地有改动) python3 bt_client.py put ./server/config/prod.ts /www/wwwroot/app/server/config/ # 直接写一行(本地没改动,想快速改) python3 bt_client.py write /www/wwwroot/app/.env 'API_URL=https://new.api.com DEBUG=false' # 重启服务 python3 bt_client.py exec 'pm2 restart app' --cwd /www/wwwroot/app

源码剖析:

  • put走的是BtClient.upload()(bt_client.py),底层调用/files?action=upload,超过 4MB 自动按 4MB 分片(chunk_size=4*1024*1024),空文件则用SaveFileBody兜底;
  • write走的是BtClient.write_file()(bt_client.py),它实现了双重自动兜底:若返回"指定文件不存在/not exist"就先CreateFile再重试;若返回FILE_SAVE_ERR Permission denied(/tmp sticky bit 或跨用户写冲突)就先DeleteFile再CreateFile再重试。这就是为什么食谱里敢直接write覆盖.env而不用担心失败;
  • exec走exec_shell(),--cwd指定执行目录(默认/root),--wait控制最长等待秒数(默认 300)。

场景 5:数据库迁移(带备份)

# 1. 先备份(exec 方式,避开 ToBackup 的面板任务队列) python3 bt_client.py exec \ "mysqldump -u\$USR -p\$PWD mydb > /www/backup/mydb_$(date +%Y%m%d_%H%M%S).sql" \ --cwd /tmp --wait 600 # 2. 执行迁移 SQL python3 bt_client.py sql mydb --from-file ./migrations/2026-04-10-add-column.sql # 3. 回滚预案(记下备份路径,必要时) python3 bt_client.py sql mydb --from-file /www/backup/mydb_xxx.sql

或用bt_deploy.py的集成sql字段把迁移融入部署:

{ "sql": "./migrations/2026-04-10.sql", "database": "mydb" }

源码剖析:sql子命令对应sql_execute()(bt_client.py),其执行策略是三通道自适应:

  1. 先试面板 API/database?action=SqlExecute(v7–v10 多数可用,v11 通常被拦截返回"指定参数无效");
  2. Linux/macOS/aaPanel 退回 shell + mysql CLI + heredoc:凭据自动从/data?action=getData&table=databases查出(_lookup_db_credentials(),bt_client.py),mysql 二进制按/www/server/mysql/bin/mysql→/www/server/mariadb/bin/mysql→/usr/local/mysql/bin/mysql→command -v mysql→mysql逐级探测,密码通过MYSQL_PWD环境变量传入(不出现在进程列表),heredoc delimiter 用 uuid 动态生成防止 SQL 内容碰撞注入;
  3. Windows 面板退回 cmd 等价方案:临时文件 +type重定向(heredoc 在 cmd 下不可用)。

sql_execute会自动为 SQL 补尾部分号;--from-file读取本地 SQL 文件内容再执行。bt_deploy.py的apply_sql()(bt_deploy.py)把sql字段(SQL 字符串或.sql文件路径)接入部署流水线,与database字段配对使用,在解压后、重启前执行。

场景 6:远程诊断(查日志 / 查进程 / 查端口)

# 查 pm2 状态 python3 bt_client.py exec 'pm2 status' --cwd /root # 查最近的 nginx error log python3 bt_client.py exec 'tail -200 /www/wwwlogs/myapp.example.com.error.log' # 查端口占用 python3 bt_client.py exec 'ss -tlnp | grep :3000' # 磁盘占用 top 10 python3 bt_client.py exec 'du -sh /www/wwwroot/* | sort -hr | head -10'

这些命令全部复用exec_shell()的远程执行通道。其实现要点(bt_client.py):

  • 命令会被自动包装上结束标记echo __BT_DONE__,轮询/files?action=GetExecShellMsg直到标记出现,从而拿到完整的 stdout+stderr 累积输出;
  • 下发接口按panel_info.major自适应:v11+ 先试ExecShell(v11 改名后的新接口),v7–v10 先试ExecShellMsg,失败自动回退另一个(actions元组的顺序);
  • 首次下发后sleep 0.8s再开始轮询——这是给宝塔创建/tmp/panelExec.pl留时间,避免FILE_SHELL_EMPTY误报;
  • 宝塔 Linux/aaPanel 的 shell 以root身份执行,因此可以ss、tail系统日志、读写任意路径。

场景 7:回滚(基于自动备份)

部署时--backup会生成/www/backup/bt_skill/<站点名>_<timestamp>.tar.gz。回滚即"选备份包 → 清空目标 → 解压还原 → 修正属主 → 重启服务":

# 1. 列出备份 python3 bt_client.py exec 'ls -la /www/backup/bt_skill/' # 2. 选一个备份包解压回目标位置 python3 bt_client.py exec ' SITE=/www/wwwroot/myapp.example.com BAK=/www/backup/bt_skill/myapp.example.com_20260410_120000.tar.gz find $SITE -mindepth 1 -delete tar -xzf $BAK -C $(dirname $SITE) chown -R www:www $SITE pm2 restart myapp '

源码剖析:备份由Deployer.backup_remote()(bt_deploy.py)完成——mkdir -p /www/backup/bt_skill后用tar -czf打包远端目录(-C <parent> <name>保证解包时还原目录层级),备份路径可被backup_dir配置项覆盖(默认/www/backup/bt_skill)。若目标目录不存在则跳过备份并给出NO_TARGET警告。recipes.md 特意提醒:绝不直接用rm -rf删除/www/wwwroot/*,先--backup再--clean(见 SKILL.md 的安全铁律)。


三、故障排查:8 个高频线上问题与修复

🔑签名校验失败 / Invalid signature

可能原因:

  1. API 密钥复制错了(多一个空格、少一个字符)
  2. 本地机器时间漂移(与面板服务器差 >60 秒)
  3. 面板设置里 API 密钥被改过了

排查:

# 对时 ntpdate pool.ntp.org # Linux sudo sntp -sS time.apple.com # macOS # 验证密钥:登宝塔面板 → 设置 → API 接口 → 查看密钥

底层原理:宝塔签名算法是request_token = md5(request_time + md5(api_key))(bt_client.py 的_sign()方法)。因为request_time取本地 Unix 秒,客户端与服务器时间偏差超过 60 秒会被面板拒绝,这就是"对时"能解决签名失败的原因。

🚫IP 未在白名单

原因:宝塔 API 接口默认只放特定 IP。

修复:

  1. 登宝塔面板 → 设置 → API 接口
  2. "IP 白名单"添加本机公网 IP(curl ifconfig.me查)
  3. 临时方案:加0.0.0.0放行全部(仅测试环境)

❓指定参数无效(指定参数无效!)

原因:API 接口的字段名在宝塔不同版本间变化。

案例:

  • ExecShellMsg→ v11 改名为ExecShell
  • SqlExecute→ v11 可能拦截,用 shell + mysql 代替

调试方法(仿效)——遍历候选字段组合,打印每次返回,找到当前版本认的字段名:

for candidate in ({"shell": cmd, "path": "/tmp"}, {"cmd": cmd}, ...): r = c.request("/files?action=<NewAction>", candidate) print(candidate, '→', r)

宝塔 v11.6.0 实测环境中所有已知正确字段名已整理在 references/api-reference.md(字段名版本差异对照表、shell 下发/取回接口、SQL 兜底方案一应俱全)。配方本身已内置该自适应:exec_shell()按主版本号选择接口并自动回退,sql_execute()失败自动降级 shell 通道。

💾FILE_SAVE_ERR Permission denied

原因:/tmp的 sticky bit(drwxrwxrwt)让SaveFileBody无法覆写其他进程创建的文件。

修复:

  1. bt_client.write_file()已自动 delete+recreate 兜底(见场景 4 的源码剖析)
  2. 或改用子目录:/tmp/bt_work/xxx代替直接/tmp/xxx
  3. 或走/files?action=upload(不受此问题影响)

⏳FILE_SHELL_EMPTY/ exec 永远 timeout

原因 A:用错了接口名。v11 必须是/files?action=ExecShell(不是ExecShellMsg)。原因 B:首次 poll 太快,/tmp/panelExec.pl还没被宝塔创建。

修复:bt_client.exec_shell()已处理——首次下发后sleep 0.8s再轮询,收到FILE_SHELL_EMPTY时sleep 1后继续(对应 api-reference.md 中"正确的轮询姿势")。

🔐Access denied for user 'xxx'(MySQL)

原因:数据库用户/密码错。

排查:

# 1. 从面板查看实际凭据 python3 bt_client.py dbs # 2. bt_client.sql_execute 默认会自动查凭据,如果还报错: # 可能是 accept 限制了 localhost(面板 → 数据库 → 权限设置) python3 bt_client.py exec "grep -r $DB_NAME /etc/mysql 2>/dev/null; cat /etc/my.cnf | head -30"

说明:dbs子命令即get_databases(),返回的每条记录含username、password、accept字段;sql_execute的_lookup_db_credentials()正是从这里自动取凭据。

📦上传文件为 0 字节/ upload 状态异常

可能原因:

  1. 文件大小超过面板 nginx 限制(默认 1GB)
  2. 分片上传中某一片失败
  3. 磁盘空间不足

排查:

python3 bt_client.py exec 'df -h / /tmp /www' python3 bt_client.py exec 'cat /www/server/nginx/conf/proxy.conf | grep client_max'

🔄pm2 重启没生效

排查:

# 1. 确认 pm2 已启动 python3 bt_client.py exec 'pm2 status' --cwd /root # 2. 确认你用的是宝塔用户的 pm2(宝塔用 www 用户时需要 sudo -u www) python3 bt_client.py exec 'which pm2; pm2 --version' # 3. 强制重启 python3 bt_client.py exec 'pm2 kill; pm2 start /www/wwwroot/app/ecosystem.config.js'

提示:宝塔 Linux 面板的 shell 以 root 执行,但部分环境下 pm2 是 www 用户安装的,因此可能需要sudo -u www或直接确认which pm2指向的二进制。


四、最佳实践:让部署变成可重复的工程流程

📁 组织 sites.json

# 1. 复制模板 cp ~/.claude/skills/ccg/bt-panel/sites.example.json ~/.claude/skills/ccg/bt-panel/sites.json # 2. 设置权限(防手滑 commit) chmod 600 ~/.claude/skills/ccg/bt-panel/sites.json # 3. 加入全局 .gitignore echo 'sites.json' >> ~/.gitignore_global

sites.example.json(仓库版见 templates/skills/bt-panel/sites.example.json)本身就是一份完整的字段字典:它演示了demo-fullstack(Node 全栈 + 数据库 + backup + restart)、demo-static(静态站 + clean)、demo-php(PHP + nginx/php-fpm 重启)三类典型别名,每个别名可用的字段包括panel、key、local、remote、database、owner、backup、exclude、restart、clean、sql、backup_dir。文件头部的_readme数组还写明了路径优先级:$BT_SITES_JSON>~/.claude/skills/ccg/bt-panel/sites.json>~/.bt-sites.json。仓库的 skills-hygiene.test.ts 会持续扫描技能目录,确保打包发布的内容里不含任何真实的 API 密钥、公网 IP 或个人路径——你在本地实践时也应对照这一标准。

🧪 部署前必看

# 先干跑 python3 bt_deploy.py --alias myapp --dry-run # 查远端当前状态(最近改动时间) python3 bt_client.py exec 'find /www/wwwroot/myapp -type f -mtime -1 | head -20' # 检查重启脚本在本地能跑通 bash -n ecosystem.config.js

--dry-run的实现值得说明:Deployer各阶段在self.dry为真时只打印"将要做什么"而不真正执行(连通测试仍会真实执行,因为需要它确认凭据有效),备份路径、tar 包远端路径照常计算(见 bt_deploy.py 中各方法的if self.dry: return分支)。

🛡 部署后核验

# 1. HTTP 探活 python3 bt_client.py exec 'curl -I -s http://localhost:3000/health' # 2. 日志尾部 python3 bt_client.py exec 'tail -50 /www/wwwroot/myapp/logs/app.log' # 3. pm2 状态 python3 bt_client.py exec 'pm2 status'

这对应 SKILL.md 响应流程中的"核验"环节:执行后通过curl -I探活或cat .env读回,确认改动真实生效,再向上汇报目标、改动范围与服务状态。

⚡ 多站点并发部署

# 用 & 并发 + wait 汇总 python3 bt_deploy.py --alias site1 & python3 bt_deploy.py --alias site2 & python3 bt_deploy.py --alias site3 & wait echo "all done"

注意事项:同一面板并发 exec 会互相覆盖/tmp/panelExec.pl(宝塔用单文件存储上一次 shell 输出),但upload和SaveFileBody是独立的,所以不同站点并发 OK,同站点并发可能有 exec 日志串。如需并发执行且要传递结果,自己写文件区分(your_cmd > /tmp/my_out_$(uuidgen).log 2>&1,见 api-reference.md)。


五、从食谱到流水线:bt-panel 在 ccg-workflow 中的位置

bt-panel不只提供手动命令,它还作为 ccg-workflow 的独立技能被注册到技能注册表(skill-registry.ts)中,user-invocable: true意味着它可被/ccg:bt-panel斜杠命令直接唤起(见 README.zh-CN.md 的技能表)。安装时,installer.ts 会把templates/skills/完整复制到~/.claude/skills/ccg/,并扫描user-invocable技能自动生成斜杠命令(skill-registry.ts)。

因此,recipes.md 中的每条命令模板,既可以被你手工执行,也可以作为 Agent 在收到"更新线上 / 部署到服务器 / 跑一下 SQL / 重启 pm2"等意图时的执行依据——配合 SKILL.md 中的响应流程(识别 → 连通 → 盘点 → 决策 → 执行 → 核验 → 上报),形成一条从"用户一句话"到"线上服务完成更新"的自动化链路。


结语

recipes.md 的价值不在于"能跑通",而在于它把真实建站运维中反复踩过的坑(v11 接口改名、sticky bit、轮询时序、并发串台)逐一沉淀成了可复用的命令与配置模板。本文在完整继承这 7 大场景、8 类故障排查与 4 组最佳实践的基础上,进一步对照 bt_client.py 与 bt_deploy.py 的源码,解释了每条命令底层的接口选择、自动兜底与安全机制。遇到更细的接口参数问题,可随时查阅 api-reference.md 的完整接口速查表。

【免费下载链接】ccg-workflow

多模型协作工作流引擎 — /ccg:go 一个命令,AI 自动分析意图、选择策略、编排 Codex + Gemini + Claude 协作执行

项目地址:https://gitcode.com/gh_mirrors/cc/ccg-workflow
点击查看免费下载

相关推荐

上一篇:RePKG:Wallpaper Engine壁纸资源提取与转换的终极指南
下一篇:GitHub中文插件终极指南:5分钟让GitHub界面说中文,新手也能快速上手

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

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

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

立即咨询