Cleaning up installs arrays from munkipkg payloads

Introduction

munkipkg is a handy way to manage the payloads and scripts for custom macOS packages. While you can use munkipkg-generated .pkgs via an MDM (any MDM that can install a .pkg) or even double-click installs, many people use these .pkgs with Munki.

One of the ways to tell Munki whether something is installed or not is to use an installs array. If the installs array is not configured correctly, though, it can result in an install loop. I’ll show you here how you can use sed to help avoid install loops for custom packages.

Why might a munkipkg custom package get an install loop?

Generally speaking, the custom packages you create with munkipkg will live somewhere on your computer. It could be a clone of a GitHub repo. It could be a mounted network drive. But the path to the payload in the custom package won’t likely match up to the path of the payload delivered by the custom package.

Let’s see what this looks like in practice.

First, let’s create the custom package:

munkipkg --create ~/Documents/GitHub/cool_custom_pkg
munkipkg: Created new package project at /Users/USERNAME/Documents/GitHub/cool_custom_pkg

Next, we’ll create the subdirectories for our example payload:

mkdir -p ~/Documents/GitHub/cool_custom_pkg/payload/Library/Application\ Support/Cool\ Custom\ Package

Then, we’ll create the example payload:

echo "Cool Custom Content" > ~/Documents/GitHub/cool_custom_pkg/payload/Library/Application\ Support/Cool\ Custom\ Package/cool_custom_settings.txt

Now, if we try to get the information for the Munki installs array (using makepkginfo), we’ll get the full local path to the payload:

makepkginfo -f ~/Documents/GitHub/cool_custom_pkg/payload/Library/Application\ Support/Cool\ Custom\ Package/cool_custom_settings.txt
<?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>_metadata</key>
    <dict>
        <key>created_by</key>
        <string>USERNAME</string>
        <key>creation_date</key>
        <date>2026-03-21T23:19:41Z</date>
        <key>munki_version</key>
        <string>6.6.5.4711</string>
        <key>os_version</key>
        <string>26.3.1</string>
    </dict>
    <key>autoremove</key>
    <false/>
    <key>catalogs</key>
    <array>
        <string>testing</string>
    </array>
    <key>installs</key>
    <array>
        <dict>
            <key>md5checksum</key>
            <string>b50f6ee0e4a3d9079bcd7dddcc991df9</string>
            <key>path</key>
            <string>/Users/USERNAME/Documents/GitHub/cool_custom_pkg/payload/Library/Application Support/Cool Custom Package/cool_custom_settings.txt</string>
            <key>type</key>
            <string>file</string>
        </dict>
    </array>
    <key>version</key>
    <string>1.0.0.0.0 (Please edit me!)</string>
</dict>
</plist>

If we were to use this installs array in the Munki item’s pkginfo, there’d be an install loop, because Munki would install the package payload to /Library/Application Support/Cool Custom Package/cool_custom_settings.txt but would be looking for the payload in /Users/USERNAME/Documents/GitHub/cool_custom_pkg/payload/Library/Application Support/Cool Custom Package/cool_custom_settings.txt, which obviously wouldn’t exist.

What’s the fix?

Well, obviously, you can just fix this manually, by deleting /Users/USERNAME/Documents/GitHub/cool_custom_pkg/payload from the pkginfo file after you paste in the installs array information.

But if you want to trim it in advance (say, if you have a script or even instructions in a README file), you can use sed to replace the offending bits.

makepkginfo -f ~/Documents/GitHub/cool_custom_pkg/payload/Library/Application\ Support/Cool\ Custom\ Package/cool_custom_settings.txt | sed 's#/Users/.*/cool_custom_pkg/payload##g'
<?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>_metadata</key>
    <dict>
        <key>created_by</key>
        <string>USERNAME</string>
        <key>creation_date</key>
        <date>2026-03-21T23:23:27Z</date>
        <key>munki_version</key>
        <string>6.6.5.4711</string>
        <key>os_version</key>
        <string>26.3.1</string>
    </dict>
    <key>autoremove</key>
    <false/>
    <key>catalogs</key>
    <array>
        <string>testing</string>
    </array>
    <key>installs</key>
    <array>
        <dict>
            <key>md5checksum</key>
            <string>b50f6ee0e4a3d9079bcd7dddcc991df9</string>
            <key>path</key>
            <string>/Library/Application Support/Cool Custom Package/cool_custom_settings.txt</string>
            <key>type</key>
            <string>file</string>
        </dict>
    </array>
    <key>version</key>
    <string>1.0.0.0.0 (Please edit me!)</string>
</dict>
</plist>

What is the sed command doing?

The | pipes the makepkginfo output to the sed command.

We’re then using s#THING TO REPLACE#THING TO REPLACE IT WITH#g. Since the thing we want to replace it with is nothing, the two last number signs are just next to each other.

Otherwise, we’re basically using a wildcard (that’s what the .* is for), so we don’t have to specify the full path (this can be helpful if you want to run the same command for different user accounts or even different paths to the custom package folder).


Posted

in

by

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *