home / skills / hitoshura25 / claude-devtools / android-signing-config

android-signing-config skill

/skills/android-signing-config

This skill configures Android release signing using dual-source credentials from environment variables and gradle.properties to streamline CI/CD and local dev.

npx playbooks add skill hitoshura25/claude-devtools --skill android-signing-config

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
7.8 KB
---
name: android-signing-config
description: Configure Android release build signing with dual-source credentials (env vars + gradle.properties)
category: android
version: 1.0.0
inputs:
  - project_path: Path to Android project
  - keystores_created: Keystores must exist in keystores/ directory
outputs:
  - Updated app/build.gradle.kts with signingConfigs
verify: "./gradlew assembleRelease"
---

# Android Signing Configuration

Configures release build signing with dual-source strategy: environment variables (CI/CD) and gradle.properties (local dev).

## Prerequisites

- Keystores exist in `keystores/` directory (run `android-keystore-generation` first)
- Android project with Gradle
- Kotlin DSL (build.gradle.kts)

## Inputs

| Input | Required | Default | Description |
|-------|----------|---------|-------------|
| project_path | Yes | . | Android project root |

## Process

### Step 1: Detect and Select Environment Variable Prefix

**Purpose:** Avoid variable name conflicts with other projects by using a project-specific prefix.

**Step 1a: Auto-detect project name**

```bash
# From settings.gradle.kts
PROJECT_NAME=$(grep "rootProject.name" settings.gradle.kts | sed 's/.*"\(.*\)".*/\1/' | tr '[:lower:]' '[:upper:]' | tr '-' '_')
echo "Detected project: $PROJECT_NAME"
```

**Step 1b: Ask user for prefix preference**

> "Choose environment variable prefix for signing configuration:
> 1. **${PROJECT_NAME}** (detected from project) - e.g., `${PROJECT_NAME}_SIGNING_KEY_STORE_PATH`
> 2. **APP** (generic) - e.g., `APP_SIGNING_KEY_STORE_PATH`
> 3. **Custom** - Enter your own prefix
>
> Select option (1/2/3):"

**Step 1c: Store selected prefix**

```bash
# Based on user selection:
# Option 1: PREFIX="$PROJECT_NAME"
# Option 2: PREFIX="APP"
# Option 3: PREFIX="{user_custom_prefix}"

echo "Using prefix: $PREFIX"
echo "Example variable: ${PREFIX}_SIGNING_KEY_STORE_PATH"
```

**GitHub Secrets to create later:**
- `${PREFIX}_SIGNING_KEY_STORE_BASE64`
- `${PREFIX}_SIGNING_KEY_ALIAS`
- `${PREFIX}_SIGNING_STORE_PASSWORD`
- `${PREFIX}_SIGNING_KEY_PASSWORD`

### Step 2: Add Signing Configuration to build.gradle.kts

Update `app/build.gradle.kts` to add signing configuration:

```kotlin
android {
    namespace = "com.example.app" // Keep existing
    compileSdk = 34 // Keep existing

    defaultConfig {
        // Keep existing config
    }

    // ADD THIS SECTION
    signingConfigs {
        create("release") {
            // Use the prefix selected in Step 1 (replace {PREFIX} with actual prefix)
            val prefix = "{PREFIX}"  // e.g., "APP" or project-specific prefix

            // Priority: environment variables (CI/CD) > gradle.properties (local dev)
            val keystorePath = System.getenv("${prefix}_SIGNING_KEY_STORE_PATH")
                ?: project.findProperty("${prefix}_SIGNING_KEY_STORE_PATH")?.toString()
            val storePass = System.getenv("${prefix}_SIGNING_STORE_PASSWORD")
                ?: project.findProperty("${prefix}_SIGNING_STORE_PASSWORD")?.toString()
            val alias = System.getenv("${prefix}_SIGNING_KEY_ALIAS")
                ?: project.findProperty("${prefix}_SIGNING_KEY_ALIAS")?.toString()
            val keyPass = System.getenv("${prefix}_SIGNING_KEY_PASSWORD")
                ?: project.findProperty("${prefix}_SIGNING_KEY_PASSWORD")?.toString()

            if (keystorePath != null && storePass != null && alias != null && keyPass != null) {
                storeFile = file(keystorePath)
                storePassword = storePass
                keyAlias = alias
                // For PKCS12 keystores, storePassword and keyPassword must be identical
                keyPassword = keyPass
            }
        }
    }

    buildTypes {
        release {
            signingConfig = signingConfigs.getByName("release")
            // ProGuard config set by android-proguard-setup skill
        }
    }

    // Validate signing config only when building release variants
    tasks.matching {
        it.name.matches(Regex(".*[aA]ssemble.*Release.*|.*[bB]undle.*Release.*"))
    }.configureEach {
        doFirst {
            val releaseConfig = android.signingConfigs.getByName("release")
            if (releaseConfig.storeFile == null) {
                throw GradleException(
                    """
                    Release signing not configured!

                    For CI/CD: Set environment variables (using prefix: $prefix):
                      - ${prefix}_SIGNING_KEY_STORE_PATH
                      - ${prefix}_SIGNING_STORE_PASSWORD
                      - ${prefix}_SIGNING_KEY_ALIAS
                      - ${prefix}_SIGNING_KEY_PASSWORD

                    For local development: Add to ~/.gradle/gradle.properties:
                      ${prefix}_SIGNING_KEY_STORE_PATH=/path/to/local-dev-release.jks
                      ${prefix}_SIGNING_STORE_PASSWORD=your-password
                      ${prefix}_SIGNING_KEY_ALIAS=local-dev
                      ${prefix}_SIGNING_KEY_PASSWORD=your-password
                    """.trimIndent()
                )
            }
        }
    }
}
```

**Detection logic:**
- Check if `signingConfigs` already exists - update if present
- Check if release `buildType` already has signingConfig - preserve other settings
- Don't modify ProGuard settings (handled by separate skill)

### Step 2: Update .gitignore

Ensure sensitive files are gitignored:

```bash
# Add to .gitignore if not present
grep -q "gradle.properties" .gitignore 2>/dev/null || echo -e "\n# Gradle properties with secrets\ngradle.properties" >> .gitignore
```

### Step 3: Configure Local Development

Ask user permission to update `~/.gradle/gradle.properties`:

```bash
# Read credentials from KEYSTORE_INFO.txt
LOCAL_PASSWORD=$(grep "Local.*Store Password:" keystores/KEYSTORE_INFO.txt | cut -d: -f2 | xargs)
PROJECT_PATH=$(pwd)
PREFIX="{PREFIX}"  # Use the prefix selected in Step 1

# Add to ~/.gradle/gradle.properties (using selected prefix)
cat >> ~/.gradle/gradle.properties << EOF

# ${PROJECT_PATH} (using prefix: ${PREFIX})
${PREFIX}_SIGNING_KEY_STORE_PATH=${PROJECT_PATH}/keystores/local-dev-release.jks
${PREFIX}_SIGNING_KEY_ALIAS=local-dev
${PREFIX}_SIGNING_STORE_PASSWORD=${LOCAL_PASSWORD}
${PREFIX}_SIGNING_KEY_PASSWORD=${LOCAL_PASSWORD}
EOF
```

**Important:** Always ask permission before modifying user's global gradle.properties!

## Verification

**MANDATORY:** Run this command to verify signing works:

```bash
# Build release APK
./gradlew assembleRelease

# Verify APK exists
ls -lh app/build/outputs/apk/release/app-release.apk

# Verify APK signature (supports APK Signature Scheme v2/v3)
$ANDROID_HOME/build-tools/34.0.0/apksigner verify --verbose app/build/outputs/apk/release/app-release.apk

# Or if apksigner is in PATH:
apksigner verify --verbose app/build/outputs/apk/release/app-release.apk
```

**Expected output:**
- Release build succeeds
- APK file exists
- `apksigner` shows "Verifies" with v2/v3 scheme confirmation

## Outputs

| Output | Location | Description |
|--------|----------|-------------|
| Signing config | app/build.gradle.kts | Dual-source signing configuration |
| Local config | ~/.gradle/gradle.properties | Local dev credentials |

## Troubleshooting

### "Signing config not found"
**Cause:** gradle.properties not configured correctly
**Fix:** Verify ~/.gradle/gradle.properties has correct paths and passwords

### "Cannot find keystore file"
**Cause:** Path in gradle.properties is incorrect
**Fix:** Use absolute path: `/full/path/to/keystores/local-dev-release.jks`

### "apksigner verification fails"
**Cause:** Wrong keystore or passwords
**Fix:** Double-check credentials in KEYSTORE_INFO.txt

## Completion Criteria

- [ ] `signingConfigs.release` exists in app/build.gradle.kts
- [ ] Release buildType uses signingConfig
- [ ] `~/.gradle/gradle.properties` configured (or env vars set for CI)
- [ ] `./gradlew assembleRelease` succeeds
- [ ] `apksigner verify` confirms APK is signed (v2/v3 schemes)

Overview

This skill configures Android release signing using a dual-source strategy: CI-friendly environment variables and local development via gradle.properties. It adds a release signingConfig to app/build.gradle.kts, validates release builds, and helps manage a project-specific environment variable prefix to avoid collisions. The goal is a reproducible, secure signing setup for CI and local workflows.

How this skill works

The script auto-detects a project prefix from settings.gradle.kts or asks the user to choose/apply a custom prefix. It injects a signingConfigs.release block into app/build.gradle.kts that reads credentials first from environment variables and falls back to project properties (gradle.properties). It also wires the release buildType to use that signingConfig and adds pre-build validation to fail fast when signing data is missing. Optionally it can append local credentials to ~/.gradle/gradle.properties after explicit user consent.

When to use it

  • Setting up release signing for CI/CD pipelines that supply secrets via environment variables or GitHub Secrets.
  • Standardizing a signing configuration across team members while allowing local gradle.properties for development.
  • Adding fail-fast validation to ensure builds fail when signing is not configured for release variants.
  • When you maintain multiple projects and want a project-specific env var prefix to avoid naming collisions.

Best practices

  • Prefer CI/CD environment variables (or base64 keystore secrets) for production builds; use local gradle.properties only for development.
  • Choose a clear prefix (project-specific or APP) and document the required secrets to store in your CI.
  • Keep keystores out of version control and ensure keystores/ and ~/.gradle/gradle.properties are gitignored.
  • Use absolute keystore paths in gradle.properties to avoid path resolution issues.
  • Always ask for explicit consent before updating ~/.gradle/gradle.properties on a developer machine.

Example use cases

  • Configure GitHub Actions to set ${PREFIX}_SIGNING_KEY_STORE_BASE64 and passwords for automated releases.
  • Local developer uses keystores/KEYSTORE_INFO.txt to populate ~/.gradle/gradle.properties for testing builds.
  • Migrating an existing app to a consistent signing strategy that supports both CI and local workflows.
  • Adding build-time validation so CI fails early if signing secrets are missing or malformed.

FAQ

What environment variables are required for CI?

Set PREFIX_SIGNING_KEY_STORE_PATH, PREFIX_SIGNING_STORE_PASSWORD, PREFIX_SIGNING_KEY_ALIAS, and PREFIX_SIGNING_KEY_PASSWORD (or store a base64 keystore and decode in CI).

Will this overwrite my existing signing config?

The logic updates or adds signingConfigs.release and preserves other buildType settings; it detects existing configs and avoids overwriting unrelated settings.