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:
parent
4369c92d40
commit
955a0f7a33
|
@ -105,6 +105,100 @@ jobs:
|
|||
name: ${{ 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
|
||||
github_release:
|
||||
name: Create GitHub Release
|
||||
|
|
|
@ -28,3 +28,6 @@ Cargo.lock
|
|||
# Compiled files for documentation
|
||||
docs/node_modules
|
||||
docs/.vuepress/dist/
|
||||
|
||||
# Ignore pkg files within the install directory
|
||||
install/**/*.pkg
|
|
@ -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>
|
|
@ -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"
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
}
|
|
@ -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>
|
|
@ -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 "AS IS" 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>
|
|
@ -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 |
|
@ -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/
|
|
@ -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>
|
Loading…
Reference in New Issue