配置
bm2.toml 描述一个项目:单个应用,配一个或多个实例。把它放在项目目录里,在该目录执行 bm2 serve。
这个文件里的任何内容都不会被执行。没有 shell 字符串,没有脚本钩子,也没有表达式语言——每个值都是数据,bm2 不认识的字段会直接报错,而不是被悄悄忽略。
不连守护进程也能校验:
bash
bm2 checkbm2 serve 在登记任何东西之前会跑同一套校验,所以非法配置永远到不了被托管的进程那里。
完整模板
toml
# 项目名:字母开头,后接字母、数字和下划线。
# 同时也是应用名,在所有已注册项目中必须唯一。
name = "api"
# 应用工作目录(绝对路径),默认为 bm2.toml 所在目录。
cwd = "/srv/api"
# 相对 cwd 的脚本路径,禁止 .. 段。
script = "src/index.ts"
# 运行时:bun 或 node。
runtime = "bun"
# 执行模式:cluster(默认,所有实例共享同一个 port,由内核分发
# 连接,应用监听需开启 reusePort)或 fork(端口从 port 起连续分配)。
exec_mode = "cluster"
# 实例数量(1..1024)。
instances = 2
port = 3000
# 单实例内存上限(MiB),超过按异常重启处理。
max_memory_mb = 512
# 连续异常重启预算,0 表示首次异常即 errored。
max_restarts = 10
# 自动重启前的延迟(毫秒),崩溃后固定,spawn 失败后按次数递增。
restart_delay_ms = 1000
# 干净退出早于此时长(毫秒)会计入重启预算。
min_uptime_ms = 10000
# SIGTERM 到 SIGKILL 的宽限期(毫秒),最大 60000。
stop_timeout_ms = 10000
# 日志轮转:单个日志超过 max_size_mb 即轮转,keep 决定保留几代
# 归档。keep = 0 表示代数不限。
[logs]
max_size_mb = 10
keep = 0字段
| 字段 | 必填 | 默认 | 约束 |
|---|---|---|---|
name | 是 | — | 字母开头,后接字母、数字或下划线 |
cwd | 否 | 配置文件所在目录 | 绝对路径 |
script | 是 | — | 相对 cwd,禁止 .. 段 |
runtime | 否 | bun | bun 或 node |
exec_mode | 否 | cluster | cluster 或 fork |
instances | 是 | — | 1 到 1024 |
port | 是 | — | 1 到 65535,且 port + instances - 1 也必须落在 1 到 65535 之内 |
max_memory_mb | 否 | 512 | 1 到 1048576 |
restart_delay_ms | 否 | 1000 | 大于等于 0 |
max_restarts | 否 | 10 | 大于等于 0,0 表示首次异常即 errored |
min_uptime_ms | 否 | 10000 | 大于等于 0 |
stop_timeout_ms | 否 | 10000 | 1 到 60000 |
[logs] max_size_mb | 否 | 10 | 1 到 102400 |
[logs] keep | 否 | 0 | 0 到 1000,0 表示代数不限 |
name 同时是状态目录下的目录名,所以只允许路径中安全的字符。
端口
默认的 cluster 模式下,所有实例共享同一个 port,由内核分发连接。exec_mode = "fork" 时端口从 port 起连续分配。
所有已注册项目的端口范围不得重叠——cluster 项目只占一个端口槽。冲突时 bm2 拒绝启动,并点名占着那段范围的项目。
越界即拒绝,不猜
越界的值会让整份配置失败,而不是被夹取或替换成默认值:
logs.max_size_mb = 0被拒绝,而不是理解成「永不轮转」。一个静默无限增长的日志,比让你写清大小更糟。logs.keep为负被拒绝,因为0已经表示不限代数。- 两个项目不能共用同一份配置文件——一份配置文件就是一个项目。
__VAR__ 占位符
文件里的字符串可以引用环境变量,标记是 __NAME__,写在引号里:
toml
mail_password = "__BM2_MAIL_PASSWORD__"
port = "__APP_PORT__"
max_memory_mb = "__APP_MEM_MB__"
[notify]
enabled = "__NOTIFY_ON__"标记必须待在引号里:不带引号的 __APP_PORT__ 不是 TOML 的值,文件当场就不成立了。数字与布尔字段也写引号里的标记,展开之后按字段类型解析,解析不了就报错并点名字段——port = "__APP_PORT__" 而 APP_PORT=abc 不会带着一个坏端口进入托管。
展开发生在解析之后,跑在解析出的字符串值上,所以注释里的 __NAME__ 只是散文,不会因为变量没设而报错。
变量未设置是硬错误并点名该变量,绝不静默空值——一个空 URL 会变成一次真实的投递尝试。
通知凭据正是它存在的理由:标记留在仓库里,真正的密文留在 .env 文件里,或解析配置的那个进程的环境里。