v17 → v18
v18 强制加密,部署前检查凭据、配置和 AI 指标
Core v18 自 18.0.1 起可用,18.0.0 未发布镜像。v18 已移除非加密运行和旧格式写入,生产启动必须提供显式字段密钥与至少 32 字符的 JWT。源码 dev 可自动生成本地字段密钥,见下方说明。Core v17.1.0 起提供 upgrade:check、upgrade:prepare 与 upgrade:rotate-jwt,v17.0.5 及更早版本不包含它们。v18 构建保留 check 与 rotate-jwt,回填并入发布迁移。请从 v17.1.0 升级;SDK、CLI 和主题的最低版本会在实际发布验证后补齐。
计划改变什么
| 范围 | 当前 v17 | v18 计划 |
|---|---|---|
| 凭据写入 | v17.0.4 可分阶段启用 V2,有临时明文逃生口 | v18 已生效:生产显式 hex64 字段密钥、dev 可自动生成本地密钥;JWT 至少 32 字符、固定 V2;旧关闭配置报错 |
| 普通摘要、精读 GET | 可能隐式生成 | 只读,无结果 data: null;生成显式触发 |
| 非流式执行入口 | 旧 GET 可用 | 备份、清缓存、测试邮件与速记表态用 POST;GET SSE 保留 |
| 配置与参数 | v17 保留旧配置、排序与别名兼容 | v18 已生效:发布前迁移旧配置,正常服务只认新结构,PATCH 拒绝未知声明字段;数字排序和评论别名移除仍在后续阶段 |
| AI 元数据 | 旧响应字段保留 | 统一 generationMetrics,后续才删除旧列与旧响应字段 |
| SDK | 5.x 兼容版先转换请求 | 协调 SDK 6.x;保留 legacy 入口与 CLI V2 响应适配 |
加密强制、旧配置迁移和配置严格校验已在 v18 实现;其余最终破坏仍分阶段推进。当前继续支持旧 GET 和 SDK 5.x 签名,不改变普通 AI GET 的既有行为,不删除物理列。API /api/v3、snake_case、成功/错误 envelope 和 SSE 帧协议保持。
准备顺序
- 更早的实例先按历史指南升级到 v17.1.0,保存数据库、文件、原字段密钥、原 JWT 和备份私钥。
- 取得包含工具的构建,先不要启动业务副本。独立工具读取
PG_URL/PG_CONNECTION_STRING或PG_*、字段密钥和 JWT;业务进程用 CLI/YAML 配置时,也要提供等效环境变量。先用 check 诊断现有配置。 - JWT 不足 32 字符时先执行下方维护轮换。不要直接改环境变量,否则现有 OAuth token 可能无法解密。
- 删除旧加密开关,保存原有效字段密钥与符合要求的 JWT。先停止全部旧副本和后台写入者,再使用本构建运行
node migrate.mjs(源码为pnpm -C apps/core migrate)。配置转换是必跑的发布前数据迁移,无需额外开关;失败阻止新服务启动,成功才记录20261109-canonical-config。这一步需要维护窗口,不能让旧版在迁移后继续写回旧结构。 - 同一个 migrate 还会回填 AI 指标,并必跑
20261109-required-secret-storage,完成 Snippet/Webhook 加密、API Key 哈希和 OAuth 加密。迁移成功后运行 check,再启动新业务副本,验证原 API Key、OAuth、配置消费与 AI 指标。Compose 中 app 与 mx-migrate 必须使用同一构建与秘密。
启动配置与旧开关
- 生产环境的
MX_ENCRYPT_KEY或ENCRYPT_KEY必须是显式的 64 位十六进制密钥;不再回退到机器 ID,也不再派生短密钥。只有从未加密的新实例才用openssl rand -hex 32生成首个字段密钥并持久保存。 JWT_SECRET(别名JWTSECRET)至少 32 字符;已有短 JWT 必须先按下方维护流程轮换。- 旧开关的
true值暂接受并提示删除;false或其他无效值会阻止启动,check 报告DEPRECATED_ENCRYPTION_FLAG。它们不再控制运行模式。 - 配置、Snippet 和 Webhook 新写入固定 AES-256-GCM,使用随机 96 位 nonce、128 位认证标签,并以 AAD 绑定所属字段;API Key 固定哈希,OAuth 固定加密。历史明文和默认 AES-256-ECB 仍可读取并迁移。
- 破坏性变更:删除
ENCRYPT_ALGORITHM、命令行--encrypt_algorithm和 YAMLencrypt_algorithm。保留旧项会报错,即使填旧默认值、aes-256-gcm或空环境变量也不接受;应删除配置项。环境变量检查报告REMOVED_ENCRYPTION_OPTION。 - app 与 mx-migrate 使用相同且持久保存的密钥。字段加密必填不影响 ZIP 备份和历史归档读取;备份加密仍由独立设置控制。
历史字段解密固定为旧默认 AES-256-ECB。曾使用其他旧算法的实例,必须先在旧版本中使用原密钥和原算法将相关凭据转换为 V2,并验证凭据可用后再升级。删除配置项不会转换自定义算法密文。原算法和密钥仍须随旧快照单独保管,以便在隔离的旧版本环境恢复。
源码开发环境
dev 未提供字段密钥时,首次启动自动生成随机密钥,以仅所有者可读写的 0600 权限保存到当前工作目录的 tmp/dev-encryption.key;重启和多个开发进程复用同一密钥。显式 CLI、YAML 或环境变量密钥始终优先。已有本地密钥损坏或显式密钥无效时会报错,不会自动覆盖。字段仍使用 AES-256-GCM 加密,JWT 长度要求不变。
本地密钥须和开发数据库一同保留;删除 tmp 也会删除它。生产构建不会生成或读取这个默认密钥,即使设置 NODE_ENV=development。独立升级检查仍读取环境配置;检查开发库或将它迁到生产前,须显式配置原本地密钥,不能重新生成。
源码工作区中的命令(需要 v18 代码和已完成 schema 迁移的数据库):
pnpm -C apps/core upgrade:check --json
pnpm -C apps/core migrate
pnpm -C apps/core upgrade:check --json构建产物中用 node migrate.mjs 运行正式迁移,用 node upgrade.mjs check --json 检查结果。在包含该入口的镜像中,可使用 docker compose run --rm --no-deps app node upgrade.mjs check --json 启动独立检查;数据库需已运行。该检查进程不启动 HTTP、业务定时任务或 Redis。Compose 会先校验 JWT 非空,字段密钥缺失由迁移进程报告;诊断缺失配置时使用源码命令或直接 docker run,避免先被 Compose 拦截。
check 退出码:0 为 ready,2 为 needs-action,1 为检查失败。它只读,输出计数和路径,不输出秘密。空库可识别为新装;历史实例须完成规范配置迁移并通过实际数据检查,没有单独准备标记。
所有回填都属于正式发布迁移,无需独立 prepare、dry-run 或凭据迁移开关。在 v17.1.0 上预先执行 upgrade:prepare 是可选的:已准备和未准备的实例由同一发布迁移处理,已转换的凭据不会被再次改写。配置迁移持有数据库锁,与正常配置 PATCH 串行;按原路径解密,再转换配置并按新路径加密。凭据迁移哈希旧 API Key、封装 OAuth 并加密 Snippet/Webhook,原 API Key 字符串保持有效。新的 v18 凭据迁移有独立账本 ID,历史 opt-in 凭据迁移记录不会使本次回填被跳过。
每条迁移成功后才记录完成,损坏密文、未知配置形状、错误字段密钥或 JWT 会使对应批次失败并回滚,阻断发布。先前成功的迁移保留账本,重试时自动跳过;重复执行不会重复哈希 API Key 或轮换已加密 token。迁移和检查完成后启动新副本,不清空所有 Redis 状态。
配置一次性迁移
发布迁移把 githubToken 转为 github.token,将 Dodo 凭据转为 membership.apiKey / webhookSigningKey,把独立图片生成和 TTS 配置并入 ai.providers 与功能配置,删除旧配置行。历史 SMTP 的 smtp.options 连接参数迁为 smtp.host / port / secure。凭据先按原字段路径解密,再按新路径加密,保留实际值。
普通启动、读取和 PATCH 不再转换旧结构,也不再返回独立 imageGenerationOptions / ttsOptions 分区。旧分区读写返回 CONFIG_NOT_FOUND;未知或旧字段返回 CONFIG_VALIDATION_FAILED,不会静默忽略。部分更新、空秘密保留原值,以及 record / 自由 JSON 参数仍有效。
长期运行的实例常残留已无任何 schema 声明的旧字段。发布迁移会删除这些字段,并在日志中按路径列出,不输出值;record 与自由 JSON 的动态键不受影响。需要保留这些值时,以升级前的数据库快照为准。
数据库缺少配置迁移记录时,新服务拒绝初始化配置并提示运行发布迁移。转换器只保留在发布迁移与只读检查中,供历史数据升级;旧归档仍按下方隔离恢复流程处理。
规范凭据为空时保留已有旧凭据;新旧值都非空且不同时,报告 CONFIG_SECRET_CONFLICT 与字段路径,配置迁移整批回滚。先在旧实例或隔离维护环境核对并明确解决冲突,再重跑迁移;不要尝试启动新服务后通过旧字段 PATCH 修复。错误密钥、损坏密文和不支持的配置形状同样阻塞迁移,不写完成记录。
维护窗口轮换 JWT
先停止全部业务副本、后台任务和其他写入者。将原 JWT 和至少 32 字符的新 JWT 放在两个仅操作者可读的文件中,不把秘密值放在命令行或聊天记录里。新 JWT 用随机源生成,下例产出 64 个字符;文件末尾的单个换行会被工具忽略,原 JWT 请用编辑器写入文件:
umask 077
openssl rand -hex 32 > /secure/new-jwt随后执行轮换:
pnpm -C apps/core upgrade:rotate-jwt --maintenance-confirmed --old-jwt-file /secure/old-jwt --new-jwt-file /secure/new-jwt构建产物对应 node upgrade.mjs rotate-jwt 加同样参数;容器以只读 volume 挂载这两个文件,再传容器内路径。工具只读取文件,不改写秘密文件或业务环境。
事务内用旧 JWT 解开 OAuth,再用新 JWT 重封装;保留 provider token,不联系服务商,不改字段密钥和 API Key。成功会清空登录会话,所有用户须重新登录。之后由操作者将 全部副本 配置为同一新 JWT,再启动。失败整批回滚;原 JWT 丢失时不得清空 token 伪装成功,应找回原秘密或重新授权。
历史 Better Auth 裸 hex 密文也会验证与转换。疑似该格式但验证失败的 token 会阻塞操作,需要核对原秘密或重新授权,不会当明文继续。
消费者与恢复边界
兼容 SDK 将速记表态改为 POST,并把数值排序转换成 asc / desc;已有 token 参数转为认证头。Admin 的备份、清缓存与测试邮件提前走 POST。尚未验证的客户端不能宣称兼容 v18。
回退最低版本须是经过实测的准备版,当前发布 tag 尚未确定。回退代码不会回退 schema;回到更老版本必须有旧快照和当时的秘密。
当前恢复工具没有 v18 隔离预检闭环。历史 ZIP 或 v1 加密归档应先在隔离维护环境中恢复,用归档当时的字段密钥、JWT、恢复私钥完成 migrate 和 check,再验证并安排未来 v18 切换;不要把未迁移数据直接交给 v18 业务。数据库恢复和文件恢复是两个阶段,保留恢复前快照。
配置字段类型已按新结构同步到 API Client 源码,将随 SDK 6 发布。旧 githubToken 和 smtp.options 调用须改为嵌套 GitHub 配置与平铺 SMTP 字段;这是配套大版本契约,不能将该类型删除作为 SDK 5.x patch 发布。