Skip to content

配置 ​

bm2.toml 描述一个项目:单个应用,配一个或多个实例。把它放在项目目录里,在该目录执行 bm2 serve。

这个文件里的任何内容都不会被执行。没有 shell 字符串,没有脚本钩子,也没有表达式语言——每个值都是数据,bm2 不认识的字段会直接报错,而不是被悄悄忽略。

不连守护进程也能校验:

bash
bm2 check

bm2 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否bunbun 或 node
exec_mode否clustercluster 或 fork
instances是—1 到 1024
port是—1 到 65535,且 port + instances - 1 也必须落在 1 到 65535 之内
max_memory_mb否5121 到 1048576
restart_delay_ms否1000大于等于 0
max_restarts否10大于等于 0,0 表示首次异常即 errored
min_uptime_ms否10000大于等于 0
stop_timeout_ms否100001 到 60000
[logs] max_size_mb否101 到 102400
[logs] keep否00 到 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 文件里,或解析配置的那个进程的环境里。