diff --git a/.github/workflows/platform-builds.yml b/.github/workflows/platform-builds.yml
index c657727..82e8389 100644
--- a/.github/workflows/platform-builds.yml
+++ b/.github/workflows/platform-builds.yml
@@ -50,4 +50,7 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }}
+ APPLE_ID: ${{ secrets.APPLE_ID }}
+ APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
+ APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
\ No newline at end of file
diff --git a/NOTARIZATION.md b/NOTARIZATION.md
new file mode 100644
index 0000000..22bd64a
--- /dev/null
+++ b/NOTARIZATION.md
@@ -0,0 +1,143 @@
+# macOS Notarization Setup
+
+This document explains how to set up notarization for macOS builds of MQTT Explorer.
+
+## Overview
+
+macOS notarization is a security feature required by Apple for all software distributed outside the Mac App Store. Starting with macOS 10.15 (Catalina), all software must be notarized to run without warnings on macOS.
+
+## Prerequisites
+
+1. An active Apple Developer account
+2. Xcode command line tools installed on the build machine
+3. An app-specific password for notarization
+
+## Setup Steps
+
+### 1. Create an App-Specific Password
+
+1. Sign in to [appleid.apple.com](https://appleid.apple.com)
+2. Navigate to "Sign-In and Security" section
+3. Under "App-Specific Passwords", click "Generate an app-specific password"
+4. Enter a descriptive name (e.g., "MQTT Explorer Notarization")
+5. Copy the generated password (you won't be able to see it again)
+
+### 2. Find Your Team ID
+
+1. Sign in to [developer.apple.com](https://developer.apple.com/account)
+2. Navigate to "Membership Details"
+3. Copy your Team ID (a 10-character alphanumeric string)
+
+### 3. Configure GitHub Secrets
+
+Add the following secrets to your GitHub repository:
+
+- `APPLE_ID`: Your Apple ID email address (e.g., `your.email@example.com`)
+- `APPLE_APP_SPECIFIC_PASSWORD`: The app-specific password created in step 1
+- `APPLE_TEAM_ID`: Your Team ID from step 2
+
+To add secrets:
+1. Go to your repository on GitHub
+2. Navigate to Settings → Secrets and variables → Actions
+3. Click "New repository secret" for each of the above
+
+## How It Works
+
+### Build Configuration
+
+The notarization process is configured in `package.json`:
+
+```json
+{
+ "build": {
+ "mac": {
+ "hardenedRuntime": true,
+ "gatekeeperAssess": false,
+ "entitlements": "res/entitlements.mac.plist",
+ "entitlementsInherit": "res/entitlements.mac.inherit.plist"
+ },
+ "afterSign": "./dist/scripts/notarize.js"
+ }
+}
+```
+
+### Notarization Script
+
+The `scripts/notarize.ts` script handles the notarization process:
+
+1. Checks if the build is for macOS
+2. Verifies that required environment variables are set
+3. Submits the app to Apple's notarization service
+4. Waits for notarization to complete
+5. Staples the notarization ticket to the app
+
+### Entitlements
+
+Different entitlements are used for different build types:
+
+- **DMG builds** (regular distribution):
+ - `res/entitlements.mac.plist` - Main entitlements
+ - `res/entitlements.mac.inherit.plist` - Inherited entitlements for child processes
+
+- **MAS builds** (Mac App Store):
+ - `res/entitlements.mas.plist` - App Store specific entitlements
+
+### CI/CD Integration
+
+The GitHub Actions workflow (`platform-builds.yml`) automatically:
+
+1. Builds the macOS app when code is pushed to `release` or `beta` branches
+2. Signs the app with the developer certificate
+3. Notarizes the app using the configured secrets
+4. Publishes the notarized app to GitHub releases
+
+## Troubleshooting
+
+### Notarization Fails
+
+If notarization fails, check:
+
+1. **Credentials**: Ensure all three secrets are correctly set in GitHub
+2. **App-specific password**: Verify it hasn't expired or been revoked
+3. **Team ID**: Confirm it matches your developer account
+4. **Entitlements**: Ensure the entitlements files are valid and appropriate for your app
+
+### Checking Notarization Status
+
+You can check the notarization status of a built app:
+
+```bash
+# Check if an app is notarized
+spctl -a -vv /path/to/MQTT\ Explorer.app
+
+# Check notarization history
+xcrun notarytool history --apple-id your.email@example.com --team-id YOUR_TEAM_ID
+```
+
+### Local Testing
+
+To test notarization locally:
+
+```bash
+# Set environment variables
+export APPLE_ID="your.email@example.com"
+export APPLE_APP_SPECIFIC_PASSWORD="your-app-specific-password"
+export APPLE_TEAM_ID="YOUR_TEAM_ID"
+
+# Build and notarize
+yarn build
+yarn package mac
+```
+
+## Security Considerations
+
+- Never commit Apple credentials to the repository
+- Use app-specific passwords, not your main Apple ID password
+- Rotate app-specific passwords periodically
+- Limit access to GitHub secrets to trusted maintainers only
+
+## References
+
+- [Apple Notarization Documentation](https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution)
+- [electron-builder Code Signing](https://www.electron.build/code-signing)
+- [@electron/notarize](https://github.com/electron/notarize)
diff --git a/Readme.md b/Readme.md
index 9b7720f..6923282 100644
--- a/Readme.md
+++ b/Readme.md
@@ -116,6 +116,10 @@ yarn ui-test
Create a PR to `release` branch.
There needs to be a "feat: some new feature" or "fix: some bugfix" commit for a new release to be created
+### macOS Notarization
+
+macOS builds are automatically notarized during the release process. To set up notarization credentials, see [NOTARIZATION.md](NOTARIZATION.md).
+
## Create a beta release
Create a PR to `beta` branch. A "feat" or "fix" commit is necessary to create a new version.
diff --git a/package.json b/package.json
index 999e6f8..da5fa1e 100644
--- a/package.json
+++ b/package.json
@@ -49,10 +49,10 @@
"appId": "de.t7n.apps.mqtt-explorer",
"category": "public.app-category.developer-tools",
"hardenedRuntime": true,
+ "gatekeeperAssess": false,
"publish": [
"github"
- ],
- "entitlements": "res/entitlements.mas.plist"
+ ]
},
"linux": {
"category": "Development",
@@ -76,7 +76,8 @@
"buildResources": "res",
"output": "build"
},
- "afterPack": "./dist/scripts/afterPack.js"
+ "afterPack": "./dist/scripts/afterPack.js",
+ "afterSign": "./dist/scripts/notarize.js"
},
"author": "Thomas Nordquist",
"email": "xxnerowingerxx@gmail.com",
diff --git a/package.ts b/package.ts
index db06d65..9e44d07 100644
--- a/package.ts
+++ b/package.ts
@@ -117,6 +117,18 @@ async function buildWithOptions(options: builder.CliOptions, buildInfo: BuildInf
? 'res/MQTT_Explorer_Store_Distribution_Profile.provisionprofile'
: 'res/MQTTExplorerdmg.provisionprofile'
dotProp.set(packageJson, 'build.mac.provisioningProfile', provisioningProfile)
+
+ // Set different entitlements for MAS vs DMG builds
+ if (buildInfo.package === 'mas') {
+ // MAS builds use the same sandboxed entitlements for parent and child processes
+ dotProp.set(packageJson, 'build.mac.entitlements', 'res/entitlements.mas.plist')
+ dotProp.set(packageJson, 'build.mac.entitlementsInherit', 'res/entitlements.mas.plist')
+ } else {
+ // DMG builds use different entitlements for notarization
+ // Parent app has network permissions, child processes have minimal permissions
+ dotProp.set(packageJson, 'build.mac.entitlements', 'res/entitlements.mac.plist')
+ dotProp.set(packageJson, 'build.mac.entitlementsInherit', 'res/entitlements.mac.inherit.plist')
+ }
}
try {
diff --git a/res/entitlements.mac.inherit.plist b/res/entitlements.mac.inherit.plist
new file mode 100644
index 0000000..38c887b
--- /dev/null
+++ b/res/entitlements.mac.inherit.plist
@@ -0,0 +1,12 @@
+
+
+
+
+ com.apple.security.cs.allow-jit
+
+ com.apple.security.cs.allow-unsigned-executable-memory
+
+ com.apple.security.cs.allow-dyld-environment-variables
+
+
+
diff --git a/res/entitlements.mac.plist b/res/entitlements.mac.plist
new file mode 100644
index 0000000..52b3fc5
--- /dev/null
+++ b/res/entitlements.mac.plist
@@ -0,0 +1,14 @@
+
+
+
+
+ com.apple.security.cs.allow-jit
+
+ com.apple.security.cs.allow-unsigned-executable-memory
+
+ com.apple.security.cs.allow-dyld-environment-variables
+
+ com.apple.security.network.client
+
+
+
diff --git a/scripts/notarize.ts b/scripts/notarize.ts
new file mode 100644
index 0000000..4b7d9cd
--- /dev/null
+++ b/scripts/notarize.ts
@@ -0,0 +1,49 @@
+import { notarize } from '@electron/notarize'
+import * as path from 'path'
+
+interface Context {
+ electronPlatformName: string
+ appOutDir: string
+ packager: {
+ appInfo: {
+ productFilename: string
+ }
+ }
+}
+
+export default async function notarizing(context: Context) {
+ const { electronPlatformName, appOutDir } = context
+
+ // Only notarize macOS builds
+ if (electronPlatformName !== 'darwin') {
+ return
+ }
+
+ // Check for required environment variables
+ const appleId = process.env.APPLE_ID
+ const appleIdPassword = process.env.APPLE_APP_SPECIFIC_PASSWORD
+ const teamId = process.env.APPLE_TEAM_ID
+
+ if (!appleId || !appleIdPassword || !teamId) {
+ console.warn('Skipping notarization: APPLE_ID, APPLE_APP_SPECIFIC_PASSWORD, or APPLE_TEAM_ID not set')
+ return
+ }
+
+ const appName = context.packager.appInfo.productFilename
+ const appPath = path.join(appOutDir, `${appName}.app`)
+
+ console.log(`Notarizing ${appPath}...`)
+
+ try {
+ await notarize({
+ appPath,
+ appleId,
+ appleIdPassword,
+ teamId,
+ })
+ console.log('Notarization successful!')
+ } catch (error) {
+ console.error('Notarization failed:', error)
+ throw error
+ }
+}