Everbody loves cron. It’s the classic basic scheduler, 80/20 flexibility, gets the job done. cron has your back.

But in the maaagical world of OS X, I discovered there were these things called Launch Daemons. Theyre’s simply a custom launchd.plist, an XML file to define tasks in Apple’s terms. Sweet, I can do that …

The logrotated.plist Daemon

From what I could tell, out of the box, Leopard doesn’t give you logrotate. So, here it is as a Launch Daemon:

Great. So what do I do with it? Name it anything *.plist – ‘logrotated.plist’ perhaps – and put it here:

  • /System/Library/LaunchDaemons or
  • /Library/LaunchDaemons

I tend to use System for any services I wan to launch on boot (chkconfig-ish), and anything that’s just a scheduled task is more pedestrian.

You register daemons through launchctl. When you change it, just:

% launchctl unload FILE.plist
% launchctl load FILE.plist

Sure, not a hard thing. What I did want to point out were some of the things that I learned:

  • Provide each space-separated component of the command line as <string /> element in the ProgramArgument <array />. It’s the easiest way to go.
  • I tend to keep the KeepAlive, LaunchOnlyOnce and RunAtLoad all in sync for each daemon. It seems to guarantee compatability.
  • I still haven’t gotten quite the hand of OnDemand (c’mon, I’m new to this). When you are developing your daemons, keep a watch on /var/log/system.log – if you screw up, you’ll probably see it there in one fashion or another.
  • StartCalendarInterval is the equivalent of the cron schedule mask. See the Nabble post I commented above. You can’t do ranges, but wildcards are do-able sorta by omission. If you want mulltiple disparate schedules, as you see above, you need to pass in an <array /> of <dict />s, not just a single <dict /> when you only need one mask.
  • Yes, datatypes matter! I couldn’t figure out for the life of me why my daemon wouldn’t start, until I realized that my Hour and Minute values were configured as <string />s – vs <integer />s – because I’d been, well, you know, trying to do ranges and wildcards the cron-style way. launchctl didn’t complain, it just didn’t … work. So don’t do that.
  • Any daemon which has a schedule that fires off when the system is down will be automatically executed shortly after the system re-awakens. I don’t know exactly how quickly, but I’ve seen it in action (though I’ve read some dispute about its efficacy in my searches).

The Trials and Tribulations of MacPorts

But of course this Epistle wouldn’t be any fun without a twist, right? Well, lucky us, because it wasn’t that easy.

I’m using MacPorts as my yum – eg. my package manager. It’s great, and easy to set up, but it’s quite as stable as I’ve seen under Linux. Or rather, I’ve seen a disproportionate number of issues in the times I’ve used it. However, I’m eternally grateful because it saves me so much time … the successes far outweigh the problems.

When things do go bad, as they did when I built logrotate 3.7.7, you have to set up a custom Portfile and source repository so that you can effectively drive MacPorts to fetch and build your specific version. logrotate 3.7.1_1 turned out to be much more stable.

Techonova and Joe Homs go into very excellent and welcome detail in their posts on how to pull this off. A summary of what you need to do is:

  • watch the build failure – debug with port -d – to identify where the dying source is located. That’ll be PATH/TO/PROJECT
  • visit http://trac.macosforge.org/projects/macports/log/trunk/dports/PATH/TO/PROJECT` and snag the Portfile. That’s a MacPorts spec file, and you can tweak it to do your bidding.
  • create a source directory that you’ll keep around for a while (mine is /Users/Shared/macports).
  • register that directory – your local Portfile repository – with /opt/local/etc/macports/sources.conf (using file:// protocol)
  • copy the Portfile into a PATH/TO/PROJECT sub-directory structure (eg. sysutils/logrotate). Basically, the same path you’ll have snagged it from.
  • pull down a previous source revision and snapshot it locally. That could come from SVN or git, tarball, whatever. Start search from the homepage setting of the Portfile.
  • tweak the Portfile to ‘make sense’ for the source you’ve pulled down. If you based upon a close-enough version, it should be as easy as tweaking the version, etc. – no custom build tasks. I’m glossing over when I say that it’s usually a matter of version-based naming conventions and MD5 checksums.
  • re-build the repository’s PortIndex (which you’ll need to do every time you make an addition / change)

Please feel free to consult the other two posts to fill in the details that I was so cavalier about.

In Summary

This was another one of those occasions when I was glad I kept track of what I was doing while I was in the moment. Pack your config files with comments, because they’re invaluable. And drop READMEs around where you’re likely to find them … I filled in a lot of the extra details for this post from those breadcrumbs I left for myself, things I could have easily wasted 15-20m on re-discovering by braille :open_mouth: