chore: add macOS notarization support for DMG builds (#944)
This commit is contained in:
3
.github/workflows/platform-builds.yml
vendored
3
.github/workflows/platform-builds.yml
vendored
@@ -50,4 +50,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }}
|
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 }}
|
||||||
|
|
||||||
143
NOTARIZATION.md
Normal file
143
NOTARIZATION.md
Normal file
@@ -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)
|
||||||
@@ -116,6 +116,10 @@ yarn ui-test
|
|||||||
Create a PR to `release` branch.
|
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
|
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 beta release
|
||||||
|
|
||||||
Create a PR to `beta` branch. A "feat" or "fix" commit is necessary to create a new version.
|
Create a PR to `beta` branch. A "feat" or "fix" commit is necessary to create a new version.
|
||||||
|
|||||||
@@ -49,10 +49,10 @@
|
|||||||
"appId": "de.t7n.apps.mqtt-explorer",
|
"appId": "de.t7n.apps.mqtt-explorer",
|
||||||
"category": "public.app-category.developer-tools",
|
"category": "public.app-category.developer-tools",
|
||||||
"hardenedRuntime": true,
|
"hardenedRuntime": true,
|
||||||
|
"gatekeeperAssess": false,
|
||||||
"publish": [
|
"publish": [
|
||||||
"github"
|
"github"
|
||||||
],
|
]
|
||||||
"entitlements": "res/entitlements.mas.plist"
|
|
||||||
},
|
},
|
||||||
"linux": {
|
"linux": {
|
||||||
"category": "Development",
|
"category": "Development",
|
||||||
@@ -76,7 +76,8 @@
|
|||||||
"buildResources": "res",
|
"buildResources": "res",
|
||||||
"output": "build"
|
"output": "build"
|
||||||
},
|
},
|
||||||
"afterPack": "./dist/scripts/afterPack.js"
|
"afterPack": "./dist/scripts/afterPack.js",
|
||||||
|
"afterSign": "./dist/scripts/notarize.js"
|
||||||
},
|
},
|
||||||
"author": "Thomas Nordquist",
|
"author": "Thomas Nordquist",
|
||||||
"email": "xxnerowingerxx@gmail.com",
|
"email": "xxnerowingerxx@gmail.com",
|
||||||
|
|||||||
12
package.ts
12
package.ts
@@ -117,6 +117,18 @@ async function buildWithOptions(options: builder.CliOptions, buildInfo: BuildInf
|
|||||||
? 'res/MQTT_Explorer_Store_Distribution_Profile.provisionprofile'
|
? 'res/MQTT_Explorer_Store_Distribution_Profile.provisionprofile'
|
||||||
: 'res/MQTTExplorerdmg.provisionprofile'
|
: 'res/MQTTExplorerdmg.provisionprofile'
|
||||||
dotProp.set(packageJson, 'build.mac.provisioningProfile', provisioningProfile)
|
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 {
|
try {
|
||||||
|
|||||||
12
res/entitlements.mac.inherit.plist
Normal file
12
res/entitlements.mac.inherit.plist
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>com.apple.security.cs.allow-jit</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
14
res/entitlements.mac.plist
Normal file
14
res/entitlements.mac.plist
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>com.apple.security.cs.allow-jit</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.network.client</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
49
scripts/notarize.ts
Normal file
49
scripts/notarize.ts
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user