Sign APK/AAB via Gradle build

This commit is contained in:
Niels van Velzen
2026-06-13 20:23:12 +02:00
committed by Niels van Velzen
parent da02e7c93b
commit fb7ebed903
5 changed files with 88 additions and 134 deletions
+44 -62
View File
@@ -10,93 +10,75 @@ jobs:
name: Publish
runs-on: ubuntu-24.04
if: ${{ contains(github.repository_owner, 'jellyfin') }}
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Setup Java
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: temurin
java-version-file: .tools-versions
- name: Setup Gradle
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
- name: Set JELLYFIN_VERSION
run: echo "JELLYFIN_VERSION=$(echo ${GITHUB_REF#refs/tags/v} | tr / -)" >> $GITHUB_ENV
- name: Decode keystore
run: echo "${{ secrets.KEYSTORE }}" | base64 --decode > keystore.jks
- name: Assemble release files
run: ./gradlew assemble bundleProprietaryRelease versionTxt
- name: Sign libre APK
id: libreSign
uses: r0adkll/sign-android-release@349ebdef58775b1e0d8099458af0816dc79b6407 # tag=v1
env:
BUILD_TOOLS_VERSION: "34.0.0"
with:
releaseDirectory: app/build/outputs/apk/libre/release
signingKeyBase64: ${{ secrets.KEYSTORE }}
keyStorePassword: ${{ secrets.KEYSTORE_PASSWORD }}
alias: ${{ secrets.KEY_ALIAS }}
keyPassword: ${{ secrets.KEY_PASSWORD }}
- name: Sign proprietary APK
id: proprietarySign
uses: r0adkll/sign-android-release@349ebdef58775b1e0d8099458af0816dc79b6407 # tag=v1
env:
BUILD_TOOLS_VERSION: "34.0.0"
with:
releaseDirectory: app/build/outputs/apk/proprietary/release
signingKeyBase64: ${{ secrets.KEYSTORE }}
keyStorePassword: ${{ secrets.KEYSTORE_PASSWORD }}
alias: ${{ secrets.KEY_ALIAS }}
keyPassword: ${{ secrets.KEY_PASSWORD }}
- name: Sign proprietary app bundle
id: proprietaryBundleSign
uses: r0adkll/sign-android-release@349ebdef58775b1e0d8099458af0816dc79b6407 # tag=v1
env:
BUILD_TOOLS_VERSION: "34.0.0"
with:
releaseDirectory: app/build/outputs/bundle/proprietaryRelease
signingKeyBase64: ${{ secrets.KEYSTORE }}
keyStorePassword: ${{ secrets.KEYSTORE_PASSWORD }}
alias: ${{ secrets.KEY_ALIAS }}
keyPassword: ${{ secrets.KEY_PASSWORD }}
KEYSTORE_FILE: ${{ github.workspace }}/keystore.jks
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
SIGNING_KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
SIGNING_KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
run: ./gradlew assemble bundleRelease versionTxt
- name: Prepare release archive
run: |
mkdir -p build/jellyfin-publish
mv app/build/outputs/apk/*/*/jellyfin-android-*-libre-debug.apk build/jellyfin-publish/
mv app/build/outputs/apk/*/*/jellyfin-android-*-proprietary-debug.apk build/jellyfin-publish/
mv app/build/outputs/apk/*/*/jellyfin-android-*-libre-release-unsigned.apk build/jellyfin-publish/
mv app/build/outputs/apk/*/*/jellyfin-android-*-proprietary-release-unsigned.apk build/jellyfin-publish/
mv ${{ steps.libreSign.outputs.signedReleaseFile }} build/jellyfin-publish/jellyfin-android-v${{ env.JELLYFIN_VERSION }}-libre-release.apk
mv ${{ steps.proprietarySign.outputs.signedReleaseFile }} build/jellyfin-publish/jellyfin-android-v${{ env.JELLYFIN_VERSION }}-proprietary-release.apk
mv ${{ steps.proprietaryBundleSign.outputs.signedReleaseFile }} build/jellyfin-publish/jellyfin-android-v${{ env.JELLYFIN_VERSION }}-proprietary-release.aab
mv app/build/outputs/apk/libre/debug/jellyfin-android-*-libre-debug.apk build/jellyfin-publish/
mv app/build/outputs/apk/libre/release/jellyfin-android-*-libre-release.apk build/jellyfin-publish/
mv app/build/outputs/bundle/libreRelease/jellyfin-android-*-libre-release.aab build/jellyfin-publish/
mv app/build/outputs/apk/proprietary/debug/jellyfin-android-*-proprietary-debug.apk build/jellyfin-publish/
mv app/build/outputs/apk/proprietary/release/jellyfin-android-*-proprietary-release.apk build/jellyfin-publish/
mv app/build/outputs/bundle/proprietaryRelease/jellyfin-android-*-proprietary-release.aab build/jellyfin-publish/
mv app/build/version.txt build/jellyfin-publish/
- name: Upload release archive to GitHub release
uses: alexellis/upload-assets@13926a61cdb2cb35f5fdef1c06b8b591523236d3 # 0.4.1
env:
GITHUB_TOKEN: ${{ secrets.JF_BOT_TOKEN }}
with:
asset_paths: '["build/jellyfin-publish/*"]'
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh release upload "$GITHUB_REF_NAME" build/jellyfin-publish/* --clobber
- name: Configure deployment
run: |
mkdir -p ~/.ssh
echo "${{ secrets.REPO_KEY }}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
- name: Upload release archive to repo.jellyfin.org
uses: burnett01/rsync-deployments@66257cad6bfeb2171d3b6bfa6c9a22279dd9c3a1 # 8.0.5
with:
switches: -vrptz
path: build/jellyfin-publish/
remote_path: /srv/incoming/android/v${{ env.JELLYFIN_VERSION }}/
remote_host: ${{ secrets.REPO_HOST }}
remote_user: ${{ secrets.REPO_USER }}
remote_key: ${{ secrets.REPO_KEY }}
env:
TARGET_PATH: /srv/incoming/android/v${{ env.JELLYFIN_VERSION }}/
SOURCE_PATH: build/jellyfin-publish/
run: rsync -vrptz -e "ssh -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no" "$SOURCE_PATH" "${{ secrets.REPO_USER }}@${{ secrets.REPO_HOST }}:$TARGET_PATH"
- name: Update repo.jellyfin.org symlinks
uses: appleboy/ssh-action@0ff4204d59e8e51228ff73bce53f80d53301dee2 # v1.2.5
with:
host: ${{ secrets.REPO_HOST }}
username: ${{ secrets.REPO_USER }}
key: ${{ secrets.REPO_KEY }}
envs: JELLYFIN_VERSION
script_stop: true
script: |
env:
REPO_HOST: ${{ secrets.REPO_HOST }}
REPO_USER: ${{ secrets.REPO_USER }}
run: |
ssh -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no "${REPO_USER}@${REPO_HOST}" "bash -s" <<EOF
if [ -d "/srv/repository/main/client/android/versions/v${{ env.JELLYFIN_VERSION }}" ] && [ -n "${{ env.JELLYFIN_VERSION }}" ]; then
sudo rm -r /srv/repository/main/client/android/versions/v${{ env.JELLYFIN_VERSION }}
fi
sudo mv /srv/incoming/android/v${{ env.JELLYFIN_VERSION }} /srv/repository/main/client/android/versions/v${{ env.JELLYFIN_VERSION }}
cd /srv/repository/main/client/android;
sudo rm -rf *.apk version.txt;
sudo ln -s versions/v${JELLYFIN_VERSION}/jellyfin-android-v${JELLYFIN_VERSION}-*.apk .;
sudo ln -s versions/v${JELLYFIN_VERSION}/version.txt .;
sudo ln -s versions/v${{ env.JELLYFIN_VERSION }}/jellyfin-android-v${{ env.JELLYFIN_VERSION }}-*.apk .;
sudo ln -s versions/v${{ env.JELLYFIN_VERSION }}/version.txt .;
EOF
+20 -7
View File
@@ -39,23 +39,36 @@ android {
vectorDrawables.useSupportLibrary = true
}
val releaseSigningConfig = SigningHelper.loadSigningConfig(project)?.let { config ->
signingConfigs.create("release") {
storeFile = config.storeFile
storePassword = config.storePassword
keyAlias = config.keyAlias
keyPassword = config.keyPassword
signingConfigs {
val keystoreFile = getProperty("keystore.file")
val keystorePassword = getProperty("keystore.password")
val signingKeyAlias = getProperty("signing.key.alias")
val signingKeyPassword = getProperty("signing.key.password")
if (keystoreFile != null && keystorePassword != null && signingKeyAlias != null && signingKeyPassword != null) {
create("release") {
storeFile = file(keystoreFile)
storePassword = keystorePassword
keyAlias = signingKeyAlias
keyPassword = signingKeyPassword
}
}
}
dependenciesInfo {
includeInBundle = false
includeInApk = false
}
buildTypes {
getByName("release") {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
signingConfig = releaseSigningConfig
signingConfig = signingConfigs.findByName("release")
}
getByName("debug") {
applicationIdSuffix = ".debug"
isDebuggable = true
+11
View File
@@ -0,0 +1,11 @@
import org.gradle.api.Project
/**
* Helper function to retrieve configuration variable values
*/
fun Project.getProperty(name: String): String? {
// sample.var --> SAMPLE_VAR
val environmentName = name.uppercase().replace(".", "_")
return findProperty(name)?.toString() ?: System.getenv(environmentName) ?: null
}
-49
View File
@@ -1,49 +0,0 @@
import org.gradle.api.Project
import java.io.File
import java.util.*
object SigningHelper {
fun loadSigningConfig(project: Project): Config? {
val serializedKeystore = System.getenv("KEYSTORE") ?: return null
val storeFile = try {
project.file("/tmp/keystore.jks").apply {
writeBytes(Base64.getDecoder().decode(serializedKeystore))
}
} catch (e: RuntimeException) {
return null
}
val storePassword = System.getenv("KEYSTORE_PASSWORD") ?: return null
val keyAlias = System.getenv("KEY_ALIAS") ?: return null
val keyPassword = System.getenv("KEY_PASSWORD") ?: return null
return Config(
storeFile,
storePassword,
keyAlias,
keyPassword
)
}
data class Config(
/**
* Store file used when signing.
*/
val storeFile: File,
/**
* Store password used when signing.
*/
val storePassword: String,
/**
* Key alias used when signing.
*/
val keyAlias: String,
/**
* Key password used when signing.
*/
val keyPassword: String
)
}
+13 -16
View File
@@ -10,29 +10,26 @@ import org.gradle.api.Project
* v2.0.0 -> 2.0.0
* null -> 0.0.0-dev.1 (unless different fallback set)
*/
fun Project.getVersionName(fallback: String = "0.0.0-dev.1"): String {
val configuredVersion = System.getenv("JELLYFIN_VERSION")
?: findProperty("jellyfin.version")?.toString()
return configuredVersion?.removePrefix("v") ?: fallback
}
fun Project.getVersionName(fallback: String = "0.0.0-dev.1") =
getProperty("jellyfin.version")
?.removePrefix("v")
?: fallback
/**
* Get the version code for a given semantic version.
* Does not validate the input and thus will throw an exception when parts are missing.
*
* The pre-release part ("-rc.1", "-beta.1" etc.) defaults to 99 when not specified.
* The pre-release part ("-rc.1", "-beta.1" etc.) defaults to 99
*
* Sample output:
* MA.MI.PA-PR > MA MI PA PR
* 0.0.0-dev.1 > 1
* 0.0.0 > 99
* 1.1.1 > 1 01 01 99
* 0.7.0 > 7 00 99
* 99.99.99 > 99 99 99 99
* 2.0.0-rc.3 > 2 00 00 03
* 2.0.0 > 2 00 00 99
* 99.99.99-rc.1 > 99 99 99 01
* MA.MI.PA-PR -> MAMIPAPR
* 0.0.0 -> 99
* 1.1.1 -> 1010199
* 0.7.0 -> 70099
* 99.99.99 -> 99999999
* 2.0.0-rc.3 -> 2000003
* 2.0.0 -> 2000099
* 99.99.99-rc.1 -> 99999901
*/
fun getVersionCode(versionName: String): Int {
// Split to core and pre release parts with a default for pre release (null)