sign build
Some checks failed
🏗️ Build and Test JellyTau / Run Tests (push) Successful in 10m47s
Traceability Validation / Check Requirement Traces (push) Failing after 4s
🏗️ Build and Test JellyTau / Build Android APK (push) Failing after 1m41s

This commit is contained in:
Duncan Tourolle 2026-06-20 17:37:48 +02:00
parent 144abb2f8b
commit 26286ac6e7
6 changed files with 298 additions and 94 deletions

View File

@ -185,24 +185,54 @@ jobs:
- name: Install dependencies
run: bun install
- name: Build for Android
run: bun run tauri android build
env:
ANDROID_NDK_HOME: ${{ android.ndk-home }}
ANDROID_SDK_ROOT: ${{ android.sdk-root }}
ANDROID_HOME: ${{ android.sdk-root }}
- name: Resolve Android NDK path
run: echo "ANDROID_NDK_HOME=$ANDROID_SDK_ROOT/ndk/25.1.8937393" >> "$GITHUB_ENV"
- name: Prepare Android artifacts
- name: Set app version from tag
run: |
REF="${GITHUB_REF#refs/tags/v}"
VERSION="${REF#refs/heads/}"
# On non-tag runs keep whatever is in tauri.conf.json
if echo "$GITHUB_REF" | grep -q '^refs/tags/v'; then
echo "Setting version to $VERSION"
sed -i "s/\"version\": \"[^\"]*\"/\"version\": \"$VERSION\"/" src-tauri/tauri.conf.json
fi
grep '"version"' src-tauri/tauri.conf.json
- name: Initialize Android project
run: bun run tauri android init
env:
ANDROID_NDK_HOME: ${{ env.ANDROID_NDK_HOME }}
- name: Sync custom Android sources & gradle config
run: ./scripts/sync-android-sources.sh
- name: Write signing keystore
run: |
echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 -d > "$RUNNER_TEMP/jellytau-release.jks"
cat > src-tauri/gen/android/keystore.properties <<EOF
storeFile=$RUNNER_TEMP/jellytau-release.jks
storePassword=${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
keyAlias=${{ secrets.ANDROID_KEY_ALIAS }}
keyPassword=${{ secrets.ANDROID_KEY_PASSWORD }}
EOF
- name: Build signed Android APK
run: bun run tauri android build --apk
env:
ANDROID_NDK_HOME: ${{ env.ANDROID_NDK_HOME }}
ANDROID_SDK_ROOT: ${{ env.ANDROID_SDK_ROOT }}
ANDROID_HOME: ${{ env.ANDROID_SDK_ROOT }}
- name: Collect & verify signed APK
run: |
mkdir -p dist/android
# Copy APK
if [ -f "src-tauri/gen/android/app/build/outputs/apk/release/app-release.apk" ]; then
cp src-tauri/gen/android/app/build/outputs/apk/release/app-release.apk dist/android/jellytau-release.apk
fi
# Copy AAB (Android App Bundle) if built
if [ -f "src-tauri/gen/android/app/build/outputs/bundle/release/app-release.aab" ]; then
cp src-tauri/gen/android/app/build/outputs/bundle/release/app-release.aab dist/android/jellytau-release.aab
fi
APK=$(find src-tauri/gen/android/app/build/outputs/apk -name '*-release.apk' | head -1)
if [ -z "$APK" ]; then echo "❌ No release APK produced"; exit 1; fi
cp "$APK" dist/android/jellytau-release.apk
APKSIGNER=$(find "$ANDROID_SDK_ROOT/build-tools" -name apksigner | sort -V | tail -1)
echo "🔏 Verifying signature with $APKSIGNER"
"$APKSIGNER" verify --print-certs dist/android/jellytau-release.apk
ls -lah dist/android/
- name: Upload Android build artifact
@ -294,44 +324,41 @@ jobs:
echo "---" >> release_notes.md
echo "Built with Tauri, SvelteKit, and Rust 🦀" >> release_notes.md
- name: Create GitHub Release
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
with:
name: ${{ steps.tag_name.outputs.RELEASE_NAME }}
body_path: release_notes.md
files: |
artifacts/linux/*
artifacts/android/*
draft: false
prerelease: ${{ contains(steps.tag_name.outputs.VERSION, 'rc') || contains(steps.tag_name.outputs.VERSION, 'beta') || contains(steps.tag_name.outputs.VERSION, 'alpha') }}
- name: Publish Gitea release & upload assets
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload to Gitea Releases
# GITEA_TOKEN (a PAT) is preferred; falls back to the auto-provided token.
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
AUTO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -e
command -v jq >/dev/null || { echo "❌ jq is required on the runner"; exit 1; }
VERSION="${{ steps.tag_name.outputs.VERSION }}"
API="${GITHUB_SERVER_URL}/api/v1"
REPO="${GITHUB_REPOSITORY}"
TOKEN="${GITEA_TOKEN:-$AUTO_TOKEN}"
case "$VERSION" in *rc*|*beta*|*alpha*) PRE=true;; *) PRE=false;; esac
echo "📦 Release artifacts prepared for $VERSION"
echo ""
echo "Linux:"
ls -lh artifacts/linux/ || echo "No Linux artifacts"
echo ""
echo "Android:"
ls -lh artifacts/android/ || echo "No Android artifacts"
echo ""
echo "✅ Release $VERSION is ready!"
echo "📄 Release notes saved to release_notes.md"
PAYLOAD=$(jq -n \
--arg tag "$VERSION" \
--arg name "JellyTau $VERSION" \
--rawfile body release_notes.md \
--argjson pre "$PRE" \
'{tag_name:$tag, name:$name, body:$body, draft:false, prerelease:$pre}')
- name: Publish release notes
run: |
echo "## 🎉 Release Published"
echo ""
echo "**Version:** ${{ steps.tag_name.outputs.VERSION }}"
echo "**Tag:** ${{ github.ref }}"
echo ""
echo "Artifacts:"
echo "- Linux artifacts in: artifacts/linux/"
echo "- Android artifacts in: artifacts/android/"
echo ""
echo "Visit the Release page to download files."
echo "📦 Creating release $VERSION on $REPO"
RESP=$(curl -fsS -X POST "$API/repos/$REPO/releases" \
-H "Authorization: token $TOKEN" \
-H "Content-Type: application/json" \
-d "$PAYLOAD")
RELEASE_ID=$(echo "$RESP" | jq -r '.id')
echo "Release id=$RELEASE_ID"
for f in artifacts/android/* artifacts/linux/*; do
[ -f "$f" ] || continue
echo "⬆️ Uploading $(basename "$f")"
curl -fsS -X POST \
"$API/repos/$REPO/releases/$RELEASE_ID/assets?name=$(basename "$f")" \
-H "Authorization: token $TOKEN" \
-F "attachment=@$f" >/dev/null
done
echo "✅ Release $VERSION published with assets"

3
.gitignore vendored
View File

@ -47,3 +47,6 @@ vite.config.ts.timestamp-*
# Logs
logs
*.log
# Android signing keystore (NEVER commit)
android-keystore/

View File

@ -31,4 +31,14 @@ for kt_file in "$SOURCE_DIR"/*.kt; do
fi
done
# Restore the app module build.gradle.kts (media3 deps + release signing config).
# gen/android is regenerated by `tauri android init`, so this tracked template is
# the source of truth and must be copied back after any (re)generation.
APP_GRADLE_SRC="$PROJECT_ROOT/src-tauri/android/app/build.gradle.kts"
APP_GRADLE_DST="$PROJECT_ROOT/src-tauri/gen/android/app/build.gradle.kts"
if [ -f "$APP_GRADLE_SRC" ]; then
cp "$APP_GRADLE_SRC" "$APP_GRADLE_DST"
echo " Copied: app/build.gradle.kts"
fi
echo "✓ Android sources synced successfully"

144
src-tauri/Cargo.lock generated
View File

@ -2,6 +2,12 @@
# It is not intended for manual editing.
version = 4
[[package]]
name = "Inflector"
version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
[[package]]
name = "adler2"
version = "2.0.1"
@ -1745,7 +1751,7 @@ dependencies = [
"js-sys",
"log",
"wasm-bindgen",
"windows-core 0.62.2",
"windows-core",
]
[[package]]
@ -2011,10 +2017,13 @@ dependencies = [
"serde",
"serde_json",
"sha2",
"specta",
"specta-typescript",
"tauri",
"tauri-build",
"tauri-plugin-opener",
"tauri-plugin-os",
"tauri-specta",
"tempfile",
"tokio",
"tokio-rusqlite",
@ -2795,6 +2804,12 @@ dependencies = [
"windows-link 0.2.1",
]
[[package]]
name = "paste"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "pathdiff"
version = "0.2.3"
@ -3933,6 +3948,51 @@ dependencies = [
"system-deps",
]
[[package]]
name = "specta"
version = "2.0.0-rc.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab7f01e9310a820edd31c80fde3cae445295adde21a3f9416517d7d65015b971"
dependencies = [
"chrono",
"paste",
"specta-macros",
"thiserror 1.0.69",
]
[[package]]
name = "specta-macros"
version = "2.0.0-rc.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0074b9e30ed84c6924eb63ad8d2fe71cdc82628525d84b1fcb1f2fd40676517"
dependencies = [
"Inflector",
"proc-macro2",
"quote",
"syn 2.0.112",
]
[[package]]
name = "specta-serde"
version = "0.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77216504061374659e7245eac53d30c7b3e5fe64b88da97c753e7184b0781e63"
dependencies = [
"specta",
"thiserror 1.0.69",
]
[[package]]
name = "specta-typescript"
version = "0.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3220a0c365e51e248ac98eab5a6a32f544ff6f961906f09d3ee10903a4f52b2d"
dependencies = [
"specta",
"specta-serde",
"thiserror 1.0.69",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.1"
@ -4092,7 +4152,7 @@ dependencies = [
"unicode-segmentation",
"url",
"windows",
"windows-core 0.61.2",
"windows-core",
"windows-version",
"x11-dl",
]
@ -4149,6 +4209,7 @@ dependencies = [
"serde_json",
"serde_repr",
"serialize-to-javascript",
"specta",
"swift-rs",
"tauri-build",
"tauri-macros",
@ -4337,6 +4398,34 @@ dependencies = [
"wry",
]
[[package]]
name = "tauri-specta"
version = "2.0.0-rc.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b23c0132dd3cf6064e5cd919b82b3f47780e9280e7b5910babfe139829b76655"
dependencies = [
"heck 0.5.0",
"serde",
"serde_json",
"specta",
"specta-typescript",
"tauri",
"tauri-specta-macros",
"thiserror 2.0.17",
]
[[package]]
name = "tauri-specta-macros"
version = "2.0.0-rc.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a4aa93823e07859546aa796b8a5d608190cd8037a3a5dce3eb63d491c34bda8"
dependencies = [
"heck 0.5.0",
"proc-macro2",
"quote",
"syn 2.0.112",
]
[[package]]
name = "tauri-utils"
version = "2.8.1"
@ -5143,7 +5232,7 @@ dependencies = [
"webview2-com-macros",
"webview2-com-sys",
"windows",
"windows-core 0.61.2",
"windows-core",
"windows-implement",
"windows-interface",
]
@ -5167,7 +5256,7 @@ checksum = "36695906a1b53a3bf5c4289621efedac12b73eeb0b89e7e1a89b517302d5d75c"
dependencies = [
"thiserror 2.0.17",
"windows",
"windows-core 0.61.2",
"windows-core",
]
[[package]]
@ -5192,7 +5281,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys 0.61.2",
"windows-sys 0.48.0",
]
[[package]]
@ -5223,7 +5312,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893"
dependencies = [
"windows-collections",
"windows-core 0.61.2",
"windows-core",
"windows-future",
"windows-link 0.1.3",
"windows-numerics",
@ -5235,7 +5324,7 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8"
dependencies = [
"windows-core 0.61.2",
"windows-core",
]
[[package]]
@ -5247,21 +5336,8 @@ dependencies = [
"windows-implement",
"windows-interface",
"windows-link 0.1.3",
"windows-result 0.3.4",
"windows-strings 0.4.2",
]
[[package]]
name = "windows-core"
version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
dependencies = [
"windows-implement",
"windows-interface",
"windows-link 0.2.1",
"windows-result 0.4.1",
"windows-strings 0.5.1",
"windows-result",
"windows-strings",
]
[[package]]
@ -5270,7 +5346,7 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
dependencies = [
"windows-core 0.61.2",
"windows-core",
"windows-link 0.1.3",
"windows-threading",
]
@ -5315,7 +5391,7 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
dependencies = [
"windows-core 0.61.2",
"windows-core",
"windows-link 0.1.3",
]
@ -5328,15 +5404,6 @@ dependencies = [
"windows-link 0.1.3",
]
[[package]]
name = "windows-result"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
dependencies = [
"windows-link 0.2.1",
]
[[package]]
name = "windows-strings"
version = "0.4.2"
@ -5346,15 +5413,6 @@ dependencies = [
"windows-link 0.1.3",
]
[[package]]
name = "windows-strings"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
dependencies = [
"windows-link 0.2.1",
]
[[package]]
name = "windows-sys"
version = "0.45.0"
@ -5750,7 +5808,7 @@ dependencies = [
"webkit2gtk-sys",
"webview2-com",
"windows",
"windows-core 0.61.2",
"windows-core",
"windows-version",
"x11-dl",
]

View File

@ -50,6 +50,9 @@ sha2 = "0.10"
getrandom = "0.2"
log = "0.4"
env_logger = "0.11"
tauri-specta = { version = "=2.0.0-rc.21", features = ["derive", "typescript"] }
specta-typescript = "=0.0.9"
specta = { version = "=2.0.0-rc.22", features = ["chrono", "derive"] }
# Linux-specific dependencies
[target.'cfg(target_os = "linux")'.dependencies]

View File

@ -0,0 +1,103 @@
import java.util.Properties
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("rust")
}
val tauriProperties = Properties().apply {
val propFile = file("tauri.properties")
if (propFile.exists()) {
propFile.inputStream().use { load(it) }
}
}
// Release signing: loaded from gen/android/keystore.properties if present.
// Falls back to no signing config (debug-signed) when the file is absent.
val keystoreProperties = Properties().apply {
val propFile = rootProject.file("keystore.properties")
if (propFile.exists()) {
propFile.inputStream().use { load(it) }
}
}
android {
compileSdk = 36
namespace = "com.dtourolle.jellytau"
defaultConfig {
manifestPlaceholders["usesCleartextTraffic"] = "false"
applicationId = "com.dtourolle.jellytau"
minSdk = 24
targetSdk = 36
versionCode = tauriProperties.getProperty("tauri.android.versionCode", "1").toInt()
versionName = tauriProperties.getProperty("tauri.android.versionName", "1.0")
}
signingConfigs {
create("release") {
keystoreProperties.getProperty("storeFile")?.let {
storeFile = file(it)
storePassword = keystoreProperties.getProperty("storePassword")
keyAlias = keystoreProperties.getProperty("keyAlias")
keyPassword = keystoreProperties.getProperty("keyPassword")
}
}
}
buildTypes {
getByName("debug") {
manifestPlaceholders["usesCleartextTraffic"] = "true"
isDebuggable = true
isJniDebuggable = true
isMinifyEnabled = false
packaging { jniLibs.keepDebugSymbols.add("*/arm64-v8a/*.so")
jniLibs.keepDebugSymbols.add("*/armeabi-v7a/*.so")
jniLibs.keepDebugSymbols.add("*/x86/*.so")
jniLibs.keepDebugSymbols.add("*/x86_64/*.so")
}
}
getByName("release") {
if (keystoreProperties.getProperty("storeFile") != null) {
signingConfig = signingConfigs.getByName("release")
}
isMinifyEnabled = true
proguardFiles(
*fileTree(".") { include("**/*.pro") }
.plus(getDefaultProguardFile("proguard-android-optimize.txt"))
.toList().toTypedArray()
)
}
}
kotlinOptions {
jvmTarget = "1.8"
}
buildFeatures {
buildConfig = true
}
}
rust {
rootDirRel = "../../../"
}
dependencies {
implementation("androidx.webkit:webkit:1.14.0")
implementation("androidx.appcompat:appcompat:1.7.1")
implementation("androidx.activity:activity-ktx:1.10.1")
implementation("com.google.android.material:material:1.12.0")
// Media3 dependencies for audio playback
implementation("androidx.media3:media3-exoplayer:1.5.0")
implementation("androidx.media3:media3-exoplayer-hls:1.5.0")
implementation("androidx.media3:media3-session:1.5.0")
implementation("androidx.media3:media3-common:1.5.0")
implementation("com.google.guava:guava:33.0.0-android")
// Media library for VolumeProviderCompat (remote volume control)
implementation("androidx.media:media:1.7.0")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.4")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.0")
}
apply(from = "tauri.build.gradle.kts")