Skip to content

Latest commit

 

History

History
500 lines (360 loc) · 14.3 KB

File metadata and controls

500 lines (360 loc) · 14.3 KB

GitHub Secrets Setup

This guide explains how to set up all GitHub Secrets required by CI/CD workflows.

Overview

Several configuration files are excluded from version control for security. GitHub Secrets allow CI/CD workflows to recreate these files at build time.

Required Secrets

You need to add 7 secrets to your GitHub repository:

Firebase (existing):

  1. GOOGLE_SERVICES_JSON - Android Firebase configuration
  2. FIREBASE_OPTIONS_DART - Flutter Firebase configuration

Android signing (new):

  1. ANDROID_KEYSTORE_BASE64 - Base64-encoded upload keystore
  2. ANDROID_KEY_ALIAS - Key alias inside the keystore
  3. ANDROID_KEY_PASSWORD - Key password
  4. ANDROID_KEYSTORE_PASSWORD - Keystore (store) password

Google Play (new):

  1. GOOGLE_PLAY_SERVICE_ACCOUNT_JSON - Service account JSON for automated Play uploads

Step 1: Complete Firebase Setup Locally

Before setting up GitHub Secrets, you must complete the Firebase setup on your local machine:

  1. Follow the instructions in FIREBASE_SETUP.md
  2. Create a Firebase project
  3. Download google-services.json to android/app/
  4. Run flutterfire configure to generate lib/firebase_options.dart
  5. Verify the app builds locally with Firebase

Step 2: Prepare Secret Values

Secret 1: GOOGLE_SERVICES_JSON

Get the content:

# From project root, copy the entire file content
cat android/app/google-services.json

Copy the entire JSON output. It should look like this:

{
  "project_info": {
    "project_number": "123456789012",
    "project_id": "your-firebase-project",
    "storage_bucket": "your-firebase-project.appspot.com"
  },
  "client": [
    {
      "client_info": {
        "mobilesdk_app_id": "1:123456789012:android:abcdef1234567890",
        "android_client_info": {
          "package_name": "ralcock.cbf"
        }
      },
      "oauth_client": [],
      "api_key": [
        {
          "current_key": "AIzaSyXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
        }
      ],
      "services": {
        "appinvite_service": {
          "other_platform_oauth_client": []
        }
      }
    }
  ],
  "configuration_version": "1"
}

Secret 2: FIREBASE_OPTIONS_DART

Get the content:

# From project root, copy the entire file content
cat lib/firebase_options.dart

Copy the entire Dart file content. It should look like this:

// File generated by FlutterFire CLI.
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
import 'package:flutter/foundation.dart'
    show defaultTargetPlatform, kIsWeb, TargetPlatform;

class DefaultFirebaseOptions {
  static FirebaseOptions get currentPlatform {
    if (kIsWeb) {
      return web;
    }
    switch (defaultTargetPlatform) {
      case TargetPlatform.android:
        return android;
      // ... more platforms
    }
  }

  static const FirebaseOptions web = FirebaseOptions(
    apiKey: 'AIzaSyXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
    appId: '1:123456789012:web:abcdef1234567890',
    // ... more config
  );

  static const FirebaseOptions android = FirebaseOptions(
    apiKey: 'AIzaSyXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
    appId: '1:123456789012:android:abcdef1234567890',
    // ... more config
  );
}

Step 3: Add Secrets to GitHub

Option A: Via GitHub Web Interface

  1. Navigate to your repository on GitHub

    • Go to: https://github.com/YOUR_USERNAME/cambridge-beer-festival-app
  2. Open Settings

    • Click Settings tab (requires admin access)
  3. Navigate to Secrets

    • In the left sidebar: Secrets and variablesActions
  4. Add First Secret

    • Click New repository secret
    • Name: GOOGLE_SERVICES_JSON
    • Value: Paste the entire content from android/app/google-services.json
    • Click Add secret
  5. Add Second Secret

    • Click New repository secret
    • Name: FIREBASE_OPTIONS_DART
    • Value: Paste the entire content from lib/firebase_options.dart
    • Click Add secret

Option B: Via GitHub CLI

# Install GitHub CLI if not already installed
# macOS: brew install gh
# Other: https://cli.github.com/

# Authenticate
gh auth login

# Add GOOGLE_SERVICES_JSON secret
gh secret set GOOGLE_SERVICES_JSON < android/app/google-services.json

# Add FIREBASE_OPTIONS_DART secret
gh secret set FIREBASE_OPTIONS_DART < lib/firebase_options.dart

Step 4: Verify Secrets Are Set

Check via Web Interface

  1. Go to: Settings → Secrets and variables → Actions
  2. You should see both secrets listed:
    • GOOGLE_SERVICES_JSON
    • FIREBASE_OPTIONS_DART

Check via GitHub CLI

gh secret list

Expected output:

CLOUDFLARE_API_TOKEN       Updated 2024-XX-XX
CODECOV_TOKEN              Updated 2024-XX-XX
FIREBASE_OPTIONS_DART      Updated 2024-XX-XX
GOOGLE_SERVICES_JSON       Updated 2024-XX-XX

Step 5: Test CI/CD Build

Push a change to trigger the workflow:

# Make a small change
echo "# Test" >> README.md

# Commit and push
git add README.md
git commit -m "Test Firebase CI/CD"
git push

Monitor the workflow:

  1. Go to your repository on GitHub
  2. Click Actions tab
  3. Click on the running workflow
  4. Check that these steps succeed:
    • "Create Firebase google-services.json"
    • "Create Firebase options"
    • "Get dependencies"
    • "Run tests"
    • "Build web" / "Build Android"

How It Works

The GitHub Actions workflows include these steps:

Flutter App CI/CD (.github/workflows/ci.yml):

- name: Create Firebase google-services.json
  run: echo '${{ secrets.GOOGLE_SERVICES_JSON }}' > android/app/google-services.json

- name: Create Firebase options
  run: echo '${{ secrets.FIREBASE_OPTIONS_DART }}' > lib/firebase_options.dart

These steps:

  1. Read the secret value from GitHub Secrets
  2. Write it to the expected file location
  3. Allow subsequent build steps to access the files

Security Best Practices

✅ Do This

  • Keep secrets in GitHub Secrets - Never commit them to the repository
  • Restrict repository access - Only trusted collaborators should have admin access
  • Use environment-specific secrets - Different secrets for staging/production
  • Rotate secrets periodically - Update Firebase config if compromised
  • Enable branch protection - Require reviews for changes to main branch

❌ Don't Do This

  • Don't commit secrets to Git - They're in .gitignore for a reason
  • Don't share secret values - Send setup instructions instead
  • Don't use secrets in fork PRs - GitHub doesn't expose secrets to forks
  • Don't log secret values - Be careful with debug output

Troubleshooting

Secret not found

Error: secret GOOGLE_SERVICES_JSON not found

Solution:

  1. Verify secret name is exactly GOOGLE_SERVICES_JSON (case-sensitive)
  2. Check you added it as a repository secret, not environment secret
  3. Ensure you have admin access to the repository

Invalid JSON format

Error: Error parsing google-services.json

Solution:

  1. Verify the secret contains valid JSON
  2. Check for trailing commas or syntax errors
  3. Re-download from Firebase Console if needed
  4. Use a JSON validator: https://jsonlint.com/

Build fails with "Firebase not initialized"

Solution:

  1. Verify both secrets are set correctly
  2. Check workflow logs for file creation steps
  3. Ensure flutterfire configure generated correct config
  4. Verify secret contains complete file content

Secrets not working in fork PRs

This is expected GitHub behavior. Secrets are not exposed to workflows triggered by forks for security reasons.

Solution:

  • Contributors must set up their own Firebase project and secrets
  • Or run tests locally before submitting PR
  • Or maintainer merges to a branch in the main repo to trigger CI

Updating Secrets

If you need to update Firebase configuration:

Update GOOGLE_SERVICES_JSON

# After downloading new google-services.json from Firebase Console
gh secret set GOOGLE_SERVICES_JSON < android/app/google-services.json

Or via web interface:

  1. Settings → Secrets and variables → Actions
  2. Click Update next to GOOGLE_SERVICES_JSON
  3. Paste new value
  4. Click Update secret

Update FIREBASE_OPTIONS_DART

# After running flutterfire configure
gh secret set FIREBASE_OPTIONS_DART < lib/firebase_options.dart

Related Documentation


Quick Reference

# List all secrets
gh secret list

# Set/update a secret from file
gh secret set SECRET_NAME < path/to/file

# Set/update a secret from stdin
echo "secret value" | gh secret set SECRET_NAME

# Delete a secret
gh secret delete SECRET_NAME

Need Help?

If you encounter issues:

  1. Check the Firebase Setup Guide
  2. Verify secrets are set correctly in GitHub Settings
  3. Check GitHub Actions logs for specific error messages
  4. Ensure local Firebase setup works before adding to CI/CD
  5. Review this guide's troubleshooting section

Android Signing Secrets

These secrets are required by .github/workflows/release-android.yml to sign the APK and AAB with the upload keystore before uploading to Google Play.

How Play App Signing works

Key Held by Purpose
App signing key Google Signs APKs delivered to users
Upload key You (these secrets) Signs the AAB you submit; Google verifies then re-signs

Storing only the upload key in CI means the real distribution key is never exposed. If the upload key is compromised you can rotate it in Play Console without affecting users.

Step 1: Identify the correct keystore

Replacing an existing Play Store app? Use the original signing keystore — the same one that was used to sign previous releases. Do NOT generate a new keystore. The original keystore must first be enrolled in Play App Signing (see android-release.md). Enrolling it as the app signing key is what ensures existing users receive the update seamlessly. After enrollment, the same keystore serves as the upload key in CI.

For a brand new app with no existing Play Store history, generate a keystore:

keytool -genkey -v -keystore upload-keystore.jks \
  -keyalg RSA -keysize 2048 -validity 10000 \
  -alias upload

Step 2: Base64-encode the keystore

# Linux/Mac
base64 -i upload-keystore.jks | tr -d '\n'

# Windows (PowerShell)
[Convert]::ToBase64String([IO.File]::ReadAllBytes("upload-keystore.jks"))

Step 3: Add the four secrets

Go to: Repository Settings → Secrets and variables → Actions → New repository secret

Secret Value
ANDROID_KEYSTORE_BASE64 The full base64 string from step 2
ANDROID_KEY_ALIAS The alias used in keytool (e.g. upload)
ANDROID_KEY_PASSWORD The key password entered in keytool
ANDROID_KEYSTORE_PASSWORD The keystore password entered in keytool

Via GitHub CLI:

# Encode and set in one step
base64 -i upload-keystore.jks | tr -d '\n' | gh secret set ANDROID_KEYSTORE_BASE64
echo -n "your-key-alias"    | gh secret set ANDROID_KEY_ALIAS
echo -n "your-key-password" | gh secret set ANDROID_KEY_PASSWORD
echo -n "your-store-password" | gh secret set ANDROID_KEYSTORE_PASSWORD

Google Play Service Account Secret

This secret is required by the publish-google-play job in release-android.yml to upload the signed AAB to the Internal track automatically on every release.

Step 1: Enable the Google Play API

  1. Open Google Cloud Console and select (or create) the project linked to your Play Console account
  2. Go to APIs & Services → Library
  3. Search for Google Play Android Developer API and click Enable

Step 2: Create a service account

  1. Go to APIs & Services → Credentials → Create Credentials → Service account
  2. Name it (e.g. github-play-publisher), click Create and continue
  3. Skip optional role assignment, click Done
  4. Click the service account → Keys tab → Add key → Create new key → JSON
  5. Download the JSON file — this is your secret value

Step 3: Grant Play Console access

  1. Open Google Play Console
  2. Go to Setup → API access
  3. Click Link next to the Google Cloud project from step 1 (if not already linked)
  4. Under Service accounts, find the account you created and click Grant access
  5. Set permissions to Release manager (or at minimum Release to Internal testing)
  6. Click Apply and Invite user

Step 4: Add the secret

gh secret set GOOGLE_PLAY_SERVICE_ACCOUNT_JSON < path/to/service-account.json

Or via the web interface: paste the entire JSON file content as the secret value.

What happens when it's configured

Every push of a v* tag will automatically:

  1. Build the signed AAB and create a GitHub Release
  2. Attempt to upload the AAB to the Internal track in Play Console

First release only: The Play upload step will be skipped until the app has been submitted to Play Console at least once manually (the API cannot create a new listing). The workflow will still succeed — the GitHub Release with the signed AAB is always created. See the first-upload steps in android-release.md. From the second release onwards, the AAB appears in Internal testing automatically.

From there you manually promote to Alpha → Beta → Production in Play Console.

Troubleshooting

"Permission denied" or "403 Forbidden"

  • Verify the service account was granted access in Play Console (step 3)
  • Check the app is published at least once manually before the API can upload updates

"Package not found"

  • Ensure the packageName in the workflow (ralcock.cbf) matches the app in Play Console exactly

"Upload key certificate does not match"

  • The upload keystore registered in Play Console must match the one in ANDROID_KEYSTORE_BASE64
  • On first upload, Play Console registers your upload certificate automatically