Skip to content

Commit a4e415b

Browse files
committed
ci: add release workflows
1 parent 99935dc commit a4e415b

10 files changed

Lines changed: 323 additions & 10 deletions

File tree

.github/workflows/ci.yml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,14 @@ jobs:
3535
uses: gradle/actions/setup-gradle@v5
3636

3737
- name: Decode google-services.json
38-
run: echo "$GOOGLE_SERVICES_JSON_BASE64" | base64 -d > app/google-services.json
38+
run: |
39+
set -euo pipefail
40+
if [ -z "${GOOGLE_SERVICES_JSON_BASE64:-}" ]; then
41+
echo "GOOGLE_SERVICES_JSON_BASE64 is empty; using checked-in placeholder."
42+
exit 0
43+
fi
44+
mkdir -p app/src/debug
45+
printf '%s' "$GOOGLE_SERVICES_JSON_BASE64" | base64 -d > app/src/debug/google-services.json
3946
env:
4047
GOOGLE_SERVICES_JSON_BASE64: ${{ secrets.GOOGLE_SERVICES_JSON_BASE64 }}
4148

.github/workflows/e2e.yml

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,14 @@ jobs:
5757
uses: gradle/actions/setup-gradle@v5
5858

5959
- name: Decode google-services.json
60-
run: echo "$GOOGLE_SERVICES_JSON_BASE64" | base64 -d > app/google-services.json
60+
run: |
61+
set -euo pipefail
62+
if [ -z "${GOOGLE_SERVICES_JSON_BASE64:-}" ]; then
63+
echo "GOOGLE_SERVICES_JSON_BASE64 is empty; using checked-in placeholder."
64+
exit 0
65+
fi
66+
mkdir -p app/src/debug
67+
printf '%s' "$GOOGLE_SERVICES_JSON_BASE64" | base64 -d > app/src/debug/google-services.json
6168
env:
6269
GOOGLE_SERVICES_JSON_BASE64: ${{ secrets.GOOGLE_SERVICES_JSON_BASE64 }}
6370

@@ -100,7 +107,14 @@ jobs:
100107
uses: gradle/actions/setup-gradle@v5
101108

102109
- name: Decode google-services.json
103-
run: echo "$GOOGLE_SERVICES_JSON_BASE64" | base64 -d > app/google-services.json
110+
run: |
111+
set -euo pipefail
112+
if [ -z "${GOOGLE_SERVICES_JSON_BASE64:-}" ]; then
113+
echo "GOOGLE_SERVICES_JSON_BASE64 is empty; using checked-in placeholder."
114+
exit 0
115+
fi
116+
mkdir -p app/src/debug
117+
printf '%s' "$GOOGLE_SERVICES_JSON_BASE64" | base64 -d > app/src/debug/google-services.json
104118
env:
105119
GOOGLE_SERVICES_JSON_BASE64: ${{ secrets.GOOGLE_SERVICES_JSON_BASE64 }}
106120

.github/workflows/e2e_migration.yml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,14 @@ jobs:
3737
uses: gradle/actions/setup-gradle@v5
3838

3939
- name: Decode google-services.json
40-
run: echo "$GOOGLE_SERVICES_JSON_BASE64" | base64 -d > app/google-services.json
40+
run: |
41+
set -euo pipefail
42+
if [ -z "${GOOGLE_SERVICES_JSON_BASE64:-}" ]; then
43+
echo "GOOGLE_SERVICES_JSON_BASE64 is empty; using checked-in placeholder."
44+
exit 0
45+
fi
46+
mkdir -p app/src/debug
47+
printf '%s' "$GOOGLE_SERVICES_JSON_BASE64" | base64 -d > app/src/debug/google-services.json
4148
env:
4249
GOOGLE_SERVICES_JSON_BASE64: ${{ secrets.GOOGLE_SERVICES_JSON_BASE64 }}
4350

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
name: Release Internal
2+
3+
on:
4+
workflow_dispatch:
5+
6+
concurrency:
7+
group: ${{ github.workflow }}-${{ github.ref }}
8+
cancel-in-progress: false
9+
10+
env:
11+
TERM: xterm-256color
12+
FORCE_COLOR: 1
13+
14+
jobs:
15+
build-internal:
16+
if: github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release-') || startsWith(github.ref, 'refs/tags/v')
17+
runs-on: ubuntu-latest
18+
timeout-minutes: 45
19+
environment: release-internal
20+
21+
permissions:
22+
contents: read
23+
packages: read
24+
25+
steps:
26+
- name: Checkout
27+
uses: actions/checkout@v6
28+
29+
- name: Setup Java
30+
uses: actions/setup-java@v5
31+
with:
32+
java-version: '17'
33+
distribution: 'adopt'
34+
35+
- name: Setup Gradle
36+
uses: gradle/actions/setup-gradle@v5
37+
38+
- name: Decode mainnet release google-services.json
39+
env:
40+
MAINNET_RELEASE_GOOGLE_SERVICES_JSON_BASE64: ${{ secrets.MAINNET_RELEASE_GOOGLE_SERVICES_JSON_BASE64 }}
41+
run: |
42+
set -euo pipefail
43+
test -n "$MAINNET_RELEASE_GOOGLE_SERVICES_JSON_BASE64"
44+
mkdir -p app/src/mainnetRelease
45+
printf '%s' "$MAINNET_RELEASE_GOOGLE_SERVICES_JSON_BASE64" | base64 --decode > app/src/mainnetRelease/google-services.json
46+
47+
- name: Decode internal keystore
48+
env:
49+
INTERNAL_KEYSTORE_BASE64: ${{ secrets.INTERNAL_KEYSTORE_BASE64 }}
50+
run: |
51+
set -euo pipefail
52+
test -n "$INTERNAL_KEYSTORE_BASE64"
53+
umask 077
54+
keystore_path="$RUNNER_TEMP/internal.keystore"
55+
printf '%s' "$INTERNAL_KEYSTORE_BASE64" | base64 --decode > "$keystore_path"
56+
echo "KEYSTORE_FILE=$keystore_path" >> "$GITHUB_ENV"
57+
58+
- name: Build internal release APK
59+
env:
60+
GPR_USER: ${{ secrets.GPR_USER || github.actor }}
61+
GPR_TOKEN: ${{ secrets.GPR_TOKEN || github.token }}
62+
GITHUB_TOKEN: ${{ secrets.GPR_TOKEN || github.token }}
63+
KEYSTORE_PASSWORD: ${{ secrets.INTERNAL_KEYSTORE_PASSWORD }}
64+
KEY_ALIAS: ${{ secrets.INTERNAL_KEY_ALIAS }}
65+
KEY_PASSWORD: ${{ secrets.INTERNAL_KEY_PASSWORD }}
66+
run: ./gradlew assembleMainnetRelease --no-daemon --stacktrace
67+
68+
- name: Verify internal release signature
69+
run: |
70+
set -euo pipefail
71+
android_sdk_root="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-}}"
72+
test -n "$android_sdk_root"
73+
apksigner_path="$(find "$android_sdk_root/build-tools" -name apksigner -type f | sort -V | tail -n 1)"
74+
test -n "$apksigner_path"
75+
76+
apk_count=0
77+
while IFS= read -r -d '' apk_path; do
78+
apk_count=$((apk_count + 1))
79+
"$apksigner_path" verify --verbose --print-certs "$apk_path"
80+
done < <(find app/build/outputs/apk/mainnet/release -name 'bitkit-mainnet-release-*.apk' -print0)
81+
test "$apk_count" -gt 0
82+
83+
- name: Collect internal artifacts
84+
id: artifacts
85+
run: |
86+
set -euo pipefail
87+
artifact_dir="$RUNNER_TEMP/internal-release"
88+
mkdir -p "$artifact_dir"
89+
find app/build/outputs/apk/mainnet/release -name 'bitkit-mainnet-release-*.apk' -print0 |
90+
xargs -0 -I {} cp {} "$artifact_dir/"
91+
(cd "$artifact_dir" && sha256sum *.apk > SHA256SUMS.txt)
92+
echo "artifact_dir=$artifact_dir" >> "$GITHUB_OUTPUT"
93+
94+
- name: Upload internal artifacts
95+
uses: actions/upload-artifact@v6
96+
with:
97+
name: bitkit-internal-release-${{ github.run_number }}
98+
path: ${{ steps.artifacts.outputs.artifact_dir }}
99+
retention-days: 30

.github/workflows/release.yml

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
name: Release
2+
3+
on:
4+
workflow_dispatch:
5+
6+
concurrency:
7+
group: ${{ github.workflow }}-${{ github.ref }}
8+
cancel-in-progress: false
9+
10+
env:
11+
TERM: xterm-256color
12+
FORCE_COLOR: 1
13+
14+
jobs:
15+
build-release:
16+
if: github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release-') || startsWith(github.ref, 'refs/tags/v')
17+
runs-on: ubuntu-latest
18+
timeout-minutes: 45
19+
environment: release
20+
21+
permissions:
22+
contents: read
23+
packages: read
24+
25+
steps:
26+
- name: Checkout
27+
uses: actions/checkout@v6
28+
29+
- name: Setup Java
30+
uses: actions/setup-java@v5
31+
with:
32+
java-version: '17'
33+
distribution: 'adopt'
34+
35+
- name: Setup Gradle
36+
uses: gradle/actions/setup-gradle@v5
37+
38+
- name: Decode mainnet release google-services.json
39+
env:
40+
MAINNET_RELEASE_GOOGLE_SERVICES_JSON_BASE64: ${{ secrets.MAINNET_RELEASE_GOOGLE_SERVICES_JSON_BASE64 }}
41+
run: |
42+
set -euo pipefail
43+
test -n "$MAINNET_RELEASE_GOOGLE_SERVICES_JSON_BASE64"
44+
mkdir -p app/src/mainnetRelease
45+
printf '%s' "$MAINNET_RELEASE_GOOGLE_SERVICES_JSON_BASE64" | base64 --decode > app/src/mainnetRelease/google-services.json
46+
47+
- name: Decode release keystore
48+
env:
49+
BITKIT_KEYSTORE_BASE64: ${{ secrets.BITKIT_KEYSTORE_BASE64 }}
50+
run: |
51+
set -euo pipefail
52+
test -n "$BITKIT_KEYSTORE_BASE64"
53+
umask 077
54+
keystore_path="$RUNNER_TEMP/bitkit.keystore"
55+
printf '%s' "$BITKIT_KEYSTORE_BASE64" | base64 --decode > "$keystore_path"
56+
echo "KEYSTORE_FILE=$keystore_path" >> "$GITHUB_ENV"
57+
58+
- name: Build release artifacts
59+
env:
60+
GPR_USER: ${{ secrets.GPR_USER || github.actor }}
61+
GPR_TOKEN: ${{ secrets.GPR_TOKEN || github.token }}
62+
GITHUB_TOKEN: ${{ secrets.GPR_TOKEN || github.token }}
63+
KEYSTORE_PASSWORD: ${{ secrets.BITKIT_KEYSTORE_PASSWORD }}
64+
KEY_ALIAS: ${{ secrets.BITKIT_KEY_ALIAS }}
65+
KEY_PASSWORD: ${{ secrets.BITKIT_KEY_PASSWORD }}
66+
run: ./gradlew assembleMainnetRelease bundleMainnetRelease --no-daemon --stacktrace
67+
68+
- name: Verify release signatures
69+
run: |
70+
set -euo pipefail
71+
android_sdk_root="${ANDROID_HOME:-${ANDROID_SDK_ROOT:-}}"
72+
test -n "$android_sdk_root"
73+
apksigner_path="$(find "$android_sdk_root/build-tools" -name apksigner -type f | sort -V | tail -n 1)"
74+
test -n "$apksigner_path"
75+
76+
apk_count=0
77+
while IFS= read -r -d '' apk_path; do
78+
apk_count=$((apk_count + 1))
79+
"$apksigner_path" verify --verbose --print-certs "$apk_path"
80+
done < <(find app/build/outputs/apk/mainnet/release -name 'bitkit-mainnet-release-*.apk' -print0)
81+
test "$apk_count" -gt 0
82+
83+
bundle_count=0
84+
while IFS= read -r -d '' bundle_path; do
85+
bundle_count=$((bundle_count + 1))
86+
verify_output="$(mktemp)"
87+
if ! jarsigner -verify -verbose -certs "$bundle_path" 2>&1 | tee "$verify_output"; then
88+
rm -f "$verify_output"
89+
exit 1
90+
fi
91+
if grep -qi "jar is unsigned" "$verify_output"; then
92+
echo "Unsigned bundle: $bundle_path"
93+
rm -f "$verify_output"
94+
exit 1
95+
fi
96+
if ! grep -qi "jar verified" "$verify_output"; then
97+
echo "Bundle signature verification did not report success: $bundle_path"
98+
rm -f "$verify_output"
99+
exit 1
100+
fi
101+
rm -f "$verify_output"
102+
done < <(find app/build/outputs/bundle/mainnetRelease -name 'bitkit-mainnet-release-*.aab' -print0)
103+
test "$bundle_count" -gt 0
104+
105+
- name: Collect release artifacts
106+
id: artifacts
107+
run: |
108+
set -euo pipefail
109+
artifact_dir="$RUNNER_TEMP/release"
110+
mkdir -p "$artifact_dir"
111+
find app/build/outputs/bundle/mainnetRelease -name 'bitkit-mainnet-release-*.aab' -print0 |
112+
xargs -0 -I {} cp {} "$artifact_dir/"
113+
find app/build/outputs/apk/mainnet/release -name 'bitkit-mainnet-release-*.apk' -print0 |
114+
xargs -0 -I {} cp {} "$artifact_dir/"
115+
(cd "$artifact_dir" && sha256sum *.aab *.apk > SHA256SUMS.txt)
116+
echo "artifact_dir=$artifact_dir" >> "$GITHUB_OUTPUT"
117+
118+
- name: Upload release artifacts
119+
uses: actions/upload-artifact@v6
120+
with:
121+
name: bitkit-release-${{ github.run_number }}
122+
path: ${{ steps.artifacts.outputs.artifact_dir }}
123+
retention-days: 30

.github/workflows/ui-tests.yml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,14 @@ jobs:
4141
gradle-${{ runner.os }}-
4242
4343
- name: Decode google-services.json
44-
run: echo "$GOOGLE_SERVICES_JSON_BASE64" | base64 -d > app/google-services.json
44+
run: |
45+
set -euo pipefail
46+
if [ -z "${GOOGLE_SERVICES_JSON_BASE64:-}" ]; then
47+
echo "GOOGLE_SERVICES_JSON_BASE64 is empty; using checked-in placeholder."
48+
exit 0
49+
fi
50+
mkdir -p app/src/debug
51+
printf '%s' "$GOOGLE_SERVICES_JSON_BASE64" | base64 -d > app/src/debug/google-services.json
4552
env:
4653
GOOGLE_SERVICES_JSON_BASE64: ${{ secrets.GOOGLE_SERVICES_JSON_BASE64 }}
4754

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ local.properties
1717
!*.local.template*
1818
# Secrets
1919
google-services.json
20+
# Tracked fallback config used only so debug variants compile from a fresh clone.
21+
!app/google-services.json
2022
.env
2123
.env.*
2224
!.env.example

README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,15 @@ This repository contains the **native Android app** for Bitkit.
1010

1111
#### 1. Firebase Configuration
1212

13-
Download `google-services.json` from the Firebase Console for each of the following build flavor groups,:
14-
- dev/tnet/mainnetDebug: Place in `app/google-services.json`
13+
Dev and testnet debug builds use a checked-in placeholder at `app/google-services.json` so a fresh clone can compile without private Firebase files.
14+
15+
Download `google-services.json` from the Firebase Console when you need real Firebase integration for push notifications testing:
16+
- Debug builds: Place in `app/src/debug/google-services.json`
1517
- mainnetRelease: Place in `app/src/mainnetRelease/google-services.json`
1618

17-
> **Note**: Each flavor requires its own Firebase project configuration. The mainnet flavor will fail to build without its dedicated `google-services.json` file.
19+
The debug file above is ignored by Git and takes precedence over the checked-in placeholder. To use real Firebase integration across debug variants, make sure it includes each application ID you build.
20+
21+
> **Note**: Placeholder config is only for local dev and testnet debug builds. FCM token registration and push notifications require real Firebase configuration. The mainnet release flavor should always use the real `mainnetRelease/google-services.json` file.
1822
1923
#### 2. GitHub Packages setup
2024

app/google-services.json

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{
2+
"project_info": {
3+
"project_number": "000000000000",
4+
"project_id": "bitkit-debug-placeholder",
5+
"storage_bucket": "bitkit-debug-placeholder.appspot.com"
6+
},
7+
"client": [
8+
{
9+
"client_info": {
10+
"mobilesdk_app_id": "1:000000000000:android:0000000000000000000000",
11+
"android_client_info": {
12+
"package_name": "to.bitkit.dev"
13+
}
14+
},
15+
"oauth_client": [],
16+
"api_key": [
17+
{
18+
"current_key": "AIzaSyDebugPlaceholderKeyForLocalBuilds000"
19+
}
20+
],
21+
"services": {
22+
"appinvite_service": {
23+
"other_platform_oauth_client": []
24+
}
25+
}
26+
},
27+
{
28+
"client_info": {
29+
"mobilesdk_app_id": "1:000000000000:android:0000000000000000000002",
30+
"android_client_info": {
31+
"package_name": "to.bitkit.tnet"
32+
}
33+
},
34+
"oauth_client": [],
35+
"api_key": [
36+
{
37+
"current_key": "AIzaSyDebugPlaceholderKeyForLocalBuilds002"
38+
}
39+
],
40+
"services": {
41+
"appinvite_service": {
42+
"other_platform_oauth_client": []
43+
}
44+
}
45+
}
46+
],
47+
"configuration_version": "1"
48+
}

settings.gradle.kts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@ fun getGithubCredentials(
1212
passKey: String = "gpr.key",
1313
userKey: String = "gpr.user",
1414
): Pair<String?, String?> {
15-
val user = System.getenv("GITHUB_ACTOR")
15+
val user = System.getenv("GPR_USER")
16+
?: System.getenv("GITHUB_ACTOR")
1617
?: providers.gradleProperty(userKey).orNull
1718
?: localProperties.getProperty(userKey)
18-
val key = System.getenv("GITHUB_TOKEN")
19+
val key = System.getenv("GPR_TOKEN")
20+
?: System.getenv("GITHUB_TOKEN")
1921
?: providers.gradleProperty(passKey).orNull
2022
?: localProperties.getProperty(passKey)
2123
return user to key

0 commit comments

Comments
 (0)