Files
iboard/docs/021任务.md
T
jif2.zhang 317072539d
Docker Build and Push / build-image (push) Successful in 1m27s
docs(0.2.1): 记录 OTA 链路打通与遗留清单实测数据
2026-10-27 08:12:54 +08:00

19 KiB
Raw Blame History

0.2.1 发布任务 — 目标与现状

最后更新:2026-10-27 08:20 分支:dev-0.2 与 master 均已发布(a1c267b) 状态:全部完成 —— 生产已切 0.2.1 单端口架构,master 发布 CI 通过,Android APK 与 Windows 安装包均已产出

零、执行结果速览(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;恢复:徽标消失
master 发布 已推 master(a1c267b);CI build-image 10/10 步成功(版本校验 / 前端 / OTA 签名包 / 镜像 / 部署步按设计跳过)
Android android-1.0 CI 16/16 步成功,APK 20MB(versionName 0.2.1)已发布到 Gitea packages
Windows 本地 npm run build:win 成功:NSIS 6.9MB + 便携 zip 7.9MB
OTA 热更 已打通:https://iboard.honor3.com/api/ota/check/0 返回 0.2.1-ota、min_binary_version: 0.2.1、签名与下载地址齐全

迁移过程中踩到并修掉的坑

  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。
  6. OTA 包此前完全没有:手工构建的镜像内 /app/ota-package 只有 README(ota-package/ 被 .gitignore 排除,CI 部署段又跳过),线上 OTA 接口返回空 → 已给 app 容器加 data/ota 挂载,并新增 CI 步骤用 docker cp 经 bind-mount 容器写入宿主(CI 在容器内无法直接写宿主文件)。
  7. CI 首次写入路径多写了一层 data/ota(与 compose 的 deploy/prod/data/ota 不一致)→ 路径已对齐。
  8. OTA_MIN_BINARY 由 0.1.0 提升到 0.2.1,防止旧外壳拉到新域名前端后被 CSP 拦截白屏。

一、任务目标(用户原始要求)

  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。


五、关键路径与命令速查

# 仓库
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:准备(不改动现网)

# 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)

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)

docker run --rm --network iboard-prod_default \
  -e DATABASE_URL="postgresql://iboard:<PG_PASSWORD>@pg:5432/iboard" \
  -e SRC_DATABASE_URL="mysql://root:<MYSQL_PASSWORD>@<host>: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:切换生产

# 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:发布

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
切换后严重故障 docker start iboard(旧容器仍在,见 6.2 警告:旧镜像已被 prune,不能重建)

六、遗留清单与清理建议(2026-10-27 实测)

6.1 本机(Windows)

路径 占用 建议 说明
workbetch/iboard(主 worktree) 3.81 GB 保留 含 node_modules 0.49 GB + src-tauri/target 2.56 GB
workbetch/iboard-dev-0.2.1 0.53 GB 可删 内容已全部并入 dev-0.2
workbetch/iboard-dev-windows 8.88 GB 可删 同上,最大占用(含 Rust 编译产物)
iboard/src-tauri/target 2.56 GB 可选 cargo clean 删后下次打包需重编译(约 8 分钟)
iboard/artifacts/windows 32 MB 保留最新安装包 0.2.1 exe/zip
%TEMP%\node-portable 78 MB 保留 本机无系统 Node/npm,npm run build:win 依赖它
%TEMP%\esbuild-*(4 个) 8 MB 可删 构建残留
%TEMP%\iboard-e2e、iboard-shell-offline < 1 MB 可删 验证截图
%TEMP%\edge-e2e-profile ~100 MB 可删 无头浏览器测试配置
# 删除 worktree 与已合并分支(内容已在 dev-0.2)
git worktree remove ../iboard-dev-0.2.1
git worktree remove ../iboard-dev-windows
git branch -d dev-0.2.1 dev-windows-0.1
git branch -D tmp-offline-edit

6.2 服务器 baihu

路径/对象 占用 建议 说明
/app/iboard-prod 72 MB 保留 当前生产
/app/iboard 24 KB 暂留 → 可删 旧生产只剩 cache/(auth.txt 已复制到新栈)+ start.sh
/app/iboard-0.2.1 71 MB 可删 验证栈已 down;其中 deploy/0.2.1/data 67 MB 是废弃 PG 数据
/app/cicd 36 KB 保留 act_runner 配置
容器 iboard(Exited) — 暂留 当前唯一回滚手段(见下方警告)
镜像 iboard-021-app:latest 924 MB 可删 验证栈镜像
镜像 iboard:latest 934 MB 保留 CI 构建产物
悬空镜像 + 构建缓存 18.7 GB + 2.4 GB 可清 docker image prune -f && docker builder prune -af

⚠ 回滚能力警告(实测更正) 原计划保留的 iboard:mysql-final 旧镜像已被 docker image prune 清除 —— 旧镜像失去 tag 后成为悬空镜像被回收。 现在唯一回滚途径是重启仍存在的旧容器 iboard(镜像层随容器保留):

docker stop iboard-prod-app-1
docker start iboard          # 旧 MySQL 版立即恢复 3001

在再次执行 docker image prune 前,务必先确认旧容器仍在;容器一旦删除,MySQL 版镜像只能从 git 历史重新构建。

6.3 代码与其他

  • 临时测试脚本(scripts/_prod-e2e.mjs、_browser-test.mjs、_shell-offline-test.mjs)含真实口令,已加入 .gitignore(scripts/_*.mjs),不要入库
  • 0.2.1 验证库里的测试任务(如 浏览器验证任务)已按 MySQL 现状一并迁入 PG,如需清理直接在 PG 删除
  • prisma/migrations/ 仍是 MySQL 语法,新环境建表必须用 prisma db push --accept-data-loss(已写入 deploy/prod/README.md)
  • JWT_SECRET 仍为弱值(iboard),建议择机换新(换新需全员重新登录)
  • node_modules 在多个 Node 版本间切换过,遇 esbuild 权限报错时清空重装

七、外壳升级策略:必须整包升级,不能只靠 OTA

结论

平台 结论 关键原因
Windows 外壳(0.1.0) 必须重装 exe ① CSP 白名单编译进原生二进制,0.1.0 不含 https://iboard.honor3.com;② 旧外壳 OTA 检查地址指向 13001,该端口已下线,连检查都失败
Android(0.1.0 APK) 必须换装 APK CSP 同样是原生配置;旧 APK 的 OTA 端点(3001)虽然有效,但拉到 0.2.1 前端会被 CSP 拦截 → 白屏

为什么 OTA 不够

tauri-plugin-hotswap 的 OTA 包内容只有 bundle.tar.gz(即 dist/ 前端资源),不触碰原生层:

配置项 位置 OTA 能否修改
CSP 白名单(connect-src) tauri.conf.json → 编译期 generate_context! 注入二进制 ❌
OTA 检查 endpoint 同上 ❌
窗口尺寸/最小尺寸 同上(另可被 shell-config.json 覆盖) ❌
托盘 / 悬浮球 / 自启动 Rust src-tauri/src/shell/* ❌
页面 JS/CSS/WASM(含离线编辑、徽标、编号展示) dist/ ✅

因此旧外壳无法通过 OTA 获得访问新域名的能力。

已采取的保护措施

CI 的 OTA_MIN_BINARY 已从 0.1.0 提升到 0.2.1:

  • 旧外壳(0.1.0 < 0.2.1)会被 hotswap 判定为不兼容 → 拒绝接收新前端
  • 旧外壳继续运行旧前端,仍访问 http://bh.vps.honor3.com:3001(该地址现指向新栈,/api 完全兼容)
  • 结果:旧外壳不会白屏,只是缺少离线编辑、项目编号等 0.2.1 新能力

升级后

  • 新外壳版本 0.2.1,CSP 含 https://iboard.honor3.com,OTA 恢复正常(后续纯前端改动可继续走 OTA)
  • Windows 窗口尺寸通过 %APPDATA%\com.iboard.app\shell-config.json 的 window_width 单值配置