03.09.09

Sysadmin: Update OS X Leopard Automatically by Running Software Update

Posted in OS X, Programming, Sysadmin at 2:14 pm by cygnil

I’ve had occasion to spend quite a bit of time managing Macs in a lab environment (that is to say, lots of Macs used by nobody-knows-how-many users), and updating the OS efficiently across several machines has always been a problem.

Radmind is certainly one option, except that the issue of dynamic libraries makes it similarly time-intensive. That is, some updates will force dynamic libraries (.dylib) to be recompiled, and libraries compiled on one machine have sometimes broken when copied over to another machine (Quicktime is an especially frequent case of this). Knowing which libraries are okay to copy over and when it’s okay to do so requires a bit of voodoo and vigilance, which doesn’t make Radmind quite the best solution in this case.

It’s also not hard to schedule Apple’s softwareupdate command-line tool to run every so often. However, it’s fairly dumb: if run via launchd, it won’t tell you when an update recommends that you reboot. Even if the output gets piped out to a file somewhere, the computer has no way of acting on it.

Because of this, I’ve written a quick Perl script to be run periodically to check for updates, download them if present, and install them. If a user is currently logged in and the updates require rebooting the machine, the updates will be deferred until either the next time the update check is scheduled or, if the updates have been installed and a user logged in during the process, the next five-minute block of time that a user isn’t logged in.

To start with, save this script somewhere on your system; the rest of this article assumes it’s saved as /usr/local/bin/swupdate-weekly.pl:


#!/usr/bin/perl

# Apparently it took until Leopard to have a way to reliably detect
# whether a user was logged into the system. Be aware that this might
# not work as intended or at all under OS 10.4.

use strict;

my $verbose = $ARGV[0] eq "-v";
if ($verbose) {print "Checking list...n";}
my $check = `/usr/sbin/softwareupdate --list`;
if ($check =~ /No new software available/ ||
    ($check =~ /restart/ && `who` ne '')) { exit; }

if ($verbose) {print "Installing updates...n";}
`/usr/sbin/softwareupdate --install --all`;
if ($check =~ /restart/)
{
    while ( `who` ne '' ) { sleep 300; }
    if ($verbose) {print "Rebooting...n";}
    `/sbin/reboot -n`;
}

Now add this property list file to your system, preferably somewhere like /Library/LaunchDeamons/swupdate-weekly.plist:


<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">

<plist version="1.0">
    <dict>
        <key>Label</key>
        <string>swupdate-weekly</string>
        <key>ProgramArguments</key>
        <array>
            <string>/usr/bin/perl</string>
            <string>/usr/local/bin/swupdate-weekly.pl</string>
        </array>
        <key>LowPriorityIO</key>
        <true/>
        <key>Nice</key>
        <integer>1</integer>
        <key>startCalendarInterval</key>
        <dict>
            <key>Hour</key>
            <integer>5</integer>
            <key>Minute</key>
            <integer>0</integer>
            <key>Weekday</key>
            <integer>1</integer>
        </dict>
    </dict>
</plist>

Finally, set the .plist file to load when the machine starts up by using launchctl:

launchctl load -w /Library/LaunchDaemons/swupdate-weekly.plist

VoilĂ ! Your machines should update automatically now. One thing I’d like to do is add a clause to check whether there are any further updates before or after rebooting and handle those appropriately, but for the moment this has been getting us by.

Edit: Thanks to Mike O. for catching some errors. Should be fixed now.

Leave a Comment

You must be logged in to post a comment.