生产部署环境与上线流程
状态:✅ 当前有效。2026-08-27 首次整理,来源于登录服务器实地核查(非猜测)。
服务器
- 地址:
root@60.205.214.169(hostnamehexyun)。本机已配好免密 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/lemonbossbind-mount 到容器内/app/opt/databind-mount 到容器内/data- entrypoint:
docker-entrypoint.sh→node server/index.js
- 端口:容器内 3010 → 宿主机
127.0.0.1:3011(对外走反向代理到lemonboss.wudaima.com,代理具体配置未查)。
更新上线流程
- 本地正常提交,
git push origin main。 - 登录服务器:
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 体量大且没必要)。
- 必须用
docker restart lemonboss让新代码生效——bind mount 是实时的,但 Node 进程不会自动热重载,必须重启容器内进程。- 验证:
curl http://127.0.0.1:3011/api/health应返回 200docker 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 两家公司同步到生产时踩出来的经验。
「公司数据」分两半,必须分开处理:
- 工作文件:
data/companies/{cid}/(memory / conversations / workspace 等),普通文件,直接rsync -az到/opt/lemonboss/data/companies/{cid}/,传完chown hexyun:hexyun(root 传的文件 默认 root:root)。 - 注册信息:「这家公司雇了谁、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/)。