Apple Mac OS X launchd For The Complete Idiot
(on OS X Lion and above... a short tutorial)

[Update: May 1, 2015: Since writing this several years ago I have switched to the Lingon program. I leave this article up in case someone wants to configure launchd "by hand" which if you ask me, is a total PITA. I recommend Lingon! If you want to contact me use the contact form on our NewMedia Create site.]

I like cron. Cron works... it works great. But Apple has deprecated it (fancy word for saying it is no longer supported even though it still runs.) So who knows how long the venerable old cron will be around for those of us who have migrated to OS X from Linux.

I'm going to explain to you how to have a script run as root, because that is what I had to do. My bash shell script (below) goes out to my server and downloads a directory of files and then takes one and re-loads a mySQL database. I use XAMPP and it requires that starting and stopping the database be done as root... so my script has to run that way.

(Maybe it is better that ALL scripts be run as root... so as not to get permission or ownership issues. Anyway, this is about running as root... if you don't have to run as root, you can run as your own user by putting the plist file in your OWN ~/library/LaunchDaemons folder.... more on that later.)

Quick overview:

Now there are three entitles in using Apple's launchd (the "d" is for daemon.)

1. The first thing you will obviously have to do is have a script that does something that is important to you. Mine is below... I'l call it my-script.sh (sh for shell... just an old convention I use from Unix days.) The script has a file name. This is the "program name" you put in your .plist

2. You have to have a plist file. You should name it com.something-or-other.plist. I called mine: com.my-script.plist

3. There is a "label" name... coded in the plist... which is how launchd is going to refer to your plist. You can think of this as a 'job' name. I made mine the same as the plist file name. However, you enter it without the .plist suffix. I don't know why... ask the idiot engineers at Apple and let me know!)

4. You must save the .plist file in a directory.

5. You must use the "launchctl" command to load the .plist file.

Here is what you do.

- Write your bash script. It can reside anywhere but make sure you know where it is... full path... like /user/xxx/documents (where xxx is your user name.) Make sure the damn thing runs at the command line in Terminal by being in the directory that has the script... and entering ./my-script.sh (obviously your own script name.) Also make sure it runs as root by entering: sudo ./my-script.sh (You need the dot slash... that's a whole 'nother story... just do it.)

- Using a text editor (the free TextWranger is so much better than Apple's TextEdit) create your dot plist XML file. See mine below. There is no way I can teach you all the options in these files... there are lots of examples on the net. Basically you must have a Label, a Program, and an Interval or Calendar of when to run this thing. I just said you must have a Program tag? Well you don't. It is recommended that you use <key>ProgramArguments </key> instead. I don't know why. The first item after that is your script name. Oh... yeah you need the <array> tags as well. XML is a major PITA to me... but many folks like it.

Note that the Label does not use the .plist suffix. Don't ask... just accept this foolishness.

And yes, you need all the gibberish at the start of the file.

Notice that my plist says that this job is to be run at 6:01 PM and at 12:01 PM. There are a million ways you can code this and you will have to do you own research on this... again there are plenty of examples on the net on how to code these plist files for when to run jobs.

- You next have to get your script into the /Library/LaunchDeamons directory. There are a bunch of "library" directories... there is one for you (the user) and there is one under the System directory. DO NOT USE THE SYSTEM LIBRARY (/system/library) or you are asking for trouble. Use the (Macintosh HD) /Library/LaunchDeamons directory which you will find in Finder if you click your boot hard-drive icon. To run the script as root it has to get into the directory with root as the owner. They way I did it was to copy it in via the terminal as root (using sudo):

sudo cp my-script.plist /Library/LaunchDaemons/my-script.plist

(In Terminal, the directory (folder) names are NOT case sensitive. Use caps or don't use them. Your choice.)

UPDATE: If you don't need to run as root, you can put your .plist file in the (user) ~/Library/LaunchAgents directory and you don't need "sudo" to do it, you can create it there or drag/copy it in from some other folder. (Note: the "~/" notation is a bash-script shortcut for "/Users/[your user name]")

- You are almost there. The next thing you have to do is "load" the job... meaning you have to tell OS X that there is a new job it needs to check on every minute. The method for this is the "launchctl" command. Again, you need to do this as root in this example:

sudo launchctl load /library/launchdaemons/my-script.plist

If the command works you don't get any message. If not (i.e. you have some coding error) you will get a cryptic message like "Nothing to load." If you are new to plist coding as I am, you will have left out a closing-tag for <array> or <dict>.

That should do it. When the time comes, your script should run.

Making changes

If you want to make changes you need to "unload" the script:

sudo launchctl unload /library/launchdaemons/my-script.plist

You can then open the script from the /library/launchdaemons directory with TextWrangler. When you save it, it will ask for your password as it knows it is a "root-owned" file.

You then have to load the file again (see command above.)

I hope this helps. If you wish to contact me go to www.ancins.com, scroll down to the "Contact" link on the far right, and use that. I'll help if I can.



Al Canton, President
Adams-Blake Publishing
___________________________________________
Jaya123 is the web-based 'back office' solution
for small business. Order-entry, accounting,
billing, and more... all on the web for a few
dollars a month. Try the free demo at:
http://www.jaya123.com


This is saved as com.my-script.plist in /Library/LaunchDaemons

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.my-script</string>
<key>ProgramArguments</key>
<array>
<string>/Users/al/Documents/wget/my-script.sh</string>
</array>
<key>StartCalendarInterval</key>
<array>
<dict>
<key>Hour</key>
<integer>18</integer>
<key>Minute</key>
<integer>01</integer>
</dict>
<dict>
<key>Hour</key>
<integer>12</integer>
<key>Minute</key>
<integer>01</integer>
</dict>
</array>
</dict>
</plist>


This is my bash script saved as my-script.sh in my /documents/wget folder:

#!/bin/sh
cd /users/al/documents/wget
rsync -a -r -q -z -t --delete -e 'ssh -i /Users/al/.ssh/id_rsa' xxxx@xxxxxxx:/xxx/xxx/xxx/xxx/ backupsugar
chmod -R 777 backupsugar
rm /users/al/documents/wget/sugar.bak
gunzip --stdout backupsugar/backup_current_sugar/dbsugar*.bak.gz>sugar.bak
#
#
#=== XAMP
x=""
t=$(/Applications/XAMPP/xamppfiles/bin/mysqladmin status 2>/dev/null | /usr/bin/grep Uptime)
#
if [ "$t" = "$x" ] ; then
#echo 'space: not running'
run=0
else
#echo "mysql is running"
run=1
fi
if [ "$run" = 0 ] ; then
#echo "starting MySQL"
/Applications/XAMPP/xamppfiles/xampp startmysql >/dev/null
sleep 20
fi
#
/Applications/XAMPP/xamppfiles/bin/mysql -u root -h localhost sugar < /users/al/documents/wget/sugar.bak
sleep 20
#
if [ "$run" = 0 ] ; then
#echo "closing MySQL"
/Applications/XAMPP/xamppfiles/xampp stopmysql >/dev/null
fi


Update: May 17, 2012
Current URL for this page: http://answer123.com/misc/launchd_notes.html