Automate manifest distribution to masterless puppet clients from an S3 bucket

At Ooyala, I managed our Macintosh fleet with puppet. The problem we ran into is that we have users in remote locations rarely need to connect to the VPN, and when their machines aren't on the VPN or corp network, they can't connect to the puppetmaster and don't receive any updates. This made me switch to masterless puppet for our Macs so that they could pull their manifests (and other files) from S3, so the remote users could get updates when they weren't working from one of our offices and weren't on the corporate network or VPN.

When I was looking for examples of how to implement masterless, I didn't find tools for automating distribution of manifests to the nodes, so I wrote Miyamoto.  I got Ooyala to allow me to open-source it before I left, and the source is at https://github.com/unixorn/miyamoto

Miyamoto automates creating debs or OSX pkgs that contain all your manifest files and then publishing them to an S3 bucket so your client nodes can download and install them.

Here are a few caveats:
  • You lose the benefits of running a puppetmaster. No reporting, no dashboard, etc. While Miyamoto does write a json status file into the status subdirectory of your S3 bucket, you'll have to write something to scrape those files and convert them into something useful.
  • I ripped out all the company-specific stuff in a two day period between documenting things for my replacement and doing some other last minute tasks. I'm pretty sure everything still works, but if you run into issues, let me know and I'll try to help.
  • Currently Miyamoto only works with OS X and Debian based linux distributions. Redhat support should be trivial, but I didn't have time to work on that since Ooyala is an Ubuntu shop.
  • Miyamoto assumes you're using a single git repository for your manifests, and that there are specific branch names you're consistently using for stable & testing. If you're not a git user or are using multiple repos, or don't use the same branch names when you release a new stable environment, you'll need to tweak the Rakefiles.
  • Miyamoto also assumes you're willing to have a single set of AWS credentials that your nodes will use to download new manifests from a read only tree and write their statuses back to a specific directory in S3.


Creative Suite 5.5 refuses to accept a serial number to end trial mode

My wife recently upgraded to a MacBook Air, and when I went to install CS 5.5 on the MBA it accepted the serial number during installation but then would bring up the Adobe Application Manager screen when you started any CS application asking to either run in trial mode, or enter a serial number. And it wouldn't take the serial number that it had just accepted during installation. Yay!

So to spare anyone else the multiple chat sessions with Adobe support, with multiple escalations per session, here's how to fix it.

  1. Quit all your CS applications
  2. Open a terminal window.
  3. sudo -s
  4. cd '/Library/Application Support/Adobe/Adobe PCD'. You'll see a cache directory in there. Don't delete it like the tech support guy tells you to (Out of paranoia, I renamed mine instead), that'll trigger a message when you try to start a CS app telling you to reinstall CS.
  5. cd cache - you'll find a file named cache.db rename it to cache_broken.db.
  6. Open one of your CS applications. It'll ask you for the serial and this time, it'll accept it.


Weird Lion audio issue with headphones

My wife was having a weird audio problem on 10.7.2 - she'd be listening to iTunes, plug in her headphones, then get silence. Restarting iTunes would make the audio work on the headphones, but then if you unplug the headphones, silence until restarting it again. Same symptoms with watching YouTube videos in Safari.

Poked around a bit, finally found a corrupt preference file - com.apple.BezelServices.plist - deleted it, logged out and back in, and sound is working right again.


Automate removing Diginotar CA certificate

Ed Marczak posted how to delete the untrustworthy Diginotar CA cert on his blog. Read about why you want to trash it at ComputerWeekly.com.

To make it easier for you to do the right thing and delete it on the machines you manage, here's a quick how to on automating it.

If you're using puppet to manage your Macs, just add this exec to one of your manifests.

  exec { "Eliminate untrustworthy DigiNotar CA.":
    command => "/usr/bin/security delete-certificate -Z C060ED44CBD881BD0EF86C0BA287DDCF8167478C /System/Library/Keychains/SystemRootCertificates.keychain",
    onlyif => "/usr/bin/security find-certificate -c Diginotar  /System/Library/Keychains/SystemRootCertificates.keychain",

If you're using another system management tool, here's how to make a package with the luggage that will delete the Diginotar cert when you install the pkg.

Download the luggage from github.

Create a Makefile with the following contents:

#   Copyright 2011 Joe Block
#   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,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.

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


Create a postflight script with the following contents:

# Trash untrustworthy Diginotar root ca cert.
# See http://www.computerweekly.com/Articles/2011/08/30/247730/Microsoft-warns-of-fraudulent-digital-certificate-issued-by.htm
# and
# http://radiotope.com/content/remove-certificate
# for why.

/usr/bin/security find-certificate -c Diginotar  /System/Library/Keychains/SystemRootCertificates.keychain
if [ $? -eq 0 ];then
logger "Deleting Diginotar root cert from SystemRootCertificates.keychain"
/usr/bin/security delete-certificate -Z C060ED44CBD881BD0EF86C0BA287DDCF8167478C /System/Library/Keychains/SystemRootCertificates.keychain

The Makefile and postflight can be downloaded from the luggage-examples repo on github.

Drop the Makefile and postflight script into a new directory and sudo make pkg.

Now you have a pkg you can push with whatever system management software you're using for your Macintoshes.

Edit for clarity: This only deletes the Diginotar CA cert from the System keychain. It doesn't update Firefox/Opera/Chrome's caches, you'll need to update them separately.


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)


Losing ZFS

Luis Gerbarg has an interesting article with his thoughts about the reasons behind the absence of ZFS in 10.6 on his blog at http://devwhy.blogspot.com/2009/10/loss-of-zfs.html.

Here's hoping that he's right and Apple is already working on their own next generation filesystem - HFS+ is getting a bit long in the tooth, and it'd be nice to see an alternative in 10.7.


Released the Luggage

I've been rewriting a tool I originally wrote when I was at Google that allows you to generate an OS X pkg file using a Makefile without using PackageMaker.app's gui at all. At Google we used the internal tool to create most of the packages we used to create disk images with InstaDMG or with puppet. After I left, I missed it, so I'm releasing my rewrite, the Luggage, with the Apache 2.0 license. The source is at http://github.com/unixorn/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?

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

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, tar up the application (making sure to use Apple's tar so resource forks don't get mangled) with /usr/bin/tar cvj Foo.app.tar.bz Foo.app, then update PAYLOAD with

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 useunbz2-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 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
 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.

Make targets

  • make dmg - create a package, then wrap it in a dmg. 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.

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:
    @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.


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.

Creative Commons License

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

Creative Commons Logo