Build and Release #46
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: Build and Release | |
| on: | |
| release: | |
| types: [published] | |
| permissions: | |
| contents: write | |
| env: | |
| DOTNET_VERSION: '9.0.x' | |
| DOTNET_MULTILEVEL_LOOKUP: 0 | |
| DOTNET_NOLOGO: true | |
| jobs: | |
| # ========================================== | |
| # Build Server (TelegramDownloader) | |
| # ========================================== | |
| build-server: | |
| name: Build Server | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: '📄 Checkout' | |
| uses: actions/checkout@v4 | |
| - name: '🔧 Setup .NET 10' | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: '10.0.x' | |
| - name: '🔍 Verify SDK version' | |
| run: | | |
| # Remove global.json to allow .NET 10 for server build | |
| rm -f global.json | |
| dotnet --version | |
| dotnet --list-sdks | |
| - name: '📦 Extract version' | |
| id: version | |
| run: | | |
| TAG=${{ github.event.release.tag_name }} | |
| VERSION=${TAG#v} | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| echo "tag=$TAG" >> $GITHUB_OUTPUT | |
| - name: '🔨 Build all server platforms' | |
| run: | | |
| VERSION=${{ steps.version.outputs.version }} | |
| platforms=("win-x64" "win-x86" "win-arm64" "linux-x64" "linux-arm" "linux-arm64" "osx-x64" "osx-arm64") | |
| for platform in "${platforms[@]}"; do | |
| echo "Building $platform..." | |
| dotnet publish TelegramDownloader/TelegramDownloader.csproj \ | |
| -r $platform \ | |
| -c Release \ | |
| -o bin/server-$platform \ | |
| -p:PublishSingleFile=true \ | |
| -p:Version=$VERSION \ | |
| --self-contained | |
| done | |
| - name: '📦 Create server ZIP archives' | |
| run: | | |
| TAG=${{ steps.version.outputs.tag }} | |
| mkdir -p releases | |
| for dir in bin/server-*; do | |
| platform=$(basename $dir | sed 's/server-//') | |
| zip -r "releases/TelegramFileManager-Server-${TAG}-${platform}.zip" "$dir" | |
| done | |
| ls -la releases/ | |
| - name: '📤 Upload server artifacts' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: server-binaries | |
| path: releases/*.zip | |
| retention-days: 1 | |
| # ========================================== | |
| # Build Android APK (Signed) | |
| # ========================================== | |
| build-android: | |
| name: Build Android APK | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: '📄 Checkout' | |
| uses: actions/checkout@v4 | |
| - name: '🔧 Setup .NET' | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: ${{ env.DOTNET_VERSION }} | |
| - name: '🔍 Verify SDK version' | |
| run: | | |
| dotnet --version | |
| dotnet --list-sdks | |
| dotnet workload list | |
| - name: '🧹 Clean existing workloads' | |
| run: dotnet workload clean --all || true | |
| - name: '📱 Install Android workload' | |
| run: dotnet workload install android maui-android --skip-sign-check | |
| - name: '📦 Extract version' | |
| id: version | |
| run: | | |
| TAG=${{ github.event.release.tag_name }} | |
| VERSION=${TAG#v} | |
| # Extract version number for Android (must be integer) | |
| VERSION_CODE=$(echo $VERSION | sed 's/\.//g' | sed 's/[^0-9]//g') | |
| # If empty or starts with 0, use date-based version | |
| if [ -z "$VERSION_CODE" ] || [ "${VERSION_CODE:0:1}" = "0" ]; then | |
| VERSION_CODE=$(date +%Y%m%d) | |
| fi | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| echo "version_code=$VERSION_CODE" >> $GITHUB_OUTPUT | |
| echo "tag=$TAG" >> $GITHUB_OUTPUT | |
| - name: '🔐 Decode Keystore' | |
| if: ${{ env.ANDROID_KEYSTORE_BASE64 != '' }} | |
| env: | |
| ANDROID_KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }} | |
| run: | | |
| echo "$ANDROID_KEYSTORE_BASE64" | base64 -d > tfmaudio.keystore | |
| echo "ANDROID_KEYSTORE_PATH=$(pwd)/tfmaudio.keystore" >> $GITHUB_ENV | |
| - name: '🔨 Build Android APK (Signed)' | |
| if: ${{ env.ANDROID_KEYSTORE_PATH != '' }} | |
| env: | |
| ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} | |
| ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} | |
| ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} | |
| run: | | |
| VERSION=${{ steps.version.outputs.version }} | |
| VERSION_CODE=${{ steps.version.outputs.version_code }} | |
| dotnet publish TFMAudioApp/TFMAudioApp.csproj \ | |
| -c Release \ | |
| -f net9.0-android \ | |
| -p:BuildSingleTarget=android \ | |
| -p:ApplicationDisplayVersion=$VERSION \ | |
| -p:ApplicationVersion=$VERSION_CODE \ | |
| -p:AndroidKeyStore=true \ | |
| -p:AndroidSigningKeyStore=$ANDROID_KEYSTORE_PATH \ | |
| -p:AndroidSigningKeyAlias=$ANDROID_KEY_ALIAS \ | |
| -p:AndroidSigningKeyPass=$ANDROID_KEY_PASSWORD \ | |
| -p:AndroidSigningStorePass=$ANDROID_KEYSTORE_PASSWORD | |
| - name: '🔨 Build Android APK (Unsigned - for testing)' | |
| if: ${{ env.ANDROID_KEYSTORE_PATH == '' }} | |
| run: | | |
| VERSION=${{ steps.version.outputs.version }} | |
| VERSION_CODE=${{ steps.version.outputs.version_code }} | |
| echo "⚠️ Building unsigned APK (no keystore configured)" | |
| dotnet publish TFMAudioApp/TFMAudioApp.csproj \ | |
| -c Release \ | |
| -f net9.0-android \ | |
| -p:BuildSingleTarget=android \ | |
| -p:ApplicationDisplayVersion=$VERSION \ | |
| -p:ApplicationVersion=$VERSION_CODE | |
| - name: '📦 Prepare APK for release' | |
| run: | | |
| TAG=${{ steps.version.outputs.tag }} | |
| mkdir -p releases | |
| # Find the APK (signed or unsigned) | |
| APK_PATH=$(find TFMAudioApp/bin/Release -name "*.apk" | head -1) | |
| if [ -n "$APK_PATH" ]; then | |
| cp "$APK_PATH" "releases/TFMAudioApp-${TAG}.apk" | |
| echo "✅ APK prepared: releases/TFMAudioApp-${TAG}.apk" | |
| ls -la releases/ | |
| else | |
| echo "❌ No APK found!" | |
| exit 1 | |
| fi | |
| - name: '📤 Upload Android artifact' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: android-apk | |
| path: releases/*.apk | |
| retention-days: 1 | |
| # ========================================== | |
| # Build Windows App | |
| # ========================================== | |
| build-windows: | |
| name: Build Windows App | |
| runs-on: windows-2022 | |
| steps: | |
| - name: '📄 Checkout' | |
| uses: actions/checkout@v4 | |
| - name: '🔧 Setup .NET' | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: ${{ env.DOTNET_VERSION }} | |
| - name: '🔍 Verify SDK version' | |
| run: | | |
| dotnet --version | |
| dotnet --list-sdks | |
| dotnet workload list | |
| - name: '🧹 Clean existing workloads' | |
| run: dotnet workload clean --all | |
| - name: '🪟 Install MAUI workload' | |
| run: dotnet workload install maui-windows --skip-sign-check | |
| - name: '📦 Extract version' | |
| id: version | |
| shell: bash | |
| run: | | |
| TAG=${{ github.event.release.tag_name }} | |
| VERSION=${TAG#v} | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| echo "tag=$TAG" >> $GITHUB_OUTPUT | |
| - name: '🔨 Build Windows App' | |
| run: | | |
| $VERSION = "${{ steps.version.outputs.version }}" | |
| dotnet publish TFMAudioApp/TFMAudioApp.csproj ` | |
| -c Release ` | |
| -f net9.0-windows10.0.19041.0 ` | |
| -p:BuildSingleTarget=windows ` | |
| -p:ApplicationDisplayVersion=$VERSION ` | |
| -p:WindowsPackageType=None ` | |
| -p:PublishSingleFile=false ` | |
| -o bin/windows-app | |
| - name: '🔐 Sign Windows App (if certificate available)' | |
| shell: pwsh | |
| env: | |
| WINDOWS_CERT_BASE64: ${{ secrets.WINDOWS_CERT_BASE64 }} | |
| WINDOWS_CERT_PASSWORD: ${{ secrets.WINDOWS_CERT_PASSWORD }} | |
| run: | | |
| # Check if certificate is configured | |
| if ([string]::IsNullOrEmpty($env:WINDOWS_CERT_BASE64)) { | |
| Write-Host "⚠️ No Windows certificate configured, skipping signing" | |
| exit 0 | |
| } | |
| # Decode certificate | |
| $certBytes = [Convert]::FromBase64String($env:WINDOWS_CERT_BASE64) | |
| $certPath = "$(Get-Location)\code_signing.pfx" | |
| [IO.File]::WriteAllBytes($certPath, $certBytes) | |
| # Find signtool | |
| $signTool = Get-ChildItem -Path "C:\Program Files (x86)\Windows Kits\10\bin" -Recurse -Filter "signtool.exe" | | |
| Where-Object { $_.FullName -match "x64" } | | |
| Select-Object -First 1 -ExpandProperty FullName | |
| if ($signTool) { | |
| # Sign all EXE and DLL files | |
| Get-ChildItem -Path "bin\windows-app" -Include "*.exe","*.dll" -Recurse | ForEach-Object { | |
| & $signTool sign /f $certPath /p $env:WINDOWS_CERT_PASSWORD /t http://timestamp.digicert.com /fd SHA256 $_.FullName | |
| } | |
| Write-Host "✅ Windows app signed successfully" | |
| } else { | |
| Write-Host "⚠️ SignTool not found, skipping signing" | |
| } | |
| # Clean up certificate | |
| if (Test-Path $certPath) { | |
| Remove-Item $certPath -Force | |
| } | |
| - name: '📦 Create Windows ZIP' | |
| shell: bash | |
| run: | | |
| TAG=${{ steps.version.outputs.tag }} | |
| mkdir -p releases | |
| cd bin | |
| 7z a -tzip "../releases/TFMAudioApp-Windows-${TAG}.zip" windows-app/* | |
| cd .. | |
| ls -la releases/ | |
| - name: '📤 Upload Windows artifact' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: windows-app | |
| path: releases/*.zip | |
| retention-days: 1 | |
| # ========================================== | |
| # Build macOS App | |
| # ========================================== | |
| build-macos: | |
| name: Build macOS App | |
| runs-on: macos-13 | |
| steps: | |
| - name: '📄 Checkout' | |
| uses: actions/checkout@v4 | |
| - name: '🔧 Setup .NET' | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: '9.0.200' | |
| - name: '🔍 Verify SDK version' | |
| run: | | |
| dotnet --version | |
| dotnet --list-sdks | |
| - name: '🧹 Clean existing workloads and packs' | |
| run: | | |
| # Remove any pre-installed workloads and packs to avoid version conflicts | |
| dotnet workload clean --all || true | |
| rm -rf ~/.dotnet/packs/Microsoft.MacCatalyst.* || true | |
| rm -rf ~/.dotnet/packs/Microsoft.iOS.* || true | |
| rm -rf ~/.dotnet/metadata/workloads || true | |
| - name: '🍎 Install MAUI workload' | |
| run: | | |
| dotnet workload install maui-maccatalyst --skip-sign-check | |
| dotnet workload list | |
| # Show what SDK packs were installed | |
| ls -la ~/.dotnet/packs/ | grep -i catalyst || true | |
| - name: '📦 Extract version' | |
| id: version | |
| run: | | |
| TAG=${{ github.event.release.tag_name }} | |
| VERSION=${TAG#v} | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| echo "tag=$TAG" >> $GITHUB_OUTPUT | |
| - name: '🔨 Build macOS App (Intel x64)' | |
| run: | | |
| VERSION=${{ steps.version.outputs.version }} | |
| dotnet publish TFMAudioApp/TFMAudioApp.csproj \ | |
| -c Release \ | |
| -f net9.0-maccatalyst \ | |
| -r maccatalyst-x64 \ | |
| -p:BuildSingleTarget=maccatalyst \ | |
| -p:ApplicationDisplayVersion=$VERSION \ | |
| -p:CreatePackage=true \ | |
| -o bin/macos-x64 | |
| - name: '🔨 Build macOS App (Apple Silicon arm64)' | |
| run: | | |
| VERSION=${{ steps.version.outputs.version }} | |
| dotnet publish TFMAudioApp/TFMAudioApp.csproj \ | |
| -c Release \ | |
| -f net9.0-maccatalyst \ | |
| -r maccatalyst-arm64 \ | |
| -p:BuildSingleTarget=maccatalyst \ | |
| -p:ApplicationDisplayVersion=$VERSION \ | |
| -p:CreatePackage=true \ | |
| -o bin/macos-arm64 | |
| - name: '📦 Create macOS ZIPs' | |
| run: | | |
| TAG=${{ steps.version.outputs.tag }} | |
| mkdir -p releases | |
| # Package Intel (x64) version | |
| APP_X64=$(find bin/macos-x64 -name "*.app" -type d | head -1) | |
| if [ -n "$APP_X64" ]; then | |
| cd "$(dirname "$APP_X64")" | |
| zip -r "${GITHUB_WORKSPACE}/releases/TFMAudioApp-macOS-Intel-${TAG}.zip" "$(basename "$APP_X64")" | |
| cd "${GITHUB_WORKSPACE}" | |
| echo "✅ macOS Intel app packaged" | |
| fi | |
| # Package Apple Silicon (arm64) version | |
| APP_ARM64=$(find bin/macos-arm64 -name "*.app" -type d | head -1) | |
| if [ -n "$APP_ARM64" ]; then | |
| cd "$(dirname "$APP_ARM64")" | |
| zip -r "${GITHUB_WORKSPACE}/releases/TFMAudioApp-macOS-AppleSilicon-${TAG}.zip" "$(basename "$APP_ARM64")" | |
| cd "${GITHUB_WORKSPACE}" | |
| echo "✅ macOS Apple Silicon app packaged" | |
| fi | |
| ls -la releases/ | |
| - name: '📤 Upload macOS artifact' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: macos-app | |
| path: releases/* | |
| retention-days: 1 | |
| # ========================================== | |
| # Upload all artifacts to Release | |
| # ========================================== | |
| upload-release: | |
| name: Upload to Release | |
| needs: [build-server, build-android, build-windows, build-macos] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: '📥 Download all artifacts' | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: artifacts | |
| - name: '📋 List artifacts' | |
| run: | | |
| echo "Downloaded artifacts:" | |
| find artifacts -type f -name "*.*" | head -50 | |
| - name: '🚀 Upload to GitHub Release' | |
| env: | |
| GH_TOKEN: ${{ secrets.TOKENBuild }} | |
| GH_REPO: ${{ github.repository }} | |
| run: | | |
| TAG=${{ github.event.release.tag_name }} | |
| # Function to upload with retry | |
| upload_with_retry() { | |
| local file=$1 | |
| local max_attempts=5 | |
| local attempt=1 | |
| local delay=10 | |
| while [ $attempt -le $max_attempts ]; do | |
| echo "Uploading $(basename $file) (attempt $attempt/$max_attempts)..." | |
| if gh release upload "$TAG" "$file" --clobber 2>&1; then | |
| echo "✅ Successfully uploaded $(basename $file)" | |
| return 0 | |
| else | |
| echo "⚠️ Upload failed, waiting ${delay}s before retry..." | |
| sleep $delay | |
| delay=$((delay * 2)) | |
| attempt=$((attempt + 1)) | |
| fi | |
| done | |
| echo "❌ Failed to upload $(basename $file) after $max_attempts attempts" | |
| return 1 | |
| } | |
| # Upload all files (zip, apk, pkg) | |
| find artifacts -type f \( -name "*.zip" -o -name "*.apk" -o -name "*.pkg" \) | while read file; do | |
| upload_with_retry "$file" | |
| sleep 3 | |
| done | |
| echo "✅ All uploads completed!" |