Skip to content

Commit 26ace65

Browse files
authored
chore(build): notarize dmg COMPASS-7489 (#5354)
* notarize dmg * pr feedback * lint * pr feedback
1 parent 61223e8 commit 26ace65

File tree

2 files changed

+111
-51
lines changed

2 files changed

+111
-51
lines changed
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// eslint-disable-next-line strict
2+
'use strict';
3+
const download = require('download');
4+
const path = require('path');
5+
const { promises: fs } = require('fs');
6+
const debug = require('debug')('hadron-build:macos-notarization');
7+
const { promisify } = require('util');
8+
const childProcess = require('child_process');
9+
const execFile = promisify(childProcess.execFile);
10+
11+
async function setupMacosNotary() {
12+
try {
13+
await fs.access('macnotary/macnotary');
14+
debug('macnotary already downloaded');
15+
} catch (err) {
16+
debug('downloading macnotary');
17+
await download(process.env.MACOS_NOTARY_CLIENT_URL, 'macnotary', {
18+
extract: true,
19+
strip: 1 // remove leading platform + arch directory
20+
});
21+
await fs.chmod('macnotary/macnotary', 0o755); // ensure +x is set
22+
}
23+
}
24+
25+
/**
26+
* Notarize a resource with the macOS notary service.
27+
* https://wiki.corp.mongodb.com/display/BUILD/How+to+use+MacOS+notary+service
28+
*
29+
* Notarization is a three step process:
30+
* 1. All the files to be notarized are zipped up into a single file.
31+
* 2. The zip file is uploaded to the notary service. It signs all the
32+
* files in the zip file and returns a new zip file with the signed files.
33+
* 3. The orginal files are removed and the signed files are unzipped into
34+
* their place.
35+
*
36+
* @param {string} src The path to the resource to notarize. It can be a directory or a file.
37+
* @param {object} notarizeOptions
38+
* @param {string} notarizeOptions.bundleId
39+
* @param {string} [notarizeOptions.macosEntitlements]
40+
*/
41+
async function notarize(src, notarizeOptions) {
42+
debug(`Signing and notarizing "${src}"`);
43+
44+
await setupMacosNotary();
45+
46+
const fileName = path.basename(src);
47+
const unsignedArchive = `${fileName}.zip`;
48+
const signedArchive = `${fileName}.signed.zip`;
49+
50+
const execOpts = {
51+
cwd: path.dirname(src),
52+
encoding: 'utf8',
53+
};
54+
55+
// Step:1 - zip up the file/folder to unsignedArchive
56+
debug(`running "zip -y -r '${unsignedArchive}' '${fileName}'"`);
57+
await execFile('zip', ['-y', '-r', unsignedArchive, fileName], execOpts);
58+
59+
try {
60+
// Step:2 - send the zip to notary service and save the result to signedArchive
61+
debug(`sending file to notary service (bundle id = ${notarizeOptions.bundleId})`);
62+
const macnotaryResult = await execFile(path.resolve('macnotary/macnotary'), [
63+
'-t', 'app',
64+
'-m', 'notarizeAndSign',
65+
'-u', process.env.MACOS_NOTARY_API_URL,
66+
'-b', notarizeOptions.bundleId,
67+
'-f', unsignedArchive,
68+
'-o', signedArchive,
69+
'--verify',
70+
...(notarizeOptions.macosEntitlements ? ['-e', notarizeOptions.macosEntitlements] : [])
71+
], execOpts);
72+
debug('macnotary result:', macnotaryResult.stdout, macnotaryResult.stderr);
73+
debug('ls', (await execFile('ls', ['-lh'], execOpts)).stdout);
74+
75+
// Step:3 - remove existing src, unzip signedArchive to src
76+
debug('removing existing directory contents');
77+
await execFile('rm', ['-r', fileName], execOpts);
78+
debug(`unzipping with "unzip -u ${signedArchive}"`);
79+
await execFile('unzip', ['-u', signedArchive], execOpts);
80+
} finally {
81+
// cleanup - remove signedArchive and unsignedArchive
82+
debug('ls', (await execFile('ls', ['-lh'], execOpts)).stdout);
83+
debug(`removing ${signedArchive} and ${unsignedArchive}`);
84+
await execFile('rm', ['-r', signedArchive, unsignedArchive], execOpts).catch(err => {
85+
debug('error cleaning up', err);
86+
});
87+
}
88+
}
89+
90+
module.exports = { notarize };

packages/hadron-build/lib/target.js

Lines changed: 21 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,20 @@
11
// eslint-disable-next-line strict
22
'use strict';
33
const chalk = require('chalk');
4-
const childProcess = require('child_process');
5-
const download = require('download');
64
const fs = require('fs');
75
const _ = require('lodash');
86
const semver = require('semver');
97
const path = require('path');
10-
const { promisify } = require('util');
118
const normalizePkg = require('normalize-package-data');
129
const parseGitHubRepoURL = require('parse-github-repo-url');
1310
const ffmpegAfterExtract = require('electron-packager-plugin-non-proprietary-codecs-ffmpeg').default;
1411
const windowsInstallerVersion = require('./windows-installer-version');
1512
const debug = require('debug')('hadron-build:target');
16-
const execFile = promisify(childProcess.execFile);
1713
const which = require('which');
1814
const plist = require('plist');
1915
const { sign, getSignedFilename } = require('./signtool');
2016
const tarGz = require('./tar-gz');
17+
const { notarize } = require('./mac-notary-service');
2118

2219
function _canBuildInstaller(ext) {
2320
var bin = null;
@@ -562,7 +559,6 @@ class Target {
562559
};
563560

564561
this.createInstaller = async() => {
565-
const appDirectoryName = `${this.productName}.app`;
566562
const appPath = this.appPath;
567563

568564
{
@@ -580,58 +576,32 @@ class Target {
580576
await fs.promises.writeFile(plistFilePath, plist.build(plistContents));
581577
}
582578

583-
if (process.env.MACOS_NOTARY_KEY &&
584-
process.env.MACOS_NOTARY_SECRET &&
585-
process.env.MACOS_NOTARY_CLIENT_URL &&
586-
process.env.MACOS_NOTARY_API_URL) {
587-
debug(`Signing and notarizing "${appPath}"`);
588-
// https://wiki.corp.mongodb.com/display/BUILD/How+to+use+MacOS+notary+service
589-
debug(`Downloading the notary client from ${process.env.MACOS_NOTARY_CLIENT_URL} to ${path.resolve('macnotary')}`);
590-
await download(process.env.MACOS_NOTARY_CLIENT_URL, 'macnotary', {
591-
extract: true,
592-
strip: 1 // remove leading platform + arch directory
593-
});
594-
await fs.promises.chmod('macnotary/macnotary', 0o755); // ensure +x is set
579+
const isNotarizationPossible = process.env.MACOS_NOTARY_KEY &&
580+
process.env.MACOS_NOTARY_SECRET &&
581+
process.env.MACOS_NOTARY_CLIENT_URL &&
582+
process.env.MACOS_NOTARY_API_URL;
595583

596-
debug(`running "zip -y -r '${appDirectoryName}.zip' '${appDirectoryName}'"`);
597-
await execFile('zip', ['-y', '-r', `${appDirectoryName}.zip`, appDirectoryName], {
598-
cwd: path.dirname(appPath)
599-
});
600-
debug(`sending file to notary service (bundle id = ${this.bundleId})`);
601-
const macnotaryResult = await execFile(path.resolve('macnotary/macnotary'), [
602-
'-t', 'app',
603-
'-m', 'notarizeAndSign',
604-
'-u', process.env.MACOS_NOTARY_API_URL,
605-
'-b', this.bundleId,
606-
'-f', `${appDirectoryName}.zip`,
607-
'-o', `${appDirectoryName}.signed.zip`,
608-
'--verify',
609-
...(this.macosEntitlements ? ['-e', this.macosEntitlements] : [])
610-
], {
611-
cwd: path.dirname(appPath),
612-
encoding: 'utf8'
613-
});
614-
debug('macnotary result:', macnotaryResult.stdout, macnotaryResult.stderr);
615-
debug('ls', (await execFile('ls', ['-lh'], { cwd: path.dirname(appPath), encoding: 'utf8' })).stdout);
616-
debug('removing existing directory contents');
617-
await execFile('rm', ['-r', appDirectoryName], {
618-
cwd: path.dirname(appPath)
619-
});
620-
debug(`unzipping with "unzip -u '${appDirectoryName}.signed.zip'"`);
621-
await execFile('unzip', ['-u', `${appDirectoryName}.signed.zip`], {
622-
cwd: path.dirname(appPath),
623-
encoding: 'utf8'
624-
});
625-
debug('ls', (await execFile('ls', ['-lh'], { cwd: path.dirname(appPath), encoding: 'utf8' })).stdout);
626-
debug(`removing '${appDirectoryName}.signed.zip' and '${appDirectoryName}.zip'`);
627-
await fs.promises.unlink(`${appPath}.signed.zip`);
628-
await fs.promises.unlink(`${appPath}.zip`);
584+
const notarizationOptions = {
585+
bundleId: this.bundleId,
586+
macosEntitlements: this.macosEntitlements
587+
};
588+
589+
if (isNotarizationPossible) {
590+
await notarize(appPath, notarizationOptions);
629591
} else {
630592
console.error(chalk.yellow.bold(
631-
'WARNING: macos notary service credentials not set -- skipping signing and notarization!'));
593+
'WARNING: macos notary service credentials not set -- skipping signing and notarization of .app!'));
632594
}
595+
633596
const createDMG = require('electron-installer-dmg');
634597
await createDMG(this.installerOptions);
598+
599+
if (isNotarizationPossible) {
600+
await notarize(this.installerOptions.dmgPath, notarizationOptions);
601+
} else {
602+
console.error(chalk.yellow.bold(
603+
'WARNING: macos notary service credentials not set -- skipping signing and notarization of .dmg!'));
604+
}
635605
};
636606
}
637607

0 commit comments

Comments
 (0)