Skip to content
Jaharmi edited this page Sep 21, 2011 · 16 revisions

History of the Luggage

Packagemaker.app makes it nearly impossible to have someone review the changes you’ve made in a package. They have to rummage through screen after screen, hoping that they remember the previous settings and that they don’t accidentally make an unwanted change. At Google, I wrote an internal tool using Make that allowed the other members of my group to easily review package changes – every package is generated by a Makefile. Since Makefiles are text, the diffs were easily presented in the internal code review tool. We made a lot of packages for use with puppet and InstaDMG using that tool, and after I left Google, I wanted to have a similar tool available, so I wrote the Luggage.

What the Luggage provides

Easy repackaging of GUI apps that don’t come with Apple pkg installers, custom scripts and configuration files, payload-less packages that do settings changes on target machines in their postflight/postupdate scripts. These packages are easily used in InstaDMG to create master disk images for deployment, or system management systems like puppet to distribute new software to managed Macintoshes.
How it works

You create a Makefile describing where you want each file in the package to end up on the target machines. luggage.make includes rules that will generate a package root with appropriate permissions, copy the files into place, create a package version-stamped with the date, then wrap the package in a disk image file.
So how do I use it?

Getting started

Check out the luggage from git. cd into the directory and do make bootstrap_files which will copy luggage.make and prototype.plist into /usr/local/share/luggage where luggage expects to find them. Now you’re ready to create your first package.

Requirements for all Makefiles

Every package makefile must include luggage.make, specify a title for the package, a base reversed domain for creating the package id, and the payload listing which files we want installed by the package. Include luggage.make before your variable declarations so your declarations override the internal ones in luggage.make. Please note that TITLE must not include spaces – it’s going to be used as part of the package id. Also remember to include luggage.make before any of your definitions so your definitions override the defaults.

include /usr/local/share/luggage/luggage.make
TITLE=install_foo
REVERSE_DOMAIN=com.example.corp
PAYLOAD=

Creating a package for a GUI Application

For our first example, we’re going to repackage a GUI application that didn’t come with a PKG installer. First, make a bzip2 compressed tar of the application (making sure to use Apple’s tar so resource forks don’t get mangled) with /usr/bin/tar cvjf Foo.app.tar.bz2 Foo.app, then update PAYLOAD with

PAYLOAD=unbz2-applications-foo.app

This tells luggage.make that we want Foo.app.tar.bz2 untarred into /Applications. If we want it installed in /Applications/Utilities instead, we’d use unbz2-utilities-foo.app instead, Now we can create a package with “make pkg”, or have it automatically wrapped in a disk image with “make dmg”. That’s all it takes.

Packaging scripts and configuration files

Similarly, if you want to install bar as /usr/local/bin/bar, add pack-usr-local-bin-bar to PAYLOAD.

Available PAYLOAD additions

rule name ownership permissions destination
pack-etc-foo root:wheel 644 /etc/foo
pack-usr-bin-foo root:wheel 755 /usr/bin/foo
pack-usr-sbin-foo root:wheel 755 /usr/sbin/foo
pack-usr-local-bin-foo root:wheel 755 /usr/local/bin/foo
pack-usr-local-sbin-foo root:wheel 755 /usr/local/sbin/foo
pack-hookscript-foo root:wheel 755 /etc/hooks/foo
pack-Library-LaunchAgents-foo root:wheel 644 /Library/LaunchAgents/foo
pack-Library-LaunchDaemons-foo root:wheel 644 /Library/LaunchDaemons/foo
pack-Library-Preferences-foo root:admin 644 /Library/Preferences/foo
pack-ppd-foo root:admin 644 /Library/Printers/PPDs/Contents/Resources/foo
pack-user-template-plist-foo root:wheel 644 /System/Library/User\ Template/English.lproj/Library/Preferences/foo
pack-var-root-Library-Preferences-foo root:wheel 600 /var/root/Library/Preferences/foo
unbz2-applications-foo root:admin based on tarball contents /Applications/Foo
ungz-applications-foo root:admin based on tarball contents /Applications/Foo
unbz2-utilities-foo root:admin based on tarball contents /Applications/Utilities/Foo
ungz-utilities-foo root:admin based on tarball contents /Applications/Utilities/Foo

Adding preflight/postflight/postinstall/postupdate scripts

Name the script preflight, postflight, postinstall, or postupdate, then add pack-script-XX to PAYLOAD and it will automatically be added to the final package.

Adding a file to the top level of a DMG

Adding bundle-foo.txt will add foo.txt to the top level of the dmg output when doing a make dmg. This will not affect the output of make pkg or make pkgls.

Make targets

  • make dmg – create a package, then wrap it in a dmg. Result will be in the current directory.
  • make zip – create a package, then wrap it in a zip. Result will be in the current directory.
  • make pkg – create a package and copy it into the current directory
  • make pkgls – create a package, then list the contents so you can confirm it’s generating a package with a payload that matches what you’re expecting. This does not leave a copy in your current directory.

Customizing your packages

How do I add a file to my package that is installed somewhere luggage.make doesn’t cover?

luggage.make defines several convenience targets that create various directory paths within the package root. You can use them to create the parent directory for your target location, then create your target location. Here’s an example that creates /etc/cups so you can install a custom cupsd.conf

l_cups: l_etc
    @sudo mkdir ${WORK_D}/etc/cups
    @sudo chown root:_lp ${WORK_D}/etc/cups
    @sudo chmod 755 ${WORK_D}/etc/cups
pack-cupsd.conf: l_cups
    @sudo ${CP} cupsd.conf ${WORK_D}/etc/cups
    @sudo chown root:_lp ${WORK_D}/etc/cups/cupsd.conf
    @sudo chmod 644 ${WORK_D}/etc/cups/cupsd.conf

Now all you need to do is add pack-cupsd.conf to your PAYLOAD variable, and make will create /etc/cups in your package root, change the permissions and ownership, then copy in your cupsd.conf.

I need to install foo with permissions different from the defaults luggage.make uses. How do I change the ownership/permissions for files installed using luggage.make?

You could make your changes using a postflight script, but for convenience, luggage.make runs make modify_packageroot after it has created the package root and copied the install files there, but before it invokes packagemaker’s command line tool to create the package.

If you add that target to your package’s Makefile, it will override the dummy one in luggage.make and let you change the permissions of files in your package root. ${WORK_D} contains the path to your package root. For example, if your package installed a modified cupsd.conf to /etc/cups, and you needed it to have a group of _lp, you would add the following to your package Makefile:

modify_packageroot:
    @sudo chgrp _lp ${WORK_D}/etc/cups/cupsd.conf

I don’t want the package version to be based on the current date. How can I force it to something specific?

By default, luggage.make sets the version number to YYYYMMDD. If you prefer to set it to something specific, set PACKAGE_VERSION=something_numeric.

FAQ

My makefile looks ok, but I get an error about a missing separator

You’ve probably indented your rules with spaces rather than a tab. Make requires the indentation be with tabs.

I just want to wrap an app, is there an easier way?

app2luggage.rb to the rescue! It takes care of the tedium of wrapping up a self-contained application — the kinds that typically rely on drag and drop for installation — in an installer package. It does the archiving, permissions-handling, escaping-spaces-in-application-names, Makefile creation, and packaging so that you don’t have to!

  1. [First time only] Make sure The Luggage is installed
  2. [First time only] Run sudo gem install trollop to install trollop, a Ruby gem, first if you don’t already have it installed; app2luggage is written in Ruby and requires this addition to function
  3. Install the GUI app you intend to package into /Applications
  4. Run sudo app2luggage.rb --application /Applications/Some\ Annoying\ App.app --package-id some_annoying_app --reverse-domain com.example

app2luggage.rb will create a new subdirectory, tar up the application for you, create a Makefile, then make a disk image (dmg) with an installer package (pkg) inside. The disk image output is the default.

If you would rather output an installer package (pkg) instead — for use with client management systems like Apple Remote Desktop, Munki, or others — and simply don’t need a disk image, turn on pkg output and turn off dmg output with:

sudo app2luggage.rb --application /Applications/Some\ Annoying\ App.app --package-id some_annoying_app --reverse-domain com.example --make-pkg --make-dmg

If you have problems with app2luggage, you can post to The Luggage Google Group (see below). It is helpful to have the script’s debug output available. To get that, add --debug 10 to your app2luggage.rb command. The friendly people on that list will probably ask for it.

I have other questions, who do I talk to?

Join the luggage’s google group http://groups.google.com/group/the-luggage