diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8395b1cd9f..6bbfccd9f0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -308,16 +308,15 @@ Then, run the tests using `pnpm run test-release`. macOS requires apps to be code signed with an Apple certificate. To test development builds you can ad-hoc sign the packaged app which will let you run it locally. -1. In `package.json` remove the macOS signing script: `"sign": "./ts/scripts/sign-macos.node.js",` -2. Build the app and ad-hoc sign the app bundle: +1. Build the app while skipping the custom macOS signing script: ``` pnpm run generate -pnpm run build +SKIP_SIGNING_SCRIPT=1 pnpm run build cd release # Pick the desired app bundle: mac, mac-arm64, or mac-universal cd mac-arm64 codesign --force --deep --sign - Signal.app ``` -3. Now you can run the app locally. +2. Now you can run the app locally. diff --git a/build/entitlements.mas-dev.inherit.plist b/build/entitlements.mas-dev.inherit.plist new file mode 100644 index 0000000000..4692fa084b --- /dev/null +++ b/build/entitlements.mas-dev.inherit.plist @@ -0,0 +1,15 @@ + + + + + + + + com.apple.security.app-sandbox + + com.apple.security.inherit + + com.apple.security.cs.allow-jit + + + diff --git a/build/entitlements.mas-dev.plist b/build/entitlements.mas-dev.plist new file mode 100644 index 0000000000..3792692b60 --- /dev/null +++ b/build/entitlements.mas-dev.plist @@ -0,0 +1,31 @@ + + + + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.device.audio-input + + com.apple.security.device.microphone + + com.apple.security.device.camera + + com.apple.security.files.downloads.read-write + + com.apple.security.files.user-selected.read-write + + com.apple.security.personal-information.photos-library + + com.apple.security.network.client + + com.apple.security.application-groups + + U68MSDN6DR.org.whispersystems.signal-desktop + + + diff --git a/package.json b/package.json index 822560e717..61c917a8fb 100644 --- a/package.json +++ b/package.json @@ -100,6 +100,7 @@ "build:styles:tailwind": "tailwindcss -i ./stylesheets/tailwind-config.css -o ./stylesheets/tailwind.css", "build:electron": "electron-builder --config.extraMetadata.environment=$SIGNAL_ENV", "build:release": "cross-env SIGNAL_ENV=production pnpm run build:electron --config.directories.output=release", + "build:mas-dev": "bash ./scripts/build-mas-dev.sh", "build:release-win32-all": "pnpm run build:release --arm64 --x64", "build:preload-cache": "node ts/scripts/generate-preload-cache.node.js", "build:emoji": "run-p build:emoji:32 build:emoji:64", @@ -442,12 +443,12 @@ } ], "mergeASARs": true, + "sign": "./ts/scripts/sign-macos.node.js", "releaseInfo": { "vendor": { "minOSVersion": "21.0.1" } }, - "sign": "./ts/scripts/sign-macos.node.js", "singleArchFiles": "node_modules/@signalapp/{libsignal-client/prebuilds/**,ringrtc/build/**,sqlcipher/prebuilds/**}", "target": [ { @@ -469,6 +470,20 @@ "NSAutoFillRequiresTextContentTypeForOneTimeCodeOnMac": true } }, + "masDev": { + "type": "development", + "sign": null, + "hardenedRuntime": false, + "entitlements": "./build/entitlements.mas-dev.plist", + "entitlementsInherit": "./build/entitlements.mas-dev.inherit.plist", + "preAutoEntitlements": false, + "extendInfo": { + "ElectronTeamID": "U68MSDN6DR", + "NSCameraUsageDescription": "Signal uses your camera for video calling.", + "NSMicrophoneUsageDescription": "Signal uses your microphone for voice and video calling.", + "ITSAppUsesNonExemptEncryption": true + } + }, "win": { "signtoolOptions": { "certificateSubjectName": "Signal Messenger, LLC", diff --git a/scripts/build-mas-dev.sh b/scripts/build-mas-dev.sh new file mode 100755 index 0000000000..73390fe2dc --- /dev/null +++ b/scripts/build-mas-dev.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# Copyright 2026 Signal Messenger, LLC +# SPDX-License-Identifier: AGPL-3.0-only + +set -euo pipefail + +if [[ -z "${MAS_PROVISIONING_PROFILE:-}" ]]; then + echo "MAS_PROVISIONING_PROFILE is required" >&2 + exit 1 +fi + +# Electron Builder applies `mac` config first and then `masDev`. +# Override mac configuration during build to ensure consistency between +# the two sets of values, otherwise the mas-dev build will crash on launch. +SIGNAL_ENV="${SIGNAL_ENV:-production}" \ +SKIP_SIGNING_SCRIPT=1 \ +pnpm run build:electron \ + --config.directories.output=release \ + --mac mas-dev \ + --arm64 \ + --publish=never \ + --config.mac.entitlements=./build/entitlements.mas-dev.plist \ + --config.mac.entitlementsInherit=./build/entitlements.mas-dev.inherit.plist \ + --config.mac.preAutoEntitlements=false \ + --config.masDev.provisioningProfile="$MAS_PROVISIONING_PROFILE" diff --git a/ts/scripts/copy-language-packs.node.ts b/ts/scripts/copy-language-packs.node.ts index b87111f593..32d1b7c48b 100644 --- a/ts/scripts/copy-language-packs.node.ts +++ b/ts/scripts/copy-language-packs.node.ts @@ -16,7 +16,7 @@ export async function afterPack({ ); let localesPath: string; - if (electronPlatformName === 'darwin') { + if (electronPlatformName === 'darwin' || electronPlatformName === 'mas') { const { productFilename } = packager.appInfo; // en.lproj/* diff --git a/ts/scripts/fuse-electron.node.ts b/ts/scripts/fuse-electron.node.ts index 7b910801b4..5288da36af 100644 --- a/ts/scripts/fuse-electron.node.ts +++ b/ts/scripts/fuse-electron.node.ts @@ -13,7 +13,7 @@ export async function afterPack({ const { productFilename } = packager.appInfo; let target; - if (electronPlatformName === 'darwin') { + if (electronPlatformName === 'darwin' || electronPlatformName === 'mas') { target = `${productFilename}.app`; } else if (electronPlatformName === 'win32') { target = `${productFilename}.exe`; @@ -43,9 +43,11 @@ export async function afterPack({ [FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false, // Disables the --inspect and --inspect-brk family of CLI options [FuseV1Options.EnableNodeCliInspectArguments]: enableInspectArguments, - // Enables validation of the app.asar archive on macOS/Windows + // Enables validation of the app.asar archive on macOS/Windows. [FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: - electronPlatformName === 'darwin' || electronPlatformName === 'win32', + electronPlatformName === 'darwin' || + electronPlatformName === 'mas' || + electronPlatformName === 'win32', // Enforces that Electron will only load your app from "app.asar" instead of // its normal search paths [FuseV1Options.OnlyLoadAppFromAsar]: true, diff --git a/ts/scripts/prune-macos-release.node.ts b/ts/scripts/prune-macos-release.node.ts index 478ceb9210..e412f1bec6 100644 --- a/ts/scripts/prune-macos-release.node.ts +++ b/ts/scripts/prune-macos-release.node.ts @@ -21,7 +21,7 @@ export async function afterPack({ packager, electronPlatformName, }: AfterPackContext): Promise { - if (electronPlatformName !== 'darwin') { + if (electronPlatformName !== 'darwin' && electronPlatformName !== 'mas') { return; } diff --git a/ts/scripts/sign-macos.node.ts b/ts/scripts/sign-macos.node.ts index ca166167d5..3bcd333ab3 100644 --- a/ts/scripts/sign-macos.node.ts +++ b/ts/scripts/sign-macos.node.ts @@ -10,6 +10,11 @@ const { realpath } = fsExtra; // eslint-disable-next-line max-len // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any export async function sign(configuration: any): Promise { + if (process.env.SKIP_SIGNING_SCRIPT === '1') { + console.log('SKIP_SIGNING_SCRIPT=1, skipping custom macOS signing script'); + return; + } + const scriptPath = process.env.SIGN_MACOS_SCRIPT; if (!scriptPath) { throw new Error(