feat: publish official resources atomically

Fixes #9

Fixes #11
This commit is contained in:
2026-07-14 00:10:39 +08:00
parent 45aba2fb2b
commit 2ff99ae9e3
10 changed files with 1055 additions and 63 deletions
@@ -120,15 +120,15 @@
工作方式:
1. 检查 URL 是否属于官方 host。
2. 把 URL 映射到本地输出路径
3. 读取输出目录下的 `official-download-manifest.json`
2. 把 URL 映射到当前工作根目录下的本地输出路径;在自动更新服务中,读侧根目录是 `current` 指向的 versioned 目录,写侧根目录是 staging
3. 读取工作根目录下的 `official-download-manifest.json`
4. 已存在目标文件只有在本地清单中的相对路径、size、BLAKE3 都匹配时才跳过;`.zip` 文件还必须通过 ZIP central directory / local header 结构校验。
5. 缺少清单、清单不匹配、ZIP 结构无效或文件损坏时重新下载。
6. `TableCatalog.bytes``BundlePackingInfo.bytes``MediaCatalog.bytes` 总是刷新并用官方 `.hash` 强校验;该 `.hash``xxHash32(seed=0)` 的十进制文本。
7. `catalog_*.hash` 当前只作为 Addressables catalog 变更标记,不作为 zip/JSON 内容校验算法;Unity Addressables/SBP builder 对 JSON/bin catalog 使用 `HashingMethods.Calculate` 生成 `Hash128` 文本,运行时用它判断 remote catalog cache 是否过期,它不能套用 seed catalog 的 `xxHash32` 规则。
8. 官方 seed `.hash` 校验失败会让当前下载失败,并移除对应 data/hash URL 的本地 manifest 条目,避免失败产物在下一轮被本地 BLAKE3 audit 误判为健康缓存。
9. 存在 `.part` 临时文件时通过 `curl --continue-at -` 尝试断点续传。
10. 新下载写入 `.part`,成功并通过必要校验后原子 rename 到最终路径;断点续传后的 `.zip` 如果结构无效,会删除 `.part` 并重新全量下载。
10. 新下载写入 `.part`,成功并通过必要校验后原子 rename 到 staging 内最终路径;断点续传后的 `.zip` 如果结构无效,会删除 `.part` 并重新全量下载。
11. 成功下载后更新本地下载清单。
12. 记录最终文件大小、本次传输字节数、官方 hash 校验数和执行状态。
13. 非官方 URL 直接拒绝。
@@ -190,10 +190,12 @@
6. 每轮都会基于最新 seed catalog 构建当前 pull plan,并检查输出目录是否已有当前 plan 的 manifest 条目或目标文件。
7. 如果远端 snapshot 未变化但输出目录没有任何当前 plan 的本地资源,仍按首次运行处理并执行全量拉取。
8. 远端无变化且本地已有资源时执行 download manifest audit,检查路径、size、BLAKE3 和 ZIP 结构。
9. 远端变化、本地 audit 发现 repair_needed首次空目录运行时,下载并校验官方 URL
10. 下载成功后写回新的 snapshot
9. 远端变化、本地 audit 发现 repair_needed,首次空目录运行,或缺少 `current` 原子发布指针时,进入下载/发布流程
10. 下载先写入 `<output>/.staging/<id>`;若已有 active release,会先 seed staging 以复用已验证文件
11. 下载、manifest、本地 BLAKE3、ZIP 和官方 `.hash` 校验完成后写入新的 snapshot。
12. 将 staging rename 为 `<output>/versions/<id>`,再原子替换 `<output>/current` symlink 指向该 versioned 目录。
该入口不安装、不执行官方启动器,也不读取生产外的本地客户端目录。Rust 正式 binary `bat` 支持单次运行、`--watch` 常驻模式、`--daemon` 后台模式,以及 `status``stop``restart``reload``logs``refresh``verify``repair``doctor``clean-stable` 管理命令。`--daemon` 会在后台状态目录下创建 `bat.sock`,使用 Unix socket JSON-RPC 作为 live control plane`bat.pid``bat-status.json``bat-daemon.log` 是快照、诊断和兼容 fallback;`bat-control.lock` 串行化控制命令,并在 stale/corrupt 时由下一次控制命令或 `clean-stable` 恢复。PID、status、log 和控制锁文件创建时使用私有权限,读取和写入时不跟随 symlink。`status``stop``logs``reload` 和默认形态的 `refresh` 优先走 RPC`reload` 会唤醒或排队 watch 循环重新自动发现并强制刷新,`restart` 才负责重启进程或替换启动参数。后台 daemon 管理某个资源目录时,前台 `run/watch/refresh/repair` 不允许直接写入同一目录;默认形态 `refresh` 会通过 RPC 触发后台刷新。正常情况下默认每 1 小时执行一次检查;每天北京时间(UTC+8)`03:00``16:00``18:00` 会中断普通 sleep 并强制执行一次自动刷新,该轮注入 `force=true`。远端和本地一致时静默等待下次检查,不一致时自动下载或 repair。下载、发现或校验失败时不等待完整正常周期,默认 60 秒后重试;如果固定时间强制刷新失败,会保留 pending force 并按失败重试周期继续重试,可用 `--error-retry``--error-retry-seconds` 调整。默认资源输出目录是 `./bat-resources`,默认后台状态目录是 `/tmp/bat-pid`,二者通过 `--output``--state-dir` 分别配置。单次运行仍保留为核心幂等路径,systemd service、容器或 Go 进程可以只负责守护该常驻进程;cron/systemd timer 调单次模式只是可选集成方式。项目是否热更新、热重载或重启进程,由上层业务集成决定。
该入口不安装、不执行官方启动器,也不读取生产外的本地客户端目录。Rust 正式 binary `bat` 支持单次运行、`--watch` 常驻模式、`--daemon` 后台模式,以及 `status``stop``restart``reload``logs``refresh``verify``repair``doctor``clean-stable` 管理命令。`--daemon` 会在后台状态目录下创建 `bat.sock`,使用 Unix socket JSON-RPC 作为 live control plane`bat.pid``bat-status.json``bat-daemon.log` 是快照、诊断和兼容 fallback;`bat-events.jsonl` 是带轮转的结构化 JSONL 事件日志;`bat-control.lock` 串行化控制命令,并在 stale/corrupt 时由下一次控制命令或 `clean-stable` 恢复。`bat-status.json``status` 子命令包含最后成功时间、下次检查时间、最后错误摘要、当前阶段和当前下载 URL 进度。PID、status、log 和控制锁文件创建时使用私有权限,读取和写入时不跟随 symlink。`status``stop``logs``reload` 和默认形态的 `refresh` 优先走 RPC`reload` 会唤醒或排队 watch 循环重新自动发现并强制刷新,`restart` 才负责重启进程或替换启动参数。后台 daemon 管理某个资源目录时,前台 `run/watch/refresh/repair` 不允许直接写入同一目录;默认形态 `refresh` 会通过 RPC 触发后台刷新。正常情况下默认每 1 小时执行一次检查;每天北京时间(UTC+8)`03:00``16:00``18:00` 会中断普通 sleep 并强制执行一次自动刷新,该轮注入 `force=true`。远端和本地一致时静默等待下次检查,不一致时自动下载或 repair。下载、发现或校验失败时不等待完整正常周期,默认 60 秒后重试;如果固定时间强制刷新失败,会保留 pending force 并按失败重试周期继续重试,可用 `--error-retry``--error-retry-seconds` 调整。默认资源输出目录是 `./bat-resources`,默认后台状态目录是 `/tmp/bat-pid`,二者通过 `--output``--state-dir` 分别配置。单次运行仍保留为核心幂等路径,systemd service、容器或 Go 进程可以只负责守护该常驻进程;cron/systemd timer 调单次模式只是可选集成方式。项目是否热更新、热重载或重启进程,由上层业务集成决定。
对应实现主要在:
@@ -231,7 +233,7 @@ Linux 生产路径:
- pull plan 会同时包含 discovery URLs 和 content URLs
- 全量样本下是 `2` 个 discovery URL + `5` 个内容 URL = `7` 个 URL
- `OfficialUpdateService` 能持久化 v2 snapshot,并在远端 marker 内容变化时触发下载决策
- `bat` 默认向 stderr 输出 `BlueArchiveToolkit` ASCII banner 和 progress logstdout 默认输出人类可读摘要;支持 `--json` 输出稳定 JSON,支持 `--no-progress` 关闭进度日志,支持 `--no-banner` 只关闭横幅,支持 `--watch --interval 1h --error-retry 60s` 常驻运行,支持 `--daemon` Unix socket JSON-RPC 控制、`status``stop``restart``reload``logs``refresh --force``verify``repair``doctor``clean-stable`,非 dry-run 使用 `.official-sync.lock` 防止并发写资源目录,控制命令使用 `bat-control.lock` 防止并发状态修改
- `bat` 默认向 stderr 输出 `BlueArchiveToolkit` ASCII banner 和 progress logstdout 默认输出人类可读摘要;支持 `--json` 输出稳定 JSON,支持 `--no-progress` 关闭进度日志,支持 `--no-banner` 只关闭横幅,支持 `--watch --interval 1h --error-retry 60s` 常驻运行,支持 `--daemon` Unix socket JSON-RPC 控制、`status``stop``restart``reload``logs``refresh --force``verify``repair``doctor``clean-stable`,非 dry-run 使用 `.official-sync.lock` 防止并发写资源目录,控制命令使用 `bat-control.lock` 防止并发状态修改,资源发布使用 `.staging``versions``current` 原子切换,daemon 写 `bat-events.jsonl` 结构化日志并在 `status` 中暴露下载进度和调度状态
- `OfficialUpdateService` 能读写 `official-bootstrap-cache.json`,并支持默认开启的 `audit_local` / `repair` CLI 行为
- 下载层能在本地文件 size/BLAKE3/path、ZIP 结构或 manifest 不匹配时重新下载
- 官方 seed `.hash` mismatch 会导致下载失败,而不是降级为本地 BLAKE3 猜测