Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ changelog:
labels:
- enhancement
- feature
- title: πŸ–₯️ Desktop
labels:
- desktop
- title: πŸ› οΈ Fixes
labels:
- bug
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pr_enforce_labels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
script: |
// Extract labels from the payload directly to avoid extra API calls
const latestLabels = context.payload.pull_request.labels.map(label => label.name);
const requiredLabels = ['bugfix', 'enhancement', 'automation', 'dependencies', 'repo', 'release', 'refactor'];
const requiredLabels = ['bugfix', 'enhancement', 'automation', 'dependencies', 'repo', 'release', 'refactor', 'desktop'];
console.log('Labels from payload:', latestLabels);
const hasRequiredLabel = latestLabels.some(label => requiredLabels.includes(label));
if (!hasRequiredLabel) {
Expand Down
69 changes: 68 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,16 @@ on:
required: false
INTERNAL_BUILDS_HOST_PAT:
required: false
DISCORD_WEBHOOK_DESKTOP:
required: false
APPLE_SIGNING_IDENTITY:
required: false
APPLE_ID:
required: false
APPLE_APP_SPECIFIC_PASSWORD:
required: false
APPLE_TEAM_ID:
required: false

concurrency:
group: ${{ github.workflow }}-${{ inputs.tag_name }}
Expand Down Expand Up @@ -284,7 +294,13 @@ jobs:
- name: Package Native Distributions
env:
ORG_GRADLE_PROJECT_appVersionName: ${{ needs.prepare-build-info.outputs.APP_VERSION_NAME }}
VERSION_CODE: ${{ needs.prepare-build-info.outputs.APP_VERSION_CODE }}
APPIMAGE_EXTRACT_AND_RUN: 1
SIGN_MACOS: ${{ runner.os == 'macOS' && 'true' || 'false' }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: ./gradlew :desktop:packageReleaseDistributionForCurrentOS -PaboutLibraries.release=true --no-daemon

- name: List Desktop Binaries
Expand All @@ -306,10 +322,61 @@ jobs:
retention-days: 1
if-no-files-found: ignore

- name: Attest Desktop artifact provenance
if: success()
uses: actions/attest-build-provenance@v4
with:
subject-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

notify-desktop-release:
if: ${{ inputs.build_desktop && !cancelled() && !failure() }}
runs-on: ubuntu-24.04-arm
needs: [prepare-build-info, release-desktop]
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_DESKTOP }}
VERSION: ${{ inputs.tag_name }}
steps:
- name: Notify Discord
run: |
if [[ -z "$DISCORD_WEBHOOK" ]]; then
echo "No DISCORD_WEBHOOK_DESKTOP secret provided. Skipping notification."
exit 0
fi

PAYLOAD=$(cat <<EOF
{
"content": null,
"embeds": [
{
"title": "πŸ–₯️ New Desktop Release: $VERSION",
"description": "Desktop installers (macOS DMG, Windows MSI/EXE, Linux DEB/RPM/AppImage) are available.",
"color": 3447003,
"fields": [
{
"name": "Version",
"value": "$VERSION",
"inline": true
}
],
"url": "https://github.com/meshtastic/Meshtastic-Android/releases/tag/$VERSION"
}
]
}
EOF
)

curl -H "Content-Type: application/json" -d "$PAYLOAD" "$DISCORD_WEBHOOK"

github-release:
if: ${{ !cancelled() && !failure() }}
runs-on: ubuntu-24.04-arm
needs: [prepare-build-info, release-google, release-fdroid, release-desktop]
needs: [prepare-build-info, release-google, release-fdroid, release-desktop, notify-desktop-release]
env:
INTERNAL_BUILDS_HOST: ${{ secrets.INTERNAL_BUILDS_HOST }}
permissions:
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ width="24%">](https://play.google.com/store/apps/details?id=com.geeksville.mesh&
The play store is the last to update of these options, but if you want to join the Play Store testing program go to [this URL](https://play.google.com/apps/testing/com.geeksville.mesh) and opt-in to become a tester.
If you encounter any problems or have questions, [ask us on the discord](https://discord.gg/meshtastic), [create an issue](https://github.com/meshtastic/Meshtastic-Android/issues), or [post in the forum](https://github.com/orgs/meshtastic/discussions) and we'll help as we can.

### Desktop

**Meshtastic Desktop** installers (macOS DMG, Windows MSI/EXE, Linux DEB/RPM/AppImage) are available from [GitHub Releases](https://github.com/meshtastic/Meshtastic-Android/releases). A Flatpak package is maintained at [vidplace7/org.meshtastic.desktop](https://github.com/vidplace7/org.meshtastic.desktop).

## Documentation

The project's documentation is generated with [Dokka](https://kotlinlang.org/docs/dokka-introduction.html) and hosted on GitHub Pages. It is automatically updated on every push to the `main` branch.
Expand Down
57 changes: 48 additions & 9 deletions RELEASE_PROCESS.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
# Meshtastic-Android Release Process
# Meshtastic Release Process

This guide summarizes the steps for releasing a new version of Meshtastic-Android. The process is fully automated via GitHub Actions and Fastlane.
This guide summarizes the steps for releasing new versions of Meshtastic Android and Desktop. The process is fully automated via GitHub Actions and Fastlane.

## Overview

The entire release process is managed by a single, manually-triggered GitHub Action: **`Create or Promote Release`**.

- **Trigger:** To start a new release or promote an existing one, a developer manually runs the workflow from the GitHub Actions tab.
- **Inputs:** The workflow requires two inputs:
- **Inputs:** The workflow requires the following inputs:
1. `version`: The base version number you are releasing (e.g., `2.4.0`).
2. `channel`: The release channel you are targeting (`internal`, `closed`, `open`, or `production`).
3. `build_desktop`: Whether to build and attach Desktop native installers (default: `false`).
- **Automation:** The workflow handles everything automatically:
- **Syncs Assets:** Fetches the latest firmware/hardware lists, protobuf definitions, and translations (Crowdin).
- **Generates Changelog:** Creates a clean changelog from commits since the last production release and commits it to the repo.
- **Updates Config:** Automatically bumps the `VERSION_NAME_BASE` in `config.properties`.
- **Verifies & Tags:** Runs lint checks, builds the app, and *only* tags the release if successful.
- **Deploys:** Uploads the build to the correct Google Play track and attaches artifacts (`.aab`/`.apk`) to a GitHub Release.
- **Deploys Android:** Uploads the build to the correct Google Play track and attaches artifacts (`.aab`/`.apk`) to a GitHub Release.
- **Deploys Desktop** *(when enabled)*: Builds native installers (DMG, MSI, EXE, DEB, RPM, AppImage) on a matrix of runners and attaches them to the GitHub Release.
- **Changelog:** Release notes are auto-generated from PR labels. Ensure PRs are labeled correctly to maintain an accurate changelog.

## Release Steps
Expand All @@ -27,13 +29,15 @@ The entire release process is managed by a single, manually-triggered GitHub Act
3. Click the **"Run workflow"** dropdown.
4. Enter the base `version` (e.g., `2.4.0`).
5. Select the `internal` channel.
6. Click **"Run workflow"**.
6. Check **`build_desktop`** if you want Desktop installers included in this release.
7. Click **"Run workflow"**.

The workflow will:
1. **Create a new commit** on the current branch containing updated assets, translations, and the new changelog.
2. **Tag** that commit with an incremental internal tag (e.g., `v2.4.0-internal.1`).
3. **Build & Deploy** the verified artifact to the Play Store Internal track.
4. Publish a **draft** pre-release on GitHub.
3. **Build & Deploy** the verified Android artifact to the Play Store Internal track.
4. **Build Desktop** *(if enabled)* native installers on macOS, Windows, and Linux runners.
5. Publish a **draft** pre-release on GitHub with all artifacts attached.

### 2. Promote to the Next Channel

Expand All @@ -54,8 +58,43 @@ After testing is complete on all pre-release channels, you can create the final

### 4. Post-Release

1. **Verify:** Check the Google Play Console to ensure the build is available on the correct track.
2. **Merge:** Merge the release branch (if one was used for stabilization) back into `main`.
1. **Verify Android:** Check the Google Play Console to ensure the build is available on the correct track.
2. **Verify Desktop** *(if built)*: Download and smoke-test at least one installer (DMG, MSI, or AppImage) from the GitHub Release.
3. **Merge:** Merge the release branch (if one was used for stabilization) back into `main`.

## Desktop Release Details

Desktop native installers are built as part of the main release pipeline when `build_desktop` is enabled. There is no separate promotion flow for Desktop β€” installers are built once during the `internal` release and attached to the GitHub Release alongside Android artifacts.

### Artifacts Produced

| Platform | Format | Runner |
|---|---|---|
| macOS | `.dmg` | `macos-latest` |
| Windows | `.msi`, `.exe` | `windows-latest` |
| Linux (x86_64) | `.deb`, `.rpm`, `.AppImage` | `ubuntu-24.04` |
| Linux (ARM64) | `.deb`, `.rpm`, `.AppImage` | `ubuntu-24.04-arm` |

### macOS Code Signing & Notarization

macOS builds are signed and notarized when the following CI secrets are configured:

| Secret | Source |
|---|---|
| `APPLE_SIGNING_IDENTITY` | Developer ID Application certificate (from Apple Developer account) |
| `APPLE_ID` | Apple ID email used for notarization |
| `APPLE_APP_SPECIFIC_PASSWORD` | App-specific password from [appleid.apple.com](https://appleid.apple.com) |
| `APPLE_TEAM_ID` | 10-character Apple Developer Team ID |

Without these secrets, macOS builds are produced unsigned. Unsigned DMGs will trigger Gatekeeper warnings on end-user machines.

### Version Alignment

Desktop uses the same version resolution chain as Android β€” both read `VERSION_CODE_OFFSET` and `VERSION_NAME_BASE` from `config.properties`, with CI passing the resolved values as environment variables. Version names are sanitized to strict `X.Y.Z` format for native installer compatibility.

### Flatpak

Flatpak packaging is maintained externally at [vidplace7/org.meshtastic.desktop](https://github.com/vidplace7/org.meshtastic.desktop). It builds `:desktop:packageUberJarForCurrentOS` (not the native distribution pipeline) and includes its own AppStream metainfo, `.desktop` entry, and JBR bundling.

## Build Attestations & Provenance

Expand Down
51 changes: 38 additions & 13 deletions desktop/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,8 @@ compose.desktop {
mainClass = "org.meshtastic.desktop.MainKt"
jvmArgs(
"-Xmx2G",
"-Dapple.awt.application.name=Meshtastic",
"-Dcom.apple.mrj.application.apple.menu.about.name=Meshtastic",
"-Dapple.awt.application.name=Meshtastic Desktop",
"-Dcom.apple.mrj.application.apple.menu.about.name=Meshtastic Desktop",
"-Dcom.apple.bundle.identifier=org.meshtastic.desktop",
)

Expand All @@ -131,7 +131,7 @@ compose.desktop {
}

nativeDistributions {
packageName = "Meshtastic"
packageName = "Meshtastic Desktop"

// Ensure critical JVM modules are included in the custom JRE bundled with the app.
// jdeps might miss some of these if they are loaded via reflection or JNI.
Expand All @@ -148,8 +148,8 @@ compose.desktop {
// Increase max heap size to prevent OOM issues on complex maps/data
jvmArgs(
"-Xmx2G",
"-Dapple.awt.application.name=Meshtastic",
"-Dcom.apple.mrj.application.apple.name=Meshtastic",
"-Dapple.awt.application.name=Meshtastic Desktop",
"-Dcom.apple.mrj.application.apple.menu.about.name=Meshtastic Desktop",
"-Dcom.apple.bundle.identifier=org.meshtastic.desktop",
)

Expand All @@ -158,6 +158,7 @@ compose.desktop {
iconFile.set(project.file("src/main/resources/icon.icns"))
minimumSystemVersion = "12.0"
bundleID = "org.meshtastic.desktop"
appCategory = "public.app-category.utilities"
entitlementsFile.set(project.file("entitlements.plist"))
infoPlist {
extraKeysRawXml =
Expand All @@ -182,22 +183,46 @@ compose.desktop {
"""
.trimIndent()
}
// TODO: To prepare for real distribution on macOS, you'll need to sign and notarize.
// You can inject these from CI environment variables.
// sign = true
// notarize = true
// appleID = System.getenv("APPLE_ID")
// appStorePassword = System.getenv("APPLE_APP_SPECIFIC_PASSWORD")
// macOS code signing and notarization.
// Required for Gatekeeper acceptance on end-user machines (without this,
// macOS 12+ shows "app cannot be opened because it is from an unidentified developer").
//
// Required CI secrets:
// APPLE_SIGNING_IDENTITY – e.g. "Developer ID Application: Meshtastic LLC (TEAMID)"
// APPLE_ID – Apple ID email used for notarization
// APPLE_APP_SPECIFIC_PASSWORD – App-specific password from appleid.apple.com
// APPLE_TEAM_ID – 10-character Apple Developer Team ID
//
// To enable, set SIGN_MACOS=true in the CI environment and uncomment the block below.
val signMacOs = System.getenv("SIGN_MACOS")?.toBoolean() ?: false
if (signMacOs) {
signing {
sign.set(true)
identity.set(System.getenv("APPLE_SIGNING_IDENTITY"))
}
notarization {
appleID.set(System.getenv("APPLE_ID"))
password.set(System.getenv("APPLE_APP_SPECIFIC_PASSWORD"))
teamID.set(System.getenv("APPLE_TEAM_ID"))
}
}
}
windows {
iconFile.set(project.file("src/main/resources/icon.ico"))
menuGroup = "Meshtastic"
// TODO: Must generate and set a consistent UUID for Windows upgrades.
// upgradeUuid = "YOUR-UPGRADE-UUID-HERE"
shortcut = true
menu = true
dirChooser = true
// Stable UUID ensures MSI upgrades replace the previous installation
// rather than installing side-by-side. NEVER change this value.
upgradeUuid = "4974EA87-98AA-470E-B590-0BD5CF9FAE8E"
}
linux {
iconFile.set(project.file("src/main/resources/icon.png"))
menuGroup = "Network"
debMaintainer = "developers@meshtastic.org"
appCategory = "Network"
rpmLicenseType = "GPLv3+"
}

// Define target formats based on the current host OS to avoid configuration errors
Expand Down
4 changes: 4 additions & 0 deletions desktop/entitlements.plist
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,9 @@
<true/>
<key>com.apple.security.device.bluetooth</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.device.usb</key>
<true/>
</dict>
</plist>
2 changes: 1 addition & 1 deletion docs/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ These items address structural gaps identified in the March 2026 architecture re
| Notifications | βœ… Desktop native notifications with system tray icon support |
| MenuBar | βœ… Removed β€” replaced with `onPreviewKeyEvent` keyboard shortcuts (⌘Q, ⌘,, βŒ˜β‡§T, ⌘1-4, ⌘/) |
| About | βœ… Shared `commonMain` screen (AboutLibraries KMP `produceLibraries` + per-platform JSON) |
| Packaging | βœ… Done β€” Native distribution pipeline in CI (DMG, MSI, DEB) |
| Packaging | βœ… Done β€” Native distribution pipeline in CI (DMG, MSI, DEB). Windows `upgradeUuid` set; macOS signing/notarization wired behind `SIGN_MACOS` env flag; desktop build attestation in release CI. Flatpak packaging maintained externally at [vidplace7/org.meshtastic.desktop](https://github.com/vidplace7/org.meshtastic.desktop) (includes AppStream metainfo, `.desktop` entry, and JBR bundling); see [PR #4807](https://github.com/meshtastic/Meshtastic-Android/pull/4807) for `flatpakGradleGenerator` integration |

## Near-Term Priorities (30 days)

Expand Down