Repository navigation
Release PWA (Mobile) #3
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release PWA (Mobile) | |
| # 由 semantic-release 创建的 v* tag 触发:构建 PWA 移动端产物并部署到 | |
| # ECS 服务器(https://entropydecrease.com/pwa/)。与 release.yml(Electron | |
| # 桌面端)共用同一 v* tag——双端同版本发布、互不干扰;PR 质量验证复用 | |
| # pr-check.yml(client job 已含 vite build 产出 PWA)。 | |
| on: | |
| push: | |
| tags: ['v*'] | |
| # 兜底触发:semantic-release 以内置 GITHUB_TOKEN 推 tag 时 GitHub 防递归 | |
| # 机制不派发 push 事件(同 release.yml 注释),Release 发布事件补跑 | |
| release: | |
| types: [published] | |
| # 手动兜底:在 UI 选择对应 tag 运行,用于补发历史版本 | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| # 同一 tag 的多个触发源串行执行,避免重复部署竞争 | |
| concurrency: | |
| group: release-pwa-${{ github.ref_name }} | |
| cancel-in-progress: false | |
| jobs: | |
| build: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| steps: | |
| # lfs: true 必需:pwa-192x192.png 等由 Git LFS 托管,不拉取则为指针文件 | |
| - uses: actions/checkout@v4 | |
| with: | |
| lfs: true | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20 | |
| cache: 'npm' | |
| cache-dependency-path: client/package-lock.json | |
| - run: cd client && npm ci | |
| - run: cd client && npm run lint | |
| - run: cd client && npm run test -- --run | |
| # PWA 构建(子路径 /pwa/ 部署):.env.production 已入库(git 跟踪), | |
| # 生产环境变量自动注入;VITE_PWA_BASE 控制构建 base 与 manifest | |
| # start_url/scope(vite.config.ts),保证子路径安装入口正确 | |
| - name: Build PWA (base=/pwa/) | |
| env: | |
| VITE_PWA_BASE: /pwa | |
| run: cd client && npx vite build | |
| - name: Verify PWA artifacts | |
| run: | | |
| cd client | |
| test -f dist/index.html && echo "index.html OK" | |
| test -f dist/manifest.webmanifest && echo "manifest.webmanifest OK" | |
| test -f dist/sw.js && echo "sw.js OK" | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: pwa-dist | |
| path: client/dist/ | |
| retention-days: 14 | |
| deploy: | |
| needs: build | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| # secrets 不可用于 if 条件(同 release.yml),先注入 job 级 env 再在 step 用 env.* 引用 | |
| env: | |
| DOWNLOAD_BASE_URL: ${{ secrets.DOWNLOAD_BASE_URL }} | |
| HAS_ALIYUN_AK: ${{ secrets.ALIYUN_ACCESS_KEY_ID != '' }} | |
| steps: | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| name: pwa-dist | |
| path: pwa-dist | |
| - name: Ensure PWA directory on server | |
| uses: appleboy/ssh-action@v1 | |
| with: | |
| host: ${{ secrets.SERVER_HOST }} | |
| username: ${{ secrets.SERVER_USER }} | |
| key: ${{ secrets.SSH_PRIVATE_KEY }} | |
| script: mkdir -p /opt/Entropydecrease/pwa | |
| - name: Upload PWA to server (clean replace) | |
| uses: appleboy/scp-action@v0.1.7 | |
| with: | |
| host: ${{ secrets.SERVER_HOST }} | |
| username: ${{ secrets.SERVER_USER }} | |
| key: ${{ secrets.SSH_PRIVATE_KEY }} | |
| source: pwa-dist/* | |
| target: /opt/Entropydecrease/pwa | |
| strip_components: 1 | |
| rm: true | |
| # scp rm:true 重建目录导致 bind mount 失效,须重启 nginx 容器恢复 | |
| # (同 deploy-website.yml 模式);顺带写入版本标记 + 验证部署。 | |
| # 自愈兜底:nginx 容器因故未运行(如服务器手动 compose 漏传 | |
| # --env-file .env.production 致上游解析失败)时,用正确 env-file 重建, | |
| # 避免 restart 报错且全站持续不可用(2026-08-16 生产事故教训) | |
| - name: Restart Nginx, write version and verify | |
| uses: appleboy/ssh-action@v1 | |
| with: | |
| host: ${{ secrets.SERVER_HOST }} | |
| username: ${{ secrets.SERVER_USER }} | |
| key: ${{ secrets.SSH_PRIVATE_KEY }} | |
| script: | | |
| echo "${{ github.ref_name }}" > /opt/Entropydecrease/pwa/version.txt | |
| if docker ps -a --format '{{.Names}}' | grep -q '^entropy-decrease-nginx$'; then | |
| docker restart entropy-decrease-nginx | |
| else | |
| cd /opt/Entropydecrease/server | |
| docker compose -f docker-compose.prod.yml --env-file .env.production up -d nginx | |
| fi | |
| sleep 5 | |
| curl -sf https://entropydecrease.com/pwa/ | grep -q "熵减" || exit 1 | |
| echo "PWA deployed: ${{ github.ref_name }}" | |
| # --------------------------------------------------------------- | |
| # CDN 预热:PWA 入口与元数据(manifest/sw.js)预热,移动端首开 | |
| # 免回源等待(入口文件虽小,CDN 冷缓存回源仍受源站出网带宽限制)。 | |
| # 与 release.yml 安装包预热共用同一套阿里云凭证;未配置时跳过。 | |
| # --------------------------------------------------------------- | |
| - name: Prefetch CDN cache (PWA) | |
| if: env.DOWNLOAD_BASE_URL != '' && env.HAS_ALIYUN_AK == 'true' | |
| continue-on-error: true | |
| env: | |
| AK_ID: ${{ secrets.ALIYUN_ACCESS_KEY_ID }} | |
| AK_SECRET: ${{ secrets.ALIYUN_ACCESS_KEY_SECRET }} | |
| run: | | |
| set -eo pipefail | |
| if [ ! -x /tmp/aliyun ]; then | |
| curl -sL https://aliyuncli.alicdn.com/aliyun-cli-linux-latest-amd64.tgz -o /tmp/aliyun.tgz | |
| tar -xzf /tmp/aliyun.tgz -C /tmp | |
| fi | |
| /tmp/aliyun configure set --profile cdnci --mode AK --region cn-hangzhou \ | |
| --access-key-id "$AK_ID" --access-key-secret "$AK_SECRET" | |
| OBJECTS="https://entropydecrease.com/pwa/"$'\n'"https://entropydecrease.com/pwa/manifest.webmanifest"$'\n'"https://entropydecrease.com/pwa/sw.js" | |
| echo "Prefetching:" | |
| echo "$OBJECTS" | |
| /tmp/aliyun cdn PushObjectCache \ | |
| --profile cdnci \ | |
| --ObjectPath "$OBJECTS" \ | |
| --Area domestic | |
| echo "PWA CDN prefetch done" | |
| # --------------------------------------------------------------------------- | |
| # Android APK(B 方案:TWA 打包):bubblewrap 基于已部署的 PWA manifest | |
| # 生成 TWA 工程并构建签名 APK,上传 GitHub Release + 服务器下载目录 | |
| # (官网「下载 Android APK」直链 = entropydecrease.com/downloads/entropydecrease.apk)。 | |
| # | |
| # ⚠️ 首次设置(2026-08-16 已完成:bubblewrap init 生成 android/twa/ + keystore): | |
| # keystore = android/twa/android.keystore(alias=android,packageId=com.entropydecrease.twa) | |
| # 1. 将 android/twa/android.keystore 转 base64 存入 Secret ANDROID_KEYSTORE_BASE64 | |
| # 2. Secret ANDROID_KEYSTORE_PASS / ANDROID_KEY_PASS = keystore 密码(init 时 keytool 输入值) | |
| # CI 只做非交互 update + build;android/twa/ 提交仓库(*.keystore 已 gitignore) | |
| # --------------------------------------------------------------------------- | |
| build-apk: | |
| needs: deploy | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-java@v4 | |
| with: | |
| distribution: temurin | |
| java-version: '17' | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20 | |
| - uses: android-actions/setup-android@v3 | |
| - name: Install bubblewrap | |
| run: npm i -g @bubblewrap/cli | |
| - name: Write signing keystore from secrets | |
| env: | |
| KEYSTORE_B64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} | |
| run: | | |
| if [ -z "$KEYSTORE_B64" ]; then | |
| echo "::error::ANDROID_KEYSTORE_BASE64 未配置(见 job 注释首次设置),跳过 APK 构建" | |
| exit 0 | |
| fi | |
| echo "$KEYSTORE_B64" | base64 -d > /tmp/entropydecrease.keystore | |
| - name: Update TWA project to latest PWA | |
| run: | | |
| cd android | |
| bubblewrap update \ | |
| --manifest https://entropydecrease.com/pwa/manifest.webmanifest \ | |
| --directory twa | |
| - name: Build signed APK | |
| env: | |
| KEYSTORE_PASS: ${{ secrets.ANDROID_KEYSTORE_PASS }} | |
| KEY_PASS: ${{ secrets.ANDROID_KEY_PASS }} | |
| run: | | |
| cd android/twa | |
| bubblewrap build \ | |
| --keystorePath /tmp/entropydecrease.keystore \ | |
| --keystorePass "$KEYSTORE_PASS" \ | |
| --keyAlias android \ | |
| --keyPass "$KEY_PASS" | |
| - name: Verify APK artifact | |
| run: test -f android/twa/app-release-signed.apk && echo "APK OK" | |
| - name: Upload APK to GitHub Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| files: android/twa/app-release-signed.apk | |
| - name: Upload APK to server downloads | |
| uses: appleboy/scp-action@v0.1.7 | |
| with: | |
| host: ${{ secrets.SERVER_HOST }} | |
| username: ${{ secrets.SERVER_USER }} | |
| key: ${{ secrets.SSH_PRIVATE_KEY }} | |
| source: android/twa/app-release-signed.apk | |
| target: /opt/Entropydecrease/downloads | |
| strip_components: 2 | |
| - name: Rename APK to canonical name | |
| uses: appleboy/ssh-action@v1 | |
| with: | |
| host: ${{ secrets.SERVER_HOST }} | |
| username: ${{ secrets.SERVER_USER }} | |
| key: ${{ secrets.SSH_PRIVATE_KEY }} | |
| script: | | |
| mv -f /opt/Entropydecrease/downloads/app-release-signed.apk \ | |
| /opt/Entropydecrease/downloads/entropydecrease.apk | |
| ls -la /opt/Entropydecrease/downloads/entropydecrease.apk |