## bunfig.toml 没拷进容器,才是那批「本地能过、容器过不了」的根因 bunfig.toml 里设了 `linker = "hoisted"`,但 Dockerfile 只拷了 package.json 和 bun.lock,于是容器里用的是默认的 isolated 布局(包都在 node_modules/.bun/ 下), 本地靠"提升"才解析得到的包在容器里一律 Could not resolve —— 而本地构建始终是好的, 只有镜像构建才炸。之前是一个一个补直接依赖补过去的(那是对的、该保留), 这里让两边布局一致,是第二道保险。 带上之后装 622 个包(isolated 是 1238),镜像重建通过,二进制在容器里 serve + healthcheck 正常。 顺手补了 main.ts 帮助文本里漏掉的 healthcheck 子命令(compose 里把 command 写错时,看到的就是这行)。 ## OJ2/CLAUDE.md OJ2 是独立仓库,之前没有自己的项目指引。写了一份,重点是几条「不知道就会踩」的: - **本机 Docker 可用**,整套依赖和上线演练都能在本机跑 —— 别沿用上一代 "本机跑不起来后端"的旧假设 - 单二进制不能依赖 node_modules,`.node` 资源导入 dev 和编译两种形态行为不同, **改完两种形态都要跑** - SQL 判题 spawn 的是二进制自己,入口必须有 argv 分发,那道递归闸不能删 - 判题状态码三处同步、raw_password 要保留、比赛只有 ACM、前端要兼容老 Chrome - 不写迁移:新旧后端跑同一套表结构,这是回滚能成立的前提 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
53 lines
2.0 KiB
TypeScript
53 lines
2.0 KiB
TypeScript
/**
|
||
* 唯一入口。所有角色都从这里按子命令分叉。
|
||
*
|
||
* 为什么不保留三个独立入口文件:`bun build --compile` 一次只产出一个二进制,
|
||
* 而部署要跑 HTTP 服务、判题 worker,SQL 判题还要 fork 一个能被 SIGKILL 的子进程
|
||
* (见 judge/sql/index.ts)。三个入口就得编三个二进制、镜像里塞三份运行时。
|
||
* 一个二进制 + 子命令,镜像里只有一份,compose 里改 command 就能换角色。
|
||
*
|
||
* oj2-api # 等同 serve
|
||
* oj2-api serve # HTTP + WebSocket
|
||
* oj2-api worker # BullMQ 判题消费者
|
||
* oj2-api healthcheck # 探活,给 Dockerfile 的 HEALTHCHECK 用
|
||
* oj2-api sql-child # SQL 判题子进程,由服务自己 spawn,不该手动调
|
||
*
|
||
* 用动态 import 而非顶层 import:这几个模块都有导入即执行的副作用
|
||
* (Bun.serve、连 Redis 开消费者),静态导入会让 sql-child 也把整个服务拉起来。
|
||
*/
|
||
|
||
export {} // 只有动态 import 的话 TS 不认这是模块,顶层 await 会报错
|
||
|
||
const command = process.argv[2] ?? "serve"
|
||
|
||
switch (command) {
|
||
case "serve":
|
||
await import("./index")
|
||
break
|
||
case "worker":
|
||
await import("./worker")
|
||
break
|
||
case "sql-child": {
|
||
const { runSqlChild } = await import("./judge/sql/child")
|
||
await runSqlChild()
|
||
break
|
||
}
|
||
// 运行镜像是 debian-slim,没有 curl/wget,探活让二进制自己做。
|
||
// 只打 /health,不碰库 —— 库挂了该由库自己的 healthcheck 报,
|
||
// 不该让 api 容器跟着被判成不健康、进而被重启。
|
||
case "healthcheck": {
|
||
const { config } = await import("./config")
|
||
try {
|
||
const response = await fetch(`http://127.0.0.1:${config.port}/health`, {
|
||
signal: AbortSignal.timeout(3000),
|
||
})
|
||
process.exit(response.ok ? 0 : 1)
|
||
} catch {
|
||
process.exit(1)
|
||
}
|
||
}
|
||
default:
|
||
console.error(`未知子命令:${command}\n可用:serve | worker | healthcheck | sql-child`)
|
||
process.exit(2)
|
||
}
|