- Generated PKCS12 keystore (production-release.jks, alias: upload) - Updated GitHub secrets: KEYSTORE_BASE64/PASSWORD/KEY_ALIAS/KEY_PASSWORD - Added keystores/ to .gitignore - Added distribution/PLAY_CONSOLE_SETUP.md: step-by-step guide to create GCP service account, link to Play Console, first manual upload, and configure PLAY_STORE_SERVICE_ACCOUNT_JSON secret - Installed android-playstore-setup skills (sub-skills) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
295 lines
8.1 KiB
Markdown
295 lines
8.1 KiB
Markdown
---
|
|
name: android-keystore-generation
|
|
description: Generate production and local development keystores for Android release signing
|
|
category: android
|
|
version: 1.0.0
|
|
inputs:
|
|
- project_path: Path to Android project
|
|
- organization: Organization name for certificate
|
|
- country_code: 2-letter country code (optional)
|
|
outputs:
|
|
- keystores/production-release.jks
|
|
- keystores/local-dev-release.jks
|
|
- keystores/KEYSTORE_INFO.txt
|
|
verify: "ls keystores/*.jks && cat keystores/KEYSTORE_INFO.txt"
|
|
---
|
|
|
|
# Android Keystore Generation
|
|
|
|
Generates dual keystores for Android release signing: production (CI/CD only) and local development.
|
|
|
|
## Prerequisites
|
|
|
|
- JDK installed (`keytool` command available)
|
|
- Write access to project directory
|
|
|
|
## Inputs
|
|
|
|
| Input | Required | Default | Description |
|
|
|-------|----------|---------|-------------|
|
|
| project_path | Yes | . | Android project root |
|
|
| organization | Yes | - | Organization name for certificate DN |
|
|
| country_code | No | US | 2-letter country code |
|
|
|
|
## Process
|
|
|
|
### Organization Name
|
|
|
|
**ALWAYS prompt the user, but provide the auto-detected value as default.**
|
|
|
|
**Step 1: Detect organization from package name**
|
|
```bash
|
|
# Extract package name from build.gradle.kts
|
|
PACKAGE=$(grep "applicationId" app/build.gradle.kts | sed 's/.*"\(.*\)".*/\1/')
|
|
echo "Package: $PACKAGE"
|
|
|
|
# Extract second segment: com.{ORG}.app → ORG
|
|
ORG=$(echo $PACKAGE | cut -d. -f2)
|
|
echo "Detected organization: $ORG"
|
|
```
|
|
|
|
**Step 2: MANDATORY - Ask the user for confirmation**
|
|
|
|
⛔ **DO NOT SKIP THIS PROMPT**
|
|
|
|
Ask the user:
|
|
> "The detected organization name is **{ORG}**.
|
|
> Press Enter to use this, or type a different name:"
|
|
|
|
Wait for user response. Use their input if provided, otherwise use the detected default.
|
|
|
|
**Step 3: Store the confirmed organization name**
|
|
```bash
|
|
ORGANIZATION="{confirmed_org_name}"
|
|
echo "Using organization: $ORGANIZATION"
|
|
```
|
|
|
|
**Why this matters:** The organization appears in the certificate's Distinguished Name.
|
|
While it doesn't affect app functionality, users may want to customize it.
|
|
|
|
### Password Generation Options
|
|
|
|
**Choose one option for keystore passwords:**
|
|
|
|
#### Option 1 (Recommended): User-Provided Password
|
|
|
|
Ask the user to provide a password for the production keystore:
|
|
|
|
> "Please enter a password for the production keystore (minimum 12 characters, mix of letters, numbers, and symbols):"
|
|
|
|
**Security benefits:**
|
|
- Password never visible to the agent during the conversation
|
|
- User has complete control over password strength and storage
|
|
- Reduces risk of password exposure in logs or conversation history
|
|
|
|
**Instructions for user:**
|
|
```bash
|
|
# User will run keytool command manually with their chosen password
|
|
# Example:
|
|
keytool -genkeypair -v \
|
|
-keystore keystores/production-release.jks \
|
|
-storetype PKCS12 \
|
|
-alias upload \
|
|
-keyalg RSA \
|
|
-keysize 2048 \
|
|
-validity 10000 \
|
|
-storepass "YOUR_PASSWORD_HERE" \
|
|
-keypass "YOUR_PASSWORD_HERE" \
|
|
-dname "CN=Android Release, OU=Android, O={ORGANIZATION}, C=US"
|
|
```
|
|
|
|
#### Option 2: Generated Password
|
|
|
|
If the user prefers a generated password, use the automated generation steps below.
|
|
|
|
**Note:** The agent will have access to this password during the session. The password will be stored in temporary files and KEYSTORE_INFO.txt.
|
|
|
|
> "Would you like me to generate a secure password? (The agent will see this password during generation)"
|
|
|
|
If yes, proceed with the automated generation steps.
|
|
|
|
### Step 1: Create Keystores Directory
|
|
|
|
```bash
|
|
mkdir -p keystores
|
|
```
|
|
|
|
### Step 2: Generate Production Keystore
|
|
|
|
**SECURITY:** This keystore is for CI/CD only. Never use locally.
|
|
|
|
**⚠️ IMPORTANT: Run each command in a SEPARATE bash call. Do NOT combine commands.**
|
|
|
|
**Step 2a: Generate password and save to file**
|
|
```bash
|
|
openssl rand -base64 24 | tr -d '/+=' | head -c 24 > /tmp/prod_password.txt
|
|
```
|
|
|
|
**Step 2b: Read and display the password**
|
|
```bash
|
|
cat /tmp/prod_password.txt
|
|
```
|
|
📝 **Copy this password now** - you'll need it for the keytool command and KEYSTORE_INFO.txt
|
|
|
|
**Step 2c: Generate the keystore**
|
|
|
|
Replace `{PASSWORD}` with the password from Step 2b, and `{ORGANIZATION}` with the confirmed organization name:
|
|
|
|
```bash
|
|
keytool -genkeypair -v \
|
|
-keystore keystores/production-release.jks \
|
|
-storetype PKCS12 \
|
|
-alias upload \
|
|
-keyalg RSA \
|
|
-keysize 2048 \
|
|
-validity 10000 \
|
|
-storepass "{PASSWORD}" \
|
|
-keypass "{PASSWORD}" \
|
|
-dname "CN=Android Release, OU=Android, O={ORGANIZATION}, C=US"
|
|
```
|
|
|
|
**If keytool prompts for confirmation**, type `yes` and press Enter.
|
|
|
|
**Step 2d: Verify keystore was created**
|
|
```bash
|
|
ls -la keystores/production-release.jks
|
|
```
|
|
|
|
**Expected output:**
|
|
```
|
|
-rw------- 1 user staff 2557 Dec 11 10:30 keystores/production-release.jks
|
|
```
|
|
|
|
### Step 3: Generate Local Development Keystore
|
|
|
|
**⚠️ IMPORTANT: Run each command in a SEPARATE bash call. Do NOT combine commands.**
|
|
|
|
**Step 3a: Generate password and save to file**
|
|
```bash
|
|
openssl rand -base64 24 | tr -d '/+=' | head -c 24 > /tmp/local_password.txt
|
|
```
|
|
|
|
**Step 3b: Read and display the password**
|
|
```bash
|
|
cat /tmp/local_password.txt
|
|
```
|
|
📝 **Copy this password now** - you'll need it for the keytool command and KEYSTORE_INFO.txt
|
|
|
|
**Step 3c: Generate the keystore**
|
|
|
|
Replace `{PASSWORD}` with the password from Step 3b:
|
|
|
|
```bash
|
|
keytool -genkeypair -v \
|
|
-keystore keystores/local-dev-release.jks \
|
|
-storetype PKCS12 \
|
|
-alias local-dev \
|
|
-keyalg RSA \
|
|
-keysize 2048 \
|
|
-validity 10000 \
|
|
-storepass "{PASSWORD}" \
|
|
-keypass "{PASSWORD}" \
|
|
-dname "CN=Local Development, OU=Development, O=Local, C=US"
|
|
```
|
|
|
|
**If keytool prompts for confirmation**, type `yes` and press Enter.
|
|
|
|
**Step 3d: Verify keystore was created**
|
|
```bash
|
|
ls -la keystores/local-dev-release.jks
|
|
```
|
|
|
|
**Expected output:**
|
|
```
|
|
-rw------- 1 user staff 2557 Dec 11 10:30 keystores/local-dev-release.jks
|
|
```
|
|
|
|
### Step 4: Create Credentials File
|
|
|
|
```bash
|
|
cat > keystores/KEYSTORE_INFO.txt << EOF
|
|
Production Keystore
|
|
===================
|
|
File: production-release.jks
|
|
Alias: upload
|
|
Store Password: $PROD_PASSWORD
|
|
Key Password: $PROD_PASSWORD (same as store - PKCS12 requirement)
|
|
|
|
⚠️ SECURITY: CI/CD ONLY - Never use on developer machines
|
|
|
|
GitHub Secrets:
|
|
SIGNING_KEY_STORE_BASE64: $(base64 -w 0 keystores/production-release.jks 2>/dev/null || base64 -i keystores/production-release.jks)
|
|
SIGNING_KEY_ALIAS: upload
|
|
SIGNING_STORE_PASSWORD: $PROD_PASSWORD
|
|
SIGNING_KEY_PASSWORD: $PROD_PASSWORD
|
|
|
|
---
|
|
|
|
Local Development Keystore
|
|
==========================
|
|
File: local-dev-release.jks
|
|
Alias: local-dev
|
|
Store Password: $LOCAL_PASSWORD
|
|
Key Password: $LOCAL_PASSWORD
|
|
|
|
Add to ~/.gradle/gradle.properties:
|
|
SIGNING_KEY_STORE_PATH=$(pwd)/keystores/local-dev-release.jks
|
|
SIGNING_KEY_ALIAS=local-dev
|
|
SIGNING_STORE_PASSWORD=$LOCAL_PASSWORD
|
|
SIGNING_KEY_PASSWORD=$LOCAL_PASSWORD
|
|
EOF
|
|
```
|
|
|
|
### Step 5: Update .gitignore
|
|
|
|
```bash
|
|
# Add to .gitignore if not present
|
|
grep -q "keystores/" .gitignore 2>/dev/null || echo "keystores/" >> .gitignore
|
|
grep -q "*.jks" .gitignore 2>/dev/null || echo "*.jks" >> .gitignore
|
|
```
|
|
|
|
## Verification
|
|
|
|
**MANDATORY:** Run these commands:
|
|
|
|
```bash
|
|
# Verify keystores exist
|
|
ls -la keystores/*.jks
|
|
|
|
# Verify credentials documented
|
|
cat keystores/KEYSTORE_INFO.txt
|
|
|
|
# Verify gitignored
|
|
grep "keystores" .gitignore
|
|
```
|
|
|
|
**Expected output:**
|
|
- Two .jks files in keystores/
|
|
- KEYSTORE_INFO.txt with passwords
|
|
- keystores/ in .gitignore
|
|
|
|
## Outputs
|
|
|
|
| Output | Location | Description |
|
|
|--------|----------|-------------|
|
|
| Production keystore | keystores/production-release.jks | For CI/CD only |
|
|
| Local keystore | keystores/local-dev-release.jks | For local testing |
|
|
| Credentials | keystores/KEYSTORE_INFO.txt | Passwords and setup info |
|
|
|
|
## Troubleshooting
|
|
|
|
### "keytool: command not found"
|
|
**Cause:** JDK not installed or not in PATH
|
|
**Fix:** Install JDK 17: `brew install openjdk@17` (macOS) or `apt install openjdk-17-jdk` (Linux)
|
|
|
|
### "openssl: command not found"
|
|
**Cause:** OpenSSL not installed
|
|
**Fix:** Use alternative password generation: `head -c 24 /dev/urandom | base64`
|
|
|
|
## Completion Criteria
|
|
|
|
- [ ] `keystores/production-release.jks` exists
|
|
- [ ] `keystores/local-dev-release.jks` exists
|
|
- [ ] `keystores/KEYSTORE_INFO.txt` exists with passwords
|
|
- [ ] `keystores/` is in `.gitignore`
|