20 KiB
客户首装升级坑位排查
更新时间:2026-05-30
本文件记录本轮针对客户真实首装、升级、回滚、许可证服务、对象存储和文档真相的排查与修复状态。它是 2026-05-30 的阶段记录,后续仍以当前代码、配置和实际验证结果为准。当前飞牛 VM 已完成首装、初始化、免费版激活、上传、备份和恢复验收,本文件后续只保留历史坑位和已修项,不再把“还没跑通”当当前事实。
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写入前,并明确提示“自动回滚不恢复数据库”。- 新增
scripts/smoke-install.ps1和scripts/backup-restore.ps1,把首装基础验收、备份、恢复和恢复后健康检查从零散手工命令收口成可执行脚本。 - 真实 Docker 首装模拟暴露出生产镜像入口脚本可能因 Windows 换行在 Linux 容器内无法执行,已在
Dockerfile中强制转为 Linux 换行。 - 部署文档已补内置/外部 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和标准输入两条可执行路径。 - 已加强:
deploy-registry.ps1 -Action DeployApp升级前默认不只备份.env和 Compose 管理的 PostgreSQL,也会尝试把 Compose 管理的 MinIO 数据卷打包到同一个backups/deploy-时间戳/目录;如果使用外部 S3 / 外部 MinIO,会写提示文件,要求按客户现场方案单独备份。 - 已补第二阶段产品化入口:
scripts/backup-restore.ps1支持Backup、Restore、Verify。恢复必须显式传入RESTORE_FLNXI_DATA,避免误覆盖客户数据;外部数据库和外部对象存储不会假装自动恢复。
P0-08 生产镜像入口脚本在 Linux 容器内可能找不到
客户影响:
客户按 Compose 首装后,postgres 和 minio 正常,主应用容器不断重启,日志只有 ./docker-entrypoint.sh: not found。这会让首装直接卡死。
证据:
- 本机 Docker 客户模拟环境第一次启动
flnxi-material:customer-smoke-20260530时,主应用容器反复重启,日志显示/usr/local/bin/docker-entrypoint.sh: exec: line 11: ./docker-entrypoint.sh: not found。 Dockerfile会复制scripts/docker-entrypoint.sh到 Linux 镜像。如果该脚本在 Windows 工作区里带 CRLF 换行,Linux 会把 shebang 解析失败,表现成文件“找不到”。
当前处理:
- 已修复:
Dockerfile复制启动脚本后执行sed -i 's/\r$//' ./docker-entrypoint.sh && chmod +x ./docker-entrypoint.sh。 - 已补测试:
scripts/__tests__/docker-entrypoint.test.ts锁定生产镜像必须转换启动脚本换行。
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、历史公开链接需单独只读审计。
- 已补脚本层第一步:
scripts/backup-restore.ps1默认备份 Compose 管理的 MinIO 数据卷;外部 S3 / MinIO 需要客户现场独立备份。 - 后续仍建议补自动审计脚本或 SOP。
P1-09 飞牛 fnOS 临时测试环境尚不能自动部署
证据:
- 本机 VMware 当前运行
E:\FeiNiu\fnOS.vmx。 - 当前 fnOS IP 为
192.168.116.128,5666、5667管理端口可达,22端口开放,4320不通。 vmrun getGuestIPAddress提示 VMware Tools 未运行,不能只依赖 VMware Tools 获取 IP。
影响:
飞牛系统可以作为后续 NAS 实机测试环境,但当前没有 SSH 凭据或容器终端权限,无法自动复制部署文件、执行 docker compose up -d、查看日志、跑备份恢复演练。
当前处理:
- 已确认 fnOS 可作为“人工配合的 NAS 实机环境”。
- 在拿到 SSH 或容器终端前,第一阶段自动首装验收先使用本机 Docker 隔离环境推进。
- 后续接入 fnOS 时,优先执行
scripts/smoke-install.ps1和scripts/backup-restore.ps1做同一套验收,不另写一套飞牛专用口径。
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作为当前真相。
影响:
其他项目照旧文档接入,会缺签名参数,联调失败。
当前处理:
- 已修复:
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 做只读并行排查,分别覆盖首装入口、升级回滚、文档真相。
- 本轮使用 multi-agent 和本机真实端口扫描确认 fnOS 测试环境:VM 在线,
192.168.116.128:5666/5667可达,22开放,4320当前未运行应用;由于缺少 SSH / 容器终端权限,未在 fnOS 内执行部署。 - 新增脚本结构测试覆盖
scripts/backup-restore.ps1和scripts/smoke-install.ps1的关键安全约束。 - 使用本机 Docker 隔离目录完成客户首装模拟:
APP_PORT=4321、MINIO_API_PORT=39200、MINIO_CONSOLE_PORT=39201,应用镜像flnxi-material:customer-smoke-20260530,首装验收脚本确认/api/health、迁移状态、版本文件和 MinIO bucket 均通过。 - 使用同一隔离环境完成备份、恢复和恢复后健康验收:
scripts/backup-restore.ps1 -Action Backup生成postgres.sql和minio-data.tar.gz,-Action Restore -ConfirmRestore RESTORE_FLNXI_DATA恢复成功。 - 使用临时本机许可证模拟服务完成免费版激活,并用登录后的管理员会话上传一条“材质”素材,接口返回
201 Created。 - 本轮修复后执行
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. 建议修复顺序
- 先修首装会失败的环境变量问题:主应用
DATABASE_URL、许可证必填变量、许可证服务.env.example。 - 再修升级会假成功的问题:Go Agent 健康检查、Agent 更新失败恢复、禁止直接传
:latest镜像。 - 再补回滚闭环:发布脚本 trap 顺序、数据库恢复文档、数据库迁移后的回滚口径。
- 再做 NAS/对象存储专项:目录权限、外部 S3 模式、历史 bucket/ACL 审计。
- 最后清文档入口:许可证对外接入文档、历史安全评估、商业化长清单和阶段性记录口径。