Building Your Own Installomator, Part 4: Building and Deploying Your Package

Building Your Own Installomator, Part 4: Building and Deploying Your Package

Building Your Own Installomator, Part 4: Building and Deploying Your Package

Forking, curating labels, and packaging an internal build

Part 4 of a 4-part series, final post

Part 3 covered pulling and curating labels. This last post covers turning that curated build into something you actually deploy: a release flow, getting the script into Jamf Pro, and packaging it with munkipkg.

A Practical Release Flow

A simple internal release process might look like this:

git switch production
git fetch upstream
git status

Pull in only the labels you want:

git checkout upstream/main -- fragments/labels/zoom.sh
git checkout upstream/main -- fragments/labels/slack.sh

 

Review:

git diff

Commit:

git add fragments/labels
git commit -m "Update selected production Installomator labels"

Build:

./assemble.sh --script

List labels in the built script:

./Installomator.sh | less

Tag your internal release:

git tag internal-2026-07-01
git push origin production --tags

Deploy Installomator.sh from that tag or build artifact.

GitHub Desktop Release Flow

If you prefer GitHub Desktop, keep the same release checkpoints, but let Desktop handle the branch, diff, commit, and tag work:

  1. Open your internal Installomator repo in GitHub Desktop.
  2. Switch to your production branch.
  3. Fetch from the remote.
  4. Bring in the selected label changes. For a curated build, this may mean copying the label files you want from an upstream-tracking branch, or using Terminal for the targeted checkout:
git checkout upstream/main -- fragments/labels/zoom.sh fragments/labels/slack.sh
  1. Review the changed files in GitHub Desktop’s diff view.
  2. Commit with a clear release-oriented message, such as:
Update selected production Installomator labels
  1. Choose Repository > Open in Terminal and build:
./assemble.sh --script
  1. Back in GitHub Desktop, create a tag for the reviewed internal build, then push the branch and tag.

GitHub Desktop does not replace the build and test steps. It gives you a friendlier review surface before you assemble and publish the internal artifact.

Deploying the Internal Build

There are two common ways to deploy your internal Installomator build:

  • Put the latest built script directly into Jamf Pro or your MDM script payload.
  • Install the script locally with a package, then call it from Jamf Pro or another MDM with a smaller wrapper script.

Both are valid. The right answer depends on what you are optimizing for:

  • Smaller curated label set: build from your curated production branch.
  • Latest upstream labels: sync from upstream, test, then deploy the latest built script or repackage it.
  • Custom label set: keep local labels in your branch or use --labels for overrides while testing.

If your internal build includes private labels, organization-specific download URLs, or local override logic, keep the source repo private and publish only the final script or package through your normal MDM process. Your admins still fetch public changes from upstream, but your internal distribution history stays inside your organization.

This split mirrors how the upstream project itself operates: code “releases” (merges to main) happen multiple times a week, while an actual signed .pkg gets built far less often. Option 1 below follows the fast cadence; Option 2 follows the slow one. There is no reason your fork has to pick just one — most teams end up doing both, for the same reasons upstream does.

Option 1: Use the Latest Built Script in Jamf Pro

If you run Installomator directly as a Jamf Pro script, your workflow is simple:

git switch production
git fetch upstream
git checkout upstream/main -- fragments/labels/zoom.sh
git commit -m "Update zoom label"
./assemble.sh --script

Then copy or upload the generated:

Installomator.sh

to Jamf Pro.

GitHub Desktop Version

With GitHub Desktop in the loop, the practical version is:

  1. Review and commit the label or fragment changes in GitHub Desktop.
  2. Open Terminal from the repository.
  3. Run:
./assemble.sh --script
  1. Confirm the generated root Installomator.sh changed as expected.
  2. Upload that built Installomator.sh to Jamf Pro as the script payload, or attach it to whatever MDM workflow you use for scripts.

The important boundary is the same as the command-line workflow: edit and review fragments, then deploy the assembled script.

This approach is useful when label changes are frequent. You do not need to rebuild an installer package every time a label changes. Your Jamf Pro script is simply the current approved build of your internal fork.

This is also the easiest approach when your main goal is to get the latest upstream label updates into production quickly. Pull from upstream, test the labels you care about, assemble the script, then update the Jamf Pro script payload.

A Jamf policy might call the uploaded script with parameters such as:

zoom NOTIFY=silent BLOCKING_PROCESS_ACTION=ignore DEBUG=0

Or, if you use a wrapper script:

#!/bin/bash

/usr/local/Installomator/Installomator.sh \
  zoom \
  NOTIFY=silent \
  BLOCKING_PROCESS_ACTION=ignore \
  DEBUG=0

This wrapper-script approach keeps Jamf policies small and readable, while the full Installomator logic lives locally on the Mac.

Option 2: Build an Installer Package Less Frequently

The installer package is usually built less frequently than labels change. That is fine. Think of the package as the delivery mechanism for the local Installomator tool, not necessarily the thing you rebuild for every label edit.

A reasonable pattern is:

  • Rebuild Installomator.sh whenever labels change.
  • Rebuild the local installer package only when you want to update the installed local script.
  • Use Jamf Pro (or other MDM) policies or wrapper scripts to call the locally installed script.

This works well for a stable production baseline. For example, you might repackage monthly, at the start of a semester, or after a batch of label updates has been reviewed. Between package rebuilds, you can still test labels from the repo using assemble.sh, or temporarily use the latest built script directly in Jamf Pro.

Regular packaged releases from the upstream project are not expected to resume on a predictable cadence any time soon, and label activity does not slow down while you wait. Building your own package with assemble.sh — trimmed to only the labels you actually use, if you want — is the approach the maintainers themselves point admins toward when this comes up.

The official assemble.sh can build a package with --pkg, but it ships with the maintainer’s own package identity hardcoded into the script’s settings block: a com.scriptingosx.Installomator identifier and a signing identity tied to the project author’s own Developer ID Installer certificate. Using --pkg as-is will try to sign with a certificate you don’t have and stamp the package with someone else’s reverse-DNS identifier, so you would need to edit those settings in your fork before it produces anything usable for an internal deployment. That is one of the reasons some teams prefer munkipkg instead: the package project is explicit, easy to version, and easy to keep in Git, with your own identifier and signing identity from the start.

Building an Installomator Package with munkipkg

munkipkg builds packages from a project folder. The basic idea is:

installomator-pkg/
|-- build-info.plist
|-- payload/
|   `-- usr/
|       `-- local/
|           `-- Installomator/
|               `-- Installomator.sh
|-- scripts/
`-- build/

The payload path controls where the file lands on the Mac. In this example, the script installs to:

/usr/local/Installomator/Installomator.sh

1. Install munkipkg

Install or clone munkipkg from the project repository:

cd ~/Developer
git clone https://github.com/munki/munki-pkg.git

Then either call the tool directly:

~/Developer/munki-pkg/munkipkg --help

or put it somewhere in your PATH, for example:

ln -s ~/Developer/munki-pkg/munkipkg /usr/local/bin/munkipkg

munkipkg uses Python 3. If /usr/bin/env python3 does not find Python on your build Mac, install or provide Python 3 first. The project README lists several options, including Munki’s bundled Python, python.org, Apple’s Command Line Tools prompt, Homebrew Python, or MacAdmins Python.

https://github.com/munki/munki-pkg

Using GitHub Desktop for this part is straightforward: clone or open the munki-pkg repository in Desktop if you want it tracked locally, but run munkipkg itself from Terminal. Desktop manages the Git history; the package builder is still a command-line tool.

2. Create a Package Project

From your internal Installomator fork:

mkdir -p packaging/installomator-pkg/payload/usr/local/Installomator
mkdir -p packaging/installomator-pkg/scripts

Create:

packaging/installomator-pkg/build-info.plist

Example:

<?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>distribution_style</key>
  <false/>
  <key>identifier</key>
  <string>edu.example.installomator</string>
  <key>install_location</key>
  <string>/</string>
  <key>name</key>
  <string>Installomator-Curated-${version}.pkg</string>
  <key>ownership</key>
  <string>recommended</string>
  <key>postinstall_action</key>
  <string>none</string>
  <key>suppress_bundle_relocation</key>
  <true/>
  <key>version</key>
  <string>2026.07.01</string>
</dict>
</plist>
 

Use your own reverse-DNS identifier and versioning scheme. Date-based versions work well for internal tooling:

2026.07.01

In GitHub Desktop, this package project will appear as a normal set of new files under packaging/. Review build-info.plist before committing so the identifier, package name, install location, and version are intentional.

3. Assemble Your Curated Script

From the root of your Installomator fork:

./assemble.sh --script

This creates or updates:

Installomator.sh

If you are working from GitHub Desktop, use Repository > Open in Terminal first. That opens Terminal at the correct repo path so ./assemble.sh --script writes the built script into the same working copy Desktop is showing.

4. Copy the Built Script into the munkipkg Payload

cp Installomator.sh packaging/installomator-pkg/payload/usr/local/Installomator/Installomator.sh
chmod 755 packaging/installomator-pkg/payload/usr/local/Installomator/Installomator.sh

After copying the built script into the payload, GitHub Desktop should show the payload copy as changed. That is a useful final review point before you build the package: confirm you are packaging the reviewed script, not an older local file.

5. Build the Package

munkipkg packaging/installomator-pkg

The output package will appear in:

packaging/installomator-pkg/build/

Install it on a test Mac:

sudo installer -pkg packaging/installomator-pkg/build/Installomator-Curated-2026.07.01.pkg -target /

Confirm the script is present:

ls -l /usr/local/Installomator/Installomator.sh
/usr/local/Installomator/Installomator.sh version

Then test one label:

sudo /usr/local/Installomator/Installomator.sh zoom DEBUG=1

Only switch to DEBUG=0 when you are ready to actually install:

sudo /usr/local/Installomator/Installomator.sh zoom DEBUG=0 NOTIFY=silent

6. Use Jamf Pro to Call the Local Script

Once the package is deployed, Jamf Pro policies can use small wrapper scripts.

Example wrapper:

#!/bin/bash

label="$4"

if [[ -z "$label" ]]; then
  echo "No Installomator label supplied in parameter 4."
  exit 1
fi

/usr/local/Installomator/Installomator.sh \
  "$label" \
  NOTIFY=silent \
  BLOCKING_PROCESS_ACTION=ignore \
  DEBUG=0

Then set Jamf parameter 4 to:

zoom

or:

googlechrome

This lets you update policy behavior without pasting the entire Installomator script into every policy.

7. When Should You Rebuild the Package?

Rebuild the package when:

  • You want to update the locally installed Installomator.sh.
  • You changed core script logic.
  • You changed production labels and want all Macs to carry the new local copy.
  • You changed your internal package metadata or install path.

You do not necessarily need to rebuild the package for every label test. During testing, use:

./assemble.sh labelname DEBUG=1

or deploy the latest script directly through Jamf Pro until you are ready to publish a new packaged build.

For many environments, the cleanest split is:

  • Frequent label iteration: use the latest built script in Jamf Pro or a test policy.
  • Stable production distribution: publish a signed/internal package less frequently.
  • Policy execution: use small Jamf Pro (or other MDM) wrapper scripts that call /usr/local/Installomator/Installomator.sh.

What Not to Do

Avoid editing the generated root Installomator.sh directly. It is tempting because it is the file you deploy, but it is not the maintainable source of truth.

Avoid auto-merging every upstream label into production. That defeats the purpose of a curated fork.

Avoid leaving local-only changes undocumented. Future you will not remember why a label is different from upstream, and future you deserves better.

Avoid testing only with DEBUG=0. Use dry/debug runs first, then install on test hardware.

Final Thoughts

Forking Installomator is not about splitting away from the community project. It is about putting a thin, controlled release process between upstream changes and your managed Macs.

The sweet spot, across this whole series, is:

  • Track upstream.
  • Curate labels deliberately.
  • Keep local changes in fragments.
  • Assemble repeatably.
  • Test before production.

That gives you the best of both worlds: the velocity of the Installomator community and the control expected in a managed Mac environment. If you landed on this post first, Part 1 covers why forking is worth the effort in the first place, plus a quick-start path if you just want a package today.

Sources

No Comments

Leave a Reply