Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
fbd7d8d
Add multi-ABI support with split APK configuration for Android builds…
Copilot Feb 10, 2026
099cb7a
Initial plan
Copilot Feb 15, 2026
33be049
Add APK signing configuration to Android build workflow
Copilot Feb 15, 2026
0498955
Add APK signing configuration to Android build workflow
Copilot Feb 15, 2026
8b26f3e
Address code review feedback: use GITHUB_WORKSPACE and improve security
Copilot Feb 15, 2026
d6a153d
Address code review feedback: use GITHUB_WORKSPACE and improve security
Copilot Feb 15, 2026
a0628f2
Fix signing configuration to gracefully handle missing credentials
Copilot Feb 15, 2026
2ee9136
Fix signing configuration to gracefully handle missing credentials
Copilot Feb 15, 2026
e851273
Refactor signing configuration to avoid code duplication
Copilot Feb 15, 2026
7a0e012
Refactor signing configuration to avoid code duplication
Copilot Feb 15, 2026
1fe8221
Fix documentation: use genkeypair instead of deprecated genkey
Copilot Feb 15, 2026
e7735cb
Fix documentation: use genkeypair instead of deprecated genkey
Copilot Feb 15, 2026
c6c4808
Add keystore cleanup step to prevent sensitive data leakage
Copilot Feb 15, 2026
00411a5
Improve security: set restrictive keystore permissions and reduce val…
Copilot Feb 15, 2026
80b5e54
Improve security: set restrictive keystore permissions and reduce val…
Copilot Feb 15, 2026
d2f6fd0
Merge commit '19a9daa0564dd2cc13b99bb9cf36b7f5b9afd486'
CircuitCoder Feb 15, 2026
2f4b9bd
Refactor signing to use committed CI keystore with ECDSA and separate…
Copilot Feb 18, 2026
1b2e1c3
Refactor signing to use committed CI keystore with ECDSA and separate…
Copilot Feb 18, 2026
07e021b
Address code review: improve error handling and reduce duplication
Copilot Feb 18, 2026
9ea37d0
Address code review: improve error handling and reduce duplication
Copilot Feb 18, 2026
f6fef48
Fix Gradle properties command and improve script robustness
Copilot Feb 18, 2026
5161dce
Fix Gradle properties command and improve script robustness
Copilot Feb 18, 2026
bc2207d
Improve documentation accuracy and script error handling
Copilot Feb 18, 2026
be08429
Improve documentation accuracy and script error handling
Copilot Feb 18, 2026
30b94a8
Final polish: validate version extraction and warn on unexpected APKs
Copilot Feb 18, 2026
9aca506
Final polish: validate version extraction and warn on unexpected APKs
Copilot Feb 18, 2026
22d3869
Add better error handling for edge cases
Copilot Feb 18, 2026
230d024
Fix APK rename script to use continue statements
Copilot Feb 18, 2026
fd7c9c7
Sign debug builds, simplify uploads, and improve keystore documentation
Copilot Feb 18, 2026
b3bedec
Sign debug builds, simplify uploads, and improve keystore documentation
Copilot Feb 18, 2026
72c1b93
Merge branch 'master' into copilot/add-signing-steps-to-build
CircuitCoder Feb 18, 2026
68bf935
Merge branch 'master' into copilot/add-signing-steps-to-build
CircuitCoder Feb 18, 2026
cb6d5d6
Add: ci.keystore
CircuitCoder Feb 18, 2026
0bd1f2e
Add: ci.keystore
CircuitCoder Feb 18, 2026
ec006c1
Make signing optional and mark unsigned APKs in filename
Copilot Feb 18, 2026
1d88651
Make signing optional and mark unsigned APKs in filename
Copilot Feb 18, 2026
0efce98
Optimize build.gradle.kts to avoid redundant environment variable checks
Copilot Feb 18, 2026
2fc5390
Optimize build.gradle.kts to avoid redundant environment variable checks
Copilot Feb 18, 2026
31d3e66
Fix signing detection in PRs and improve shell script conventions
Copilot Feb 18, 2026
9e2dccc
Trim keystore password to handle whitespace in secrets
Copilot Feb 18, 2026
2f43b82
Trim keystore password to handle whitespace in secrets
Copilot Feb 18, 2026
78a209a
Add better error handling and debugging to version extraction step
Copilot Feb 18, 2026
bf8759f
Extract version from built APK instead of running Gradle properties
Copilot Feb 18, 2026
5619b03
Use wildcard pattern to find universal APK for version extraction
Copilot Feb 18, 2026
4d1a7c3
Add better error handling for aapt command
Copilot Feb 18, 2026
bbdf2b1
Clarify why we use debug APK for version extraction
Copilot Feb 18, 2026
ca959f6
Changed APK naming
CircuitCoder Feb 18, 2026
253f3b8
Merge commit 'ca959f64470150f9ef9776068210cf66bdcee925' into copilot/…
CircuitCoder Feb 18, 2026
2ae4e7d
Use Gradle-generated APK names and collect with commit hash
Copilot Feb 18, 2026
3d8ae81
Remove obsolete rename-apks.sh script
Copilot Feb 18, 2026
48843ae
Quote shell variable assignments for safety
Copilot Feb 18, 2026
4f132ca
Modify gradle config again
CircuitCoder Feb 18, 2026
0ad5b30
Merge commit '4f132ca10be9c1f13324dcf047aa238e2801567f' into copilot/…
CircuitCoder Feb 18, 2026
b8e0ad2
Merge remote-tracking branch 'origin/copilot/add-signing-steps-to-bui…
CircuitCoder Feb 18, 2026
22fe62d
Updated workflow to match gradle build output
CircuitCoder Feb 18, 2026
1b8cdca
Manually run collection task
CircuitCoder Feb 18, 2026
700e6ed
Fixing collection task
CircuitCoder Feb 18, 2026
445cc52
Changed APK regex
CircuitCoder Feb 18, 2026
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
60 changes: 51 additions & 9 deletions .github/workflows/android-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,22 +73,64 @@ jobs:

- name: Build debug APK
working-directory: ./android
env:
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
run: ./gradlew assembleDebug --stacktrace

- name: Build release APK
working-directory: ./android
env:
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
run: ./gradlew assembleRelease --stacktrace

- name: Upload debug APKs
uses: actions/upload-artifact@v4
with:
name: kqt-android-debug-${{ steps.git-info.outputs.sha_short }}
path: android/app/build/outputs/apk/debug/*.apk
if-no-files-found: error
- name: Collect and rename APKs
working-directory: ./android
run: ./gradlew collectAndRenameApks --stacktrace

- name: Upload release APKs
- name: Check if signed
id: signing-status
shell: bash
env:
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
run: |
if [ -n "$KEYSTORE_PASSWORD" ]; then
echo "signed=true" >> $GITHUB_OUTPUT
else
echo "signed=false" >> $GITHUB_OUTPUT
fi

- name: Collect and rename APKs
shell: bash
run: |
# Create collection directory
mkdir -p upload

COMMIT_HASH="${{ steps.git-info.outputs.sha_short }}"
SIGNED="${{ steps.signing-status.outputs.signed }}"

# Determine suffix based on signing status
SUFFIX=""
if [ "$SIGNED" != "true" ]; then
SUFFIX="-UNSIGNED"
fi

# Collect APKs
for apk in android/app/build/outputs/collected/kqt-*.apk; do
if [ -f "$apk" ]; then
filename=$(basename "$apk")
# Insert commit hash before .apk extension
new_name="${filename%.apk}-${COMMIT_HASH}${SUFFIX}.apk"
cp "$apk" "upload/$new_name"
echo "Collected: $new_name"
fi
done

echo "All collected APKs:"
ls -1 upload/

- name: Upload APKs
uses: actions/upload-artifact@v4
with:
name: kqt-android-release-${{ steps.git-info.outputs.sha_short }}
path: android/app/build/outputs/apk/release/*.apk
name: kqt-apks-${{ steps.git-info.outputs.sha_short }}
path: upload/*.apk
if-no-files-found: error
94 changes: 94 additions & 0 deletions android/SIGNING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Android APK Signing Configuration

## Overview

This document describes the APK signing setup for the Android build workflow.

## CI Keystore

The repository includes a debug signing keystore at `android/app/keys/ci.keystore` that is used for signing APKs in CI builds.

### Keystore Details

- **Location**: `android/app/keys/ci.keystore`
- **Alias**: `apk`
- **Algorithm**: ECDSA (EC with 256-bit key)
- **Format**: PKCS12
- **Validity**: 10 years

This is a **debug signing keystore** used only for development and CI builds. It is password-protected and committed to the repository for convenience.

## GitHub Secrets Setup

To enable APK signing in GitHub Actions, you need to configure the following secret:

### Required Secret

- **KEYSTORE_PASSWORD**: Password for the CI keystore

### Adding Secret to GitHub

1. Go to your repository on GitHub
2. Navigate to **Settings** → **Secrets and variables** → **Actions**
3. Click **New repository secret**
4. Add the secret:
- Name: `KEYSTORE_PASSWORD`
- Value: The keystore password

## Workflow Behavior

- **All branches**: APKs are built and signed using the CI keystore
- The keystore password is provided via the `KEYSTORE_PASSWORD` secret
- If the secret is not configured, the build will fail with an error message

## Creating a New CI Keystore

If you need to regenerate the CI keystore (e.g., if compromised), use:

```bash
cd android/app/keys
keytool -genkeypair -v -keystore ci.keystore -alias apk -keyalg EC -validity 3650
```

This command will interactively prompt you for:
- Keystore password (enter it twice for confirmation)
- Key password (you can press Enter to use the same password as the keystore)
- Your name, organizational unit, organization, city, state, and country

**Notes:**
- The key password can be the same as the keystore password (press Enter when prompted for key password)
- PKCS12 is the default keystore type in modern keytool versions
- After creating the keystore, update the `KEYSTORE_PASSWORD` secret in GitHub with the password you chose

## Local Development

For local builds, set the environment variable:

```bash
export KEYSTORE_PASSWORD=your-keystore-password
cd android
./gradlew assembleRelease
```

Or provide the password inline:

```bash
cd android
KEYSTORE_PASSWORD=your-keystore-password ./gradlew assembleRelease
```

## Production Signing

**Important**: The CI keystore is for **development and testing only**. For production releases to the Google Play Store:

1. Create a separate production keystore with strong security
2. Store it securely (not in version control)
3. Use a different signing configuration for production builds
4. Never share or commit your production keystore

## Security Notes

- The CI keystore is intentionally committed to the repository for CI convenience
- It uses password protection as an additional security layer
- This is appropriate for debug/development builds but not for production releases
- Keep your production keystore separate and secure
5 changes: 4 additions & 1 deletion android/app/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
/build
/build
*.keystore
*.jks
!keys/ci.keystore
63 changes: 61 additions & 2 deletions android/app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import com.android.build.OutputFile

plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.ksp)
}

val appVersionName = "1.0.0"

android {
namespace = "plus.meow.kqt"
compileSdk = 36
Expand All @@ -12,21 +16,44 @@ android {
minSdk = 24
targetSdk = 36
versionCode = 1
versionName = "1.0"
versionName = appVersionName

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
ndk {
abiFilters += listOf("arm64-v8a", "armeabi-v7a")
}
}

val keystorePassword = System.getenv("KEYSTORE_PASSWORD")?.trim()

signingConfigs {
create("release") {
if (keystorePassword != null) {
storeFile = file("keys/ci.keystore")
storePassword = keystorePassword
keyAlias = "apk"
keyPassword = keystorePassword
}
}
}

buildTypes {
debug {
// Only sign if KEYSTORE_PASSWORD is available
if (keystorePassword != null) {
signingConfig = signingConfigs.getByName("release")
}
}
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
// Only sign if KEYSTORE_PASSWORD is available
if (keystorePassword != null) {
signingConfig = signingConfigs.getByName("release")
}
}
}

Expand All @@ -45,6 +72,38 @@ android {
}
}

tasks.register<Copy>("collectAndRenameApks") {
description = "Copies, flattens, and renames APKs to the upload directory"
group = "distribution"

// Source: The standard output directory
from(layout.buildDirectory.dir("outputs/apk"))

// Destination: Your upload folder
into(layout.buildDirectory.dir("outputs/collected"))

include("**/*.apk")

// Flattening and Renaming Logic
eachFile {
val matcher = "(.*)-(arm64-v8a|armeabi-v7a|universal)-(.*)\\.apk".toRegex().matchEntire(name)

if (matcher != null) {
val (prefix, abi, buildType) = matcher.destructured
path = "kqt-$abi-$appVersionName-$buildType.apk"
} else {
path = name.replace(".apk", "-$appVersionName.apk")
}
}

// Ensure we don't copy the empty "debug"/"release" folders
includeEmptyDirs = false
}

tasks.named("assemble") {
finalizedBy("collectAndRenameApks")
}

kotlin {
jvmToolchain(17)
compilerOptions {
Expand Down Expand Up @@ -76,4 +135,4 @@ dependencies {
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
}
}
Binary file added android/app/keys/ci.keystore
Binary file not shown.
Loading