Easy Script-Only Packages with the Luggage

Saw a post today on MacEnterprise from someone looking for an easy way of making a package that doesn't install any files, just runs a script during installation. Payloadless packages are quite easy to create with the Luggage - here's how:

1) Make a new directory. cd into it.
2) Write your script, call it postinstall.
3) Make a new file named Makefile with the following contents:

include /usr/local/share/luggage/luggage.make


4) make dmg.

make dmg will create a dmg file named TITLE-YYYYMMDD.dmg containing the package TITLE-YYYYMMDD.pkg.


Read knob files with facter for puppet

I found myself recently writing yet another fact to read the contents of a knob file, so I generalized one to read all the files found in /etc/knobs and return the contents as facts named after the files found.

Caveats: read_knobs.rb returns only the last line it finds in the file. This is on purpose. It also skips all lines beginning with # so you can use shell-style comments. Files that are empty create facts with the value true, and files containing just a t or f are normalized to true or false respectively.

Here's the source, and it's also available on github.

# fact returns knob values based on contents of /etc/knobs.
# Author: jpb@ooyala.com
# Copyright 2009 Ooyala, Inc.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# See the License for the specific language governing permissions and
# limitations under the License.
require 'facter'
def logger(message)
  system("/usr/bin/logger -t read_knobs #{message}")
# facts can only have one value. We ignore lines with shell style comments,
# and return the last valid line.
def read_knob(filename)
  knob_name = filename.split('/')[-1]
  knob_file = File.open(filename)
  # an empty knob file must have been created for a reason, so set default
  # value to true
  value = true
  knob_file.each { |line|
    if line[0,1] != "#"
      if (line.downcase.chomp == "true") or (line.downcase.chomp == "t")
        value = true
      elsif (line.downcase.chomp == "false") or (line.downcase.chomp == "f")
        value = false
        value = line.chomp
def load_knobs(knob_d)
  logger "Processing #{knob_d}..."
  if ! File.directory?(knob_d)
    logger("Can't read #{knob_d}!")
    return nil
  Dir["#{knob_d}/*"].each do |knob|
    if File.readable?(knob)
      knob_name = knob.split('/')[-1]
      Facter.add("#{knob_name}") do
        setcode do
          data = read_knob(knob)
      logger("Can't read #{knob}!")


Releasing some of my OS X administrative utility scripts

I decided to release some code I've been using at work to manage our Macintoshes.

All the code is downloadable at http://github.com/unixorn/osxtoolkit

Currently osxtoolkit includes:

  1. A puppet fact script to load the contents of files in /etc/knobs as facts named after the file name.

  2. scripts to use as login & logout hooks that run /etc/hooks/LI* on login, and /etc/hooks/LO* on logout. It uses iHook so the users can see progress.



Copy to the lib/facter directory in one of your modules.


The hook drivers only load scripts that are in /etc/hooks, have their x bit set, and have either an LI or LO prefix in their name. They're run in alphabetic order. LI* hooks are run on login, and LO* hooks are run on logout. You'll probably want to replace hook_background.jpg with something
appropriate for your local environment.



You need to download a copy of iHook from


since the hook driver scripts rely on it to present a pretty dialog with a progress bar during login & logout as they run the appropriate hook scripts.

Tar iHook.app up, then bzip2 the tarball. I recommend keeping the tarball (with version number in the name) on a central server, then letting make download it and rename it to iHook.app.tar.bz2 as part of the package build process. This will let you reproduce specific versions of the package later.

Luggage: http://http://github.com/unixorn/luggage

Luggage isn't necessary to run the hook loaders, just to make deploying them to your Macintoshes easier. You need to update TARBALL_LOCATION in the included Luggage Makefile to point to where you're storing your iHook tarball, then you can make a dmg with a pkg file containing everything necessary for the hook script suite with 'make dmg'. That dmg can then be easily deployed with InstaDMG or puppet.


Luggage 1.0rc2 released

Synced my latest changes to the Luggage. Detailed documentation here.

1.0rc2 includes
  • Fixed broken unarchive step in unbz2-applications & ungz-applications rules
  • Added recipe to install Ruby scripts to site-ruby
  • Added recipe to install Python scripts to site-python
  • Added more interesting example package - enable_login_hooks.
The enable_login_hooks example package also includes code to run all login hooks found in /etc/hooks on login, and logout hooks at logout.


(Updated to add link to github repo, documentation)

Creative Commons License

This work is licensed under a Creative Commons License.
Copyright 2007-2010, Joseph P. Block, Some Rights Reserved.

Creative Commons Logo