Files
qinglong/docs/operations/ql3-local-data-directory-adoption.md

341 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# QingLong 2.x Data Directory 接管
本流程先为完整 QingLong 2.x `data` 目录生成一个只读、确定性、按 Profile 有界的 3.0 接管计划,再把审核过的资产 no-replace
暂存、转换并稳定校验。它不会删除或修改源文件,也不替代 [SQLite 接管流程](./ql3-local-sqlite-adoption.md)。
## 1. 前置条件
- 使用最终运行 QingLong 的同一个 POSIX 用户执行;
- `dataRoot` 必须是绝对、canonical、非根、非 symlink 的目录;
- `dataRoot` 必须由当前 UID 拥有,且 group/world 不可写;
- command file 继续遵守 `ql3-adoption` 的当前 UID、canonical、单链接、`0600` 私有文件要求;
- 生产盘点建议先停止 2.x writer。若文件或目录在盘点中变化,命令会失败关闭,不会给出部分成功计划。
- stage 前必须已经完成 SQLite inspect、stage、verify 与 activation,并保留五个绝对路径和 `activationDigest`
- `deploymentRoot``stagingRoot` 的父目录必须是当前 UID 拥有的 canonical `0700` 目录;`stagingRoot` 必须尚不存在且位于
`deploymentRoot` 内、`dataRoot` 外。
- transform 前创建私有 canonical `0700` transformation parent`transformationRoot` 必须尚不存在,位于 `deploymentRoot` 内,且与
`dataRoot``stagingRoot` 互不包含。
## 2. 执行 inspect
Edge/低配路由设备:
```json
{
"schemaVersion": 1,
"operation": "local-data-directory.adoption.inspect",
"options": {
"dataRoot": "/opt/qinglong/data",
"profile": "edge"
}
}
```
Standalone/NAS
```json
{
"schemaVersion": 1,
"operation": "local-data-directory.adoption.inspect",
"options": {
"dataRoot": "/opt/qinglong/data",
"profile": "standalone"
}
}
```
```sh
chmod 0600 /secure/operator/ql3-data-directory-inspect.json
ql3-adoption run --command-file /secure/operator/ql3-data-directory-inspect.json
```
命令成功时返回 `status=inspected``qinglong3-legacy-data-directory-adoption-plan`。保存完整 JSON 和 `planDigest`,但不要把它
当作已经授权复制的 manifest。
## 3. 审核处置矩阵
| 类别 | 默认处置 | 说明 |
| --- | --- | --- |
| `config` | `transform` | 迁移到 3.0 配置模型,不原样覆盖 |
| `scripts` | `copy_reviewed` | 审核兼容性和安全后复制 |
| `db` | `transform` | 主 SQLite 走独立流程;审核 Keyv 和 sidecar |
| `upload` | `copy_reviewed` | 审核文件类型与容量后复制 |
| `ssh.d` | `transform` | 进入后续私有凭据交付,不进入普通归档 |
| `log``syslog``bak` | `retain_external` | 作为历史/恢复资产另行保留 |
| `repo``raw``dep_cache``deps` | `regenerate` | 在目标架构重新 checkout/安装 |
`recursive_content` 类别会稳定读取普通单链接文件并生成摘要;`root_only` 类别只检查类别根,不扫描内部内容。
## 4. 解释 assessment
- `reviewable`:没有未知顶层条目,也没有 unsafe 条目;仍需 operator 审核分类、数量、字节和数据库识别计数;
- `manual_review`:出现未知顶层条目或 unsafe 条目;不得继续自动 staging;
- `broadReadableEntries > 0`:存在 group/world 可读条目,是敏感性审核信号,但不等同于可被外部修改;
- `activeSqliteSidecars > 0`:发现 `database.sqlite-*``keyv.sqlite-*` 活跃 sidecar,先停止 writer 并完成 SQLite
checkpoint/一致性处置后重新盘点;
- `primaryDatabaseFiles` 应按生产布局识别 `db/database.sqlite``legacyKeyValueDatabaseFiles` 识别 `db/keyv.sqlite`
输出不会包含 `dataRoot` 原文、任意文件名或文件内容。未知名称只进入摘要。不要尝试从摘要反推或把摘要当作内容备份。
## 5. Profile 预算
| Profile | 最大条目 | 最大哈希字节 | 单文件上限 | 最大深度 |
| --- | ---: | ---: | ---: | ---: |
| Edge | 8,192 | 512 MiB | 64 MiB | 32 |
| Standalone | 65,536 | 4 GiB | 512 MiB | 64 |
超过任一限制都会以 `LOCAL_DATA_DIRECTORY_ADOPTION_CONFIGURATION_INVALID` 失败。不要为了通过门禁临时改名、删除或排除资产;先保留
现场并决定它应拆分为外部恢复资产、在目标重新生成,还是进入后续人工迁移协议。
## 6. 执行私有 stage
审核 `assessment=reviewable``primaryDatabaseFiles=1`、所有 `activeSqliteSidecars=0` 后,提交完整 plan 与 SQLite activation 双围栏:
```json
{
"schemaVersion": 1,
"operation": "local-data-directory.adoption.stage",
"options": {
"deploymentRoot": "/opt/qinglong3/adoption",
"dataRoot": "/opt/qinglong/data",
"stagingRoot": "/opt/qinglong3/adoption/staging/reviewed-data",
"profile": "edge",
"expectedPlanDigest": "<64-hex-directory-plan-digest>",
"sqlite": {
"sourcePath": "/opt/qinglong/data/db/database.sqlite",
"targetPath": "/opt/qinglong3/adoption/sqlite/qinglong3.sqlite",
"recoveryPath": "/opt/qinglong3/adoption/sqlite/database.pre-ql3.sqlite",
"manifestPath": "/opt/qinglong3/adoption/sqlite/adoption.json",
"activationPath": "/opt/qinglong3/adoption/sqlite/activation.json",
"expectedActivationDigest": "<64-hex-sqlite-activation-digest>"
}
}
}
```
成功结果为 `status=staged`。暂存区固定包含 `payload/copy-reviewed/{scripts,upload}`
`payload/transform-input/{config,db,ssh.d}` 与私有 `manifest.json`;不存在的类别不会被制造。`db/database.sqlite` 及其 sidecar 不复制,
主库恢复继续以 SQLite adoption 的 recovery/target/activation 为权威。日志、备份和缓存也不会进入 payload。
所有目录归一化为 `0700`,所有文件归一化为 `0600`。复制使用 64 KiB 缓冲,并再次执行当前 Profile 的条目、字节、单文件和深度预算。
## 7. 执行稳定 verify
保存 stage 返回的 `manifestDigest`,使用同一组路径和 SQLite activation 执行:
```json
{
"schemaVersion": 1,
"operation": "local-data-directory.adoption.verify",
"options": {
"deploymentRoot": "/opt/qinglong3/adoption",
"dataRoot": "/opt/qinglong/data",
"stagingRoot": "/opt/qinglong3/adoption/staging/reviewed-data",
"profile": "edge",
"expectedManifestDigest": "<64-hex-directory-manifest-digest>",
"sqlite": {
"sourcePath": "/opt/qinglong/data/db/database.sqlite",
"targetPath": "/opt/qinglong3/adoption/sqlite/qinglong3.sqlite",
"recoveryPath": "/opt/qinglong3/adoption/sqlite/database.pre-ql3.sqlite",
"manifestPath": "/opt/qinglong3/adoption/sqlite/adoption.json",
"activationPath": "/opt/qinglong3/adoption/sqlite/activation.json",
"expectedActivationDigest": "<64-hex-sqlite-activation-digest>"
}
}
}
```
verify 会重新验证目录计划、SQLite activation/source/target、清单 exact shape、私有权限和完整 payload 语义摘要。成功结果为
`status=verified`,且 evidence 应与 stage 的低敏 evidence 一致。
## 8. 准备版本化转换模型
只有 stage verify 成功且 operator 已保存 `manifestDigest` 后才执行 transform。`projectId` 是后续 Secret 与 binding 的目标 Project,不能
使用占位值或其他环境的 Project:
```json
{
"schemaVersion": 1,
"operation": "local-data-directory.adoption.transform",
"options": {
"deploymentRoot": "/opt/qinglong3/adoption",
"dataRoot": "/opt/qinglong/data",
"stagingRoot": "/opt/qinglong3/adoption/staging/reviewed-data",
"transformationRoot": "/opt/qinglong3/adoption/transformations/reviewed-data-v1",
"projectId": "<target-project-id>",
"profile": "edge",
"expectedManifestDigest": "<64-hex-directory-manifest-digest>",
"sqlite": {
"sourcePath": "/opt/qinglong/data/db/database.sqlite",
"targetPath": "/opt/qinglong3/adoption/sqlite/qinglong3.sqlite",
"recoveryPath": "/opt/qinglong3/adoption/sqlite/database.pre-ql3.sqlite",
"manifestPath": "/opt/qinglong3/adoption/sqlite/adoption.json",
"activationPath": "/opt/qinglong3/adoption/sqlite/activation.json",
"expectedActivationDigest": "<64-hex-sqlite-activation-digest>"
}
}
}
```
成功返回 `status=prepared``transformationDigest` 和低敏计数/摘要。私有 `model/` 包含 Project ID、环境变量名、SSH alias 和待导入
Secret 明文,必须按高敏迁移材料保护,不能写入日志、提交 Git、上传 issue 或复制进普通备份。
转换规则固定如下:
- 不执行 `config.sh`;简单非空 export 生成 Secret input,非 export setting 退役,复杂/重复行要求人工复核;
- `keyv:authInfo` 淘汰且必须重新签发 credential;`apps/lang` 只与主 SQLite authority reconciliation;未知 Keyv 内容人工复核;
- SSH 私钥和 config 分离;历史 `ProxyCommand``StrictHostKeyChecking no` 不继承,所有 binding 保持 disabled,必须人工核验 host key
- Edge 最多 128 个 Secret draft、256 个 Keyv row/4 MiBStandalone 为 512 个 Secret、2,048 row/16 MiB;单 Secret 16 KiB。
`assessment=manual_required` 不表示数据丢失:原始内容仍在 D-385 staging 中。operator 应审核 `manual-review.json` 和对应原始 snapshot
不得手改 manifest 或把 disabled binding 直接启用。
## 9. 校验转换模型
保存 transform 返回的 `transformationDigest`,在相同 options 上增加该字段并执行:
```json
{
"schemaVersion": 1,
"operation": "local-data-directory.adoption.transform.verify",
"options": {
"deploymentRoot": "/opt/qinglong3/adoption",
"dataRoot": "/opt/qinglong/data",
"stagingRoot": "/opt/qinglong3/adoption/staging/reviewed-data",
"transformationRoot": "/opt/qinglong3/adoption/transformations/reviewed-data-v1",
"projectId": "<target-project-id>",
"profile": "edge",
"expectedManifestDigest": "<64-hex-directory-manifest-digest>",
"expectedTransformationDigest": "<64-hex-transformation-digest>",
"sqlite": {
"sourcePath": "/opt/qinglong/data/db/database.sqlite",
"targetPath": "/opt/qinglong3/adoption/sqlite/qinglong3.sqlite",
"recoveryPath": "/opt/qinglong3/adoption/sqlite/database.pre-ql3.sqlite",
"manifestPath": "/opt/qinglong3/adoption/sqlite/adoption.json",
"activationPath": "/opt/qinglong3/adoption/sqlite/activation.json",
"expectedActivationDigest": "<64-hex-sqlite-activation-digest>"
}
}
}
```
成功返回 `status=verified`evidence 应与 transform 完全相同。verify 会在目标校验前后重新验证 D-385 staging、当前 2.x source 和 SQLite
activation,并检查目标 exact 文件集、私有 mode、Project/profile/path binding、Secret value digest 和完整模型树摘要。
## 10. 受认证提交 prepared model
只有 transform 返回 `assessment=ready`、transform.verify 成功,且目标 Project 已存在 active Owner/Admin RoleBinding 时才能 apply。先准备:
- deployment root 内 `0700` 的 Owner pepper keyring
- deployment root 内 `0600` 的 Owner credential presentation
- deployment root 内 `0600` 的 Local Secret keyring
- 两个不同的 UUID v4`mutationId` 与失败审计 `failureAuditEventId`
credential token 和 Secret key material 不写入 command JSON。提交命令是在 transform.verify options 上增加以下 authority
```json
{
"schemaVersion": 1,
"operation": "local-data-directory.adoption.apply",
"options": {
"deploymentRoot": "/opt/qinglong3/adoption",
"dataRoot": "/opt/qinglong/data",
"stagingRoot": "/opt/qinglong3/adoption/staging/reviewed-data",
"transformationRoot": "/opt/qinglong3/adoption/transformations/reviewed-data-v1",
"projectId": "<target-project-id>",
"profile": "edge",
"expectedManifestDigest": "<64-hex-directory-manifest-digest>",
"expectedTransformationDigest": "<64-hex-transformation-digest>",
"ownerPepperKeyringDirectory": "/opt/qinglong3/adoption/owner-keys",
"credentialFilePath": "/opt/qinglong3/adoption/owner-credential.json",
"secretKeyringPath": "/opt/qinglong3/adoption/local-secret-keyring.json",
"mutationId": "<uuid-v4>",
"failureAuditEventId": "<different-uuid-v4>",
"requestId": "legacy-data-apply-20260821",
"sqlite": {
"sourcePath": "/opt/qinglong/data/db/database.sqlite",
"targetPath": "/opt/qinglong3/adoption/sqlite/qinglong3.sqlite",
"recoveryPath": "/opt/qinglong3/adoption/sqlite/database.pre-ql3.sqlite",
"manifestPath": "/opt/qinglong3/adoption/sqlite/adoption.json",
"activationPath": "/opt/qinglong3/adoption/sqlite/activation.json",
"expectedActivationDigest": "<64-hex-sqlite-activation-digest>"
}
}
}
```
apply 会以 Owner credential 建立强认证,要求目标 Project 的 `secret.manage`,并在一个 SQLite `BEGIN IMMEDIATE` 中提交所有加密 Secret、
逐项 `secret.create` 审计、父 `legacy-data.apply` 审计、disabled model 和 canonical receipt。任何一个目标 Secret 已存在都会回滚整个批次。
成功返回 `status=committed``databaseStatus=inserted` 和低敏 digest/count evidence,不返回 Secret 名称、Project ID 或明文。
成功后 transformation root 只剩 `manifest.json``commit.json``model/` 和其中的明文文件已逻辑覆盖并删除。结果固定声明
`physicalErasureGuaranteed=false`:闪存 FTL、CoW、快照或备份可能保留旧块。需要介质级保证时应使用加密卷并销毁卷密钥或执行设备级擦除。
apply 只把模型持久化为 `activation=disabled`,不会启用 SSH、执行配置、启动任务或切换服务。
## 11. 校验 committed application
保存 apply 返回的 `receiptDigest`。将原命令 operation 改为 `local-data-directory.adoption.apply.verify`,并在 options 增加:
```json
{
"expectedReceiptDigest": "<64-hex-receipt-digest>"
}
```
verify 使用相同强认证和 exact mutation,读取 durable receipt,验证 transformation manifest 与最终 `commit.json`,但不修复中间状态。成功返回
`status=verified``databaseStatus=existing`。receipt、commit、Project/profile/transformation 或 root 文件集有任何漂移都会失败关闭。
## 12. 崩溃残留与恢复
stage 和 transform 都在创建目标根后立即写入 `.incomplete`。只有 payload/model 和 `manifest.json` 都持久化且静态校验通过后才删除。
这些阶段失败或进程崩溃后:
- 不要直接把残留目录当作恢复资产;
- 不要在原路径重试,stage/transform 会 no-replace 拒绝;
- 先保存现场用于诊断,再由 operator 显式移走残留目录,并使用一个新的空路径重试;
- verify 遇到 `.incomplete`、额外文件或缺失文件一律失败关闭。
apply 的数据库 COMMIT 是逻辑提交点。COMMIT 后清理使用 `.commit-incomplete``.reclaiming-model`。若 apply 失败且 transformation root 出现
这些条目:
- 不要重新 transform,也不要手工删除 marker/model
- 查询错误和目标数据库 readiness,保留完整诊断现场;
- 使用完全相同的 command、mutation ID 和 digest 重放 `local-data-directory.adoption.apply`
- exact replay 从 durable receipt 继续回收 model 并重建同一 `commit.json`,不会再次创建 Secret
- 未知文件、marker 漂移、双 model 或 receipt 不一致必须人工调查,不能用 apply.verify 或手改文件绕过。
## 13. 常见失败
- 根目录或条目 group/world 可写:修正 ownership/permission 后重新盘点;
- symlink、硬链接或特殊文件:保留现场,确认来源和目标后人工处置;盘点不会跟随或读取;
- 目录在盘点中变化:停止 2.x writer、同步器、下载器和仓库更新后重试;
- 单文件或总内容超过 Profile 预算:不要改用 `tar` 绕过;为该资产设计独立流式迁移/外部保留流程;
- 未知顶层条目:根据插件或用户资产来源登记明确处置,再进入后续 staging 设计。
- activation 不匹配:重新执行 SQLite verify/activation,不能只替换 digest
- `stagingRoot` 已存在:检查是否为崩溃残留,禁止覆盖或合并;
- stage/verify 后源或目标 drift:停止所有 writer,回到 inspect,生成并重新审核新的 plan。
- `transformationRoot` 已存在或有 `.incomplete`:保留诊断现场,显式移走后选择新的空路径,禁止覆盖或续写;
- Keyv schema、row 或字节超限:不要把数据库当 JSON dump 绕过,先确认版本和未知数据归属;
- `manual_required`:从 D-385 staging 审核相应原始输入;不要执行旧 shell、复用 `authInfo` 或启用旧 SSH config
- transform verify drift:停止后续 apply,保留 D-385 staging 和 transformation root,重新建立新的版本化转换路径。
- credential rejected:核对 credential presentation、pepper keyring、有效期和目标数据库绑定,不要把 token 填进 command JSON
- `APPLICATION_FORBIDDEN`:当前主体不是目标 Project 的 active owner/admin;通过正式 Policy 管理流程修复 RoleBinding 后使用新失败审计 ID重试;
- `ADOPTION_CONFLICT`:目标 Secret、mutation 或 transformation 已存在不一致事实;批次已回滚,禁止删除现有 Secret 强行导入;
- apply 在数据库 COMMIT 后清理失败:重放完全相同的 apply;不要生成新 mutation,否则会与 transformation 唯一约束冲突;
- apply.verify 报终态不完整:它不会恢复;改为重放原 apply。
## 14. 当前边界
本流程已经提供 inspect、私有 stage、版本化 transform、受认证原子 apply、崩溃恢复和三级稳定 verify。它仍不:
- 删除或修改任何源资产;
- 激活已提交的 settings/SSH binding、启动任务或切换服务;
- 把历史日志/备份复制到默认目标,或复用跨架构 repo/dependency cache
- 授权 service-manager/Compose cutover 或 Legacy rollback
- 证明固定物理路由器/NAS 上的耗时、RSS、I/O、磁盘峰值和断电恢复。
apply 成功只证明目标数据库中的 encrypted Secret、disabled model、audit 和 receipt 原子一致,并且 prepared 明文文件已逻辑回收;它仍不是
cutover 或 activation 授权。继续保留原始 2.x data directory、SQLite recovery、D-385 staging、manifest 和 `commit.json`,直到独立部署
lineage 与回滚门完成。