production-deploy.md 7.52 KB

生产部署环境与上线流程

状态:✅ 当前有效。2026-08-27 首次整理,来源于登录服务器实地核查(非猜测)。

服务器

  • 地址:root@60.205.214.169(hostname hexyun)。本机已配好免密 SSH,直接 ssh root@60.205.214.169 即可,不需要额外问用户要密码/密钥。
  • 对外域名 lemonboss.wudaima.com 由这台机器提供服务。
  • 同一台机器上还跑着大量不相关的容器(wordpress、milvus、rasa、chroma 等),docker 相关命令务必认准容器名 lemonboss,不要用 docker restart 之类命令波及别的服务。

部署拓扑

  • 代码目录(host 侧):/opt/lemonboss,属主 hexyun:hexyun。是一个独立 git 仓库,origin 与本地仓库一致,指向 git@gitlab-wuwei.hexpm.com:yibo/lemonboss.git,跟踪分支 main。
  • 运行方式:Docker 容器 lemonboss(镜像 lemonboss-base:latest),重启策略 unless-stopped。
    • /opt/lemonboss bind-mount 到容器内 /app
    • /opt/data bind-mount 到容器内 /data
    • entrypoint:docker-entrypoint.sh → node server/index.js
  • 端口:容器内 3010 → 宿主机 127.0.0.1:3011(对外走反向代理到 lemonboss.wudaima.com,代理具体配置未查)。

更新上线流程

  1. 本地正常提交,git push origin main。
  2. 登录服务器:ssh root@60.205.214.169,cd /opt/lemonboss && git pull origin main。
    • 必须用 root 拉代码:目录属主虽是 hexyun,但只有 root 配了到 gitlab-wuwei.hexpm.com 的部署 SSH key;hexyun 没配任何 key,sudo -u hexyun git pull 会直接因 host key / 鉴权失败报错。
    • git pull 完成后,把本次改动涉及的文件 chown hexyun:hexyun 改回属主(git pull 用 root 跑,新拉下来的文件会变成 root:root),避免以后应用进程(跑在 hexyun 身份下)对这些文件失去写权限。整个目录不建议全量 chown -R(node_modules 体量大且没必要)。
  3. docker restart lemonboss 让新代码生效——bind mount 是实时的,但 Node 进程不会自动热重载,必须重启容器内进程。
  4. 验证:
    • curl http://127.0.0.1:3011/api/health 应返回 200
    • docker logs --tail 40 lemonboss 确认启动日志无异常(正常应看到 ✅ 服务启动: http://0.0.0.0:3010 等心跳/看门狗初始化行)

⚠️ 环境变量是固化在容器里的,改 .env + docker restart 不会生效

2026-08-27 踩过一次坑:ANTHROPIC_BASE_URL / ANTHROPIC_API_KEY / ANTHROPIC_MODEL(决定 Claude CLI 实际打到哪个模型厂商)这几个变量,是当初 docker run -e ... 时直接固化进容器配置的(docker inspect lemonboss --format '{{.Config.Env}}' 能看到),跟 bind-mount 进去的 /opt/lemonboss/.env 文件是两码事——Node 进程虽然也会 dotenv 加载 .env,但容器注入的 env 已经先一步存在于 process.env,dotenv 默认不覆盖已存在的变量,所以改 .env 完全不生效,docker restart 只是重启同一个容器进程,也不会重新读取新的 -e 值。

要真正改这类模型/密钥配置,必须重建容器(docker restart 无效):

# 1. 保留旧容器做回滚,不要直接删
TS=$(date +%Y%m%d%H%M%S)
docker rename lemonboss lemonboss-old-$TS
docker stop lemonboss-old-$TS

# 2. 用完全相同的镜像/端口/挂载/重启策略/JWT_SECRET 重建,只改要换的那几个 -e
docker run -d --name lemonboss \
  --restart unless-stopped \
  --user 1000:1000 \
  -p 127.0.0.1:3011:3010 \
  -v /opt/lemonboss:/app \
  -v /opt/data:/data \
  -e NODE_ENV=production -e PORT=3010 -e HOST=0.0.0.0 \
  -e CORS_ORIGIN=https://lemonboss.wudaima.com \
  -e JWT_SECRET=<原样抄一份,改错会让所有已登录用户掉线> \
  -e ANTHROPIC_BASE_URL=... -e ANTHROPIC_API_KEY=... -e ANTHROPIC_MODEL=... \
  lemonboss-base:latest

# 3. 验证:health 200 + docker exec 进去看 /proc/<pid>/environ 确认新值生效 + 跑一次真实 invoke

完整现有配置可以先 docker inspect lemonboss > backup.json 存一份再动手,照抄里面的 Env/Mounts/HostConfig.PortBindings/Config.User 等字段,一个都不能漏,尤其是 JWT_SECRET。

当前模型配置(2026-08-27 起):智谱 GLM-5.3-Flash,ANTHROPIC_BASE_URL=https://open.bigmodel.cn/api/anthropic,ANTHROPIC_MODEL=glm-5.3-flash(此前是 DeepSeek deepseek-v4-flash + https://api.deepseek.com/anthropic)。

跨环境同步公司数据(graph.sqlite)

2026-08-28 把本地 mycompany / innovator-fde-trial 两家公司同步到生产时踩出来的经验。 「公司数据」分两半,必须分开处理:

  1. 工作文件:data/companies/{cid}/(memory / conversations / workspace 等),普通文件,直接 rsync -az 到 /opt/lemonboss/data/companies/{cid}/,传完 chown hexyun:hexyun(root 传的文件 默认 root:root)。
  2. 注册信息:「这家公司雇了谁、org/work graph 长什么样、goal/task/run 记录」全部在 data/graph.sqlite 里,不在文件系统里——只搬文件,网页上完全看不到这家公司。

第 2 部分不要手写 SELECT * 去搬,用 scripts/sync-company-to-graph-db.js(先 --dry-run 看 一遍行数,确认无误再去掉这个 flag 跑真的)。这个脚本已经绕开两个手搓时踩过的坑:

  • 列序不能信:本地库和生产库的表字段名一样,但物理列序可能不同——一个是老库一路 ALTER TABLE 攒出来的顺序,一个是某次重建/清空后吃到的当时最新 CREATE TABLE 字面量顺序。 SELECT * 位置对齐的 INSERT 会把值静默写进错误的列,NOT NULL 列能报错拦一部分,可空/ 同类型的列错了不会有任何提示。详见 server/services/graph-db.js 的 migrate() 函数头注释。
  • graph_runs ⇄ graph_run_nodes 是循环外键(graph_runs.parent_node_run_id 指 graph_run_nodes(id),graph_run_nodes.run_id 指 graph_runs(id)),批量搬迁需要先插 graph_runs(parent_node_run_id 置空)、再插 graph_run_nodes、最后回填。回填那条 UPDATE 的关联子查询必须给内外表都加别名——两边都用裸表名的话,SQLite 会把子查询里的列 解析到错误的关联对象,静默回填成 NULL,changes 还显示"成功",不会报错。

操作顺序:先在生产上备份(tar 打包 companies/ 目录 + 用 db.backup() 存一份 graph.sqlite.bak-<ts>),再 rsync 文件,再跑同步脚本(对着生产的 /app/data/graph.sqlite, 可以通过 docker exec lemonboss node scripts/sync-company-to-graph-db.js ... 或者先 docker cp 脚本进去跑),最后 docker restart lemonboss 让心跳/graph-seed 重新装载。

其他线索

  • /opt/lemonboss/.env 里的 OPEN_API_KEYS 是 /api/open/* 外部调用的鉴权 key,本文档不记录明文值。注意:.env 文件本身对 ANTHROPIC_* 这三个变量是摆设(见上),但 OPEN_API_KEYS 等其他没有被 docker run -e 固化的变量,还是照常从 .env 里读取生效的——只有被固化过的那几个例外。
  • /opt/lemonboss/.data/jobs-openapi.json 是 open-api invoke 端点(server/routes/open-api.js)的 job 记录,容器重启时会从这个文件恢复历史 job(日志里 [JobManager] Restored N jobs 就是这个)——这是 job 记录,不是会话历史,二者存储位置不同(会话历史见 system/agents/{agentId}/{companyId|_global}/conversations/)。