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:
|
||||
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 }}
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
@@ -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",
|
||||
|
||||
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/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 {
|
||||
|
||||
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