51256335ba62ece937195f1c4e84d7edac32987e
Docker Build and Push / build-image (push) Failing after 39s
- docker-build: 停用 docker system prune --volumes (保住 build 层缓存), 改为 image prune -f - 新增 .dockerignore, node_modules/.git/src-tauri 等不再进 build context - 前端只构建一次: CI 构建 dist 供 OTA 与镜像复用 (PREBUILT_DIST 构建参数) - Dockerfile: npm install → npm ci + BuildKit cache mount - setup-node 开启 npm 缓存; master/android-* 加 concurrency 取消旧 run - android: 只构建 aarch64; tauri target 缓存 key 按 Cargo.lock hash - minisign 改为静态二进制下载, 去掉 apt-get
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.
# iBoard 用于收集和整理经济数据,但是以策略为主体重新组织。 ## 分支规则 ### 分支类型与职责 | 分支 | 职责 | 部署 | 合并方向 | | --- | --- | --- | --- | | `master` | **主分支**(生产分支) | ✅ 唯一部署出口,push 即发布生产 | — | | `dev-*` | **开发集成分支**,如 `dev-0.2` | ❌ 不部署(只跑 CI 校验) | 合到 `master` 后由 master 部署 | | `tmp-*` | **临时分支**,针对单个问题的修改 | ❌ 不部署 | 合到对应 `dev-*` 后删除 | | `android-*` | **Android 外壳构建分支**,如 `android-1.0` | ❌ 不部署,只触发 APK 构建 | 构建前须与 `master` 同步 | > **关于 `main`**:团队约定里的「main 主分支」在本仓库落地为 **`master`**(仓库默认分支即 `master`,不另建 `main`)。 > 下文所有「main 主分支」均指 `master`。 ### 流程 ``` tmp-xxx ──合并──▶ dev-0.2 ──合并──▶ master ──push──▶ 自动部署生产 │ │ │ └──push──▶ 只跑 CI(版本一致性校验),不部署 │ └── 问题解决后删除 android-1.0 ── 从 master 同步代码后 push ──▶ 只触发 APK 构建 / 发布到 Packages ``` 1. **日常开发**:从 `dev-*` 拉 `tmp-*`,改完合回 `dev-*`,`tmp-*` 用完即删。 2. **上线**:`dev-*` 验收通过后合并到 `master`,**推送 `master` 才部署生产**。 3. **打 Android 包**:`android-*` 与 `master` 同步(`git merge master` 或 `git reset --hard origin/master`)后推送, 触发 [android-build.yml](.gitea/workflows/android-build.yml),产物发布到 Gitea Generic Packages。 ### CI 触发范围 | 工作流 | 触发分支 | 动作 | | --- | --- | --- | | [docker-build.yml](.gitea/workflows/docker-build.yml) | **仅 `master`** | 构建前端 + 生成签名 OTA 包 + 构建镜像 + 迁移数据库 + 部署生产 | | [android-build.yml](.gitea/workflows/android-build.yml) | `android-*` | 构建并签名 release APK,上传到 Packages | | [ota-build.yml](.gitea/workflows/ota-build.yml) | 仅手动 `workflow_dispatch` | 单独构建/签发 OTA 包 | | [sync-registry.yml](.gitea/workflows/sync-registry.yml) | 见文件 | 同步注册中心 | > ⚠️ **重要变更(2026-10)**:`dev-*` 分支**已取消** push 自动部署。 > 此前 `dev-*` 与 `master` 共用同一套生产环境,推送开发分支等于直接发布,容易把半成品带上线。 > 现在开发分支的提交只有合并进 `master` 并推送后才会部署。 ## 版本管理 ### 一句话规则 **同一个版本号,APK(原生包)裸写,OTA 热更包多一个 `-ota` 后缀。** ``` 分支 dev-0.2 → 版本 0.2.0 ├─ APK / 桌面安装包 versionName = 0.2.0 (无后缀) ├─ 前端运行时 APP_VERSION = 0.2.0 (无后缀) └─ OTA 热更包 version = 0.2.0-ota (多一个 -ota) ``` 版本号遵循 `主版本.次版本.修订号`,**唯一手动管理点是 `package.json` 的 `version` 字段**(当前 `0.2.0`), 该字段只写裸版本号,**不允许带 `-ota`**——`-ota` 由 `scripts/build-ota.mjs` 打包时自动追加。 版本号必须与所在分支对应:`dev-0.2` 分支上就该是 `0.2.x`。 ### 改版本号时要同步的位置 | 文件 | 字段 | 说明 | | --- | --- | --- | | `package.json` | `version` | **唯一源**,前端 `src/version.js` 与 OTA 打包脚本均读取此值 | | `src-tauri/tauri.conf.json` | `version` | Tauri 应用版本 → **APK 的 versionName**、桌面安装包版本 | | `src-tauri/Cargo.toml` | `[package].version` | Rust 包版本,需与 tauri.conf.json 一致 | | `src-tauri/Cargo.lock` | `name = "iboard"` 条目 | 同上,随 Cargo.toml 一起改 | `scripts/ci/check-version.mjs` 会在 CI 第一段就校验这四处**完全相等**且不含 `-ota`, 并校验 `dev-<major>.<minor>` 分支与版本前缀对应;任何一项不满足直接构建失败(`main`/`master` 只跳过分支校验,一致性照样查)。 ### APK 版本号是怎么来的 `tauri android init` 会按 `src-tauri/tauri.conf.json` 的 `version` 生成 `src-tauri/gen/android/app/tauri.properties`(该目录被 gitignore,CI 每次 `android init` 重新生成): ``` tauri.android.versionName = 0.2.0 # 人类可见版本,与 tauri.conf.json 一致 tauri.android.versionCode = 2000 # 整数,升级判定用 = major*1000000 + minor*1000 + patch ``` 注意: - `versionCode` 必须**单调递增**才能覆盖安装,所以版本号只能往上走(`0.2.0` → 2000 > `0.1.0` → 1000)。 - 本地如果 APK 装完显示旧版本号,是 `src-tauri/gen/android` 缓存没刷新, 删掉该目录重新 `npx tauri android init` 即可。 ### 其它 - `src/version.js` 从 `package.json` 取值,前端"我"页与 OTA 检查接口展示的即此版本,不要另写死版本号。 - OTA 升级包的 `version` = `<package.json version>-ota`(见 `scripts/build-ota.mjs`), 所以版本号错位会直接导致线上 OTA 判新失准。 - **`min_binary_version` 与这里的版本号是两件事**:它是 OTA 包要求的最低原生二进制版本 (CI 里由 `OTA_MIN_BINARY` 显式指定,当前 `0.1.0`)。前端版本升到 0.2.0 时它不应跟着抬, 否则装了 0.1.0 APK 的设备会直接收不到热更。 - `BUILD_ID` 每次编译自动生成(见 `vite.config.js` 的 `__BUILD_ID__`),无需手动维护。 - `ota-package/latest.json` 为运行时产物,不纳入版本管理。 ## 分支与 CI/CD (Gitea Actions) 分支按版本命名:`dev-0.1`、`dev-0.2`…… 对应 `docs/V<版本>/` 下的需求/设计文档。 职责与合并方向见上文「分支规则」。 | 分支 | 触发的 workflow | 行为 | | --- | --- | --- | | `master`(主分支) | `docker-build.yml` | 生产发布:构建前端 → 生成签名 OTA 包 → 打 Docker 镜像 → 数据库自动升级 → 重启容器 `iboard` (宿主 3001) | | `dev-*` | **不触发部署** | 推送只走版本一致性校验;上线须先合并到 `master` | | `tmp-*` | 不触发 | 临时分支,合到 `dev-*` 后删除 | | `android-*` | `android-build.yml` | 打签名 release APK 并上传到 Gitea Generic Package Registry(构建前须与 `master` 同步) | | 手动 | `ota-build.yml` / `sync-registry.yml` | 单独出 OTA 包 / 同步 secrets 到注册中心 | 数据库升级约定: - 唯一入口是 `prisma/migrations/` 下的迁移文件,由 `npx prisma migrate deploy` 在**启新容器之前**执行 (幂等,无新迁移时为 no-op);迁移失败会中断 workflow,不会用新镜像顶掉仍在运行的旧容器。 - 由于只有 `master` 会部署,**迁移真正作用到生产库的唯一时机就是 master 发布**; 开发分支上的迁移不会被自动应用,请在合并到 `master` 前确认可回滚。 - 本地改了 `prisma/schema.prisma` 后:写迁移 SQL → `npx prisma migrate deploy` 验证 → 提交迁移目录。 - 首页主要是放经济文章 - 数据主要是放各种经济数据和图表。 - 策略是放按照经济理论组织的相关数据和图表的对比。 ## 首页 首页显示新闻模块和报警模块的数据,按照时间最新优先,无限滚动,直到数据库中没有数据为止。 ### 新闻模块 增加一个新闻模块,数据来源是每日夜间 3点,通过妙想 MCP 获取国内外最新经济政治新闻,按影响大小排序10条。 存入数据库。 ### 报警模块 World Bank 中的数据只用来对比妙想 MCP 拉取的数据正确性。当同一时间的同一项数据不同时,记录一个报警。 ## 监控 监控分为服务器监控和服务监控两部分。 服务器监控对接 komari,地址:https://komari.honor3.com/ ## 前端 OTA 升级 只升级前端资源 (`dist/`), **不触碰** Tauri 框架/原生层, 不重打 APK. 底层使用 [`tauri-plugin-hotswap`](https://github.com/denniskribl/tauri-plugin-hotswap) (签名/回滚/资源替换全部由插件处理). ### 工作流 ``` 开发者 服务端 npm run build ──┐ scripts/build-ota.mjs │ 生成 iboard-ota.tar.gz + minisign 签名 │ 输出 latest.json (sequence/version/...) ▼ ota-package/ ── 部署到服务器 ──▶ /srv/iboard/ota-package │ ▼ GET /api/ota/check/<seq> 客户端 (Tauri) │ 启动 ─▶ notifyReady() ▼ 我页 ─▶ checkUpdate() ◀──── 200 + manifest (含 url/signature) ─────┘ applyUpdate() ─▶ 下载 tar.gz ─▶ minisign 校验 ─▶ 解压到 AppData/ota-dist location.reload() ─▶ 后续资源从 tauri://localhost 优先命中覆盖包 ``` ### 首次部署 1. **生成签名密钥** (一次性): ```bash # 安装 minisign: Windows (scoop) / macOS (brew) / Linux (apt) node scripts/ota-genkey.mjs # 产物: ota-package/hotswap.{key, pub} ``` 2. **把公钥写入 `src-tauri/tauri.conf.json`** 的 `plugins.hotswap.pubkey` 字段 (脚本会打印). 3. **配置 endpoint** (`src-tauri/tauri.conf.json`): ```json "hotswap": { "endpoint": "https://your-server/api/ota/check/{{current_sequence}}", "pubkey": "RWR...", "require_https": true } ``` 4. **服务端环境变量** `OTA_PACKAGE_DIR` 指向 `ota-package/` 所在目录. ### 发布新版前端 ```bash npm run build # 生成 dist/ node scripts/build-ota.mjs # 打成 tar.gz + 签名 + latest.json # sequence 默认 = Date.now(), 也可 --sequence 43 手动指定 # 也可加 --notes "修复登录按钮" --min-binary 0.1.0 ``` 然后把 `ota-package/` 下两个文件 (`iboard-ota.tar.gz` + `latest.json`) 同步到 服务端 `OTA_PACKAGE_DIR` 指向的目录. sequence 单调递增即可. ### 客户端使用 打开 App 的"我"页, 点 [检查更新] → [立即升级] 即可. 失败会自动回滚到内置 dist. > **注意:** Tauri 2 的 dev 模式 (`tauri dev`) 会代理到 Vite dev server, **OTA 不生效**. > OTA 仅在 release/打包构建中可用, 测试请用 `tauri build` 产物. ### 客户端判新口径(V0.2 新增) "我"页 OTA 组内有一个 **开发版本** 开关(默认关闭),切换"怎么算新版本": | 开关 | 判新依据 | 适用 | | --- | --- | --- | | 关(默认) | **版本号**逐字段比大小(`0.2.0-ota` > `0.1.0` 才算新) | 生产。旧分支被重建(sequence 变大但版本号更小)时不会把设备拉回旧版 | | 开 | **build sequence**(服务端 sequence 比本地大就算新) | 开发联调。每次构建都能推到设备 | - 开关值持久化在设备端 `localStorage.iboard_ota_dev_mode`(`'1'`/`'0'`),默认 `'0'`。 - 版本比较在 `src/utils/version.js`(纯函数,`src/api/ota.js` 统一封装两种模式)。 - **`min_binary_version` 与应用版本是两件事**:前者是前端 bundle 要求的最低原生二进制版本, 当前 APK 原生层仍是 `0.1.0`,所以 CI 里固定传 `--min-binary 0.1.0`(见 `docker-build.yml` 的 `OTA_MIN_BINARY`)。只改前端版本号时**不要**跟着抬这个值,否则旧 APK 会收不到更新。 ### Web 端 Web 浏览器访问 (同源) 时, 服务端返回简化版 manifest, 仅显示"有新版本请刷新". Web 端不会自动下载包, 也不会替换前端代码. ### CI 自动签名 (Gitea Actions) .gitea/workflows/ota-build.yml 是手动触发的 OTA 打包工作流 (每次发版前点一次). **配置 secret** (Gitea 仓库 -> Settings -> Actions -> Secrets): - MINISIGN_PRIVATE_KEY: 完整 .key 文件内容, **整段** (含首行 `untrusted comment: minisign encrypted secret key` 和 base64 行), 整段作为多行 secret 粘贴即可. **使用流程**: 1. 在 Gitea Actions 页面选 OTA Frontend Build & Sign -> Run workflow 2. 填 sequence (可选, 不填用时间戳) 和 otes (可选) 3. CI 跑完会在 artifact 里看到 ota-package/iboard-ota.tar.gz + latest.json + iboard-ota.tar.gz.minisig 4. 下载 artifact, scp 到服务器 OTA_PACKAGE_DIR 目录 5. 客户端"我"页 [检查更新] 即可触发升级 **注意**: - CI 用的私钥必须**与** auri.conf.json 里 plugins.hotswap.pubkey **配对**; 换密钥时需要同时更新 CI secret 和 conf. - 不要把私钥 commit 到 git, 整个 ota-package/hotswap.key 已被 .gitignore 排除.
Languages
TypeScript
68.9%
JavaScript
25.5%
CSS
2.6%
Rust
1.7%
Shell
0.8%
Other
0.4%