Node.js 项目部署与启动排错 SOP
停止。执行任何操作前,必须完整阅读本文档。
本 Skill 的工具均指 bt_agent_mcp 插件暴露的 MCP 工具;同一能力在宝塔 AI 内置端可能有不同命名,按当前环境可用工具调用。
本 SOP 只覆盖两个主场景:① 部署 Node 项目;② 启动排错。其他简单操作(状态查看、启停、日志、域名、改配置、删除)不做 SOP,直接使用工具。
核心规则
- 先只读侦察,再执行变更 —— 部署前必须完成项目分析;排错先读日志,不盲目重启。
- 创建后必须验证启动 —— "创建成功"不算完,必须确认进程存活 + 监听端口 + 日志无致命错误;验证失败即转入场景二。
- 删除项目属高风险,必须先获得用户明确确认;启停/重启/改配置属中风险,操作前说明影响。
- Node 版本匹配 —— 创建前用
NodeVersion(action='list') 检测已装版本;package.json 的 engines.node 不符时优先换已装版本或安装。
- 端口唯一性 —— 创建/改端口前检查占用;绑外网必须有端口(
bind_extranet 依赖 port)。
- 绑域名 = 自动反代 —— Node 的反代由 nginx 模板按
project_config.port 自动生成(proxy_pass http://127.0.0.1:{port}),无独立反代步骤。访问异常时核对:NodeProjectInfo 的 listen(实际监听端口)vs 配置端口(反代目标)是否一致,不一致 = 502。
- 依赖先行 —— nodejs 类型缺
node_modules 启动必失败。创建时 install_deps=true 或创建后装依赖再启动。
- 每个任务最多调用 15 次工具,超出后汇总当前发现并停止。
- 禁止操作:删除项目目录/源码、
rm -rf 项目目录、修改宝塔/插件自身文件、读取插件 data/ 凭据。
场景一:部署 Node 项目
Step 0 —— 预检
- 确认任务确为"部署新 Node 项目"(非排错/运维)。
- 确定项目类型:
nodejs(package.json scripts)/ general(直接指定启动文件)/ pm2(守护+cluster+自愈)。
- 需要外网域名访问时,确认 Nginx 已安装;否则只能本机/内网访问。
Step 1 —— 定位项目目录
- 用户给出
project_cwd(项目根,含 package.json),或:
- 用文件工具
Glob(pattern='**/package.json') / LS(path=...) 定位(排除 node_modules、dist、build 等构建目录)。
- 校验:目录存在、含
package.json(nodejs/pm2 必需;general 可没有)、非敏感目录(/etc、/boot、插件 data/ 等)。
Step 2 —— 分析项目(部署前必须)
调用 NodeProjectCreate(project_cwd=..., analyze_only=true)(只读,不创建)。返回:
package.json 的 name/version、engines.node、dependencies 数量。
scripts 启动项列表(nodejs 类型的 project_script 从这里选)。
node_modules 是否存在(缺 → 需先装依赖)。
port/port_hints:从入口文件启发式探测的端口(仅供参考)。
决策:
- nodejs 类型:确认启动脚本名(
start/dev/自定义),即 project_script。
- pm2 类型:确认入口文件(js 或 ecosystem 配置)与
cluster 实例数。
- 无端口或启发式不准 → 询问用户,创建时显式传
port(绑外网必需)。
Step 3 —— Node 版本确认 / 安装
NodeVersion(action='list') 检测已装版本与可用包管理器(npm/yarn/pnpm)。
- 若返回
plugin_installed=false:nodejs 版本管理器插件未装,先调 SoftwareInstall(name='nodejs') 安装插件(可再用 SoftwareList(name='nodejs') 查进度),装完继续。
- 需要新装版本时,用
NodeVersion(action='online', lts_only=true, page=...) 浏览可安装版本(按版本从新到旧排序、本地分页,lts_only=false 看全部;每项 installed 标记是否已装),从中挑选稳定版。
- 对照
engines.node(无则默认 LTS)。
- 已装 → 记录版本号(如
v20.15.0);无 → 询问用户后 NodeVersion(action='install', version=..., install_pm2=<pm2 类型时 true>)(插件安装,较慢,装完用 list 确认)。
Step 4 —— 创建项目(注册)
调用 NodeProjectCreate(project_type=..., project_name=..., project_cwd=..., nodejs_version=..., ...)。
按类型传参:
- nodejs:
project_script(scripts key,如 'start')、pkg_manager(npm/yarn/pnpm)。
- general:
project_file(启动文件绝对路径)、project_args(可选)。同时必须传 nodejs_version(三类型均必填、须已安装——general 漏传会直接报错,不是可选项)。
- pm2:
project_file(js 入口或 ecosystem 配置,内容检测自动路由)、cluster、watch、max_memory_limit。pm2 类型项目启动依赖该 node 版本已全局安装 pm2——创建工具会自动检测,该版本缺 pm2 时自动调插件安装(结果 data.pm2_auto_installed 标记);也可手动 NodeVersion(action='module', version=<nodejs_version>, modules=['pm2'])。
- 通用:
port(绑外网必需)、run_user(默认 www)、env(注意:面板对 nodejs 类型的 env 注入有缺陷——存了但不写进启动脚本,需要环境变量的项目建议用 general 类型)、install_deps(package.json 有依赖且未装 node_modules 时给 true)、release_firewall(需放行端口时)、domains=['域名'](绑域名+自动反代)。
外网映射:要域名访问 → 传 domains(自动写 server_name + proxy_pass 127.0.0.1:{port},无需额外反代步骤)。install_deps=true 较慢(npm install),可接受等待。
Step 5 —— 启动验证(强制)
创建后调用 NodeProjectInfo(project_name=...) 依次确认:
- 进程存活(
run=true)。
- 应用端口在
listen 列表(listen_ok=true)。
- 用返回的
log_files 里应用日志路径,调 filesystem Read 看尾部无致命异常(Cannot find module/EADDRINUSE/throw er)。
⚠️ pm2 类型不要信创建返回的「已自动启动」:daemon 可能未持久化(run:false、listen 空但 listen_ok=true)。创建后必须:
① 立即 NodeProjectInfo 核验 run=true;
② 间隔几秒再次 NodeProjectInfo 复检持久化(本地等待即可,不调长工具);
③ 不持久 → NodeProjectControl(action='start') 重拉再复检。
- 全通过 → 按"完成汇报"收尾。
- 任一失败 → 转场景二排错。
场景二:启动排错
适用:部署后验证失败、或用户报"Node 项目启动失败/访问异常"。
Step 0 —— 快速定位现状(只读)
并行收集,不跳过:
NodeProjectInfo(project_name=...):run、listen、listen_ok(区分"进程没起来"与"起来了但访问不了");pm2 项目看状态。
- 读应用日志尾部:用返回的
log_files 应用日志路径调 filesystem Read,找异常堆栈根因。
- 已知配置:项目类型、入口/脚本、node 版本、端口、是否已装依赖。
收集后按症状进分支。
分支 A —— 启动失败(进程起不来 / run=false)
按日志根因对号入座:
| 日志特征 |
根因 |
处置 |
Error: Cannot find module 'x' |
依赖未安装 |
NodeProjectCreate(project_cwd=<项目目录>, nodejs_version=..., project_script=..., install_deps=true) 或装依赖后重启 |
EADDRINUSE / Port 3000 is already in use |
端口被占 |
NodeProjectModify(action='config', port=新端口) 后 NodeProjectControl(action='restart') |
npm ERR! Missing script: "start" |
scripts key 名错 |
NodeProjectModify(action='config', project_script=正确key) |
throw er; Unhandled 'error' event |
启动即崩(端口/环境) |
看堆栈中具体原因;换端口或补 env |
ERR_REQUIRE_ESM / 语法错误 |
模块系统/代码问题 |
检查入口与 package.json 的 type 字段 |
| node 版本引擎不符 |
engines.node 不匹配 |
NodeProjectModify(action='config', nodejs_version=匹配版本) 或 NodeVersion(action='install') |
| 进程起来但 pid 无/端口没监听 |
启动后立即退出 |
读日志尾找 exit/Error 根因 |
- 无明确报错 → 查
NodeProjectInfo:run、listen、启动命令是否完整;log_files 有无 pm2 err.log。
- 修复后回到场景一 Step 5 重新验证。
分支 B —— 访问异常(502/无法访问,但进程活着)
NodeProjectInfo:进程在跑、端口在听吗?→ 不在则回分支 A。
- 反代端口匹配:看
listen(实际监听)vs project_config.port(反代目标)是否一致;grep proxy_pass node_<name>.conf(/www/server/panel/vhost/nginx/node_<name>.conf)确认反代指向 127.0.0.1:{实际端口}。
- 端口改了没同步 →
NodeProjectModify(action='config', port=实际端口)(有 domains 时自动重写反代)。
- 反代指向错误端口 → 同上。
- 域名绑定:
bind_extranet 是否开、域名是否在 nginx server_name(未绑 → NodeProjectModify(action='add_domain', domains=[...]))。
- 读 nginx error log(
log_files 里 /www/wwwlogs/<name>.error.log)进一步定位。
- 修正后验证。
分支 C —— PM2 项目异常
NodeProjectInfo 看 pm2 状态(online/stopped/errored)。
errored(崩溃超限)→ 读 log_files 里 pm2 err.log 找根因;修复后用 NodeProjectControl(action='restart') 重启。
- 读
/www/wwwlogs/pm2/<name>/out.log|err.log。
- 依赖问题 → 同分支 A。
分支 D —— 进程在但无响应 / 卡死
- 应用日志尾部看异常/死锁(filesystem
Read)。
SystemInfo() 看系统资源(CPU/内存/负载)。
- 汇报,建议用户确认后
NodeProjectControl(action='restart') 重启。
日常运维速查(简单操作,直接按工具描述调用)
| 需求 |
工具 |
| 查看项目列表/状态 |
NodeProjectInfo()(project_name 留空) |
| 查看单项目详情(含日志路径) |
NodeProjectInfo(project_name=...) |
| 启动 / 停止 / 重启 |
NodeProjectControl(project_name, action='start'/'stop'/'restart') |
| 看应用日志 / pm2 / nginx 日志 |
NodeProjectInfo 返回 log_files → filesystem Read |
| 改脚本/参数/node 版本/端口/用户/环境/内存 |
NodeProjectModify(action='config', ...)(重启生效) |
| 加域名 / 删域名(至少留一个) |
NodeProjectModify(action='add_domain'/'remove_domain', domains=[...]) |
| node 版本检测 / 在线浏览 / 安装 |
NodeVersion(action='list'/'online'/'install', ...) |
| 给 node 版本装全局模块(pm2/pnpm/yarn 等) |
NodeVersion(action='module', version=<nodejs_version>, modules=['pm2']) |
| 删除项目(高风险,先确认) |
NodeProjectDelete(project_name=...)(保留目录源码) |
完成汇报模板(部署成功)
返回:项目名 / 项目类型 / 目录 / node 版本 / 运行用户 / 监听端口;若绑域名:访问地址 + nginx 配置路径;应用日志路径;启动验证结果(进程/端口/日志);遗留隐患提示(依赖未装、env 未注入等)。
1---2name: bt-node-project-deploy3description: 在宝塔面板上部署 Node.js 项目(nodejs/general/pm2 三种类型,含 Nginx 域名/反代), 并在项目启动失败或访问异常时排错。 当用户要求把本机已有的 Node 项目目录部署成面板可管理的项目(含 node 版本、依赖安装、 Nginx 代理/域名、pm2 守护),或 Node 项目启动失败/访问异常需要排查根因时使用。 触发关键词:部署 Node、Node 项目部署、nodejs 部署、npm 项目部署、PM2 部署、 Node 项目启动失败、npm 起不来、Node 报错、502、访问异常、pm2 errored、Cannot find module。 日常的启停/状态/日志/改配置等简单操作不依赖本 SOP,直接按工具描述调用即可。4---56# Node.js 项目部署与启动排错 SOP78> **停止。执行任何操作前,必须完整阅读本文档。**9> 本 Skill 的工具均指 bt_agent_mcp 插件暴露的 MCP 工具;同一能力在宝塔 AI 内置端可能有不同命名,按当前环境可用工具调用。10> 本 SOP 只覆盖两个主场景:**① 部署 Node 项目;② 启动排错**。其他简单操作(状态查看、启停、日志、域名、改配置、删除)不做 SOP,直接使用工具。1112---1314## 核心规则15161. **先只读侦察,再执行变更** —— 部署前必须完成项目分析;排错先读日志,不盲目重启。172. **创建后必须验证启动** —— "创建成功"不算完,必须确认进程存活 + 监听端口 + 日志无致命错误;验证失败即转入场景二。183. **删除项目属高风险**,必须先获得用户明确确认;启停/重启/改配置属中风险,操作前说明影响。194. **Node 版本匹配** —— 创建前用 `NodeVersion(action='list')` 检测已装版本;`package.json` 的 `engines.node` 不符时优先换已装版本或安装。205. **端口唯一性** —— 创建/改端口前检查占用;**绑外网必须有端口**(`bind_extranet` 依赖 `port`)。216. **绑域名 = 自动反代** —— Node 的反代由 nginx 模板按 `project_config.port` 自动生成(`proxy_pass http://127.0.0.1:{port}`),**无独立反代步骤**。访问异常时核对:`NodeProjectInfo` 的 `listen`(实际监听端口)vs 配置端口(反代目标)是否一致,不一致 = 502。227. **依赖先行** —— nodejs 类型缺 `node_modules` 启动必失败。创建时 `install_deps=true` 或创建后装依赖再启动。238. **每个任务最多调用 15 次工具**,超出后汇总当前发现并停止。249. **禁止操作**:删除项目目录/源码、`rm -rf` 项目目录、修改宝塔/插件自身文件、读取插件 `data/` 凭据。2526---2728# 场景一:部署 Node 项目2930## Step 0 —— 预检311. 确认任务确为"部署新 Node 项目"(非排错/运维)。322. 确定**项目类型**:`nodejs`(package.json scripts)/ `general`(直接指定启动文件)/ `pm2`(守护+cluster+自愈)。333. 需要外网域名访问时,确认 Nginx 已安装;否则只能本机/内网访问。3435## Step 1 —— 定位项目目录36- 用户给出 `project_cwd`(项目根,含 package.json),或:37- 用文件工具 `Glob(pattern='**/package.json')` / `LS(path=...)` 定位(排除 `node_modules`、`dist`、`build` 等构建目录)。38- **校验**:目录存在、含 `package.json`(nodejs/pm2 必需;general 可没有)、非敏感目录(`/etc`、`/boot`、插件 `data/` 等)。3940## Step 2 —— 分析项目(部署前必须)41调用 `NodeProjectCreate(project_cwd=..., analyze_only=true)`(只读,不创建)。返回:421. `package.json` 的 `name/version`、`engines.node`、`dependencies` 数量。432. `scripts` 启动项列表(nodejs 类型的 `project_script` 从这里选)。443. `node_modules` 是否存在(缺 → 需先装依赖)。454. `port`/`port_hints`:从入口文件启发式探测的端口(仅供参考)。4647**决策**:48- nodejs 类型:确认启动脚本名(`start`/`dev`/自定义),即 `project_script`。49- pm2 类型:确认入口文件(js 或 ecosystem 配置)与 `cluster` 实例数。50- 无端口或启发式不准 → 询问用户,创建时显式传 `port`(绑外网必需)。5152## Step 3 —— Node 版本确认 / 安装531. `NodeVersion(action='list')` 检测已装版本与可用包管理器(npm/yarn/pnpm)。542. 若返回 `plugin_installed=false`:nodejs 版本管理器插件未装,**先调 `SoftwareInstall(name='nodejs')` 安装插件**(可再用 `SoftwareList(name='nodejs')` 查进度),装完继续。553. 需要新装版本时,用 `NodeVersion(action='online', lts_only=true, page=...)` 浏览可安装版本(按版本从新到旧排序、本地分页,`lts_only=false` 看全部;每项 `installed` 标记是否已装),从中挑选稳定版。564. 对照 `engines.node`(无则默认 LTS)。575. 已装 → 记录版本号(如 `v20.15.0`);无 → 询问用户后 `NodeVersion(action='install', version=..., install_pm2=<pm2 类型时 true>)`(插件安装,较慢,装完用 list 确认)。5859## Step 4 —— 创建项目(注册)60调用 `NodeProjectCreate(project_type=..., project_name=..., project_cwd=..., nodejs_version=..., ...)`。61按类型传参:62- **nodejs**:`project_script`(scripts key,如 `'start'`)、`pkg_manager`(npm/yarn/pnpm)。63- **general**:`project_file`(启动文件绝对路径)、`project_args`(可选)。**同时必须传 `nodejs_version`**(三类型均必填、须已安装——general 漏传会直接报错,不是可选项)。64- **pm2**:`project_file`(js 入口或 ecosystem 配置,内容检测自动路由)、`cluster`、`watch`、`max_memory_limit`。**pm2 类型项目启动依赖该 node 版本已全局安装 pm2**——创建工具会自动检测,该版本缺 pm2 时自动调插件安装(结果 `data.pm2_auto_installed` 标记);也可手动 `NodeVersion(action='module', version=<nodejs_version>, modules=['pm2'])`。65- **通用**:`port`(绑外网必需)、`run_user`(默认 www)、`env`(**注意:面板对 nodejs 类型的 env 注入有缺陷——存了但不写进启动脚本,需要环境变量的项目建议用 general 类型**)、`install_deps`(package.json 有依赖且未装 node_modules 时给 true)、`release_firewall`(需放行端口时)、`domains=['域名']`(绑域名+自动反代)。6667**外网映射**:要域名访问 → 传 `domains`(自动写 `server_name` + `proxy_pass 127.0.0.1:{port}`,**无需额外反代步骤**)。`install_deps=true` 较慢(npm install),可接受等待。6869## Step 5 —— 启动验证(强制)70创建后调用 `NodeProjectInfo(project_name=...)` 依次确认:711. 进程存活(`run=true`)。722. 应用端口在 `listen` 列表(`listen_ok=true`)。733. 用返回的 `log_files` 里应用日志路径,调 filesystem `Read` 看尾部无致命异常(`Cannot find module`/`EADDRINUSE`/`throw er`)。7475⚠️ **pm2 类型不要信创建返回的「已自动启动」**:daemon 可能未持久化(`run:false`、`listen` 空但 `listen_ok=true`)。创建后必须:76① 立即 `NodeProjectInfo` 核验 `run=true`;77② 间隔几秒**再次** `NodeProjectInfo` 复检持久化(本地等待即可,不调长工具);78③ 不持久 → `NodeProjectControl(action='start')` 重拉再复检。7980- 全通过 → 按"完成汇报"收尾。81- 任一失败 → **转场景二排错**。8283---8485# 场景二:启动排错8687> 适用:部署后验证失败、或用户报"Node 项目启动失败/访问异常"。8889## Step 0 —— 快速定位现状(只读)90并行收集,不跳过:911. `NodeProjectInfo(project_name=...)`:`run`、`listen`、`listen_ok`(区分"进程没起来"与"起来了但访问不了");pm2 项目看状态。922. 读应用日志尾部:用返回的 `log_files` 应用日志路径调 filesystem `Read`,找异常堆栈根因。933. 已知配置:项目类型、入口/脚本、node 版本、端口、是否已装依赖。9495收集后按症状进分支。9697## 分支 A —— 启动失败(进程起不来 / run=false)98按日志根因对号入座:99| 日志特征 | 根因 | 处置 |100|---|---|---|101| `Error: Cannot find module 'x'` | 依赖未安装 | `NodeProjectCreate(project_cwd=<项目目录>, nodejs_version=..., project_script=..., install_deps=true)` 或装依赖后重启 |102| `EADDRINUSE` / `Port 3000 is already in use` | 端口被占 | `NodeProjectModify(action='config', port=新端口)` 后 `NodeProjectControl(action='restart')` |103| `npm ERR! Missing script: "start"` | scripts key 名错 | `NodeProjectModify(action='config', project_script=正确key)` |104| `throw er; Unhandled 'error' event` | 启动即崩(端口/环境) | 看堆栈中具体原因;换端口或补 env |105| `ERR_REQUIRE_ESM` / 语法错误 | 模块系统/代码问题 | 检查入口与 `package.json` 的 `type` 字段 |106| node 版本引擎不符 | `engines.node` 不匹配 | `NodeProjectModify(action='config', nodejs_version=匹配版本)` 或 `NodeVersion(action='install')` |107| 进程起来但 pid 无/端口没监听 | 启动后立即退出 | 读日志尾找 `exit`/`Error` 根因 |108109- 无明确报错 → 查 `NodeProjectInfo`:run、listen、启动命令是否完整;`log_files` 有无 pm2 err.log。110- 修复后回到场景一 Step 5 重新验证。111112## 分支 B —— 访问异常(502/无法访问,但进程活着)1131. `NodeProjectInfo`:进程在跑、端口在听吗?→ 不在则回分支 A。1142. **反代端口匹配**:看 `listen`(实际监听)vs `project_config.port`(反代目标)是否一致;grep `proxy_pass` `node_<name>.conf`(`/www/server/panel/vhost/nginx/node_<name>.conf`)确认反代指向 `127.0.0.1:{实际端口}`。115 - 端口改了没同步 → `NodeProjectModify(action='config', port=实际端口)`(有 domains 时自动重写反代)。116 - 反代指向错误端口 → 同上。1173. **域名绑定**:`bind_extranet` 是否开、域名是否在 nginx `server_name`(未绑 → `NodeProjectModify(action='add_domain', domains=[...])`)。1184. 读 nginx error log(`log_files` 里 `/www/wwwlogs/<name>.error.log`)进一步定位。1195. 修正后验证。120121## 分支 C —— PM2 项目异常1221. `NodeProjectInfo` 看 pm2 状态(online/stopped/errored)。1232. `errored`(崩溃超限)→ 读 `log_files` 里 pm2 `err.log` 找根因;修复后用 `NodeProjectControl(action='restart')` 重启。1243. 读 `/www/wwwlogs/pm2/<name>/out.log|err.log`。1254. 依赖问题 → 同分支 A。126127## 分支 D —— 进程在但无响应 / 卡死1281. 应用日志尾部看异常/死锁(filesystem `Read`)。1292. `SystemInfo()` 看系统资源(CPU/内存/负载)。1303. 汇报,建议用户确认后 `NodeProjectControl(action='restart')` 重启。131132---133134# 日常运维速查(简单操作,直接按工具描述调用)135136| 需求 | 工具 |137|---|---|138| 查看项目列表/状态 | `NodeProjectInfo()`(project_name 留空) |139| 查看单项目详情(含日志路径) | `NodeProjectInfo(project_name=...)` |140| 启动 / 停止 / 重启 | `NodeProjectControl(project_name, action='start'/'stop'/'restart')` |141| 看应用日志 / pm2 / nginx 日志 | `NodeProjectInfo` 返回 `log_files` → filesystem `Read` |142| 改脚本/参数/node 版本/端口/用户/环境/内存 | `NodeProjectModify(action='config', ...)`(重启生效) |143| 加域名 / 删域名(至少留一个) | `NodeProjectModify(action='add_domain'/'remove_domain', domains=[...])` |144| node 版本检测 / 在线浏览 / 安装 | `NodeVersion(action='list'/'online'/'install', ...)` |145| 给 node 版本装全局模块(pm2/pnpm/yarn 等) | `NodeVersion(action='module', version=<nodejs_version>, modules=['pm2'])` |146| 删除项目(**高风险,先确认**) | `NodeProjectDelete(project_name=...)`(保留目录源码) |147148---149150# 完成汇报模板(部署成功)151152返回:项目名 / 项目类型 / 目录 / node 版本 / 运行用户 / 监听端口;若绑域名:访问地址 + nginx 配置路径;应用日志路径;启动验证结果(进程/端口/日志);遗留隐患提示(依赖未装、env 未注入等)。