bakepkg

The Modern macOS Package Builder

A robust CLI tool and Go library for building signed, notarized macOS installer packages (.pkg) with auto-generated install scripts — no manual scripting required.

go install al.essio.dev/cmd/bakepkg@latest Copied!
🚀

Simple

Build a signed, notarized .pkg from a single JSON config file.

⚙️

Auto-scripting

Generates postinstall, uninstall.sh, and upgrade scripts automatically.

🛤️

PATH Integration

Writes /etc/paths.d and /etc/manpaths.d entries so binaries are immediately discoverable.

🔐

Integrated Signing

Automatically signs binaries and the final installer with your Developer ID certificate.

🍎

Modern Notarization

Directly integrates with Apple's Notary API — no more xcrun altool.

🖥️

Distribution UI

First-class support for Welcome screens, READMEs, Licenses, and Dark Mode backgrounds.

👤

Single-User Mode

Generate distribution packages with per-user installation domain choices.

🧹

Quarantine Stripping

Strips com.apple.quarantine attributes to prevent Gatekeeper false positives.

🔬

Simulation Mode

Dry-run mode prints all commands without touching the system — perfect for CI debugging.

Quick Start

# Run with default config (bakepkg.json in current directory)
bakepkg

# Use a specific config file with verbose output
bakepkg -config custom.json -verbose

# CI/CD: pass notarization credentials via environment
export MACOSNOTARYLIB_ISSUER_ID="..."
export MACOSNOTARYLIB_KID="..."
export MACOSNOTARYLIB_PRIVATE_KEY="base64_key"
bakepkg

Options

FlagDescriptionDefault
-configPath to JSON configuration filebakepkg.json
-verboseEnable verbose outputfalse
-debugEnable debug outputfalse
-version, -VPrint version information and exit
-help, -hDisplay the help message and exit

Configuration Reference

// bakepkg.json { "id": "com.example.mytool", "name": "MyTool", "version": "1.2.0", "output": "MyTool-1.2.0.pkg", "symlink_binaries": true, "files": { "bin": ["./build/mytool"], "share/man/man1": ["./docs/mytool.1"], "etc": ["./config/defaults.yaml"] }, "signing": { "identity": "Developer ID Installer: Your Name (XYZ)", "notarize": true } }
FieldTypeDefaultDescription
idstringrequiredReverse-DNS package identifier, e.g. com.example.tool.
namestringrequiredApplication name. Used as the install prefix component and in paths.d.
versionstring"1.0.0"Package version string.
outputstring"out.pkg"Output .pkg filename.
single_userbooleanfalseProduce a distribution package with per-user installation domain choices.
symlink_binariesbooleanfalseCreate symlinks in /usr/local/bin/ for each detected binary.
filesobjectMap of dest_subdir → [source_paths]. Installed under /Library/<Name>/<Version>/.
distribution.*objectUI assets for the installer wizard: readme, license, welcome, background, background_dark.
signing.identitystringDeveloper ID Installer certificate name or hash.
signing.notarizebooleanfalseSubmit the package to Apple's Notary API after signing.
signing.issuer_idstringApp Store Connect API issuer ID. Env: MACOSNOTARYLIB_ISSUER_ID.
signing.key_idstringApp Store Connect API key ID. Env: MACOSNOTARYLIB_KID.
signing.private_key_b64stringBase64-encoded private key. Env: MACOSNOTARYLIB_PRIVATE_KEY.

Build Pipeline

1

Validate & Stage

Validate config, create a temp staging directory, copy all files into it.

2

Quarantine Strip

Remove com.apple.quarantine extended attributes from all staged files.

3

Generate Scripts

Auto-generate postinstall, preupgrade, postupgrade, and uninstall.sh from the file map.

4

Codesign Binaries

Sign all executables in the staging directory with your Developer ID Application certificate.

5

pkgbuild

Assemble the staged payload and scripts into a flat .pkg component.

6

productbuild

Wrap into a distribution package when UI assets or single_user mode is enabled. Injects <domains> for per-user installs.

7

productsign

Sign the final package with your Developer ID Installer certificate.

8

Notarize & Staple

Submit to Apple's Notary API and staple the resulting ticket to the package.

Go Library

import "al.essio.dev/cmd/bakepkg/pkg/pkginstaller" builder := pkginstaller.New().
    WithIdentifier("com.example.mytool").
    WithName("MyTool").
    WithVersion("1.2.0").
    AddFile("build/mytool", "bin/mytool").
    WithSymlinkBinaries(true).
    WithSigning(pkginstaller.Signing{
        Identity: "Developer ID Installer: Example (XYZ)",
        Notarize: true,
    })

err := builder.Build("MyTool-1.2.0.pkg")