fuzzyworld

ai-chat-android-auto / commit

Add GitLab CI pipeline for signed release builds

846a86b0830efc0b540944ad014fa859b071a93d · Pavel Safronov · 2026-05-14 20:28:49 -0700 · parents 9090d7c82e76

Add GitLab CI pipeline for signed release builds

On any tag push, CI builds a signed APK, uploads it to the Package
Registry, and creates a GitLab Release with a download link.

README: add Releasing section with CI/CD variable documentation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
 .gitlab-ci.yml | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 README.md      | 33 ++++++++++++++++++++++++++++++-
 2 files changed, 93 insertions(+), 1 deletion(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000..5f16305
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,61 @@
+# Triggered on any tag push. Push a tag of the form v1.2.3 to produce a
+# signed release APK and publish it to the project's Releases page.
+#
+# Required CI/CD variables (Settings → CI/CD → Variables, all masked):
+#   KEYSTORE_BASE64          – release keystore encoded as base64
+#                              (generate with: base64 -i release.jks | pbcopy)
+#   KEYSTORE_STORE_PASSWORD  – keystore password
+#   KEYSTORE_KEY_ALIAS       – key alias inside the keystore
+#   KEYSTORE_KEY_PASSWORD    – key password
+
+stages:
+  - build
+  - release
+
+build:
+  stage: build
+  image: jangrewe/gitlab-ci-android:34
+  rules:
+    - if: $CI_COMMIT_TAG
+  before_script:
+    - chmod +x ./gradlew
+    - echo "$KEYSTORE_BASE64" | base64 -d > keystore.jks
+    - echo "release.storeFile=../keystore.jks"               > local.properties
+    - echo "release.storePassword=$KEYSTORE_STORE_PASSWORD" >> local.properties
+    - echo "release.keyAlias=$KEYSTORE_KEY_ALIAS"           >> local.properties
+    - echo "release.keyPassword=$KEYSTORE_KEY_PASSWORD"     >> local.properties
+  script:
+    - ./gradlew assembleRelease
+    - |
+      curl --silent --show-error --fail \
+           --header "JOB-TOKEN: $CI_JOB_TOKEN" \
+           --upload-file app/build/outputs/apk/release/app-release.apk \
+           "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/apk/${CI_COMMIT_TAG}/ai-chat-android-auto-${CI_COMMIT_TAG}.apk"
+  artifacts:
+    name: "ai-chat-android-auto-$CI_COMMIT_TAG"
+    paths:
+      - app/build/outputs/apk/release/app-release.apk
+    expire_in: 30 days
+  cache:
+    key: gradle
+    paths:
+      - .gradle/
+
+release:
+  stage: release
+  image: registry.gitlab.com/gitlab-org/release-cli:latest
+  rules:
+    - if: $CI_COMMIT_TAG
+  needs:
+    - build
+  script:
+    - echo "Publishing release $CI_COMMIT_TAG"
+  release:
+    tag_name: $CI_COMMIT_TAG
+    name: "$CI_COMMIT_TAG"
+    description: "Download the signed APK from the assets section below."
+    assets:
+      links:
+        - name: "ai-chat-android-auto-$CI_COMMIT_TAG.apk"
+          url: "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/apk/${CI_COMMIT_TAG}/ai-chat-android-auto-${CI_COMMIT_TAG}.apk"
+          link_type: package
diff --git a/README.md b/README.md
index 4336eec..379f5c2 100644
--- a/README.md
+++ b/README.md
@@ -87,7 +87,38 @@ The interface requires two methods:
 ./gradlew assembleRelease
 ```
 
-Signing credentials go in `local.properties` (gitignored). See `local.properties.example`.
+Signing credentials go in `local.properties` (gitignored):
+
+```properties
+release.storeFile=path/to/release.jks
+release.storePassword=...
+release.keyAlias=...
+release.keyPassword=...
+```
+
+---
+
+## Releasing
+
+Push a tag to trigger the CI pipeline, which builds a signed APK, uploads it to
+the Package Registry, and creates a GitLab Release with a download link.
+
+```bash
+git tag v1.2.3 && git push origin v1.2.3
+```
+
+The release will appear under **Deploy → Releases** in the GitLab sidebar.
+
+### CI/CD variables
+
+Configure these in **Settings → CI/CD → Variables** (all masked):
+
+| Variable | Description |
+|---|---|
+| `KEYSTORE_BASE64` | Release keystore encoded as base64 (`base64 -i release.jks`) |
+| `KEYSTORE_STORE_PASSWORD` | Keystore password |
+| `KEYSTORE_KEY_ALIAS` | Key alias within the keystore |
+| `KEYSTORE_KEY_PASSWORD` | Key password |
 
 ---