Continuous Integration & Delivery (CI/CD)
Automating Over-The-Air releases and patches within modern CI/CD pipelines relies on the headless execution mode of the tk-ota-push CLI.
In automated environments, interactive browser authentication (PKCE) is replaced by secure environment variables, and the --json flag formats output into machine-readable JSON metadata (patch UUID, download URLs, status receipts) for subsequent pipeline steps (automated smoke tests, Slack/Teams notifications).
1. Required Environment Variables
To authorize your CI/CD runner to communicate with the TkushPatch Server server, configure the following secrets in your pipeline runner (GitHub Secrets, GitLab CI/CD Variables):
| Variable | Example / Format | Description |
|---|---|---|
OTA_SERVER_URL | https://ota.enterprise.com | Root URL of the on-premise TkushPatch Server OTA Control Plane |
OTA_TENANT_ID | digital-banking | Tenant organization identifier |
OTA_API_KEY | ota_sec_live_9f8a... | Dedicated CI/CD service token with ROLE_RELEASE_ENGINEER or ROLE_TENANT_ADMIN |
When OTA_API_KEY is present in the runner's environment, tk-ota-push immediately attaches it as an Authorization: Bearer <token> header, bypassing any local session lookups and preventing interactive browser triggers.
2. GitHub Actions Production Workflow
The workflow below illustrates an end-to-end pipeline: locking a store baseline (release) upon git tag creation, and deploying an emergency hotfix (patch) when pushing to a hotfix/* branch.
name: TkushPatch OTA Deployment Pipeline
on:
push:
tags:
- 'v*.*.*'
branches:
- 'hotfix/**'
workflow_dispatch:
inputs:
action:
description: 'OTA Action to trigger'
required: true
default: 'patch'
type: choice
options:
- patch
- release
- promote
rollout:
description: 'Rollout percentage (0-100)'
required: false
default: '100'
channel:
description: 'Target distribution channel'
required: false
default: 'stable'
jobs:
ota-pipeline:
runs-on: macos-14 # Required for unified Android and iOS compilation
env:
OTA_SERVER_URL: ${{ secrets.OTA_SERVER_URL }}
OTA_TENANT_ID: ${{ secrets.OTA_TENANT_ID }}
OTA_API_KEY: ${{ secrets.OTA_API_KEY }}
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Java 17
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
- name: Setup Dart & Flutter
uses: subosito/flutter-action@v2
with:
channel: 'stable'
cache: true
- name: Install Shorebird Toolchain
run: |
curl --proto '=https' --tlsv1.2 https://raw.githubusercontent.com/shorebirdtech/install/main/install.sh -sSf | bash
echo "$HOME/.shorebird/bin" >> $GITHUB_PATH
- name: Verify Shorebird Toolchain
run: |
shorebird --version
ls -la "$HOME/.shorebird/bin/cache/flutter"
- name: Activate tk-ota-push CLI Globally
run: |
dart pub global activate --source path ./tkushpatch_releases/tk_ota_push
echo "$HOME/.pub-cache/bin" >> $GITHUB_PATH
- name: Fetch Flutter Dependencies
run: flutter pub get
# SCENARIO 1: LOCK STORE BASELINE RELEASE (Git Tag)
- name: Lock Android & iOS Release (Tag)
if: startsWith(github.ref, 'refs/tags/v') || github.event.inputs.action == 'release'
run: |
tk-ota-push release -p android \
--artifact aab \
--obfuscate \
--split-debug-info=build/symbols \
--json > release_receipt.json
cat release_receipt.json
# SCENARIO 2: DEPLOY OTA HOTFIX PATCH (Branch or Manual)
- name: Publish OTA Patch (Android)
if: startsWith(github.ref, 'refs/heads/hotfix/') || github.event.inputs.action == 'patch'
id: deploy_patch
run: |
ROLLOUT_PERCENT=${{ github.event.inputs.rollout || '100' }}
CHANNEL_NAME=${{ github.event.inputs.channel || 'stable' }}
# Execute tk-ota-push with structured JSON output
tk-ota-push patch -p android \
-c "$CHANNEL_NAME" \
--rollout "$ROLLOUT_PERCENT" \
-m "Hotfix ${{ github.ref_name }}: ${{ github.event.head_commit.message }}" \
--json > patch_receipt.json
cat patch_receipt.json
PATCH_ID=$(jq -r '.patch_id // empty' patch_receipt.json)
echo "patch_id=$PATCH_ID" >> $GITHUB_OUTPUT
- name: Archive Obfuscation Debug Symbols
if: always()
uses: actions/upload-artifact@v4
with:
name: ota-debug-symbols
path: build/symbols/
if-no-files-found: ignore
3. GitLab CI Pipeline Template
In GitLab CI, pipelines can be divided into modular stages with granular cache management:
stages:
- prepare
- release
- patch
- promote
variables:
OTA_SERVER_URL: $OTA_SERVER_URL
OTA_TENANT_ID: $OTA_TENANT_ID
OTA_API_KEY: $OTA_API_KEY
default:
tags:
- saas-macos-medium-m1 # macOS Runner for iOS and Android
before_script:
- export PATH="$HOME/.shorebird/bin:$HOME/.pub-cache/bin:$PATH"
- if ! command -v shorebird &> /dev/null; then
curl --proto '=https' --tlsv1.2 https://raw.githubusercontent.com/shorebirdtech/install/main/install.sh -sSf | bash ;
fi
- dart pub global activate --source path ./tkushpatch_releases/tk_ota_push
- flutter pub get
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- .dart_tool/
- build/
# Job: Lock Store Baseline upon new version tag
release_store:
stage: release
only:
- tags
script:
- tk-ota-push release -p android --artifact aab --json > release_output.json
artifacts:
name: "release-symbols-$CI_COMMIT_TAG"
paths:
- build/ota_symbols/
- release_output.json
expire_in: 6 mos
# Job: Automatically deploy an OTA patch on hotfix branches
patch_ota_hotfix:
stage: patch
only:
- /^hotfix\/.*$/
script:
- tk-ota-push patch -p android --rollout 20 -c staging --staged -m "$CI_COMMIT_MESSAGE" --json > patch_output.json
- cat patch_output.json
artifacts:
reports:
dotenv: patch.env
paths:
- patch_output.json
# Job: Manual promotion from staging to stable (4-Eyes approval rule)
promote_to_stable:
stage: promote
when: manual
only:
- /^hotfix\/.*$/
script:
- PATCH_UUID=$(jq -r '.patch_id' patch_output.json)
- tk-ota-push promote --patch-id "$PATCH_UUID" --to-channel stable --json
4. CI/CD Security Best Practices
- Scoped API Tokens: Never share production
OTA_API_KEYcredentials with external or untrusted feature branches. - Mandatory
--stagedin Regulated Environments: For banking or medical applications, configure the pipeline to always publish with--staged. A QA manager or CISO must review and manually promote the patch tostablevia the Web Console ortk-ota-push promote. - Always Archive
.symbolsArtifacts: Persist thebuild/ota_symbols/folder created by--obfuscateinside your CI storage. If users encounter crashes reported in Sentry or Firebase Crashlytics, these symbol maps are indispensable for de-obfuscating stack traces.