diff --git a/.github/workflows/package-and-publish.yml b/.github/workflows/package-and-publish.yml index 201d52a31..ff50444f9 100644 --- a/.github/workflows/package-and-publish.yml +++ b/.github/workflows/package-and-publish.yml @@ -340,16 +340,37 @@ jobs: - name: Re-sign macOS app after TDLib bundling if: matrix.settings.platform == 'macos-latest' env: + APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE_BASE64 }} + APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} run: | TARGET="${{ matrix.settings.target }}" echo "Re-signing for target: $TARGET" - if [ -z "$APPLE_SIGNING_IDENTITY" ]; then - echo "No signing identity provided, skipping re-sign" + if [ -z "$APPLE_SIGNING_IDENTITY" ] || [ -z "$APPLE_CERTIFICATE" ]; then + echo "No signing identity or certificate provided, skipping re-sign" exit 0 fi + # Import certificate into a temporary keychain (same approach as tauri-action) + KEYCHAIN_PATH="$RUNNER_TEMP/tdlib-signing.keychain-db" + KEYCHAIN_PASSWORD=$(openssl rand -base64 32) + + # Create and configure keychain + security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH" + security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + + # Import certificate + CERT_PATH="$RUNNER_TEMP/certificate.p12" + echo "$APPLE_CERTIFICATE" | base64 --decode > "$CERT_PATH" + security import "$CERT_PATH" -P "$APPLE_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH" + rm "$CERT_PATH" + + # Add keychain to search list + security list-keychains -d user -s "$KEYCHAIN_PATH" $(security list-keychains -d user | tr -d '"') + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + # Find app bundle in target-specific or default location if [ -n "$TARGET" ]; then APP_BUNDLE=$(find "src-tauri/target/$TARGET/release/bundle/macos" -name "*.app" -type d 2>/dev/null | head -1) @@ -364,16 +385,22 @@ jobs: for dylib in "$APP_BUNDLE/Contents/Frameworks/"*.dylib; do if [ -f "$dylib" ]; then echo "Signing: $dylib" - codesign --force --options runtime --sign "$APPLE_SIGNING_IDENTITY" "$dylib" || true + codesign --force --options runtime --sign "$APPLE_SIGNING_IDENTITY" "$dylib" fi done # Re-sign the entire app bundle - codesign --force --deep --options runtime --sign "$APPLE_SIGNING_IDENTITY" "$APP_BUNDLE" || true + codesign --force --deep --options runtime --sign "$APPLE_SIGNING_IDENTITY" "$APP_BUNDLE" echo "App re-signed successfully" + + # Verify signature + codesign --verify --verbose=2 "$APP_BUNDLE" || echo "Warning: Signature verification had issues" else echo "Warning: No app bundle found to re-sign" fi + # Cleanup keychain + security delete-keychain "$KEYCHAIN_PATH" || true + # Re-create DMG after TDLib bundling (the original DMG was created before bundling) - name: Recreate DMG with bundled TDLib if: matrix.settings.platform == 'macos-latest' diff --git a/.github/workflows/package-android.yml b/.github/workflows/package-android.yml index 53ade7a33..82be4000c 100644 --- a/.github/workflows/package-android.yml +++ b/.github/workflows/package-android.yml @@ -87,14 +87,14 @@ jobs: if [ "$HTTP_CODE" = "200" ]; then RELEASE_ID=$(echo "$BODY" | jq -r '.id') IS_DRAFT=$(echo "$BODY" | jq -r '.draft') - # Check if Android APK is already uploaded - APK_EXISTS=$(echo "$BODY" | jq -r '.assets[]? | select(.name | endswith(".apk")) | .name' | head -1) - if [ -n "$APK_EXISTS" ]; then - echo "Android APK already exists in release: $APK_EXISTS" + # Check if Android AAB is already uploaded + AAB_EXISTS=$(echo "$BODY" | jq -r '.assets[]? | select(.name | endswith(".aab")) | .name' | head -1) + if [ -n "$AAB_EXISTS" ]; then + echo "Android AAB already exists in release: $AAB_EXISTS" echo "should-skip=true" >> $GITHUB_OUTPUT echo "release-id=$RELEASE_ID" >> $GITHUB_OUTPUT else - echo "Release found (ID: $RELEASE_ID, draft: $IS_DRAFT), no APK yet" + echo "Release found (ID: $RELEASE_ID, draft: $IS_DRAFT), no AAB yet" echo "should-skip=false" >> $GITHUB_OUTPUT echo "release-id=$RELEASE_ID" >> $GITHUB_OUTPUT fi @@ -108,7 +108,7 @@ jobs: echo "release-id=" >> $GITHUB_OUTPUT fi build-android: - name: Build and package Android APK + name: Build and package Android AAB needs: [get-version, check-release] if: ${{ !failure() && !cancelled() && needs.check-release.outputs.should-skip != 'true' }} environment: ${{ github.ref == 'refs/heads/main' && 'Production' || '' }} @@ -191,7 +191,7 @@ jobs: ANDROID_KEYSTORE: ${{ secrets.ANDROID_KEYSTORE }} run: | echo "$ANDROID_KEYSTORE" | base64 --decode > ${{ github.workspace }}/release.jks - - name: Build Android APK (signed) + - name: Build Android AAB (signed) if: needs.get-version.outputs.should-publish != '' env: BASE_URL: ${{ vars.BASE_URL }} @@ -199,54 +199,54 @@ jobs: ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} - run: npx tauri android build --apk true + run: npx tauri android build --aab true - - name: Build Android APK (unsigned) + - name: Build Android AAB (unsigned) if: needs.get-version.outputs.should-publish == '' env: BASE_URL: ${{ vars.BASE_URL }} - run: npx tauri android build --apk true + run: npx tauri android build --aab true - - name: Find APK - id: find-apk + - name: Find AAB + id: find-aab run: | - APK_ROOT="src-tauri/gen/android" - echo "Searching for APKs in $APK_ROOT..." - find "$APK_ROOT" -name '*.apk' -type f | sort - # Prefer universal release, then any signed release, then any APK (excluding unsigned) - APK_PATH=$(find "$APK_ROOT" -name '*-universal-release.apk' -type f | head -1) - if [ -z "$APK_PATH" ]; then - APK_PATH=$(find "$APK_ROOT" -name '*-release.apk' -not -name '*-unsigned.apk' -type f | head -1) - fi - if [ -z "$APK_PATH" ]; then - APK_PATH=$(find "$APK_ROOT" -name '*-release*.apk' -not -name '*-unsigned.apk' -type f | head -1) - fi - if [ -z "$APK_PATH" ]; then - APK_PATH=$(find "$APK_ROOT" -name '*.apk' -not -name '*-unsigned.apk' -type f | head -1) - fi - if [ -z "$APK_PATH" ]; then - APK_PATH=$(find "$APK_ROOT" -name '*.apk' -type f | head -1) - fi - if [ -z "$APK_PATH" ]; then - echo "No APK found" + AAB_ROOT="src-tauri/gen/android" + echo "Searching for AABs in $AAB_ROOT..." + find "$AAB_ROOT" -name '*.aab' -type f | sort + AAB_PATH=$(find "$AAB_ROOT" -name '*.aab' -type f | head -1) + if [ -z "$AAB_PATH" ]; then + echo "No AAB found" exit 1 fi - APK_FILENAME=$(basename "$APK_PATH") - echo "Selected APK: $APK_PATH" - echo "path=$APK_PATH" >> $GITHUB_OUTPUT - echo "filename=$APK_FILENAME" >> $GITHUB_OUTPUT - - name: Upload APK artifact + AAB_FILENAME=$(basename "$AAB_PATH") + echo "Selected AAB: $AAB_PATH" + echo "path=$AAB_PATH" >> $GITHUB_OUTPUT + echo "filename=$AAB_FILENAME" >> $GITHUB_OUTPUT + - name: Upload AAB artifact uses: actions/upload-artifact@v4 with: - name: android-apk - path: ${{ steps.find-apk.outputs.path }} + name: android-aab + path: ${{ steps.find-aab.outputs.path }} - - name: Upload APK to release + - name: Upload AAB to release if: needs.get-version.outputs.should-publish == 'true' && needs.check-release.outputs.release-id != '' env: RELEASE_ID: ${{ needs.check-release.outputs.release-id }} run: | curl -X POST -H "Authorization: Bearer $XGH_TOKEN" \ -H "Content-Type: application/octet-stream" \ - --data-binary "@${{ steps.find-apk.outputs.path }}" \ - "https://uploads.github.com/repos/$PUBLISH_REPO/releases/$RELEASE_ID/assets?name=${{ steps.find-apk.outputs.filename }}" + --data-binary "@${{ steps.find-aab.outputs.path }}" \ + "https://uploads.github.com/repos/$PUBLISH_REPO/releases/$RELEASE_ID/assets?name=${{ steps.find-aab.outputs.filename }}" + + # Requires secret GOOGLE_PLAY_SERVICE_ACCOUNT_JSON (full JSON key from Play Console API access) + # Track: internal | alpha | beta | production (use vars.GOOGLE_PLAY_TRACK to override) + - name: Upload AAB to Google Play + if: needs.get-version.outputs.should-publish == 'true' && secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON != '' + uses: r0adkll/upload-google-play@v1 + with: + serviceAccountJsonPlainText: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON }} + packageName: com.alphahuman.app + releaseFiles: ${{ steps.find-aab.outputs.path }} + track: ${{ vars.GOOGLE_PLAY_TRACK || 'internal' }} + releaseName: AlphaHuman v${{ needs.get-version.outputs.package-version }} + status: completed diff --git a/src-tauri/scripts/bundle-tdlib-macos.sh b/src-tauri/scripts/bundle-tdlib-macos.sh index f04f1b7e1..99d5ac53c 100755 --- a/src-tauri/scripts/bundle-tdlib-macos.sh +++ b/src-tauri/scripts/bundle-tdlib-macos.sh @@ -184,13 +184,30 @@ for dylib in "${FRAMEWORKS_DIR}"/*.dylib; do fi done -# Add rpath to the binary to look in Frameworks +# Fix rpaths in the binary echo "" -echo "Adding rpath to binary..." -# First, try to delete existing rpath if it exists (ignore errors) +echo "Fixing binary rpaths..." + +# Get all current rpaths +CURRENT_RPATHS=$(otool -l "$BINARY_PATH" | grep -A2 "cmd LC_RPATH" | grep "path " | awk '{print $2}') + +echo "Current rpaths:" +echo "$CURRENT_RPATHS" + +# Remove all rpaths that point to build directories (they won't exist at runtime) +for rpath in $CURRENT_RPATHS; do + # Remove rpaths containing /build/, /target/, or absolute paths that aren't @executable_path + if [[ "$rpath" == *"/build/"* ]] || [[ "$rpath" == *"/target/"* ]] || [[ "$rpath" != @* ]]; then + echo "Removing build-time rpath: $rpath" + install_name_tool -delete_rpath "$rpath" "$BINARY_PATH" 2>/dev/null || true + fi +done + +# Delete our target rpath first if it exists (to avoid "already exists" error) install_name_tool -delete_rpath "@executable_path/../Frameworks" "$BINARY_PATH" 2>/dev/null || true -# Add the correct rpath +# Add the correct rpath for the bundled Frameworks +echo "Adding rpath: @executable_path/../Frameworks" install_name_tool -add_rpath "@executable_path/../Frameworks" "$BINARY_PATH" # Verify the changes