-
Notifications
You must be signed in to change notification settings - Fork 0
Building and Signing
How to build BootstrapMate from source, what each make target produces, and how signing and
notarization are configured. Read this if you are producing your own installer package, or if
you need to understand why the package published by this repository is unsigned.
- Swift 6.0 toolchain (
Package.swiftdeclaresswift-tools-version:6.0). - Xcode Command Line Tools, for
codesign,pkgbuild,productsign,xcrun actool,xcrun notarytoolandxcrun stapler. - Apple Developer ID Application and Developer ID Installer certificates in a keychain, if you intend to sign.
The repository states two different minimum macOS versions and they do not agree.
Package.swift declares platforms: [.macOS(.v15)], while packaging/resources/Info.plist.template
and the actool invocation set a deployment target of 13.0, and README.md says "macOS 13.0
or later". The SPM manifest is the one that binds the build: Swift Package Manager will not
build the package on a toolchain targeting anything below macOS 15. The 13.0 deployment target
only affects the compiled icon assets and the LSMinimumSystemVersion written into the app
bundle.
The GitHub Actions runners build on macos-26 and select the newest installed Xcode_26*.app,
so that is the combination the project is known to build under.
make with no target runs all, which is build. Every target below is defined in Makefile.
| Target | What it does and produces |
|---|---|
help |
Prints the target list and the required variables. |
check-signing-config |
Fails the build unless SIGNING_IDENTITY_APP and SIGNING_IDENTITY_PKG are set, and either NOTARIZATION_PROFILE or NOTARIZATION_APPLE_ID is set, and either NOTARIZATION_TEAM_ID or NOTARIZATION_PROFILE is set. |
swift-build |
swift build -c release --arch arm64 --arch x86_64. Produces the three universal binaries in .build/apple/Products/Release. |
copy-binary |
Codesigns each binary with --options runtime --timestamp under the identifiers com.github.bootstrapmate.managedbootstrapinstall, com.github.bootstrapmate.gui and com.github.bootstrapmate.helper, copies them into the package root's Contents/MacOS as managedbootstrapinstall, BootstrapMateGUI and BootstrapMateHelper at mode 755, and creates usr/local/bootstrapmate/.placeholder. |
compile-icon |
xcrun actool over resources/BootstrapMate.icon, producing Assets.car and BootstrapMate.icns in build/actool-out. |
create-app-bundle |
Substitutes {{MARKETING_VERSION}} and {{BUILD_NUMBER}} into Info.plist.template to produce the bundle's Contents/Info.plist, and copies the two icon artifacts into Contents/Resources. |
create-launchdaemon |
Copies com.github.bootstrapmate.plist into the package root's /Library/LaunchDaemons, com.github.bootstrapmate.helper.plist into the app bundle's Contents/Library/LaunchDaemons, and packaging/scripts/postinstall into build/scripts with the executable bit set. |
sign-app |
codesign --force --options runtime --timestamp --deep over the assembled app bundle. |
build-pkg |
Runs pkgbuild --analyze, sets BundleIsRelocatable and BundleIsVersionChecked to false with PlistBuddy, then pkgbuild with identifier com.github.bootstrapmate, the version, and the scripts directory. Produces build/BootstrapMate-<version>.pkg. |
sign-pkg |
productsign --sign "$(SIGNING_IDENTITY_PKG)" --timestamp, replacing the package in place. |
notarize-pkg |
xcrun notarytool submit --wait, using the keychain profile if set and the Apple ID and password otherwise, then xcrun stapler staple. |
verify |
pkgutil --check-signature, xcrun stapler validate, and spctl --assess --type install. Each failure aborts. |
build / all
|
The whole chain: check-signing-config swift-build copy-binary create-app-bundle create-launchdaemon sign-app build-pkg sign-pkg notarize-pkg verify. |
clean |
Removes build, makes .build writable, removes .build. |
The targets are chained by prerequisite, so make build-pkg also runs sign-app, which needs
SIGNING_IDENTITY_APP. README.md describes make build-pkg as building an unsigned package;
it does not. There is no make target that produces an unsigned package.
The Makefile does -include .env and export, so any variable set in a .env file at the
repository root becomes an environment variable for the build. .env is listed in
.gitignore. You can also pass the same variables on the make command line.
The Developer ID Application certificate used for codesign on the three binaries and on the
app bundle. Use the full identity string as printed by:
security find-identity -v -p codesigningThe Developer ID Installer certificate used by productsign. It comes from the same
security find-identity listing.
The name of a notarytool keychain profile. Create one, which stores the credentials in your
keychain rather than in a file:
xcrun notarytool store-credentialsWhen this is set, notarize-pkg submits with --keychain-profile and neither the Apple ID nor
the team ID is needed.
The alternative to a keychain profile: the Apple ID of the account attached to your Developer
Program membership, and an app-specific password generated for it at appleid.apple.com. Both
must be set for this path to work.
Your ten-character Apple Developer Team ID, required when notarizing with an Apple ID rather
than a keychain profile. It appears in your Apple Developer account and in the parenthesised
suffix of the certificate names printed by security find-identity.
Optional. Overrides the timestamp version described below.
Two files in the tree look like credential templates and are not quite that.
examples/setup-credentials.sh exports the same variables plus BUNDLE_ID, and mentions a
SIGNING_KEYCHAIN variable and a ./build.sh script; none of those three exist in this
repository, so treat it as a shape reference for the four real variables only.
resources/setup-notarization.sh pulls credentials from a specific organisation's secret
store and is not usable elsewhere as written. README.md tells you to copy
examples/.env.example; that file is not present in the repository, so write your .env by
hand.
Never commit a .env, a certificate, an app-specific password or a Team ID to the repository.
Nothing about signing is needed to compile, test or run the code:
swift buildswift testswift build -c release --arch arm64 --arch x86_64The release build produces bootstrapmate, BootstrapMateApp and BootstrapMateHelper in
.build/apple/Products/Release. The CLI can be run straight from there against a test
manifest; see Command Line Reference for the flags and
Getting Started for a dry run.
If you want an unsigned installer package rather than bare binaries, run the release workflow manually from the Actions tab — see below — or download the package asset from a release and sign it yourself.
| Path | Contents |
|---|---|
.build/apple/Products/Release |
The compiled universal binaries. |
build/pkg-root |
The staged package payload, including Applications/Utilities/BootstrapMate.app, Library/LaunchDaemons and usr/local/bootstrapmate. |
build/scripts |
The postinstall script handed to pkgbuild --scripts. |
build/actool-out |
Assets.car, BootstrapMate.icns and the partial Info.plist from actool. |
build/component.plist |
The analyzed component plist, with relocation and version checking disabled. |
build/BootstrapMate-<version>.pkg |
The finished package. |
Both build/ and .build/ are ignored by git and removed by make clean.
Versions are YYYY.MM.DD.HHMM. If VERSION is not set, the Makefile evaluates
date '+%Y.%m.%d.%H%M' once at parse time. The Makefile then splits it: everything before the
last dot becomes MARKETING_VERSION, and the final component becomes BUILD_NUMBER.
A worked example. Building with VERSION=2026.06.10.1430 gives:
| Value | Result |
|---|---|
MARKETING_VERSION |
2026.06.10, written to CFBundleShortVersionString
|
BUILD_NUMBER |
1430, written to CFBundleVersion
|
pkgbuild --version |
2026.06.10.1430 |
| Package file | build/BootstrapMate-2026.06.10.1430.pkg |
| Release tag | v2026.06.10.1430 |
The split exists so that macOS shows "2026.06.10 (1430)" in the About box rather than repeating
the full string. Because the marketing version has three components and older packages had
four, BundleIsVersionChecked is disabled so the installer does not refuse the upgrade on a
version comparison.
One caveat that matters when you are verifying which build is on a Mac: the version reported by
--version, by the log header and in the run summary is computed from the clock at the moment
the process starts, not from the build. It is always "now" and tells you nothing. The
authoritative version of an installed copy is the app bundle's:
defaults read /Applications/Utilities/BootstrapMate.app/Contents/Info CFBundleShortVersionString.github/workflows/ci.yml runs on pushes and pull requests to main. It resolves
dependencies, builds debug, runs swift test, builds the universal release binaries, and then
in a second job assembles a package root and runs pkgbuild unsigned as a smoke test. It
publishes no artifacts, and the package it builds is discarded.
.github/workflows/release.yml runs on workflow_dispatch and on any pushed tag matching
v*. It takes the version from the tag with the leading v removed, builds all three binaries
universal, compiles the icon, assembles the same app bundle and LaunchDaemon layout the
Makefile produces, and runs pkgbuild with relocation and version checking disabled. It
uploads a workflow artifact named BootstrapMate-<version>-pkg, and on a tag it creates a
GitHub release carrying the package.
The workflow deliberately performs no codesign, no productsign, no notarization and no
stapling. This public repository holds no Developer ID, so the released package is unsigned
and un-notarized; the release notes say so. Signing, notarizing and stapling happen in a
separate, private downstream pipeline that pulls the release asset. The Makefile is the local
signed-build tool and is not used by CI at all.
If you deploy from these releases, you must sign the package yourself before it will install under a Gatekeeper policy that requires it. See Deployment.