ci: Notarize MacOS Binaries and Add Flat Package Installers (#3571)

* ci: Notarize MacOS

* Modify logging and documentation to be better

* Make a copy of certain parts of the deploy workflow

* Delete testing workflow
This commit is contained in:
Kevin Song 2022-02-27 15:58:28 -06:00 committed by GitHub
parent 4369c92d40
commit 955a0f7a33
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 746 additions and 0 deletions

View File

@ -105,6 +105,100 @@ jobs:
name: ${{ matrix.name }} name: ${{ matrix.name }}
path: ${{ matrix.name }} path: ${{ matrix.name }}
# Notarize starship binaries for MacOS and build notarized pkg installers
notarize_and_pkgbuild:
runs-on: macos-latest
needs: github_build
strategy:
fail-fast: false
matrix:
include:
- target: x86_64-apple-darwin
arch: x86_64
name: starship-x86_64-apple-darwin.tar.gz
pkgname: starship-x86_64-apple-darwin.pkg
- target: aarch64-apple-darwin
arch: aarch64
name: starship-aarch64-apple-darwin.tar.gz
pkgname: starship-aarch64-apple-darwin.pkg
env:
KEYCHAIN_FILENAME: app-signing.keychain-db
KEYCHAIN_ENTRY: AC_PASSWORD
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Notarize | Set up secrets
env:
APP_CERTIFICATE_BASE64: ${{ secrets.APPLEDEV_APPSIGNKEY_BASE64 }}
INSTALL_CERTIFICATE_BASE64: ${{ secrets.APPLEDEV_INSTALLERSIGNKEY_BASE64 }}
P12_PASSWORD: ${{ secrets.APPLEDEV_SIGNKEY_PASS }}
KEYCHAIN_PASSWORD: ${{ secrets.APPLEDEV_SIGNKEY_PASS }}
APPLEID_USERNAME: ${{ secrets.APPLEDEV_ID_NAME }}
APPLEID_TEAMID: ${{ secrets.APPLEDEV_TEAM_ID }}
APPLEID_PASSWORD: ${{ secrets.APPLEDEV_PASSWORD }}
run: |
APP_CERTIFICATE_PATH="$RUNNER_TEMP/app_certificate.p12"
INSTALL_CERTIFICATE_PATH="$RUNNER_TEMP/install_certificate.p12"
KEYCHAIN_PATH="$RUNNER_TEMP/$KEYCHAIN_FILENAME"
# import certificates from secrets
echo -n "$APP_CERTIFICATE_BASE64" | base64 --decode --output $APP_CERTIFICATE_PATH
echo -n "$INSTALL_CERTIFICATE_BASE64" | base64 --decode --output $INSTALL_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 certificates to keychain
security import $APP_CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security import $INSTALL_CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
# Add Apple Developer ID credentials to keychain
xcrun notarytool store-credentials "$KEYCHAIN_ENTRY" --team-id "$APPLEID_TEAMID" --apple-id "$APPLEID_USERNAME" --password "$APPLEID_PASSWORD" --keychain "$KEYCHAIN_PATH"
- name: Notarize | Build docs
run: |
cd docs
npm install
npm run build
- name: Notarize | Download artifacts
uses: actions/download-artifact@v2
with:
name: ${{ matrix.name }}
path: artifacts
- name: Notarize | Unpack Binaries
run: tar xf artifacts/${{ matrix.name }}
- name: Notarize | Build, Sign, and Notarize Pkg
run: bash install/macos_packages/build_and_notarize.sh starship docs ${{ matrix.arch }} ${{ matrix.pkgname }}
- name: Notarize | Upload Notarized Flat Installer
uses: actions/upload-artifact@v2
with:
name: ${{ matrix.pkgname }}
path: ${{ matrix.pkgname }}
- name: Notarize | Package Notarized Binary
run: tar czvf ${{ matrix.name }} starship
- name: Notarize | Upload Notarized Binary
uses: actions/upload-artifact@v2
with:
name: ${{ matrix.name }}
path: ${{ matrix.name }}
- name: Cleanup Secrets
if: ${{ always() }}
run: |
KEYCHAIN_PATH="$RUNNER_TEMP/$KEYCHAIN_FILENAME"
security delete-keychain $KEYCHAIN_PATH
# Create GitHub release with Rust build targets and release notes # Create GitHub release with Rust build targets and release notes
github_release: github_release:
name: Create GitHub Release name: Create GitHub Release

3
.gitignore vendored
View File

@ -28,3 +28,6 @@ Cargo.lock
# Compiled files for documentation # Compiled files for documentation
docs/node_modules docs/node_modules
docs/.vuepress/dist/ docs/.vuepress/dist/
# Ignore pkg files within the install directory
install/**/*.pkg

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>arch</key>
<array>
<string>arm64</string>
</array>
</dict>
</plist>

View File

@ -0,0 +1,115 @@
#!/bin/bash
set -euo pipefail
# Envrionmental variables that need to be set. These are sane defaults
# KEYCHAIN_ENTRY=AC_PASSWORD # Or whatever you picked for <AUTH_ITEM_NAME>
# RUNNER_TEMP=~/Library/Keychains/
# KEYCHAIN_FILENAME=login.keychain-db
#
# Environmental variables that can be set if needed. Else they will default to
# values selected for the CI
#
# The identifier for the application signing key. Can be a name or a fingerprint
# APPLICATION_KEY_IDENT=E03290CABE09E9E42341C8FC82608E91241FAD4A
# The identifier for the installer signing key. Can be a name or a fingerprint
# INSTALLATION_KEY_IDENT=E525359D0B5AE97B7B6F5BB465FEC872C117D681
usage(){
echo "Builds, signs, and notarizes starship."
echo "Read readme.md in the script directory to see the assumptions the script makes."
echo "Usage: $0 <path-to-starship-binary> <path-to-docs-directory> <arch> [pkgname]"
echo " Example: $0 target/release/starship docs/ x64"
echo " Example: $0 target/debug/starship docs/ arm64 starship-1.2.1-arm64.pkg"
echo ""
echo "If no pkgname is provided, the package will be named starship-<version>-<arch>.pkg"
}
script_dir="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
source "$script_dir/common.sh"
if [[ -z ${KEYCHAIN_ENTRY+x} ]]; then
error "Environmental variable KEYCHAIN_ENTRY must be set."
fi
if [[ -z ${RUNNER_TEMP+x} ]]; then
error "Environmental variable RUNNER_TEMP must be set."
fi
if [[ -z ${KEYCHAIN_FILENAME+x} ]]; then
error "Environmental variable KEYCHAIN_FILENAME must be set."
fi
keychain_path="$RUNNER_TEMP/$KEYCHAIN_FILENAME"
if [[ ! -f "$keychain_path" ]]; then
error "Could not find keychain at $keychain_path"
fi
if [[ -z ${APPLICATION_KEY_IDENT+x} ]]; then
APPLICATION_KEY_IDENT=E03290CABE09E9E42341C8FC82608E91241FAD4A
echo "APPLICATION_KEY_IDENT not set. Using default value of $APPLICATION_KEY_IDENT"
fi
if [[ -z ${INSTALLATION_KEY_IDENT+x} ]]; then
INSTALLATION_KEY_IDENT=E525359D0B5AE97B7B6F5BB465FEC872C117D681
echo "INSTALLATION_KEY_IDENT not set. Using default value of $INSTALLATION_KEY_IDENT"
fi
if [[ -z ${3+x} ]]; then
usage
exit 1
fi
starship_binary="$1"
starship_docs_dir="$2"
arch="$3"
pkgname="${4:-}"
if [[ ! -d "$starship_docs_dir/.vuepress/dist" ]]; then
error "Documentation does not appear to have been built!"
fi
echo ">>>> Signing binary"
codesign --timestamp --keychain "$keychain_path" --sign "$APPLICATION_KEY_IDENT" --verbose -f -o runtime "$starship_binary"
# Make ZIP file to notarize binary
if [ "$starship_binary" != "starship" ]; then
cp "$starship_binary" starship
fi
zip starship.zip starship
echo ">>>> Submitting binary for notarization"
xcrun notarytool submit starship.zip --keychain-profile "$KEYCHAIN_ENTRY" --wait
# Don't think this is actually necessary, but not costly so why not
rm starship
unzip starship.zip
# Create the component package
echo ">>>> Building Component Package"
bash "$script_dir/build_component_package.sh" "starship" "$starship_docs_dir/.vuepress/dist"
# Create the distribution package
echo ">>>> Building Distribution Package"
resources_path="$script_dir/pkg_resources"
bash "$script_dir/build_distribution_package.sh" "starship-component.pkg" "$resources_path" "$arch"
# Codesign the package installer
productsign --timestamp --sign "$INSTALLATION_KEY_IDENT" starship-unsigned.pkg starship.pkg
# Notarize the package installer
echo ">>>> Submitting .pkg for notarization"
xcrun notarytool submit starship.pkg --keychain-profile "$KEYCHAIN_ENTRY" --wait
# Staple things
echo ">>>> Running final steps"
xcrun stapler staple starship.pkg
# Rename to expected name
if [ "$pkgname" = "" ]; then
version="$(starship_version "$starship_binary")"
pkgname="starship-$version-$arch.pkg"
fi
echo ">>>> Placing final output at $pkgname"
mv starship.pkg "$pkgname"

View File

@ -0,0 +1,90 @@
#!/bin/bash
set -euo pipefail
# Requirements:
# - MacOS
# - A starship repository with binaries and documentation already built.
# Usage: run this script, passing $1 to the repository path. The script assumes
# it is being run from within a starship repository if $1 is not provided.
usage(){
echo "Builds a component package for macOS."
echo "Assumes that the following items already exist:"
echo " - A starship binary which has already been notarized"
echo " - Documentation created by \`npm run build\`, usually in a dist"
echo " directory at <repo>/docs/.vuepress/dist"
echo "Usage: $0 <path-to-starship-binary> <path-to-dist-directory>"
}
script_dir="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
source "$script_dir/common.sh"
cleanup_server(){
if [[ -n "${server_pid-}" ]]; then
echo "Killing HTTP server ($server_pid) to clean up."
kill "$server_pid"
rm "x86_64-apple-darwin-simple-http-server"
else
echo "No server found, exiting normally."
fi
}
if [[ "$OSTYPE" != 'darwin'* ]]; then
error "This script only works on MacOS"
fi
if [[ "${2-undefined}" = "undefined" ]]; then
usage
exit 1
fi
starship_program_file="$1"
starship_documentation_dir="$2"
if [ ! -f "$starship_program_file" ]; then
error "Could not find starship binary at $starship_program_file"
fi
if [ ! -d "$starship_documentation_dir" ]; then
error "Could not find starship documentation at $starship_documentation_dir"
fi
pkgdir="$(mktemp -d)"
mkdir -p "$pkgdir/usr/local/bin"
cp "$starship_program_file" "$pkgdir/usr/local/bin/starship"
# Now we get to make documentation! Vuepress was not designed to build locally
# (too many assumptions about running on an HTTP server), so we do the hackiest
# thing imagineable: start an http server, and use wget to make a local mirror.
# First, we need to install the server. There are several options, but this one
# provides prebuilt binaries for MacOS, making it the easiest. (yay rust)
server_prog_name="x86_64-apple-darwin-simple-http-server"
latest_server_version="$(curl -L -s -H 'Accept: application/json' https://github.com/TheWaWaR/simple-http-server/releases/latest | sed -e 's/.*"tag_name":"\([^"]*\)".*/\1/')"
curl -LOk "https://github.com/TheWaWaR/simple-http-server/releases/download/$latest_server_version/$server_prog_name"
chmod u+x "$server_prog_name"
# Next, we build the documentation and serve it via simple-http-server
trap cleanup_server INT
"./$server_prog_name" --ip 127.0.0.1 --index "$starship_documentation_dir" &
server_pid="$!"
# Give the server a chance to come online before trying to mirror it
echo "Sleeping to give the server a chance to come online..."
sleep 3
# Use wget to make a mirror of the site and move it into the package. Not installed
# on MacOS by default, but lucky for us, it does exist on GHActions runners.
# Wget may return nonzero exit codes even if things were mostly fine (e.g. 404 for
# some links on translated pages) so we simply ignore if it has a failure
wget --mirror --convert-links --adjust-extension --page-requisites --no-parent 127.0.0.1:8000 &> wget.log || true
mkdir -p "$pkgdir/usr/local/share/doc/"
mv 127.0.0.1:8000 "$pkgdir/usr/local/share/doc/starship"
# Technically a race condition here, but very unlikely to hit it in practice.
cleanup_server
trap - INT
# Build the component package
version="$(starship_version "$starship_program_file")"
pkgbuild --identifier com.starshipprompt.starship --version "$version" --root $pkgdir starship-component.pkg

View File

@ -0,0 +1,67 @@
#!/bin/bash
component_package="$1"
resources="$2"
arch="$3"
usage(){
echo "Builds a distribution package for macOS."
echo "Assumes that the following items already exist:"
echo " - A starship component package"
echo " - Resources in a pkg_resources directory"
echo "Usage: $0 <path-to-component-package> <path-to-pkg-resources> <arch>"
echo " where arch is one of \"arm64\" or \"x86_64\""
}
script_dir="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
source "$script_dir/common.sh"
if [[ "$OSTYPE" != 'darwin'* ]]; then
error "This script only works on MacOS"
fi
if [[ "${3-undefined}" = "undefined" ]]; then
usage
exit 1
fi
# Generate a distribution file with the appropriate architecture plists
if [[ "$arch" == "x86_64" || "$arch" == "x64" ]]; then
archplist="$script_dir/x86_64.plist"
elif [[ "$arch" == "arm64" || "$arch" == "aarch64" ]]; then
archplist="$script_dir/aarch64.plist"
else
error "Invalid architecture: $arch"
fi
productbuild --synthesize --package starship-component.pkg --product "$archplist" starship_raw.dist
# A terrible hacky way to insert nodes into XML without needing a full XML parser:
# search for a line that matches our opening tag and insert our desired lines after it
# Solution taken from https://www.theunixschool.com/2012/06/insert-line-before-or-after-pattern.html
while read -r line
do
echo "$line"
if echo "$line" | grep -qF '<installer-gui-script '; then
echo '<welcome file="welcome.html" mime-type="text-html" />'
echo '<license file="license.html" mime-type="text-html" />'
echo '<conclusion file="conclusion.html" mime-type="text-html" />'
echo '<background file="icon.png" scaling="proportional" alignment="bottomleft"/>'
fi
done < starship_raw.dist > starship.dist
# The above script does not correctly take care of the last line. Apply fixup.
echo '</installer-gui-script>' >> starship.dist
echo "Creating distribution package with following distribution file:"
cat starship.dist
echo "Resource directory is $resources"
echo "Component package is $component_package"
# Build the distribution package
productbuild --distribution starship.dist --resources "$resources" --package-path "$component_package" starship-unsigned.pkg
# Clean up the distribution files
rm -- *.dist

View File

@ -0,0 +1,22 @@
#!/bin/bash
error(){
echo "[ERROR]: $1"
exit 1
}
starship_version(){
starship_program_file="$1"
# Check if this is a relative path: if so, prepend './' to it
if [ "$1" = "${1#/}" ]; then
starship_program_file="./$starship_program_file"
fi
if "$starship_program_file" -V 2>&1 > /dev/null; then
"$starship_program_file" -V | grep -Eo '[0-9]+\.[0-9]+\.[0-9]+'
else
# try to get this information from Cargo.toml
pushd "$(git rev-parse --show-toplevel)" || true
grep '^version = \"\(.*\)\"' Cargo.toml | cut -f 2 -d '"'
popd
fi
}

View File

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html>
<head>
<title>Install Starship</title>
<meta charset="utf-8">
</head>
<body>
<font face="Helvetica Neue" size="4">
<p>Starship has been installed!</p>
<p>Visit <a href="https://starship.rs">https://starship.rs</a> to get
instructions on how to configure your shell to use starship.</p>
<p>If you do not have internet access, you can view an offline copy of the
the documentation by opening
<a href="file:///usr/local/share/doc/starship/index.html">
<code>
/usr/local/share/doc/starship/index.html.
</code>
</a>
</p>
<p>Unless you modified the installer, your copy of starship was installed to
<code>/usr/local/bin/starship</code>.
</p>
</font>
</body>
</html>

View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<head>
<title>ISC License</title>
<meta charset="utf-8">
</head>
<body>
<font face="Helvetica Neue" size="4">
<p>ISC License</p>
<p>Copyright (c) 2019-2022, Starship Contributors</p>
<p>Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.</p>
<p>THE SOFTWARE IS PROVIDED &quot;AS IS&quot; AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.</p>
</font>
</body>
</html>

View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<title>Install Starship</title>
<meta charset="utf-8">
</head>
<body>
<font face="Helvetica Neue" size="4">
<p>Starship is the minimal, blazing-fast, and infinitely customizable prompt for any shell!</p>
<p>This installer will install Starship on to <code>/usr/local/</code> on your system.</p>
<p>After the installation, you will need to modify your shell startup files to
use starship.</p>
</font>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

View File

@ -0,0 +1,263 @@
# MacOS Codesigning Scripts
Well, here we are. The Apple notarization procedure is complex enough that I
need an actual pile of scripts and writeup to be able to remember how to do it.
The basic procedure is as follows:
- Build code
- Build docs
- Sign binary with Developer Application ID
- Upload binary to notarization service to get notarized
- Use code + docs to generate a component package
- Use component package to generate a distribution package
- Sign distribution package with Developer Installer ID
- Upload distribution package to notarization service to get notarized
Et. voila, you have a notarized distribution package which can be installed.
I fully anticipate that this procedure will break in the future, so here is the
(scant) documentation that I have been able to scrape together on these procedures,
along with some commentary on things that I've found and requirements for these
scripts.
You will need XCode installed.
## Short-form Command Line Invocation
If you have the prerequisites set up (including the environment variables as
described below, built docs, and built release binary), you can generate the
package file with the following command:
```
./install/macos_packages/build_and_notarize.sh target/release/starship docs x64
```
or `arm64` if building on Apple silicon.
## Setting Up Credentials
### Apple Developer Account
In order to get the signing keys, you need to have a developer account. You can
buy one at https://developer.apple.com/programs/ for $100 a year (at time of
writing).
There is no other way to acquire an account, which is needed to obtain
non-self-signed keys and to be able to notarize files.
### Signing Keys
To generate the signing keys, I went through the [XcodeGUI](https://help.apple.com/xcode/mac/current/#/dev154b28f09), though there are
several other methods to do this. You will need at least one Application signing
key and one Installer signing key.
To check what signing keys are available, you can use the following command:
```
security find-identity -p basic -v
```
### Notarization Credentials
To be able to notarize objects, you will need an app-specific password. You will
need to set it up using the instructions [on this page](https://support.apple.com/en-us/HT204397).
You will also need your team ID, which can be found at https://developer.apple.com/account/#/membership
(if it goes to the home page, click on "Membership" on the left panel), and your
Apple ID (usually an email address).
If you want to enter everything manually, most commands that require these values
accept the `--apple-id`, `--team-id`, and `--password` flags. However, I find it
simpler to store the credentials in the keychain. You can do so with the following
command:
```
xcrun notarytool store-credentials "<AUTH_ITEM_NAME>" --apple-id "<apple-id>" --password "<password>" --team-id "<team-id>"
```
where `<AUTH_ITEM_NAME>` is a name you will use later to refer to the credentials,
and the other three items are the Apple ID, the Team ID, and the app-specific password,
respectively. For the rest of this document, I will assume that its value is
`AC_PASSWORD` for compatibility with Apple's website, though you may choose
whatever you like.
### Script Assumptions
The scripts in this directory assume that the signing keys and the notarization
credentials are unlocked and available within a specific keychain file, stored
in a file at `$RUNNER_TEMP/$KEYCHAIN_FILENAME`. Additionally, it assumes that
the `AUTH_ITEM_NAME` used to refer to the notarization credentials is found in
the environment under the variable `KEYCHAIN_ENTRY`.
The CI environment ensures that the keychain file exists at the appropriate
locations and is destroyed after use. If you are running these scripts locally,
the values that correspond to what Apple uses in their tutorials are:
```
KEYCHAIN_ENTRY=AC_PASSWORD # Or whatever you picked for <AUTH_ITEM_NAME> above
RUNNER_TEMP=~/Library/Keychains
KEYCHAIN_FILENAME=login.keychain
```
Note to developers: because the keychain file may be a user's personal keychain,
you MUST NEVER WRITE TO THE KEYCHAIN FILE in these scripts. On the CI, CI actions
will ensure that the keychain file is shredded after use.
## Codesigning a Binary
This is actually fairly simple. Run
```
codesign --timestamp --sign "<Key ID>" --verbose -f -o runtime <binary>
```
to sign the binary file. `--timestamp` is not required for signing, but will be
required for notarization. `<Key ID>` can be one of two things: the name of the
signing key (on the right of `security find-identity -p basic -v`), or the key
hash (the hex string on the left of the command).
Usually you can use name of the key, but if you have multiple keys like me, you
may need to use the hex string to specify.
## Notarizing a Binary
Once the binary has been signed, you need to package it into a .zip file in order
to be able to send it to Apple for notarization. The simplest way to do this is
to run `zip <archive.zip> <binary>`.
Then, run `xcrun notarytool submit <archive.zip> --keychain-profile "AC_PASSWORD" --wait`
to submit the binary for notarization. The `--wait` flag will cause the tool to
block until the notarialization is complete. If you want to be able to leave and
check the results later, omit `--wait` (though starship notarization usually takes
no more than 60s).
Finally, you should check the submission logs. To get a record of all notarization
attempts, run
```
xcrun notarytool history --keychain-profile "AC_PASSWORD"
```
Find the `id` of the attempt you wish to view, then run one of these commmands:
```
xcrun notarytool info <run-id> --keychain-profile "AC_PASSWORD"
xcrun notarytool log <run-id> --keychain-profile "AC_PASSWORD"
```
The `log` command downloads a JSON log of the notarization attempt, and can reveal
warnings that should be fixed before the next submission.
Additional details on the notarization process can be found at https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution/customizing_the_notarization_workflow.
Note that while Apple has a lot of requirements on their pages, including stuff
like Hardened Runtime requirements and listing entitlements, as far as I can tell,
starship does not require any of these even though we do things like send
notifications and access the network via an HTTP client. Nonetheless, I'm
linking the [entitlements page](https://developer.apple.com/documentation/bundleresources/entitlements)
here in case it becomes important later.
## Creating a Component Package
Since I'm only dealing with one binary, we will make one Component package and
one Distribution package. Surprisingly, the flat package (.pkg) format is not
documented by Apple. [This guide](https://matthew-brett.github.io/docosx/flat_packages.html)
and many of the links within it are the best documentation available on the subject.
To build a component package, we first need to create a temporary directory and
create a pseudo-filesystem within it (similar to makepkg on Arch). For example,
if we place the directory at `$TEMP_DIR/usr/local/bin/starship`, the binary
will be installed at `/usr/local/bin/starship` once the installer runs.
An aside on docs: We would also like to include documentation in the pkg.
Unfortunately, Vuepress currently cannot build with relative paths, and any
attempt at hacking this in seems to create even more problems. Instead, the
scripts do the dumbest thing imaginable: build the documentation, serve it with
a simple HTTP server, and then use `wget` to make a local copy which can be
viewed offline.
Once everything is placed in the correct locations, we can run the following
command to generate the component package:
```
pkgbuild --identifier com.starshipprompt.starship --version "<version>" --root <pkgdir> output.pkg
```
## Notarizing the Component Package (and why we don't need to)
Fortunately for us, Apple has confirmed that we only need to notarize the
[outermost installer mechanism](https://developer.apple.com/forums/thread/122045).
Therefore, if we are sending the component package on its own, we should notarize
it now. However, for starship, we will bundle this into a distribution package,
so we don't need to notarize this pkg file.
## Creating a Distribution Package
To create a distribution, we do the following steps:
- Use `productbuild` to generate a skeleton distribution file.
- Insert custom welcome/license/conclusion and icon files into the installer.
- Build the installer with `productbuild`.
I have elected not to make a fat binary due to concerns over startup cost, so
there are two .plist files that can be used to specify the architecture required.
## Signing the Distribution package
This is also fairly simple, and analagous to signing the binary.
```
productsign --timestamp --sign "<Key ID>" <input.pkg> <output.pkg>
```
## Notarizing the Distribution Package
Also analagous to notarizing the binary. We run
```
xcrun notarytool submit <package.pkg> --keychain-profile "AC_PASSWORD" --wait
```
and also check the submission logs.
Note: you may need to enter your password a ridiculous number of times (like 4+)
in order to successfully notarize this.
## Stapling the Result
Finally, we staple the notarization ticket to the package, ensuring that anyone
who downloads the file can see that the installer was notarized:
```
xcrun stapler staple <package>
```
Note that `.dmg`, `.app`, and `.pkg` files can be stapled, but `.zip` and
binary files cannot. Distributing the latter files alone will require that the
installing computer can access the internet to verify notarization of the app.
## Putting It All Together
If you don't want to run these commands, a full workflow is available in
`build_and_notarize` script. Check the documentation at the top of the script
for environment variables and arguments that need to be set--it is a fairly
complicated script, but this is a fairly complicated procedure.
# Testing Notarization
To test if a particular item is notarized, run one of the following commands:
```
codesign --test-requirement="=notarized" --verify --verbose <file>
spctl -a -vvv -t install <file>
```
# External Links
https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution
https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution/customizing_the_notarization_workflow
https://github.com/akeru-inc/xcnotary
https://www.reddit.com/r/rust/comments/q8r90b/notarization_of_rust_binary_for_distribution_on/

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>arch</key>
<array>
<string>x86_64</string>
</array>
</dict>
</plist>