feat: 自动部署 + android 每次提交触发
Some checks failed
Android Build & Publish / build-android (push) Failing after 5s
Docker Build and Push / build-image (push) Failing after 4m32s

- docker-build.yml: main 分支推送 → 构建 OTA 包 → Docker 镜像 → :3002 自动部署
- Dockerfile: node:22-alpine, dist + server + ota-package, healthcheck
- android-build.yml: 触发改为 on push (所有分支), 每次提交都构建
- ota-package/README.md 占位, 本地 docker build 不报错
- .gitignore / .dockerignore 调整
This commit is contained in:
jif2.zhang 2026-09-11 17:12:58 +08:00
parent 38b5e3190a
commit 84fb01b8a3
6 changed files with 183 additions and 4 deletions

12
.dockerignore Normal file
View File

@ -0,0 +1,12 @@
# Docker build context 排除项 (镜像内自行构建 dist, 不需要本地构建产物/依赖)
node_modules
dist
.git
.gitea
src-tauri
prototype
photograph
# 本地 ota-package 中的私钥不得进入镜像 (CI 里的 ota-package 只含公钥产物)
ota-package/hotswap.key
.DS_Store

View File

@ -1,13 +1,11 @@
name: Android Build & Publish name: Android Build & Publish
# 仅在 android-* 分支 push 时触发 (如 android/release, android-1.0) # 每次提交触发 (所有分支 push; 主分支提交会同时触发 docker-build.yml 自动部署)
# 构建 release APK (keystore 签名) 并上传到 Cloudflare R2 (S3 兼容 API) # 构建 release APK (keystore 签名) 并上传到 Cloudflare R2 (S3 兼容 API)
# R2 凭据从配置注册中心 (bh.vps.honor3.com:8008, CommonExternalService, 分组 cloudflare) 读取 # R2 凭据从配置注册中心 (bh.vps.honor3.com:8008, CommonExternalService, 分组 cloudflare) 读取
# 参考: D:\workbench\iboard\.gitea\workflows\android-build.yml # 参考: D:\workbench\iboard\.gitea\workflows\android-build.yml
on: on:
push: push:
branches:
- 'android-*'
jobs: jobs:
build-android: build-android:

View File

@ -0,0 +1,100 @@
name: Docker Build and Push
# 仅 main 分支 push 触发 (每次提交 → 自动部署)
# 每次主分支提交: 构建前端 + 生成 OTA 升级包 (minisign 签名) → 打进 Docker 镜像 → 部署到 :3002
# OTA 随镜像发布: 容器内 OTA_PACKAGE_DIR=/app/ota-package 指向打包的 ota-package/
# 依赖配置注册中心 (bh.vps.honor3.com:8008):
# - service-wardrobe / ota : MINISIGN_PRIVATE_KEY (整段 .key 内容, 多行)
on:
push:
branches:
- main
- master
jobs:
build-image:
runs-on: ubuntu-latest
permissions:
contents: read
env:
# VITE_API_BASE 留空 = 前端走同源 /api (OTA server 同进程托管首页 + OTA 接口)
VITE_API_BASE: ""
CONFIG_CENTER_URL: '${{ secrets.CONFIG_CENTER_URL }}'
CONFIG_CENTER_TOKEN: '${{ secrets.CONFIG_CENTER_TOKEN }}'
REGISTRY_URL_FALLBACK: 'http://bh.vps.honor3.com:8008'
CFG_PROJECT_SIGNING: 'proj-mtwjnvir-k52zvp'
steps:
- uses: https://gitea.com/actions/checkout@v4
- name: Setup Node
uses: https://gitea.com/actions/setup-node@v4
with:
node-version: 22
- name: Install deps
run: npm ci --no-audit --no-fund
- name: Build frontend
# dist/ 供 OTA 打包用 (Dockerfile 内也会构建一份)
run: npm run build
- name: Install minisign
run: sudo apt-get update && sudo apt-get install -y minisign
- name: Fetch config center (minisign key)
run: |
set -euo pipefail
export CONFIG_CENTER_URL="${CONFIG_CENTER_URL:-$REGISTRY_URL_FALLBACK}"
python3 - <<'PYEOF'
import json, os, urllib.request
url = os.environ["CONFIG_CENTER_URL"]
token = os.environ.get("CONFIG_CENTER_TOKEN", "") or ""
req = urllib.request.Request(f"{url}/api/projects/{os.environ['CFG_PROJECT_SIGNING']}/configs")
if token:
req.add_header("Authorization", "Bearer " + token)
with urllib.request.urlopen(req, timeout=20) as r:
data = json.load(r).get("data", [])
wanted = {}
for cfg in data:
if cfg.get("key") == "MINISIGN_PRIVATE_KEY":
wanted[cfg["key"]] = cfg.get("value", "")
with open(os.environ["GITHUB_ENV"], "a", encoding="utf-8") as f:
for k, v in wanted.items():
if "\n" in v or v.startswith("untrusted comment"):
f.write(f"{k}<<EOF\n{v}\nEOF\n")
else:
f.write(f"{k}={v}\n")
if "MINISIGN_PRIVATE_KEY" not in wanted:
raise SystemExit("配置中心缺少 MINISIGN_PRIVATE_KEY (service-wardrobe/ota)")
PYEOF
- name: Build & sign OTA package
# 每次主分支提交生成 OTA 升级包, sequence 默认时间戳 (单调递增)
# 产物 ota-package/ (tar.gz + latest.json + .minisig) 进入 docker build context, 由 Dockerfile COPY 进镜像
env:
OTA_NOTES: ${{ github.sha }}
run: |
set -euo pipefail
node scripts/build-ota.mjs --out ota-package --notes ${OTA_NOTES}
echo "===== latest.json ====="
cat ota-package/latest.json
echo "===== bundle sha256 ====="
sha256sum ota-package/wardrobe-ota.tar.gz
- name: Build Docker image
run: docker build -t wardrobe:latest .
- name: List images
run: docker images
- name: Remove old container
run: docker rm -vf wardrobe || true
- name: Recycle container
run: docker system prune --volumes -f
- name: Run container
# 一键镜像: 前端 + OTA + 首页, 端口 3002 (与 iboard 的 3001 同机共存)
run: |
docker run -d --name wardrobe -p 3002:3002 wardrobe:latest

4
.gitignore vendored
View File

@ -21,7 +21,9 @@ src-tauri/gen/android/keystore.properties
!.env.example !.env.example
# OTA 升级产物与私钥 (全部由 scripts/build-ota.mjs、ota-genkey.mjs 与 CI 生成) # OTA 升级产物与私钥 (全部由 scripts/build-ota.mjs、ota-genkey.mjs 与 CI 生成)
ota-package/ # 保留 ota-package/README.md 占位文件, 供本地 docker build 使用
ota-package/*
!ota-package/README.md
# wrangler 本地缓存 # wrangler 本地缓存
.wrangler .wrangler

61
Dockerfile Normal file
View File

@ -0,0 +1,61 @@
# ===============================================================
# Wardrobe 一体化镜像: 静态前端 (dist/) + Express API (server/) 同进程
# 构建: docker build -t wardrobe:latest .
# 可选 --build-arg VITE_API_BASE=https://api.example.com 让前端走绝对地址
# 运行: docker run -d --name wardrobe -p 3002:3002 wardrobe:latest
# ===============================================================
FROM node:22-alpine AS builder
WORKDIR /app
# 国内构建时可换源
RUN npm config set registry https://registry.npmmirror.com
# 先拷贝 package 文件, 利用 Docker 缓存
COPY package*.json ./
# 装全量依赖 (dev 也装, 后面 build 要用)
RUN npm install
# 拷贝源码
COPY . .
# 构建前端 (输出到 dist/)
ARG VITE_API_BASE=
ENV VITE_API_BASE=$VITE_API_BASE
RUN npm run build
# 运行时阶段
FROM node:22-alpine
WORKDIR /app
# 阿里源加速
RUN npm config set registry https://registry.npmmirror.com
# 只装运行时依赖
COPY package*.json ./
RUN npm install --omit=dev
# 拷贝构建产物
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/server ./server
# OTA 升级包: CI (docker-build.yml) 生成的 ota-package/, 随镜像发布
# 本地构建时只有占位文件 (ota-package/README.md), server 找不到 latest.json 则 OTA 返回"无更新"
COPY ota-package ./ota-package
ENV OTA_PACKAGE_DIR=/app/ota-package
# OTA bundle 下载用的绝对 base (manifest.url 必须是绝对 URL, 插件不拼 base, 相对路径会导致下载 0% 失败)
# 生产为 http 明文 (无 https), 客户端 tauri.conf.json 的 hotswap.require_https 已设为 false 放行 http
ENV OTA_PUBLIC_BASE=http://bh.vps.honor3.com:3002
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget -qO- http://127.0.0.1:3002/api/health || exit 1
EXPOSE 3002
ENV NODE_ENV=production
ENV PORT=3002
CMD ["node", "--experimental-strip-types", "--no-warnings", "server/index.js"]

6
ota-package/README.md Normal file
View File

@ -0,0 +1,6 @@
# ota-package/ 目录说明
# 本目录全部内容均由脚本/CI 生成:
# - scripts/ota-genkey.mjs / build-ota.mjs 生成 hotswap.{key,pub}、bundle、latest.json
# - CI (docker-build.yml / ota-build.yml) 在构建时重新生成并打进镜像 / 上传 R2
# 仅保留本 README 作为占位, 保证本地 `docker build` 时 COPY ota-package 不报错。
# server 找不到 latest.json 时 OTA 检查接口返回 204 (无更新)。