Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
197 changes: 147 additions & 50 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -414,62 +414,159 @@ jobs:
path: bundle-artifacts/
continue-on-error: true

- name: Download signed APK from Google Play
- name: Download Google Play Signed APK
id: download-apk
env:
PLAY_STORE_SERVICE_ACCOUNT_JSON: ${{ secrets.PLAY_STORE_SERVICE_ACCOUNT_JSON }}
run: |
# Check if AAB exists
AAB_PATH=$(find bundle-artifacts -name "*.aab" 2>/dev/null | head -1)
if [ -z "$AAB_PATH" ]; then
echo "No AAB found in artifacts, skipping signed APK download"
echo "found=false" >> $GITHUB_OUTPUT
exit 0
fi

echo "Found AAB: $AAB_PATH"

# Download bundletool
echo "Downloading bundletool..."
curl -L -o bundletool-all.jar https://github.com/google/bundletool/releases/latest/download/bundletool-all.jar

# Verify download
if [ ! -f bundletool-all.jar ]; then
echo "Failed to download bundletool"
exit 1
fi

# Create a dummy debug keystore for bundletool (required for APK generation)
echo "Creating dummy keystore..."
keytool -genkey -v -keystore debug.keystore -alias androiddebugkey \
-keyalg RSA -keysize 2048 -validity 10000 \
-dname "CN=Android Debug,O=Android,C=US" \
-storepass android -keypass android

# Generate universal APK from the AAB
echo "Generating universal APK set..."
java -jar bundletool-all.jar build-apks \
--bundle="$AAB_PATH" \
--output=apks.apks \
--mode=universal \
--ks=debug.keystore \
--ks-pass=pass:android \
--ks-key-alias=androiddebugkey \
--key-pass=pass:android

# Extract the universal APK
unzip -q apks.apks universal.apk

VERSION_NAME="${{ needs.prepare.outputs.version }}"
OUTPUT_FILE="v2er-${VERSION_NAME}_google_play_signed.apk"

# Note: This APK is signed with debug key, the actual Google Play signed APK
# is only available after deployment to Play Store
mv universal.apk "$OUTPUT_FILE"
VERSION_CODE="${{ needs.prepare.outputs.version_code }}"
PACKAGE_NAME="me.ghui.v2er"

# Create Python script to download signed universal APK
cat > download_signed_apk.py << 'EOF'
Copy link

Copilot AI Sep 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using heredoc to create a Python script inline poses security risks. Consider storing the Python script as a separate file in the repository to improve maintainability and security review capabilities.

Copilot uses AI. Check for mistakes.
import json
import os
import sys
import requests
from google.oauth2 import service_account
from googleapiclient.discovery import build

def download_signed_apk():
try:
# Load service account credentials
service_account_info = json.loads(os.environ['PLAY_STORE_SERVICE_ACCOUNT_JSON'])
credentials = service_account.Credentials.from_service_account_info(
service_account_info,
scopes=['https://www.googleapis.com/auth/androidpublisher']
)

# Build the service
service = build('androidpublisher', 'v3', credentials=credentials)

package_name = os.environ['PACKAGE_NAME']
version_code = int(os.environ['VERSION_CODE'])

print(f"Attempting to download signed APK for {package_name} version {version_code}")

# Get the signed universal APK download URL
# Note: This requires the app to be released and processed by Google Play
result = service.generatedapks().list(
packageName=package_name,
versionCode=version_code
).execute()

if 'generatedApks' not in result or not result['generatedApks']:
print("No generated APKs found. App may not be processed yet by Google Play.")
return False

# Find universal APK
universal_apk = None
for apk in result['generatedApks']:
if apk.get('targetingInfo', {}).get('abiTargeting') is None:
# This should be the universal APK
universal_apk = apk
break

if not universal_apk:
print("Universal APK not found in generated APKs")
return False

# Download the APK
download_url = universal_apk.get('downloadUrl')
if not download_url:
print("Download URL not available for universal APK")
return False

print(f"Downloading APK from: {download_url}")
response = requests.get(download_url, stream=True)
Copy link

Copilot AI Sep 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The download request lacks timeout and certificate verification settings. Add timeout parameters and ensure SSL verification is enabled to prevent hanging requests and security vulnerabilities.

Suggested change
response = requests.get(download_url, stream=True)
response = requests.get(download_url, stream=True, timeout=30, verify=True)

Copilot uses AI. Check for mistakes.
response.raise_for_status()

output_filename = f"v2er-{os.environ['VERSION_NAME']}_google_play_signed.apk"
with open(output_filename, 'wb') as f:
for chunk in response.iter_content(chunk_size=8192):
f.write(chunk)

print(f"Successfully downloaded: {output_filename}")
print(f"apk_path={output_filename}")

return True

except Exception as e:
print(f"Error downloading signed APK: {str(e)}")
print("This may be because:")
print("1. The app hasn't been processed by Google Play yet")
print("2. The version hasn't been released to any track")
print("3. API permissions are insufficient")
return False

if __name__ == "__main__":
success = download_signed_apk()
sys.exit(0 if success else 1)
EOF

echo "Generated APK: $OUTPUT_FILE"
echo "apk_path=$OUTPUT_FILE" >> $GITHUB_OUTPUT
echo "found=true" >> $GITHUB_OUTPUT
# Set environment variables for the script
export PACKAGE_NAME="$PACKAGE_NAME"
export VERSION_CODE="$VERSION_CODE"
export VERSION_NAME="$VERSION_NAME"

# Run the download script
echo "Attempting to download Google Play signed APK..."
if python3 download_signed_apk.py > download_output.txt 2>&1; then
Copy link

Copilot AI Sep 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The output redirection captures both stdout and stderr, making it difficult to distinguish between normal output and error messages. Consider using separate handling for stdout and stderr or using more explicit error handling.

Copilot uses AI. Check for mistakes.
echo "Successfully downloaded Google Play signed APK"
cat download_output.txt

# Extract the APK path from output
APK_PATH=$(grep "apk_path=" download_output.txt | cut -d'=' -f2)
if [ -f "$APK_PATH" ]; then
echo "apk_path=$APK_PATH" >> $GITHUB_OUTPUT
echo "found=true" >> $GITHUB_OUTPUT
else
echo "APK file not found after download"
echo "found=false" >> $GITHUB_OUTPUT
fi
else
echo "Failed to download Google Play signed APK, falling back to universal APK generation"
cat download_output.txt

# Fallback: Generate universal APK from AAB as before
AAB_PATH=$(find bundle-artifacts -name "*.aab" 2>/dev/null | head -1)
if [ -z "$AAB_PATH" ]; then
echo "No AAB found for fallback, skipping"
echo "found=false" >> $GITHUB_OUTPUT
exit 0
fi

echo "Generating universal APK from AAB as fallback..."

# Download bundletool
curl -L -o bundletool-all.jar https://github.com/google/bundletool/releases/latest/download/bundletool-all.jar

# Create dummy keystore
keytool -genkey -v -keystore debug.keystore -alias androiddebugkey \
-keyalg RSA -keysize 2048 -validity 10000 \
-dname "CN=Android Debug,O=Android,C=US" \
-storepass android -keypass android

# Generate universal APK
java -jar bundletool-all.jar build-apks \
--bundle="$AAB_PATH" \
--output=apks.apks \
--mode=universal \
--ks=debug.keystore \
--ks-pass=pass:android \
--ks-key-alias=androiddebugkey \
--key-pass=pass:android

# Extract APK
unzip -q apks.apks universal.apk
OUTPUT_FILE="v2er-${VERSION_NAME}_google_play_signed.apk"
mv universal.apk "$OUTPUT_FILE"

echo "Generated fallback APK: $OUTPUT_FILE"
Comment on lines +563 to +566
Copy link

Copilot AI Sep 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fallback APK is being named with '_google_play_signed' suffix even though it's signed with a debug certificate. This naming is misleading and could confuse users about the actual signing status of the APK.

Copilot uses AI. Check for mistakes.
echo "apk_path=$OUTPUT_FILE" >> $GITHUB_OUTPUT
echo "found=true" >> $GITHUB_OUTPUT
fi

- name: Create Google Play link info
if: steps.download-apk.outputs.found == 'true'
Expand Down
Loading