diff --git a/.github/workflows/macos_ci.yml b/.github/workflows/macos_ci.yml
index 89ecf7694..dee73ff64 100644
--- a/.github/workflows/macos_ci.yml
+++ b/.github/workflows/macos_ci.yml
@@ -1,73 +1,77 @@
name: macOS Build CI
on:
workflow_call:
push:
branches: [master]
paths-ignore:
- 'landing/**'
- 'docs/**'
- 'keyserver/**'
- 'native/**'
- 'shared/**'
jobs:
build:
name: Build macOS app
runs-on: macos-12
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install Developer certificate
env:
MACOS_BUILD_CERTIFICATE_BASE64: ${{ secrets.MACOS_BUILD_CERTIFICATE_BASE64 }}
MACOS_BUILD_P12_PASSWORD: ${{ secrets.MACOS_BUILD_P12_PASSWORD }}
+ MACOS_PROVISIONPROFILE_BASE64: ${{ secrets.MACOS_PROVISIONPROFILE_BASE64 }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
run: |
# create variables
CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
+ PROVISIONPROFILE_PATH=$GITHUB_WORKSPACE/desktop/macOS_App_Provisioning_Profile.provisionprofile
# import certificate from secrets
echo -n "$MACOS_BUILD_CERTIFICATE_BASE64" | base64 --decode --output $CERTIFICATE_PATH
# create temporary keychain
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
# import certificate to keychain
security import $CERTIFICATE_PATH -P "$MACOS_BUILD_P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
+ # import provisioning profile from secrets
+ echo -n "$MACOS_PROVISIONPROFILE_BASE64" | base64 --decode --output $PROVISIONPROFILE_PATH
- name: sudo ./install_protobuf.sh
working-directory: ./scripts
run: sudo ./install_protobuf.sh
- name: npm install -g yarn
run: npm install -g yarn
- name: yarn ci-cleaninstall
run: yarn ci-cleaninstall
- name: Build App
env:
APPLE_USER_NAME: ${{secrets.APPLE_USER_NAME}}
APPLE_APP_SPECIFIC_PASSWORD: ${{secrets.APPLE_APP_SPECIFIC_PASSWORD}}
TEAM_ID: ${{secrets.TEAM_ID}}
working-directory: './desktop'
run: yarn make --arch universal
- name: Clean up keychain
if: ${{ always() }}
run: security delete-keychain $RUNNER_TEMP/app-signing.keychain-db
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
name: macos-artifacts
path: ./desktop/out/make/**/*
if-no-files-found: error
retention-days: 1
diff --git a/.gitignore b/.gitignore
index 209c2745d..8c86c462f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,46 +1,47 @@
.DS_Store
node_modules
landing/node_modules
landing/dist
lib/node_modules
web/node_modules
web/dist
keyserver/dist
keyserver/node_modules
keyserver/addons/rust-node-addon/target
keyserver/addons/rust-node-addon/napi
keyserver/secrets
keyserver/facts
keyserver/*.env
keyserver/*.env.*
services/tunnelbroker/target
services/tunnelbroker/src/libcpp/test/build
.eslintcache
.vscode
!.vscode/extensions.json
# CMake
native/cpp/**/build
services/*/build
services/build
services/lib/src/build
shared/protos/build
# Shared libraries
shared/tunnelbroker-client/target
# Nix
result*
.direnv
# Electron
desktop/out/
desktop/assets/
desktop/dist/
+desktop/*.provisionprofile
diff --git a/desktop/entitlements-dev.plist b/desktop/entitlements-dev.plist
new file mode 100644
index 000000000..7c07567c3
--- /dev/null
+++ b/desktop/entitlements-dev.plist
@@ -0,0 +1,24 @@
+
+
+
+
+ com.apple.application-identifier
+ H98Y8MH53M.app.comm.macos
+ com.apple.developer.aps-environment
+ development
+ com.apple.security.cs.allow-jit
+
+ com.apple.security.device.audio-input
+
+ com.apple.security.device.bluetooth
+
+ com.apple.security.device.camera
+
+ com.apple.security.device.print
+
+ com.apple.security.device.usb
+
+ com.apple.security.personal-information.location
+
+
+
diff --git a/desktop/entitlements.plist b/desktop/entitlements.plist
new file mode 100644
index 000000000..17ccb352e
--- /dev/null
+++ b/desktop/entitlements.plist
@@ -0,0 +1,24 @@
+
+
+
+
+ com.apple.application-identifier
+ H98Y8MH53M.app.comm.macos
+ com.apple.developer.aps-environment
+ production
+ com.apple.security.cs.allow-jit
+
+ com.apple.security.device.audio-input
+
+ com.apple.security.device.bluetooth
+
+ com.apple.security.device.camera
+
+ com.apple.security.device.print
+
+ com.apple.security.device.usb
+
+ com.apple.security.personal-information.location
+
+
+
diff --git a/desktop/forge.config.cjs b/desktop/forge.config.cjs
index e63073135..6ef4d7bd0 100644
--- a/desktop/forge.config.cjs
+++ b/desktop/forge.config.cjs
@@ -1,157 +1,187 @@
const babel = require('@babel/core');
const { PluginBase } = require('@electron-forge/plugin-base');
const fs = require('fs-extra');
const klaw = require('klaw');
const path = require('path');
const transformDirectoryWithBabel = async (dirPath, outDirPath) => {
for await (const { path: filePath, stats } of klaw(dirPath)) {
if (stats.isFile()) {
const outPath = path.resolve(
outDirPath,
path.relative(dirPath, filePath),
);
const { code } = await new Promise(resolve =>
babel.transformFile(filePath, (err, result) => {
if (err) {
console.error(err);
}
resolve(result);
}),
);
if (code) {
await fs.outputFile(outPath, code);
}
}
}
};
const runBabel = async () => {
await Promise.all([
fs.outputFile('./dist/package.json', JSON.stringify({ type: 'commonjs' })),
transformDirectoryWithBabel('./src', './dist'),
]);
};
class BabelPlugin extends PluginBase {
name = 'BabelPlugin';
getHooks() {
return {
// This hook runs during the packaging of the final executable
prePackage: [runBabel],
};
}
// This function runs only in development mode, just before the
// application starts
async startLogic() {
await runBabel();
// startLogic allows us to run electron ourselves and return the process
// object. Electron Forge (package which handles bundling, packaging and
// running dev mode) will then watch it instead of spawing electron by
// itself. But we are fine with the default behaviour (Electron Forge
// spawning electron) so we return false.
return false;
}
}
+const optionsForFile = filePath => {
+ const entitlements =
+ process.env?.ENV === 'dev'
+ ? 'entitlements-dev.plist'
+ : 'entitlements.plist';
+
+ const basename = path.basename(filePath);
+ if (basename === 'Comm' || basename === 'Comm.app') {
+ return { entitlements };
+ }
+
+ return {};
+};
+
const signingOptions = {
packagerMacos: {},
makerMacos: {},
makerWindows: {},
};
-if (process.env?.ENV !== 'dev') {
+if (process.env?.ENV === 'dev') {
+ if (fs.existsSync('macOS_App_Development_Profile.provisionprofile')) {
+ signingOptions.packagerMacos = {
+ osxSign: {
+ identity: 'Development',
+ preEmbedProvisioningProfile: true,
+ provisioningProfile: 'macOS_App_Development_Profile.provisionprofile',
+ optionsForFile,
+ },
+ };
+ }
+} else {
signingOptions.packagerMacos = {
- osxSign: { identity: 'Developer ID Application' },
+ osxSign: {
+ identity: 'Developer ID Application',
+ preEmbedProvisioningProfile: true,
+ provisioningProfile: 'macOS_App_Provisioning_Profile.provisionprofile',
+ optionsForFile,
+ },
osxNotarize: {
tool: 'notarytool',
appleId: process.env?.APPLE_USER_NAME,
appleIdPassword: process.env?.APPLE_APP_SPECIFIC_PASSWORD,
teamId: process.env?.TEAM_ID,
},
};
signingOptions.makerMacos = {
'code-sign': {
'signing-identity': 'Developer ID Application',
'identifier': 'app.comm.macos',
},
};
signingOptions.makerWindows = {
certificateFile: process.env?.WINDOWS_CERTIFICATE,
certificatePassword: process.env?.WINDOWS_PASSWORD,
};
}
module.exports = {
packagerConfig: {
name: 'Comm',
icon: 'icons/icon',
ignore: [
'src',
'.*config\\.cjs',
'\\.eslintrc\\.json',
'\\.flowconfig',
'flow-typed',
],
appBundleId: 'app.comm.macos',
...signingOptions.packagerMacos,
},
makers: [
{
name: '@electron-forge/maker-dmg',
platforms: ['darwin'],
config: {
icon: 'icons/icon.icns',
background: 'icons/dmg_background.png',
additionalDMGOptions: { ...signingOptions.makerMacos },
contents: opts => [
{ x: 340, y: 100, type: 'link', path: '/Applications' },
{ x: 100, y: 100, type: 'file', path: opts.appPath },
],
},
},
{
name: '@electron-forge/maker-zip',
platforms: ['darwin'],
},
{
name: '@electron-forge/maker-squirrel',
platforms: ['win32'],
config: {
name: 'Comm',
title: 'Comm',
authors: 'Comm Technologies, Inc.',
description: 'Comm is a private messaging app for communities!',
iconUrl: 'https://comm-external.s3.amazonaws.com/icon.ico',
setupIcon: 'icons/icon.ico',
loadingGif: 'icons/win_installer.gif',
...signingOptions.makerWindows,
},
},
],
plugins: [new BabelPlugin()],
hooks: {
generateAssets: async () => {
await Promise.all([
fs.copy('../keyserver/fonts', './assets/fonts'),
fs.copy('../web/theme.css', './assets/theme.css'),
fs.copy('../web/typography.css', './assets/typography.css'),
]);
},
prePackage: async (forgeConfig, platform, arch) => {
if (
arch === 'universal' &&
(fs.existsSync('./out/Comm-darwin-x64') ||
fs.existsSync('./out/Comm-darwin-arm64'))
) {
throw new Error(
'Due to a bug in @electron/universal, please first run ' +
'`yarn clean-build` or remove previous builds artifacts: ' +
'"out/Comm-darwin-x64" and/or "out/Comm-darwin-arm64"\n',
);
}
},
},
};