# 0.2.1 发布任务 — 目标与现状 > 最后更新:2026-10-27 08:05 > 分支:`dev-0.2`(已推送) > 状态:**生产已切换到 0.2.1 单端口架构并验证通过;剩余 "合 master 发布" 一步** ## 零、执行结果速览(2026-10-27 已完成) | 项目 | 结果 | | --- | --- | | 生产地址 | `https://iboard.honor3.com` → baihu **3001**(NPM + 有效证书) | | 生产架构 | `iboard-prod-app-1`(3001 对外) + `iboard-prod-pg-1`(内网) + `iboard-prod-powersync-1`(内网) | | 端口 | **只剩 3001**;13001 / 13002 / 13443 全部下线 | | 数据迁移 | MySQL → PG 完成:Project 3、Task 25、BoardColumn 12、User 1、CheckIn 17、News 19、IndicatorPoint 245、Mihome 等全部迁入 | | 旧生产 | 容器 `iboard` 已 **stop(未删除)**,`docker start iboard` 可秒级回滚 | | 登录态 | `JWT_SECRET` 沿用 `iboard`,现有用户无需重新登录 | | 米家凭证 | `/app/iboard/cache` → `deploy/prod/data/cache`(`auth.txt` + `apizero/`) | | E2E 验证 | 在线:项目卡片 + `ADMIN-16`/`ADMIN-18` 编号;断网:徽标出现、任务页数据完整、无 fail fetch;恢复:徽标消失 | ### 迁移过程中踩到并修掉的坑 1. `prisma/migrations/` 是 **MySQL 语法**(反引号),PG 无法 `migrate deploy` → 改用 `prisma db push --accept-data-loss` 按 schema 建表。 2. `db push` 首次因参数不被识别而输出帮助(表结构停在旧 schema,缺 `autoArchive`)→ 去掉错误参数重跑,列已补齐。 3. 新库缺 **uuid 镜像触发器**(`projectUuid`/`columnUuid`/`parentUuid`/`taskUuid`)→ 从 0.2.1 验证库导出 5 个函数 + 5 个触发器,落库并入库 `deploy/prod/sql/uuid-triggers.sql`。 4. bash 把 `$$` 展开成 PID 导致 TRUNCATE 脚本报错 → 改用 SQL 文件传入。 5. baihu 上 `docker build` 缺 buildx → 改用 `docker compose build`。 --- ## 一、任务目标(用户原始要求) 1. 域名 `iboard.honor3.com` 已指向 3000 端口、HTTPS 证书已配好 → 客户端全面切到该域名。 2. **Windows 外壳 + 离线编辑功能正式合并到 `dev-0.2`**,完整版本号 **0.2.1**。 3. 确保 **Android** 功能正常(通过 CI 编译验证,不在本地装 Android 工具链)。 4. 确保 **CI/CD** 正常。 5. Windows **本地打包正常**即可。 6. `dev-0.2` 更新完成后 **通过 `master` 分支发布**。 7. 追加要求:**不要再有 13002 这类独立端口**,所有版本合并成一套。 --- ## 二、已完成 ### 2.1 分支合并 | 内容 | 结果 | | --- | --- | | `dev-0.2.1`(离线编辑 + 项目编号 + PG/PowerSync)→ `dev-0.2` | 已合并 `e689e44` | | `dev-windows-0.1`(Windows 外壳:托盘/悬浮球/吸附/自启动)→ `dev-0.2` | 已按路径迁入 `eef2969` | | `dev-0.2` → `android-1.0`(触发 APK 构建) | 已合并 `4958359` | | 冲突处理 | `projects.js`/`tasks.js`/`api/task.js` 手工合并,**两边功能都保留**(`autoArchive` 归档 + `code` 编号) | | 归档功能补齐到本地库 | `src/api/task.js` 新增 `archive`/`unarchive`/`archivedList`;`schema.js` 加 `archived/archivedAt/archiveSource/autoArchive`;`sync-config.yaml` 与 upload 处理器同步 | | Prisma 客户端补齐 | dev-0.2 原来缺任务模块 6 个模型,已重新 generate `fc23919` | ### 2.2 版本与域名 - 版本统一 **0.2.1**:`package.json` / `src-tauri/tauri.conf.json` / `Cargo.toml` / `Cargo.lock`(`scripts/ci/check-version.mjs` 通过) - 域名切 `https://iboard.honor3.com`: - `src/api/client.js` 注释、`.env.shell`、`scripts/wsl/build-apk.sh`、`.gitea/workflows/android-build.yml`、`.gitea/workflows/docker-build.yml`、`Dockerfile`(`OTA_PUBLIC_BASE`)、`tauri.conf.json`(OTA endpoint + CSP) - 旧域名 `bh.vps.honor3.com:3001` 仅保留在 CSP 白名单里做滚动兼容 ### 2.3 单端口架构(去掉 13002) | 改动 | 位置 | | --- | --- | | Express 增加 `/sync`、`/socket` 同域反代到内网 powersync | `server/powersync-proxy.js` + `server/index.js`(挂在 `express.json()` 之前,支持长连接) | | 生产编排:app(3001 对外) + pg(内网) + powersync(内网) | `deploy/prod/docker-compose.yml` | | 生产部署脚本(幂等:迁移 → publication → 起栈 → 健康检查) | `scripts/deploy/prod.sh` | | PG 初始化(powersync_storage 用户) | `deploy/prod/initdb/01-powersync-storage.sh` | | 逻辑复制 publication(**建表后**执行) | `deploy/prod/sql/publication.sql` | | 部署文档 | `deploy/prod/README.md`、`.env.example` | | CI 部署段改为调用宿主脚本,未配 PG secrets 时自动跳过 | `.gitea/workflows/docker-build.yml` | ### 2.4 Windows 本地打包 — 通过 ✅ `npm run build:win` 全流程成功(本机需 Node 20.19+;已用便携 Node 20.18.1 之外的 Node 24 验证过 vite 构建): | 产物 | 路径 | 大小 | | --- | --- | --- | | 安装包 | `artifacts/windows/iBoard_0.2.1_x64-setup.exe` | 6.9 MB | | 便携版 zip | `artifacts/windows/iBoard_0.2.1_x64-portable.zip` | 7.9 MB | 外壳能力(继承 dev-windows-0.1):托盘、悬浮球吸附、自动隐藏、单实例、自启动、窗口尺寸单值配置 (`%APPDATA%\com.iboard.app\shell-config.json` 的 `window_width`,高度按 405:720 锁定推导)。 离线能力(继承 0.2.1):GET 响应本地缓存、同步凭证持久化(离线不清库)、左上角「离线」徽标。 ### 2.5 Android CI — 通过 ✅ - 触发:`android-1.0` 分支 push(`4ded8e3`) - 结果:**16 个步骤全部 success**,`Build release APK` + `Publish APK to Gitea packages` 成功 - 产物:`app-universal-release.apk`(20 MB),versionName = **0.2.1** 已发布到 `http://git.honor3.com/api/packages/Fullstack/generic/iboard/1.0-4ded8e30/` - 首次失败原因:runner 宿主机(baihu)磁盘 **95%** → `No space left on device`; 清理悬空镜像 + 构建缓存后释放 11.5 GB,重跑通过。**磁盘现已 60%(39 GB 可用)** ### 2.6 CI/CD 现状 | Workflow | 触发 | 状态 | | --- | --- | --- | | `docker-build.yml` | `master` push | 改造完成,未在 master 上验证(部署段会因缺 secrets 跳过) | | `android-build.yml` | `android-*` push | **已验证通过** | | `ota-build.yml` / `sync-registry.yml` | 参照原逻辑 | 未改动 | --- ## 三、当前卡点(生产切换未执行) 生产镜像 **已构建成功**:`iboard:latest` = `db6b27b851b8`(924 MB,2026-10-26 22:53) baihu 上当前跑着两套: | 容器 | 用途 | 端口 | | --- | --- | --- | | `iboard`(旧) | **现网生产**,MySQL(宿主 3306) | 3001 | | `iboard-021-app-1` 等 4 个 | 0.2.1 验证栈 | 13001 / 13002 / 13443 | | (未启动)`iboard-prod` | **新生产栈**(PG + PowerSync + app) | 3001(计划) | `/app/iboard-prod` 已上传源码、`deploy/prod/.env` 已生成(6 个密钥,600 权限)。 **剩余步骤(约 4 步)**: 1. 起新栈(先用 `HOST_PORT=13011` 灰度,不动现网) 2. 数据迁移:现网 MySQL → 新 PG(`scripts/etl-mariadb-to-pg.mjs`,只读源库) 3. 验证:健康检查、登录、任务列表、离线编辑(断网后仍可读)、同步回流 4. 切换:停 `iboard` 旧容器 → 新栈改绑 3001 → 域名 `iboard.honor3.com` 生效 → 下线验证栈(13001/13002/13443) 然后 `dev-0.2` → `master` 合并推送,触发 CI 发布。 --- ## 四、需要确认的信息(阻塞项) 1. **Gitea Actions Secrets 是否要配 PG 密钥?** 需配:`PG_PASSWORD`、`PS_STORAGE_PASSWORD`、`JWT_SECRET`、`POWERSYNC_JWT_SECRET`、`POWERSYNC_JWT_KEY_B64`。 不配的话:master 的 CI 只构建镜像不部署,生产靠手工执行 `scripts/deploy/prod.sh`(当前默认路径)。 2. **生产数据迁移的时间窗口**:迁移需短暂停写(现网 MySQL 只读快照)。是否可以挑一个低峰时间、期间 (~10 分钟) 站点只读? 3. **现网 MySQL 里的米家凭证/缓存**:`/app/iboard/cache`(`auth.txt` 等)是否要一并搬到新栈的 `deploy/prod/data/cache`? 4. **用户密码**:迁移只搬数据,`JWT_SECRET` 若换新,所有人需重新登录;是否沿用现网 `JWT_SECRET`(`iboard`)以保持登录态?**建议换新**(当前值过弱)。 5. **域名端口说明**:`iboard.honor3.com` 实际反代到 baihu **3001**(NPM 配置:`114.134.187.170:3001`,SSL 强制)。 你提到"已转向 3000 端口"——请确认指的是 `honor3.com` 根域(→ homepage)而非 `iboard` 子域, 否则需要调整 NPM 的 forward_port。 --- ## 五、关键路径与命令速查 ```bash # 仓库 C:\Users\Rabbit\Documents\workbetch\iboard # dev-0.2 主工作区 C:\Users\Rabbit\Documents\workbetch\iboard-dev-0.2.1 # 研究分支 worktree C:\Users\Rabbit\Documents\workbetch\iboard-dev-windows # Windows 外壳 worktree # 服务器 ssh root@baihu # 生产 + CI runner(baihu 上跑 act_runner,注册名 baishe) ssh root@baishe # 反代(NPM)、Gitea、homepage # 生产目录 /app/iboard-prod/deploy/prod/{docker-compose.yml,.env,data/,powersync/,sql/,initdb/} # 常用 docker compose -f /app/iboard-prod/deploy/prod/docker-compose.yml --env-file /app/iboard-prod/deploy/prod/.env ps bash /app/iboard-prod/scripts/deploy/prod.sh curl -s https://iboard.honor3.com/api/health ``` ## 五之二、后续动作执行步骤(已确认方案,按序执行) ### 决策确认(2026-10-27) | 项 | 决定 | | --- | --- | | PG 密钥 | **不进 Gitea Secrets**,直接写在服务器 `/app/iboard-prod/deploy/prod/.env`(600) | | 迁移时机 | 立即执行 | | 米家凭证 | 保留,`/app/iboard/cache` 搬到新栈 `deploy/prod/data/cache` | | `JWT_SECRET` | 沿用现网值(`iboard`),保持现有登录态 | | 域名 | `iboard.honor3.com` → baihu **3001**(NPM 已配好,SSL 强制) | ### 步骤 A:准备(不改动现网) ```bash # A1. 沿用现网 JWT_SECRET (其余密钥保持已生成的随机值) cd /app/iboard-prod/deploy/prod sed -i 's/^JWT_SECRET=.*/JWT_SECRET=iboard/' .env # A2. 搬米家缓存 (auth.txt 等) mkdir -p data/cache rsync -a /app/iboard/cache/ data/cache/ # A3. 保留旧镜像用于回滚 docker tag iboard:latest iboard:mysql-final # 需在重新构建前执行; 若已覆盖见"回滚预案" ``` ### 步骤 B:起灰度栈(不动现网 3001) ```bash cd /app/iboard-prod/deploy/prod HOST_PORT=13011 docker compose --project-name iboard-prod \ --env-file .env -f docker-compose.yml up -d pg # 迁移建表 docker run --rm --network iboard-prod_default \ -e DATABASE_URL="postgresql://iboard:$(grep ^PG_PASSWORD .env | cut -d= -f2)@pg:5432/iboard" \ iboard:latest npx prisma migrate deploy # 建 publication (必须在建表之后) docker compose --project-name iboard-prod --env-file .env -f docker-compose.yml exec -T pg \ psql -U iboard -d iboard -v ON_ERROR_STOP=1 -f /dev/stdin < sql/publication.sql # 起 app + powersync (只有 13011 对外) HOST_PORT=13011 docker compose --project-name iboard-prod \ --env-file .env -f docker-compose.yml up -d app powersync # 验收 curl -s http://127.0.0.1:13011/api/health # {"ok":true,...} ss -tlnp | grep -E ':(13011|5432|8080)\b' # 只应看到 13011 ``` ### 步骤 C:数据迁移(MySQL → PG) ```bash docker run --rm --network iboard-prod_default \ -e DATABASE_URL="postgresql://iboard:@pg:5432/iboard" \ -e SRC_DATABASE_URL="mysql://root:@:3306/iboard" \ iboard:latest node scripts/etl-mariadb-to-pg.mjs # 迁移后重启同步服务重建快照 docker compose --project-name iboard-prod --env-file .env -f docker-compose.yml restart powersync ``` **验收**:项目 3 个(ADMIN / IBOARD / P3F921)、任务 ~22 条、用户 1 个(admin@iboard.local); `docker compose logs powersync` 显示 replication 已激活且无报错。 ### 步骤 D:功能验证(13011) - 登录 `admin@iboard.local` - 任务页:项目卡片、任务编号(如 `ADMIN-16`) - **离线验证**:断网后列表仍可读、左上角出现「离线」徽标,恢复网络自动同步回流 - 监控 / 宏观 / 打卡页有数据 ### 步骤 E:切换生产 ```bash # E1. 停旧容器 (MySQL 版) docker rm -vf iboard || true # E2. 新栈改绑 3001 cd /app/iboard-prod/deploy/prod HOST_PORT=3001 docker compose --project-name iboard-prod \ --env-file .env -f docker-compose.yml up -d app curl -s https://iboard.honor3.com/api/health # 经 NPM + 证书 # E3. 下线 0.2.1 验证栈 (释放 13001/13002/13443) cd /app/iboard-0.2.1/deploy/0.2.1 && docker compose down ``` **验收**:`https://iboard.honor3.com` 可登录、任务正常、离线可编辑; `ss -tlnp` 只剩 3001(外加仅供本机的 3306)。 ### 步骤 F:发布 ```bash git checkout master && git merge dev-0.2 --no-edit git push origin master # 触发 CI: 构建镜像 + OTA ``` CI 未配 PG secrets → 只构建不部署;生产已由步骤 B~E 手工就绪。 ### 回滚预案 | 场景 | 动作 | | --- | --- | | 新栈起不来 | 旧容器未停,现网不受影响,回步骤 B 排查 | | 迁移数据不对 | 清空 `deploy/prod/data/pg` 后重跑 ETL | | 切换后严重故障 | 用 `iboard:mysql-final` 旧镜像按原 MySQL 参数恢复 3001 | ## 六、已知遗留 - `dev-0.2.1` / `dev-windows-0.1` 两个 worktree 分支内容已全部并入 `dev-0.2`,可择机删除。 - `tmp-offline-edit` 分支 = `1cd5b85`,历史遗留,可删。 - 临时测试脚本 `scripts/_browser-test.mjs`、`scripts/_shell-offline-test.mjs` 含真实口令,**不要入库**。 - 0.2.1 验证库里可能残留测试任务(`浏览器验证任务` / `IBOARD-8`),迁移后建议清理。 - `node_modules` 曾在多种 Node 版本间切换,如遇 esbuild 权限报错,清 `node_modules` 重装即可。