Apple Packages

February 25th, 2010 Leave a comment Go to comments

Introduction & Overview

The concept of software packages is a simple one. Bundle the necessary files for a program into a single file that can be read and understood by an installer tool which then copies the files to their proper locations and does any necessary steps to enable, automate, or otherwise perform some action concerning the new software using scripts of one form or another.

I am biased in favor of native format packages over third party distribution solutions, being a huge fan of both Apple’s .pkg and .mpkg systems and Debian’s .deb format. Each is structured differently but performs the function described above.

Building packages is a great way for an administrator to save time, especially for large software deployments, and it gives a trackable (package installs are logged and generally create some form of ‘receipt’, which details every file installed and  documents what scripts are run) installation, which makes auditing the software and filesystem on a machine, well, not necessarily easy but easier than trying to keep track of everything and do all the accounting by hand.

Benefits

Packages are the only form of complex distribution method supported by Apple’s Remote Desktop software. While this was enough of a reason for me, there are those who would require more of a reason to invest any amount of time into learning about packages and how they can help streamline any administration task. To many, the “Copy To…” and “Send to UNIX…” commands represent all they need to do their job, and I am not about to tell them that they are wrong.

Architecture

The Number One Thing to keep in mind when reading the next section is that any .pkg or .mpkg you create is just like any Application, Keynote document, Pages document, OmniGraffle Document, or anything else saved as a Bundle. Its nothing magical; it contains no executable binary, unless you really want to get technical and count any images or the main compressed archive in which everything is stored. It is a Bundle, which is a concept alien to Windows users, new Mac Users, and even some advanced Mac Geeks.

Bundles

Bundles are no more than a Folder with a special name and structure. Take any folder you have, and rename it by adding a “.pkg”, “.wdgt” or .”bundle”, go ahead and try it, I’ll wait. Cool, eh? You can even try opening it at this point, but since that renamed folder is missing several key pieces, the program probably won’t open it, or will throw an error. There is nothing special about that folder other than its name, rename it again and remove the .whatever and it’ll change back to a normal folder again. Finder knows that a folder that ends in .pkg is a Package, just like it knows that any File ending in .jpg is a JPEG Image. Since it knows the what, it also knows the who, at least in terms of what Application gets the honor of opening it. That, in very simple terms, is what constitutes a bundle, its just a folder, with a collection of files in specifically defined locations, named with a special extension.

.pkg and .mpkg bundles are opened by Installer.app, just like .jpg and .gif are both opened by Preview.app (by default). If you have ever went poking around the internals of OS X, you might have stumbled across Installer in /System/Library/Core Services/, which also houses many other useful apps that we all take for granted like Archive Utility.app, Dock.app, loginwindow.app, and Finder.app.

Installer.app merely reads the configuration files present, and if necessary presents the user with the installer interface. Its actually a little more complex than this, but nothing really relevant to our cause. Its a sidebar I may tack on later if I find a way to shoe horn it in For now, suffice it to say that Installer.app handles the GUI of package installs when one is needed, though there are other forces that can also read and interact with packages as needed (ex: command line tools).

Order of Operations

Packages are very sequential, and there is good reason for this. They must consistently execute the same way each time, on each system, with as little variation as possible. Any variation that *can* happen needs to be predicted and handled (user changing the install location, a prerequisite file or program not being found , etc.

Apple’s packages execute in this order:

  1. Check Scripts
  2. Pre-Execute Scripts (preflight)
  3. Pre-Install / Pre-Upgrade Scripts
  4. Decompress / Deploy Files (using the Archive.gz and/or .bom files)
  5. Post-Install / Post-Upgrade Scripts
  6. Post-Execute Scripts (postflight)
  7. Cleanup Scripts

On OS X, to give developers the most flexibility, Apple left the scripting language up to the deployer. I prefer Perl scripts, but Bash, Python, Applescript, Ruby, PHP and many many others will all work, and each have their strengths and pitfalls concerning this task.

Also of note is that “Requirements” can be set within the package that are outside the purview of the scripts. Often enough these built in requirements checks are enough, but where they are not, there is the combination of scripting and strings files (files containing keyed error messages) to step in and do any heavy lifting.

Check Script

InstallCheck

If you have ever run a package and then immediately gotten a prompt “This Package contains an application which will determine if the software can be installed on this computer” it was tripped by this script being present. It is basically something that runs before ANYTHING else is done, so you can do things like check OS versions, firmware, machine model, architecture, etc. If this script exits with a value OTHER than zero, it is considered a failure and the install is aborted. So basically you could say:

#!/usr/bin/perl

my $sysVersion = `sw_vers | grep 'ProductVersion' | awk '{ print $2 }'`; # Sets $sysVersion to the current system's OS (ex. 10.5.7)

print $sysVersion;
if ($sysVersion =~ m/10.5./)
{
      # Do Stuff
	print "Doing Stuffn";
} else {
	print "Failedn";
      exit 1;
}

That would basically check if you’re running Leopard, if not, the updater fails. A more complex regular expression could check for point releases, etc.

There are many other uses for this script, for a particular Office 2004 Update I was re-packaging for deployment, I needed to check that the previous update had been applied (thank you cascading requirements). Since I installed the updater log files with each package I published, I was able to do this:

if (-e "/Applications/Microsoft Office 2004/Updater Logs/11.3.7 Update Log.txt")
{
        exit 0;
} else {
        exit 112;
}

…And it worked beautifully. This is also a good example of using a Strings file.

InstallCheck.Strings

.Strings files are just short text files containing error codes and corresponding custom error messages to display. They are used for localization, as well as presenting a more informative error message to users. As in the above example, if the script fails, I exit with a status of 112. This is where Apple’s deployment guidelines get tricky, as this particular area is kind of cryptic. Different exit codes correspond to different lines in the string file but the relationship is not straightforward. See this Source for a more the official explanation.  The long version is that they want you to use a 7-bit (128 possible values) code for error messages, but you can only use 4 of those 7 bits (bits 5 and 6 are always 1 and bit 7 appears to always have to be 0) , giving you 31 possible messages.  They further say that you’re only to use 15 of them (messages 16-31), reserving the rest for their use (1-4 are pre-canned Apple responses).  So anyway, your 7-digit code for message 16 looks like:
0111000

Which is 112 in decimal.

Here’s the short version:

112 = 16
113 = 17
114 = 18
115 = 19

127 = 31

Example InstallCheck.strings file:

"16" = "This Update Cannot be Installed Until Microsoft Office 2004 11.3.7 is in
stalled";
"17" = "Please Close all Office Applications Before Continuing";

Why does this matter to a lowly sys admin building packages for ARD distribution? Because these strings get returned if the package errors when its being deployed. They show up in ARD, making it a quick glance if a machine fails “Oh its not been patched/software doesn’t exist/etc”. Non-cryptic error codes save you from having to chase down error messages in system logs and other slums when you could be getting on with your day!

Leopard

Leopard brought several changes to the Installer application, namely a new format that eschews the coveted Bundle format for an archive format called Xar (similar to Zip, Tar, Rar, etc).  It allows for more streamlined distribution (no more having to nest inside of a disk image (dmg))  If you bust open one of these new .pkg or .mpkg files (using command line tools or Apple’s GUI Tool), you’ll find some metadata files (plists) and if its a metapackage you’ll probably find a  few good old 10.4-era packages inside.  Extract these out and you’ll find them to be more or less the same .pkg bundles I have been explaining.  This may not always be the case, the new format has a similar structure, but some things have been moved around, and as developers and packagers stretch into the new areas the package format will evolve like everything else.

Sources

The Apple Software Delivery Guide was something that I studied heavily when getting into using Packages, and remains a valuable resource today. The section on Managed Installs deals specifically with Packages, their functions, and their inner workings, but the entire guide is a useful read for anybody working with Mac software from a SysAdmin perspective.

  1. No comments yet.
  1. No trackbacks yet.