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', ); } }, }, };