Sat - May 22, 2004

Web access to radmind data (updated)


It's useful to be able to quickly view radmind data via a webpage. Kris Steinhoff has written a radmind management module for Webmin. This module allows one to perform many common radmind management tasks via a webpage. We wrote a standalone CGI that gives us read-only access to radmind data. (With the help of Chris Buskirk, I've added some files the CGI depends on and updated the script to fix the viewing of check-ins in domains other than .com - you should be able to get this up and running very easily on a Mac OS X radmind/web server)

In our organization, only a few people are authorized to edit radmind data, but it was useful to provide quick read-only access so that a tech could verify what applications a user had assigned to him or her in his/her command file. We also wanted code that could do some sanity checking for us - if a command file was referenced in the config file - did that command file actually exist? If a hostname was referenced in the config file, was that hostname actually in DNS? Did the transcripts referenced in a command file actually exist? This data helps any tech quickly determine the source of many radmind errors.

Our CGI allows the user to view the config file, command files, and transcripts. These are all hyperlinked, so if you are viewing the config file, you can click on a command filename to view it. When viewing a command file, you can click on a transcript to view its contents. Any item that doesn't exist is flagged in red and not clickable, so you know there is a problem.

Additionally, the CGI knows about the /var/radmind/client/updated directory that is managed by the radmindCheckin cgi we use to report successful radmind runs. It can display the last time any given machine successfully ran radmind, sorting by hostname, command file name , or date.

Without further ado, here are the files needed to implement the CGI on Mac OS X (I'd include the CGI in the text of this post, but iBlog insists on rendering the HTML that is embedded in the script) :

radmind cgi.zip

And some screen shots:

Main menu: (note an additional option that is not in the script provided here. It's an option to view our Makefile, which we use to build our command files from "base" command files.)



View Config file:




View command file list:




View transcript list. The script also looks to see how many command files reference each transcript. This way you can figure out which transcripts may no longer be needed:




Viewing an individual transcript:




Viewing the Check-in report. You can click any column heading to resort the list by hostname, command file name, or date.

Posted at 11:41 PM     Read More  


Mon - May 10, 2004

Running radmind via cron (by way of periodic) and on-demand


Here are some more of the scripts I use to automate radmind...

Rather than editing crontab files, I put a script in /private/etc/periodic/daily, called 900.autoradmind:
900.autoradmind


This script checks to see the last time radmind ran; sees if there are any needed updates on the server (via ktcheck), and checks to see if anyone is logged in at the console.
If no-one is logged in, it runs a radmind session via a script at /usr/local/radmind/scripts/radmindNow
If someone is logged in and there are updates on the server, or it has been more than seven days since the last radmind session, it attempts to log the user out gracefully. If there are any unsaved documents, this will fail.
If it fails to run radmind and it's been more than seven days since the last run, it notifies the admin and alerts the user. I'll share this alert app at a later date (once I've made it easily customizable...)

When the 900.autoradmind script does run radmind, it calls this script:
radmindNow


#!/bin/sh

radmindTriggerFile="/var/radmind/client/.radmindOnLogout"
restartCheckFile="/usr/local/radmind/tmp/restartCheck.T"

touch $radmindTriggerFile
/usr/local/radmind/scripts/logoutHook

exit 0

Which as you can see, simply calls the logoutHook:
logoutHook


#!/bin/sh

radmindTriggerFile="/var/radmind/client/.radmindOnLogout"
restartCheckFile="/usr/local/radmind/tmp/restartCheck.T"

if [ -f $radmindTriggerFile ]; then
   rm $radmindTriggerFile
   rm -f $restartCheckFile
   /Applications/Utilities/radmind/iHook.app/Contents/MacOS/iHook --no-titlebar --script=/usr/local/radmind/scripts/run_radmind.pl
   if [ -s $restartCheckFile ]; then
      shutdown -r now
   fi
fi
exit 0


Which in turn calls iHook and my run_radmind script, which is detailed elsewhere.

This arrangement gives me a lot of flexibility. I can ssh in and run the run_radmind script if I want to see the output, but it's not important (or desirable) for the user at the machine to see the output. (Typically I'll do this if I know no-one is logged in and no-one is likely to log in.) Or, if I want iHook to come up and give the user some indication of what is going on, I can run the "radmindNow" script. Finally, since I am using a logoutHook script, I can let the user trigger a radmind session by running this app:
Update this Mac.zip




Which is just an AppleScript applet:
activate
set theMessage to "Warning: update this machine?" & return & return & "You will be logged out and software on this machine will be updated. This may take several minutes and may require a restart. Are you sure you want to continue?"
display dialog theMessage buttons {"No", "Yes"} default button "Yes" giving up after 15 with icon caution
if (button returned of result is equal to "Yes") then
       try
              do shell script "touch /var/radmind/client/.radmindOnLogout"
              ignoring application responses
                     tell application "loginwindow" to «event aevtrlgo»
              end ignoring
       on error theError
              display dialog "Error: " & theError giving up after 60
       end try
end if


If the user launches this application and clicks yes, they will be logged out and a radmind session will run.

Posted at 10:10 PM     Read More  

run_radmind script (updated)


Here is the run_radmind script I use. It's called on demand at logout, and as a periodic task nightly.

First the script itself:
run_radmind.pl


Now, some commentary (in blue):

#!/usr/bin/perl
################################################################################
# run_radmind
#
# This script runs radmind.
#
# Requires the radmind client tools (and a running server obviously).
# (See http://rsug.itd.umich.edu/software/radmind)
#
# Can also be used with iHook
# (See http://rsug.itd.umich.edu/software/ihook)
#

This script started out as a light edit of University of Utah's run_radmind script, and I've added my own tweaks and changes. Of particular coolness was UofU's execute_command subroutine, which allowed the output of the radmind tools to appear in iHook's drawer AND be captured to a file, which is invaluable for troubleshooting.

# Copyright (c) 2002 University of Utah Student Computing Labs.
# All Rights Reserved.
#
# Many, many modifications by Greg Neagle, Walt Disney Feature Animation
#
# Permission to use, copy, modify, and distribute this software and
# its documentation for any purpose and without fee is hereby granted,
# provided that the above copyright notice appears in all copies and
# that both that copyright notice and this permission notice appear
# in supporting documentation, and that the name of The University
# of Utah not be used in advertising or publicity pertaining to
# distribution of the software without specific, written prior
# permission. This software is supplied as is without expressed or
# implied warranties of any kind.
#
################################################################################
################################################################################
# change these to match your installation
$rserver = "-h radmind";
$cksum = "";
$fsdiffpath = "/";
$ktcheckoutput = "/usr/local/radmind/tmp/ktcheck_output.log";
$ktcheckoutput_backup_number = 5;
$fsdiffoutput = "/usr/local/radmind/tmp/fsdiff_output.log";
$fsdiffoutput_backup_number = 5;
$ignoreList = "/usr/local/radmind/etc/ignore";
$lapplyinput = "/usr/local/radmind/tmp/diff.T";
$lapplyoutput = "/usr/local/radmind/tmp/lapply_output.log";
$lapplyoutput_backup_number = 5;
$max_tries = 1;

This next bit of code lets me use the same script when it is run at logout and when it is run by cron overnight. In the first instance, I run without checksums so fsdiff is faster. In the second instance, I can turn checksums on and presumably no-one will be around to complain how long fsdiff takes.

# if the checksum trigger file exists, run with checksumming on
$checksumTriggerFile = "/var/radmind/client/.radmindWithChecksums";
if (-f $checksumTriggerFile) {
    $cksum = "-c sha1";
    unlink $checksumTriggerFile;
}

$radmind_error = "/usr/local/radmind/tmp/radmind_error";
$radmind_log = "/var/log/run_radmind.log";
$errorlog = "/usr/local/radmind/tmp/radmind_error.log";
$ktcheck = "/usr/local/bin/ktcheck -c sha1 $rserver 2> \"$errorlog\"";

The fsdiffCommand variable below takes advantage of a new fsdiff feature (-v) which gives percent-done feedback:

$fsdiffCommand = "/usr/local/bin/fsdiff -A $cksum -o $fsdiffoutput -v $fsdiffpath 2> \"$errorlog\"";
$lapply = "/usr/local/bin/lapply -F $cksum $rserver $lapplyinput 2> \"$errorlog\"";

$restartCheckFile = "/usr/local/radmind/tmp/restartCheck.T";
$rebootList = "/usr/local/radmind/etc/rebootList";
$rebootIgnoreList = "/usr/local/radmind/etc/rebootIgnoreList";

$updatesNeededFile = "/var/radmind/client/.updatesNeeded";

$extensionsCheckFile = "/usr/local/radmind/tmp/extensionsCheck.T";
$prefsPanesCheckFile = "/usr/local/radmind/tmp/prefsPanesCheck.T";

$ktcheck_pic = "/usr/local/radmind/images/ktcheck.tif";
$fsdiff_pic = "/usr/local/radmind/images/fsdiff.tif";
$lapply_pic = "/usr/local/radmind/images/lapply.tif";

I discovered that when using radmind to upgrade a machine from 10.2.x to 10.3, that sometimes it locked up during the lapply. Since (for me at least), upgrading from 10.2.x to 10.3 via radmind takes over an hour, and I have cron set to run tasks every 15 minutes, I thought perhaps something cron ran was crashing as libraries and code was swapped out from under it. So I added code in the run_radmind script to turn cron off. This seems to have improved the reliability of the upgrade from 10.2.x to 10.3.

################################################################################
# Stop cron so it doesn't call stuff we don't want to run during radmind
#
$cronMsg = `/System/Library/StartupItems/Cron/Cron stop`;
print "$cronMsg\n";

I can't really tell if this next bit is doing anything: I still see the output from fsdiff and lapply come in chunks with pauses in between, which looks like buffering to me...
################################################################################
# Turn buffering off
#
$|++;
$oldhandle = select( STDERR );
$|++;
select( $oldhandle );

print "%UIMODE AUTOCRATIC\n"; # iHook directive (This removes the menu bar in Panther - which appears even when no-one is logged in!)
print "%BACKGROUND $ktcheck_pic\n"; # iHook directive.
print "%BEGINPOLE\n"; # iHook directive.

Note the indeterminant progress bar, since there's really no way to tell how far along ktcheck is.
################################################################################
# Keep track of time, write to log
#
$rightnow = time; # keep track of time
system "echo \"--------------------------------\" >> $radmind_log";
system "date >> $radmind_log";
system "echo \"run_radmind started\" >> $radmind_log";

################################################################################
# Roll logs
#
&roll_log($ktcheckoutput, $ktcheckoutput_backup_number);
&roll_log($fsdiffoutput, $fsdiffoutput_backup_number);
&roll_log($lapplyoutput, $lapplyoutput_backup_number);

################################################################################
# prep stuff
chdir ("/");

# radmind loop
$current_try = 1;
while (1) {
    ################################################################################
    # run ktcheck
    #
    print "Task 1 of 3: Checking for updates on the server\n";
    $result = execute_command ("$ktcheck", $ktcheckoutput);
    if ($result == 0) {
        print "No updates from server\n";
    } elsif ($result == 1) {
        print "Updates found\n";
        system "/usr/bin/touch $updatesNeededFile";
    } else {
        print "ktcheck encountered a fatal error: $result\n";
        system "echo \"ktcheck encountered a fatal error: $result\" >> $radmind_log";
        &radmindFailed;
    }
    
    print "%1\n"; # iHook directive.
    ################################################################################
    # run fsdiff
    #
    print "Task 2 of 3: Examining the filesystem for differences\n";
    print "%BACKGROUND $fsdiff_pic\n"; # iHook directive.
    $result = system "$fsdiffCommand";
    if ($result != 0) {
        print "fsdiff encountered a fatal error: $result\n";
        system "echo \"fsdiff encountered a fatal error: $result\" >> $radmind_log";
        &radmindFailed;
    }
    
This next bit filters out any lines you don't want in the final lapply. I originally used it to filter out .DS_Store files. The problem came when later you needed to remove a directory. If that directory had any .DS_Store files within, they had been removed from lapply's transcript and therefore lapply would try to remove a directory that wasn't empty and it then failed. I planned to try to write some code to work around that issue, but never got around to it.

    ################################################################################
    # filter output from fsdiff
    #
    system "cat $fsdiffoutput | fgrep -v -f $ignoreList > $lapplyinput";

    print "%1\n"; # iHook directive.
We reset the progress bar to 1% (Setting it to 0% removes it from the window, which is visually jarring!

Now we run lapply.

    ################################################################################
    # run lapply
    #
    print "Task 3 of 3: Applying changes\n";
    print "%BACKGROUND $lapply_pic\n"; # iHook directive.
    $fsdsize = ( stat( $lapplyinput ))[ 7 ];
    if ( $fsdsize > 0 ) {

We get the number of lines in the applicable transcript so we can calculate (roughly) how far done we are. See the execute_lapply subroutine for more of this.

        $lapplyLineCount = int `wc -l $lapplyinput`;
        $result = execute_lapply ("$lapply", $lapplyoutput, $lapplyLineCount, "1");
        if ($result == 0) {
            &radmindSuccessful;
            last;
        } elsif ($result == 1) {
        if ($current_try >= $max_tries) {
            print "lapply failed too many times: $result\n"; 
            system "echo \"lapply failed too many times: $result\" >> $radmind_log";
            &radmindFailed;
            } else {
                print "lapply failed, trying again: $result\n";
                system "echo \"lapply failed, trying again: $result\" >> $radmind_log";
                next;
            }
        } else {
            print "lapply encountered a fatal error - see log for errors.\n";
            system "echo \"lapply failed: $result\" >> $radmind_log";
            &radmindFailed;
        }
    } else {
        print "No changes found!\n";
        system "echo \"No changes found\" >> $radmind_log";
        &radmindSuccessful;
        last;
    }
    $current_try++;
}

This is UofUtah's subroutine. I had never figured out pipe handles in Perl, so this was an education for me:
################################################################################
# This subroutine executes the radmind commands and saves the output to a log,
# prints it to stdout, and saves stderr to a log as well.
#
sub execute_command {
    local ($thecommand, $path_to_log) = @_;
    open (COMMAND, "$thecommand |");
    open (LOG, ">>$path_to_log");
    while (<COMMAND>) {
        print STDERR;
        print LOG;
    }
    close (LOG);
    close (COMMAND);
    return $? >> 8;
}

This subroutine, based on execute_command, has the changes needed to give percent-done feedback to the user:
################################################################################
# This subroutine executes lapply and saves the output to a log,
# prints it to stdout, and saves stderr to a log as well.
# It also provides percent-done feedback for use by iHook.
#
sub execute_lapply {
    local ($thecommand, $path_to_log, $fsdiff_line_count, $startPercent) = @_;
    open (COMMAND, "$thecommand |");
    open (LOG, ">>$path_to_log");
    $lapply_line_count = 0;
    while (<COMMAND>) {
        print STDERR;
        print LOG;

For each line that comes back from lapply, we increment our counter. Since we know how many lines are in the applicable transcript, and lapply returns one line of output for (basically) each line in the applicable transcript, we have a pretty good idea how far along we are.

        $lapply_line_count++;
        if ( int ($lapply_line_count/10) == ($lapply_line_count/10) ) (This way we only update every ten files or so)
        {
            $ratio = $lapply_line_count/$fsdiff_line_count;
            if ( $ratio > 1 ) { $ratio = 1; }
            $percentDone = $startPercent + int ($ratio*(99-$startPercent));
            print "%$percentDone\n";
Or you can get fancy and do this: print "%percentDone Updated item $lapply_line_count of $fsdiff_line_count\n";
        }
    }
    close (LOG);
    close (COMMAND);
    return $? >> 8;
}
################################################################################

# If radmind was successful, do these things
#
sub radmindSuccessful {
    print "Finishing up\n";
    #print "%99\n"; # iHook directive

    # How long did radmind take?
    $rightnowwer = time;
    $radmindtime = $rightnowwer - $rightnow;

    $message = "Radmind successful and took $radmindtime seconds";
    system "echo \"$message\" >> $radmind_log";
    print "%100\n"; # iHook directive

In Jaguar, we'd delete /System/Library/Extensions.kextcache and /System/Library/Extensions.mkext to get the OS to rebuild its extensions cache on restart. I've found that method to be unreliable with Panther. A method that seems to work is to touch the /System/Library/Extensions directory. So we grep the applicable transcript for "/System/Library/Extensions/", which would catch any changes to the directory. It's important to grep for "/System/Library/Extensions/", and not "/System/Library/Extensions" - since we are touching /System/Library/Extensions, on the next radmind run, fsdiff will notice that /System/Library/Extensions has changed, and will change it back. Then our script would see /System/Library/Extensions in the transcript, touch it, and so on...

    #do we need to update extensions cache?
    #were any kernel extensions added, removed, or changed?
    system "grep /System/Library/Extensions/ $lapplyinput > $extensionsCheckFile";
    #check the extensionsCheckFile filesize. If it's greater than zero, touch /System/Library/Extensions
    $extcfsize = ( stat( $extensionsCheckFile ))[ 7 ];
    if ( $extcfsize > 0 ) {
        system "/usr/bin/touch /System/Library/Extensions";
    }

We do something similar with Preference Panes. The OS keeps a cache of Pref Panes. When radmind adds or deletes a pane from either /System/Library/PreferencePanes or /Library/PreferencePanes we need to touch /System/Library/PreferencePanes to signal to the OS to update its cache. (Preference panes can also go in ~/Library/PreferencePanes. By grepping for "/Library/PreferencePanes/" we actually catch all possible locations, though in practice we don't manage user space)

    #do we need to invalidate Preferences Panes cache?
    #were any prefs panes added, removed, or changed?
    system "grep /Library/PreferencePanes/ $lapplyinput > $prefsPanesCheckFile";
    #check the prefsPanesCheckFile filesize. If it's greater than zero, touch /System/Library/PreferencePanes
    $ppcfsize = ( stat( $prefsPanesCheckFile ))[ 7 ];
    if ( $ppcfsize > 0 ) {
        system "/usr/bin/touch /System/Library/PreferencePanes";
    }

I don't like to restart after a radmind run unless it is needed. But how to tell if it's needed? I grep through the applicable transcript and look for items that trigger a restart. First we find all the items in the $rebootList, then eliminate the items in the $rebootIgnoreList.

Here's my current $rebootList:
/mach_kernel
/System/Library/
/Library/StartupItems/
/Library/Preferences/DirectoryService/
/Library/Application\bSupport/Norton\bSolutions\bSupport/

Here's my current $rebootIgnoreList
/Library/Application\bSupport/Norton\bSolutions\bSupport/Scheduler/SymSecondaryLaunch.app/Contents/MacOS/SymSecondaryLaunch
/Library/Application\bSupport/Norton\bSolutions\bSupport/Scheduler/schedLauncher
/Library/Preferences/DirectoryService/.DSRunningSP
/Library/StartupItems/NortonMissedTasks/NortonMissedTasks
/System/Library/CoreServices/.disk_label

The idea here is to reboot if anything in the rebootList has changed, unless it is in the rebootIgnoreList.
So if radmind just installs an application with all its files under /Applications, the machine will not reboot. But if stuff in /System/Library/ changes, it generally will reboot.

    #check to see if we need a restart
    system "fgrep -f $rebootList $lapplyinput | fgrep -v -f $rebootIgnoreList > $restartCheckFile";
    #check the restartCheckFile filesize. If it's greater than zero, restart
    $rscfsize = ( stat( $restartCheckFile ))[ 7 ];
    if ( $rscfsize > 0 ) {
        print "Items installed require a restart. Restarting now...\n";
        system "sleep 2";

Note that I don't actually restart in this script - instead I let the calling script restart. This allows me to run this script manually remotely via SSH and not get kicked off at the end.

        # we'll just fall through and exit normally and let the logout hook script
        # notice that the restartCheckFile exists and actually do the restart.
    } else {
        #if we are not restarting we should restart cron
        $cronmsg = `/System/Library/StartupItems/Cron/Cron start`;
        print "$cronmsg\n";
    }

Since we were successful, we can remove the trigger file that tells us updates were needed. If the update failed, the file would not be removed.

    if (-e $updatesNeededFile) { unlink $updatesNeededFile }

I use a simple CGI to report success to a webserver. I can then see when the last successful radmind was for a given host. You can find details about this CGI elsewhere on this website.

    #check in with the radmind check-in cgi
    system "curl http://radmind/cgi-bin/radmindCheckIn";
    exit 0;

}
################################################################################
# If radmind died, this is the cleanup subroutine.
#
sub radmindFailed {
    # print error log to stdout
    open (LOGGY, "<$errorlog");
    while (<LOGGY>) {
        print;
    }
    close (LOGGY);
    #system "touch $radmind_error";
    
    # How long did radmind take?
    $rightnowwer = time;
    $radmindtime = $rightnowwer - $rightnow;
    
    #
    $message = "Radmind failed and took $radmindtime seconds";
    system "echo \"$message\" >> $radmind_log";
    system "cat \"$errorlog\" >> $radmind_log";

We're not restarting, so we should start cron back up:

    # restart cron
    $cronmsg = `/System/Library/StartupItems/Cron/Cron start`;
    print "$cronmsg\n";

If radmind fails, I send mail to root - you may (probably will) want to send it elsewhere. Of course this means sendmail/postfix must be properly configured on the system.

    # notify the authorities
    $hostname = `hostname`;
    chomp $hostname;
    system "cat $errorlog | mail -s \"Radmind failed on $hostname.\" root";

exit 1;
}

Unchanged from UofUtah:
################################################################################
# This script renames $path_to_log so that the lowest number is always the
# newest. The oldest that is greater than $number_of_backups is deleted.
#
sub roll_log {
    local ($path_to_log, $number_of_backups) = @_;
    if (-e $path_to_log) {
        if (-e $path_to_log.".bak$number_of_backups") {
            unlink $path_to_log.".bak$number_of_backups";
        }
        for ($i = $number_of_backups ; $i > 1 ; $i-- ) {
            if (-f $path_to_log.".bak".($i-1)) {
                system "/bin/mv -f \"$path_to_log".".bak".($i-1)."\" \"$path_to_log".".bak$i\"";
            }
        }
        system "/bin/mv -f \"$path_to_log\" \"$path_to_log".".bak1\"";
    }


Here's the pictures referenced by the script, which because they are TIFFs with transparency, look awful on a web page, but quite nice in iHook:
images.zip





Posted at 09:22 PM     Read More  


Fri - May 7, 2004

Monitoring radmind (updated)


(Updated 5/7/04) Once you have set up some sort of automation for radmind, so that it runs automatically every night, for example, it becomes important to be able to monitor the machines you manage to see if they are running radmind regularly and to be notified if there are any problems.

The run_radmind script we use emails us if there is a failure. But what if the failure is that the script never runs at all? Do you want to assume that because you haven't been notified of a problem, that everything is fine? So we have the script also perform an action on success. Since the vast majority of our machines are successful, and a large chunk of them run radmind every night, we don' t want to be buried in emails telling us everything is OK.

So on success, our run_radmind script calls a web CGI like so (in Perl):

#check in with the radmind check-in cgi
system "curl http://radmind/cgi-bin/radmindCheckin";

and here's the CGI:

#!/usr/bin/perl

# Radmind checkin CGI
#
# this simple CGI touches a file in $UPDATE_DIR
# with the hostname of the the machine reporting in (This assumes
# your machine's hostnames are in DNS and reverse lookup works)
# otherwise it touches a file named the same as the IP address of
# the machine reporting in.
#
# Copyright 2003 Walt Disney Feature Animation
# Permission granted to use this code freely.
#
# this CGI can be called from a script like so:
# curl "http://your.webserver.tld/cgi-bin/radmindCheckin"
#
# Original Script by Greg Neagle, WDFA.
#
# 02/11/2004 Modification of the original code to support running on Panther Server
# - Lance Ogletree , Rice University
#
# 05/06/2004 Modification to deal with Apache sometimes returning the remote IP in
# $ENV{SERVER_ADDR} and sometimes in $ENV{HTTP_PC_REMOTE_ADDR},
# depending on the server configuration.
# This should make the script more portable.
# Thanks to Josh Burker, Mercer Island School District for helping debug
# this issue.
# - Greg Neagle, WDFA
#
# 05/06/2004 Added ability to pass in a custom hostname; this might make this script
# usable in environments where useful hostnames are not in DNS.
# You could then have the machine grab a name from a file or by using
# `scutil --get LocalHostName` or `scutil --get ComputerName`
# You'd then call the CGI like so:
# curl "http://your.webserver.tld/cgi-bin/radmindCheckIn?mycustomhostname
# - Greg Neagle, WDFA
#

print "Content-type: text/plain\n";
print "\n";

print "Radmind check-in\n";
print "\n";

$UPDATE_DIR = "/var/radmind/client/updated/";
# be sure the directory pointed to by $UPDATE_DIR exists
# and that it's writable by the web server

$hostname = $ENV{QUERY_STRING};

if ($hostname eq "") {
   #no custom hostname passed in, so let's generate one
   $IP = $ENV{HTTP_PC_REMOTE_ADDR};

   if ($IP eq "") {
      # depending on Apache config/environment
      # we need to use $ENV{REMOTE_ADDR} instead
      $IP = $ENV{REMOTE_ADDR};
   }

   $hostoutput = `host $IP`;
   unless ($hostoutput =~ /not found/) {
      # parse out the hostname
      @hostoutput = split /\s+/, $hostoutput;
      $hostname = pop @hostoutput;
      $hostname =~ s/\.$//;
   } else {
      # use the IP as the hostname
      $hostname = $IP;
   }
}

print "$hostname successfully checked in.\n";
$cmd = "/usr/bin/touch $UPDATE_DIR$hostname";
`$cmd`;


The CGI is very simple. All it does is take the IP address of the incoming connection, look up the DNS hostname of that IP address, and then "touches" a file with the name of the host in a directory we've created to hold these (We used "/var/radmind/client/updated/").
The "touch" command creates an empty file if one does not exist, or updates the modification date on the file if it already exists.
If there is no hostname associated with the IP address, it touches a file named after the IP address ("192.168.1.1").
Finally, you can pass the CGI a hostname - this could be useful if the machine has a file it can use to come up with a unique name (like a radmind certificate), or you've set the "Computer Name" or Rendezvous name. In this case, you'd call the CGI like so (again in Perl):

system "curl http://radmind/cgi-bin/radmindCheckin?customhostname";

You can then tell the last time a machine successfully ran radmind by looking at the modification date of the file with its name. We have another CGI which presents this info in a nice web page. Details in anther entry on this site.

Since I originally posted this script last December, Lance Ogletree contributed a modification that lets it run properly under Panther, Josh Berker surfaced an issue (and helped me debug it) with some configurations of Apache, and I added the custom hostname feature.

Enjoy.

Posted at 10:46 PM     Read More  


Sat - January 17, 2004

Using radmind to upgrade from 10.2.x to 10.3.x - UPDATE


One of the main reasons I started this blog was to communicate my adventures in using radmind to upgrade machines from 10.2.x to 10.3.x. It turned out not to be trivial, so I wanted to share my experiences so others could benefit.

It's far past time for an update on my progress.

So far I have successfully upgraded about 85 machines from 10.2.6 to 10.3.1 or 10.3.2 using radmind. I continue to upgrade a few each week. I will have moved most of our OS X boxes to Panther before the end of January.

The key findings:

1) Make sure your command files are good! If the update fails because of a problem with a command file you can end up with a non-bootable disk.

2) Make sure your run_radmind script turns off the cron daemon while it runs. See the run_radmind script posted elsewhere on this weblog for details. If cron tries to run a task during the update, you can get a segment fault or worse.

3) Make sure your command files are free of any pre-Panther stuff - in other words, don't have a 10.3.x base with an old Safari overload or a (10.2) Developer tools overload, etc, etc. I haven't seen any problems with including overloads of non-Apple applications created on top of Jaguar, but your mileage may vary.

4) Don't try to do this update using the Radmind Assistant or by manually running the radmind tools from a shell prompt. Do it as part of a logout hook script or a script run by cron when no-one is logged in, or a StartupItem. And then leave the machine alone while it updates. At Feature Animation, a radmind-ed update from 10.2.6 to 10.3.1 took about 60-90 minutes. I haven't timed a 10.2.6 to 10.3.2 update, which is what we are doing now, but I see no reason to believe the times wouldn't be similar.

5) Even if everything goes well, at the end of my run_radmind script, it attempts to restart but fails, usually with a segment fault. If you then just do a hard restart (via the restart button or power button) it comes up fine in Panther.

Posted at 05:38 PM     Read More  


Wed - November 12, 2003

Creating new System users for Panther and Postfix


If you use radmind to upgrade a machine from 10.2.x to 10.3, several system users will not be created (unless the NetInfo database is not in your negative transcript). Without these users, postfix (the new mail transfer agent - it replaces sendmail) will not work. Other services may also be affected. Here's how you can automate creating those needed users and groups.

This fix was made easier by poking around on the Panther Install disk 1, looking at the Install packages, and looking at the scripts buried in the Resources. There is one called CreateSystemUsers that served as the basis for this script. Again, I run this as part of a custom StartupItem.

##################################################################
# check for and create system users needed by 10.3 when 
# upgraded from 10.2
##################################################################
ConsoleMessage "Validating System Users"

# Add cyrus user
nicl . -read /users/cyrus >/dev/null 2>&1
if [ $? != 0 ] ; then
    nicl . -create /users/cyrus
    nicl . -createprop /users/cyrus uid 77
    nicl . -createprop /users/cyrus gid 6
    nicl . -createprop /users/cyrus passwd '*'
    nicl . -createprop /users/cyrus change 0
    nicl . -createprop /users/cyrus expire 0
    nicl . -createprop /users/cyrus realname 'Cyrus IMAP User'
    nicl . -createprop /users/cyrus home '/var/imap'
    nicl . -createprop /users/cyrus shell '/usr/bin/false'
    nicl . -createprop /users/cyrus _writers_passwd 'cyrus'
    echo "niutil: User 'eppc' added."
fi

# add the eppc user
nicl . -read /users/eppc >/dev/null 2>&1
if [ $? != 0 ] ; then
    nicl . -create /users/eppc
    nicl . -createprop /users/eppc uid 71
    nicl . -createprop /users/eppc gid 71
    nicl . -createprop /users/eppc passwd '*'
    nicl . -createprop /users/eppc change 0
    nicl . -createprop /users/eppc expire 0
    nicl . -createprop /users/eppc realname 'Apple Events User'
    nicl . -createprop /users/eppc home '/var/empty'
    nicl . -createprop /users/eppc shell '/usr/bin/false'
    nicl . -createprop /users/eppc _writers_passwd 'eppc'
    echo "niutil: User 'eppc' added."       
fi

# add the lp user
nicl . -read /users/lp >/dev/null 2>&1
if [ $? != 0 ] ; then
    nicl . -create /users/lp
    nicl . -createprop /users/lp uid 26
    nicl . -createprop /users/lp gid 26
    nicl . -createprop /users/lp passwd '*'
    nicl . -createprop /users/lp change 0
    nicl . -createprop /users/lp expire 0
    nicl . -createprop /users/lp realname 'Printing Services'
    nicl . -createprop /users/lp home '/var/spool/cups'
    nicl . -createprop /users/lp shell '/usr/bin/false'
    nicl . -createprop /users/lp _writers_passwd 'lp'
    echo "niutil: User 'lp' added."       
fi

# add the lp group
nicl . -read /groups/lp >/dev/null 2>&1
if [ $? != 0 ] ; then
    nicl . -create /groups/lp
    nicl . -createprop /groups/lp gid 26
    nicl . -createprop /groups/lp passwd '*'
    echo "niutil: Group 'lp' added."       
fi

# add the mailman user
nicl . -read /users/mailman >/dev/null 2>&1
if [ $? != 0 ] ; then
    nicl . -create /users/mailman
    nicl . -createprop /users/mailman uid 78
    nicl . -createprop /users/mailman gid 78
    nicl . -createprop /users/mailman passwd '*'
    nicl . -createprop /users/mailman change 0
    nicl . -createprop /users/mailman expire 0
    nicl . -createprop /users/mailman realname 'Mailman user'
    nicl . -createprop /users/mailman home '/var/empty'
    nicl . -createprop /users/mailman shell '/usr/bin/false'
    nicl . -createprop /users/mailman _writers_passwd 'mailman'
    echo "niutil: User 'mailman' added."       
fi

# add the mailman group
nicl . -read /groups/mailman >/dev/null 2>&1
if [ $? != 0 ] ; then
    nicl . -create /groups/mailman
    nicl . -createprop /groups/mailman gid 78
    nicl . -createprop /groups/mailman passwd '*'
    echo "niutil: Group 'mailman' added."       
fi

# add the postfix user
nicl . -read /users/postfix >/dev/null 2>&1
if [ $? != 0 ] ; then
    nicl . -create /users/postfix
    nicl . -createprop /users/postfix uid 27
    nicl . -createprop /users/postfix gid 27
    nicl . -createprop /users/postfix passwd '*'
    nicl . -createprop /users/postfix change 0
    nicl . -createprop /users/postfix expire 0
    nicl . -createprop /users/postfix realname 'Postfix User'
    nicl . -createprop /users/postfix home '/var/spool/postfix'
    nicl . -createprop /users/postfix shell '/usr/bin/false'
    nicl . -createprop /users/postfix _writers_passwd 'postfix'
    echo "niutil: User 'postfix' added."       
fi

# add the postfix group
nicl . -read /groups/postfix >/dev/null 2>&1
if [ $? != 0 ] ; then
    nicl . -create /groups/postfix
    nicl . -createprop /groups/postfix gid 27
    nicl . -createprop /groups/postfix passwd '*'
    echo "niutil: Group 'postfix' added."       
fi

# add the postdrop group
nicl . -read /groups/postdrop >/dev/null 2>&1
if [ $? != 0 ] ; then
    nicl . -create /groups/postdrop
    nicl . -createprop /groups/postdrop gid 28
    nicl . -createprop /groups/postdrop passwd '*'
    echo "niutil: Group 'postdrop' added."       
fi

This last bit is not strictly related to the creation of missing system users, but if you want Postfix to operate as a way for processes to send outgoing mail to an external mailserver, you'll need to make a change to the /etc/hostconfig file:

##################################################################
# turn on postfix-on-demand in the hostconfig file
##################################################################
if [ "${MAILSERVER:=-NO-}" = "-NO-" ]; then
    echo "Starting Postfix"
    cp /etc/hostconfig /etc/hostconfig.bak
    grep -v MAILSERVER /etc/hostconfig.bak > /etc/hostconfig
    echo "MAILSERVER=-AUTOMATIC-" >> /etc/hostconfig
    # start postfix to create the needed directories and pipes in /private/var/spool/postfix
    /usr/sbin/postfix start
    # now we can stop postfix
    /usr/sbin/postfix stop
    # finally start the mail queue watcher
    /usr/sbin/postfix-watch
fi

Posted at 10:25 PM     Read More  

Fixing empty SSH keys


If you put the ssh_host* keys into your negative transcript (and you should), make sure they are stored as empty files on the server, or you may end of with lots of machines sharing the same keys. On the other hand, you run a risk of empty ssh_host* keys being delivered to your machines, which prevents SSH from starting. Here's a fix.

Once again, I run this script as part of a custom StartupItem. It simply looks for zero-length ssh_host* keys and generates valid replacements.

##################################################################
# SSH
##################################################################

# fix empty ssh host keys
echo "Checking ssh host keys"
if [ -f /etc/ssh_host_key ]; then
    if [ ! -s /etc/ssh_host_key ]; then
        rm /etc/ssh_host_key
        echo "Generating ssh host RSA1 key..."
        ssh-keygen -t rsa1 -f /etc/ssh_host_key -N "" -C "$(hostname)"
    fi
fi
if [ -f /etc/ssh_host_rsa_key ]; then
    if [ ! -s /etc/ssh_host_rsa_key ]; then
        rm /etc/ssh_host_rsa_key
        echo "Generating ssh host RSA key..."
        ssh-keygen -t rsa -f /etc/ssh_host_rsa_key -N "" -C "$(hostname)"
    fi
fi
if [ -f /etc/ssh_host_dsa_key ]; then
    if [ ! -s /etc/ssh_host_dsa_key ]; then
        rm /etc/ssh_host_dsa_key ];
        echo "Generating ssh host DSA key..."
        ssh-keygen -t dsa -f /etc/ssh_host_dsa_key -N "" -C "$(hostname)"
    fi
fi

Posted at 10:17 PM     Read More  


Tue - November 11, 2003

Panther global config files changes


Apple has moved the location of some global preference files, notably the files that stored Locations, Network Configuration, and Power Management settings.

Previously, these were in /private/var/db/ and /private/var/db/SystemConfiguration/
Now they are in /Library/Preferences/SystemConfiguration/

This location is more in keeping with Apple's other global preferences, but if you weren't managing those files under 10.1 and 10.2, you'll need to add

d /Library/Preferences/SystemConfiguration 0755 0 80

to your negative file for Panther to keep from managing them now.

Posted at 07:55 AM     Read More  


Mon - November 10, 2003

Using radmind to upgrade from 10.2 to 10.3


Using radmind to upgrade a machine from 10.1.5 to 10.2.x worked well. It took a long time, but it worked. Unfortunately (at least in this context), Apple has made many more under-the-hood changes in Panther that make using radmind to upgrade a machine from Jaguar to Panther more difficult.

Here are some of the issues and challenges I've discovered so far:

• Since so much of the OS is changing out from under itself when radmind is upgrading a machine from 10.2 to 10.3, it impossible to completely automate the process. Even when everything goes perfectly, the machine hangs on reboot, requiring manual intervention.

• Apple's Panther installer does more than install files. It makes other configuration changes to the machine that may not be captured by radmind, depending on what you manage. For example, it makes changes to the NetInfo database - adding several new users and groups needed by processes such as Postfix (the new mail transfer agent, replacing sendmail). If you do not manage the NetInfo database, you'll need to create these users yourself. Fortunately, Apple has provided a script to do just that, and I'll later present my modified version of it.

• Another part of the Postfix transition is a change to the /etc/hostconfig file. Under 10.2 and earlier, it typically had an entry of MAILSERVER=-NO-. It needs to be changed to MAILSERVER=-AUTOMATIC- for Postfix to fire up when there is outgoing mail to be delivered, typically by system processes (I have my main radmind script mail me when there is a problem). Also, Apple's new Fax functionality makes use of Postfix to mail out received faxes. Again, a script can help you with this reconfiguration. I'll present it later.

...more to come.

Posted at 11:17 PM     Read More  


©