Auto Update & Manual Release #276
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: Auto Update & Manual Release | |
| on: | |
| schedule: | |
| - cron: '0 0 * * *' | |
| workflow_dispatch: | |
| inputs: | |
| job_type: | |
| description: '任务类型' | |
| required: true | |
| default: 'check_update' | |
| type: choice | |
| options: | |
| - check_update | |
| - force_release | |
| permissions: | |
| contents: write | |
| concurrency: | |
| group: update-webui-kernel-${{ github.ref }} | |
| cancel-in-progress: false | |
| env: | |
| TZ: Asia/Shanghai | |
| jobs: | |
| process-and-release: | |
| if: github.ref_name == 'main' | |
| runs-on: ubuntu-latest | |
| defaults: | |
| run: | |
| shell: bash | |
| steps: | |
| - name: Checkout Code | |
| uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 | |
| with: | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| fetch-depth: 0 | |
| # ========================================== | |
| # 步骤 1 & 2: 下载更新 | |
| # ========================================== | |
| - name: Update MetaCubeXD | |
| id: webui | |
| if: github.event.inputs.job_type != 'force_release' | |
| run: | | |
| set -euo pipefail | |
| echo "🔍 检查 MetaCubeXD..." | |
| LATEST_DATA=$(curl -sf "https://api.github.com/repos/MetaCubeX/metacubexd/releases/latest") || { echo "❌ API 请求失败"; exit 1; } | |
| VERSION=$(echo "$LATEST_DATA" | jq -r '.tag_name') | |
| ASSET=$(echo "$LATEST_DATA" | jq -ec '[.assets[] | select(.name == "compressed-dist.tgz")][0]') | |
| DOWNLOAD_URL=$(echo "$ASSET" | jq -er '.browser_download_url') | |
| EXPECTED_SHA=$(echo "$ASSET" | jq -er '.digest | select(startswith("sha256:")) | sub("^sha256:"; "")') | |
| if ! printf '%s' "$VERSION" | grep -Eq '^v?[0-9]+([.][0-9]+){1,3}([-+][A-Za-z0-9._-]+)?$'; then | |
| echo "❌ MetaCubeXD 版本格式不可信: $VERSION" | |
| exit 1 | |
| fi | |
| echo "ver=$VERSION" >> "$GITHUB_OUTPUT" | |
| curl -fL --retry 3 --retry-delay 5 -o webui.tgz "$DOWNLOAD_URL" || { echo "❌ 下载失败"; exit 1; } | |
| echo "$EXPECTED_SHA webui.tgz" | sha256sum --check --strict | |
| tar -tzf webui.tgz > webui-members.txt | |
| tar -tvzf webui.tgz > webui-members-long.txt | |
| if grep -E '(^/|(^|/)\.\.(/|$))' webui-members.txt >/dev/null; then | |
| echo "❌ WebUI 压缩包包含越界路径" | |
| exit 1 | |
| fi | |
| if grep -E '^[lh]' webui-members-long.txt >/dev/null; then | |
| echo "❌ WebUI 压缩包包含链接" | |
| exit 1 | |
| fi | |
| mkdir -p temp_webui | |
| tar -xzf webui.tgz -C temp_webui | |
| TARGET_DIR="maodie/config/webui" | |
| rm -rf "$TARGET_DIR" | |
| mkdir -p "$TARGET_DIR" | |
| cp -r temp_webui/* "$TARGET_DIR"/ | |
| rm -rf temp_webui webui.tgz | |
| - name: Update Mihomo Kernel | |
| id: kernel | |
| if: github.event.inputs.job_type != 'force_release' | |
| run: | | |
| set -euo pipefail | |
| echo "🔍 检查 Mihomo Kernel..." | |
| LATEST_DATA=$(curl -sf "https://api.github.com/repos/MetaCubeX/mihomo/releases/latest") || { echo "❌ API 请求失败"; exit 1; } | |
| VERSION=$(echo "$LATEST_DATA" | jq -r '.tag_name') | |
| ASSET_NAME="mihomo-android-arm64-v8-${VERSION}.gz" | |
| ASSET=$(echo "$LATEST_DATA" | jq -ec --arg name "$ASSET_NAME" '[.assets[] | select(.name == $name)][0]') | |
| DOWNLOAD_URL=$(echo "$ASSET" | jq -er '.browser_download_url') | |
| EXPECTED_SHA=$(echo "$ASSET" | jq -er '.digest | select(startswith("sha256:")) | sub("^sha256:"; "")') | |
| if ! printf '%s' "$VERSION" | grep -Eq '^v?[0-9]+([.][0-9]+){1,3}([-+][A-Za-z0-9._-]+)?$'; then | |
| echo "❌ Mihomo 版本格式不可信: $VERSION" | |
| exit 1 | |
| fi | |
| echo "ver=$VERSION" >> "$GITHUB_OUTPUT" | |
| curl -fL --retry 3 --retry-delay 5 -o Mihomo.gz "$DOWNLOAD_URL" || { echo "❌ 下载失败"; exit 1; } | |
| echo "$EXPECTED_SHA Mihomo.gz" | sha256sum --check --strict | |
| gzip -d Mihomo.gz | |
| file Mihomo | grep -q 'ELF 64-bit.*ARM aarch64' | |
| chmod +x Mihomo | |
| mkdir -p maodie/kernel | |
| mv -f Mihomo maodie/kernel/Mihomo | |
| # ========================================== | |
| # 步骤 3: 核心逻辑 (含 Commit 提取功能) | |
| # ========================================== | |
| - name: Prepare Release Info | |
| id: commit | |
| env: | |
| JOB_TYPE: ${{ github.event.inputs.job_type }} | |
| WEBUI_VER: ${{ steps.webui.outputs.ver }} | |
| KERNEL_VER: ${{ steps.kernel.outputs.ver }} | |
| REPOSITORY: ${{ github.repository }} | |
| RUN_ID: ${{ github.run_id }} | |
| RUN_ATTEMPT: ${{ github.run_attempt }} | |
| run: | | |
| set -euo pipefail | |
| MODE="$JOB_TYPE" | |
| : "${MODE:=check_update}" | |
| SHORT_HASH=$(git rev-parse --short HEAD) | |
| TAG_NAME="v$(date +'%Y%m%d')-${RUN_ID}-${RUN_ATTEMPT}" | |
| # --- 提取 Git 变更日志 --- | |
| PREV_TAG=$(git describe --tags --abbrev=0 2>/dev/null || true) | |
| if [ -z "$PREV_TAG" ]; then | |
| COMMIT_LOGS=$(git log --pretty=format:"- %s (%h)" -n 10) | |
| else | |
| COMMIT_LOGS=$(git log --pretty=format:"- %s (%h)" "$PREV_TAG"..HEAD) | |
| fi | |
| : "${COMMIT_LOGS:=- (No new commits)}" | |
| SHOULD_RELEASE="false" | |
| COMMIT_MSG="" | |
| # --- 分支逻辑 --- | |
| if [ "$MODE" = "force_release" ]; then | |
| echo "🚀 强制发布模式" | |
| SHOULD_RELEASE="true" | |
| COMMIT_MSG="chore: manual release $TAG_NAME" | |
| cat > /tmp/body.md <<BODYEOF | |
| 📦 **手动发布** | |
| - 版本: $TAG_NAME | |
| - 基础 Commit: $SHORT_HASH | |
| ### 📝 变更日志 (Changelog): | |
| $COMMIT_LOGS | |
| BODYEOF | |
| else | |
| # 检测更新模式 | |
| git add maodie/config/webui maodie/kernel/Mihomo | |
| CHANGED_FILES=$(git diff --staged --name-only) | |
| if [ -z "$CHANGED_FILES" ]; then | |
| echo "✅ 无更新" | |
| echo "release=false" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| SHOULD_RELEASE="true" | |
| IS_WEBUI_CHANGED=$(echo "$CHANGED_FILES" | grep -c "maodie/config/webui" || true) | |
| IS_KERNEL_CHANGED=$(echo "$CHANGED_FILES" | grep -c "maodie/kernel/Mihomo" || true) | |
| SUMMARY="🔧 **自动更新**" | |
| if [ "$IS_WEBUI_CHANGED" -gt 0 ] && [ "$IS_KERNEL_CHANGED" -gt 0 ]; then | |
| COMMIT_MSG="chore: update Kernel($KERNEL_VER) & WebUI($WEBUI_VER)" | |
| SUMMARY="🔥 **双重更新**\n- 内核: $KERNEL_VER\n- 面板: $WEBUI_VER" | |
| elif [ "$IS_KERNEL_CHANGED" -gt 0 ]; then | |
| COMMIT_MSG="chore: update Kernel to $KERNEL_VER" | |
| SUMMARY="🚀 **内核更新**\n- 版本: $KERNEL_VER" | |
| elif [ "$IS_WEBUI_CHANGED" -gt 0 ]; then | |
| COMMIT_MSG="chore: update WebUI to $WEBUI_VER" | |
| SUMMARY="🎨 **面板更新**\n- 版本: $WEBUI_VER" | |
| fi | |
| cat > /tmp/body.md <<BODYEOF | |
| $(echo -e "$SUMMARY") | |
| ### 📝 变更日志 (Changelog): | |
| $COMMIT_LOGS | |
| 📌 **Release Tag**: $TAG_NAME | |
| BODYEOF | |
| fi | |
| # --- 更新配置文件 --- | |
| NEW_VERSION_CODE=$(date +'%s') | |
| OLD_VERSION_CODE=$(sed -n 's/^versionCode=//p' module.prop) | |
| if [ -n "$OLD_VERSION_CODE" ] && [ "$NEW_VERSION_CODE" -le "$OLD_VERSION_CODE" ]; then | |
| echo "❌ versionCode 未严格递增" | |
| exit 1 | |
| fi | |
| UPDATE_URL="https://raw.githubusercontent.com/$REPOSITORY/main/update.json" | |
| CHANGELOG_URL="https://raw.githubusercontent.com/$REPOSITORY/main/changelog.md" | |
| cp /tmp/body.md changelog.md | |
| sed -i "s|^version=.*|version=$TAG_NAME|" module.prop | |
| sed -i "s|^versionCode=.*|versionCode=$NEW_VERSION_CODE|" module.prop | |
| sed -i "s|^updateJson=.*|updateJson=$UPDATE_URL|" module.prop | |
| jq -n \ | |
| --arg ver "$TAG_NAME" \ | |
| --argjson code "$NEW_VERSION_CODE" \ | |
| --arg zip "https://github.com/$REPOSITORY/releases/download/$TAG_NAME/Maodie-Launcher-$TAG_NAME.zip" \ | |
| --arg log "$CHANGELOG_URL" \ | |
| '{ version: $ver, versionCode: $code, zipUrl: $zip, changelog: $log }' > update.json | |
| git add module.prop update.json changelog.md | |
| if git diff --cached --quiet; then | |
| echo "✅ 没有需要提交的变更" | |
| echo "release=false" >> $GITHUB_OUTPUT | |
| exit 0 | |
| fi | |
| echo "release=$SHOULD_RELEASE" >> $GITHUB_OUTPUT | |
| echo "release_tag=$TAG_NAME" >> $GITHUB_OUTPUT | |
| echo "commit_msg=$COMMIT_MSG" >> $GITHUB_OUTPUT | |
| git config user.name 'github-actions[bot]' | |
| git config user.email 'github-actions[bot]@users.noreply.github.com' | |
| git commit -m "$COMMIT_MSG" | |
| git push origin "HEAD:refs/heads/release-staging/$TAG_NAME" | |
| echo "release_commit=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" | |
| { | |
| echo 'body<<EOF' | |
| cat /tmp/body.md | |
| echo 'EOF' | |
| } >> $GITHUB_OUTPUT | |
| # ========================================== | |
| # 步骤 4: 打包 | |
| # ========================================== | |
| - name: Zip Module | |
| if: steps.commit.outputs.release == 'true' | |
| run: | | |
| set -euo pipefail | |
| mkdir -p maodie/run | |
| FINAL_ZIP="Maodie-Launcher-${{ steps.commit.outputs.release_tag }}.zip" | |
| zip -r "$FINAL_ZIP" META-INF maodie webroot customize.sh module.prop service.sh post-fs-data.sh uninstall.sh action.sh \ | |
| -x "maodie/run/*" "maodie/config/proxy_providers/*" \ | |
| "maodie/config/cache.db" "maodie/config/adblock.enabled" \ | |
| "maodie/config/adblock.state" "maodie/config/config.yaml.last-good" | |
| zip "$FINAL_ZIP" maodie/run/.gitkeep | |
| if [ ! -f "$FINAL_ZIP" ]; then | |
| echo "❌ 错误:文件未生成!" | |
| exit 1 | |
| fi | |
| unzip -t "$FINAL_ZIP" >/dev/null | |
| unzip -Z1 "$FINAL_ZIP" > zip-members.txt | |
| for required in module.prop customize.sh service.sh maodie/kernel/Mihomo maodie/config/config.yaml; do | |
| grep -Fx "$required" zip-members.txt >/dev/null || { echo "❌ ZIP 缺少 $required"; exit 1; } | |
| done | |
| echo "✅ 打包完成: $(ls -lh "$FINAL_ZIP" | awk '{print $5, $NF}')" | |
| echo "zip_name=$FINAL_ZIP" >> $GITHUB_ENV | |
| # ========================================== | |
| # 步骤 5: 发布 Release | |
| # ========================================== | |
| - name: Create Draft Release | |
| if: steps.commit.outputs.release == 'true' | |
| uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2 | |
| with: | |
| tag_name: ${{ steps.commit.outputs.release_tag }} | |
| target_commitish: ${{ steps.commit.outputs.release_commit }} | |
| name: Maodie Launcher ${{ steps.commit.outputs.release_tag }} | |
| body: ${{ steps.commit.outputs.body }} | |
| draft: true | |
| prerelease: false | |
| files: ${{ env.zip_name }} | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Publish Release And Metadata | |
| if: steps.commit.outputs.release == 'true' | |
| env: | |
| COMMIT_MSG: ${{ steps.commit.outputs.commit_msg }} | |
| RELEASE_TAG: ${{ steps.commit.outputs.release_tag }} | |
| RELEASE_COMMIT: ${{ steps.commit.outputs.release_commit }} | |
| TARGET_BRANCH: ${{ github.ref_name }} | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| git push origin "$RELEASE_COMMIT:refs/heads/$TARGET_BRANCH" | |
| if ! gh release edit "$RELEASE_TAG" --draft=false; then | |
| git revert --no-edit "$RELEASE_COMMIT" | |
| git push origin "HEAD:refs/heads/$TARGET_BRANCH" | |
| exit 1 | |
| fi | |
| git push origin --delete "release-staging/$RELEASE_TAG" |