In my last post I promised a tool I’ve been working on to automate the building of a package for crankd.

buildCrankPkg is a small script that will:

I’ve included two examples, one that implements calling Munki and Puppet as detailed in the last post, and one to run a Casper policy.

If you’re happy with what crankd does and using the command line, head on over to the repository and enjoy. If you need a bit more help to get started, read on.


First off, you’re going to need to get the buildCrankPkg repository.

cd ~/src
git clone

You’re left with three directories that you need to fill:

Assuming you cloned the buildCrankPkg repository to ~/src/buildCrankPkg, save the following as ~/src/buildCrankPkg/crankd/ (or copy the example). The only change between this one and the from last time is that we’re calling the JAMF binary to run a Casper policy (I know, the horror, I do actually use Casper occasionally). Our trigger’s name is NetworkTrigger - the line you’d need to customise to change this is 28.
#!/usr/bin/env python
# The OnNetworkLoad method is called from crankd on a network state change, all other
# methods assist it. Modified from Gary Larizza's script (
# Last Revised - 10/07/2013
__author__ = 'Graham Gilbert ('
__version__ = '0.2'
import syslog
import subprocess
from time import sleep
class CrankTools():
"""The main CrankTools class needed for our crankd config plist"""
def policyRun(self):
"""Checks for an active network connection and calls the jamf binary if it finds one.
If the network is NOT active, it logs an error and exits
Arguments: None
Returns: Nothing
command = ['jamf','policy','-trigger','NetworkTrigger']
if not self.LinkState('en1'):
elif not self.LinkState('en0'):
syslog.syslog(syslog.LOG_ALERT, "Internet Connection Not Found, Puppet Run Exiting...")
def callCmd(self, command):
"""Simple utility function that calls a command via subprocess
Arguments: command - A list of arguments for the command
Returns: Nothing
task = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
def LinkState(self, interface):
"""This utility returns the status of the passed interface.
interface - Either en0 or en1, the BSD interface name of a Network Adapter
status - The return code of the subprocess call
return["ipconfig", "getifaddr", interface])
def OnNetworkLoad(self, *args, **kwargs):
"""Called from crankd directly on a Network State Change. We sleep for 10 seconds to ensure that
an IP address has been cleared or attained, and then perform a Puppet run and a Munki run.
*args and **kwargs - Catchall arguments coming from crankd
Returns: Nothing
def main():
crank = CrankTools()
if __name__ == '__main__':

Now for the preferences - no change from last time here, as we’ve not changed the name of our class or method. This goes into ~/src/buildCrankPkg/Preferences/com.googlecode.pymacadmin.crankd.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "">
<plist version="1.0">

One last step until we can build our package is the Launch Daemon - we’re going to use the one that’s included in the repository, as 99% of people won’t need to change it.

Prepare the build!

Our package needs to have the version number of 2.1 and we’re going to set the package’s identifier to com.example.crankd

cd ~/src/buildCrankPkg
sudo ./ --version 2.1 --identifier com.example.crankd

Your package will be in ~/src/buildCrankPkg waiting for you.