Every Mac admin has received crappy packages from software vendors. Sometimes they don’t follow Apple standards at all. This include stashing files inside the pkg and placing them with a perl script, having worthless receipts, etc.
It turns out that Luggage can save the day. Luggage is a Makefile-based tool that leverages PackageMaker’s CLI functionality. More info about Luggage here: https://github.com/unixorn/luggage . This writeup will only cover fixing bad apple packages. For more general information regarding Luggage, check out their github wiki page.
So how does one fix crappy installers? Using some nifty Luggage tricks of course!
One of the most common Apple hack-pkg methods I’ve come across is simply copying the files into place using a postflight and fixing the permissions in that very same script. Luckily, this gives us a cheat sheet for fixing the package because we know where the files need to be as well as what permissions they need to have.
Some general rules for fixing bad packages using Luggage:
1. Always fix the package in an automated way. The goal is to be able to drop the bad package into the directory with the Makefile and just run “make pkg” to create a fixed package.
2. Always use the built in Luggage tools and shortcuts whenever possible. Check /usr/local/share/luggage/luggage.make to see what is available
3. Split up your stanzas in the Makefile logically. Try not to throw everything into one stanza unless it makes sense to do so.
Labstats is a great example of the postflight-installed payload. By looking at the postflight, it gives up all the secrets that we need to know. In steps #3 and #4 it clearly lists which files get copied where and step #5 shows which permissions these files need:
#Step 3: Create necessary directories sudo mkdir "/Applications/LabStats" sudo mkdir "/Library/Application Support/LabStats" #Step 4: Copy files to correct location sudo cp -r "$1/Contents/Executable/" "/Applications/LabStats" sudo cp -r "$1/Contents/Bundles/" "/Applications/LabStats" sudo cp "$1/Contents/Launch Daemon/LabStats.plist" "/Library/LaunchDaemons" sudo cp "$1/Contents/Launch Agent/LabStats.plist" "/Library/LaunchAgents" sudo cp "$1/host.txt" "/Library/Application Support/LabStats" #Step 5: Set Permissions sudo chmod 644 /Library/LaunchAgents/LabStats.plist sudo chmod 644 /Library/LaunchDaemons/LabStats.plist sudo chmod -R 755 /Applications/LabStats sudo chmod 755 "/Library/Application Support/LabStats" sudo chmod -R 644 "/Library/Application Support/LabStats/*" sudo chmod +x /Applications/LabStats/updater.sh sudo chmod -R +x /Applications/LabStats/LabStatsUserSpace.app/
Lets translate that to what we need Luggage to do:
LABSTATS_PACKAGE_NAME="LabStats 5 Client.pkg"</pre>
prepare-files: l_Applications l_Library_Application_Support
# Created necessary directories and copies the files into them
@sudo mkdir -p ${WORK_D}/Applications/LabStats
@sudo mkdir -p "${WORK_D}/Library/Application Support/LabStats"
@sudo ${CP} -R ./${LABSTATS_PACKAGE_NAME}/Contents/Executable/ ${WORK_D}/Applications/LabStats/
@sudo ${CP} -R ./${LABSTATS_PACKAGE_NAME}/Contents/Bundles/ ${WORK_D}/Applications/LabStats/
pack-labstats: prepare-files l_Library_LaunchDaemons l_Library_LaunchAgents
# Fix permissions on the installed files
@sudo chmod -R 755 ${WORK_D}/Applications/LabStats
@sudo chmod +x ${WORK_D}/Applications/LabStats/updater.sh
@sudo chmod -R +x ${WORK_D}/Applications/LabStats/LabStatsUserSpace.app
# Installs LaunchDaemon and applies proper permissions
@sudo ${CP} "./"${LABSTATS_PACKAGE_NAME}"/Contents/Launch Daemon/LabStats.plist" ${WORK_D}/Library/LaunchDaemons
@sudo chmod 755 ${WORK_D}/Library/LaunchDaemons/LabStats.plist
# Installs LaunchAgent and applies proper Permissions
@sudo ${CP} "./"${LABSTATS_PACKAGE_NAME}"/Contents/Launch Agent/LabStats.plist" ${WORK_D}/Library/LaunchAgents
@sudo chmod 755 ${WORK_D}/Library/LaunchAgents/LabStats.plist
# Installs host.txt config file and corrects permissions on Application Support folder
@sudo ${CP} "./"${LABSTATS_PACKAGE_NAME}"/host.txt" "${WORK_D}/Library/Application Support/Labstats"
@sudo chmod -R 644 "${WORK_D}/Library/Application Support/LabStats"
@sudo chmod 755 "${WORK_D}/Library/Application Support/LabStats"
<pre>
With Luggage, ${WORK_D} represents a working directory where we stash all our files in the hierarchy that they would be found after being installed. For instance, if there is a file in ${WORK_D}/Library/Preferences/com.company.some.plist, when the package is built using Luggage, it will deliver a file named com.company.some.plist into /Library/Preferences/ on the target that you are install the package to.
If you step through this chunk of Makefile, you can see that I am doing exactly the same things the postflight is doing, but in a way that Luggage can work with it. Instead of copying files into position using a postflight, we are copying them into the ${WORK_D} so that Luggage can package them up into a proper Apple Package.
Now that we have the proper directory structure, the files are in place and we know the permissions are correct, the only thing left to do is take the other bits of the postflight from the crappy installer and place them in the proper location.
Here are rest of the steps from the postflight as provided by ComputerLabSolutions:
#!/bin/bash #Step 1: Kill any exising processes sudo killall mono sudo killall LabStatsClient #step 1.5 Stop any agents. launchctl unload /Library/LaunchDaemons/LabStats.plist #launchctl unload -D all /Library/LaunchAgents/LabStats.plist #launchctl unload /Library/LaunchAgents/LabStats.plist #Step 2: Delete any existing LabStats resources if [ -d "/Applications/LabStats" ]; then sudo rm -rf "/Applications/LabStats" fi if [ -d "/Library/StartupItems/CLSService" ]; then sudo rm -rf "/Library/StartupItems/CLSService" fi if [ -d "/Library/Application Support/LabStats" ]; then sudo rm -rf "/Library/Application Support/LabStats" fi if [ -d "/Library/StartupItems/CLSService" ]; then sudo rm -rf "/Library/StartupItems/CLSService" fi if [ "/Library/LaunchDaemons/LabStats.plist" ]; then sudo rm -rf "/Library/LaunchDaemons/LabStats.plist" fi if [ "/Library/LaunchAgents/LabStats.plist" ]; then sudo rm -rf "/Library/LaunchAgents/LabStats.plist" fi ... #Step 5: Reload agents launchctl load /Library/LaunchDaemons/LabStats.plist # RJS- Commented this out; it runs the UserSpace as root, which is odd # The other option is to find out a way to # launch this for each running user # launchctl load /Library/LaunchAgents/LabStats.plist
It becomes clear that Steps 1, 1.5 and 2 should be done before we install anything. I’ve cut and pasted those sections into a new file named ‘preinstall’ which sits in the same directory as the Makefile. Likewise, Step 5 shouldn’t be done before the install, so we will put it into a file named ‘postflight’ also in the same directory as the Makefile.
At the end of all this, we end up with an Apple Package that installs files using the proper mechanism as well as preinstall and postflight scripts that are being used in their proper capacity.
For the Labstats example, all this work is posted and ready to be used here: https://github.com/unixorn/luggage-examples . I would caution against blindly using this Makefile and expecting it to work, but you can clearly see how it is meant to function and tweak as is necessary.
Cisco AnyConnect is another example of an Apple Package gone wrong. You can see an example of how I handled repacking it here: https://github.com/unixorn/luggage-examples/tree/master/ciscoanyconnect
Special thanks to Gary Larizza and Allister Banks. They had great Makefiles for Puppet that helped me immensely with these repack Makefiles.
On Twitter