From 26286ac6e707e039e5c9e9f4b51fc5001d877202 Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Sat, 20 Jun 2026 17:37:48 +0200 Subject: [PATCH] sign build --- .gitea/workflows/build-release.yml | 129 +++++++++++++--------- .gitignore | 3 + scripts/sync-android-sources.sh | 10 ++ src-tauri/Cargo.lock | 144 +++++++++++++++++-------- src-tauri/Cargo.toml | 3 + src-tauri/android/app/build.gradle.kts | 103 ++++++++++++++++++ 6 files changed, 298 insertions(+), 94 deletions(-) create mode 100644 src-tauri/android/app/build.gradle.kts diff --git a/.gitea/workflows/build-release.yml b/.gitea/workflows/build-release.yml index c6ace6e..e4dd8f4 100644 --- a/.gitea/workflows/build-release.yml +++ b/.gitea/workflows/build-release.yml @@ -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 <> 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" diff --git a/.gitignore b/.gitignore index 396c52d..30d5672 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,6 @@ vite.config.ts.timestamp-* # Logs logs *.log + +# Android signing keystore (NEVER commit) +android-keystore/ diff --git a/scripts/sync-android-sources.sh b/scripts/sync-android-sources.sh index 3148590..dd84de0 100755 --- a/scripts/sync-android-sources.sh +++ b/scripts/sync-android-sources.sh @@ -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" diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index a3e9815..f554d32 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -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", ] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 0a404eb..e833cee 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -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] diff --git a/src-tauri/android/app/build.gradle.kts b/src-tauri/android/app/build.gradle.kts new file mode 100644 index 0000000..02d0187 --- /dev/null +++ b/src-tauri/android/app/build.gradle.kts @@ -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") \ No newline at end of file