GitLab CI/CD Configuration
Overview
The Telegram SMS project uses GitLab CI/CD for automated building, testing, and releasing. This document provides a comprehensive guide to understanding and maintaining the CI/CD pipeline.
Configuration File: .reallsys/.gitlab-ci.yml
Pipeline Architecture
Stages
The pipeline consists of two stages:
- build: Compiles the Android application
- deploy: Deploys artifacts to GitHub releases
Build Variants
Three build types are supported:
- Release Build (
build_release): Production builds formasterbranch - Nightly Build (
build_nightly): Pre-release builds fornightlybranch - Debug Build (
build_debug): Manual debug builds for testing
Environment Configuration
Docker Image
Base Image: alvrme/alpine-android:android-36-jdk21
This image includes:
- Alpine Linux (lightweight)
- Android SDK 36 (compileSdk)
- JDK 21
- Essential build tools
Global Variables
variables:
OWNER: telegram-sms # GitHub organization/user
REPO: telegram-sms # Repository name
GIT_DEPTH: 0 # Full git history
GIT_SUBMODULE_STRATEGY: recursive # Clone submodules
GHR_VERSION: "0.17.0" # GitHub release tool version
GRADLE_USER_HOME: "${CI_PROJECT_DIR}/.gradle"
GRADLE_OPTS: "-Dorg.gradle.daemon=false -Dorg.gradle.parallel=true -Dorg.gradle.caching=true"Global Settings
default:
timeout: 30m # Maximum job duration
interruptible: true # Allow cancellation when new pipeline startsCaching Strategy
Cache Configuration
cache:
key:
files:
- .reallsys/scripts/requirements.txt # Python dependencies
- app/build.gradle # Android dependencies
prefix: ${CI_PROJECT_ID}
paths:
- .gradle/caches/ # Gradle build cache
- .gradle/wrapper/ # Gradle wrapper
- .cache/pip/ # Python pip cache
policy: pull-pushBenefits:
- Speeds up builds by reusing dependencies
- Reduces network bandwidth
- Cache invalidates when dependencies change
Job Templates
Environment Setup (.setup_environment)
A reusable template that all jobs inherit. It performs:
1. Dependency Installation
apk --summary --no-cache add git openssl bash curl wget ca-certificates zip unzip2. Keystore Setup
# Decode base64-encoded keystore from CI variable
echo -n "${KEYSTORE}" | base64 -d > app/keys.jks
# Validate keystore integrity
keytool -list -keystore app/keys.jks -storepass "${KEYSTORE_PASSWORD}"3. Git Configuration
git config --global user.email "[email protected]"
git config --global user.name "GitLab CI Bot"
git config --global http.postBuffer 5242880004. Gradle Setup
# Configure Gradle for optimal CI performance
cat >> gradle.properties << EOF
org.gradle.jvmargs=-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError
org.gradle.parallel=true
org.gradle.caching=true
org.gradle.configureondemand=true
EOF5. Version Generation
For master branch (Ubuntu-style versioning):
# Format: YY.MM or YY.MM.PATCH
UBUNTU_VERSION="$(date +%y.%m)" # e.g., 26.01
# Check for existing versions this month
EXISTING_TAGS=$(git tag -l "${UBUNTU_VERSION}*")
# Increment patch number if needed
# 26.01 -> 26.01.1 -> 26.01.2, etc.
VERSION_NAME="${UBUNTU_VERSION}.${PATCH_NUM}"
VERSION_CODE="${CI_PIPELINE_ID}"
RELEASE_TAG="${VERSION_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHORT_SHA}"For other branches (timestamp-based):
RELEASE_TAG="$(date +%Y%m%d%H%M)-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHORT_SHA}"
VERSION_CODE="${CI_PIPELINE_ID}"
VERSION_NAME="${RELEASE_TAG}"Build APK Template (.build_apk)
.build_apk:
script:
- ./gradlew clean assembleRelease --stacktrace --no-daemonBuild Jobs
1. Release Build (build_release)
Trigger: Pushes to master branch
Workflow:
Environment Setup (inherited from template)
Keystore Validation
bashkeytool -list -keystore app/keys.jks -storepass "${KEYSTORE_PASSWORD}"Language Pack Copy
bash./gradlew app:copy_language_pack --no-daemonBuild APK
bash./gradlew clean assembleRelease --stacktrace --no-daemonDownload GitHub Release Tool (ghr)
bashwget "https://github.com/tcnksm/ghr/releases/download/v${GHR_VERSION}/ghr_v${GHR_VERSION}_linux_amd64.tar.gz" tar -xzf ghr.tar.gzSync to GitHub
bashgit push "https://${GITHUB_ACCESS_KEY}@github.com/${OWNER}/${REPO}.git" \ "HEAD:refs/heads/${CI_COMMIT_REF_NAME}"Generate Changelog (with Breaking Changes detection)
bash# Fetch tags from source repository git remote add source https://github.com/telegram-sms/telegram-sms git fetch source --tags # Install Python dependencies pip3 install -r .reallsys/scripts/requirements.txt # Run AI-powered changelog generator python3 .reallsys/scripts/changelogGenerate.py # Read generated changelog RELEASE_CHANGELOG=$(cat CHANGELOG.md)The script automatically detects Breaking Changes by analyzing commit messages for:
- Explicit markers:
BREAKING CHANGE:,breaking:,!:in commit messages - Conventional commits with breaking marker:
feat!:,fix!:,refactor!: - Library migrations:
Paper to MMKV,SharedPreferences to MMKV - Data format changes:
migrate,migration,replace X with Y - API changes:
change api,incompatible,deprecate - Removed features:
removed feature,rename package
Example Breaking Changes:
refactor(storage)!: migrate SharedPreferences to MMKV refactor(storage): replace Paper library with MMKV for resend list managementWhen breaking changes are detected, the script:
- ⚠️ Prints warning during execution
- Places Breaking Changes section first in changelog
- Prompts AI to provide detailed migration steps
See Breaking Changes Detection for comprehensive details.
- Explicit markers:
Create GitHub Release
bashghr -t "${GITHUB_ACCESS_KEY}" \ -u "${OWNER}" \ -r "${REPO}" \ -n "${RELEASE_TAG}" \ -b "${RELEASE_CHANGELOG}" \ -prerelease \ "${RELEASE_TAG}" \ "./app/build/outputs/apk/release/"
Retry Policy:
- Max retries: 2
- Retry on:
runner_system_failure,stuck_or_timeout_failure
2. Nightly Build (build_nightly)
Trigger: Pushes to nightly branch
Differences from Release Build:
- No language pack copying (uses embedded)
- No keystore validation (uses debug signing)
- Deploys to
telegram-sms-nightlyrepository - Uses
-generatenotesinstead of AI changelog - Includes commit hash in version name
Workflow:
- Environment Setup
- Build APK
- Download ghr
- Sync to GitHub nightly repository
- Create pre-release with auto-generated notes
Retry Policy:
- Max retries: 2
- Retry on:
runner_system_failure,stuck_or_timeout_failure,script_failure
3. Debug Build (build_debug)
Trigger: Manual web trigger only
Purpose: Testing and debugging
Workflow:
- Environment Setup
- Build Debug APKbash
./gradlew assembleDebug --stacktrace --no-daemon
Artifacts:
- Name:
telegram-sms-debug-${CI_COMMIT_SHORT_SHA} - Path:
app/build/outputs/apk/debug/*.apk - Retention: 3 days
Cache Policy: pull only (no push)
Required CI/CD Variables
Secrets (Protected & Masked)
Configure in GitLab: Settings > CI/CD > Variables
| Variable | Description | Example | Required For |
|---|---|---|---|
KEYSTORE | Base64-encoded keystore file | MIIJrQIBA... | Release only |
KEYSTORE_PASSWORD | Keystore password | your-password | Release only |
ALIAS_NAME | Key alias name | telegram_sms_key | Release only |
ALIAS_PASS | Key password | your-key-password | Release only |
GITHUB_ACCESS_KEY | GitHub Personal Access Token | ghp_xxxxx | All builds |
ONEAPI_BASE_URL | OneAPI endpoint URL | https://api.example.com/v1/chat/completions | Release only |
ONEAPI_API_KEY | OneAPI authentication key | sk-xxxxx | Release only |
Built-in Variables
GitLab provides these automatically:
CI_COMMIT_REF_NAME: Branch nameCI_COMMIT_SHORT_SHA: Short commit hash (8 chars)CI_PIPELINE_ID: Unique pipeline ID (used as version code)CI_PROJECT_DIR: Project directory pathCI_PROJECT_ID: GitLab project ID
Keystore Management
Creating Keystore
keytool -genkey -v \
-keystore keys.jks \
-keyalg RSA \
-keysize 2048 \
-validity 10000 \
-alias telegram_sms_keyEncoding for GitLab CI
base64 -w 0 keys.jks > keys.jks.base64Copy contents of keys.jks.base64 to GitLab CI variable KEYSTORE.
Security Best Practices
- ✅ Use protected variables (only available on protected branches)
- ✅ Use masked variables (hidden in logs)
- ✅ Store keystore separately (never commit to git)
- ✅ Use strong passwords (min 16 characters)
- ✅ Limit access to keystore file
- ✅ Regular backup of keystore (loss = unable to update app)
Version Naming Strategy
Release Builds (master)
Format: YY.MM or YY.MM.PATCH
Examples:
- First release in January 2026:
26.01 - Second release in January 2026:
26.01.1 - Third release in January 2026:
26.01.2 - First release in February 2026:
26.02
Version Code: ${CI_PIPELINE_ID} (auto-incrementing)
Nightly Builds
Format: YYYYMMDDHHMM-branch-sha
Example: 202601071430-nightly-a1b2c3d4
Debug Builds
Version Name: Debug
Troubleshooting
Common Issues
1. Keystore Validation Failed
Error: Keystore validation failed - file may be corrupted or password incorrect
Solutions:
- Verify
KEYSTOREvariable is correctly base64-encoded - Check
KEYSTORE_PASSWORDis correct - Ensure keystore file is not corrupted
- Re-encode keystore:
base64 -w 0 keys.jks
2. GitHub Push Failed
Error: Failed to push to GitHub
Solutions:
- Verify
GITHUB_ACCESS_KEYhasrepoandworkflowscopes - Check token hasn't expired
- Ensure repository exists and is accessible
- Verify branch protection rules allow force push (if needed)
3. Gradle Build Failed
Error: Various Gradle errors
Solutions:
- Check
app/build.gradlesyntax - Verify dependencies are accessible
- Clear cache: Delete
.gradlecache in GitLab CI settings - Check JDK version compatibility (requires JDK 21)
4. Language Pack Copy Failed
Error: Task 'copy_language_pack' not found
Solutions:
- Ensure submodule
app/language_packis initialized - Check
GIT_SUBMODULE_STRATEGY: recursiveis set - Verify
app/build.gradlehascopy_language_packtask defined
5. Changelog Generation Failed
Error: API request failed or No commit history found
Solutions:
- Verify
ONEAPI_BASE_URLandONEAPI_API_KEYare set correctly - Check OneAPI service is accessible from GitLab runner
- Ensure at least one git tag exists
- Verify
GIT_DEPTH: 0to fetch full history
6. Out of Memory Error
Error: java.lang.OutOfMemoryError: Java heap space
Solutions:
- Increase heap size in
gradle.properties:propertiesorg.gradle.jvmargs=-Xmx3072m -XX:MaxMetaspaceSize=512m - Use incremental builds:
org.gradle.configureondemand=true - Enable parallel builds:
org.gradle.parallel=true
Performance Optimization
Cache Strategy
- Gradle Dependencies: Cached per
app/build.gradlehash - Gradle Wrapper: Reused across builds
- Python Packages: Cached per
requirements.txthash
Cache Hit Rate Target: >80%
Build Time Optimization
Typical Build Times:
- First Build (cold cache): ~15-20 minutes
- Incremental Build (warm cache): ~5-8 minutes
- Debug Build: ~3-5 minutes
Optimization Techniques:
- Parallel Gradle execution
- Gradle build cache
- Dependency caching
- Incremental compilation
- No Gradle daemon (CI environment)
Pipeline Monitoring
Key Metrics
Monitor these in GitLab CI/CD analytics:
- Success Rate: Should be >95%
- Average Duration: Target <10 minutes (with cache)
- Cache Hit Rate: Target >80%
- Retry Rate: Should be <5%
Logs and Debugging
Enable Verbose Logging:
variables:
CI_DEBUG_TRACE: "true" # Enable debug modeDownload Artifacts:
- Navigate to pipeline > job > right sidebar > "Download artifacts"
- APKs available for 30 days (debug) or indefinitely (releases)
Integration with GitHub
Release Process Flow
GitLab CI (Build) → GitHub Release (Publish) → Users (Download)- GitLab: Builds APK, validates, signs
- GitHub: Hosts releases, provides download links
- Users: Download from GitHub Releases page
Why Dual Platform?
- GitLab: Private CI/CD with better control and caching
- GitHub: Public face, community engagement, issue tracking
Synchronization
- Code synced from GitLab to GitHub after successful build
- Releases published to GitHub using
ghrtool - Issues and PRs managed on GitHub
Maintenance Checklist
Regular Tasks
- [ ] Weekly: Check build success rate
- [ ] Monthly: Review cache hit rates
- [ ] Monthly: Update base Docker image if needed
- [ ] Quarterly: Rotate GitHub access tokens
- [ ] Quarterly: Update
ghrversion - [ ] Annually: Review and update keystore backup
Dependency Updates
- [ ] JDK: Follow Android Studio recommendations
- [ ] Android SDK: Update when new version stabilizes
- [ ] Gradle: Update gradually, test thoroughly
- [ ] Python packages: Update
requestslibrary regularly
Security Updates
- [ ] Rotate
GITHUB_ACCESS_KEYif compromised - [ ] Re-encode keystore if exposed
- [ ] Review CI/CD variable access permissions
- [ ] Monitor GitLab security advisories
Related Documentation
- Breaking Changes Detection - Automated breaking change detection in changelogs
- Translation Checker - Multi-language translation validation
- Data Structure Versioning - MMKV schema migrations
- GitHub Actions Workflow - Alternative CI for manual builds
Advanced Configuration
Custom Build Variants
To add a new build variant:
Define variant in
app/build.gradle:gradlebuildTypes { staging { applicationIdSuffix ".staging" versionNameSuffix "-staging" // ... other config } }Add CI job in
.reallsys/.gitlab-ci.yml:yamlbuild_staging: stage: build rules: - if: $CI_COMMIT_BRANCH == "staging" script: - ./gradlew assembleStagingRelease
Conditional Changelog
Skip AI changelog for specific commits:
script:
- |
if echo "${CI_COMMIT_MESSAGE}" | grep -q "\[skip changelog\]"; then
RELEASE_CHANGELOG="No changelog for this release"
else
python3 .reallsys/scripts/changelogGenerate.py
RELEASE_CHANGELOG=$(cat CHANGELOG.md)
fiMatrix Builds
Build multiple ABIs in parallel:
build_release:
parallel:
matrix:
- ABI: [armeabi-v7a, arm64-v8a, x86, x86_64]
script:
- ./gradlew assembleRelease -Pabi=${ABI}Support and Contribution
Reporting Issues
If you encounter CI/CD issues:
- Check this documentation first
- Review pipeline logs in GitLab
- Search existing issues on GitHub
- Create new issue with:
- Pipeline URL
- Error logs
- Environment details
Contributing
To improve the CI/CD pipeline:
- Fork the repository
- Make changes in
.reallsys/.gitlab-ci.yml - Test with manual pipeline trigger
- Submit pull request with detailed description
- Reference this documentation in commit message
Changelog
v2.0 (2026-01):
- Added breaking changes detection
- Improved keystore validation
- Enhanced error handling
- Updated documentation
v1.5 (2025-12):
- Added AI-powered changelog generation
- Implemented Ubuntu-style versioning
- Added Python dependency caching
v1.0 (2025-01):
- Initial GitLab CI/CD setup
- Basic build and release workflow
- GitHub integration