Skip to content

Instantly share code, notes, and snippets.

Last active March 26, 2024 20:05
Show Gist options
  • Star 38 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save txoof/0636835d3cc65245c6288b2374799c43 to your computer and use it in GitHub Desktop.
Save txoof/0636835d3cc65245c6288b2374799c43 to your computer and use it in GitHub Desktop.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "">
<plist version="1.0">
<!-- These are required for binaries built by PyInstaller -->


  • Create a developer account with Apple
  • Download and install X-Code from the Apple App Store
  • Open and run X-Code app and install whatever extras it requires
  • Open the preferences pane (cmd+,)
    • click the + in the lower right corner
    • choose Apple ID
    • enter your apple ID and password
    • Previously created keys can be downloaded and installed from

Create an App-Specific password for altool to use

  • Instructions from Apple
  • Open KeyChain Access
  • Create a "New Password Item"
    • Keychain Item Name: Developer-altool
    • Account Name: your developer account email
    • Password: the application-specific password you just created

Create an executable binary with Pyinstaller

NB! Additional args such as --add-data may be needed to build a functional binary

  • Create a onefile binary
    • pyinstaller --onefile

Sign the executable

  • Add the entitements.plist to the directory (see below)
  • List the available keys and locate a Developer ID Application certificate:
    • security find-identity -p basic -v
    1) ABC123 "Apple Development: ()"
    2) XYZ234 "Developer ID Installer: Aaron Ciuffo ()"
    3) QRS333 "Developer ID Application: Aaron Ciuffo ()"
    4) LMN343 "Developer ID Application: Aaron Ciuffo ()"
    5) ZPQ234 "Apple Development: ()"
    6) ASD234 "Developer ID Application: Aaron Ciuffo ()"
    7) 01010A "Developer ID Application: Aaron Ciuffo ()"
       7 valid identities found
  • codesign --deep --force --options=runtime --entitlements ./entitlements.plist --sign "HASH_OF_DEVELOPER_ID APPLICATION" --timestamp ./dist/

Package as a pkg for installation

  • Create a temp directory to build the package:
    • mkdir /tmp/myapp
  • Use ditto to build the pkg installer structure
    • ditto /path/to/myapp /tmp/myapp/path/to/install/location
    • repeat for all files that should be packaged
  • build the pkackage
  • productbuild --identifier "com.your.pkgname.pkg" --sign "HASH_OF_INSTALLER_ID" --timestamp --root /tmp/myapp / myapp.pkg


  • xcrun altool --notarize-app --primary-bundle-id "com.foobar.fooapp" --username="" --password "@keychain:Developer-altool" --file ./myapp.pkg
  • Check email for successful notarization
    • Alternatively check status using:
      • xcrun altool --notarization-history 0 -u "developer@***" -p "@keychain:Developer-altool"
  • If notarization fails use the following to review a detailed log:
  xcrun altool --notarization-info "Your-Request-UUID" \
             --username "" \                                    
             --password "@keychain:Developer-altool"   

Staple notarization to pkg

  • add the notariztaion to the pkg
    • xcrun stapler staple ghostscript64.pkg

Useful Resources

Alternative workflows that may have issues:

Create a bundled app with pyinstaller

  • Only ".app" bundles appear to work using this procedure
    • pyinstaller --windowed --onefile
    • edit the spec file app = BUNDLE section to include a bundle_identifier
    app = BUNDLE(exe,
  • NOTE! Appbundles will not execute properly -- they must be run by execuing the

package as a dmg

NB! This may not work for single file executables -- use the PKG method above

  • Create a .dmg:
    • clean any uneeded files out of ./dist; only the .app should remain
    • hdiutil create ./myapp.dmg -ov -volname "MyApp" -fs HFS+ -srcfolder "./dist"
  • Shrink and make read-only:
    • $hdiutil convert ./myapp.dmg -format UDZO -o myapp.dmg
Copy link

Copy link

txoof commented Feb 18, 2022

@barrownicholas Haha, after writing this, I did the same thing:

My solution is not at all elegant, but it's been getting the job done for the moment. It will be nice to see how you approached this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment