From a76d5ee0a10f403b759b13d4cbd006734d18b63d Mon Sep 17 00:00:00 2001 From: James Rich Date: Tue, 14 Apr 2026 17:22:45 -0500 Subject: [PATCH] feat(desktop): add Conveyor packaging for cross-platform distribution Replace the 4-OS matrix desktop build in release.yml with a single Conveyor runner on ubuntu-24.04 that cross-compiles all platform packages (macOS, Windows, Linux) from one machine. - Add Conveyor Gradle plugin (dev.hydraulic.conveyor v2.0) - Add conveyor.conf with app metadata, icons, JVM modules, entitlements - Add ci.conveyor.conf for CI overrides (production URL, Apple notarization) - Add defaults.conf.example template for local signing keys - Update release.yml: single Conveyor job replaces 4-OS matrix - Add 4 optional secrets: CONVEYOR_SIGNING_KEY, APPLE_TEAM_ID, APPLE_ID, APPLE_APP_SPECIFIC_PASSWORD - Add Conveyor Maven repo to settings.gradle.kts Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/release.yml | 62 ++++++++++++++++++----------- .gitignore | 5 +++ desktop/build.gradle.kts | 10 +++-- desktop/ci.conveyor.conf | 15 +++++++ desktop/conveyor.conf | 73 +++++++++++++++++++++++++++++++++++ desktop/defaults.conf.example | 11 ++++++ settings.gradle.kts | 1 + 7 files changed, 152 insertions(+), 25 deletions(-) create mode 100644 desktop/ci.conveyor.conf create mode 100644 desktop/conveyor.conf create mode 100644 desktop/defaults.conf.example diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 40d8e40f3..7095ae938 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -53,6 +53,14 @@ on: required: false INTERNAL_BUILDS_HOST_PAT: required: false + CONVEYOR_SIGNING_KEY: + required: false + APPLE_TEAM_ID: + required: false + APPLE_ID: + required: false + APPLE_APP_SPECIFIC_PASSWORD: + required: false concurrency: group: ${{ github.workflow }}-${{ inputs.tag_name }} @@ -252,13 +260,9 @@ jobs: release-desktop: if: ${{ inputs.build_desktop }} - runs-on: ${{ matrix.os }} + runs-on: ubuntu-24.04 needs: [prepare-build-info] environment: Release - strategy: - fail-fast: false - matrix: - os: [macos-latest, windows-latest, ubuntu-24.04, ubuntu-24.04-arm] env: GRADLE_CACHE_URL: ${{ secrets.GRADLE_CACHE_URL }} GRADLE_CACHE_USERNAME: ${{ secrets.GRADLE_CACHE_USERNAME }} @@ -277,35 +281,49 @@ jobs: gradle_encryption_key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} cache_read_only: 'false' - - name: Install dependencies for AppImage - if: runner.os == 'Linux' - run: sudo apt-get update && sudo apt-get install -y libfuse2 - - - name: Package Native Distributions + - name: Build Desktop JAR env: ORG_GRADLE_PROJECT_appVersionName: ${{ needs.prepare-build-info.outputs.APP_VERSION_NAME }} - APPIMAGE_EXTRACT_AND_RUN: 1 - run: ./gradlew :desktop:packageReleaseDistributionForCurrentOS -PaboutLibraries.release=true --no-daemon + VERSION_CODE: ${{ needs.prepare-build-info.outputs.APP_VERSION_CODE }} + run: ./gradlew :desktop:jar :desktop:writeConveyorConfig -PaboutLibraries.release=true --no-daemon - - name: List Desktop Binaries - if: runner.os == 'Linux' - run: ls -R desktop/build/compose/binaries/main-release + - name: Package with Conveyor + uses: hydraulic-software/conveyor/actions/build@v22.0 + with: + command: make copied-site + signing_key: ${{ secrets.CONVEYOR_SIGNING_KEY }} + agree_to_license: 1 + extra_flags: -f desktop/ci.conveyor.conf -o desktop/build/conveyor-output + env: + APPLE_NOTARIZATION_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + APPLE_NOTARIZATION_APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} - name: Upload Desktop Artifacts if: always() uses: actions/upload-artifact@v7 with: - name: desktop-${{ runner.os }}-${{ runner.arch }} + name: desktop-all-platforms path: | - desktop/build/compose/binaries/main-release/*/*.dmg - desktop/build/compose/binaries/main-release/*/*.msi - desktop/build/compose/binaries/main-release/*/*.exe - desktop/build/compose/binaries/main-release/*/*.deb - desktop/build/compose/binaries/main-release/*/*.rpm - desktop/build/compose/binaries/main-release/*/*.AppImage + desktop/build/conveyor-output/*.zip + desktop/build/conveyor-output/*.tar.gz + desktop/build/conveyor-output/*.msix + desktop/build/conveyor-output/*.exe + desktop/build/conveyor-output/*.deb + desktop/build/conveyor-output/debian/*.deb + desktop/build/conveyor-output/download.html retention-days: 1 if-no-files-found: ignore + - name: Attest Desktop provenance + if: success() + uses: actions/attest-build-provenance@v4 + with: + subject-path: | + desktop/build/conveyor-output/*.zip + desktop/build/conveyor-output/*.tar.gz + desktop/build/conveyor-output/*.msix + github-release: if: ${{ !cancelled() && !failure() }} runs-on: ubuntu-24.04-arm diff --git a/.gitignore b/.gitignore index 8447bc7f7..5b7935a8e 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,8 @@ wireless-install.sh firebase-debug.log .agent_plans/ .agent_refs/ + +# Conveyor packaging (desktop) +desktop/defaults.conf +desktop/generated.conveyor.conf +desktop/build/conveyor-output/ diff --git a/desktop/build.gradle.kts b/desktop/build.gradle.kts index fdf7cee5c..019d7b1a0 100644 --- a/desktop/build.gradle.kts +++ b/desktop/build.gradle.kts @@ -32,6 +32,7 @@ plugins { alias(libs.plugins.meshtastic.koin) id("meshtastic.kover") alias(libs.plugins.aboutlibraries) + id("dev.hydraulic.conveyor") version "2.0" } // ── Version resolution (mirrors app/build.gradle.kts) ──────────────────────── @@ -52,6 +53,11 @@ val resolvedIsDebug: Boolean = project.findProperty("desktop.release")?.toString val resolvedMinFwVersion: String = configProperties.getProperty("MIN_FW_VERSION") ?: "" val resolvedAbsMinFwVersion: String = configProperties.getProperty("ABS_MIN_FW_VERSION") ?: "" +// Set project version for the Conveyor Gradle plugin (requires strict semver X.Y.Z) +val sanitizedVersion = Regex("^\\d+\\.\\d+\\.\\d+").find(resolvedVersionName)?.value ?: "1.0.0" +version = sanitizedVersion +group = "org.meshtastic" + // ── Generate DesktopBuildConfig ────────────────────────────────────────────── // Mirrors AGP's BuildConfig for Android so the desktop runtime has access to the // same version metadata without hardcoding. @@ -209,9 +215,7 @@ compose.desktop { else -> targetFormats(TargetFormat.Deb, TargetFormat.Rpm, TargetFormat.AppImage) } - // Reuse the resolved version from the top of this script (mirrors app/build.gradle.kts). - // Native installers require strict numeric semantic versions (X.Y.Z) without suffixes. - val sanitizedVersion = Regex("^\\d+\\.\\d+\\.\\d+").find(resolvedVersionName)?.value ?: "1.0.0" + // Reuse the project-level version for native installers. packageVersion = sanitizedVersion description = "Meshtastic Desktop Application" diff --git a/desktop/ci.conveyor.conf b/desktop/ci.conveyor.conf new file mode 100644 index 000000000..266e42197 --- /dev/null +++ b/desktop/ci.conveyor.conf @@ -0,0 +1,15 @@ +// Conveyor CI overrides — used by GitHub Actions release workflow. +// Reads signing key and Apple notarization credentials from environment variables. +include required("conveyor.conf") + +app { + // Production update channel + site.base-url = "https://github.com/meshtastic/Meshtastic-Android/releases/latest/download" + + // Apple notarization (optional — only used when APPLE_NOTARIZATION_TEAM_ID is set) + mac.notarization { + team-id = ${?env.APPLE_NOTARIZATION_TEAM_ID} + apple-id = ${?env.APPLE_NOTARIZATION_APPLE_ID} + app-specific-password = ${?env.APPLE_NOTARIZATION_PASSWORD} + } +} diff --git a/desktop/conveyor.conf b/desktop/conveyor.conf new file mode 100644 index 000000000..5eaab42fb --- /dev/null +++ b/desktop/conveyor.conf @@ -0,0 +1,73 @@ +// Meshtastic Desktop — Conveyor packaging configuration +// https://conveyor.hydraulic.dev/latest/configs/jvm/ + +// Import Gradle-generated config (classpath, mainClass, version, vendor, JVM args, JDK) +// Regenerate with: ./gradlew :desktop:writeConveyorConfig +include required("generated.conveyor.conf") + +// Library compatibility for native extraction (JNA, SQLite, etc.) +include required("https://raw.githubusercontent.com/hydraulic-software/conveyor/master/configs/jvm/extract-native-libraries.conf") + +app { + display-name = Meshtastic + fsname = meshtastic + license = GPL-3.0-or-later + vcs-url = "https://github.com/meshtastic/Meshtastic-Android" + + // Conveyor renders all required formats from a single high-res PNG + icons = src/main/resources/icon.png + + // Update channel — published to GitHub Releases + site.base-url = "localhost:3000" + + // ── JVM ────────────────────────────────────────────────────────────────────── + jvm { + // Modules that jdeps may miss (reflection, JNI) + modules += java.net.http + modules += jdk.accessibility + modules += jdk.crypto.ec + modules += jdk.unsupported + modules += java.sql + modules += java.naming + + // Extract native libs from JARs for faster startup and clean uninstalls + extract-native-libraries = true + } + + // ── macOS ──────────────────────────────────────────────────────────────────── + mac { + info-plist { + NSBluetoothAlwaysUsageDescription = "Meshtastic uses Bluetooth to communicate with your Meshtastic radio device." + NSLocalNetworkUsageDescription = "Meshtastic uses your local network to discover Meshtastic devices connected via WiFi." + NSUserNotificationAlertStyle = alert + LSMinimumSystemVersion = "12.0" + CFBundleURLTypes = [{ + CFBundleURLName = "Meshtastic deep link" + CFBundleURLSchemes = [meshtastic] + }] + } + entitlements-plist { + com.apple.security.cs.allow-jit = true + com.apple.security.cs.allow-unsigned-executable-memory = true + com.apple.security.cs.disable-library-validation = true + com.apple.security.device.bluetooth = true + } + } + + // ── Windows ────────────────────────────────────────────────────────────────── + windows { + // Stable upgrade UUID for in-place MSI upgrades + upgrade-uuid = "b8f3d4a1-7e52-4c89-9a1b-3f6e8d2c5a70" + } + + // ── Linux ──────────────────────────────────────────────────────────────────── + linux { + desktop-file { + "Desktop Entry" { + Categories = "Network;" + } + } + } +} + +conveyor.compatibility-level = 22 diff --git a/desktop/defaults.conf.example b/desktop/defaults.conf.example new file mode 100644 index 000000000..ff90de709 --- /dev/null +++ b/desktop/defaults.conf.example @@ -0,0 +1,11 @@ +// Local Conveyor defaults — copy to defaults.conf and fill in your values. +// This file is checked in as a template; defaults.conf is gitignored. +conveyor.billing-email = "you@example.com" + +// Uncomment for real code signing: +// app.signing-key = "" +// app.mac.notarization { +// team-id = "YOUR_TEAM_ID" +// apple-id = "you@example.com" +// app-specific-password = ${env.APPLE_ASP} +// } diff --git a/settings.gradle.kts b/settings.gradle.kts index 656d6f831..6d6654826 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -62,6 +62,7 @@ pluginManagement { mavenCentral() gradlePluginPortal() maven { url = uri("https://jitpack.io") } + maven("https://maven.hq.hydraulic.software") // Conveyor packaging plugin } }