Overview
Create a single automated release workflow that builds and publishes artifacts for all platforms (Desktop JAR, Android APK, iOS IPA, macOS DMG) from one tag push.
Current State
- ✅ Desktop: Automated builds on every push
- ✅ Desktop: Automated packagecloud.io deployment
- ✅ Android: Builds debug/release APKs
- ❌ Android: Manual release process
- ❌ iOS: No CI/CD automation
- ❌ No unified release workflow
CI/CD Score Impact
Current: CI/CD A- (90/100)
With this + #74: CI/CD A+ (98/100)
Proposed Unified Workflow
Trigger
git tag v2.1.0
git push origin v2.1.0
# Single command triggers builds for all platforms
Create .github/workflows/release.yml
name: Unified Release
on:
push:
tags:
- 'v*'
jobs:
prepare:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.get_version.outputs.version }}
changelog: ${{ steps.get_changelog.outputs.changelog }}
steps:
- uses: actions/checkout@v4
- name: Get version from tag
id: get_version
run: echo "version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
- name: Extract changelog for this version
id: get_changelog
run: |
# Extract section from CHANGELOG.md for this version
sed -n "/## \[${{ steps.get_version.outputs.version }}\]/,/## \[/p" CHANGELOG.md | head -n -1 > release-notes.md
echo "changelog<<EOF" >> $GITHUB_OUTPUT
cat release-notes.md >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Verify version consistency
run: |
# Check pom.xml, build.gradle versions match tag
grep "<version>${{ steps.get_version.outputs.version }}</version>" pom.xml || exit 1
grep "version = '${{ steps.get_version.outputs.version }}'" jnexus-core/build.gradle || exit 1
build-desktop:
needs: prepare
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21'
- name: Build JAR
run: mvn clean package
- name: Upload JAR
uses: actions/upload-artifact@v4
with:
name: jnexus-desktop-${{ needs.prepare.outputs.version }}
path: target/jnexus-*-jar-with-dependencies.jar
build-android:
needs: prepare
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21'
- name: Build Core
run: gradle :jnexus-core:build
- name: Build Android Release APK
run: gradle :jnexus-android:assembleRelease
- name: Sign APK
uses: r0adkll/sign-android-release@v1
with:
releaseDirectory: jnexus-android/build/outputs/apk/release
signingKeyBase64: ${{ secrets.ANDROID_SIGNING_KEY }}
alias: ${{ secrets.ANDROID_KEY_ALIAS }}
keyStorePassword: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
keyPassword: ${{ secrets.ANDROID_KEY_PASSWORD }}
- name: Upload APK
uses: actions/upload-artifact@v4
with:
name: jnexus-android-${{ needs.prepare.outputs.version }}
path: jnexus-android/build/outputs/apk/release/*-signed.apk
build-ios:
needs: prepare
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Set up Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: latest-stable
- name: Install Apple Certificate
env:
BUILD_CERTIFICATE_BASE64: ${{ secrets.IOS_BUILD_CERTIFICATE }}
P12_PASSWORD: ${{ secrets.IOS_P12_PASSWORD }}
KEYCHAIN_PASSWORD: ${{ secrets.IOS_KEYCHAIN_PASSWORD }}
run: |
# Decode certificate
echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o certificate.p12
# Create keychain
security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
# Import certificate
security import certificate.p12 -k build.keychain -P "$P12_PASSWORD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" build.keychain
- name: Install Provisioning Profile
env:
PROVISIONING_PROFILE_BASE64: ${{ secrets.IOS_PROVISIONING_PROFILE }}
run: |
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
echo -n "$PROVISIONING_PROFILE_BASE64" | base64 --decode -o ~/Library/MobileDevice/Provisioning\ Profiles/profile.mobileprovision
- name: Build iOS IPA
run: |
cd jnexus-ios
xcodebuild -scheme JNexus-iOS \
-configuration Release \
-archivePath build/JNexus.xcarchive \
archive
xcodebuild -exportArchive \
-archivePath build/JNexus.xcarchive \
-exportPath build \
-exportOptionsPlist ExportOptions.plist
- name: Upload IPA
uses: actions/upload-artifact@v4
with:
name: jnexus-ios-${{ needs.prepare.outputs.version }}
path: jnexus-ios/build/JNexus.ipa
build-macos:
needs: prepare
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Set up Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: latest-stable
- name: Build macOS App
run: |
cd jnexus-ios
xcodebuild -scheme JNexus-macOS \
-configuration Release \
-archivePath build/JNexus-macOS.xcarchive \
archive
- name: Create DMG
run: |
# Create DMG from .app bundle
create-dmg \
--volname "JNexus" \
--window-pos 200 120 \
--window-size 600 400 \
--icon-size 100 \
--app-drop-link 425 120 \
jnexus-macos-${{ needs.prepare.outputs.version }}.dmg \
jnexus-ios/build/JNexus-macOS.xcarchive/Products/Applications/JNexus.app
- name: Upload DMG
uses: actions/upload-artifact@v4
with:
name: jnexus-macos-${{ needs.prepare.outputs.version }}
path: jnexus-macos-${{ needs.prepare.outputs.version }}.dmg
create-release:
needs: [prepare, build-desktop, build-android, build-ios, build-macos]
runs-on: ubuntu-latest
steps:
- name: Download all artifacts
uses: actions/download-artifact@v4
- name: Create GitHub Release
uses: softprops/action-gh-release@v1
with:
name: Release v${{ needs.prepare.outputs.version }}
body: ${{ needs.prepare.outputs.changelog }}
draft: false
prerelease: false
files: |
jnexus-desktop-${{ needs.prepare.outputs.version }}/*.jar
jnexus-android-${{ needs.prepare.outputs.version }}/*.apk
jnexus-ios-${{ needs.prepare.outputs.version }}/*.ipa
jnexus-macos-${{ needs.prepare.outputs.version }}/*.dmg
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Deploy to packagecloud.io
run: |
# Existing packagecloud deployment
curl -F "package[distro_version_id]=190" \
-F "package[package_file]=@jnexus-desktop-${{ needs.prepare.outputs.version }}/*.jar" \
https://${{ secrets.PACKAGECLOUD_TOKEN }}:@packagecloud.io/api/v1/repos/flossware/jnexus/packages.json
- name: Notify release
run: |
# Send notification (Slack, Discord, email, etc.)
echo "Released v${{ needs.prepare.outputs.version }} successfully"
iOS Signing Setup
Required Secrets (GitHub Settings → Secrets):
- : Base64-encoded .p12 file
- : Password for .p12 file
- : Temporary keychain password
- : Base64-encoded .mobileprovision file
Generate:
# Export certificate from Xcode
base64 -i certificate.p12 | pbcopy
# Export provisioning profile
base64 -i profile.mobileprovision | pbcopy
Android Signing Setup
Required Secrets:
- : Base64-encoded keystore file
- : Key alias
- : Keystore password
- : Key password
Generate:
# Create keystore (one-time)
keytool -genkey -v -keystore jnexus.keystore -alias jnexus -keyalg RSA -keysize 2048 -validity 10000
# Encode for GitHub Secrets
base64 -i jnexus.keystore | pbcopy
Release Checklist (Automated)
The workflow enforces:
Manual Pre-Release Steps
Before pushing tag:
- Update version in pom.xml, build.gradle, Info.plist
- Update CHANGELOG.md with release notes
- Commit: On branch main
Your branch is up to date with 'github/main'.
nothing to commit, working tree clean
4. Tag:
5. Push:
Post-Release Steps (Automated)
Workflow will:
- Build all artifacts
- Create GitHub Release
- Attach all artifacts
- Deploy JAR to packagecloud.io
- Send notifications
Rollback Process
If release fails:
# Delete tag locally and remotely
git tag -d v2.1.0
git push origin :refs/tags/v2.1.0
# Delete GitHub Release (manual via UI or gh CLI)
gh release delete v2.1.0
# Fix issue, increment version, retry
Acceptance Criteria
Priority
Medium - Streamlines release process significantly
Related
Overview
Create a single automated release workflow that builds and publishes artifacts for all platforms (Desktop JAR, Android APK, iOS IPA, macOS DMG) from one tag push.
Current State
CI/CD Score Impact
Current: CI/CD A- (90/100)
With this + #74: CI/CD A+ (98/100)
Proposed Unified Workflow
Trigger
git tag v2.1.0 git push origin v2.1.0 # Single command triggers builds for all platformsCreate .github/workflows/release.yml
iOS Signing Setup
Required Secrets (GitHub Settings → Secrets):
Generate:
Android Signing Setup
Required Secrets:
Generate:
Release Checklist (Automated)
The workflow enforces:
Manual Pre-Release Steps
Before pushing tag:
Your branch is up to date with 'github/main'.
nothing to commit, working tree clean
4. Tag:
5. Push:
Post-Release Steps (Automated)
Workflow will:
Rollback Process
If release fails:
Acceptance Criteria
Priority
Medium - Streamlines release process significantly
Related