Skip to content
Snippets Groups Projects
Unverified Commit fea20a40 authored by Cam Saul's avatar Cam Saul
Browse files

Metabase Mac App build script / instructions updates

[ci skip]
parent 265695ce
No related branches found
No related tags found
No related merge requests found
......@@ -40,7 +40,7 @@
<key>SUEnableAutomaticChecks</key>
<true/>
<key>SUFeedURL</key>
<string>https://s3.amazonaws.com/downloads.metabase.com/appcast.xml</string>
<string>https://downloads.metabase.com/appcast.xml</string>
<key>SUPublicDSAKeyFile</key>
<string>dsa_pub.pem</string>
</dict>
......
......@@ -6,10 +6,12 @@ package Metabase::Util;
use Cwd 'getcwd';
use Exporter;
use JSON;
use Readonly;
use Term::ANSIColor qw(:constants);
our @ISA = qw(Exporter);
our @EXPORT = qw(config
config_or_die
announce
print_giant_success_banner
get_file_or_die
......@@ -17,16 +19,21 @@ our @EXPORT = qw(config
OSX_ARTIFACTS_DIR
artifact);
my $config_file = getcwd() . '/bin/config.json';
Readonly my $config_file => getcwd() . '/bin/config.json';
warn "Missing config file: $config_file\n" .
"Please copy $config_file.template, and edit it as needed.\n"
unless (-e $config_file);
my $config = from_json(`cat $config_file`) if -e $config_file;
Readonly my $config => from_json(`cat $config_file`) if -e $config_file;
sub config {
return $config ? $config->{ $_[0] } : '';
}
sub config_or_die {
my ($configKey) = @_;
return config($configKey) or die "Missing config.json property '$configKey'";
}
sub announce {
print "\n\n" . GREEN . $_[0] . RESET . "\n\n";
}
......
{
"codesigningIdentity": "Developer ID Application: Metabase, Inc",
"awsProfile": "metabase",
"awsBucket": "downloads.metabase.com"
"codesigningIdentity": "Developer ID Application: Metabase, Inc",
"appStoreConnectProviderShortName": "BR27ZJK7WW",
"awsProfile": "metabase",
"awsBucket": "downloads.metabase.com",
"cloudFrontDistributionID": "E35CJLWZIZVG7K"
}
......@@ -95,19 +95,21 @@ sub build {
# Codesign Metabase.app
sub codesign {
my $codesigning_cert_name = config('codesigningIdentity') or return;
Readonly my $codesigning_cert_name => config_or_die('codesigningIdentity');
announce "Codesigning $app...";
system('codesign', '--force', '--verify',
'--sign', $codesigning_cert_name,
'-r=designated => anchor trusted',
'--timestamp',
'--options', 'runtime',
'--deep', get_file_or_die($app)) == 0 or die "Code signing failed: $!\n";
}
# Verify that Metabase.app was signed correctly
sub verify_codesign {
return unless config('codesigningIdentity');
config_or_die('codesigningIdentity');
announce "Verifying codesigning for $app...";
......@@ -154,8 +156,8 @@ sub generate_appcast {
remove_tree($appcast);
my $aws_bucket = config('awsBucket') or return;
my $signature = generate_signature() or return;
Readonly my $aws_bucket => config_or_die('awsBucket');
Readonly my $signature => generate_signature() or die 'Failed to generate appcast signature';
open(my $out, '>', $appcast) or die "Unable to write to $appcast: $!";
print $out Text::Caml->new->render_file(get_file_or_die('bin/templates/appcast.xml.template'), {
......@@ -296,6 +298,85 @@ sub create_dmg {
remove_tree($temp_dmg, $dmg_source_dir);
}
# ------------------------------------------------------------ NOTORIZATION ------------------------------------------------------------
sub getAppleID {
return $ENV{'METABASE_MAC_APP_BUILD_APPLE_ID'} or die 'Make sure you export the env var METABASE_MAC_APP_BUILD_APPLE_ID';
}
sub getAscProvider {
return config_or_die('appStoreConnectProviderShortName');
}
sub notarize_file {
my ($filename) = @_;
announce "Notarizing $filename...";
Readonly my $appleID => getAppleID;
Readonly my $ascProvider => getAscProvider;
system('xcrun', 'altool', '--notarize-app',
'--primary-bundle-id', 'com.metabase.Metabase',
'--username', $appleID,
'--password', '@keychain:METABASE_MAC_APP_BUILD_PASSWORD',
'--asc-provider', $ascProvider,
'--file', $filename
) == 0 or die $!;
}
sub wait_for_notarization {
announce "Waiting for notarization...";
Readonly my $appleID => getAppleID;
Readonly my $ascProvider => getAscProvider;
my $status = `xcrun altool --notarization-history 0 -u "$appleID" -p "\@keychain:METABASE_MAC_APP_BUILD_PASSWORD" --asc-provider $ascProvider` or die $!;
print "$status\n";
if ($status =~ m/in progress/) {
print "Notarization is still in progress, waiting a few seconds and trying again...\n";
sleep 5;
wait_for_notarization();
} else {
announce "Notarization successful.";
return "Done";
}
}
sub staple_notorization {
my ($filename) = @_;
announce "Stapling notarization to $filename...";
system('xcrun', 'stapler', 'staple',
'-v', $filename) == 0 or die $1;
announce "Notarization stapled successfully.";
}
# Verify that an app is Signed & Notarized correctly. See https://help.apple.com/xcode/mac/current/#/dev1cc22a95c
sub verify_notarization {
# e.g. /Applications/Metabase.app
my ($appFile) = @_;
announce "Verifying that $appFile is notarized correctly...";
system('spctl', '-a', '-v', $appFile) == 0 or die $!;
announce "Verification successful.";
}
sub notarize_files {
notarize_file(get_file_or_die($zipfile));
notarize_file(get_file_or_die($dmg));
wait_for_notarization();
staple_notorization(get_file_or_die($dmg));
verify_notarization(get_file_or_die($app));
}
# ------------------------------------------------------------ UPLOADING ------------------------------------------------------------
......@@ -303,8 +384,8 @@ sub create_dmg {
# Upload artifacts to AWS
# Make sure to run `aws configure --profile metabase` first to set up your ~/.aws/config file correctly
sub upload {
my $aws_profile = config('awsProfile') or return;
my $aws_bucket = config('awsBucket') or return;
Readonly my $aws_profile => config_or_die('awsProfile');
Readonly my $aws_bucket => config_or_die('awsBucket');
# Make a folder that contains the files we want to upload
Readonly my $upload_dir => artifact('upload');
......@@ -332,6 +413,20 @@ sub upload {
announce "Upload finished."
}
sub create_cloudfront_invalidation {
announce "Creating CloudFront invalidation...";
system ('aws', 'configure',
'set', 'preview.cloudfront', 'true') == 0 or die $!;
system ('aws', 'cloudfront', 'create-invalidation',
'--profile', config_or_die('awsProfile'),
'--distribution-id', config_or_die('cloudFrontDistributionID'),
'--paths', '/appcast.xml') == 0 or die $!;
announce "CloudFront invalidation created successfully.";
}
# ------------------------------------------------------------ RUN ALL ------------------------------------------------------------
......@@ -345,7 +440,9 @@ sub all {
generate_appcast;
edit_release_notes;
create_dmg;
notarize_files;
upload;
create_cloudfront_invalidation;
}
# Run all the commands specified in command line args, otherwise default to running 'all'
......
......@@ -3,64 +3,115 @@
NOTE: These instructions are only for packaging a built Metabase uberjar into `Metabase.app`. They are not useful if your goal is to work on Metabase itself; for development, please see
our [developers' guide](developers-guide.md).
## Prereqs
## First-Time Configuration
1. Install XCode.
### Building
1. Install XCode command-line tools. In `Xcode` > `Preferences` > `Locations` select your current Xcode version in the `Command Line Tools` drop-down.
The following steps need to be done before building the Mac App:
1. Install XCode.
1. Run `./bin/build` to build the latest version of the uberjar.
1. Add a JRE to the `OSX/Metabase/jre`
1. Next, you'll need to run the following commands before building the app:
You can download a copy of a JRE from https://adoptopenjdk.net/releases.html — make sure you download a JRE rather than JDK. Move the `Contents/Home` directory from the JRE archive into `OSX/Metabase/jre`. For example:
```bash
# Fetch and initialize git submodule
git submodule update --init
wget https://github.com/AdoptOpenJDK/openjdk8-binaries/releases/download/jdk8u232-b09/OpenJDK8U-jre_x64_mac_hotspot_8u232b09.tar.gz
tar -xzvf OpenJDK8U-jre_x64_mac_hotspot_8u232b09.tar.gz
mv jdk8u232-b09-jre/Contents/Home/ OSX/Metabase/jre
```
# Install Perl modules used by ./bin/osx-setup and ./bin/osx-release
sudo cpan install File::Copy::Recursive Readonly String::Util Text::Caml JSON
You are fine to use whatever the latest JRE version available is. I have been using the HotSpot JRE instead of the OpenJ9 one but it ultimately shouldn't make a difference.
1. Copy Metabase uberjar to OSX resources dir
# Copy JRE and uberjar
./bin/osx-setup
```bash
cp /path/to/metabase.jar OSX/Resources/metabase.jar
```
Every time you want to build a new version of the Mac App, you can simple update the bundled uberjar the same way.
At this point, you should try opening up the Xcode project and building the Mac App in Xcode by clicking the run button. The app should build and launch at this point. If it doesn't, ask Cam for help!
`./bin/osx-setup` will copy over things like the JRE into the Mac App directory for you. You only need to do this once the first time you plan on building the Mac App.
This also runs `./bin/build` to get the latest uberjar and copies it for you; if the script fails near the end, you can just copy the uberjar to `OSX/Resources/metabase.jar`.)
### Releasing
## Releasing
The following steps are prereqs for releasing the Mac App:
A handy Perl script called `./bin/osx-release` takes care of all of the details for you. Before you run it for the first time, you'll need to set up a few additional things:
```bash
# Install aws command-line client (if needed)
brew install awscli
1. Install XCode command-line tools. In `Xcode` > `Preferences` > `Locations` select your current Xcode version in the `Command Line Tools` drop-down.
# Configure AWS Credentials
# You'll need credentials that give you permission to write the metabase-osx-releases S3 bucket.
# You just need the access key ID and secret key; use the defaults for locale and other options.
aws configure --profile metabase
1. Install CPAN modules
# Obtain a copy of the private key used for signing the app (ask Cam)
# and put a copy of it at ./dsa_priv.pem
cp /path/to/private/key.pem OSX/dsa_priv.pem
```
```bash
sudo cpan
install force File::Copy::Recursive Readonly String::Util Text::Caml JSON
quit
```
You can install [PerlBrew](https://perlbrew.pl/) if you want to install CPAN modules without having to use `sudo`.
You'll need the `Apple Developer ID Application Certificate` in your computer's keychain.
You'll need to generate a Certificate Signing Request from Keychain Access, and have Sameer go to [the Apple Developer Site](https://developer.apple.com/account/mac/certificate/)
and generate one for you, then load the file on your computer.
Normally you shouldn't have to use `install force` to install the modules above, but `File::Copy::Recursive` seems fussy lately and has a failing test that prevents it from installing normally.
Finally, you may need to open the project a single time in Xcode to make sure the appropriate "build schemes" are generated (these are not checked into CI).
Run `open OSX/Metabase.xcodeproj` to open the project, which will automatically generate the appropriate schemes. This only needs to be done once.
1. Install AWS command-line client (if needed)
After that, you are good to go:
```bash
# Build the latest version of the uberjar and copy it to the Mac App build directory
# (You can skip this step if you just ran ./bin/osx-setup, because it does this step for you)
./bin/build && cp target/uberjar/metabase.jar OSX/Resources/metabase.jar
```bash
brew install awscli
```
1. Configure AWS Credentials for `metabase` profile (used to upload artifacts to S3)
# Bundle entire app, and upload to s3
./bin/osx-release
```
You'll need credentials that give you permission to write the metabase-osx-releases S3 bucket.
You just need the access key ID and secret key; use the defaults for locale and other options.
```bash
aws configure --profile metabase
```
1. Obtain a copy of the private key for signing app updates (ask Cam) and put a copy of it at `OSX/dsa_priv.pem`
```bash
cp /path/to/private/key.pem OSX/dsa_priv.pem
```
1. Add `Apple Developer ID Application Certificate` to your computer's keychain.
You'll need to generate a Certificate Signing Request from Keychain Access, and have Sameer go to [the Apple Developer Site](https://developer.apple.com/account/mac/certificate/) and generate one for you, then load the file on your computer.
1. Export your Apple ID for building the app as `METABASE_MAC_APP_BUILD_APPLE_ID`. (This Apple ID must be part of the Metabase org in the Apple developer site. Ask Cam or Sameer to add you if it isn't.)
```bash
# Add this to .zshrc or .bashrc
export METABASE_MAC_APP_BUILD_APPLE_ID=my_email@whatever.com
```
1. Create an App-Specific password for the Apple ID in the previous step
Go to https://appleid.apple.com/account/manage then `Security` > `App-Specific Passwords` > `Generate Password`
1. Store the password in Keychain
```bash
xcrun altool \
--store-password-in-keychain-item "METABASE_MAC_APP_BUILD_PASSWORD" \
-u "$METABASE_MAC_APP_BUILD_APPLE_ID" \
-p <secret_password>
```
## Building & Releasing the Mac App
After following the configuration steps above, to build and release the app you can use the `./bin/osx-release` script:
1. Copy latest uberjar to the Mac App build directory
```bash
cp path/to/metabase.jar OSX/Resources/metabase.jar
```
1. Bundle entire app, and upload to s3
```bash
./bin/osx-release
```
## Debugging ./bin/osx-release
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment