# 命令

```bash
bm2 serve                 # 登记并启动：生产模式（读 .env 与 .env.production）
bm2 dev                   # 同上，开发模式（读 .env 与 .env.development），单实例并跟随日志
bm2 stop <name>           # 停下，但留在册
bm2 remove <name>         # 退役：停下、注销、交还名字，日志保留
bm2 clean [--dry-run]     # 扫掉状态目录里的无主目录
bm2 remove <name> -f      # 退役并删除它的状态目录（含日志）
bm2 kill [-y]             # 杀掉全部项目并退出守护进程
bm2 list [name] [--json]  # 显示状态，可只看一个项目或输出 JSON
bm2 check                 # 校验当前目录的 bm2.toml
bm2 reload                # 换入新守护进程，应用继续运行
bm2 notify [target]       # 给本项目的目标发一条测试消息
bm2 upgrade               # 升级到最新版并换班
bm2 version               # 打印版本
```

## 动词按层次分

`kill` 属于守护进程。应用的生命周期由 `stop`、`remove`、`clean` 表达。`kill` 曾经也能退役单个项目，那让一个动词同时横跨两个层次——帮助文本自己也解释不清。

| 命令 | 需要守护进程 | 作用 |
| --- | --- | --- |
| `serve` | 需要，没有则自动拉起 | 生产模式：登记、启动、写状态 |
| `dev` | 需要，没有则自动拉起 | 开发模式：登记、启动、写状态 |
| `stop <name>` | 需要 | 停实例，保登记 |
| `remove <name>` | 需要 | 停实例、注销、删登记与实例状态 |
| `clean` | 不需要 | 删掉状态目录下的无主目录 |
| `kill` | 需要 | 停掉全部项目并退出守护进程 |
| `list` | 不需要 | 没有守护进程时什么也不打印 |
| `check` | 不需要 | 只校验配置 |

## `serve` 与 `dev`

在放着 `bm2.toml` 的目录里执行。两者只差一件事：读哪两个 `.env` 文件。`serve` 用 production,`dev` 用 development，监督行为完全相同——崩溃重启、内存上限、风暴预算、通知上报一视同仁。调试时的行为必须和线上一致，否则调试本身就没有意义。

它会校验配置、登记项目并启动。每次执行都是一次完整重启，所以改任何字段——实例数、端口、脚本——在下一次执行时生效。

`dev` 只跑一个实例。开发会话就是一个进程监听一个端口，所以配置里的 `instances` 在开发模式下不生效。这不是被悄悄丢掉——项目记录里带着模式，`bm2 list --json` 同时给出模式和实际实例数。

`dev` 启动后会在当前终端跟随实例日志，直到你按 Ctrl+C。日志照常落盘，跟随只是看着，不是牵着：应用继续运行，停止它用 `bm2 stop <name>`。脚本与 CI 里用 `bm2 dev --no-follow`，它跟 `serve` 一样发完请求就返回。

对已停止的项目再执行一次会清除停止标记，把它恢复起来。模式记在项目里，`bm2 list --json` 的每一行都带着它，`bm2 reload` 也不会把它弄丢。

名字已被**另一份**配置文件占用时拒绝登记，并报出占用者的路径与两条出路：改名，或先退役那一个。

## `stop <name>`

优雅停止：先 `SIGTERM`，超过 `stop_timeout_ms` 后 `SIGKILL`。项目从 `list` 输出中消失，但仍留在册。

停止是持久的。守护进程重启或 `bm2 reload` 都不会把它带回来——只有 `bm2 serve` 或 `bm2 dev` 可以。

## `remove <name>`

退役项目：停实例、注销项目、删掉它的登记与实例状态文件。**日志保留**，因为想翻日志的时刻通常发生在项目消失之后。

`-f` 额外删除 `<state_dir>/<name>/`，连日志一起。这个删除由 CLI 执行，并且只在项目从名册中消失之后——否则就是从还握着这个文件的进程脚下把日志抽走。

退役之后名字可以重新登记。新项目不继承上一代的任何东西：统计、重启历史、启动探测，都不继承。

## `clean [--dry-run]`

扫描状态目录的第一层。没有 `project.json` 的目录就是无主目录：可能是硬删除留下的残骸，也可能来自另一台机器。

`clean` 逐条列出并删除，`--dry-run` 只列不动。它纯本地执行，不需要守护进程——状态目录换机器之后正是这种场景。

判据刻意保守——登记一定会先写 `project.json`——所以最坏结果是漏删某个目录，而不可能是删掉在册项目的日志。bm2 从未写过的目录会留在原地并给出说明。

## `kill`

```bash
bm2 kill -y     # 停掉全部项目、注销它们、退出守护进程
```

裸 `bm2 kill` 拒绝执行并说明原因，`-y` 是确认。带项目名是用法错误，提示会指路到 `stop` 与 `remove`。

对 `bm2d` 发送 `SIGTERM`（例如 `systemctl restart`）走的是同一条路径。

## `list`

实例以一张框线表列出：名称、实例序号、PID、端口、启动模式（`development` / `production`）、执行模式（`cluster` / `fork`）、状态、启动次数、内存、运行时长，以及项目的工作目录。不加项目名时，表上方还有一行守护进程摘要。重启间隙的 `stopped` 实例不显示，而 `bm2 stop` 停止的项目、`restarting` 与 `errored` 的实例保持可见，便于诊断。

`--json` 输出同样的机器可读数据。`list` 不需要守护进程，没有登记任何项目时就是一个空结果。

## `check`

解析并校验当前目录的 `bm2.toml`，不触碰守护进程：字段约束、`__VAR__` 展开、脚本是否存在。部署前跑一次很有用，它和 `serve` / `dev` 执行的是同一套检查。

## `reload`

在不停止被托管应用的前提下换入新的 `bm2d`：旧守护进程分离，新守护进程以不变的 PID 收养仍在运行的实例。手动替换二进制之后用它——`bm2 upgrade` 会自动做这一步。

CLI 与守护进程会互相校验协议版本。版本不一致时直接提示执行 `bm2 reload`，而不是给一个查不到原因的错误。

## `notify`

给本项目的通知目标发一条测试消息。写上目标名则只发给那一个。要在项目目录执行，因为目标来自那个项目的 `bm2.toml`。

测试消息绕过事件筛选，直接投递，用来确认端点和凭据是否可用。

## `upgrade`

查 mooncakes 上的最新版本，与本机版本比对。

有新版本时执行 `moon install`，把 `bm2` 与 `bm2d` 装到 `~/.moon/bin`，再让新守护进程接管。

装的是 `moon install` 的默认目录，与初次安装保持一致。手工从 GitHub Releases 下载的也要放进这个目录，否则升级替换的是 `~/.moon/bin` 那份，跑起来的还是你放的那份。

应用全程继续运行。

升级需要本机有 `moon` 命令。从 GitHub Release 手动下载安装的机器通常没有，这时会直接说清楚该怎么做，不会自动换一套下载逻辑。

`moon install` 失败时旧版本原样保留，不会留下半套新版本。

已经是最新版本时直接说清楚，不做无谓的安装。

只换了二进制、不想重新安装时用 `bm2 reload`，效果相同。

## `version`

打印当前版本，例如 `bm2 0.7.1`。

排查问题时先带上它，CLI 与守护进程的版本必须配套。

## 进度输出

`serve`、`dev`、`stop` 这类需要等待的命令会边等边打印实例的状态变化。

```
api: online
api: 停止中
api: 已停止
```

每行是一条状态变化，最后一个实例消失时打印移除行。

这是给人看的进度，不是结果。最终结果仍然是命令末尾那一句消息与随后打印的实例表。

`--json`（用于 `list`）只给机器读，不掺进度行。

## 异步行为

`serve`、`dev`、`stop`、`remove`、`kill` 都是异步的：守护进程立即应答，CLI 轮询到操作真正结束。这样守护进程永远不会因为一个停止超时而阻塞——也是这些命令在结果真实发生之后才报告结果的原因。

如果守护进程意外崩溃并留下过期的端点文件，下一个 CLI 请求会短暂等待、清掉过期文件、拉起一个新的守护进程，并重试一次。

## 退出码

| 码 | 含义 |
| --- | --- |
| 0 | 成功 |
| 1 | 失败：守护进程拒绝、连不上，或目标不存在 |
| 2 | 用法错误，在询问守护进程之前就被参数解析拦下 |
