mirror of
https://github.com/Yuyi-Oak/BlueArchiveToolkit.git
synced 2026-07-21 22:11:26 +08:00
feat: add official full pull smoke
Emit runtime download progress and validation summaries for official sync. Add repeatable real-network full pull smoke runbook and script. Fixes #4 Fixes #6
This commit is contained in:
+3
-1
@@ -21,7 +21,9 @@
|
||||
- 新增官方同步路径安全边界:拒绝危险输出目录和 snapshot 路径逃逸,下载目标、manifest、daemon PID/status/log/control 文件不跟随 symlink,daemon 状态文件默认使用 `0600` 权限
|
||||
- 新增官方资源原子发布布局:非 dry-run 下载先进入 `.staging/<id>`,校验和 manifest/snapshot 写入完成后发布到 `versions/<id>`,再原子切换 `current` symlink
|
||||
- 新增 daemon 可观测性:`bat-events.jsonl` 结构化 JSONL 日志、日志轮转、status 中的当前下载进度、最后成功时间、下次检查时间和最后错误摘要
|
||||
- 新增运行时下载与校验 progress log:总体下载进度、单文件开始/完成状态、官方 `.hash`、本地 BLAKE3、需修复项和 ZIP 结构校验摘要
|
||||
- 新增官方资源同步生产部署模板:release binary symlink 路径、systemd unit、运行用户、日志位置、升级和回滚流程
|
||||
- 新增真实官方网络全量拉取 smoke:`scripts/official-full-pull-smoke.sh`、`make official-smoke` 和 `docs/guides/official-full-pull-smoke.md`
|
||||
- 新增官方资源下载校验:官方 URL 拒绝、`.part` 续传、重试、本地 size+BLAKE3、官方 seed `.hash` 校验
|
||||
- 新增 Addressables 当前真实形态 fixture/golden 测试
|
||||
- 新增 SQLite Resource Repository 和粗粒度 FFI JSON 接口
|
||||
@@ -31,7 +33,7 @@
|
||||
- [x] 实现 CAS 存储引擎
|
||||
- [x] 实现 Rust 官方资源同步核心链路
|
||||
- [ ] 实现 Go CLI 最小可用入口
|
||||
- [ ] 记录真实官方网络全量下载 smoke test
|
||||
- [x] 固化真实官方网络全量下载 smoke test 文档和可重复命令
|
||||
- [ ] 实现 AssetBundle 解析器
|
||||
- [ ] 实现翻译系统
|
||||
- [ ] 实现 Patch 引擎
|
||||
|
||||
+9
-8
@@ -1,9 +1,9 @@
|
||||
# BlueArchiveToolkit 当前工作区状态
|
||||
|
||||
- **更新时间**:2026-07-11
|
||||
- **更新时间**:2026-07-14
|
||||
- **状态来源**:本地工作区盘点、代码验证和最新提交
|
||||
- **状态分支**:`experiment`
|
||||
- **最新已推送功能提交**:`2bdc55a feat: support cancellable official sync`
|
||||
- **最新已推送功能提交**:以当前 `git log --oneline -1` 为准
|
||||
- **权威计划**:`PROJECT_PLAN.md`
|
||||
|
||||
---
|
||||
@@ -26,7 +26,7 @@ Rust 侧官方日服资源链路已经从实验验证推进到正式入口:
|
||||
10. 官方同步会拒绝危险输出目录、路径逃逸和现有 symlink 路径组件;下载目标、`.part`、manifest、snapshot、PID、status、log 和控制锁文件不会跟随 symlink,daemon 状态类文件默认以 `0600` 权限创建。
|
||||
11. `bat status` 会显示最后成功时间、下次检查时间、最后错误摘要、当前阶段、当前下载 URL 进度、结构化日志路径和轮转日志路径。
|
||||
|
||||
仍需明确:这不是完整产品完成。Go CLI 最小入口、完整 AssetBundle 解析、Patch、翻译系统、API Server 和 Web 仍是后续工作;真实官方网络全量下载 smoke test 尚未记录在仓库文档中。
|
||||
仍需明确:这不是完整产品完成。Go CLI 最小入口、完整 AssetBundle 解析、Patch、翻译系统、API Server 和 Web 仍是后续工作;真实官方网络全量拉取 smoke 已固化为可重复脚本和 runbook,真实大文件产物与运行报告默认保存在 `/tmp` 隔离目录,不纳入 Git。
|
||||
|
||||
---
|
||||
|
||||
@@ -36,6 +36,7 @@ Rust 侧官方日服资源链路已经从实验验证推进到正式入口:
|
||||
- `PROJECT_PLAN.md`:最终目标、里程碑和近期任务。
|
||||
- `DOCS_INDEX.md`:文档阅读顺序和索引。
|
||||
- `docs/guides/official-resource-test-pull.md`:官方资源拉取与自动更新用户指南。
|
||||
- `docs/guides/official-full-pull-smoke.md`:真实官方全量拉取 smoke runbook 和可重复命令。
|
||||
- `docs/architecture/official-resource-backend.md`:官方资源后端设计和审核说明。
|
||||
- `docs/reports/CURRENT_GAPS.md`:当前缺口和关闭顺序。
|
||||
|
||||
@@ -134,7 +135,7 @@ Rust 侧官方日服资源链路已经从实验验证推进到正式入口:
|
||||
待完成:
|
||||
|
||||
- 将官方同步下载结果作为用户级流程自动导入 CAS + ResourceRepository。
|
||||
- 为真实线上全量下载建立受控 smoke test 记录。
|
||||
- 真实线上全量下载 smoke 已固化为 `scripts/official-full-pull-smoke.sh` 和 `make official-smoke`;实际运行报告由脚本写入隔离输出目录。
|
||||
- 增加更多失败恢复和权限场景测试。
|
||||
|
||||
### `bat-assetbundle`
|
||||
@@ -220,7 +221,7 @@ git status --short
|
||||
|
||||
未执行:
|
||||
|
||||
- 真实官方网络全量下载 smoke test。
|
||||
- 本次状态更新未执行真实官方网络全量下载 smoke;该流程已由 `docs/guides/official-full-pull-smoke.md` 和 `scripts/official-full-pull-smoke.sh` 固化,运行后会在隔离目录生成 `SMOKE_REPORT.md`。
|
||||
- Go CLI 端到端测试,因为 Go CLI 尚未实现。
|
||||
- Web/API 测试,因为 Web/API 尚未实现。
|
||||
|
||||
@@ -257,7 +258,7 @@ cargo run -p bat-infrastructure --bin bat -- \
|
||||
2. 官方同步结果接入 CAS + ResourceRepository 的用户级工作流。
|
||||
3. AssetBundle UnityFS 基础解析。
|
||||
4. Addressables parser 对更多官方 catalog 结构的覆盖。
|
||||
5. 真实官方网络全量下载 smoke test 记录。
|
||||
5. 按 smoke runbook 在具备网络和磁盘窗口的环境中执行真实官方全量拉取,并保留本地报告。
|
||||
6. Patch 和翻译系统仍应后置。
|
||||
|
||||
---
|
||||
@@ -268,11 +269,11 @@ cargo run -p bat-infrastructure --bin bat -- \
|
||||
|
||||
1. 实现 Go CLI 最小框架和 `doctor`。
|
||||
2. 为 `bat` 增加发布型构建/安装说明和服务化验证。
|
||||
3. 补端到端 smoke:dry-run、真实下载到隔离目录、二次运行静默 up-to-date、本地损坏后 repair。
|
||||
3. 按端到端 smoke runbook 执行 dry-run、真实下载到隔离目录、二次运行静默 up-to-date、本地损坏后 repair。
|
||||
4. 开始 AssetBundle parser 的 UnityFS header/block/directory。
|
||||
|
||||
---
|
||||
|
||||
- **当前总体完成度**:约 22%
|
||||
- **当前基线状态**:Rust 官方资源同步链路已具备可运行闭环;产品级 CLI/API/Web 仍未完成。
|
||||
- **下一工程里程碑**:Go CLI 最小可用 + 官方同步端到端 smoke + AssetBundle 解析起步。
|
||||
- **下一工程里程碑**:Go CLI 最小可用 + 官方同步结果接入 CAS/ResourceRepository + AssetBundle 解析起步。
|
||||
|
||||
+11
-7
@@ -1,6 +1,6 @@
|
||||
# BlueArchiveToolkit 文档索引
|
||||
|
||||
- **更新时间**:2026-07-06
|
||||
- **更新时间**:2026-07-14
|
||||
- **说明**:本索引用于快速定位当前权威文档和历史资料。
|
||||
|
||||
---
|
||||
@@ -12,6 +12,7 @@
|
||||
- `CURRENT_STATUS.md`:当前工作区真实状态。
|
||||
- `docs/reports/CURRENT_GAPS.md`:当前实现缺口和关闭顺序。
|
||||
- `docs/guides/official-resource-test-pull.md`:官方资源拉取与自动更新用户指南。
|
||||
- `docs/guides/official-full-pull-smoke.md`:真实官方全量拉取 smoke runbook 和可重复命令。
|
||||
- `docs/architecture/official-resource-backend.md`:官方资源后端职责、工作原理和审核说明。
|
||||
- `CHANGELOG.md`:版本变更记录。
|
||||
- `CLAUDE.md`:长期开发约束和项目要求。
|
||||
@@ -26,6 +27,7 @@
|
||||
- `docs/guides/deployment.md`:部署指南。
|
||||
- `deployments/systemd/`:官方资源同步生产 systemd unit 和环境文件示例。
|
||||
- `docs/guides/official-resource-test-pull.md`:官方资源拉取与自动更新用户指南。
|
||||
- `docs/guides/official-full-pull-smoke.md`:真实官方全量拉取 smoke runbook 和可重复命令。
|
||||
- `docs/reports/current-stage-prepush.md`:当前阶段说明与推送前核查记录。
|
||||
- `docs/reports/current-status-handoff.md`:给下一次对话使用的当前进度交接说明。
|
||||
- `docs/guides/baseline.md`:稳定工程基线指南。
|
||||
@@ -73,11 +75,12 @@
|
||||
1. `CURRENT_STATUS.md`
|
||||
2. `PROJECT_PLAN.md`
|
||||
3. `docs/guides/official-resource-test-pull.md`
|
||||
4. `docs/architecture/official-resource-backend.md`
|
||||
5. `docs/reports/CURRENT_GAPS.md`
|
||||
6. `docs/guides/baseline.md`
|
||||
7. `docs/architecture/README.md`
|
||||
8. `docs/guides/development.md`
|
||||
4. `docs/guides/official-full-pull-smoke.md`
|
||||
5. `docs/architecture/official-resource-backend.md`
|
||||
6. `docs/reports/CURRENT_GAPS.md`
|
||||
7. `docs/guides/baseline.md`
|
||||
8. `docs/architecture/README.md`
|
||||
9. `docs/guides/development.md`
|
||||
|
||||
---
|
||||
|
||||
@@ -92,12 +95,13 @@
|
||||
- CAS V1:原子写入、BLAKE3 校验、引用计数、GC、并发测试和损坏检测。
|
||||
- 文档整理和路线图重制。
|
||||
- Rust 官方资源同步闭环:`bat`、`--auto-discover`、`--watch`、`--daemon`、Unix socket JSON-RPC 后台控制、`status`、`stop`、`restart`、`reload`、`refresh`、`logs`、`verify`、`repair`、`doctor`、`clean-stable`、北京时间固定强制刷新、snapshot、manifest audit/repair、官方 seed `.hash` 校验。
|
||||
- 真实官方网络全量拉取 smoke 已固化为 `scripts/official-full-pull-smoke.sh` 和 `make official-smoke`,默认写入 `/tmp` 隔离目录并输出本地运行报告。
|
||||
- `bat` 运行时 progress log 已覆盖总体下载进度、单文件下载进度和校验结果摘要。
|
||||
- Addressables 当前真实形态 fixture/golden 覆盖。
|
||||
- SQLite Resource Repository 和粗粒度 FFI JSON 接口。
|
||||
|
||||
优先待办:
|
||||
|
||||
- 落地 Go CLI 最小可用入口。
|
||||
- 记录真实官方网络全量下载 smoke test。
|
||||
- 将官方同步结果接入 CAS + ResourceRepository 的用户级流程。
|
||||
- 开始 AssetBundle UnityFS 解析。
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.PHONY: help build test clean check fmt lint install dev docker-build docker-up docker-down
|
||||
.PHONY: help build test clean check fmt lint install dev docker-build docker-up docker-down official-smoke
|
||||
|
||||
# 默认目标
|
||||
.DEFAULT_GOAL := help
|
||||
@@ -67,6 +67,10 @@ bench: ## 运行性能基准测试
|
||||
echo "$(YELLOW)No Go packages yet, skipping Go benchmarks...$(NC)"; \
|
||||
fi
|
||||
|
||||
official-smoke: ## 运行真实官方全量拉取 smoke(默认写入 /tmp 隔离目录)
|
||||
@echo "$(BLUE)Running official full pull smoke...$(NC)"
|
||||
./scripts/official-full-pull-smoke.sh
|
||||
|
||||
# ============================================================================
|
||||
# 代码质量
|
||||
# ============================================================================
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
- 真实 Patch apply/diff。
|
||||
- Translation Memory、Glossary、AI Provider。
|
||||
- API Server、SDK、Web 管理后台。
|
||||
- 真实官方网络全量下载 smoke test 尚未在本仓库记录。
|
||||
|
||||
详细状态见:
|
||||
|
||||
@@ -33,6 +32,7 @@
|
||||
- [文档索引](DOCS_INDEX.md)
|
||||
- [当前缺口清单](docs/reports/CURRENT_GAPS.md)
|
||||
- [官方资源拉取与自动更新指南](docs/guides/official-resource-test-pull.md)
|
||||
- [官方全量拉取 Smoke Runbook](docs/guides/official-full-pull-smoke.md)
|
||||
- [官方资源后端说明](docs/architecture/official-resource-backend.md)
|
||||
|
||||
---
|
||||
@@ -111,7 +111,18 @@ cargo run -p bat-infrastructure --bin bat -- clean-stable
|
||||
|
||||
`verify` 会以只读方式检查当前官方计划、`current` 指向的 active release 中 download manifest 的 size+BLAKE3、ZIP 结构,以及本地已有官方 seed `.bytes/.hash` 对的 xxHash32;发现缺失、远端变化或本地损坏会返回非 0。`repair` 会在异常资源存在时复用当前同步链路重新下载必要文件。`clean-stable` 只清理 `.part`、临时状态文件、失效或损坏的 PID/锁/socket,不删除正式资源。
|
||||
|
||||
CLI 默认启动时会向 stderr 打印 `BlueArchiveToolkit` ASCII banner,并继续把阶段进度日志写到 stderr,例如自动发现、拉取 catalog、audit、下载第 N/总数个 URL 等;命令结果默认以人类可读摘要写到 stdout。需要给上层程序保留稳定结构化输出时加 `--json --no-progress`,只想关闭横幅但保留日志时可加 `--no-banner`。
|
||||
CLI 默认启动时会向 stderr 打印 `BlueArchiveToolkit` ASCII banner,并继续把阶段进度日志写到 stderr,例如自动发现、拉取 catalog、audit、总体下载进度、单文件下载进度、校验结果摘要、snapshot 和 publish;命令结果默认以人类可读摘要写到 stdout。需要给上层程序保留稳定结构化输出时加 `--json --no-progress`,只想关闭横幅但保留日志时可加 `--no-banner`。
|
||||
|
||||
真实官方网络全量拉取 smoke 已固化为可重复命令,默认使用 `/tmp/bat-official-smoke-<UTC timestamp>/` 隔离目录,不会写入已有客户端、生产目录或开发机人工维护资源目录:
|
||||
|
||||
```bash
|
||||
scripts/official-full-pull-smoke.sh
|
||||
|
||||
# 或
|
||||
make official-smoke
|
||||
```
|
||||
|
||||
该 smoke 会执行 dry-run plan、首次全量拉取、二次 `up_to_date` 检查、本地文件破坏后的 `repair`、repair 后 `verify`,并在 `report/SMOKE_REPORT.md` 记录命令、输出目录、active release、文件数量、release 大小和被破坏文件。大型官方资源文件不纳入 Git。
|
||||
|
||||
生产资源输出目录必须使用独立目录,不要指向已有客户端目录,也不要指向 `/home/wanye/D/BlueArchive` 这类人工维护或开发资源目录。需要覆盖默认位置时,用 `--output <资源目录>`;需要覆盖后台状态目录时,用 `--state-dir <状态目录>`。
|
||||
|
||||
@@ -162,7 +173,7 @@ BlueArchiveToolkit/
|
||||
2. 补齐 AssetBundle UnityFS header/block/directory 解析。
|
||||
3. 扩展 Addressables catalog 解析覆盖,继续用真实形态 fixture/golden 锁定行为。
|
||||
4. 将官方同步结果接入 CAS + ResourceRepository 的用户级工作流。
|
||||
5. 执行并记录一次真实官方网络全量下载 smoke test。
|
||||
5. 按 smoke runbook 在具备网络和磁盘窗口的环境中执行真实官方全量拉取,并保留本地报告。
|
||||
|
||||
不建议在 Go CLI、资源解析和文本提取基础能力完成前优先开发 Web UI。
|
||||
|
||||
|
||||
@@ -233,12 +233,12 @@ Linux 生产路径:
|
||||
- pull plan 会同时包含 discovery URLs 和 content URLs
|
||||
- 全量样本下是 `2` 个 discovery URL + `5` 个内容 URL = `7` 个 URL
|
||||
- `OfficialUpdateService` 能持久化 v2 snapshot,并在远端 marker 内容变化时触发下载决策
|
||||
- `bat` 默认向 stderr 输出 `BlueArchiveToolkit` ASCII banner 和 progress log,stdout 默认输出人类可读摘要;支持 `--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` 中暴露下载进度和调度状态
|
||||
- `bat` 默认向 stderr 输出 `BlueArchiveToolkit` ASCII banner 和 progress log,stdout 默认输出人类可读摘要;progress log 覆盖总体下载进度、单文件开始/完成状态和校验结果摘要;支持 `--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 猜测
|
||||
- 非官方 URL 会在 fetch/download 入口被拒绝
|
||||
- 尚未在仓库中记录真实官方网络全量下载 smoke test
|
||||
- 真实官方网络全量下载 smoke 已固化为 `scripts/official-full-pull-smoke.sh`、`make official-smoke` 和 `docs/guides/official-full-pull-smoke.md`;大型资源文件和运行报告默认保留在 `/tmp` 隔离目录,不纳入 Git
|
||||
|
||||
相关验证主要来自:
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ git check-ignore -v Cargo.lock CLAUDE.md
|
||||
CAS V1 和 Rust 官方同步闭环完成后,下一阶段优先推进:
|
||||
|
||||
1. Go CLI 的 `doctor` 和基础命令框架。
|
||||
2. 真实官方网络全量下载 smoke 记录。
|
||||
2. 按 `docs/guides/official-full-pull-smoke.md` 执行真实官方网络全量下载 smoke,并保留隔离目录报告。
|
||||
3. 官方同步结果接入 CAS + ResourceRepository。
|
||||
4. AssetBundle UnityFS 解析。
|
||||
|
||||
@@ -99,6 +99,7 @@ CAS V1 和 Rust 官方同步闭环完成后,下一阶段优先推进:
|
||||
2. `CURRENT_STATUS.md`
|
||||
3. `docs/reports/CURRENT_GAPS.md`
|
||||
4. `docs/guides/official-resource-test-pull.md`
|
||||
5. `docs/architecture/official-resource-backend.md`
|
||||
6. `docs/architecture/adr/0001-engine-and-application-boundaries.md`
|
||||
7. `docs/architecture/adr/0002-cas-v1-design-boundary.md`
|
||||
5. `docs/guides/official-full-pull-smoke.md`
|
||||
6. `docs/architecture/official-resource-backend.md`
|
||||
7. `docs/architecture/adr/0001-engine-and-application-boundaries.md`
|
||||
8. `docs/architecture/adr/0002-cas-v1-design-boundary.md`
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
# 官方全量拉取 Smoke Runbook
|
||||
|
||||
本文档固定真实官方网络全量拉取 smoke 的可重复命令。该流程会访问官方日服 HTTP 资源并下载完整 Windows + Android 资源集合,输出目录必须是隔离目录。
|
||||
|
||||
## 目标
|
||||
|
||||
验证当前 `bat` 能在真实官方网络环境下完成:
|
||||
|
||||
1. `--auto-discover --dry-run --plan`
|
||||
2. 首次全量拉取并发布到 `current`
|
||||
3. 第二次运行返回 `up_to_date`
|
||||
4. 人工破坏 active release 中一个资源文件
|
||||
5. `repair` 检出并重新下载损坏文件
|
||||
6. `verify` 在 repair 后通过
|
||||
|
||||
## 一条命令
|
||||
|
||||
默认写入 `/tmp/bat-official-smoke-<UTC timestamp>/`:
|
||||
|
||||
```bash
|
||||
scripts/official-full-pull-smoke.sh
|
||||
```
|
||||
|
||||
也可以通过 Makefile 执行:
|
||||
|
||||
```bash
|
||||
make official-smoke
|
||||
```
|
||||
|
||||
脚本默认会先构建 release binary:
|
||||
|
||||
```bash
|
||||
cargo build --release -p bat-infrastructure --bin bat
|
||||
```
|
||||
|
||||
## 输出
|
||||
|
||||
默认目录结构:
|
||||
|
||||
```text
|
||||
/tmp/bat-official-smoke-<timestamp>/
|
||||
resources/
|
||||
current -> versions/<id>
|
||||
versions/<id>/
|
||||
.staging/
|
||||
state/
|
||||
report/
|
||||
01-dry-run-plan.command.txt
|
||||
01-dry-run-plan.stdout.json
|
||||
01-dry-run-plan.stderr.log
|
||||
02-first-full-pull.command.txt
|
||||
02-first-full-pull.stdout.json
|
||||
02-first-full-pull.stderr.log
|
||||
03-second-up-to-date.command.txt
|
||||
03-second-up-to-date.stdout.json
|
||||
03-second-up-to-date.stderr.log
|
||||
04-repair-after-damage.command.txt
|
||||
04-repair-after-damage.stdout.json
|
||||
04-repair-after-damage.stderr.log
|
||||
05-verify-after-repair.command.txt
|
||||
05-verify-after-repair.stdout.json
|
||||
05-verify-after-repair.stderr.log
|
||||
damaged-file.txt
|
||||
damaged-file.before.txt
|
||||
SMOKE_REPORT.md
|
||||
```
|
||||
|
||||
`damaged-file.before.txt` 只记录被破坏文件的路径、大小和 sha256,不复制原始资源文件。`SMOKE_REPORT.md` 记录本次 smoke 的实际输出目录、active release、文件数量、release 大小和被破坏的文件。大型资源文件不进入 Git。
|
||||
|
||||
脚本会在关键步骤后自动检查:
|
||||
|
||||
- 首次全量拉取 stderr log 包含总体下载进度、单文件进度和校验结果。
|
||||
- 二次运行 stdout JSON 包含 `update_status=up_to_date`。
|
||||
- repair stdout JSON 包含 `command=repair` 和 `status=completed`。
|
||||
- repair stderr log 包含总体下载进度、单文件进度和校验结果。
|
||||
- repair 后 verify stdout JSON 包含 `healthy=true`。
|
||||
|
||||
## 环境变量
|
||||
|
||||
- `BAT_SMOKE_ROOT`:覆盖默认根目录。
|
||||
- `BAT_SMOKE_OUTPUT`:覆盖资源发布根目录。
|
||||
- `BAT_SMOKE_STATE_DIR`:覆盖 daemon/status 目录。
|
||||
- `BAT_SMOKE_REPORT_DIR`:覆盖报告目录。
|
||||
- `BAT_BIN`:使用已有 `bat` binary。
|
||||
- `BAT_SMOKE_SKIP_BUILD=1`:跳过 release 构建。
|
||||
- `BAT_SMOKE_ALLOW_NON_TMP=1`:允许 `BAT_SMOKE_OUTPUT` 指向非 `/tmp` 路径。只可用于确认隔离的测试目录。
|
||||
|
||||
## 安全边界
|
||||
|
||||
脚本默认拒绝非 `/tmp` 输出目录,且拒绝复用非空输出目录。不要把输出目录指向已有客户端、官方启动器安装目录、生产资源目录或开发机人工维护资源目录。
|
||||
|
||||
## 成功判定
|
||||
|
||||
脚本全部步骤退出码为 0 即表示 smoke 通过。重点检查:
|
||||
|
||||
- `03-second-up-to-date.stdout.json` 中 `update_status` 为 `up_to_date`。
|
||||
- `04-repair-after-damage.stdout.json` 中 repair 完成,且有重新下载或修复行为。
|
||||
- `05-verify-after-repair.stdout.json` 中 `healthy` 为 `true`。
|
||||
- `02-first-full-pull.stderr.log` 和 `04-repair-after-damage.stderr.log` 中包含下载总体进度、单文件进度和校验结果日志。
|
||||
@@ -258,13 +258,26 @@ cargo run -p bat-infrastructure --bin bat -- \
|
||||
--error-retry 60s
|
||||
```
|
||||
|
||||
默认平台是 `Windows,Android`,无需显式传 `--platforms`;只有要覆盖默认平台时才传。`--interval` 是正常检查周期,默认 `1h`;watch/daemon 模式还会在每天北京时间(UTC+8)`03:00`、`16:00`、`18:00` 强制执行一次自动刷新,该轮会注入 `force=true`,并且会中断普通 interval 的 sleep。`--error-retry` 是下载、发现或校验失败后的重试周期,默认 `60s`,也可以用 `--error-retry-seconds 60`。CLI 默认启动时向 stderr 打印 `BlueArchiveToolkit` ASCII banner,并把阶段进度日志写到 stderr,包括自动发现、server-info、marker、catalog、audit、download、snapshot 和 publish 阶段;daemon 还会写 `bat-events.jsonl` 结构化日志并按大小轮转。命令结果默认以人类可读摘要写到 stdout。需要纯机器输出时加 `--json --no-progress`,需要显式开启进度日志则用 `--progress`;只想关闭横幅但保留日志时可加 `--no-banner`。错误时 stderr 输出 JSON error,watch 模式下错误 JSON 的 `next_retry_seconds` 使用失败重试周期;如果未关闭 progress,错误 JSON 前可能已有 banner 和进度日志。普通错误 exit `1`,资源目录锁冲突 exit `75`,`verify` 或 `doctor` 发现问题也返回非 0。
|
||||
默认平台是 `Windows,Android`,无需显式传 `--platforms`;只有要覆盖默认平台时才传。`--interval` 是正常检查周期,默认 `1h`;watch/daemon 模式还会在每天北京时间(UTC+8)`03:00`、`16:00`、`18:00` 强制执行一次自动刷新,该轮会注入 `force=true`,并且会中断普通 interval 的 sleep。`--error-retry` 是下载、发现或校验失败后的重试周期,默认 `60s`,也可以用 `--error-retry-seconds 60`。CLI 默认启动时向 stderr 打印 `BlueArchiveToolkit` ASCII banner,并把阶段进度日志写到 stderr,包括自动发现、server-info、marker、catalog、audit、download、snapshot 和 publish 阶段;download 阶段会输出总体下载进度和单文件开始/完成状态,audit 阶段会输出官方 `.hash`、本地 BLAKE3、需修复项和 ZIP 结构校验结果摘要。daemon 还会写 `bat-events.jsonl` 结构化日志并按大小轮转。命令结果默认以人类可读摘要写到 stdout。需要纯机器输出时加 `--json --no-progress`,需要显式开启进度日志则用 `--progress`;只想关闭横幅但保留日志时可加 `--no-banner`。错误时 stderr 输出 JSON error,watch 模式下错误 JSON 的 `next_retry_seconds` 使用失败重试周期;如果未关闭 progress,错误 JSON 前可能已有 banner 和进度日志。普通错误 exit `1`,资源目录锁冲突 exit `75`,`verify` 或 `doctor` 发现问题也返回非 0。
|
||||
|
||||
生产可以直接运行 `--watch`,也可以用 `--daemon` 后台运行,或者用 systemd service、容器或 Go 进程守护它。cron/systemd timer 仍可调用单次模式,但不再是 Rust 自动更新的唯一方式。项目是否热更新、热重载或重启进程,由上层业务集成决定。生产资源目录应使用独立输出目录,不要指向现有客户端或人工维护的资源目录;上层读取资源时应读取 `--output/current`,不要读取 `.staging` 或 `versions` 中未切换的目录。非 dry-run 每轮会创建 `--output/.official-sync.lock`,防止并发写同一资源目录;live daemon 还会阻止前台写命令直接修改它正在管理的同一目录。
|
||||
|
||||
需要只做探测时可以加 `--dry-run`。需要关闭本地 audit 或 repair 时可以显式使用 `--no-audit-local` 或 `--no-repair`,但生产同步默认应保持开启。
|
||||
|
||||
## 6. 例外输入
|
||||
## 6. 真实全量 smoke
|
||||
|
||||
真实官方网络全量拉取 smoke 已固化为 runbook 和脚本:
|
||||
|
||||
```bash
|
||||
scripts/official-full-pull-smoke.sh
|
||||
|
||||
# 或
|
||||
make official-smoke
|
||||
```
|
||||
|
||||
默认输出在 `/tmp/bat-official-smoke-<UTC timestamp>/`,脚本会执行 dry-run plan、首次全量拉取、二次 `up_to_date`、本地文件破坏后的 `repair`、repair 后 `verify`,并检查 stderr progress log 中存在总体下载进度、单文件进度和校验结果摘要。完整说明见 `docs/guides/official-full-pull-smoke.md`。
|
||||
|
||||
## 7. 例外输入
|
||||
|
||||
可接受的 `server-info` 输入是:
|
||||
|
||||
@@ -275,7 +288,7 @@ cargo run -p bat-infrastructure --bin bat -- \
|
||||
|
||||
如果没有显式 `server-info` 输入,必须手动加 `--auto-discover` 才会走官方 metadata / `GameMainConfig` 辅助发现链路。不要在 Linux 生产任务里依赖已安装启动器或本地客户端目录。
|
||||
|
||||
## 7. 代码入口
|
||||
## 8. 代码入口
|
||||
|
||||
当前可用的用户入口:
|
||||
|
||||
|
||||
@@ -319,26 +319,30 @@
|
||||
|
||||
- GitHub Actions 或等价 CI 执行 format、lint、test、build。
|
||||
|
||||
### G-018:真实官方网络全量下载 smoke test 未记录
|
||||
### G-018:真实官方网络全量下载 smoke test 已固化为可重复命令
|
||||
|
||||
状态:**未关闭**
|
||||
状态:**已关闭(已固化可重复 smoke 命令;真实下载产物不纳入 Git)**
|
||||
|
||||
现象:
|
||||
历史现象:
|
||||
|
||||
- 本地测试覆盖 mock、fixture、synthetic import 和 CLI 参数。
|
||||
- 尚未在隔离输出目录记录一次真实官方网络全量下载。
|
||||
- 曾缺少真实官方网络全量下载的固定 runbook 和可重复命令。
|
||||
|
||||
影响:
|
||||
处理结果:
|
||||
|
||||
- 无法用文档证明当前 `bat` 在真实官方网络环境下完成首次下载、二次静默 up-to-date 和本地损坏 repair。
|
||||
- 新增 `scripts/official-full-pull-smoke.sh`,默认在 `/tmp/bat-official-smoke-<UTC timestamp>/` 下创建隔离资源目录、状态目录和报告目录。
|
||||
- 新增 `make official-smoke` 统一入口。
|
||||
- 新增 `docs/guides/official-full-pull-smoke.md`,记录目标、命令、输出结构、环境变量、安全边界和成功判定。
|
||||
- smoke 流程覆盖 dry-run plan、首次全量拉取、二次 `up_to_date`、人工破坏 active release 文件后的 `repair`、repair 后 `verify`。
|
||||
- 脚本会检查二次 `up_to_date`、repair 完成、verify `healthy=true`,并检查首次拉取和 repair 的 stderr log 中存在总体下载进度、单文件进度和校验结果日志。
|
||||
- 运行报告 `SMOKE_REPORT.md` 记录实际输出目录、active release、文件数量、release 大小和被破坏文件;大型官方资源文件保留在隔离输出目录,不纳入 Git。
|
||||
|
||||
验收:
|
||||
|
||||
- 使用独立目录执行 dry-run。
|
||||
- 执行真实首次下载,不指向任何现有客户端或人工维护资源目录。
|
||||
- 二次运行返回 `up_to_date`,watch 模式在 up-to-date 时静默。
|
||||
- 人工破坏一个本地文件后,audit 检出并 repair。
|
||||
- 记录命令、摘要、输出目录和未纳入 Git 的大文件位置。
|
||||
- 使用 `scripts/official-full-pull-smoke.sh` 或 `make official-smoke`。
|
||||
- 默认输出目录必须是独立 `/tmp` 目录;非 `/tmp` 路径需要显式设置 `BAT_SMOKE_ALLOW_NON_TMP=1`,且输出目录必须为空。
|
||||
- 脚本退出码为 0 即表示 runbook 验收通过。
|
||||
- 真实网络执行需要外部网络和足够磁盘空间;本仓库只保存 runbook、脚本和测试,不保存官方大文件。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# 当前阶段说明
|
||||
|
||||
- **更新时间**:2026-07-06
|
||||
- **更新时间**:2026-07-14
|
||||
- **状态分支**:`experiment`
|
||||
- **最新功能提交**:`789402c feat: add official resource sync pipeline`
|
||||
- **最新功能提交**:以当前 `git log --oneline -1` 为准
|
||||
- **用途**:当前阶段交付说明、推送前核查和下一步依据。
|
||||
|
||||
## 1. 当前目标
|
||||
@@ -28,6 +28,8 @@
|
||||
- `OfficialResourcePullService` 的官方 URL 拒绝、`.part` 续传、重试、本地 manifest、官方 seed `.hash` 校验。
|
||||
- `OfficialUpdateService` 的 auto-discover、bootstrap cache、v2 snapshot、remote marker diff、本地 audit/repair。
|
||||
- `bat` one-shot 和 `--watch`。
|
||||
- `bat` 运行时 progress log 覆盖总体下载进度、单文件进度和校验结果摘要。
|
||||
- 真实官方网络全量下载 smoke 已固化为 `scripts/official-full-pull-smoke.sh`、`make official-smoke` 和 `docs/guides/official-full-pull-smoke.md`。
|
||||
- `bat-ffi` 的 Manifest inspect 和 sync plan JSON API。
|
||||
- `SqliteResourceRepository`。
|
||||
|
||||
@@ -35,7 +37,6 @@
|
||||
|
||||
- Go CLI 最小可用入口。
|
||||
- 官方同步下载结果自动导入 CAS + ResourceRepository 的用户级流程。
|
||||
- 真实官方网络全量下载 smoke 记录。
|
||||
- 完整 AssetBundle 解析。
|
||||
- Patch、翻译系统、API Server、Web。
|
||||
|
||||
@@ -62,11 +63,11 @@ git status --short
|
||||
|
||||
## 4. 当前结论
|
||||
|
||||
当前 Rust 官方资源同步链路已经具备可运行闭环,但仓库还不是完整产品。下一步不应继续只扩展 example,而应补齐 Go CLI 最小入口、真实网络 smoke、CAS/ResourceRepository 编排和 AssetBundle 解析。
|
||||
当前 Rust 官方资源同步链路已经具备可运行闭环,并已固化真实网络 smoke 的可重复命令,但仓库还不是完整产品。下一步不应继续只扩展 example,而应补齐 Go CLI 最小入口、CAS/ResourceRepository 编排和 AssetBundle 解析。
|
||||
|
||||
## 5. 下一步
|
||||
|
||||
1. 实现 Go CLI:`bat doctor`、`bat sync --help`、`bat official sync --help`。
|
||||
2. 为 `bat` 做真实官方网络 smoke,输出目录必须隔离。
|
||||
2. 按 `docs/guides/official-full-pull-smoke.md` 在具备网络和磁盘窗口的环境中执行真实官方网络 smoke,输出目录必须隔离。
|
||||
3. 将官方同步下载结果接入 CAS + `SqliteResourceRepository`。
|
||||
4. 开始 AssetBundle UnityFS header/block/directory 解析。
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
# 当前进度交接
|
||||
|
||||
- **更新时间**:2026-07-06
|
||||
- **更新时间**:2026-07-14
|
||||
- **状态分支**:`experiment`
|
||||
- **最新功能提交**:`789402c feat: add official resource sync pipeline`
|
||||
- **最新功能提交**:以当前 `git log --oneline -1` 为准
|
||||
- **用途**:给下一次对话快速恢复上下文,优先以当前工作区和已验证结果为准。
|
||||
|
||||
## 1. 现在处在什么阶段
|
||||
|
||||
项目已经完成基础整理、CAS V1 和 Rust 官方资源同步闭环。下一阶段重点是 Go CLI 最小可用、真实官方网络 smoke、同步结果进入 CAS/ResourceRepository,以及 AssetBundle 解析起步。
|
||||
项目已经完成基础整理、CAS V1、Rust 官方资源同步闭环,以及真实官方网络 smoke 的可重复命令固化。下一阶段重点是 Go CLI 最小可用、同步结果进入 CAS/ResourceRepository,以及 AssetBundle 解析起步。
|
||||
|
||||
已确认的事实:
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
- 默认平台是 `Windows + Android`。
|
||||
- 真实下载会维护 `official-download-manifest.json`,并使用 size + BLAKE3、本地 audit/repair 和官方 seed `.hash` 校验。
|
||||
- `--watch` 是 Rust 内部常驻检查模式,正常检查默认 1 小时,失败重试默认 60 秒;外部 systemd/container 只负责守护进程。
|
||||
- `bat` progress log 会输出总体下载进度、单文件进度和校验结果摘要。
|
||||
- 真实官方网络 smoke 已固化为 `scripts/official-full-pull-smoke.sh`、`make official-smoke` 和 `docs/guides/official-full-pull-smoke.md`,默认写入 `/tmp` 隔离目录。
|
||||
|
||||
## 2. 现在不要误解的点
|
||||
|
||||
@@ -34,7 +36,7 @@
|
||||
仍待实现的主线工作:
|
||||
|
||||
1. Go CLI 最小入口:`bat doctor`、`bat sync --help`、`bat official sync --help`。
|
||||
2. 真实官方网络 smoke:dry-run、首次下载、二次 up-to-date、本地损坏 repair。
|
||||
2. 按 smoke runbook 在具备网络和磁盘窗口的环境中执行真实官方网络 smoke,并保留隔离目录报告。
|
||||
3. 官方同步下载结果进入 CAS + `SqliteResourceRepository` 的用户级流程。
|
||||
4. Addressables parser 覆盖更多真实 catalog 结构。
|
||||
5. AssetBundle UnityFS header/block/directory 解析。
|
||||
@@ -57,4 +59,5 @@
|
||||
- `DOCS_INDEX.md`
|
||||
- `docs/reports/CURRENT_GAPS.md`
|
||||
- `docs/guides/official-resource-test-pull.md`
|
||||
- `docs/guides/official-full-pull-smoke.md`
|
||||
- `docs/architecture/official-resource-backend.md`
|
||||
|
||||
@@ -1048,6 +1048,10 @@ impl OfficialUpdateService {
|
||||
};
|
||||
|
||||
if !should_download {
|
||||
progress(OfficialUpdateProgress::new(
|
||||
"audit",
|
||||
verification_progress_message(&report.verification_summary),
|
||||
));
|
||||
progress(OfficialUpdateProgress::new(
|
||||
"finish",
|
||||
"资源已是最新;无需下载",
|
||||
@@ -1138,6 +1142,16 @@ impl OfficialUpdateService {
|
||||
let final_audit = staging_fetcher
|
||||
.audit_local_manifest(&pull_plan)
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
let final_verification_summary = OfficialVerificationSummary::new(
|
||||
final_audit.manifest_blake3_verified_count(),
|
||||
final_audit.repair_needed_count(),
|
||||
pull_report.official_hash_verified_count(),
|
||||
final_audit.zip_structure_verified_count(),
|
||||
);
|
||||
progress(OfficialUpdateProgress::new(
|
||||
"audit",
|
||||
verification_progress_message(&final_verification_summary),
|
||||
));
|
||||
progress(OfficialUpdateProgress::new(
|
||||
"snapshot",
|
||||
format!("写入快照 {}", staging_snapshot_path.display()),
|
||||
@@ -1175,12 +1189,7 @@ impl OfficialUpdateService {
|
||||
report.official_seed_hash_verified_count = pull_report.official_hash_verified_count();
|
||||
report.local_manifest_verified_count = final_audit.verified_count();
|
||||
report.local_manifest_repair_needed_count = final_audit.repair_needed_count();
|
||||
report.verification_summary = OfficialVerificationSummary::new(
|
||||
final_audit.manifest_blake3_verified_count(),
|
||||
final_audit.repair_needed_count(),
|
||||
pull_report.official_hash_verified_count(),
|
||||
final_audit.zip_structure_verified_count(),
|
||||
);
|
||||
report.verification_summary = final_verification_summary;
|
||||
report.snapshot_written = Some(final_snapshot_path);
|
||||
|
||||
progress(OfficialUpdateProgress::new(
|
||||
@@ -1356,7 +1365,13 @@ fn progress_from_pull_event(event: OfficialResourcePullProgress) -> OfficialUpda
|
||||
match event.kind {
|
||||
OfficialResourcePullProgressKind::Started => OfficialUpdateProgress::new(
|
||||
"download",
|
||||
format!("({}/{}) 开始 {}", event.index, event.total, event.url),
|
||||
format!(
|
||||
"下载进度:总体 {}/{} ({:.1}%);单文件开始 URL={}",
|
||||
event.index,
|
||||
event.total,
|
||||
download_progress_percent(event.index, event.total),
|
||||
event.url
|
||||
),
|
||||
)
|
||||
.with_download_progress(&event),
|
||||
OfficialResourcePullProgressKind::Finished => {
|
||||
@@ -1364,9 +1379,10 @@ fn progress_from_pull_event(event: OfficialResourcePullProgress) -> OfficialUpda
|
||||
OfficialUpdateProgress::new(
|
||||
"download",
|
||||
format!(
|
||||
"({}/{}) 完成 状态={} 文件字节={} 本轮传输字节={} {}",
|
||||
"下载进度:总体 {}/{} ({:.1}%);单文件完成 状态={} 文件字节={} 本轮传输字节={} URL={}",
|
||||
event.index,
|
||||
event.total,
|
||||
download_progress_percent(event.index, event.total),
|
||||
status,
|
||||
event.bytes.unwrap_or(0),
|
||||
event.transferred_bytes.unwrap_or(0),
|
||||
@@ -1378,6 +1394,24 @@ fn progress_from_pull_event(event: OfficialResourcePullProgress) -> OfficialUpda
|
||||
}
|
||||
}
|
||||
|
||||
fn download_progress_percent(index: usize, total: usize) -> f64 {
|
||||
if total == 0 {
|
||||
100.0
|
||||
} else {
|
||||
(index as f64 / total as f64) * 100.0
|
||||
}
|
||||
}
|
||||
|
||||
fn verification_progress_message(summary: &OfficialVerificationSummary) -> String {
|
||||
format!(
|
||||
"校验结果:官方 .hash={};本地 BLAKE3 通过={};本地需修复={};ZIP 结构通过={}",
|
||||
summary.official_hash_verified_count,
|
||||
summary.local_manifest_blake3_verified_count,
|
||||
summary.local_manifest_repair_needed_count,
|
||||
summary.zip_structure_verified_count
|
||||
)
|
||||
}
|
||||
|
||||
fn localized_pull_status(status: crate::OfficialResourcePullStatus) -> &'static str {
|
||||
match status {
|
||||
crate::OfficialResourcePullStatus::SkippedExisting => "已复用",
|
||||
|
||||
@@ -229,6 +229,17 @@ fn official_update_first_run_pulls_without_pre_audit() {
|
||||
assert!(events
|
||||
.iter()
|
||||
.any(|event| { event.stage == "decision" && event.message.contains("首次拉取=true") }));
|
||||
assert!(events.iter().any(|event| {
|
||||
event.stage == "download"
|
||||
&& event.message.contains("下载进度:总体")
|
||||
&& event.message.contains("单文件")
|
||||
}));
|
||||
assert!(events.iter().any(|event| {
|
||||
event.stage == "audit"
|
||||
&& event.message.contains("校验结果:官方 .hash=5")
|
||||
&& event.message.contains("本地 BLAKE3 通过=19")
|
||||
&& event.message.contains("ZIP 结构通过=6")
|
||||
}));
|
||||
|
||||
let published = report.published_version_path.as_ref().unwrap();
|
||||
assert!(published.exists());
|
||||
@@ -280,6 +291,12 @@ fn official_update_second_run_audits_existing_resources_before_reuse() {
|
||||
assert!(events.iter().any(|event| {
|
||||
event.stage == "audit" && event.message.contains("审计本地下载 manifest")
|
||||
}));
|
||||
assert!(events.iter().any(|event| {
|
||||
event.stage == "audit"
|
||||
&& event.message.contains("校验结果:官方 .hash=0")
|
||||
&& event.message.contains("本地 BLAKE3 通过=19")
|
||||
&& event.message.contains("ZIP 结构通过=6")
|
||||
}));
|
||||
assert!(events.iter().any(|event| {
|
||||
event.stage == "decision" && event.message.contains("首次拉取=false")
|
||||
}));
|
||||
|
||||
Executable
+184
@@ -0,0 +1,184 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
cd "${REPO_ROOT}"
|
||||
|
||||
timestamp="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||
SMOKE_ROOT="${BAT_SMOKE_ROOT:-/tmp/bat-official-smoke-${timestamp}}"
|
||||
OUTPUT_DIR="${BAT_SMOKE_OUTPUT:-${SMOKE_ROOT}/resources}"
|
||||
STATE_DIR="${BAT_SMOKE_STATE_DIR:-${SMOKE_ROOT}/state}"
|
||||
REPORT_DIR="${BAT_SMOKE_REPORT_DIR:-${SMOKE_ROOT}/report}"
|
||||
BAT_BIN="${BAT_BIN:-${REPO_ROOT}/target/release/bat}"
|
||||
|
||||
fail() {
|
||||
printf 'error: %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
real_output="$(realpath -m "${OUTPUT_DIR}")"
|
||||
case "${real_output}" in
|
||||
/tmp/*) ;;
|
||||
*)
|
||||
if [[ "${BAT_SMOKE_ALLOW_NON_TMP:-}" != "1" ]]; then
|
||||
fail "BAT_SMOKE_OUTPUT must be under /tmp by default; set BAT_SMOKE_ALLOW_NON_TMP=1 only for an isolated test path"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ -e "${OUTPUT_DIR}" ]] && [[ -n "$(find "${OUTPUT_DIR}" -mindepth 1 -maxdepth 1 -print -quit 2>/dev/null)" ]]; then
|
||||
fail "output directory is not empty: ${OUTPUT_DIR}"
|
||||
fi
|
||||
|
||||
mkdir -p "${OUTPUT_DIR}" "${STATE_DIR}" "${REPORT_DIR}"
|
||||
|
||||
if [[ "${BAT_SMOKE_SKIP_BUILD:-}" != "1" ]]; then
|
||||
cargo build --release -p bat-infrastructure --bin bat
|
||||
fi
|
||||
|
||||
[[ -x "${BAT_BIN}" ]] || fail "bat binary is not executable: ${BAT_BIN}"
|
||||
|
||||
write_command_file() {
|
||||
local name="$1"
|
||||
shift
|
||||
local command_file="${REPORT_DIR}/${name}.command.txt"
|
||||
: >"${command_file}"
|
||||
printf '$' >>"${command_file}"
|
||||
printf ' %q' "$@" >>"${command_file}"
|
||||
printf '\n' >>"${command_file}"
|
||||
}
|
||||
|
||||
run_step() {
|
||||
local name="$1"
|
||||
shift
|
||||
printf '==> %s\n' "${name}"
|
||||
write_command_file "${name}" "$@"
|
||||
"$@" >"${REPORT_DIR}/${name}.stdout.json" 2>"${REPORT_DIR}/${name}.stderr.log"
|
||||
}
|
||||
|
||||
require_contains() {
|
||||
local path="$1"
|
||||
local needle="$2"
|
||||
local description="$3"
|
||||
if ! grep -Fq "${needle}" "${path}"; then
|
||||
fail "${description}; expected '${needle}' in ${path}"
|
||||
fi
|
||||
}
|
||||
|
||||
run_step "01-dry-run-plan" \
|
||||
"${BAT_BIN}" \
|
||||
--auto-discover \
|
||||
--output "${OUTPUT_DIR}" \
|
||||
--state-dir "${STATE_DIR}" \
|
||||
--dry-run \
|
||||
--plan \
|
||||
--json \
|
||||
--no-progress
|
||||
|
||||
run_step "02-first-full-pull" \
|
||||
"${BAT_BIN}" \
|
||||
--auto-discover \
|
||||
--output "${OUTPUT_DIR}" \
|
||||
--state-dir "${STATE_DIR}" \
|
||||
--json \
|
||||
--progress \
|
||||
--no-banner
|
||||
|
||||
require_contains "${REPORT_DIR}/02-first-full-pull.stderr.log" "下载进度:总体" "first pull did not emit total download progress"
|
||||
require_contains "${REPORT_DIR}/02-first-full-pull.stderr.log" "单文件" "first pull did not emit single-file download progress"
|
||||
require_contains "${REPORT_DIR}/02-first-full-pull.stderr.log" "校验结果:" "first pull did not emit verification summary"
|
||||
|
||||
run_step "03-second-up-to-date" \
|
||||
"${BAT_BIN}" \
|
||||
--auto-discover \
|
||||
--output "${OUTPUT_DIR}" \
|
||||
--state-dir "${STATE_DIR}" \
|
||||
--json \
|
||||
--no-progress
|
||||
|
||||
require_contains "${REPORT_DIR}/03-second-up-to-date.stdout.json" '"update_status": "up_to_date"' "second run did not report up_to_date"
|
||||
|
||||
current_link="${OUTPUT_DIR}/current"
|
||||
[[ -e "${current_link}" ]] || fail "current release symlink/path was not created: ${current_link}"
|
||||
current_dir="$(realpath "${current_link}")"
|
||||
|
||||
damage_file="$(find "${current_dir}" -type f \
|
||||
! -name 'official-sync-snapshot.json' \
|
||||
! -name 'official-download-manifest.json' \
|
||||
-print -quit)"
|
||||
[[ -n "${damage_file}" ]] || fail "no resource file found to damage under ${current_dir}"
|
||||
|
||||
printf '%s\n' "${damage_file}" >"${REPORT_DIR}/damaged-file.txt"
|
||||
{
|
||||
printf 'path: %s\n' "${damage_file}"
|
||||
printf 'bytes: %s\n' "$(wc -c <"${damage_file}" | tr -d ' ')"
|
||||
printf 'sha256: %s\n' "$(sha256sum "${damage_file}" | awk '{print $1}')"
|
||||
} >"${REPORT_DIR}/damaged-file.before.txt"
|
||||
printf 'BAT official smoke intentional corruption\n' >"${damage_file}"
|
||||
|
||||
run_step "04-repair-after-damage" \
|
||||
"${BAT_BIN}" \
|
||||
repair \
|
||||
--auto-discover \
|
||||
--output "${OUTPUT_DIR}" \
|
||||
--state-dir "${STATE_DIR}" \
|
||||
--json \
|
||||
--progress \
|
||||
--no-banner
|
||||
|
||||
require_contains "${REPORT_DIR}/04-repair-after-damage.stdout.json" '"command": "repair"' "repair command report missing"
|
||||
require_contains "${REPORT_DIR}/04-repair-after-damage.stdout.json" '"status": "completed"' "repair command did not complete"
|
||||
require_contains "${REPORT_DIR}/04-repair-after-damage.stderr.log" "下载进度:总体" "repair did not emit total download progress"
|
||||
require_contains "${REPORT_DIR}/04-repair-after-damage.stderr.log" "单文件" "repair did not emit single-file download progress"
|
||||
require_contains "${REPORT_DIR}/04-repair-after-damage.stderr.log" "校验结果:" "repair did not emit verification summary"
|
||||
|
||||
run_step "05-verify-after-repair" \
|
||||
"${BAT_BIN}" \
|
||||
verify \
|
||||
--auto-discover \
|
||||
--output "${OUTPUT_DIR}" \
|
||||
--state-dir "${STATE_DIR}" \
|
||||
--json \
|
||||
--no-progress
|
||||
|
||||
require_contains "${REPORT_DIR}/05-verify-after-repair.stdout.json" '"healthy": true' "verify after repair did not report healthy=true"
|
||||
|
||||
file_count="$(find "${current_dir}" -type f | wc -l | tr -d ' ')"
|
||||
current_size="$(du -sh "${current_dir}" | awk '{print $1}')"
|
||||
report_file="${REPORT_DIR}/SMOKE_REPORT.md"
|
||||
|
||||
cat >"${report_file}" <<EOF
|
||||
# Official Full Pull Smoke Report
|
||||
|
||||
- generated_at_utc: ${timestamp}
|
||||
- repo: ${REPO_ROOT}
|
||||
- bat: ${BAT_BIN}
|
||||
- output: ${OUTPUT_DIR}
|
||||
- state_dir: ${STATE_DIR}
|
||||
- current_release: ${current_dir}
|
||||
- current_release_size: ${current_size}
|
||||
- current_release_file_count: ${file_count}
|
||||
- damaged_file: ${damage_file}
|
||||
|
||||
## Steps
|
||||
|
||||
1. dry-run with full URL plan: 01-dry-run-plan
|
||||
2. first full pull: 02-first-full-pull
|
||||
3. second up-to-date check: 03-second-up-to-date
|
||||
4. intentional local file damage and repair: 04-repair-after-damage
|
||||
5. verify after repair: 05-verify-after-repair
|
||||
|
||||
Each step has:
|
||||
|
||||
- <step>.command.txt
|
||||
- <step>.stdout.json
|
||||
- <step>.stderr.log
|
||||
|
||||
The large resource output is intentionally outside Git and remains under:
|
||||
|
||||
\`${OUTPUT_DIR}\`
|
||||
EOF
|
||||
|
||||
printf '\nSmoke report: %s\n' "${report_file}"
|
||||
printf 'Resource output: %s\n' "${OUTPUT_DIR}"
|
||||
Reference in New Issue
Block a user