I'm building a Github Actions job to build, sign and notarize a PKG file.
I'm using an Apple Id account (the workflow needs username and password) as well as a Developer Id Installer certificate with private key (encrypted). Both are saved as secrets (base64) and will be converted in the workflow to a .p12 file, then added to keychain.
This job is part of a bigger workflow in a private repository, that generates first the files (using Pyinstaller) from the software, and then export the PKG on a AWS S3 Bucket.
jobs:
[...]
pkg:
needs: [...]
runs-on: macos-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Download macos bin file
uses: actions/download-artifact@v2
with:
name: macos-bin-files
path: dist/
- name:
run: |
----- Create certificate files from secrets base64 -----
echo ${{ secrets.DEVELOPER_ID_INSTALLER_CER }} | base64 --decode > certificate_installer.cer
echo ${{ secrets.DEVELOPER_ID_INSTALLER_KEY }} | base64 --decode > certificate_installer.key
----- Create p12 file -----
openssl pkcs12 -export -name zup -in certificate_installer.cer -inkey certificate_installer.key -passin pass:${{ secrets.KEY_PASSWORD }} -out certificate_installer.p12 -passout pass:${{ secrets.P12_PASSWORD }}
----- Configure Keychain -----
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
security create-keychain -p "${{ secrets.KEYCHAIN_PASSWORD }}" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "${{ secrets.KEYCHAIN_PASSWORD }}" $KEYCHAIN_PATH
----- Import certificates on Keychain -----
security import certificate_installer.p12 -P "${{ secrets.P12_PASSWORD }}" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
----- Generate PKG from files -----
use a macos installer script (ref: https://github.com/KosalaHerath/macos-installer-builder/tree/master/macOS-x64)
----- Sign PKG file -----
productsign --sign "${{ secrets.DEVELOPER_ID_INSTALLER_NAME }}" $INPUT_FILE_PATH $OUTPUT_FILE_PATH
- name: "Notarize Release Build PKG"
uses: devbotsxyz/xcode-notarize@v1
with:
product-path: $PATH_TO_PKG
appstore-connect-username: ${{ secrets.APPLE_ACCOUNT_USERNAME }}
appstore-connect-password: ${{ secrets.APPLE_ACCOUNT_PASSWORD }}
primary-bundle-id: 'BUNDLE_ID'
- name: "Staple Release Build"
uses: devbotsxyz/xcode-staple@v1
with:
product-path: $PATH_TO_PKG
[...]
I followed the Github Action official documentation for installing an Apple certificate on macOS runners and this part is working as expected. I can add the certificate to keychain and use it to sign the PKG file with the productsign
command.
However, when I send the PKG to Apple for notarization, it returns me that error:
Error: Notarization status <invalid> - Package Invalid
Error: Notarization failed
The PKG is working as expected when distributed (just need to be opened as admin as not notarized), so the problem doesn't seem related to the package implementation.
I tried to perform the notarization with command lines, following links from different sources:
However, even with those command lines (without using the notarize action), I couldn't notarize the PKG file.
What is wrong with my workflow, did I miss something before trying to notarize the package?
PS: I couldn't find any reference doing this on a Github Actions macos runner...
After setting the devbotsxyz/xcode-notarize@v1
field verbose:true
, I observed that Apple returns a link to a JSON explaining why the package is INVALID for notarization.
In this JSON, it was informed that all the files composing my PKG (there are many as I couldn't use --onefile
with Pyinstaller in my context) were invalid because they weren't signed and timestamped.
After some researches, I found this post on the Apple Developer Forum and understood that PKG files need to be signed INSIDE OUT: First, you sign each file generated by Pyinstaller, then sign the PKG gathering all those files.
To do so, you can't use the productsign
command (as it only work for .pkg, .zip and .dmg) but codesign
.
However, codesign
doesn't use a Developer Id Installer certificate
, but a Developer Id Application certificate
, so I had to add this new certificate to the workflow as well.
Note that as my PKG would be composed of hundreds of files, I also needed a bash script to use codesign to sign each one of them.
My final workflow looks like this:
jobs:
[...]
pkg:
needs: [...]
runs-on: macos-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Download macos bin file
uses: actions/download-artifact@v4
with:
name: macos-bin-files
path: dist/
- name:
run: |
----- Create certificate files from secrets base64 -----
echo ${{ secrets.DEVELOPER_ID_INSTALLER_CER }} | base64 --decode > certificate_installer.cer
echo ${{ secrets.DEVELOPER_ID_INSTALLER_KEY }} | base64 --decode > certificate_installer.key
echo ${{ secrets.DEVELOPER_ID_APPLICATION_CER }} | base64 --decode > certificate_application.cer
echo ${{ secrets.DEVELOPER_ID_APPLICATION_KEY }} | base64 --decode > certificate_application.key
----- Create p12 file -----
openssl pkcs12 -export -name zup -in certificate_installer.cer -inkey certificate_installer.key -passin pass:${{ secrets.KEY_PASSWORD }} -out certificate_installer.p12 -passout pass:${{ secrets.P12_PASSWORD }}
openssl pkcs12 -export -name zup -in certificate_application.cer -inkey certificate_application.key -passin pass:${{ secrets.KEY_PASSWORD }} -out certificate_application.p12 -passout pass:${{ secrets.P12_PASSWORD }}
----- Configure Keychain -----
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
security create-keychain -p "${{ secrets.KEYCHAIN_PASSWORD }}" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "${{ secrets.KEYCHAIN_PASSWORD }}" $KEYCHAIN_PATH
----- Import certificates on Keychain -----
security import certificate_installer.p12 -P "${{ secrets.P12_PASSWORD }}" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security import certificate_application.p12 -P "${{ secrets.P12_PASSWORD }}" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
----- Codesign files with script -----
use a script to sign each file from the artifact (ref: https://gist.github.com/GuillaumeFalourd/4efc73f1a6014b791c0ef223a023520a)
----- Generate PKG from codesigned files -----
use a macos installer script (ref: https://github.com/KosalaHerath/macos-installer-builder/tree/master/macOS-x64)
----- Sign PKG file -----
productsign --sign "${{ secrets.DEVELOPER_ID_INSTALLER_NAME }}" $INPUT_FILE_PATH $OUTPUT_FILE_PATH
- name: Notarize Release Build PKG
uses: lando/notarize-action@v2
with:
product-path: $PATH_TO_PKG
appstore-connect-username: ${{ secrets.APPLE_ACCOUNT_USERNAME }}
appstore-connect-password: ${{ secrets.APPLE_ACCOUNT_PASSWORD }}
appstore-connect-team-id: FY8GAUX283 # if using notarytool
- name: "Staple Release Build"
uses: devbotsxyz/xcode-staple@v1
with:
product-path: $PATH_TO_PKG
[...]
EDIT: Notary tools have been updated on Apple. Previously the action below was used in the process but has been removed from the marketplace:
- name: "Notarize Release Build PKG"
uses: devbotsxyz/xcode-notarize@v1
with:
product-path: $PATH_TO_PKG
appstore-connect-username: ${{ secrets.APPLE_ACCOUNT_USERNAME }}
appstore-connect-password: ${{ secrets.APPLE_ACCOUNT_PASSWORD }}
primary-bundle-id: 'BUNDLE_ID'
I created this one to substitute the notarize and staple actions:
- name: "Notarize and Staple Release Build"
uses: GuillaumeFalourd/notary-tools@v1
with:
product_path: "$PATH_TO_PKG_OR_APP"
apple_id: ${{ secrets.NOTARIZATION_USERNAME }}
password: ${{ secrets.NOTARIZATION_PASSWORD }}
team_id: ${{ secrets.NOTARIZATION_TEAMID }}
staple: true
xcode_path: '/Applications/Xcode.app'