14 KiB
客户首装升级坑位排查
更新时间:2026-05-30
本文件记录本轮针对客户真实首装、升级、回滚、许可证服务、对象存储和文档真相的只读排查结果。它不是整改完成记录,后续修复时要以当前代码、配置和实际验证结果为准。
1. 结论
当前项目已经有比较清楚的主线:docker-compose.prod.yml + .env 做客户首装,deploy-registry.ps1 做版本化发布和已有客户升级。但离“客户能按步骤稳定安装和升级”还有几个硬坑:
- 首装照
.env.example复制后,主应用可能直接连到错误数据库地址。 - 许可证服务的
docker compose config目前用示例环境文件会直接失败。 - 主应用许可证激活依赖的关键变量没有进入部署必填清单。
- Go Agent 的一键升级链路存在失败后仍像成功、失败后不恢复旧容器的问题。
deploy-registry.ps1的自动回滚只退应用镜像和.env,不退数据库;涉及迁移时不能把它当完整回滚。- 外部 S3 / MinIO、NAS 目录权限、历史对象 ACL 还需要专项演练和审计。
2. P0:会直接卡首装、升级或回滚
P0-01 主应用首装可能连错数据库
客户影响:
客户按文档执行 cp .env.example .env 后,如果没有特别处理 DATABASE_URL,生产 Compose 会优先使用占位地址 postgresql://username:password@host:5432/database,而不是自动回退到内置 PostgreSQL。结果就是容器启动后数据库连接超时。
证据:
.env.example:41写入了占位DATABASE_URL=postgresql://username:password@host:5432/database。docker-compose.prod.yml:113会优先使用.env里的DATABASE_URL。docs/deployment/部署说明.md:60到75的必填变量清单没有提醒要删除占位DATABASE_URL或改成 Compose 内部地址。- 本轮只读验证:
docker compose -f docker-compose.prod.yml --env-file .env.example config输出的DATABASE_URL仍是占位地址。
建议:
- 首装用内置 PostgreSQL 时,
.env.example不应给一个会被直接使用的错误DATABASE_URL。 - 部署说明必须明确两种模式:内置 PostgreSQL 不填
DATABASE_URL;外部 PostgreSQL 才填写真实连接串。
P0-02 主应用许可证激活缺少真正必填变量
客户影响:
服务可能能启动,但首次激活许可证时失败。客户会以为“系统装好了但激活不了”,排查成本高。
证据:
.env.example:81到85有LICENSE_PUBLIC_KEY和LOCAL_ENCRYPTION_KEY。docs/deployment/部署说明.md:60到75的必填变量没有列这两个变量。lib/license/crypto-service.ts会检查许可证公钥配置。lib/license/license-service.ts保存本地许可证时依赖本地加密能力。
建议:
- 把
LICENSE_PUBLIC_KEY、LOCAL_ENCRYPTION_KEY加入部署必填清单。 - 首装脚本或检查脚本应在启动前发现缺失,而不是让客户到激活页才失败。
P0-03 许可证服务示例环境文件无法启动 Compose
客户影响:
如果客户或实施人员单独部署 license-server,按示例 .env.example 走,docker compose config 就会失败,服务起不来。
证据:
license-server/docker-compose.yml:29强制要求LICENSE_REQUEST_SIGNING_SECRET。license-server/.env.example:1到27没有这个变量。- 本轮只读验证:
docker compose -f license-server/docker-compose.yml --env-file license-server/.env.example config直接报缺少LICENSE_REQUEST_SIGNING_SECRET。
建议:
- 补齐
license-server/.env.example。 license-server/scripts/generate-keys.ts也要同步生成或提示这个变量。
P0-04 许可证服务示例数据库地址不适配容器
客户影响:
即使补上签名密钥,许可证服务容器内访问 localhost:5432 也会连到自己,不会连到 Compose 里的 PostgreSQL,仍然启动失败或运行异常。
证据:
license-server/.env.example:8和13使用localhost:5432。license-server/docker-compose.yml:38的数据库服务名是postgres。
建议:
- Compose 场景下示例地址应使用
postgres:5432。 - 本地裸机开发和 Compose 部署要分开给示例。
P0-05 Go Agent 健康检查超时后仍返回成功
客户影响:
Agent 升级时,新容器如果一直不健康,代码仍可能把结果当成功。前端显示“更新完成”,客户实际打不开系统。
证据:
flnxi-agent-go/internal/docker/client.go:195开始等待健康状态。flnxi-agent-go/internal/docker/client.go:216超时后仍return nil。
建议:
- 超时必须返回错误。
- 前端和接口要把失败状态明确展示出来。
P0-06 Go Agent 升级失败没有恢复旧容器
客户影响:
当前更新流程先停旧容器、删旧容器,再创建新容器。新容器失败时没有恢复旧容器,也没有数据库备份/恢复。客户现场可能直接断服务。
证据:
flnxi-agent-go/internal/api/handlers.go:502开始停止当前容器。flnxi-agent-go/internal/api/handlers.go:511到514删除旧容器。flnxi-agent-go/internal/api/handlers.go:537到540健康失败只记录错误,没有恢复旧容器。
建议:
- Agent 升级前要保存旧镜像、旧容器配置和数据备份状态。
- 新容器失败时至少自动恢复旧容器;涉及数据库迁移时必须进入人工确认流程。
P0-07 发布脚本自动回滚不等于数据库回滚
客户影响:
如果新版本已经执行了数据库迁移,之后健康检查失败,deploy-registry.ps1 只恢复 .env 和旧应用镜像,不恢复数据库。旧应用可能不兼容新库,客户会以为“已回滚”,但系统仍可能不可用。
证据:
scripts/docker-entrypoint.sh:39容器启动时自动执行npx prisma migrate deploy。deploy-registry.ps1:621到637会做pg_dump备份。deploy-registry.ps1:591到618的rollback()只恢复.env和旧应用镜像。
建议:
- 文档和脚本输出必须明确:自动回滚只是应用层回滚,不是完整数据回滚。
- 涉及数据库结构变更时,要有单独的恢复演练和人工确认步骤。
3. P1:会造成交付不稳或现场误操作
P1-01 远端 .env 写入失败时可能不会自动恢复
证据:
deploy-registry.ps1:688开始写远端.env。deploy-registry.ps1:698才挂载trap rollback ERR。
影响:
如果 .env 写入中途失败,可能留下半新半旧配置,下次手工 docker compose up 会继续踩坑。
建议:
- 在任何远端
.env写入前就启用回滚保护。
P1-02 手动数据库恢复文档不可直接照做
证据:
docs/deployment/部署说明.md:250到254把备份写在容器内/tmp。docs/deployment/部署说明.md:257说要复制到宿主机或外部位置。docs/deployment/部署说明.md:281到285恢复时又直接让容器读/tmp/flnxi-backup-...dump,没有给docker cp或标准输入恢复路径。
影响:
真出事故时,照文档恢复大概率找不到备份文件。
建议:
- 改成一条可执行路径:要么
docker cp到容器后恢复,要么用宿主机备份文件通过 stdin 管道恢复。
P1-03 示例密钥非空,Compose 不会拦截弱默认
证据:
.env.example:8、12、43、57到58都是replace-with...占位值。docker-compose.prod.yml:21、43到44、132到135只检查非空。
影响:
客户漏改时系统可能照样启动,带着可猜密钥上线。
建议:
- 增加启动前环境检查,发现
replace-with、your-、空公钥等占位值时直接阻断。
P1-04 NAS 挂载目录权限容易卡上传和激活
证据:
Dockerfile:292到293使用非 root 用户nextjs。docker-compose.prod.yml:140到142直接挂载./uploads、./data。
影响:
NAS 上目录常由宿主机 root 或图形界面用户创建,容器内 nextjs 可能没有写权限。客户会在上传素材或保存许可证实例时才发现失败。
建议:
- 部署文档给出创建目录和授权步骤。
- 更稳的方式是安装脚本自动处理目录权限。
P1-05 外部 S3 / MinIO 口径容易误导
证据:
.env.example:48到53给了外部 S3 写法。docker-compose.prod.yml:116到123在应用环境中强制覆盖为内置minio:9000和SERVER_IP:9000。
影响:
客户以为填 .env 就能接外部对象存储,实际仍会走内置 MinIO。
建议:
- 明确区分“内置 MinIO 模式”和“外部对象存储模式”。
- 外部对象存储模式需要单独 compose 覆盖文件或明确删改环境覆盖。
P1-06 历史对象权限还没有审计闭环
证据:
docker-compose.prod.yml:78到86当前 MinIO 初始化会默认关闭匿名下载。lib/s3.ts:137到139、184到186只有显式开启时才写public-read。docs/商业化地基排查问题清单_20260524.md:121已记录外部 S3 或历史对象级 ACL 需要单独审计。
影响:
新上传默认更安全,但旧 bucket、外部 S3、历史对象 ACL 可能仍然公开。
建议:
- 做一份只读审计脚本或 SOP,检查 bucket policy、对象 ACL、历史公开链接。
P1-07 上传预览图和 PSD 展示图仍有大图内存风险
证据:
app/api/materials/upload/route.ts:574预览图使用readBuffer()。app/api/materials/upload/route.ts:629PSD 展示图使用readBuffer()。app/api/materials/upload/route.ts:633会继续切割展示图。
影响:
大图或异常尺寸图片可能拖垮低配 NAS 内存。原始文件已经走流式上传,但图片处理链路还不是完全流式。
建议:
- 增加图片尺寸、像素数和单张展示图大小限制。
- 对 PSD 展示图切割做更明确的上限和错误提示。
P1-08 许可证对外接入文档过期
证据:
docs/其他项目接入许可证系统说明.md仍把自己描述为可单独对外发送的接入说明。- 当前
docs/许可证服务接入说明.md已把LICENSE_REQUEST_SIGNING_SECRET、HMAC 签名、timestamp/nonce/signature作为当前真相。
影响:
其他项目照旧文档接入,会缺签名参数,联调失败。
建议:
- 更新对外接入文档,补齐签名密钥和签名示例。
- 修完前不要单独对外发送旧文档。
4. P2:容易误导、但不一定马上阻断
P2-01 仍有不存在或不明确的一键安装口径
证据:
docker-compose.prod.yml:109到110提到install.sh会生成.env.production。- 根目录没有客户首装
install.sh;存在的是flnxi-agent-go/install.sh。
影响:
实施人员容易分不清 Compose 首装和 Agent 安装。
建议:
- 删除或改写这段注释。
P2-02 Podman 只写目标,没有可执行路径
证据:
docs/deployment/部署说明.md:53到54写支持 Docker / Podman。- 实际命令主要是
docker compose,例如docs/deployment/部署说明.md:136。
影响:
只提供 Podman 或图形容器入口的 NAS,客户仍不知道该怎么装。
建议:
- 明确当前客户主线是 Docker Compose。
- Podman 另开适配文档或验证清单,不要混在主线步骤里。
P2-03 历史安全评估仍在讲已修复问题
证据:
docs/许可证链路安全评估_20260312.md仍保留旧结论,内容与当前签名校验实现不一致。docs/README.md:45到50虽然标注历史材料,但仍放在活跃索引里。
影响:
后续排查会被旧结论带偏。
建议:
- 移入归档目录,或在顶部明确“历史快照,不作为当前事实来源”。
P2-04 商业化长清单仍混合旧状态和当前状态
证据:
docs/商业化地基排查问题清单_20260524.md:5说自己是事实入口,但又混有已完成、待整改和审计痕迹。docs/商业化地基8项收口记录_20260530.md:5把阶段性记录抬得过高。
影响:
后续接手时容易把旧状态当当前真相。
建议:
- 新增“当前开放项”短文档。
- 长清单改成内部审计流水,不作为默认事实入口。
P2-05 旧构建脚本仍保留 latest 口径
证据:
scripts/build-material-image.ps1:121仍会打latest。scripts/README.md:39已说明它是历史/内部维护备用入口。
影响:
内部人员可能误拿 latest 当客户升级锚点。
建议:
- 继续把客户升级主线限定在
deploy-registry.ps1的固定版本镜像。 - 旧脚本仅保留内部兼容用途,必要时移动到 legacy 目录。
5. 已做验证
- 本轮使用 multi-agent 做只读并行排查,分别覆盖首装入口、升级回滚、文档真相。
- 本地执行
docker compose -f docker-compose.prod.yml --env-file .env.example config,确认DATABASE_URL占位值会进入最终配置。 - 本地执行
docker compose -f license-server/docker-compose.yml --env-file license-server/.env.example config,确认因缺少LICENSE_REQUEST_SIGNING_SECRET失败。 - 子 agent 执行
deploy-registry.ps1PowerShell 语法解析,并运行npm test -- --runTestsByPath scripts/__tests__/deploy-registry-script.test.ts scripts/__tests__/docker-entrypoint.test.ts --runInBand,测试通过。 - 本轮没有启动客户环境容器,没有写入服务器,没有修改旧参考源码目录。
6. 建议修复顺序
- 先修首装会失败的环境变量问题:主应用
DATABASE_URL、许可证必填变量、许可证服务.env.example。 - 再修升级会假成功的问题:Go Agent 健康检查、Agent 更新失败恢复、禁止直接传
:latest镜像。 - 再补回滚闭环:发布脚本 trap 顺序、数据库恢复文档、数据库迁移后的回滚口径。
- 再做 NAS/对象存储专项:目录权限、外部 S3 模式、历史 bucket/ACL 审计。
- 最后清文档入口:许可证对外接入文档、历史安全评估、商业化长清单和阶段性记录口径。