Build All UI APP #10
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: Build All UI APP | |
| on: | |
| workflow_dispatch: | |
| jobs: | |
| prepare: | |
| name: Prepare Version and Create Release | |
| runs-on: ubuntu-latest | |
| outputs: | |
| version: ${{ steps.version.outputs.VERSION }} | |
| app_version: ${{ steps.version.outputs.APP_VERSION }} | |
| release_tag: ${{ steps.version.outputs.RELEASE_TAG }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Get version info | |
| id: version | |
| run: | | |
| BASE_VERSION=$(cat VERSION) | |
| # 使用当前日期时间生成tag (格式: v20260202-HHMMSS) | |
| CURRENT_DATE=$(TZ='Asia/Shanghai' date +'%Y%m%d-%H%M%S') | |
| RELEASE_TAG="v${CURRENT_DATE}" | |
| APP_VERSION="${BASE_VERSION}" | |
| echo "VERSION=${RELEASE_TAG}" >> $GITHUB_OUTPUT | |
| echo "APP_VERSION=${APP_VERSION}" >> $GITHUB_OUTPUT | |
| echo "RELEASE_TAG=${RELEASE_TAG}" >> $GITHUB_OUTPUT | |
| echo "Building version: ${RELEASE_TAG}" | |
| echo "App version: ${APP_VERSION}" | |
| - name: Create Release | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| RELEASE_TAG="${{ steps.version.outputs.RELEASE_TAG }}" | |
| # 检查release是否已存在 | |
| if gh release view "$RELEASE_TAG" >/dev/null 2>&1; then | |
| echo "Release $RELEASE_TAG already exists, deleting it..." | |
| gh release delete "$RELEASE_TAG" -y || true | |
| git push --delete origin "$RELEASE_TAG" || true | |
| fi | |
| # 创建新的release | |
| echo "Creating release $RELEASE_TAG..." | |
| gh release create "$RELEASE_TAG" \ | |
| --title "Release $RELEASE_TAG" \ | |
| --notes "Automated build for $RELEASE_TAG" \ | |
| --draft=false \ | |
| --prerelease=false | |
| echo "Release $RELEASE_TAG created successfully" | |
| build-android: | |
| name: Build Android APK | |
| needs: prepare | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Set up Go | |
| uses: actions/setup-go@v5 | |
| with: | |
| go-version: '1.25.3' | |
| cache: true | |
| - name: Install Fyne CLI | |
| run: | | |
| go install fyne.io/tools/cmd/fyne@latest | |
| - name: Set up JDK 17 | |
| uses: actions/setup-java@v4 | |
| with: | |
| distribution: 'temurin' | |
| java-version: '17' | |
| - name: Install Android SDK | |
| uses: android-actions/setup-android@v3 | |
| with: | |
| packages: 'platform-tools' | |
| - name: Install Android NDK | |
| run: | | |
| sdkmanager --install "ndk;25.2.9519653" | |
| echo "ANDROID_NDK_HOME=$ANDROID_SDK_ROOT/ndk/25.2.9519653" >> $GITHUB_ENV | |
| - name: Install Android Build Tools | |
| run: | | |
| sdkmanager --install "build-tools;34.0.0" | |
| sdkmanager --install "platforms;android-34" | |
| - name: Configure Git for Private Modules | |
| run: | | |
| git config --global url."https://${{ secrets.GHT }}@github.com/".insteadOf "https://github.com/" | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GHT }} | |
| - name: Download dependencies | |
| run: go mod download | |
| env: | |
| GOPRIVATE: github.com/oneclickvirt/security,github.com/oneclickvirt/privatespeedtest | |
| - name: Verify dependencies | |
| run: go mod verify | |
| - name: Update FyneApp.toml version | |
| run: | | |
| sed -i "s/Version = .*/Version = \"${{ needs.prepare.outputs.version }}\"/" FyneApp.toml | |
| cat FyneApp.toml | |
| - name: Build Android APK (arm64) | |
| env: | |
| ANDROID_NDK_HOME: ${{ env.ANDROID_NDK_HOME }} | |
| GOPRIVATE: github.com/oneclickvirt/security,github.com/oneclickvirt/privatespeedtest | |
| FYNE_BUILD_FLAGS: "-trimpath -ldflags '-s -w'" | |
| run: | | |
| fyne package --os android --app-id com.oneclickvirt.ecsgui --app-version "${{ needs.prepare.outputs.app_version }}" --release | |
| if [ -f *.apk ]; then | |
| mv *.apk ecs-gui-android-arm64-${{ needs.prepare.outputs.version }}.apk | |
| echo "ARM64 APK 构建成功" | |
| ls -lh ecs-gui-android-arm64-${{ needs.prepare.outputs.version }}.apk | |
| else | |
| echo "ARM64 APK 构建失败" | |
| exit 1 | |
| fi | |
| - name: Build Android APK (x86_64) | |
| env: | |
| ANDROID_NDK_HOME: ${{ env.ANDROID_NDK_HOME }} | |
| GOPRIVATE: github.com/oneclickvirt/security,github.com/oneclickvirt/privatespeedtest | |
| FYNE_BUILD_FLAGS: "-trimpath -ldflags '-s -w'" | |
| run: | | |
| fyne package --os android/amd64 --app-id com.oneclickvirt.ecsgui --app-version "${{ needs.prepare.outputs.app_version }}" --release | |
| # 查找新生成的 APK(排除已重命名的 arm64 版本) | |
| NEW_APK=$(find . -maxdepth 1 -name "*.apk" -not -name "ecs-gui-android-*" -type f | head -n 1) | |
| if [ -n "$NEW_APK" ]; then | |
| mv "$NEW_APK" ecs-gui-android-x86_64-${{ needs.prepare.outputs.version }}.apk | |
| echo "x86_64 APK 构建成功" | |
| ls -lh ecs-gui-android-x86_64-${{ needs.prepare.outputs.version }}.apk | |
| else | |
| echo "x86_64 APK 构建失败" | |
| exit 1 | |
| fi | |
| - name: Install UPX | |
| run: | | |
| wget -q https://github.com/upx/upx/releases/download/v4.2.1/upx-4.2.1-amd64_linux.tar.xz | |
| tar -xf upx-4.2.1-amd64_linux.tar.xz | |
| sudo mv upx-4.2.1-amd64_linux/upx /usr/local/bin/ | |
| upx --version | |
| - name: Compress APK with UPX | |
| run: | | |
| for apk in goecs-gui-android-*.apk; do | |
| if [ -f "$apk" ]; then | |
| echo "压缩前大小: $(du -h "$apk")" | |
| # 解压 APK | |
| unzip -q "$apk" -d "${apk%.apk}_extracted" | |
| # 对 lib 目录下的 .so 文件使用 UPX 压缩 | |
| find "${apk%.apk}_extracted/lib" -name "*.so" -type f | while read so_file; do | |
| echo "正在压缩: $so_file" | |
| upx --best --lzma "$so_file" || echo "警告: $so_file 压缩失败,继续处理" | |
| done | |
| # 重新打包 APK | |
| cd "${apk%.apk}_extracted" | |
| zip -r -q "../${apk%.apk}_compressed.apk" . | |
| cd .. | |
| # 替换原 APK | |
| mv "${apk%.apk}_compressed.apk" "$apk" | |
| rm -rf "${apk%.apk}_extracted" | |
| echo "压缩后大小: $(du -h "$apk")" | |
| fi | |
| done | |
| - name: Download signing keystore | |
| run: | | |
| # 使用 GitHub Token 下载私有仓库的 JKS 文件 | |
| curl -L \ | |
| -H "Authorization: token ${{ secrets.GHT }}" \ | |
| -H "Accept: application/vnd.github.v3.raw" \ | |
| -o oneclickvirt.jks \ | |
| "https://api.github.com/repos/oneclickvirt/jks/contents/oneclickvirt.jks" | |
| # 验证文件是否下载成功 | |
| if [ -f oneclickvirt.jks ]; then | |
| echo "✓ JKS 文件下载成功" | |
| ls -lh oneclickvirt.jks | |
| else | |
| echo "✗ JKS 文件下载失败" | |
| exit 1 | |
| fi | |
| - name: Sign APK files | |
| run: | | |
| # 安装 apksigner (包含在 Android build-tools 中) | |
| BUILD_TOOLS_VERSION="34.0.0" | |
| APKSIGNER="$ANDROID_SDK_ROOT/build-tools/$BUILD_TOOLS_VERSION/apksigner" | |
| if [ ! -f "$APKSIGNER" ]; then | |
| echo "✗ apksigner 未找到" | |
| exit 1 | |
| fi | |
| echo "使用 apksigner: $APKSIGNER" | |
| # 对所有 APK 文件进行签名 | |
| for apk in ecs-gui-android-*.apk; do | |
| if [ -f "$apk" ]; then | |
| echo "正在签名: $apk" | |
| SIGNED_APK="${apk%.apk}_signed.apk" | |
| # 签名 APK | |
| $APKSIGNER sign \ | |
| --ks oneclickvirt.jks \ | |
| --ks-key-alias gui \ | |
| --ks-pass pass:${{ secrets.KEYSTORE_PASSWORD }} \ | |
| --key-pass pass:${{ secrets.KEY_PASSWORD }} \ | |
| --out "$SIGNED_APK" \ | |
| "$apk" | |
| # 验证签名 | |
| $APKSIGNER verify "$SIGNED_APK" | |
| if [ $? -eq 0 ]; then | |
| echo "✓ $apk 签名成功" | |
| # 替换原始 APK | |
| mv "$SIGNED_APK" "$apk" | |
| else | |
| echo "✗ $apk 签名验证失败" | |
| exit 1 | |
| fi | |
| fi | |
| done | |
| # 清理 JKS 文件 | |
| rm -f oneclickvirt.jks | |
| - name: List build artifacts | |
| run: | | |
| ls -lh ecs-gui-android-*.apk | |
| du -sh ecs-gui-android-*.apk | |
| - name: Upload to release | |
| shell: bash | |
| run: | | |
| RELEASE_TAG="${{ needs.prepare.outputs.release_tag }}" | |
| for file in ecs-gui-android-*.apk; do | |
| if [ -f "$file" ]; then | |
| echo "Uploading $file to release $RELEASE_TAG" | |
| gh release upload "$RELEASE_TAG" "$file" --clobber | |
| fi | |
| done | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Upload ARM64 APK | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ecs-gui-android-arm64-${{ needs.prepare.outputs.version }} | |
| path: ecs-gui-android-arm64-${{ needs.prepare.outputs.version }}.apk | |
| retention-days: 90 | |
| - name: Upload x86_64 APK | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ecs-gui-android-x86_64-${{ needs.prepare.outputs.version }} | |
| path: ecs-gui-android-x86_64-${{ needs.prepare.outputs.version }}.apk | |
| retention-days: 90 | |
| build-desktop: | |
| name: Build Desktop Apps | |
| needs: prepare | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| # macOS builds | |
| - os: macos-latest | |
| platform: darwin | |
| arch: arm64 | |
| name: macos-arm64 | |
| - os: macos-15-intel | |
| platform: darwin | |
| arch: amd64 | |
| name: macos-amd64 | |
| # Windows builds | |
| - os: windows-latest | |
| platform: windows | |
| arch: amd64 | |
| name: windows-amd64 | |
| # Linux builds | |
| - os: ubuntu-24.04-arm | |
| platform: linux | |
| arch: arm64 | |
| name: linux-arm64 | |
| - os: ubuntu-latest | |
| platform: linux | |
| arch: amd64 | |
| name: linux-amd64 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Set up Go | |
| uses: actions/setup-go@v5 | |
| with: | |
| go-version: '1.25.3' | |
| cache: true | |
| - name: Install Fyne CLI | |
| run: go install fyne.io/tools/cmd/fyne@latest | |
| - name: Install Linux dependencies | |
| if: matrix.platform == 'linux' | |
| run: | | |
| if [ "${{ matrix.arch }}" == "arm64" ]; then | |
| sudo dpkg --add-architecture arm64 | |
| sudo apt-get update | |
| sudo apt-get install -y gcc-aarch64-linux-gnu libc6-dev-arm64-cross | |
| sudo apt-get install -y libgl1-mesa-dev xorg-dev | |
| sudo apt-get install -y \ | |
| libgl1-mesa-dev:arm64 \ | |
| libx11-dev:arm64 \ | |
| libxrandr-dev:arm64 \ | |
| libxxf86vm-dev:arm64 \ | |
| libxi-dev:arm64 \ | |
| libxcursor-dev:arm64 \ | |
| libxinerama-dev:arm64 | |
| else | |
| # AMD64 使用默认源 | |
| sudo apt-get update | |
| sudo apt-get install -y libgl1-mesa-dev xorg-dev | |
| fi | |
| - name: Configure Git for Private Modules | |
| run: | | |
| git config --global url."https://${{ secrets.GHT }}@github.com/".insteadOf "https://github.com/" | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GHT }} | |
| - name: Download dependencies | |
| run: go mod download | |
| env: | |
| GOPRIVATE: github.com/oneclickvirt/security,github.com/oneclickvirt/privatespeedtest | |
| - name: Verify dependencies | |
| run: go mod verify | |
| - name: Update FyneApp.toml version | |
| shell: bash | |
| run: | | |
| if [ "${{ runner.os }}" == "macOS" ]; then | |
| sed -i '' "s/Version = .*/Version = \"${{ needs.prepare.outputs.version }}\"/" FyneApp.toml | |
| else | |
| sed -i "s/Version = .*/Version = \"${{ needs.prepare.outputs.version }}\"/" FyneApp.toml | |
| fi | |
| cat FyneApp.toml | |
| - name: Install UPX | |
| shell: bash | |
| run: | | |
| if [ "${{ runner.os }}" == "macOS" ]; then | |
| brew install upx | |
| upx --version | |
| elif [ "${{ runner.os }}" == "Windows" ]; then | |
| echo "跳过 Windows UPX 安装(不支持)" | |
| fi | |
| - name: Build for ${{ matrix.name }} | |
| env: | |
| GOPRIVATE: github.com/oneclickvirt/security,github.com/oneclickvirt/privatespeedtest | |
| FYNE_BUILD_FLAGS: "-trimpath -ldflags '-s -w -checklinkname=0'" | |
| GOARCH: ${{ matrix.arch }} | |
| shell: bash | |
| run: | | |
| # macOS 需要特殊处理:先编译再打包 | |
| if [ "${{ matrix.platform }}" == "darwin" ]; then | |
| # 使用独立目录避免冲突 | |
| BUILD_DIR="build-${{ matrix.name }}" | |
| mkdir -p "$BUILD_DIR" | |
| echo "Building macOS binary with ldflags..." | |
| go build -trimpath -ldflags "-checklinkname=0 -s -w" -o "$BUILD_DIR/ecs-gui-bin" . | |
| echo "Packaging macOS app with fyne..." | |
| # 在根目录运行 fyne package,指定编译好的二进制文件路径 | |
| fyne package -os darwin -name ecs-gui --exe "$BUILD_DIR/ecs-gui-bin" --app-version "${{ needs.prepare.outputs.app_version }}" --release | |
| if [ -d "ecs-gui.app" ]; then | |
| TARFILE="ecs-gui-${{ matrix.name }}-${{ needs.prepare.outputs.version }}.tar.gz" | |
| echo "Creating tar file: $TARFILE" | |
| tar -czf "$TARFILE" ecs-gui.app | |
| echo "✓ macOS app 构建成功" | |
| # 清理构建目录和生成的 app | |
| rm -rf "$BUILD_DIR" ecs-gui.app | |
| else | |
| echo "✗ macOS app 构建失败" | |
| exit 1 | |
| fi | |
| elif [ "${{ matrix.platform }}" == "linux" ]; then | |
| # Linux 构建 - 使用独立目录避免冲突 | |
| BUILD_DIR="build-${{ matrix.name }}" | |
| mkdir -p "$BUILD_DIR" | |
| echo "Building Linux binary for ${{ matrix.arch }}..." | |
| if [ "${{ matrix.arch }}" == "arm64" ]; then | |
| # ARM64 交叉编译 | |
| CC=aarch64-linux-gnu-gcc CGO_ENABLED=1 GOOS=linux GOARCH=arm64 \ | |
| go build -trimpath -ldflags "-checklinkname=0 -s -w" -o "$BUILD_DIR/ecs-gui" . | |
| else | |
| # AMD64 本地编译 | |
| CGO_ENABLED=1 GOOS=linux GOARCH=amd64 \ | |
| go build -trimpath -ldflags "-checklinkname=0 -s -w" -o "$BUILD_DIR/ecs-gui" . | |
| fi | |
| echo "Packaging Linux app with fyne..." | |
| # 在根目录运行 fyne package,指定编译好的二进制文件路径 | |
| fyne package -os linux -name ecs-gui --exe "$BUILD_DIR/ecs-gui" --app-version "${{ needs.prepare.outputs.app_version }}" --release | |
| # 查找生成的 tar.xz 文件 | |
| if [ -f "ecs-gui.tar.xz" ]; then | |
| mv "ecs-gui.tar.xz" ecs-gui-${{ matrix.name }}-${{ needs.prepare.outputs.version }}.tar.xz | |
| echo "✓ Linux tar.xz 构建成功" | |
| # 清理构建目录 | |
| rm -rf "$BUILD_DIR" | |
| else | |
| echo "✗ Linux tar.xz 构建失败" | |
| ls -la . | |
| exit 1 | |
| fi | |
| else | |
| # Windows 构建 | |
| echo "Building ${{ matrix.platform }} with fyne package..." | |
| # 在根目录运行 fyne package | |
| fyne package -os ${{ matrix.platform }} -name ecs-gui --app-version "${{ needs.prepare.outputs.app_version }}" --release | |
| if [ "${{ matrix.platform }}" == "windows" ]; then | |
| if [ -f "ecs-gui.exe" ]; then | |
| mv "ecs-gui.exe" ecs-gui-${{ matrix.name }}-${{ needs.prepare.outputs.version }}.exe | |
| echo "✓ Windows exe 构建成功" | |
| else | |
| echo "✗ Windows exe 构建失败" | |
| exit 1 | |
| fi | |
| fi | |
| fi | |
| - name: Compress with UPX | |
| shell: bash | |
| run: | | |
| # Windows 跳过 UPX 压缩 | |
| if [ "${{ runner.os }}" == "Windows" ]; then | |
| echo "跳过 Windows exe 的 UPX 压缩(不支持)" | |
| exit 0 | |
| fi | |
| for file in ecs-gui-${{ matrix.name }}-*; do | |
| if [ -f "$file" ]; then | |
| echo "压缩前大小: $(du -h "$file")" | |
| if [[ "$file" == *.tar.gz ]]; then | |
| # macOS app 需要解压后压缩二进制 | |
| echo "正在处理 macOS app: $file" | |
| tar -xzf "$file" | |
| # 查找并压缩 macOS 可执行文件 | |
| if [ -d ecs-gui.app/Contents/MacOS ]; then | |
| for binary in ecs-gui.app/Contents/MacOS/*; do | |
| if [ -f "$binary" ] && [ -x "$binary" ]; then | |
| echo "正在压缩 macOS 二进制: $binary" | |
| upx --best --lzma "$binary" || echo "警告: $binary 压缩失败" | |
| fi | |
| done | |
| fi | |
| # 重新打包 | |
| rm "$file" | |
| tar -czf "$file" ecs-gui.app | |
| rm -rf ecs-gui.app | |
| echo "压缩后大小: $(du -h "$file")" | |
| elif [[ "$file" == *.tar.xz ]]; then | |
| # Linux app 需要解压后压缩二进制 | |
| echo "正在处理 Linux app: $file" | |
| mkdir -p ecs-gui-extracted | |
| tar -xJf "$file" -C ecs-gui-extracted | |
| # 查找并压缩 Linux 可执行文件 | |
| find ecs-gui-extracted -type f -executable | while read binary; do | |
| if file "$binary" | grep -q "ELF"; then | |
| echo "正在压缩 Linux 二进制: $binary" | |
| upx --best --lzma "$binary" || echo "警告: $binary 压缩失败" | |
| fi | |
| done | |
| # 重新打包 | |
| rm "$file" | |
| tar -cJf "$file" -C ecs-gui-extracted . | |
| rm -rf ecs-gui-extracted | |
| echo "压缩后大小: $(du -h "$file")" | |
| fi | |
| fi | |
| done | |
| - name: List build artifacts | |
| shell: bash | |
| run: | | |
| ls -lh ecs-gui-${{ matrix.name }}-* | |
| du -sh ecs-gui-${{ matrix.name }}-* | |
| - name: Upload to release | |
| shell: bash | |
| run: | | |
| RELEASE_TAG="${{ needs.prepare.outputs.release_tag }}" | |
| for file in ecs-gui-${{ matrix.name }}-*; do | |
| if [ -f "$file" ]; then | |
| echo "Uploading $file to release $RELEASE_TAG" | |
| gh release upload "$RELEASE_TAG" "$file" --clobber | |
| fi | |
| done | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Upload build artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ecs-gui-${{ matrix.name }}-${{ needs.prepare.outputs.version }} | |
| path: ecs-gui-${{ matrix.name }}-${{ needs.prepare.outputs.version }}.* | |
| retention-days: 90 |