15 KiB
15 KiB
部署方案
母体(完整前后端 + PostgreSQL)的交付与更新方案:产物包形态、服务器目录约定、容器编排、迁移时机、安全边界与固定操作流程。范围:只定「东西怎么装到运营方服务器、怎么更新」;不改业务语义,不覆盖 R13 的 CFW 抽取版部署面。设计对齐作者现场运维心智——本地产物 → 传到服务器约定目录 → 一条固定命令 → 看容器状态。
| 项目 | 内容 |
|---|---|
| 状态 | 已实现(定稿 2026-10-12) |
| 关联 | 技术选型.md(Bun / Hono / Drizzle / Vue)· 同步引擎设计.md(独立 apps/engine 进程)· 需求清单.md R13/R14 · users表字段设计.md(部署期初始化)· README.md |
| 产出 | 产物包布局 · 服务器目录 · Dockerfile / Compose / Caddy 要点 · 打包与更新流程 · 迁移与安全约定 |
1. 设计约束(来自现场经验与项目事实)
- 操作者心智(作者工作流,优先对齐):
- 前端:本地打包
dist/→ 放到服务器指定目录 → 执行固定命令 →docker ps看容器全绿; - 后端:拿到
jar类产物 → 上传指定目录 → 同一固定命令 → 看容器。
- 前端:本地打包
- 仓库事实:
apps/server与apps/engine两个独立进程;前端为静态dist(server 不托管静态);数据在 PostgreSQL;配置全走 env;packages/db已有按 journal 的幂等runMigrations。 - 能力边界:作者是前端,Docker 不娴熟;服务器侧只需 Docker,不要求 Bun / Node / 源码构建能力。
- 协作约定:交付物保证「一条命令链」可部署(AGENTS);密钥
SK_KEY_ENCRYPTION_SECRET丢失 = 已登记 key 全部无法解密。
由此收敛出的形态一句话:常驻 Docker 编排 + 每次更新只替换产物包 + 一条 compose 命令完成重建/重启/迁表。
2. 总体结构
本地(打包) 服务器(运营方)
────────── ────────────────
bun run pack:release /opt/naku/ ← 约定目录
├─ 前端 dist/ ├─ docker-compose.yml
├─ naku-server(linux 单文件) ├─ Dockerfile
├─ naku-engine(linux 单文件) ├─ .dockerignore
├─ naku-migrate(linux 单文件) ├─ Caddyfile
└─ drizzle/(迁移 SQL + journal) ├─ entrypoint-pg.sh
│ ├─ entrypoint-app.sh
▼ ├─ .env ← 密钥在此,不进包
release/ ──── 上传覆盖 ──────────► └─ release/ ← 每次更新整目录覆盖
固定命令: docker compose up -d --build
验收: docker compose ps
| 角色 | 职责 | 谁改 |
|---|---|---|
| 常驻编排(compose / Dockerfile / Caddyfile / entrypoint-*.sh / .env) | 描述系统怎么跑 | 装机时放好;改架构时才动 |
产物包 release/ |
当前要跑的版本 | 每次发版整目录替换 |
首次安装与日常更新同一套命令;差别只是首次多一步「把常驻编排放到服务器」。
3. 产物包 release/
本地 bun run pack:release 产出,内容固定为:
release/
dist/ # apps/web 构建产物(index.html · assets/…)
naku-server # Bun --compile,目标平台 linux-x64
naku-engine # 同上
naku-migrate # 同上;只跑迁移后退出
drizzle/ # packages/db/drizzle/ 的 SQL + meta/_journal.json
3.1 打包约定
| 项 | 约定 |
|---|---|
| 命令入口 | 优先 bun run naku pack(交互:平台 / 内容 / tar.gz);一键等价 bun run pack:release 或 naku pack --yes |
| 脚本落点 | scripts/cli.ts(citty + clack)· scripts/pack-core.ts(纯打包逻辑)· scripts/pack-release.ts(非交互兼容入口) |
| 目标平台 | 默认 bun-linux-x64;--target= 可切 bun-linux-arm64 / bun-darwin-* / bun-windows-*(含 arm64) |
| 构建顺序 | 清空输出目录 → build:web → 三次 --compile → 拷 drizzle/ →(可选)tar.gz |
| 禁止入包 | .env · 源码 · node_modules · 任何密钥 |
| 校验 | 打包后检查五条目都在且非空(Windows 本机编译允许 .exe 后缀);缺一即失败退出 |
| 本地试跑 | naku build / build:server / build:engine → bin/(当前平台),与部署包 release/ 分离 |
3.2 为何是单文件而不是「服务器上构建」或「上传源码」
- 对齐
jar心智:产物即交付物,服务器零构建依赖; - 更新体积小、失败面小(不跑
bun install/vite build); - 运行时镜像只需 Debian slim +
ca-certificates+libstdc++6,不需要 Bun。
代价:目标平台写在打包脚本里;换 CPU 架构需重新打包(在脚本参数上留出口即可)。
4. 服务器目录(/opt/naku/)
路径可改,下文以 /opt/naku 指代「约定目录」。
/opt/naku/
docker-compose.yml
Dockerfile
.dockerignore
Caddyfile
entrypoint-pg.sh
entrypoint-app.sh
.env
release/ ← 上传覆盖,不入库
5. 配置文件设计
5.1 Dockerfile(两阶段,只吃 release/)
# ---------- 应用运行时 ----------
# 用 Debian slim:与 Bun --compile(glibc)同源,兼容面更稳;
# CA 与 libstdc++ 显式安装,不依赖 slim 是否自带
FROM debian:bookworm-slim AS app
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates libstdc++6 \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY release/naku-server release/naku-engine release/naku-migrate /app/
COPY release/drizzle /app/drizzle
ENV MIGRATIONS_FOLDER=/app/drizzle
CMD ["/app/naku-server"]
# ---------- 网关(Caddy + 前端静态) ----------
FROM caddy:2-alpine AS proxy
COPY Caddyfile /etc/caddy/Caddyfile
COPY release/dist /srv
要点:
- 构建期不编译业务代码,只装配产物;
docker compose up -d --build的语义是「用当前release/重建镜像并重启」; app阶段一个镜像供server/engine/migrate三用,靠 compose 的command区分;- 应用运行时选
debian:bookworm-slim(glibc,对齐 Bun--compile目标);ca-certificates(HTTPS 证书校验)与libstdc++6(单文件 C++ 运行时)显式安装——两个都不指望镜像自带; - 网关仍用官方
caddy:2-alpine(与应用基底无关,体积友好); - PostgreSQL 使用官方镜像,不进本 Dockerfile。
5.2 docker-compose.yml(五服务)
| 服务 | 来源 | command | 说明 |
|---|---|---|---|
postgres |
postgres:17-alpine |
官方入口 | 卷 pgdata;healthcheck 用 pg_isready |
migrate |
target: app |
/app/naku-migrate |
一次性:restart: "no";迁完退出 |
server |
target: app |
/app/naku-server |
管理面 / 成员视图 API |
engine |
target: app |
/app/naku-engine |
轮询同步 |
caddy |
target: proxy |
官方入口 | 唯一对外:80 / 443 |
依赖关系:
postgres (healthy) ──► migrate (completed successfully) ──► server ──► caddy
└──────► engine
要点:
migrate独立服务,而不是塞进 server 启动:语义是「部署时迁表」,避免 server/engine 双进程抢迁;depends_on: service_completed_successfully保证先迁后跑。- 环境变量:
DATABASE_URL·SK_KEY_ENCRYPTION_SECRET·NEWAPI_BASE·COOKIE_SECURE·LOG_LEVEL·SYNC_INTERVAL_MS· 可选ADMIN_INIT_*——均从.env注入server/engine(migrate只需要DATABASE_URL)。 DATABASE_URL单源:用户 / 密码 / 库名都写在这一串里;entrypoint-pg.sh解析出POSTGRES_USER/PASSWORD/DB喂给官方库镜像,entrypoint-app.sh把主机改写为 compose 服务名postgres。开发与部署共用同一份.env语义。- 端口:仅
caddy映射到宿主机;默认80/443,可用.env的HTTP_PORT/HTTPS_PORT覆盖(容器内仍为 80/443)。server/engine/postgres均不对外。 - 容器名固定(
container_name):naku-postgres/naku-migrate/naku-server/naku-engine/naku-caddy——单副本约定下使用;服务间 DNS 仍用服务名(如server:18456)。
5.3 Caddyfile
# 站点地址按部署环境改:
# 本地/内网 HTTP → :80
# 公网 HTTPS → your.domain.example(Caddy 自动签证书)
:80 {
root * /srv
handle /api/* {
reverse_proxy server:18456
}
handle {
try_files {path} /index.html
file_server
}
}
与 API契约-公共约定 的命名空间约定一致:/api/* 归接口,其余归 SPA(非 /api 回退 index.html)。
5.4 entrypoint-pg.sh / entrypoint-app.sh
| 脚本 | 职责 |
|---|---|
entrypoint-pg.sh |
从 DATABASE_URL 拆出 POSTGRES_USER / POSTGRES_PASSWORD / POSTGRES_DB,exec 官方 docker-entrypoint.sh postgres |
entrypoint-app.sh |
将 DATABASE_URL 的主机段改写为 postgres(保留端口与路径),再 exec "$@" 启动传入的二进制 |
二者属常驻编排,打进镜像或只读挂载皆可(建议只读挂载,改脚本不用重建镜像)。
5.5 packages/db/src/migrate-cli.ts(新增入口)
loadEnv → createDb(DATABASE_URL) → runMigrations(db, MIGRATIONS_FOLDER) → 退出码
- 复用已有
runMigrations(Drizzle migrator,按 journal 幂等:已应用的迁移号跳过); - 失败打日志并以非 0 退出,使
migrate服务失败 →server/engine不会启动(service_completed_successfully门闩)。
6. 操作流程
6.1 首次安装
# 本地
bun run pack:release
# 服务器
# 将 docker-compose.yml · Dockerfile · .dockerignore · Caddyfile · entrypoint-*.sh 放入 /opt/naku/
# 将 release/ 上传到 /opt/naku/release/
cp .env.example /opt/naku/.env # 再编辑填写
chmod 600 /opt/naku/.env
cd /opt/naku
docker compose up -d --build
docker compose ps
6.2 日常更新(与首次同一命令)
# 本地
bun run pack:release
# 上传,整目录覆盖 /opt/naku/release/
# 服务器(固定命令)
cd /opt/naku
docker compose up -d --build
docker compose ps
- 有表变更时,新
release/drizzle/自动带新 SQL,migrate启动时补跑——无需手工; - 观察构建/迁移日志:
docker compose logs migrate;同步:docker compose logs -f engine。
6.3 HTTPS(可选)
Caddyfile站点行:80→ 域名;.env中COOKIE_SECURE=true;- 重新执行固定命令。
6.4 验收标准
docker compose ps:postgreshealthy,server/engine/caddyrunning,migrateexited(0);- 浏览器打开站点可见页面;
/api/docs可访问; docker compose logs engine出现就绪/轮次日志(已配置 key 时)。
7. 迁移策略(裁决)
| 问题 | 裁决 |
|---|---|
| 会不会「多灌几次」? | 不会。 使用 Drizzle migrate()(journal 表),重复执行只应用未跑过的迁移号 |
| 谁来跑? | compose 一次性服务 migrate(naku-migrate 单文件),不随 server/engine 长驻 |
| 何时跑? | 每次固定命令(含首次)启动/更新时 |
| 不用什么? | 不用 db:push 进部署链——它是 schema diff 硬推,不适合生产更新命令 |
| 失败怎么办? | migrate 非 0 退出 → server/engine 不启动;修复 SQL 或数据后重跑固定命令 |
8. 安全约定
| 点 | 约定 |
|---|---|
.env 位置 |
只在服务器约定目录;不进产物包、不进 git |
| 权限 | chmod 600 .env,仅部署用户可读 |
SK_KEY_ENCRYPTION_SECRET |
生成一次、入库后不可更换;与数据库同等备份(.env 必须备份) |
| 对外暴露 | 仅 Caddy 的 80/443;库与应用进程不映射公网端口 |
COOKIE_SECURE |
必须与访问方式匹配:HTTP + true 会「登录后立刻 401」;HTTPS/域名用 true |
| 日志 | 不打印密钥与完整 DATABASE_URL(含密码) |
| 范围说明 | 单运营方内部服务器场景;不上 vault/KMS。若未来有多租户或公有多环境,再升档 |
9. 与开发方式的关系
| 开发 | 部署 | |
|---|---|---|
| 进程 | bun run dev:server / dev:engine / dev:web |
compose:server / engine / caddy |
| 库 | 任意 PostgreSQL,DATABASE_URL 指向即可 |
compose 内 postgres,同一 DATABASE_URL 语义(主机由 entrypoint 改写) |
| 建表 | bun run db:push(开发期) |
migrate 服务(生产幂等迁移) |
| 前端 | Vite dev server | release/dist 由 Caddy 托管 |
| 配置 | .env |
服务器 .env |
开发不依赖本方案的 Docker 配置;部署也不依赖本机 Bun。
10. 文件改动清单
| 文件 | 动作 |
|---|---|
scripts/cli.ts · scripts/pack-core.ts · scripts/pack-release.ts |
打包/构建 CLI 与核心逻辑 |
packages/db/src/migrate-cli.ts |
迁移 CLI 入口 |
package.json |
naku · pack:release · build:server · build:engine |
Dockerfile · docker-compose.yml · .dockerignore |
两阶段镜像 · 五服务(含 container_name) |
Caddyfile · entrypoint-pg.sh · entrypoint-app.sh |
与 compose 同级平铺 |
README.md |
「部署」章与文档地图 |
AGENTS.md |
命令表(pack / build / naku) |
.env.example |
部署语义注释 |
11. 已知取舍(否决时改这里)
migrate独立单文件:多一个产物,换来「先迁后跑、失败不启」的清晰门闩。若嫌多,可改为 server 启动时迁(仍幂等),但失去 migrate 失败阻断 engine 的不对称控制。- 目标平台写死 linux-x64:匹配常见服务器;ARM 用打包参数切换,不为此引入交叉编译矩阵。
DATABASE_URL单源 + 两个 entrypoint:少一处密码不一致;多两个常驻小 shell。若偏好 compose 原生POSTGRES_*,需接受密码在.env写两处(或DATABASE_URL仅应用用)。- 产物包而非源码包:服务器零构建;代价是发版必须在本地(或 CI)跑
pack:release。
12. 验收清单(实现后)
bun run naku pack --yes(或pack:release)在 Windows 产出完整release/(五条目齐全)- 首次安装按 §6.1 一次命令链可起,
docker compose ps形态符合 §6.4 - 仅替换
release/后重跑固定命令,版本切换成功且数据保留(卷不重建) - 连续两次固定命令:第二次
migrate不重复应用已存在迁移(日志可证) - 故意放一份坏 SQL:
migrate失败,server/engine未启动 COOKIE_SECURE与 HTTP/HTTPS 交叉场景符合 §8- 产物包内无
.env、无源码、无node_modules