flnxi-Wu/docs/客户首装升级坑位排查_20260530.md
2026-05-30 18:25:51 +08:00

16 KiB
Raw Blame History

客户首装升级坑位排查

更新时间:2026-05-30

本文件记录本轮针对客户真实首装、升级、回滚、许可证服务、对象存储和文档真相的排查与修复状态。它是 2026-05-30 的阶段记录,后续仍以当前代码、配置和实际验证结果为准。

1. 结论

当前项目已经有比较清楚的主线:docker-compose.prod.yml + .env 做客户首装,deploy-registry.ps1 做版本化发布和已有客户升级。本轮已先处理会直接卡住客户首装、升级和回滚判断的 7 类硬坑:

  • .env.example 不再给内置 PostgreSQL 模式写错误 DATABASE_URL 占位值。
  • 主应用许可证激活依赖的 LICENSE_PUBLIC_KEY、LOCAL_ENCRYPTION_KEY 已进入生产必填和启动前检查。
  • license-server/.env.example 已补 LICENSE_REQUEST_SIGNING_SECRET,示例数据库地址已改为 Compose 服务名 postgres。
  • Go Agent 健康检查超时会返回失败,升级健康失败后会尽力恢复旧容器。
  • deploy-registry.ps1 的回滚保护已提前到远端 .env 写入前,并明确提示“自动回滚不恢复数据库”。
  • 部署文档已补内置/外部 PostgreSQL、内置/外部 MinIO、NAS 挂载目录权限、历史 ACL 审计和 Podman 口径。
  • 许可证外部接入文档和历史安全评估已补当前签名要求和历史快照提示。

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 留空,外部 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 保存本地许可证时依赖本地加密能力。

当前处理:

  • 已修复:docker-compose.prod.yml 强制要求这两个变量,scripts/docker-entrypoint.sh 也会在启动前拦截空值和明显占位值;部署说明同步加入必填清单。

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,文案说明裸机本地调试可改回 localhost 或用 LICENSE_DB_HOST=localhost 生成。

P0-05 Go Agent 健康检查超时后仍返回成功

客户影响:

Agent 升级时,新容器如果一直不健康,代码仍可能把结果当成功。前端显示“更新完成”,客户实际打不开系统。

证据:

  • flnxi-agent-go/internal/docker/client.go:195 开始等待健康状态。
  • flnxi-agent-go/internal/docker/client.go:216 超时后仍 return nil。

当前处理:

  • 已修复:WaitForHealthy 超时会返回错误,并支持上下文取消;新增 Go 测试覆盖超时失败。

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 健康失败只记录错误,没有恢复旧容器。

当前处理:

  • 已修复第一层保护:Go Agent 更新失败会尽力删除失败的新容器,并用旧容器配置和旧镜像重建旧版本容器;直接传入 image 时也禁止无固定 tag 或 latest。
  • 仍需注意: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 和旧应用镜像。

当前处理:

  • 已修复:发布脚本回滚输出和部署文档都明确“自动回滚只恢复应用镜像和 .env,不自动恢复数据库”;手动数据库恢复命令已补 docker cp 和标准输入两条可执行路径。

3. P1:会造成交付不稳或现场误操作

P1-01 远端 .env 写入失败时可能不会自动恢复

证据:

  • deploy-registry.ps1:688 开始写远端 .env。
  • deploy-registry.ps1:698 才挂载 trap rollback ERR。

影响:

如果 .env 写入中途失败,可能留下半新半旧配置,下次手工 docker compose up 会继续踩坑。

当前处理:

  • 已修复:trap rollback ERR 已提前到 upsert_env APP_IMAGE 之前。

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 和宿主机标准输入恢复两种路径。

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 可能没有写权限。客户会在上传素材或保存许可证实例时才发现失败。

当前处理:

  • 已修复文档层:部署说明已加入 uploads/、data/ 目录创建和可写权限说明。
  • 后续可继续做安装脚本自动处理。

P1-05 外部 S3 / MinIO 口径容易误导

证据:

  • .env.example:48 到 53 给了外部 S3 写法。
  • docker-compose.prod.yml:116 到 123 在应用环境中强制覆盖为内置 minio:9000 和 SERVER_IP:9000。

影响:

客户以为填 .env 就能接外部对象存储,实际仍会走内置 MinIO。

当前处理:

  • 已修复主线配置和文档:docker-compose.prod.yml 现在允许 .env 覆盖 S3 变量;部署说明区分内置 MinIO 和外部 S3 / MinIO。

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 可能仍然公开。

当前处理:

  • 已补文档提醒:部署说明和坑位清单明确外部 S3、历史 bucket policy、对象级 ACL、历史公开链接需单独只读审计。
  • 后续仍建议补自动审计脚本或 SOP。

P1-07 上传预览图和 PSD 展示图仍有大图内存风险

证据:

  • app/api/materials/upload/route.ts:574 预览图使用 readBuffer()。
  • app/api/materials/upload/route.ts:629 PSD 展示图使用 readBuffer()。
  • app/api/materials/upload/route.ts:633 会继续切割展示图。

影响:

大图或异常尺寸图片可能拖垮低配 NAS 内存。原始文件已经走流式上传,但图片处理链路还不是完全流式。

建议:

  • 增加图片尺寸、像素数和单张展示图大小限制。
  • 对 PSD 展示图切割做更明确的上限和错误提示。

P1-08 许可证对外接入文档过期

证据:

  • docs/其他项目接入许可证系统说明.md 仍把自己描述为可单独对外发送的接入说明。
  • 当前 docs/许可证服务接入说明.md 已把 LICENSE_REQUEST_SIGNING_SECRET、HMAC 签名、timestamp/nonce/signature 作为当前真相。

影响:

其他项目照旧文档接入,会缺签名参数,联调失败。

当前处理:

  • 已修复:docs/其他项目接入许可证系统说明.md 已补 LICENSE_REQUEST_SIGNING_SECRET、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 安装。

当前处理:

  • 已修复:docker-compose.prod.yml 中误导性的客户首装脚本口径已改成当前 .env / Compose 真相。

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 虽然标注历史材料,但仍放在活跃索引里。

影响:

后续排查会被旧结论带偏。

当前处理:

  • 已修复:历史安全评估顶部已加历史快照提示,并补充主链路 HMAC 签名结论已过期;docs/README.md 也要求引用前按当前代码复核。

P2-04 商业化长清单仍混合旧状态和当前状态

证据:

  • docs/商业化地基排查问题清单_20260524.md:5 说自己是事实入口,但又混有已完成、待整改和审计痕迹。
  • docs/商业化地基8项收口记录_20260530.md:5 把阶段性记录抬得过高。

影响:

后续接手时容易把旧状态当当前真相。

当前处理:

  • 已修复阶段性记录口径:docs/商业化地基8项收口记录_20260530.md 已改为 2026-05-30 阶段快照,不再高于当前代码和部署文件。

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 会展开到内置 postgres 服务,S3_ACCESS_KEY_ID / S3_SECRET_ACCESS_KEY 会从 MINIO_ROOT_USER / MINIO_ROOT_PASSWORD 回填。
  • 本轮修复后执行 docker compose -f license-server/docker-compose.yml --env-file license-server/.env.example config,确认示例环境文件已包含 LICENSE_REQUEST_SIGNING_SECRET,并展开到 @postgres:5432。
  • 执行 npm test -- --runTestsByPath scripts/__tests__/docker-entrypoint.test.ts scripts/__tests__/deploy-registry-script.test.ts license-server/lib/__tests__/env-example.test.ts --runInBand,3 个测试套件、9 个测试通过。
  • 执行 npm run check,type-check、next lint、prisma validate 通过。
  • 本机没有直接安装 Go,已使用 Docker golang:1.22-alpine 执行 go test ./...,Go Agent 全量测试通过。
  • 本轮没有启动客户环境容器,没有写入服务器,没有修改旧参考源码目录。

6. 建议修复顺序

  1. 先修首装会失败的环境变量问题:主应用 DATABASE_URL、许可证必填变量、许可证服务 .env.example。
  2. 再修升级会假成功的问题:Go Agent 健康检查、Agent 更新失败恢复、禁止直接传 :latest 镜像。
  3. 再补回滚闭环:发布脚本 trap 顺序、数据库恢复文档、数据库迁移后的回滚口径。
  4. 再做 NAS/对象存储专项:目录权限、外部 S3 模式、历史 bucket/ACL 审计。
  5. 最后清文档入口:许可证对外接入文档、历史安全评估、商业化长清单和阶段性记录口径。