From 545e5c85fb6531983b9b0884bd533245ce781f38 Mon Sep 17 00:00:00 2001 From: Innei Date: Sun, 8 Nov 2026 21:18:05 +0800 Subject: [PATCH 1/7] docs: correct v17 guidance and add v18 preparation runbook --- content/docs/configure/encryption.mdx | 90 +++++--------------- content/docs/configure/environment.mdx | 18 ++-- content/docs/deploy/agent.mdx | 6 +- content/docs/deploy/docker.mdx | 24 +++--- content/docs/deploy/source.mdx | 21 +++-- content/docs/getting-started/quick-start.mdx | 5 +- content/docs/migrate/index.mdx | 5 ++ content/docs/migrate/meta.json | 4 + content/docs/migrate/v14-to-v15.mdx | 21 +++++ content/docs/migrate/v15-to-v16.mdx | 22 +++++ content/docs/migrate/v16-to-v17.mdx | 23 +++++ content/docs/migrate/v18-preparation.mdx | 68 +++++++++++++++ content/docs/use/backup-restore.mdx | 54 +++--------- content/docs/use/update.mdx | 9 +- package.json | 6 +- pnpm-lock.yaml | 3 + public/agent-skills/mix-space-expert.md | 12 ++- src/components/env-variable-config.tsx | 6 +- src/components/env-variable-format.mts | 13 +++ tests/env-variable-format.test.mjs | 25 ++++++ 20 files changed, 288 insertions(+), 147 deletions(-) create mode 100644 content/docs/migrate/v14-to-v15.mdx create mode 100644 content/docs/migrate/v15-to-v16.mdx create mode 100644 content/docs/migrate/v16-to-v17.mdx create mode 100644 content/docs/migrate/v18-preparation.mdx create mode 100644 src/components/env-variable-format.mts create mode 100644 tests/env-variable-format.test.mjs diff --git a/content/docs/configure/encryption.mdx b/content/docs/configure/encryption.mdx index c642243..a6c1e21 100644 --- a/content/docs/configure/encryption.mdx +++ b/content/docs/configure/encryption.mdx @@ -1,92 +1,48 @@ --- title: 数据加密 -description: 加密你的 Mix Space +description: v17 凭据加密配置、原密钥保留与 v18 准备 --- -Mix Space 提供了敏感配置的加密存储能力。默认为关。 +本文以已发布的 Core v17.0.4 为基线。v18 尚未发布,后续强制加密计划见 [v18 准备说明](/docs/migrate/v18-preparation)。 -为什么需要 Key 加密。 +## 三种秘密分别保管 -假设数据库被脱库。如果开启了 Key 加密,即便攻击者拿到了全部数据,也读不出被标记为加密的字段。同时,也需要谨慎开启此功能——开启后你需要记住加密密钥,否则你将永远丢失这些数据。 +| 秘密 | 用途 | 恢复时的要求 | +| --- | --- | --- | +| `MX_ENCRYPT_KEY` / `ENCRYPT_KEY` | 配置、Snippet、Webhook 的字段加密 | 保留产生这些密文的原密钥 | +| `JWT_SECRET` | 登录与 Better Auth OAuth token 加密 | 保留原值;轮换需要维护窗口 | +| 备份恢复私钥 | 打开 `.mxbackup` 归档 | 与备份分开保存;不能替代上面两种秘密 | -开启前请务必备份数据库。忘记密钥 = 这些数据永久无法恢复。 +已有实例不要为解决解密错误而生成新密钥。新密钥无法解开旧数据。先从部署配置、秘密管理器或历史快照找回原值;丢失原秘密时,需要重新授权或重新配置相应凭据。 -## 哪些字段会被加密 +## v17 如何开启 -加密范围不是全部配置项,而是由服务端 schema 上的 `encrypt` 元数据决定,**随版本变化**。 - -判定规则:服务端遍历配置 schema 时,会读每个字段的 `encrypt` 元数据;**父级被标记时,整棵子树都继承加密**。绝大多数密码类字段是通过 `field.password()` / `field.passwordHalfGrid()` 定义的,这两个 helper 会在构造 schema 时自动注入 `encrypt: true`,所以你在配置里看到的「密码」输入框,基本都属于加密范围。 - -当前版本(v14.15.2)覆盖的主要字段: - -| 区域 | 字段 | -| --- | --- | -| OAuth | 各服务商的 client secret、Apple 私钥 | -| 图床 / 对象存储 | S3、R2 的 `secretKey` | -| 邮件 | Resend API Key、SMTP 密码 | -| AI | 各 Provider 的 `apiKey` | -| Webhook | 签名密钥 | -| 搜索推送 | 百度推送 Token、Bing API key | -| 会员 | API Key | -| 其他 | 高德地图 Key、设备 Key、Personal Access Token 等 | - - -想确认当前版本的准确清单,可以看服务端 `apps/core/src/modules/configs/` 下的 `configs.schema.ts`:凡是写成 `field.password(...)` 或 `field.passwordHalfGrid(...)` 的字段都会被加密,另加显式标注 `{ encrypt: true }` 的字段。文档这里的说明可能落后于代码,以源码为准。 - - -## 如何开启 - -你可以附加 `--encrypt_enable` 来启动服务。如: +为从未加密过的新实例生成随机字段密钥,并保存到秘密管理器: ```bash -node out/main.mjs --encrypt_enable +openssl rand -hex 32 ``` - -**只要设置了加密密钥而没有显式关闭,加密就会被自动开启。** +将结果配置为 `MX_ENCRYPT_KEY`(优先)或 `ENCRYPT_KEY`,同时在 app 和 mx-migrate 的共享环境中启用 `MX_ENCRYPT_ENABLE=true`。开启时必须显式提供 **64 位十六进制**密钥;长度不足、非 hex、仅提供开启开关都会在启动时失败。不要依赖 machine-id 回退,也不要使用示例公共密钥。 -判定逻辑是`ENCRYPT_ENABLE` 未设置时,回落到「是否设置了密钥」这一项。也就是说,你只填了密钥、没管开关,加密就已经生效了。如果暂时只想把密钥填进配置、暂不加密,必须显式写 `ENCRYPT_ENABLE=false`。 - +设置密钥且未指定开关时会自动开启加密。命令行 `--encrypt_key` / `--encrypt_enable` 仍可使用;升级工具使用环境变量,不读取业务进程的 CLI/YAML 参数。 -## 指定密钥 +## V2 写入与历史数据 -可以通过 `--encrypt_key ` 指定,也可以用环境变量。环境变量有两名,`MX_ENCRYPT_KEY` 优先于 `ENCRYPT_KEY`: +`MX_SECRET_STORAGE_V2=true` 要求已开启字段加密,且 `JWT_SECRET` 至少 32 字符。它为配置、Snippet 和 Webhook 使用认证加密,为 API Key 使用哈希,为 OAuth access/refresh token 使用 Better Auth 加密。配置的密码字段由 schema 的 `encrypt` 元数据决定,数据库和 Redis 中均保留密文;空秘密 PATCH 保留旧值。 -```bash -# 推荐 -export MX_ENCRYPT_KEY=<64 位密钥> -# 等价写法 -export ENCRYPT_KEY=<64 位密钥> -``` - -密钥的校验分两层,容易搞混: - -| 情况 | 行为 | -|------|------| -| 提供的密钥**不是 64 位** | 启动即报错 | -| 提供的密钥**短于 64 位** | 不报错,服务端会用 `sha256(key + 'mx-encrypt-salt')` 派生并补齐到 64 位 | -| 提供的密钥**长于 64 位** | 启动即报错 | -| 提供的密钥**不是合法 hex 字符** | **启动时不校验**,直到真正加解密时才抛出 `Invalid key length` | +V2 字段密文绑定字段路径;不能把密文直接搬到另一个字段。旧明文和 ECB 保留读取与迁移能力。`ENCRYPT_ALGORITHM` 默认 `aes-256-ecb`,供旧格式使用;V2 固定使用 AES-256-GCM。 -推荐用 `openssl rand -hex 32` 生成,产出的正是 64 位小写 hex,一次通过全部校验: - -```bash -openssl rand -hex 32 -``` +滚动部署时,先让全部副本具备新格式读取能力,再在全部副本启用 V2,最后回填旧数据。旧 `migrate:secrets` 完成记录仅代表该次凭据回填,不代表已完成未来 v18 的配置与指标准备。新准备工具尚未发布,见 [准备流程](/docs/migrate/v18-preparation)。 -加密算法可用 `ENCRYPT_ALGORITHM` 调整,默认 `aes-256-ecb`。 +## v17.0.4 临时明文逃生口 - - 不指定密钥时,服务端默认取机器的 - [machine-id](https://www.npmjs.com/package/node-machine-id) 并派生。 - +只有关闭字段加密且 `MX_SECRET_STORAGE_V2=false` 时,普通配置写入可继续保留明文。它不适用于已有密文,也不适用于明确要求加密的授权功能。关闭开关不会解密旧数据;遇到已有密文仍须开启加密并提供原密钥。 - - 关闭加密开关**不会**把已经写入的密文还原成明文。此前加密过的字段会继续以密文形式存储和返回,密文带有 `$${mx}$$` 标识前缀。如果你打算关闭加密,需要先准备好原密钥完成解密。 - +v18 计划移除此逃生口,要求显式字段密钥、至少 32 字符的 JWT,并固定 V2 写入;当前 v17 的合法旧行为仍保留。 -## 疑难解答 +## 解密失败时 -如果出现 `Invalid key length`,说明你的密钥**不是合法的 hex 字符串**(长度问题在启动阶段就会被拦下,不会走到这里)。请用 `openssl rand -hex 32` 重新生成一个 64 位小写 hex 字符串。 +检查密钥来源、别名优先级、原 JWT 和密文所属路径。错误密钥、损坏密文或未知格式会失败,不会回落为明文成功。备份解封装成功也不表示归档内凭据能解密,详见 [备份与恢复](/docs/use/backup-restore)。 diff --git a/content/docs/configure/environment.mdx b/content/docs/configure/environment.mdx index 1d2c9af..8f106bb 100644 --- a/content/docs/configure/environment.mdx +++ b/content/docs/configure/environment.mdx @@ -17,8 +17,8 @@ description: 所有环境变量的完整说明 | 变量名 | 说明 | 默认值 | 示例 | | --- | --- | --- | --- | -| `JWT_SECRET` | JWT 签名密钥。**运行时不做长度校验**,建议 16-32 个字符。未设置时 token 会在重启后全部失效 | - | `my-secret-key` | -| `JWTSECRET` | `JWT_SECRET` 的别名,两者都设置时以 `JWT_SECRET` 为准 | - | `my-secret-key` | +| `JWT_SECRET` | 登录与 OAuth token 加密秘密。启用 V2 时至少 32 字符;已有实例保留原值,短 JWT 用维护轮换处理 | - | `原实例秘密或新生成的随机值` | +| `JWTSECRET` | `JWT_SECRET` 的别名,两者都设置时以 `JWT_SECRET` 为准 | - | `原实例秘密或新生成的随机值` | | `JWT_EXPIRE` | 登录令牌有效期(天) | `14` | `30` | | `ALLOWED_ORIGINS` | 允许的跨域域名。**自建站必须显式设置**,否则你的域名不在白名单内 | 内置默认白名单(`innei.ren`、`localhost:*`、`*.dev`、`*.vercel.app` 等) | `example.com,www.example.com` | | `SNOWFLAKE_WORKER_ID` | 工作节点 ID,取值 `0-1023` 的整数。**生产环境不设置会直接启动失败**,单实例填 `1` | - | `1` | @@ -45,9 +45,9 @@ cluster 模式下可安全使用多实例:服务端会基于 `NODE_APP_INSTANC | 变量名 | 说明 | 默认值 | 示例 | | --- | --- | --- | --- | -| `REDIS_URL` | 完整连接串,**优先级最高**,会覆盖下面三项。支持 `redis://` / `rediss://`(`rediss` 自动启用 TLS),path 里的 `/0` 表示 db 序号 | - | `redis://:pass@host:6379/0` | -| `REDIS_CONNECTION` | `REDIS_URL` 的别名 | - | - | -| `REDIS_CONNECTION_STRING` | `REDIS_URL` 的别名,优先级最高 | - | - | +| `REDIS_URL` | 完整连接串,优先级低于 `REDIS_CONNECTION_STRING`、`REDIS_CONNECTION`。支持 `redis://` / `rediss://`(`rediss` 自动启用 TLS),path 里的 `/0` 表示 db 序号 | - | `redis://:pass@host:6379/0` | +| `REDIS_CONNECTION` | 完整连接串,优先于 `REDIS_URL` | - | - | +| `REDIS_CONNECTION_STRING` | 完整连接串,优先级最高 | - | - | | `REDIS_HOST` | 地址 | `localhost` | `redis` | | `REDIS_PORT` | 端口 | `6379` | `6379` | | `REDIS_PASSWORD` | 密码 | - | `secret` | @@ -57,16 +57,20 @@ cluster 模式下可安全使用多实例:服务端会基于 `NODE_APP_INSTANC | 变量名 | 说明 | 默认值 | 示例 | | --- | --- | --- | --- | | `ENCRYPT_ENABLE` | 启用加密 | `false`(⚠️ 只要设置了密钥就等价于 `true`)| `true` | -| `ENCRYPT_KEY` | 加密密钥,必须正好 64 位 | 自动取 machine-id 派生 | `openssl rand -hex 32` | +| `ENCRYPT_KEY` | 开启时必须显式提供 64 位十六进制字段密钥 | 无可用默认密钥 | `openssl rand -hex 32` | | `MX_ENCRYPT_KEY` | `ENCRYPT_KEY` 的别名,**优先于** `ENCRYPT_KEY` | 同上 | - | | `MX_ENCRYPT_ENABLE` | `ENCRYPT_ENABLE` 的别名,**优先于** `ENCRYPT_ENABLE` | 同上 | - | -| `ENCRYPT_ALGORITHM` | 加密算法 | `aes-256-ecb` | `aes-256-cbc` | +| `ENCRYPT_ALGORITHM` | 旧格式算法;V2 固定 AES-256-GCM | `aes-256-ecb` | `aes-256-ecb` | +| `MX_SECRET_STORAGE_V2` | V2 凭据写入;要求启用加密、JWT 至少 32 字符,全部副本兼容后才启用 | `false` | `true` | +| `MX_MIGRATE_SECRETS` | 旧凭据回填的一次性迁移开关,不放入常驻 app 环境;不等于 v18 准备完成 | 未开启 | `1` | | `ADMIN_UPDATE_S3_BASE_URL` | 后台更新包(`latest.json` + `admin-.zip`)的下载源 | `https://admin-r2.innei.dev` | - | `ENCRYPT_ENABLE` 的判定逻辑是「未设置时,回落到是否设置了密钥」。**只填密钥、不管开关,加密就已经开启。** 想暂不加密必须显式写 `ENCRYPT_ENABLE=false`。详见 [数据加密](/docs/configure/encryption)。 +当前 v17.0.4 保留关闭加密的临时明文逃生口,不能读取已有密文或绕过明确要求加密的功能。v18 的强制配置与新准备工具尚未发布,见 [v18 准备说明](/docs/migrate/v18-preparation)。 + ## 缓存与限流 | 变量名 | 说明 | 默认值 | 示例 | diff --git a/content/docs/deploy/agent.mdx b/content/docs/deploy/agent.mdx index bfe4931..c591926 100644 --- a/content/docs/deploy/agent.mdx +++ b/content/docs/deploy/agent.mdx @@ -156,9 +156,9 @@ Agent 可以帮你执行命令行操作和调用 API,但涉及敏感信息( 你是 Mix Space 部署专家。请 SSH 到我的服务器(IP: ___,用户: root), 帮我完成以下操作: 1. 安装 Docker 和 Docker Compose -2. 下载 Mix Space 的 docker-compose.yml -3. 设置 JWT_SECRET 为随机字符串,ALLOWED_ORIGINS 为我的域名 -4. 启动所有服务 +2. 固定同一个已发布版本的 docker-compose.yml 和 app/mx-migrate 镜像,先阅读大版本迁移说明 +3. 新装生成并保存至少 32 字符的 JWT;启用加密时保存 hex64 字段密钥,ALLOWED_ORIGINS 为我的域名;已有实例保留原秘密,短 JWT 需要维护轮换 +4. 为 app/mx-migrate 配置相同环境,发布前迁移成功后再启动业务副本;v18 准备工具尚未发布,不能在 v17.0.4 上调用 5. 配置 Nginx 反向代理,将 ___ 域名指向 localhost:2333 6. 用 Certbot 申请 SSL 证书 7. 验证 https://___ 可以正常访问 diff --git a/content/docs/deploy/docker.mdx b/content/docs/deploy/docker.mdx index f3b50b1..dda1c54 100644 --- a/content/docs/deploy/docker.mdx +++ b/content/docs/deploy/docker.mdx @@ -36,17 +36,19 @@ docker-compose -v ## 拉取编排文件 [step] -自 v12 起 Core 已从 MongoDB 迁移到 PostgreSQL,编排文件中不再有 mongo 服务。当前版本(v14)的 `docker-compose.yml` 由 `app`、`mx-migrate`、`postgres`、`redis` 四个服务组成。 +自 v12 起 Core 已从 MongoDB 迁移到 PostgreSQL,编排文件中不再有 mongo 服务。当前 v17 的 `docker-compose.yml` 由 `app`、`mx-migrate`、`postgres`、`redis` 四个服务组成。 ```bash cd && mkdir -p mx-space/core && cd $_ # 拉取 docker-compose.yml 文件(全球) -wget https://fastly.jsdelivr.net/gh/mx-space/core@master/docker-compose.yml +wget https://fastly.jsdelivr.net/gh/mx-space/core@v17.0.4/docker-compose.yml # 拉取 docker-compose.yml 文件(Gh-Proxy 全球) -wget "https://gh-proxy.org/https://github.com/mx-space/core/blob/master/docker-compose.yml" +wget "https://gh-proxy.org/https://github.com/mx-space/core/blob/v17.0.4/docker-compose.yml" ``` +本文固定已发布的 v17.0.4 编排。下载后把 app 和 mx-migrate 两处镜像都固定为 `innei/mx-server:17.0.4`,不要让一方使用 latest。未来准备工具尚未包含在该镜像,见 [v18 准备说明](/docs/migrate/v18-preparation)。 + ## 配置环境变量 [step] 我们需要正确配置环境变量,打开获取到的`docker-compose.yml`文件。 @@ -71,15 +73,15 @@ x-mx-env: &mx-env ``` -`SNOWFLAKE_WORKER_ID` 是必填项(`0-1023` 的整数),生产环境缺失会导致 mx-core 直接启动失败。官方文件里的默认值是 `'1'`。另外 `mx-migrate` 也会引用同一个 `*mx-env` 块并跑完整的 Nest 上下文,所以这里改了迁移步骤也会同步生效——**不要只给 `app` 服务单独加变量**。 +`SNOWFLAKE_WORKER_ID` 是必填项(`0-1023` 的整数),生产环境缺失会导致 mx-core 直接启动失败。官方文件里的默认值是 `'1'`。另外 `mx-migrate` 也会引用同一个 `*mx-env` 块执行独立发布前迁移,所以这里改了迁移步骤也会同步生效——**不要只给 `app` 服务单独加变量**。 ### 配置生成 -在下方的组件中填入配置,点击复制后粘贴到 `docker-compose.yml` 的 `x-mx-env` 块中,覆盖 `JWT_SECRET` 和 `ALLOWED_ORIGINS` 的值,如果需要开启加密功能,请添加并将 `ENCRYPT_ENABLE` 的值改为 `true`,并填入 `ENCRYPT_KEY`。 +在下方的组件中填入配置,点击复制后粘贴到 `docker-compose.yml` 的 `x-mx-env` 块中,覆盖 `JWT_SECRET` 和 `ALLOWED_ORIGINS` 的值,如果需要开启加密功能,请添加并将 `ENCRYPT_ENABLE` 的值改为 `true`,并填入原 `ENCRYPT_KEY`;从未加密的新实例才用 `openssl rand -hex 32` 生成并持久保存。复制内容是 YAML 映射,所有值按字符串引用,美元符号按 Compose 规则转义。 -注意:只要设置了 `ENCRYPT_KEY`(或 `MX_ENCRYPT_KEY`)而没有显式设 `ENCRYPT_ENABLE=false`,加密就会被**隐式开启**。如果暂时只想把密钥填进去而暂不加密,必须显式写 `ENCRYPT_ENABLE: false`。 +注意:只要设置了 `ENCRYPT_KEY`(或 `MX_ENCRYPT_KEY`)而没有显式设 `ENCRYPT_ENABLE=false`,加密就会被**隐式开启**。如果暂时只想把密钥填进去而暂不加密,必须显式写 `ENCRYPT_ENABLE: 'false'`。 @@ -112,7 +114,7 @@ x-mx-env: &mx-env type={{ 'JWT 密钥': { description: - '用于签发用户 JWT,务必保存好不要泄露。运行时不做长度校验,但建议使用 16-32 个字符。未设置时 token 会在服务重启后全部失效。', + '用于签发用户 JWT,务必保存好不要泄露。V2 要求至少 32 字符;已有 JWT 不要直接更换,短值需要维护轮换。', type: 'string', }, '被允许的域名': { @@ -127,12 +129,14 @@ x-mx-env: &mx-env }, '加密密钥': { description: - '开启加密后必填,必须正好 64 位,否则启动即报错。推荐用 openssl rand -hex 32 生成(产出 64 位小写 hex)。未提供时服务端会用 machine-id 派生。此操作不可逆,请谨慎。', + '开启加密后必填,必须正好 64 位,否则启动即报错。推荐用 openssl rand -hex 32 生成(产出 64 位小写 hex)。必须显式配置,不依赖 machine-id。已有实例保留原密钥,新实例随机生成并单独保存。', type: 'string', }, }} /> +开启 V2 前确认全部副本支持新格式,JWT 至少 32 字符,再在共享块设置 `MX_SECRET_STORAGE_V2: 'true'`。历史实例还需要凭据回填,不能只切开关。 + ### 启动服务 ```bash diff --git a/content/docs/deploy/source.mdx b/content/docs/deploy/source.mdx index acec278..b8f0e30 100644 --- a/content/docs/deploy/source.mdx +++ b/content/docs/deploy/source.mdx @@ -11,7 +11,7 @@ import { Step, Steps } from 'fumadocs-ui/components/steps'; ## 要求 -- [Node.js](https://nodejs.org/zh-cn/) 22.12+(v14.7.0 起源码部署的最低要求) +- [Node.js](https://nodejs.org/zh-cn/) 26+(v15 起要求;Node.js 22、24 不再支持) - [PostgreSQL](https://www.postgresql.org/download/) 16+ - [Redis](https://redis.io/download) - [Git](https://git-scm.com/downloads)、[PNPM](https://pnpm.io/installation)、[PM2](https://pm2.keymetrics.io/docs/usage/quick-start/) @@ -22,8 +22,10 @@ import { Step, Steps } from 'fumadocs-ui/components/steps'; ### 克隆并安装 ```bash -git clone https://github.com/mx-space/core.git --depth=1 +git clone https://github.com/mx-space/core.git --branch v17.0.4 --depth=1 cd core +npm install -g corepack +corepack enable pnpm i ``` @@ -33,7 +35,6 @@ pnpm i ```bash pnpm build -pnpm bundle ``` @@ -71,8 +72,8 @@ module.exports = { NODE_PATH: nodePath, // 必填:允许跨域的域名,多个用逗号分隔 ALLOWED_ORIGINS: 'your-domain.com', - // 必填:JWT 密钥。未设置时 token 会在重启后失效(建议 16-32 个字符,运行时不做长度校验) - JWT_SECRET: 'your-jwt-secret', + // 必填:JWT 密钥。未设置时 token 会在重启后失效(V2 要求至少 32 字符;已有实例保留原值) + JWT_SECRET: 'REPLACE_WITH_YOUR_SAVED_INSTANCE_SECRET', // 必填:Snowflake worker ID,整数 0-1023。生产环境不设置会直接启动失败。 // cluster 模式可安全使用多实例:core 会基于 NODE_APP_INSTANCE 为每个实例分配不重叠的 ID SNOWFLAKE_WORKER_ID: '1', @@ -87,7 +88,15 @@ module.exports = { -### 启动 +### 先迁移,再启动 + +先为独立迁移进程配置同样的数据库、Redis、字段密钥、JWT 等环境变量(例如 `apps/core/.env`),不要只放在 PM2 的 env 对象里。已有秘密保留,新装随机生成并持久保存。仓库根运行: + +```bash +pnpm -C apps/core migrate +``` + +确认迁移退出成功,再进入 `apps/core`: ```bash pm2 start ecosystem.config.cjs diff --git a/content/docs/getting-started/quick-start.mdx b/content/docs/getting-started/quick-start.mdx index 8aca279..6515e9f 100644 --- a/content/docs/getting-started/quick-start.mdx +++ b/content/docs/getting-started/quick-start.mdx @@ -5,7 +5,7 @@ title: 5 分钟快速体验 import { Callout } from 'fumadocs-ui/components/callout' import { Step, Steps } from 'fumadocs-ui/components/steps' -本指南的目标是在 **5 分钟** 内让你在本地运行起一个完整的 Mix Space,**无需配置域名,无需填写环境变量**(全部使用默认值)。 +本指南的目标是在 **5 分钟** 内让你在本地运行起一个完整的 Mix Space,无需公网域名。本文以 v17.0.4 为本地体验基线;公开部署前需要保存自己的 JWT 与字段密钥,见 [Docker 部署](/docs/deploy/docker)。v18 尚未发布,未来会要求显式加密配置,见 [准备说明](/docs/migrate/v18-preparation)。 @@ -22,8 +22,9 @@ import { Step, Steps } from 'fumadocs-ui/components/steps' 执行以下命令克隆后端仓库并启动服务: ```bash - git clone https://github.com/mx-space/core.git --depth=1 + git clone https://github.com/mx-space/core.git --branch v17.0.4 --depth=1 cd core + # 将 compose 中 app / mx-migrate 两处镜像固定为 innei/mx-server:17.0.4 docker compose up -d ``` diff --git a/content/docs/migrate/index.mdx b/content/docs/migrate/index.mdx index f31f256..3a592d8 100644 --- a/content/docs/migrate/index.mdx +++ b/content/docs/migrate/index.mdx @@ -8,6 +8,11 @@ import { Card, Cards } from 'fumadocs-ui/components/card' import { ArrowUp, Globe } from 'lucide-react' + 准备工具、凭据转换和发布边界 + Agent 历史与 WebSocket 协议 + BCP 47 语言标签 + Node.js 26 运行时 + } href={'/docs/migrate/v13-to-v14'} diff --git a/content/docs/migrate/meta.json b/content/docs/migrate/meta.json index ba75587..dd7e129 100644 --- a/content/docs/migrate/meta.json +++ b/content/docs/migrate/meta.json @@ -4,6 +4,10 @@ "root": true, "pages": [ "index", + "v18-preparation", + "v16-to-v17", + "v15-to-v16", + "v14-to-v15", "v13-to-v14", "v12-to-v13", "v11-to-v12", diff --git a/content/docs/migrate/v14-to-v15.mdx b/content/docs/migrate/v14-to-v15.mdx new file mode 100644 index 0000000..02a0590 --- /dev/null +++ b/content/docs/migrate/v14-to-v15.mdx @@ -0,0 +1,21 @@ +--- +title: v14 → v15 +description: Node.js 26 与发布包运行时要求 +--- + +v15 将 Core、构建工具和发布包的最低运行时统一为 Node.js 26。此变更已经发布,不属于 v18 的新增破坏。 + +## 升级前 + +- 保存数据库、文件与原秘密的快照。 +- 官方 Docker 镜像已经包含 Node.js 26,主机无需另装 Node。 +- 源码、自定义镜像、Railway/nixpacks 及运行 CLI 的主机必须先升级到 Node.js 26;Node.js 22、24 不再受支持。 +- Node.js 26 不再内置 Corepack,需自行安装,再按仓库 `packageManager` 选择 pnpm。`@mx-space/api-client`、CLI、MongoDB 迁移 CLI 和 Webhook 包也声明 Node.js 26 要求。 + +固定目标版本的镜像和编排文件,运行发布前 `mx-migrate`,成功后启动 app。源码部署见 [部署步骤](/docs/deploy/source)。 + +## 验证与回退 + +确认 `node --version`、迁移退出状态、`/api/v3/ping` 和后台登录。需要回退时使用原版本代码和兼容的数据库;涉及数据恢复时同时恢复原文件和秘密,不能仅替换镜像。 + +[官方 v15.0.0 发布说明](https://github.com/mx-space/core/releases/tag/v15.0.0) diff --git a/content/docs/migrate/v15-to-v16.mdx b/content/docs/migrate/v15-to-v16.mdx new file mode 100644 index 0000000..41225b9 --- /dev/null +++ b/content/docs/migrate/v15-to-v16.mdx @@ -0,0 +1,22 @@ +--- +title: v15 → v16 +description: 语言代码切换为 BCP 47 标签 +--- + +v16 的存储、AI 设置与 API 输出统一使用 `zh-CN`、`zh-TW`、`en-US`、`ja-JP` 等 BCP 47 标签。繁体中文可以独立作为翻译目标。 + +## 受影响的调用 + +旧的 `zh`、`en`、`ja` 应迁为相应语言标签;`jp`、`cn`、`english` 等别名不再接受。`tw` 表示阿坎语,不能代替 `zh-TW`。客户端比较 `sourceLang`、`availableTranslations` 和 `targetLang` 时,应按语言与文字系统匹配,避免只比较旧的短代码。 + +`@mx-space/ai` 1.x 移除了 `normalizeLanguageCode`、`normalizeTargetLang`,改用 `canonicalizeLanguageTag`、`assertLanguageTag`、`matchLanguage`。Core 与适配后的 Yohaku 网页、移动端一起部署;其他主题先确认支持这些标签。 + +## 迁移与验证 + +备份后让发布前迁移 `0047_language_tag_bcp47` 完成,再切换业务副本。同一文章的新旧标签冲突时保留最近写入的记录;无法映射的值保持原样,并输出 `RAISE NOTICE`,需要检查处理。 + +验证翻译目标、语言列表、摘要、精读、搜索和朗读。`en-GB` 可匹配 `en-US`,`zh-HK` 可匹配 `zh-TW`。没有繁体摘要时不再用简体摘要替代。 + +迁移没有 down;只回退代码不会恢复旧标签或被合并的行。需要完整回退数据时使用升级前快照和原秘密。 + +[官方 v16.0.0 发布说明](https://github.com/mx-space/core/releases/tag/v16.0.0) diff --git a/content/docs/migrate/v16-to-v17.mdx b/content/docs/migrate/v16-to-v17.mdx new file mode 100644 index 0000000..b61eec0 --- /dev/null +++ b/content/docs/migrate/v16-to-v17.mdx @@ -0,0 +1,23 @@ +--- +title: v16 → v17 +description: 服务端 Agent 会话与新 WebSocket 协议 +--- + +v17 将编辑器 AI Agent 的完整 transcript 放到服务端,后台与服务端改用新的 WebSocket 消息形状。 + + +发布迁移会重建 Agent 会话表,清空升级前的 Agent 历史对话。旧对话无法带入新协议。此项属于 v17,不能在 v18 升级说明中再次当作新破坏。 + + +## 操作顺序 + +1. 备份数据库、文件与原秘密,确认可以接受旧 Agent 对话清空。 +2. 固定 v17 的镜像与匹配编排,让发布前 `mx-migrate` 完成。 +3. 一起部署 Core 和同版本内置 Admin,刷新原先打开的后台标签页;独立旧 Admin 不兼容新 Agent 协议。 +4. 验证登录、新建 Agent 对话、重新打开历史、工具调用和修改建议恢复。 + +文章、评论等业务内容不属于该会话表清空范围。回退到旧 Agent 协议需要升级前完整快照;只回退镜像不能找回已清空的对话。 + +v17.0.4 修复了关闭加密时普通配置写入失败的兼容问题;这是临时逃生口,见 [数据加密](/docs/configure/encryption)。下一阶段见 [v18 准备](/docs/migrate/v18-preparation)。 + +[官方 v17.0.0 发布说明](https://github.com/mx-space/core/releases/tag/v17.0.0) diff --git a/content/docs/migrate/v18-preparation.mdx b/content/docs/migrate/v18-preparation.mdx new file mode 100644 index 0000000..926cd21 --- /dev/null +++ b/content/docs/migrate/v18-preparation.mdx @@ -0,0 +1,68 @@ +--- +title: v18 准备(未发布) +description: 先部署兼容准备版,再检查凭据、配置和 AI 指标 +--- + + +v18 尚未发布。本文的 `upgrade:*` 是正在开发的 v17 准备版工具,已发布的 v17.0.4 不包含它们。最低准备版 tag、SDK、CLI 和主题版本会在实际发布验证后补齐;不要直接在现有镜像上运行不存在的命令。 + + +## 计划改变什么 + +| 范围 | 当前 v17 | v18 计划 | +| --- | --- | --- | +| 凭据写入 | 可分阶段启用 V2,有临时明文逃生口 | 显式 hex64 字段密钥、JWT 至少 32 字符、固定 V2;显式关闭报错 | +| 普通摘要、精读 GET | 可能隐式生成 | 只读,无结果 `data: null`;生成显式触发 | +| 非流式执行入口 | 旧 GET 可用 | 备份、清缓存、测试邮件与速记表态用 POST;GET SSE 保留 | +| 配置与参数 | 保留旧配置、排序与别名兼容 | 拒绝未知声明字段、数字排序;移除评论列表 `state` 查询别名,更新 body 的 state 仍可用 | +| AI 元数据 | 旧响应字段保留 | 统一 generationMetrics,后续才删除旧列与旧响应字段 | +| SDK | 5.x 兼容版先转换请求 | 协调 SDK 6.x;保留 legacy 入口与 CLI V2 响应适配 | + +准备版继续支持旧 GET 和 SDK 5.x 签名,不改变普通 AI GET 的既有行为,不删除物理列。API `/api/v3`、snake_case、成功/错误 envelope 和 SSE 帧协议保持。 + +## 准备顺序 + +1. 更早的实例先按历史指南升级到兼容的 v17,保存数据库、文件、原字段密钥、原 JWT 和备份私钥。 +2. 部署包含准备工具的版本到全部副本。业务进程用 CLI/YAML 配置时,也要为独立工具显式提供等效环境变量;工具读取 `PG_URL` / `PG_CONNECTION_STRING` 或 `PG_*`、字段密钥、JWT 和 V2 开关。 +3. JWT 不足 32 字符时先执行下方维护轮换。不要直接改环境变量,否则现有 OAuth token 可能无法解密。 +4. 全部副本启用字段加密和 `MX_SECRET_STORAGE_V2=true`,确认旧副本及后台任务已经停止旧格式写入,再执行准备。 +5. 检查、dry-run、正式 prepare,完成后重启业务副本,验证原 API Key、OAuth、配置消费与 AI 指标。 + +源码工作区中的命令(需要准备版代码和已完成 schema 迁移的数据库): + +```bash +pnpm -C apps/core upgrade:check --json +pnpm -C apps/core upgrade:prepare --all-replicas-v2 --dry-run +pnpm -C apps/core upgrade:prepare --all-replicas-v2 +pnpm -C apps/core upgrade:check --json +``` + +构建产物中使用 `node upgrade.mjs check --json`、`node upgrade.mjs prepare --all-replicas-v2 --dry-run` 和正式 prepare。在包含该入口的镜像中,可使用 `docker compose run --rm --no-deps app node upgrade.mjs check --json` 启动独立检查;数据库需已运行。该进程不启动 HTTP、业务定时任务或 Redis。 + +`check` 退出码:`0` 为 ready,`2` 为 needs-action,`1` 为检查失败。它只读,输出计数和路径,不输出秘密。空库可识别为新装,不要求历史准备标记;历史实例须完成实际转换。 + +`--all-replicas-v2` 是操作者对全部副本状态的明确确认,工具不能自动检查集群。prepare 持有数据库锁,与正常配置 PATCH 串行;按原路径解密,再转换配置、重新加密、哈希旧 API Key、封装 OAuth 并补齐指标。原 API Key 字符串保持有效。dry-run 在事务内演练后回滚,`would-be-ready` 不代表数据库已经准备完成。 + +只有全部残留检查通过才记录准备标记。损坏密文、未知配置形状、错误字段密钥或 JWT 都会失败并回滚。准备完成后重启副本淘汰旧配置缓存,不清空所有 Redis 状态。旧 `migrate:secrets` 账本不能替代此检查。 + +## 维护窗口轮换 JWT + +先停止全部业务副本、后台任务和其他写入者。将原 JWT 和至少 32 字符的新 JWT 放在两个仅操作者可读的文件中,不把秘密值放在命令行或聊天记录里: + +```bash +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、恢复私钥完成 check / prepare,再验证并安排未来 v18 切换;不要把未准备数据直接交给 v18 业务。数据库恢复和文件恢复是两个阶段,保留恢复前快照。 diff --git a/content/docs/use/backup-restore.mdx b/content/docs/use/backup-restore.mdx index e25126a..6fd408c 100644 --- a/content/docs/use/backup-restore.mdx +++ b/content/docs/use/backup-restore.mdx @@ -1,64 +1,38 @@ --- title: 备份与恢复 -description: 保护你的 Mix Space 数据,学会备份与回滚 +description: ZIP、加密归档、原秘密与恢复边界 --- import { Callout } from 'fumadocs-ui/components/callout' -## 手动备份 +## 创建与保存 -在后台后台的「备份」页面中点击「立即备份」,系统会将数据打包为 zip 文件,保存到服务器的绝对目录: +后台「备份」页可以立即备份并下载。未启用归档加密时生成 ZIP;在「设置 → 存储 → 备份」启用备份加密后,新归档为 `.mxbackup`。定时备份、S3 上传和恢复前安全快照沿用该设置。字段加密与归档加密是两项独立设置,未来字段加密必填不等于所有备份必须加密。 -``` -~/.mx-space/backup/YYYY-MM-DD_HH:mm:ss/backup-YYYY-MM-DD_HH:mm:ss.zip -``` +启用时浏览器生成公私钥对,只把公钥提交给服务端;私有恢复密钥只展示一次,务必复制或下载后单独保管。需要 HTTPS 或 localhost 的 Web Crypto 环境。服务器配置中的公钥不能用来恢复,旧归档仍需要创建时对应的私钥。 - -数据目录固定为 `~/.mx-space`(开发模式下为项目下的 `./tmp`),不随部署路径变化。目录名与文件名的时间戳格式都是 `YYYY-MM-DD_HH:mm:ss`——注意其中**包含冒号**,这是 Linux/macOS 允许、Windows 不允许的。 + +备份私钥、字段加密密钥与 JWT 分别保存。归档打开后,其中的加密凭据仍需要当时的原字段密钥和 JWT。不要把恢复私钥与备份一起放在数据目录。 -建议定期手动下载备份包到本地,以防服务器故障导致数据丢失。 - -## 自动备份 - -目前该功能仅支持备份到支持 S3 协议的云服务,暂不支持其他类型的云服务。当然,你也可以通过设置计划任务等方法达到和此功能一样的效果。 - -设置入口:后台「设置 → 存储 → 备份」,填入云服务所提供的对应参数即可。 - ## 恢复 - -该操作涉及修改数据库,请多备份几个工作日的 backup,数据无价请谨慎操作! - -一般情况下回滚只会导致 analyze 数据页丢失 IP & PV 的数据。 - - -服务端提供了两条现成的恢复路径,**都不需要手工重命名备份包**: +上传已有 ZIP / `.mxbackup`,或选择服务器上的备份回滚,都无需手工改名。加密归档在恢复对话框粘贴私钥或导入 PEM 文件;它只随本次认证请求发送,不保存到服务器设置。旧 ZIP 不需要归档密钥,早期 v1 对称加密归档输入原 64 位十六进制密钥。 -1. **上传备份包恢复** —— 在后台后台的「备份」页面中上传你手上的 zip 备份包即可,服务端会先把它落到临时目录,再直接执行恢复。 -2. **按目录名回滚** —— 如果你想回到某次已有备份,直接指定该备份的目录名即可。 +服务端先验证整个加密归档,再解包和恢复数据库。缺少/错误私钥、损坏归档会失败;不要重新生成密钥冒充恢复。恢复需要 `pg_dump`、`pg_restore`、`psql`,官方镜像包含客户端工具。 -恢复操作开始前,服务端会**自动先做一次新备份**,所以回滚本身也是可逆的。回滚会导致 analyze 数据页丢失 IP 与 PV 数据,其余内容(文章、评论等)不受影响。操作前仍建议多备份几个工作日的包。 +恢复会覆盖数据库业务内容和数据文件。操作前服务端尝试创建安全快照,仍应额外保存可恢复的外部副本。会话、日志、搜索索引等运行时表按当前结构重建为空表。数据库替换在单个事务内,文件随后复制,二者不是同一个原子事务;文件复制失败时保留快照处理,不应报告整次恢复成功。 -替换完成后会提示「数据库有变动,将在 x 秒后重载页面」,重载后请先检查文章、评论等有没有丢失。 - -## 命令行备份(v12+) +恢复后核对文章、评论、登录、凭据消费及文件。跨版本恢复须遵循对应迁移指南。未来 v18 的历史恢复应走「隔离准备版恢复 → check / prepare → 验证 → v18」路径,见 [v18 准备说明](/docs/migrate/v18-preparation)。当前没有自动隔离预检,不要恢复后直接开放未来 v18 业务。 -自 v12 起,Mix Space 使用 PostgreSQL 作为数据库。服务端自身的备份用的是 `pg_dump --format=custom`(custom 格式),你手工备份时建议保持一致: +## 手工数据库快照 -```bash -# 备份(custom 格式,dump 落在 mx-space/pg.dump) -pg_dump -Fc postgresql://mx:mx@localhost:5432/mx_core > pg.dump -``` - -恢复时用 `pg_restore`,并加上服务端同样使用的清理参数: +服务端使用 PostgreSQL custom 格式。手工备份可以使用: ```bash -pg_restore --clean --if-exists -d postgresql://mx:mx@localhost:5432/mx_core pg.dump +pg_dump -Fc "$PG_URL" > pg.dump ``` - -`--clean --if-exists` 会在恢复前删除已存在的同名对象。如果你要的是纯 SQL 文本备份,用 `pg_dump` 不加 `-Fc`,恢复时改用 `psql < backup.sql`。 - +该文件仅是数据库快照,不包含数据目录、秘密管理器或恢复私钥。请在隔离数据库中演练恢复;不要将生产库作为首次验证目标。 diff --git a/content/docs/use/update.mdx b/content/docs/use/update.mdx index 2d8d9a8..86e0211 100644 --- a/content/docs/use/update.mdx +++ b/content/docs/use/update.mdx @@ -28,10 +28,10 @@ docker compose pull && docker compose up -d ```bash pnpm i pnpm build -pnpm bundle +pnpm -C apps/core migrate ``` -随后进入 `./apps/core`,执行以下命令启动服务: +先确认迁移成功,并为迁移进程提供与 app 相同的数据库、字段密钥、JWT 等环境变量。随后进入 `./apps/core` 启动服务: ```bash pnpm prod @@ -50,6 +50,11 @@ pm2 start ecosystem.config.cjs + 准备工具、凭据转换和发布边界 + Agent 历史与 WebSocket 协议 + BCP 47 语言标签 + Node.js 26 运行时 + { - const lines = variableNames.map((v, i) => - format === 'yaml' ? `- ${v.key}=${values[i]}` : `${v.key}=${values[i]}`, - ); - navigator.clipboard.writeText(lines.join('\n')).then(() => { + navigator.clipboard.writeText(formatEnvVariables(variableNames, values, format)).then(() => { setCopied(true); setTimeout(() => setCopied(false), 2000); }); diff --git a/src/components/env-variable-format.mts b/src/components/env-variable-format.mts new file mode 100644 index 0000000..8a81cca --- /dev/null +++ b/src/components/env-variable-format.mts @@ -0,0 +1,13 @@ +/** Compose environment mappings contain strings; dollar escaping prevents interpolation. */ +export function formatEnvVariables( + variables: ReadonlyArray<{ key: string }>, + values: readonly string[], + format: 'yaml' | 'env', +): string { + return variables.map((variable, index) => { + const value = values[index] ?? ''; + return format === 'yaml' + ? `${variable.key}: ${JSON.stringify(value.replaceAll('$', () => '$$'))}` + : `${variable.key}=${value}`; + }).join('\n'); +} diff --git a/tests/env-variable-format.test.mjs b/tests/env-variable-format.test.mjs new file mode 100644 index 0000000..cd4e67f --- /dev/null +++ b/tests/env-variable-format.test.mjs @@ -0,0 +1,25 @@ +import assert from 'node:assert/strict'; +import { execFileSync, spawnSync } from 'node:child_process'; +import test from 'node:test'; +import { parse } from 'yaml'; +import { formatEnvVariables } from '../src/components/env-variable-format.mts'; + +test('YAML output is a string-valued mapping with quoted special values', () => { + const values = ['false', '00123', 'a: b # value', 'quote" slash\\\nnext line']; + const variables = values.map((_, index) => ({ key: `KEY_${index}` })); + const result = parse(formatEnvVariables(variables, values, 'yaml')); + assert.equal(Array.isArray(result), false); + values.forEach((value, index) => assert.equal(result[`KEY_${index}`], value)); +}); + +test('Compose preserves literal dollar signs in copied secrets', { skip: spawnSync('docker', ['image', 'inspect', 'alpine:latest']).status !== 0 }, () => { + const environment = formatEnvVariables([{ key: 'JWT_SECRET' }], ['literal$secret${UNSET}'], 'yaml'); + const compose = `services:\n app:\n image: alpine:latest\n pull_policy: never\n network_mode: none\n environment:\n${environment.split('\n').map((line) => ` ${line}`).join('\n')}\n`; + const result = execFileSync('docker', ['compose', '-f', '-', 'run', '--rm', '--no-deps', 'app', 'printenv', 'JWT_SECRET'], { input: compose, encoding: 'utf8', env: { PATH: process.env.PATH, HOME: process.env.HOME, COMPOSE_PROJECT_NAME: `mx-docs-test-${process.pid}` } }); + assert.equal(result.trim(), 'literal$secret${UNSET}'); +}); + +test('existing env output and caller values remain unchanged', () => { + const values = Object.freeze(['test-value']); + assert.equal(formatEnvVariables([{ key: 'KEY' }], values, 'env'), 'KEY=test-value'); +}); From 0719e7965966c9346a9a802a6d7a2f9829c8b1e3 Mon Sep 17 00:00:00 2001 From: Innei Date: Sun, 8 Nov 2026 21:36:52 +0800 Subject: [PATCH 2/7] docs: explain conflicting credential resolution --- content/docs/migrate/v18-preparation.mdx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/content/docs/migrate/v18-preparation.mdx b/content/docs/migrate/v18-preparation.mdx index 926cd21..a293bbe 100644 --- a/content/docs/migrate/v18-preparation.mdx +++ b/content/docs/migrate/v18-preparation.mdx @@ -45,6 +45,8 @@ pnpm -C apps/core upgrade:check --json 只有全部残留检查通过才记录准备标记。损坏密文、未知配置形状、错误字段密钥或 JWT 都会失败并回滚。准备完成后重启副本淘汰旧配置缓存,不清空所有 Redis 状态。旧 `migrate:secrets` 账本不能替代此检查。 +如果规范凭据为空,转换会保留已有旧凭据;新旧值都非空且不同时,检查返回 `CONFIG_SECRET_CONFLICT` 与字段路径,准备停止。在后台重新明确填写要保留的规范凭据并保存,再运行检查。旧配置读取继续兼容,但不明确替换凭据的部分保存不能悄悄删除冲突值。 + ## 维护窗口轮换 JWT 先停止全部业务副本、后台任务和其他写入者。将原 JWT 和至少 32 字符的新 JWT 放在两个仅操作者可读的文件中,不把秘密值放在命令行或聊天记录里: From 847dc3c61a829c70a44a607a83f128b744133ee3 Mon Sep 17 00:00:00 2001 From: Innei Date: Sun, 8 Nov 2026 23:21:08 +0800 Subject: [PATCH 3/7] docs: explain required encryption on the v18 development branch --- content/docs/configure/encryption.mdx | 4 ++-- content/docs/configure/environment.mdx | 4 +++- content/docs/migrate/v18-preparation.mdx | 24 ++++++++++++++++-------- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/content/docs/configure/encryption.mdx b/content/docs/configure/encryption.mdx index a6c1e21..ecd2eda 100644 --- a/content/docs/configure/encryption.mdx +++ b/content/docs/configure/encryption.mdx @@ -3,7 +3,7 @@ title: 数据加密 description: v17 凭据加密配置、原密钥保留与 v18 准备 --- -本文以已发布的 Core v17.0.4 为基线。v18 尚未发布,后续强制加密计划见 [v18 准备说明](/docs/migrate/v18-preparation)。 +本文固定已发布的 Core v17.0.4。未发布的 v18 开发分支已删除关闭加密和旧格式写入,不能沿用下文的 v17 逃生口,详见 [v18 准备说明](/docs/migrate/v18-preparation)。 ## 三种秘密分别保管 @@ -41,7 +41,7 @@ V2 字段密文绑定字段路径;不能把密文直接搬到另一个字段 只有关闭字段加密且 `MX_SECRET_STORAGE_V2=false` 时,普通配置写入可继续保留明文。它不适用于已有密文,也不适用于明确要求加密的授权功能。关闭开关不会解密旧数据;遇到已有密文仍须开启加密并提供原密钥。 -v18 计划移除此逃生口,要求显式字段密钥、至少 32 字符的 JWT,并固定 V2 写入;当前 v17 的合法旧行为仍保留。 +此逃生口仅适用于已发布的 v17.0.4。未发布的 v18 开发分支已经移除它,要求显式字段密钥、至少 32 字符的 JWT,并固定 V2 写入;旧关闭配置须删除。 ## 解密失败时 diff --git a/content/docs/configure/environment.mdx b/content/docs/configure/environment.mdx index 8f106bb..8e7dced 100644 --- a/content/docs/configure/environment.mdx +++ b/content/docs/configure/environment.mdx @@ -7,6 +7,8 @@ description: 所有环境变量的完整说明 如果你不确定某个变量的作用,保持默认值即可。 +以下默认行为固定已发布的 Core v17.0.4。未发布的 v18 开发分支已强制字段加密和 V2 写入,字段密钥与至少 32 字符的 JWT 必填;旧关闭配置须删除,见 [v18 准备说明](/docs/migrate/v18-preparation)。 + 以下环境变量适用于 Mix Space Core 后端服务。Docker 用户在 `docker-compose.yml` 顶部的 `x-mx-env` 锚点中设置;源码用户在 `.env` 或 `ecosystem.config.cjs` 中设置。 @@ -69,7 +71,7 @@ cluster 模式下可安全使用多实例:服务端会基于 `NODE_APP_INSTANC `ENCRYPT_ENABLE` 的判定逻辑是「未设置时,回落到是否设置了密钥」。**只填密钥、不管开关,加密就已经开启。** 想暂不加密必须显式写 `ENCRYPT_ENABLE=false`。详见 [数据加密](/docs/configure/encryption)。 -当前 v17.0.4 保留关闭加密的临时明文逃生口,不能读取已有密文或绕过明确要求加密的功能。v18 的强制配置与新准备工具尚未发布,见 [v18 准备说明](/docs/migrate/v18-preparation)。 +当前 v17.0.4 保留关闭加密的临时明文逃生口,不能读取已有密文或绕过明确要求加密的功能。未发布的 v18 开发分支已实现强制加密,新准备工具也尚未发布,见 [v18 准备说明](/docs/migrate/v18-preparation)。 ## 缓存与限流 diff --git a/content/docs/migrate/v18-preparation.mdx b/content/docs/migrate/v18-preparation.mdx index a293bbe..ff05a4d 100644 --- a/content/docs/migrate/v18-preparation.mdx +++ b/content/docs/migrate/v18-preparation.mdx @@ -1,34 +1,42 @@ --- title: v18 准备(未发布) -description: 先部署兼容准备版,再检查凭据、配置和 AI 指标 +description: 本开发分支已强制加密,部署前检查凭据、配置和 AI 指标 --- -v18 尚未发布。本文的 `upgrade:*` 是正在开发的 v17 准备版工具,已发布的 v17.0.4 不包含它们。最低准备版 tag、SDK、CLI 和主题版本会在实际发布验证后补齐;不要直接在现有镜像上运行不存在的命令。 +v18 尚未发布。本开发分支已移除非加密运行和旧格式写入,启动必须提供显式字段密钥与至少 32 字符的 JWT。本文的 `upgrade:*` 工具尚未发布,已发布的 v17.0.4 不包含它们。最低准备版 tag、SDK、CLI 和主题版本会在实际发布验证后补齐;不要直接在现有镜像上运行不存在的命令。 ## 计划改变什么 | 范围 | 当前 v17 | v18 计划 | | --- | --- | --- | -| 凭据写入 | 可分阶段启用 V2,有临时明文逃生口 | 显式 hex64 字段密钥、JWT 至少 32 字符、固定 V2;显式关闭报错 | +| 凭据写入 | v17.0.4 可分阶段启用 V2,有临时明文逃生口 | **本开发分支已生效**:显式 hex64 字段密钥、JWT 至少 32 字符、固定 V2;旧关闭配置报错 | | 普通摘要、精读 GET | 可能隐式生成 | 只读,无结果 `data: null`;生成显式触发 | | 非流式执行入口 | 旧 GET 可用 | 备份、清缓存、测试邮件与速记表态用 POST;GET SSE 保留 | | 配置与参数 | 保留旧配置、排序与别名兼容 | 拒绝未知声明字段、数字排序;移除评论列表 `state` 查询别名,更新 body 的 state 仍可用 | | AI 元数据 | 旧响应字段保留 | 统一 generationMetrics,后续才删除旧列与旧响应字段 | | SDK | 5.x 兼容版先转换请求 | 协调 SDK 6.x;保留 legacy 入口与 CLI V2 响应适配 | -准备版继续支持旧 GET 和 SDK 5.x 签名,不改变普通 AI GET 的既有行为,不删除物理列。API `/api/v3`、snake_case、成功/错误 envelope 和 SSE 帧协议保持。 +加密强制已在本分支实现;其余最终破坏仍分阶段推进。当前继续支持旧 GET 和 SDK 5.x 签名,不改变普通 AI GET 的既有行为,不删除物理列。API `/api/v3`、snake_case、成功/错误 envelope 和 SSE 帧协议保持。 ## 准备顺序 1. 更早的实例先按历史指南升级到兼容的 v17,保存数据库、文件、原字段密钥、原 JWT 和备份私钥。 -2. 部署包含准备工具的版本到全部副本。业务进程用 CLI/YAML 配置时,也要为独立工具显式提供等效环境变量;工具读取 `PG_URL` / `PG_CONNECTION_STRING` 或 `PG_*`、字段密钥、JWT 和 V2 开关。 +2. 取得包含工具的构建,先不要启动业务副本。独立工具读取 `PG_URL` / `PG_CONNECTION_STRING` 或 `PG_*`、字段密钥和 JWT;业务进程用 CLI/YAML 配置时,也要提供等效环境变量。先用 check 诊断现有配置。 3. JWT 不足 32 字符时先执行下方维护轮换。不要直接改环境变量,否则现有 OAuth token 可能无法解密。 -4. 全部副本启用字段加密和 `MX_SECRET_STORAGE_V2=true`,确认旧副本及后台任务已经停止旧格式写入,再执行准备。 +4. 删除 `ENCRYPT_ENABLE`、`MX_ENCRYPT_ENABLE` 和 `MX_SECRET_STORAGE_V2`,保存原有效字段密钥与符合要求的 JWT。停止旧副本及后台写入者,再部署本构建到全部副本;新写入自动加密,无需启用开关。确认没有旧格式写入者,再执行准备。 5. 检查、dry-run、正式 prepare,完成后重启业务副本,验证原 API Key、OAuth、配置消费与 AI 指标。 -源码工作区中的命令(需要准备版代码和已完成 schema 迁移的数据库): +### 启动配置与旧开关 + +- `MX_ENCRYPT_KEY` 或 `ENCRYPT_KEY` 必须是显式的 64 位十六进制密钥;不再回退到机器 ID,也不再派生短密钥。只有从未加密的新实例才用 `openssl rand -hex 32` 生成首个字段密钥并持久保存。 +- `JWT_SECRET`(别名 `JWTSECRET`)至少 32 字符;已有短 JWT 必须先按下方维护流程轮换。 +- 旧开关的 `true` 值暂接受并提示删除;`false` 或其他无效值会阻止启动,check 报告 `DEPRECATED_ENCRYPTION_FLAG`。它们不再控制运行模式。 +- 配置、Snippet 和 Webhook 固定认证加密,API Key 固定哈希,OAuth 固定加密;历史明文和 ECB 仍可读取并显式迁移。`ENCRYPT_ALGORITHM` 只用于历史读取。 +- app 与 mx-migrate 使用相同且持久保存的密钥。字段加密必填不影响 ZIP 备份和历史归档读取;备份加密仍由独立设置控制。 + +源码工作区中的命令(需要本分支代码和已完成 schema 迁移的数据库): ```bash pnpm -C apps/core upgrade:check --json @@ -37,7 +45,7 @@ pnpm -C apps/core upgrade:prepare --all-replicas-v2 pnpm -C apps/core upgrade:check --json ``` -构建产物中使用 `node upgrade.mjs check --json`、`node upgrade.mjs prepare --all-replicas-v2 --dry-run` 和正式 prepare。在包含该入口的镜像中,可使用 `docker compose run --rm --no-deps app node upgrade.mjs check --json` 启动独立检查;数据库需已运行。该进程不启动 HTTP、业务定时任务或 Redis。 +构建产物中使用 `node upgrade.mjs check --json`、`node upgrade.mjs prepare --all-replicas-v2 --dry-run` 和正式 prepare。在包含该入口的镜像中,可使用 `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` 为检查失败。它只读,输出计数和路径,不输出秘密。空库可识别为新装,不要求历史准备标记;历史实例须完成实际转换。 From 0587f716d27c294441bb5bdd5b756ca8bdfb1ae1 Mon Sep 17 00:00:00 2001 From: Innei Date: Mon, 9 Nov 2026 00:33:53 +0800 Subject: [PATCH 4/7] docs: require release-time configuration migration for v18 --- content/docs/migrate/v18-preparation.mdx | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/content/docs/migrate/v18-preparation.mdx b/content/docs/migrate/v18-preparation.mdx index ff05a4d..c82ec5b 100644 --- a/content/docs/migrate/v18-preparation.mdx +++ b/content/docs/migrate/v18-preparation.mdx @@ -14,19 +14,19 @@ v18 尚未发布。本开发分支已移除非加密运行和旧格式写入, | 凭据写入 | v17.0.4 可分阶段启用 V2,有临时明文逃生口 | **本开发分支已生效**:显式 hex64 字段密钥、JWT 至少 32 字符、固定 V2;旧关闭配置报错 | | 普通摘要、精读 GET | 可能隐式生成 | 只读,无结果 `data: null`;生成显式触发 | | 非流式执行入口 | 旧 GET 可用 | 备份、清缓存、测试邮件与速记表态用 POST;GET SSE 保留 | -| 配置与参数 | 保留旧配置、排序与别名兼容 | 拒绝未知声明字段、数字排序;移除评论列表 `state` 查询别名,更新 body 的 state 仍可用 | +| 配置与参数 | v17 保留旧配置、排序与别名兼容 | **本分支已生效**:发布前迁移旧配置,正常服务只认新结构,PATCH 拒绝未知声明字段;数字排序和评论别名移除仍在后续阶段 | | AI 元数据 | 旧响应字段保留 | 统一 generationMetrics,后续才删除旧列与旧响应字段 | | SDK | 5.x 兼容版先转换请求 | 协调 SDK 6.x;保留 legacy 入口与 CLI V2 响应适配 | -加密强制已在本分支实现;其余最终破坏仍分阶段推进。当前继续支持旧 GET 和 SDK 5.x 签名,不改变普通 AI GET 的既有行为,不删除物理列。API `/api/v3`、snake_case、成功/错误 envelope 和 SSE 帧协议保持。 +加密强制、旧配置迁移和配置严格校验已在本分支实现;其余最终破坏仍分阶段推进。当前继续支持旧 GET 和 SDK 5.x 签名,不改变普通 AI GET 的既有行为,不删除物理列。API `/api/v3`、snake_case、成功/错误 envelope 和 SSE 帧协议保持。 ## 准备顺序 1. 更早的实例先按历史指南升级到兼容的 v17,保存数据库、文件、原字段密钥、原 JWT 和备份私钥。 2. 取得包含工具的构建,先不要启动业务副本。独立工具读取 `PG_URL` / `PG_CONNECTION_STRING` 或 `PG_*`、字段密钥和 JWT;业务进程用 CLI/YAML 配置时,也要提供等效环境变量。先用 check 诊断现有配置。 3. JWT 不足 32 字符时先执行下方维护轮换。不要直接改环境变量,否则现有 OAuth token 可能无法解密。 -4. 删除 `ENCRYPT_ENABLE`、`MX_ENCRYPT_ENABLE` 和 `MX_SECRET_STORAGE_V2`,保存原有效字段密钥与符合要求的 JWT。停止旧副本及后台写入者,再部署本构建到全部副本;新写入自动加密,无需启用开关。确认没有旧格式写入者,再执行准备。 -5. 检查、dry-run、正式 prepare,完成后重启业务副本,验证原 API Key、OAuth、配置消费与 AI 指标。 +4. 删除旧加密开关,保存原有效字段密钥与符合要求的 JWT。**先停止全部旧副本和后台写入者**,再使用本构建运行 `node migrate.mjs`(源码为 `pnpm -C apps/core migrate`)。配置转换是必跑的发布前数据迁移,无需额外开关;失败阻止新服务启动,成功才记录 `20261109-canonical-config`。这一步需要维护窗口,不能让旧版在迁移后继续写回旧结构。 +5. 继续 check、dry-run、正式 prepare,完成凭据和指标准备后再启动新业务副本,验证原 API Key、OAuth、配置消费与 AI 指标。Compose 中 app 与 mx-migrate 必须使用同一构建与秘密。 ### 启动配置与旧开关 @@ -53,7 +53,15 @@ pnpm -C apps/core upgrade:check --json 只有全部残留检查通过才记录准备标记。损坏密文、未知配置形状、错误字段密钥或 JWT 都会失败并回滚。准备完成后重启副本淘汰旧配置缓存,不清空所有 Redis 状态。旧 `migrate:secrets` 账本不能替代此检查。 -如果规范凭据为空,转换会保留已有旧凭据;新旧值都非空且不同时,检查返回 `CONFIG_SECRET_CONFLICT` 与字段路径,准备停止。在后台重新明确填写要保留的规范凭据并保存,再运行检查。旧配置读取继续兼容,但不明确替换凭据的部分保存不能悄悄删除冲突值。 +## 配置一次性迁移 + +发布迁移把 `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 参数仍有效。 + +数据库缺少配置迁移记录时,新服务拒绝初始化配置并提示运行发布迁移。转换器只保留在发布迁移与离线准备工具中,供历史数据升级;旧归档仍按下方隔离恢复流程处理。 + +规范凭据为空时保留已有旧凭据;新旧值都非空且不同时,报告 `CONFIG_SECRET_CONFLICT` 与字段路径,配置迁移整批回滚。先在旧实例或隔离维护环境核对并明确解决冲突,再重跑迁移;不要尝试启动新服务后通过旧字段 PATCH 修复。错误密钥、损坏密文和不支持的配置形状同样阻塞迁移,不写完成记录。 ## 维护窗口轮换 JWT @@ -76,3 +84,5 @@ pnpm -C apps/core upgrade:rotate-jwt --maintenance-confirmed --old-jwt-file /sec 回退最低版本须是经过实测的准备版,当前发布 tag 尚未确定。回退代码不会回退 schema;回到更老版本必须有旧快照和当时的秘密。 当前恢复工具没有 v18 隔离预检闭环。历史 ZIP 或 v1 加密归档应先在隔离的准备版环境中恢复,用归档当时的字段密钥、JWT、恢复私钥完成 check / prepare,再验证并安排未来 v18 切换;不要把未准备数据直接交给 v18 业务。数据库恢复和文件恢复是两个阶段,保留恢复前快照。 + +配置字段类型已经按新结构同步到本分支的 API Client。旧 `githubToken` 和 `smtp.options` 调用须改为嵌套 GitHub 配置与平铺 SMTP 字段;这是配套大版本契约,不能将该类型删除作为 SDK 5.x patch 发布。 From 3aaf24a31a652989a1f4653423ebc427bcc2c82c Mon Sep 17 00:00:00 2001 From: Innei Date: Mon, 9 Nov 2026 01:35:12 +0800 Subject: [PATCH 5/7] docs: consolidate v18 preparation into release migrations --- content/docs/migrate/v18-preparation.mdx | 17 ++++++++--------- content/docs/use/backup-restore.mdx | 2 +- public/agent-skills/mix-space-expert.md | 2 +- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/content/docs/migrate/v18-preparation.mdx b/content/docs/migrate/v18-preparation.mdx index c82ec5b..521a290 100644 --- a/content/docs/migrate/v18-preparation.mdx +++ b/content/docs/migrate/v18-preparation.mdx @@ -26,7 +26,7 @@ v18 尚未发布。本开发分支已移除非加密运行和旧格式写入, 2. 取得包含工具的构建,先不要启动业务副本。独立工具读取 `PG_URL` / `PG_CONNECTION_STRING` 或 `PG_*`、字段密钥和 JWT;业务进程用 CLI/YAML 配置时,也要提供等效环境变量。先用 check 诊断现有配置。 3. JWT 不足 32 字符时先执行下方维护轮换。不要直接改环境变量,否则现有 OAuth token 可能无法解密。 4. 删除旧加密开关,保存原有效字段密钥与符合要求的 JWT。**先停止全部旧副本和后台写入者**,再使用本构建运行 `node migrate.mjs`(源码为 `pnpm -C apps/core migrate`)。配置转换是必跑的发布前数据迁移,无需额外开关;失败阻止新服务启动,成功才记录 `20261109-canonical-config`。这一步需要维护窗口,不能让旧版在迁移后继续写回旧结构。 -5. 继续 check、dry-run、正式 prepare,完成凭据和指标准备后再启动新业务副本,验证原 API Key、OAuth、配置消费与 AI 指标。Compose 中 app 与 mx-migrate 必须使用同一构建与秘密。 +5. 同一个 migrate 还会回填 AI 指标,并必跑 `20261109-required-secret-storage`,完成 Snippet/Webhook 加密、API Key 哈希和 OAuth 加密。迁移成功后运行 check,再启动新业务副本,验证原 API Key、OAuth、配置消费与 AI 指标。Compose 中 app 与 mx-migrate 必须使用同一构建与秘密。 ### 启动配置与旧开关 @@ -40,18 +40,17 @@ v18 尚未发布。本开发分支已移除非加密运行和旧格式写入, ```bash pnpm -C apps/core upgrade:check --json -pnpm -C apps/core upgrade:prepare --all-replicas-v2 --dry-run -pnpm -C apps/core upgrade:prepare --all-replicas-v2 +pnpm -C apps/core migrate pnpm -C apps/core upgrade:check --json ``` -构建产物中使用 `node upgrade.mjs check --json`、`node upgrade.mjs prepare --all-replicas-v2 --dry-run` 和正式 prepare。在包含该入口的镜像中,可使用 `docker compose run --rm --no-deps app node upgrade.mjs check --json` 启动独立检查;数据库需已运行。该进程不启动 HTTP、业务定时任务或 Redis。当前分支的 Compose 会先校验密钥和 JWT 非空;诊断缺失配置时使用源码命令或直接 `docker run`,避免先被 Compose 拦截。 +构建产物中用 `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` 为检查失败。它只读,输出计数和路径,不输出秘密。空库可识别为新装,不要求历史准备标记;历史实例须完成实际转换。 +`check` 退出码:`0` 为 ready,`2` 为 needs-action,`1` 为检查失败。它只读,输出计数和路径,不输出秘密。空库可识别为新装;历史实例须完成规范配置迁移并通过实际数据检查,没有单独准备标记。 -`--all-replicas-v2` 是操作者对全部副本状态的明确确认,工具不能自动检查集群。prepare 持有数据库锁,与正常配置 PATCH 串行;按原路径解密,再转换配置、重新加密、哈希旧 API Key、封装 OAuth 并补齐指标。原 API Key 字符串保持有效。dry-run 在事务内演练后回滚,`would-be-ready` 不代表数据库已经准备完成。 +所有回填都属于正式发布迁移,无需独立 prepare、dry-run 或凭据迁移开关。配置迁移持有数据库锁,与正常配置 PATCH 串行;按原路径解密,再转换配置并按新路径加密。凭据迁移哈希旧 API Key、封装 OAuth 并加密 Snippet/Webhook,原 API Key 字符串保持有效。新的 v18 凭据迁移有独立账本 ID,历史 opt-in 凭据迁移记录不会使本次回填被跳过。 -只有全部残留检查通过才记录准备标记。损坏密文、未知配置形状、错误字段密钥或 JWT 都会失败并回滚。准备完成后重启副本淘汰旧配置缓存,不清空所有 Redis 状态。旧 `migrate:secrets` 账本不能替代此检查。 +每条迁移成功后才记录完成,损坏密文、未知配置形状、错误字段密钥或 JWT 会使对应批次失败并回滚,阻断发布。先前成功的迁移保留账本,重试时自动跳过;重复执行不会重复哈希 API Key 或轮换已加密 token。迁移和检查完成后启动新副本,不清空所有 Redis 状态。 ## 配置一次性迁移 @@ -59,7 +58,7 @@ pnpm -C apps/core upgrade:check --json 普通启动、读取和 PATCH 不再转换旧结构,也不再返回独立 `imageGenerationOptions` / `ttsOptions` 分区。旧分区读写返回 `CONFIG_NOT_FOUND`;未知或旧字段返回 `CONFIG_VALIDATION_FAILED`,不会静默忽略。部分更新、空秘密保留原值,以及 record / 自由 JSON 参数仍有效。 -数据库缺少配置迁移记录时,新服务拒绝初始化配置并提示运行发布迁移。转换器只保留在发布迁移与离线准备工具中,供历史数据升级;旧归档仍按下方隔离恢复流程处理。 +数据库缺少配置迁移记录时,新服务拒绝初始化配置并提示运行发布迁移。转换器只保留在发布迁移与只读检查中,供历史数据升级;旧归档仍按下方隔离恢复流程处理。 规范凭据为空时保留已有旧凭据;新旧值都非空且不同时,报告 `CONFIG_SECRET_CONFLICT` 与字段路径,配置迁移整批回滚。先在旧实例或隔离维护环境核对并明确解决冲突,再重跑迁移;不要尝试启动新服务后通过旧字段 PATCH 修复。错误密钥、损坏密文和不支持的配置形状同样阻塞迁移,不写完成记录。 @@ -83,6 +82,6 @@ pnpm -C apps/core upgrade:rotate-jwt --maintenance-confirmed --old-jwt-file /sec 回退最低版本须是经过实测的准备版,当前发布 tag 尚未确定。回退代码不会回退 schema;回到更老版本必须有旧快照和当时的秘密。 -当前恢复工具没有 v18 隔离预检闭环。历史 ZIP 或 v1 加密归档应先在隔离的准备版环境中恢复,用归档当时的字段密钥、JWT、恢复私钥完成 check / prepare,再验证并安排未来 v18 切换;不要把未准备数据直接交给 v18 业务。数据库恢复和文件恢复是两个阶段,保留恢复前快照。 +当前恢复工具没有 v18 隔离预检闭环。历史 ZIP 或 v1 加密归档应先在隔离维护环境中恢复,用归档当时的字段密钥、JWT、恢复私钥完成 migrate 和 check,再验证并安排未来 v18 切换;不要把未迁移数据直接交给 v18 业务。数据库恢复和文件恢复是两个阶段,保留恢复前快照。 配置字段类型已经按新结构同步到本分支的 API Client。旧 `githubToken` 和 `smtp.options` 调用须改为嵌套 GitHub 配置与平铺 SMTP 字段;这是配套大版本契约,不能将该类型删除作为 SDK 5.x patch 发布。 diff --git a/content/docs/use/backup-restore.mdx b/content/docs/use/backup-restore.mdx index 6fd408c..276cc3e 100644 --- a/content/docs/use/backup-restore.mdx +++ b/content/docs/use/backup-restore.mdx @@ -25,7 +25,7 @@ import { Callout } from 'fumadocs-ui/components/callout' 恢复会覆盖数据库业务内容和数据文件。操作前服务端尝试创建安全快照,仍应额外保存可恢复的外部副本。会话、日志、搜索索引等运行时表按当前结构重建为空表。数据库替换在单个事务内,文件随后复制,二者不是同一个原子事务;文件复制失败时保留快照处理,不应报告整次恢复成功。 -恢复后核对文章、评论、登录、凭据消费及文件。跨版本恢复须遵循对应迁移指南。未来 v18 的历史恢复应走「隔离准备版恢复 → check / prepare → 验证 → v18」路径,见 [v18 准备说明](/docs/migrate/v18-preparation)。当前没有自动隔离预检,不要恢复后直接开放未来 v18 业务。 +恢复后核对文章、评论、登录、凭据消费及文件。跨版本恢复须遵循对应迁移指南。未来 v18 的历史恢复应走「隔离恢复 → migrate → check → 验证 → v18」路径,见 [v18 准备说明](/docs/migrate/v18-preparation)。当前没有自动隔离预检,不要恢复后直接开放未来 v18 业务。 ## 手工数据库快照 diff --git a/public/agent-skills/mix-space-expert.md b/public/agent-skills/mix-space-expert.md index cc24d5d..894717e 100644 --- a/public/agent-skills/mix-space-expert.md +++ b/public/agent-skills/mix-space-expert.md @@ -89,4 +89,4 @@ docker compose up -d - For code questions, reference the actual file paths in the monorepo - When troubleshooting, start with the most common cause first ### Version preparation -Core v17.0.4 retains a temporary plaintext escape hatch only with encryption and V2 both disabled; existing ciphertext still requires its original key. v18 is not released. The upcoming preparation build adds `upgrade:check`, transactional `upgrade:prepare`, and file-based maintenance JWT rotation. v17.0.4 does not contain those commands. Do not invent a minimum preparation tag. Refer to `/docs/migrate/v18-preparation`. Never replace an existing key with a random one to solve a decryption error. +Core v17.0.4 retains a temporary plaintext escape hatch only with encryption and V2 both disabled; existing ciphertext still requires its original key. v18 is not released. The upcoming build uses mandatory release-phase `migrate` for configuration, credentials and AI metrics, plus read-only `upgrade:check` and file-based maintenance JWT rotation. There is no separate prepare command, preparation marker or credential-migration activation flag. The v18 credential migration runs even if the historical opt-in backfill was recorded. v17.0.4 does not contain the new tools. Do not invent a minimum preparation tag. Refer to `/docs/migrate/v18-preparation`. Never replace an existing key with a random one to solve a decryption error. From dae63bc8423f351a2e39c2d6b8b60eea1176cd91 Mon Sep 17 00:00:00 2001 From: Innei Date: Mon, 9 Nov 2026 02:21:34 +0800 Subject: [PATCH 6/7] docs: mark encryption algorithm selection as removed in v18 --- content/docs/configure/encryption.mdx | 2 ++ content/docs/configure/environment.mdx | 2 +- content/docs/migrate/v18-preparation.mdx | 5 ++++- public/agent-skills/mix-space-expert.md | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/content/docs/configure/encryption.mdx b/content/docs/configure/encryption.mdx index ecd2eda..94cfaeb 100644 --- a/content/docs/configure/encryption.mdx +++ b/content/docs/configure/encryption.mdx @@ -35,6 +35,8 @@ openssl rand -hex 32 V2 字段密文绑定字段路径;不能把密文直接搬到另一个字段。旧明文和 ECB 保留读取与迁移能力。`ENCRYPT_ALGORITHM` 默认 `aes-256-ecb`,供旧格式使用;V2 固定使用 AES-256-GCM。 +未发布的 v18 开发分支已删除 `ENCRYPT_ALGORITHM`、`--encrypt_algorithm` 和 YAML `encrypt_algorithm`,新写入固定 AES-256-GCM。旧项即使填默认值也会报错,应删除;历史读取固定为 AES-256-ECB。使用其他历史算法的实例须先在旧版本用原密钥和原算法转换为 V2 并验证,再升级;详见 [v18 准备说明](/docs/migrate/v18-preparation)。 + 滚动部署时,先让全部副本具备新格式读取能力,再在全部副本启用 V2,最后回填旧数据。旧 `migrate:secrets` 完成记录仅代表该次凭据回填,不代表已完成未来 v18 的配置与指标准备。新准备工具尚未发布,见 [准备流程](/docs/migrate/v18-preparation)。 ## v17.0.4 临时明文逃生口 diff --git a/content/docs/configure/environment.mdx b/content/docs/configure/environment.mdx index 8e7dced..96fa323 100644 --- a/content/docs/configure/environment.mdx +++ b/content/docs/configure/environment.mdx @@ -62,7 +62,7 @@ cluster 模式下可安全使用多实例:服务端会基于 `NODE_APP_INSTANC | `ENCRYPT_KEY` | 开启时必须显式提供 64 位十六进制字段密钥 | 无可用默认密钥 | `openssl rand -hex 32` | | `MX_ENCRYPT_KEY` | `ENCRYPT_KEY` 的别名,**优先于** `ENCRYPT_KEY` | 同上 | - | | `MX_ENCRYPT_ENABLE` | `ENCRYPT_ENABLE` 的别名,**优先于** `ENCRYPT_ENABLE` | 同上 | - | -| `ENCRYPT_ALGORITHM` | 旧格式算法;V2 固定 AES-256-GCM | `aes-256-ecb` | `aes-256-ecb` | +| `ENCRYPT_ALGORITHM` | v17 旧格式算法;V2 固定 AES-256-GCM。v18 已删除此项,保留会报错 | `aes-256-ecb`(仅 v17) | `aes-256-ecb`(仅 v17) | | `MX_SECRET_STORAGE_V2` | V2 凭据写入;要求启用加密、JWT 至少 32 字符,全部副本兼容后才启用 | `false` | `true` | | `MX_MIGRATE_SECRETS` | 旧凭据回填的一次性迁移开关,不放入常驻 app 环境;不等于 v18 准备完成 | 未开启 | `1` | | `ADMIN_UPDATE_S3_BASE_URL` | 后台更新包(`latest.json` + `admin-.zip`)的下载源 | `https://admin-r2.innei.dev` | - | diff --git a/content/docs/migrate/v18-preparation.mdx b/content/docs/migrate/v18-preparation.mdx index 521a290..d729608 100644 --- a/content/docs/migrate/v18-preparation.mdx +++ b/content/docs/migrate/v18-preparation.mdx @@ -33,9 +33,12 @@ v18 尚未发布。本开发分支已移除非加密运行和旧格式写入, - `MX_ENCRYPT_KEY` 或 `ENCRYPT_KEY` 必须是显式的 64 位十六进制密钥;不再回退到机器 ID,也不再派生短密钥。只有从未加密的新实例才用 `openssl rand -hex 32` 生成首个字段密钥并持久保存。 - `JWT_SECRET`(别名 `JWTSECRET`)至少 32 字符;已有短 JWT 必须先按下方维护流程轮换。 - 旧开关的 `true` 值暂接受并提示删除;`false` 或其他无效值会阻止启动,check 报告 `DEPRECATED_ENCRYPTION_FLAG`。它们不再控制运行模式。 -- 配置、Snippet 和 Webhook 固定认证加密,API Key 固定哈希,OAuth 固定加密;历史明文和 ECB 仍可读取并显式迁移。`ENCRYPT_ALGORITHM` 只用于历史读取。 +- 配置、Snippet 和 Webhook 新写入固定 AES-256-GCM,使用随机 96 位 nonce、128 位认证标签,并以 AAD 绑定所属字段;API Key 固定哈希,OAuth 固定加密。历史明文和默认 AES-256-ECB 仍可读取并迁移。 +- **破坏性变更**:删除 `ENCRYPT_ALGORITHM`、命令行 `--encrypt_algorithm` 和 YAML `encrypt_algorithm`。保留旧项会报错,即使填旧默认值、`aes-256-gcm` 或空环境变量也不接受;应删除配置项。环境变量检查报告 `REMOVED_ENCRYPTION_OPTION`。 - app 与 mx-migrate 使用相同且持久保存的密钥。字段加密必填不影响 ZIP 备份和历史归档读取;备份加密仍由独立设置控制。 +历史字段解密固定为旧默认 AES-256-ECB。曾使用其他旧算法的实例,必须先在旧版本中使用原密钥和原算法将相关凭据转换为 V2,并验证凭据可用后再升级。删除配置项不会转换自定义算法密文。原算法和密钥仍须随旧快照单独保管,以便在隔离的旧版本环境恢复。 + 源码工作区中的命令(需要本分支代码和已完成 schema 迁移的数据库): ```bash diff --git a/public/agent-skills/mix-space-expert.md b/public/agent-skills/mix-space-expert.md index 894717e..4b4bdb7 100644 --- a/public/agent-skills/mix-space-expert.md +++ b/public/agent-skills/mix-space-expert.md @@ -89,4 +89,4 @@ docker compose up -d - For code questions, reference the actual file paths in the monorepo - When troubleshooting, start with the most common cause first ### Version preparation -Core v17.0.4 retains a temporary plaintext escape hatch only with encryption and V2 both disabled; existing ciphertext still requires its original key. v18 is not released. The upcoming build uses mandatory release-phase `migrate` for configuration, credentials and AI metrics, plus read-only `upgrade:check` and file-based maintenance JWT rotation. There is no separate prepare command, preparation marker or credential-migration activation flag. The v18 credential migration runs even if the historical opt-in backfill was recorded. v17.0.4 does not contain the new tools. Do not invent a minimum preparation tag. Refer to `/docs/migrate/v18-preparation`. Never replace an existing key with a random one to solve a decryption error. +Core v17.0.4 retains a temporary plaintext escape hatch only with encryption and V2 both disabled; existing ciphertext still requires its original key. v18 is not released. The upcoming build uses mandatory release-phase `migrate` for configuration, credentials and AI metrics, plus read-only `upgrade:check` and file-based maintenance JWT rotation. There is no separate prepare command, preparation marker or credential-migration activation flag. The v18 credential migration runs even if the historical opt-in backfill was recorded. v18 removes and rejects `ENCRYPT_ALGORITHM`, CLI `--encrypt_algorithm` and YAML `encrypt_algorithm`, even with old default values. Field writes are fixed to AES-256-GCM; historical field reads are fixed to AES-256-ECB. Instances with a custom legacy algorithm must convert affected credentials to V2 on the prior version with the original algorithm and key, and verify them before upgrading. Keep original settings for isolated old-snapshot recovery. v17.0.4 does not contain the new tools. Do not invent a minimum preparation tag. Refer to `/docs/migrate/v18-preparation`. Never replace an existing key with a random one to solve a decryption error. From 9a9f4c4ed3a174456981998c94c867dd36a02539 Mon Sep 17 00:00:00 2001 From: Innei Date: Mon, 9 Nov 2026 02:47:13 +0800 Subject: [PATCH 7/7] docs: explain persistent local encryption keys in development --- content/docs/configure/encryption.mdx | 2 +- content/docs/configure/environment.mdx | 2 +- content/docs/migrate/v18-preparation.mdx | 12 +++++++++--- public/agent-skills/mix-space-expert.md | 2 ++ 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/content/docs/configure/encryption.mdx b/content/docs/configure/encryption.mdx index 94cfaeb..25a355e 100644 --- a/content/docs/configure/encryption.mdx +++ b/content/docs/configure/encryption.mdx @@ -43,7 +43,7 @@ V2 字段密文绑定字段路径;不能把密文直接搬到另一个字段 只有关闭字段加密且 `MX_SECRET_STORAGE_V2=false` 时,普通配置写入可继续保留明文。它不适用于已有密文,也不适用于明确要求加密的授权功能。关闭开关不会解密旧数据;遇到已有密文仍须开启加密并提供原密钥。 -此逃生口仅适用于已发布的 v17.0.4。未发布的 v18 开发分支已经移除它,要求显式字段密钥、至少 32 字符的 JWT,并固定 V2 写入;旧关闭配置须删除。 +此逃生口仅适用于已发布的 v17.0.4。未发布的 v18 开发分支已经移除它,生产要求显式字段密钥、至少 32 字符的 JWT,并固定 V2 写入;旧关闭配置须删除。源码 dev 未配置字段密钥时可自动生成并保存在 `tmp/dev-encryption.key`,重启复用;开发库和该文件须一起保留,详见 [v18 开发环境说明](/docs/migrate/v18-preparation#源码开发环境)。 ## 解密失败时 diff --git a/content/docs/configure/environment.mdx b/content/docs/configure/environment.mdx index 96fa323..a6e583c 100644 --- a/content/docs/configure/environment.mdx +++ b/content/docs/configure/environment.mdx @@ -7,7 +7,7 @@ description: 所有环境变量的完整说明 如果你不确定某个变量的作用,保持默认值即可。 -以下默认行为固定已发布的 Core v17.0.4。未发布的 v18 开发分支已强制字段加密和 V2 写入,字段密钥与至少 32 字符的 JWT 必填;旧关闭配置须删除,见 [v18 准备说明](/docs/migrate/v18-preparation)。 +以下默认行为固定已发布的 Core v17.0.4。未发布的 v18 开发分支已强制字段加密和 V2 写入,生产环境须提供显式字段密钥,源码 dev 可自动生成并持久保存本地密钥;JWT 至少 32 字符,旧关闭配置须删除,见 [v18 准备说明](/docs/migrate/v18-preparation)。 以下环境变量适用于 Mix Space Core 后端服务。Docker 用户在 `docker-compose.yml` 顶部的 `x-mx-env` 锚点中设置;源码用户在 `.env` 或 `ecosystem.config.cjs` 中设置。 diff --git a/content/docs/migrate/v18-preparation.mdx b/content/docs/migrate/v18-preparation.mdx index d729608..dc079db 100644 --- a/content/docs/migrate/v18-preparation.mdx +++ b/content/docs/migrate/v18-preparation.mdx @@ -4,14 +4,14 @@ description: 本开发分支已强制加密,部署前检查凭据、配置和 --- -v18 尚未发布。本开发分支已移除非加密运行和旧格式写入,启动必须提供显式字段密钥与至少 32 字符的 JWT。本文的 `upgrade:*` 工具尚未发布,已发布的 v17.0.4 不包含它们。最低准备版 tag、SDK、CLI 和主题版本会在实际发布验证后补齐;不要直接在现有镜像上运行不存在的命令。 +v18 尚未发布。本开发分支已移除非加密运行和旧格式写入,生产启动必须提供显式字段密钥与至少 32 字符的 JWT。源码 dev 可自动生成本地字段密钥,见下方说明。本文的 `upgrade:*` 工具尚未发布,已发布的 v17.0.4 不包含它们。最低准备版 tag、SDK、CLI 和主题版本会在实际发布验证后补齐;不要直接在现有镜像上运行不存在的命令。 ## 计划改变什么 | 范围 | 当前 v17 | v18 计划 | | --- | --- | --- | -| 凭据写入 | v17.0.4 可分阶段启用 V2,有临时明文逃生口 | **本开发分支已生效**:显式 hex64 字段密钥、JWT 至少 32 字符、固定 V2;旧关闭配置报错 | +| 凭据写入 | v17.0.4 可分阶段启用 V2,有临时明文逃生口 | **本开发分支已生效**:生产显式 hex64 字段密钥、dev 可自动生成本地密钥;JWT 至少 32 字符、固定 V2;旧关闭配置报错 | | 普通摘要、精读 GET | 可能隐式生成 | 只读,无结果 `data: null`;生成显式触发 | | 非流式执行入口 | 旧 GET 可用 | 备份、清缓存、测试邮件与速记表态用 POST;GET SSE 保留 | | 配置与参数 | v17 保留旧配置、排序与别名兼容 | **本分支已生效**:发布前迁移旧配置,正常服务只认新结构,PATCH 拒绝未知声明字段;数字排序和评论别名移除仍在后续阶段 | @@ -30,7 +30,7 @@ v18 尚未发布。本开发分支已移除非加密运行和旧格式写入, ### 启动配置与旧开关 -- `MX_ENCRYPT_KEY` 或 `ENCRYPT_KEY` 必须是显式的 64 位十六进制密钥;不再回退到机器 ID,也不再派生短密钥。只有从未加密的新实例才用 `openssl rand -hex 32` 生成首个字段密钥并持久保存。 +- 生产环境的 `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 仍可读取并迁移。 @@ -39,6 +39,12 @@ v18 尚未发布。本开发分支已移除非加密运行和旧格式写入, 历史字段解密固定为旧默认 AES-256-ECB。曾使用其他旧算法的实例,必须先在旧版本中使用原密钥和原算法将相关凭据转换为 V2,并验证凭据可用后再升级。删除配置项不会转换自定义算法密文。原算法和密钥仍须随旧快照单独保管,以便在隔离的旧版本环境恢复。 +### 源码开发环境 + +dev 未提供字段密钥时,首次启动自动生成随机密钥,以仅所有者可读写的 `0600` 权限保存到当前工作目录的 `tmp/dev-encryption.key`;重启和多个开发进程复用同一密钥。显式 CLI、YAML 或环境变量密钥始终优先。已有本地密钥损坏或显式密钥无效时会报错,不会自动覆盖。字段仍使用 AES-256-GCM 加密,JWT 长度要求不变。 + +本地密钥须和开发数据库一同保留;删除 `tmp` 也会删除它。生产构建不会生成或读取这个默认密钥,即使设置 `NODE_ENV=development`。独立升级检查仍读取环境配置;检查开发库或将它迁到生产前,须显式配置原本地密钥,不能重新生成。 + 源码工作区中的命令(需要本分支代码和已完成 schema 迁移的数据库): ```bash diff --git a/public/agent-skills/mix-space-expert.md b/public/agent-skills/mix-space-expert.md index 4b4bdb7..030c0a8 100644 --- a/public/agent-skills/mix-space-expert.md +++ b/public/agent-skills/mix-space-expert.md @@ -89,4 +89,6 @@ docker compose up -d - For code questions, reference the actual file paths in the monorepo - When troubleshooting, start with the most common cause first ### Version preparation + +For the unreleased v18 source development server, omitting the field key generates a private random key in `tmp/dev-encryption.key` relative to the working directory. It persists across restarts and concurrent workers; explicit keys take precedence and invalid keys are never replaced automatically. Field encryption and JWT requirements remain. Preserve the local key with the development database. Release builds never use this fallback, even with `NODE_ENV=development`; standalone diagnostics still need the effective key in environment configuration. Supply the original local key explicitly when checking or promoting a development database. Core v17.0.4 retains a temporary plaintext escape hatch only with encryption and V2 both disabled; existing ciphertext still requires its original key. v18 is not released. The upcoming build uses mandatory release-phase `migrate` for configuration, credentials and AI metrics, plus read-only `upgrade:check` and file-based maintenance JWT rotation. There is no separate prepare command, preparation marker or credential-migration activation flag. The v18 credential migration runs even if the historical opt-in backfill was recorded. v18 removes and rejects `ENCRYPT_ALGORITHM`, CLI `--encrypt_algorithm` and YAML `encrypt_algorithm`, even with old default values. Field writes are fixed to AES-256-GCM; historical field reads are fixed to AES-256-ECB. Instances with a custom legacy algorithm must convert affected credentials to V2 on the prior version with the original algorithm and key, and verify them before upgrading. Keep original settings for isolated old-snapshot recovery. v17.0.4 does not contain the new tools. Do not invent a minimum preparation tag. Refer to `/docs/migrate/v18-preparation`. Never replace an existing key with a random one to solve a decryption error.