8.5. Automating System Tasks

8.5.1. Configuring Local Administrative Permissions

One thing you may notice after working with Open Directory is that there is no good way to define local administrator access to desktops via an Open Directory group. You can add users to the Open Directory Admin group, but due to the way that the DS Search Path is traversed (which is explained in Chapter 2), OS X desktops will search the local admin group for many administrative checks. Unlike the Active Directory plug-in, the LDAPv3 plug-in does not provide the ability to specify a local administrative group mapping. So, you, the administrator, are left to your own devices to accomplish this task. Luckily, we have just what you need. The following script is also available via digital download (file 8_setNetworkAdminRights.sh).

#!/bin/bash

############################
##
## Local Permissioning script for setting local administrative groups
## for system and ARD admin access. Compatible with 10.5, and 10.6
## Uses dseditgroup to modify the active systems group membership. As
## Such, it can only operate on the active system volume.
##
## Written by Beau Hunter
## 04/04/09   [email protected]
##
########################################################

declare -x version=08310901
export PATH="/usr/bin:/bin:/usr/sbin:/sbin"

## vars
## localAdminGroup: specify network group to provide local admin access to
declare -x localAdminGroup="od_desktopadmins"

## setupLocalARDGroups: if '1', ard access groups
## will be created in local Directory Services
declare -x -i setupLocalARDGroups=1

## resetARD: if '1', ARD access will be configured
declare -x -i configARD=1

## resetARD: if '1', all ARD access privileges will be reset
declare -x -i resetARD=1

## ardAdminUser: the specified user will be have ARD admin access
declare -x ardAdminUser="hax_admin"

## ardAdminGroup: the specified group will be given ARD admin access
declare -x ardAdminGroup="mobo"

## ardInteractGroup: the specified group will be given ARD interact access
declare -x ardInteractGroup="monitors"


## static and system vars
declare -x scriptName=setNetworkAdminRights
declare -x theDate=$(date +'%Y%m%d')
declare -x -i isTiger="$(sw_vers | grep -c 10.4)"
declare -x -i isLeopard="$(sw_vers | grep -c 10.5)"
declare -x -i isSnowLeopard="$(sw_vers | grep -c 10.6)"
declare -x ARDVersion="$(defaults read 
/System/Library/CoreServices/RemoteManagement/ARDAgent.app/Contents/Info
CFBundleShortVersionString)" declare -x -i ARDMajorVersion="$(echo "$ARDVersion" | awk -F. '{print $1}')" declare -x -i ARDMinorVersion="$(echo "$ARDVersion" | awk -F. '{print $2}')" function getGUIDforGroup() { ## outputs a GUID for passed group name declare -x theGroupName="$1" declare -x GUID="$(/usr/bin/dscl /Search read /Groups/"$theGroupName" GeneratedUID |
awk '{print $2}')" if [ ! -z "$GUID" ]; then echo $GUID else logger -s -t "$scriptName" "Error! Could not determine GUID for group
"$theGroupName"" return 1 fi return 0 }

if [ "$USER" != root ]; then
    echo "Must run as root user, exiting!!"
    exit 1
fi


if [ ! -z "$localAdminGroup" ]; then
       GUID=$(getGUIDforGroup "$localAdminGroup")
    if [ $? == 0 ]; then
        logger -s -t "$scriptName" "Nesting Directory Group: $localAdminGroup into local 
Group: admin" /usr/sbin/dseditgroup -o edit -a "${localAdminGroup:?}" -t group admin else logger -s -t "$scriptName" "Error! Could not determine GUID for group
"$localAdminGroup"" fi fi if [ $configARD -eq 1 ]; then if [ $setupLocalARDGroups -eq 1 ]; then if [ "$isSnowLeopard" -ge 1 ]; then ardAdminLocalGroup="com.apple.local.ard_admin" ardInteractLocalGroup="com.apple.local.ard_interact" else ardAdminLocalGroup="ard_admin" ardInteractLocalGroup="ard_interact" fi else ardAdminLocalGroup=$ardAdminGroup ardInteractLocalGroup=$ardInteractGroup fi ## Process our Admin Group if [ ! -z "$ardAdminGroup" ]; then GUID=$(getGUIDforGroup "$ardAdminGroup") if [ $? == 0 ]; then if [ $setupLocalARDGroups -eq 1 ]; then logger -s -t "$scriptName" "Nesting Directory Group: $ardAdminGroup into
local Group: $ardAdminLocalGroup" /usr/bin/dscl . read /Groups/$ardAdminLocalGroup &> /dev/null ||
/usr/sbin/dseditgroup -o create -i 115 -g 2806364B-49F6-4F18-89F9-D159BB93B08C
$ardAdminLocalGroup /usr/sbin/dseditgroup -o edit -a "${ardAdminGroup:?}" -t group
$ardAdminLocalGroup fi else logger -s -t "$scriptName" "Error! Failed to create Local ARD Admin Group:
$ardAdminLocalGroup" errorCode=1 fi fi ## Process our Interact Group if [ ! -z "$ardInteractGroup" ]; then GUID=$(getGUIDforGroup "$ardInteractGroup") if [ $? == 0 ]; then

if [ $setupLocalARDGroups -eq 1 ]; then
                    logger -s -t "$scriptName" "Nesting Directory Group: 
$ardInteractGroup into local Group:$ardInteractLocalGroup" /usr/bin/dscl . read /Groups/"$ardInteractLocalGroup" &> /dev/null ||
/usr/sbin/dseditgroup -o create -i 116 -g 2806364B-49F6-4F18-89F9-D159BB93B08D
"$ardInteractLocalGroup" /usr/sbin/dseditgroup -o edit -a "$ardInteractGroup" -t group
"$ardInteractLocalGroup" else ardInteractLocalGroup=$ardInteractGroup fi else logger -s -t "$scriptName" "Error! Failed to create Local ARD Interact Group" errorCode=2 fi fi ## Process our kickstart commands kickstart="/System/Library/CoreServices/RemoteManagement/
ARDAgent.app/Contents/Resources/kickstart" if [ $resetARD -eq 1 ]; then logger -s -t "$scriptName" "Resetting ARD permissions" "$kickstart" -uninstall -settings "$kickstart" -configure -access -off fi if [ ! -z "$ardAdminUser" ]; then id "$ardAdminUser" &> /dev/null if [ $? == 0 ]; then logger -s -t "$scriptName" "Setting ARD access for user "$ardAdminUser"" "$kickstart" -configure -access -on -users "$ardAdminUser" -privs -all else logger -s -t "$scriptName" "Could not resolve user "$ardAdminUser"" errorCode=3 fi fi ## reset Directory Services and flush cache /usr/bin/dscacheutil -flushcache /usr/bin/killall DirectoryService sleep 2 id &> /dev/null if ( [ ! -z "$ardAdminGroup" ] && [ ! -z "$ardInteractGroup" ] ); then logger -s -t "$scriptName" "Setting ARD access for groups
$ardAdminLocalGroup,$ardInteractLocalGroup" elif [ ! -z "$ardAdminGroup" ]; then logger -s -t "$scriptName" "Setting ARD access for groups $ardAdminLocalGroup" elif [ ! -z "$ardInteractGroup" ]; then logger -s -t "$scriptName" "Setting ARD access for groups $ardInteractLocalGroup" fi if ( [ $ARDMajorVersion -eq 3 ] && [ $ARDMinorVersion -ge 3 ]); then logger -s -t "$scriptName" "Kickstart -configure -clientopts -setdirlogins -
dirlogins yes -restart -agent"

"$kickstart" -configure -clientopts -setdirlogins -dirlogins yes -restart -agent
    elif ( [ $ARDMajorVersion -eq 3 ] ) ; then
        logger -s -t "$scriptName" "Kickstart -configure -clientopts -setdirlogins -
dirlogins yes -setdirgroups -dirgroups $ardAdminLocalGroup,$ardInteractLocalGroup
-restart -agent" "$kickstart" -configure -clientopts -setdirlogins -dirlogins yes -setdirgroups -
dirgroups $ardAdminLocalGroup,$ardInteractLocalGroup -restart -agent else logger -s -t "$scriptName" "ARD Version: $ARDVersion not supported!" exit 5 fi fi

8.5.2. Allow Local Users to Manage Printers

You can also provide administrative access to a number of granular functions within Mac OS X by adding a user to the corresponding local group, rather than having a bunch of extraneous administrative users on your system. A great example is one of the ways to allow print queue management in Mac OS X. The Managed Client framework (MCX) has the ability to allow a user to add a printer. The following script was largely created to address an issue with older OS X 10.5-based machines where allowing users to modify printer lists via MCX was sometimes problematic. However, the script is useful to provide granular control to printing functions. Another way to allow a user to add printers and also let them manage queues is to add a user to the lpadmin group, (the group historically used for managing "line printers" that now refers to all printers). The lpadmin group provides capabilities for numerous printing functions, such as resuming print queues, which is not available to standard users. Printing in OS X is supplied via CUPS, which provides granular access to numerous functions. The following script, also available via digital download (file 8_setPrinterAdminRights.sh), adds a specified Open Directory group into the local _lpadmin group, thereby granting directory users lpadmin rights.

#!/bin/sh

PATH=/bin:/sbin:/usr/bin:/usr/sbin

## only members of the following group will be given printer admin rights
declare -x printAdminGroup="staff"

## modifies cupsd.conf to NOT require admin group membership to add printers,
## mainly needed for early versions of 10.5 where the equivalent MCX function
## was unstable.
declare -x modifyCupsdDotConf=false


###### script usage vars, should need to make changes  beyond this point. ######


declare -x theDate=`date +'%m%d%y'`
declare -x version="20090721_20:03"
declare -x scriptTag="setPrinterAdminRights"

logger -s -t "$scriptTag" "Executing $0 v.$version..."


### Add printer admin  ###

## Make sure an admin group was specified
if [ -z "$printAdminGroup" ]; then
       logger -s -t "$scriptTag" "ERROR: No print admin group specified, exiting!"
       exit 1
fi

## Add specified admin group to local lpadmin group
logger -s -t "$scriptTag" "Adding $printAdminGroup to lpadmin group."
dseditgroup -o edit -a "$printAdminGroup" -t group lpadmin
addMemberReturnCode=$?
if [ $addMemberReturnCode == 0 ]; then
       logger -s -t "$scriptTag" "Successfully added $printAdminGroup to lpadmin"
else
       logger -s -t "$scriptTag" "Failed to add $printAdminGroup to lpadmin, returnCode: 
$addMemberReturnCode" fi ## modify our cupsd.conf file if applicable, this gives lpadmin permissions to add/modify
printers if [ ${modifyCupsdDotConf:?} == "true" ]; then logger -s -t "$scriptTag" "Granting group lpadmin rights to add printers in
cupsd.conf!" perl −00pe 's/(<Limit CUPS-Add-Modify-Printer.*?)(AuthType.*)(Require user)(
@SYSTEM$)(.*?</Limit>)/$1$3 @SYSTEM @lpadmin$5/ms' -i /etc/cups/cupsd.conf else logger -s -t "$scriptTag" "cupsd.conf not being touched" killall cupsd fi

8.5.2.1. Home Folder Permission Maintenance

If you maintain a large number of home directories, you may want to periodically flush the filesystem structure on the system to guarantee proper access restrictions are in place. This can be useful, for instance, to protect files and folders that users add directly into the root of their home directory, often with global read access. Unwitting users can place sensitive data inside these folders, not realizing they are exposed to every use in the system. (User home folder structure is covered in depth in Chapter 7.)

The script listed in this section can be used to fix such permissions problems on home folders. The homeDirectories variable defines all root home folders on the machine in question and allows for a customizable depth. For instance, an institution might have two home folder sharepoints on an AFP server, say mapped to /studenthomes1 and /studenthomes2. Inside these folders, each home folder might contain a list of subdirectories denoting the graduation year of a student, each of which contain user home directories. On top of all this, you have the local /Users sharepoint, which we will add to the system as an example. To address these three home folders, we would specify the following homeDirectories value:

homeDirectories="/studenthomes1:1,/studenthomes2:1,/Users:0"

Using these values, the script will iterate through each of the specified folders, repairing home folders for each user.

You can also use this script to employ ACLs for administrative access, perhaps for a group of users—supervisors —who need read/write access to all User home folders. Alternatively, you might want to give your filesystemadmins group access to all data on the share. This is specified via the aclGroups variable, and allows you to indicate one of three access levels: fc (equivalent to a Full Control ACE), rw (equivalent to a Read/Write ACE), ro (equivalent to Read Only). (See Chapter 4 for more information on ACLs.) Our desired access rights would be accomplished with the following aclGroups entry:

declare -x aclGroups="filesystemadmins:fc,supervisors:rw"

The script also has a variable removeOrphans that, when set to true, will remove any file or folder found at the specified home folder depth that is not associated with an active user in the system. This check will fail if the name of the folder is not equivalent to an active user's shortname. This can be a very handy function if you have a large number of users to manage and want to ensure that former users' folders are cleaned from the system.

By setting these variables to the desired values in the following script (which is also available via digital download, file 8_cleanupHomeFolders), we can ensure that these groups have the appropriate access to all user home folders, and also that user data has complete confidentiality to the home folder's owner, outside of the ~/Public and ~/Sites directories. The script ensures that these folders have the appropriate access rights.

#!/bin/sh

##########  Home Directory Privilege Repair Script #####################
##  Written by Beau Hunter  08/22/08
##  [email protected]
##
##  Script which automates the management of home directory permissions
##  It's typical usage is to ensure proper permissions on every user's
##  home directory. That is, mode 700 to all home folders except ~/Public
##  and ~/Sites. Additionally, if useACLs is set to true, then ACE's will
##  be pushed to each home directory for its respective user.
##
##  On top of this, you can specify global admin groups via the aclGroups
##  variable, in addition to a permission set to apply to each group.
##
##  The tool can be used to cleanup stale home
##  folders for non-existent users by placing the homes in an orphanage
##  folder.
##
##########################################################################

PATH=/bin:/sbin:/usr/bin:/usr/sbin

## homeDirectories: Comma separated list of home roots, specify the
## depth via a colon. For instance, a standard
## OS X local home folder has user homes directly in
## /Users, thus I could specify a homeLoc of
## /Users:0. However, a depth of 0 is the default
## depth so it can be omitted.
declare -x homeDirectories="/testUsers:1"

declare -x repairPrivs=true
declare -x removeACLs=true
declare -x useACLs=true


## $aclGroups Groups sets an inherited ACL across $homeLoc, groups should be
## comma delimited. Access levels can be delimited with a colon,
## supported values are: "fc", "rw", and "ro". Default is rw.
## Example:
## aclGroups="admin:fc,powerusers:rw,rousers:ro"

declare -x aclGroups="admin:fc,staff:rw"
declare -x removeOrphans=true ## Remove non-user directories from the path.
declare -x orphanageName="orphanage" ## the name of the orphanage folder


#### int script vars, probably don't need to make changes beyond this point ####

declare -x date=`date +'%m%d%y'`
declare -x version="20080822_12:03"
declare -x scriptTag="$(basename "$0")"

logger -s -t "$scriptTag" "Executing script: $scriptTag v.$version"


function repairPrivs() {
       ## repair privileges on all items in a particular home folder
       ## expects home profiles based on users shortname.
       ## if the directory name is not resolvable as a user, we skip
       ## A directory path can be passed as a variable, otherwise
    ## executes based on PWD

    declare -x scriptTag="$scriptTag:repairPrivs()"


    if [ -n "$1" ]; then
        declare -x passedDirectory=$1
        if [ -d "$passedDirectory" ]; then
            cd "$passedDirectory"
        else
            logger -s -t "$scriptTag" "structureForOSX() passed directory: 
"$passedDirectory" does not exist!" return 1 fi fi

logger -s -t "$scriptTag" "Validating users in "$(pwd)" for privilege repair"

       IFS=$'
'
       for fileObject in `ls | grep -v .DS_Store | grep -v "$orphanageName" | egrep -v 
'^.'`; do #logger -s -t "$scriptTag" "Validating $fileObject for priviledge repair" id "$fileObject" &> /dev/null if [ $? == 0 ]; then #logger -s -t "$scriptTag" " - validation passed, changing
permissions for $fileObject at `pwd`/$fileObject" logger -s -t "$scriptTag" " Validation passed for $fileObject,
changing permissions" else logger -s -t "$scriptTag" " Validation failed for '$fileObject',
it is an orphan " ## get our pwd and get our current directory. We ## mimic our structure in the orphanage, this script ## needs more facilities to handle depth properly. declare -x PWD="$(pwd)" if [ "$homeDepth" == 0 ]; then declare -x orphanDir="$homeLoc/$orphanageName" else declare -x orphanDir="$homeLoc/$orphanageName/$(basename "$PWD")" fi if [ "$removeOrphans" == true ]; then logger -s -t "$scriptTag" " - Placing $fileObject in
orphanage:$orphanDir!" if [ ! -d "${orphanDir:?}" ]; then mkdir -p "${orphanDir:?}" if [ $? != 0 ]; then logger -s -t "$scriptTag" " - ERROR:
Could not create $orphanDir, not moving!" continue fi fi mv "$fileObject" ${orphanDir:?}/ if [ $? != 0 ]; then logger -s -t "$scriptTag" " - ERROR:
Could not move user home "$fileObject" to orphanage!" fi fi continue fi #echo chown -R "$fileObject":admin "$fileObject" chown -f -R "$fileObject":admin "$fileObject" if [ ${removeACLs:?} == "true" ]; then #logger -s -t "$scriptTag" " - removing ACL's"

chmod -f -R -N "$fileObject"
               fi

               ## Apply ACLs to the user dir, we do an explicit ACE at the user's home
               ## and then apply inherited ACLs to children.
               if [ ${useACLs:?} == "true" ]; then
                      logger -s -t "$scriptTag" "  - applying user ACL's"
                      chmod +a "$fileObject:allow:list,add_file,search,delete, 
add_subdirectory,delete_child,readattr,writeattr,readextattr,writeextattr,
readsecurity,writesecurity,chown,file_inherit,directory_inherit" "$fileObject" chmod -f -R +ai "$fileObject:allow:list,add_file,search,delete,
add_subdirectory,delete_child,readattr,writeattr,readextattr,writeextattr,
readsecurity,writesecurity,chown,file_inherit,directory_inherit" "$fileObject"/* fi chmod 755 "$fileObject" chmod -R 700 "$fileObject"/* if [ -d "$fileObject"/Sites ]; then chmod -R 775 "$fileObject"/Sites fi if [ -d "$fileObject"/Public ]; then chmod -R 775 "$fileObject"/Public chmod -R 773 "$fileObject"/Public/Drop Box fi done ## if we were passed a directory, traverse out of it if [ -n "$passedDirectory" ]; then cd "$OLDPWD" fi } ## end repairPrivs() function setACLForGroup() { ## passes $directory as first argument, $group as second argument, and $permissions ## this sets an explicit ACL at $directory, with all children receiving an
'inherited' ACL ## we accept several different permission types: ## "fc"(Full Control) ## "rw" (Read and Write) ## "ro" (Read Only) ## "append" (Append Only) declare -x directory=$1 declare -x group=$2 declare -x permissions=$3 declare -x scriptTag="$scriptTag:setACLForGroup()" logger -s -t "$scriptTag" "Attempting to apply: ACL to dir:$directory for group:
$group with perms:$permissions" ## sanity check our directory if [ ! -d "$directory" ]; then logger -s -t "$scriptTag" " - ERROR: Could not apply ACL.. dir:
$directory does not exist!"

return 1
       fi

       ## sanity check our group
       dscl /Search read /Groups/"$group" name &> /dev/null
       dsclCode=$?
       if [ $dsclCode != 0 ]; then
               logger -s -t "$scriptTag" " - ERROR: could not apply ACL.. group: 
$group does not exist! dscl code: $dsclCode" return 2 fi ## sanity check our permissions ##if ( [ "$permissions" != "fc" ] && [ "$permissions" != "rw" ]
&& [ "$permissions" != "ro" ] ); then ## logger -s -t "$scriptTag" "setACLForGroup() could not apply
ACL.. permissions:$permissions invalid, use 'fc'(Full Control), 'rw' (Read and Write),
'ro' (Read Only)!" ## return 3 ##fi ## deploy our ACL's case "$permissions" in fc) ace="allow:list,add_file,search,delete,
add_subdirectory,delete_child,readattr,writeattr,readextattr,writeextattr,
readsecurity,writesecurity,chown,file_inherit,directory_inherit";; rw) ace="allow:list,add_file,search,delete,
add_subdirectory,delete_child,readattr,writeattr,readextattr,writeextattr,
readsecurity,file_inherit,directory_inherit";; append) ace="allow:list,add_file,search,add_subdirectory,
readattr,writeattr,readextattr,writeextattr,readsecurity,file_inherit,
directory_inherit";; ro) ace="allow:list,search,readattr,readextattr,
readsecurity,file_inherit,directory_inherit";; *) logger -s -t "$scriptTag" "setACLForGroup() could not
apply ACL.. permissions:$permissions invalid!! defaulting to 'ro' (Read Only)!" ace="allow:list,search,readattr,readextattr,
readsecurity,file_inherit,directory_inherit" permissions="ro" ;; esac logger -s -t "$scriptTag" " - applying ACL to dir:$directory for group:
$group with perms:$permissions" /bin/chmod +a "$group:$ace" "$directory" chmodCode1=$? if [ $? != 0 ]; then logger -s -t "$scriptTag" " - Failed applying ACL to
top level of dir:$directory code:$chmodCode1... exiting!" return $chmodCode1 fi /bin/chmod -f -R +ai "$group:$ace" "$directory"/*

chmodCode2=$?
       if [ $? != 0 ]; then
               logger -s -t "$scriptTag" "  - Failed applying ACL to dir: 
$directory code:$chmodCode2" return $chmodCode2 fi return 0 } ## end setACLForGroup() ######### START ############# ############################# ## Iterate through all of our specified homeDirectories. OLDIFS=$IFS IFS=',' for homeEntry in $homeDirectories; do ## check to ensure we have a good homeLoc homeLoc=$(echo $homeEntry | awk -F: '{print$1}') homeDepth=$(echo $homeEntry | awk -F: '/[0-9]/ {print$2}') if [ -z "$homeDepth" ]; then homeDepth=0 fi if [ -d "${homeLoc:?}" ]; then cd "$homeLoc" else logger -s -t "$scriptTag" "Fatal error, $homeLoc is not a directory" errorOccured=true fi if [ $homeDepth == 0 ]; then if [ "$restructureHomes" == "true" ]; then logger -s -t "$scriptTag" "Restructuring home folders for $homeLoc" structureForOSX fi if [ "$repairPrivs" == "true" ]; then logger -s -t "$scriptTag" "Reparing Privileges for $homeLoc" repairPrivs fi else IFS=$OLDIFS for homeDir in `ls | grep -v "$orphanageName" | grep -v "Shared" |
egrep -v "^."`; do if [ -d "${homeLoc:?}/$homeDir" ]; then cd "$homeLoc/$homeDir" else continue fi if [ "$repairPrivs" == "true" ]; then logger -s -t "$scriptTag" "Reparing Privileges for $homeLoc/$homeDir" repairPrivs fi

cd ..
        done
    fi

    ## Deploy our aclGroups to the root of the home directory
    if [ ! -z "$aclGroups" ]; then
        IFS=$','
        for group in $aclGroups; do
            groupName=`printf "$group" | awk -F: '{print$1}'`
            groupRights=`printf "$group" | awk -F: '{print$2}'`
            setACLForGroup "$homeLoc" "$groupName" "$groupRights"
        done
    fi
done

8.5.2.2. Enabling the Software Firewall

The next script enables the Application Firewall in Mac OS X, which should generally be done in all mass deployments where security is even a minimal concern. The script ends with exit 0, which you may have noticed in previous scripts as well. The script brings in positional parameters from PackageMaker, setting them as variables (discussed in detail in Chapter 6). Then the paths for commands used in the script are declared, with more lines in the script dedicated to declaring variables than to the payload, a common occurrence.

#!/bin/bash
declare -x DSTROOT="$3"              # Installation Volume of mount point.
declare -x SYSROOT="$4"              # The root directory for the system.

declare -x PLIST="${DSTROOT}/Library/Preferences/com.apple.alf.plist"

declare -x defaults="/usr/bin/defaults"
declare -x plutil="/usr/bin/plutil"
declare -x chmod="/bin/chmod"
declare -x mv="/bin/mv"

"$defaults" write "${PLIST%.plist}" 'globalstate' -int 1 &&
echo "Plist Edited: ${PLIST}"

if $plutil "${PLIST:?}" >/dev/null ; then
        echo "Plist written successfully"
       $plutil -convert 'binary1' "${PLIST:?}"
       # Not needed , just for good measure
       $chmod +r "${PLIST:?}"
else
       "$mv" "${PLIST:?}" "${PLIST:?}.bad"
fi
exit 0

Furthermore, we can build on the logic just introduced. The following script will loop through all of the local users on a system and alter the umask variable for each. Each section is documented accordingly; note the beginning, where variables from the positional parameters are mapped into paths for packages, mount points, and the system root. Having a custom system root allows the script to be run against a non-booted drive, as would be common with InstaDMG style workflows.

#!/bin/bash
# Standard Package Install Postional Parameters $1 $3 $4
declare -x PKGBUNDLE="$1"       #    Full path to the install package.
declare -x DSTROOT="$3"         #    Installation Volume of mount point.
declare -x SYSROOT="$4"         #    The root directory for the system.

# Command short hand
declare -x awk="/usr/bin/awk"
declare -x chown="/usr/sbin/chown"
declare -x chmod="/bin/chmod"
declare -x basename="/usr/bin/basename"
declare -x dirname="/usr/bin/dirname"
declare -x id="/usr/bin/id"
declare -x ls="/bin/ls"
declare -x plutil="/usr/bin/plutil"
declare -x sudo="/usr/bin/sudo"
declare -x whoami="/usr/bin/whoami"

# Run time varibles
declare -x SCRIPT="${0##*/}" ; SCRIPTNAME="${SCRIPT%%.*}"
declare -x USER_TEMPLATE="$DSTROOT/System/Library/User Template/English.lproj"
declare -x FINDER_PREFS="$DSROOT/Library/Preferences/com.apple.finder.plist"

# User customized values, also use a file in the same directory <script>.conf
declare -ix UMASK=2
declare -x HOME_PATH="/Users"
# You could change this if you have an external Volume hosting homes
source "${PKGBUNDLE:?}/Contents/Resources/${SCRIPTNAME:-"$SCRIPT_NAME"}.conf"

#       As root is not covered in /Users/* set it here
if [ "$DSTROOT" = '/' ] ; then       #       If Installing on the startup disk
       echo "Setting umask for current user $($whoami):$UMASK"
       $defaults -g 'NSUmask' -int ${UMASK:?}
       #       -g means .GlobalPreferences.plist for the current user
fi

#      This sets the Finder umask, which is not done in umask Doctor AFAIK
echo "Setting Global umask for the Finder: $FINDER_PREF to $UMASK"
$defaults write ${FINDER_PREFS%.plist} 'umask' -int ${UMASK:?}

#       Loop through the homedirectorys in <Destination Volume>/Users/*
loopThroughHomes(){
OLD_IFS="$IFS" IFS=$'
'
#       Reset the Field Sep to spaces don't hose us.
for USERHOME in "${DSTROOT}${HOME_PATH:-"/Users"}"/* ; do
# Start looping through the path on the destination Volume,defaults to /Users
       test -d "$USERHOME" || continue
                #       Skip anything thats not a directory
       test -d "$USERHOME/Library" || continue
                #       If the loop folder is missing a Library skip it
                #       This will skip Filevault, Shared, Deleted Users etc.

#       Setup the loop variables
declare USER_NAME="$($basename "$USERHOME")"
       #       Pull the username from /Users/<username>
declare USER_PREF="$USERHOME/Library/Preferences/.GlobalPreferences.plist"
       #       The users Dot Global Preferences file
declare -i NSUMASK=$($defaults read "$USER_PREF" 'NSUmask' 2>/dev/null)

       test ${NSUMASK:?} =${UMASK:?} && continue
       #       If value is already set or to craziness like 0 , then continue
echo "Processing: $USER: $USER_PREF"
echo "Preference file: $USER_PREF"
if [ "$DSTROOT" = '/' ] ; then
       #       if we are running on the active startup Volume
               $id "${USER_NAME:?}" &>/dev/null || continue
               #      Check if the user is valid via DS search policy
               #      Skip if the user's id lookup fails protects against del
$sudo -u "$USER" $defaults write ${USER_PREF%.plist} 'NSUmask' -int $UMASK
#       Actively set the Global preferences as the user to keep ownership
       echo "Configured $GLOBAL_PREF for $USER"
else
       declare OWNER_UID="$($ls -lnd "$USERHOME/Library" |
                                             $awk '/^d/{print $3;exit}')"
       #       If we can't rely on DirectoryService, then pull the parent UID
       $defaults write ${USER_PREF%.plist} 'NSUmask' -int ${UMASK:?}

       echo "Chaining ownership on $USER_PREF to UID:$OWNER_UID"
       $chown "${OWNER_UID:-0} ${USER_PREF:?}"
fi

done
IFS="$OLD_IFS" #      Reset our field separator
return 0
} # End loopThroughHomes()

# Validate plist syntax and ownership and move if they fail the tests
checkPlistFiles(){
declare PLISTS="$@" #  Read in all the given files in the PLISTS array
for PLIST in $PLISTS ; do
declare -i OWNER_UID="$($ls -lnd "$($dirname "$PLIST_CHECK")"|
                                             $awk '/^d/{print $3;exit}')"
declare -i PLIST_UID"$($ls -ln "$PLIST_CHECK"|
                                             $awk '/^d/{print $3;exit}')"
$plutil "${PLIST:?}" 1>/dev/null
done
return $?
} # End checkPlistFiles()

loopThroughHomes
checkPlistFiles
exit 0

8.5.3. Managing Items in ARD

Apple Remote Desktop has the ability to use a task server, but not to share databases by default. You can import and export databases and copy information between computers manually from within ARD, but not actually share databases. In com.apple.RemoteDesktop, there is an array called ComputerDatabase. This array lists all of the items in the All Computers list within Remote Desktop. You can view a much less human friendly output of all of the hosts in All Computers by running the following command:

defaults read com.apple.RemoteDesktop ComputerDatabase

You can push an entry into the list by using the defaults command to write an item into that array in com.apple.RemoteDesktop. Here's a command to do so for a computer with a name of CharlesTest and an IP address of 10.10.10.10. Most of the other fields are extraneous and could probably be removed from the command, but it works as is:

defaults write com.apple.RemoteDesktop ComputerDatabase -array-add ' 
{ addedToDOC = 0;collectingAppUsage = 1;collectingUserAccounting = 1;
docInfoUpToDate = 0;hostname = CharlesTest.local;name = "CharlesTest";
ncFlags = 0;networkAddress = "10.10.10.10";preferHostname = 0;
showCursorForLegacy = 1;uuid = "C8F8966B-ED28-4221-CCE0-E1385D366717"; }'

You will need to restart the Remote Desktop services before you can see the new entry in the Remote Desktop application. You can just reboot, or you can restart using a pair of commands similar to the following:

launchctl stop `launchctl list | grep com.apple.RemoteDesktop | awk '{print $3}'`
launchctl stop `launchctl list | grep com.apple.RemoteDesktopAgent | awk '{print $3}'`

8.5.4. Disk Utilization

df is a great tool for checking the amount of free space on a disk (and the amount that's taken). df has a number of options for viewing the output and can even look at free iNodes and blocks rather than just showing free space. However, df is going to come up short if you're hunting for where all your free space went within a given volume.

For this, look to du, a great tool for checking disk utilization, more at the directory level. For example, the following command shows you how much space is being taken by each application in the /Applications directory:

du -d 1 /Applications/

Now run the command without the -d 1 parameters:

du /Applications/

The -d flag limits the depth that the command will traverse. By specifying 0, you'd only see the files in a given directory, whereas if you specify -d 2, you'll see the sizes of the child directories from the path you specified and their children (since that's two). You can go as deep as you want with the depth setting, but the data returned by the command can be too much, at times. Also, the longer it will take for the command to complete as it's calculating more and more data.

Some other flags that are useful are -x and -H. These will traverse mount points and symbolic links, respectively (both of which are not followed by default). This can help to keep your command's output limited to the host and volume of directories underneath the specified parent directory.

If you're interested in seeing way too much information, try just running:

du -a

If you suddenly have only 1KB of free space available, a series of du commands can turn up information about where all of your data is in no time.

8.5.5. Network Setup

Networking on Mac OS X can be automated. In many environments, system administrators will want to reorder the network interfaces to leverage wired connections over wireless when both are available. Therefore, we're going to go ahead and do two things at once, explain how to configure the interface and show how to automate this configuration from the command line so you can quickly deploy and then troubleshoot issues with this machine-specific part of your deployment.

Before getting started, it is important to note that there is a significant distinction in the nomenclature used in Mac OS X for network interfaces (devices) vs. network services. An interface is a physical network adapter. These are indicated by traditional Unix names such as en0, en1, fw0, and so on. You can determine which is which in a variety of ways, such as using ifconfig or Network Utility from /Applications/Utilities. A network service, in this context, is an abstraction of a network interface. Each service will have a physical adapter, and a physical adapter can have multiple services, which is how, for example, you would go about assigning two IP addresses to a single physical adapter. Things can get even more confusing when bond interfaces, where you are virtualizing a service to spread across multiple interfaces, in which case multiple interfaces are represented as a single network service.

To get a list of the network services running on your machine, you can use the following command:

networksetup -listallnetworkservices

And that command might return the following:

Ethernet
Airport
FireWire

There are about as many naming conventions for interfaces as there are actual interfaces. For the purposes of this example, we're going to patch Ethernet into the network and rename it to WiredNetwork, using the networksetup command again, with the -renamenetworkservice option as follows:

networksetup -renamenetworkservice Ethernet WiredNetwork

While it's not required to rename your network services, people often do. As you can see, it's quick and easy and can save you a bunch of time in the future in terms of troubleshooting, remote support, and automation facilitation. Renaming is very specific; the command looks for a pattern in the name and replaces it with a new pattern. So Built-in Ethernet would need to be enclosed in quotes, "Built-in Ethernet", and so forth. Now let's go ahead and rename the other services to WirelessNetwork using the following command:

networksetup -renamenetworkservice AirPort WirelessNetwork

Next, we want to make sure that the WiredNetwork is listed above WirelessNetwork. This will ensure that standard communications DNS, directory services, HTTP management traffic and other unnecessary traffic default to the wired network. To start, let's look at what order the services are listed in. We're going to use networksetup yet again, this time with the -listnetworkserviceorder option as follows:

networksetup -listnetworkserviceorder

This should provide a listing similar to the following, though perhaps in a different order:

(1) WirelessNetwork
(Hardware Port: Ethernet, Device: en1)

(2) WiredNetwork
(Hardware Port: Ethernet, Device: en0)

(3) FireWire
(Hardware Port: FireWire, Device: fw0)

Here we see that WirelessNetwork is listed as the first item in the network service order. Because we actually want the WiredNetwork first, we're going to reorder our services using the networksetup command with the -ordernetworkservices option. Using this option, you simply list each service in order, as you can see here:

networksetup -ordernetworkservices WiredNetwork WirelessNetwork FireWire

Notice that we include FireWire in the command. This is because you have to include all of your network services for the command to execute successfully. Now we are actually going to disable the FireWire network service (when we do, the interface itself will still function) using the -setnetworkserviceenabled option of the networksetup command. Because the FireWire service is automatically named FireWire, we simply tell networksetup to setnetworkserviceenabled to off as follows:

networksetup -setnetworkserviceenabled FireWire off

Because most environments do not support IPv6 yet, we're going to disable this for both WiredNetwork and WirelessNetwork using the -setv6off option as follows:

networksetup -setv6off WiredNetwork
networksetup -setv6off WirelessNetwork

Once IPv6 has been disabled, we're going to configure the IPv4 settings for our two network interfaces. For example, WiredNetwork might be set up to use DHCP. In that case there's not much configuration that needs to occur. While DHCP should be the default setting used with the controller, it would still be wise to specify it again anyway (just in case), using the next command, where -setdhcp is the option that enables DHCP for the WiredNetwork service.

networksetup -setdhcp WiredNetwork

While the WiredNetwork could be DHCP, in this case we're going to set it as a static IP address of 10.100.1.11. The subnet mask will be 255.255.0.0 and the gateway will be 10.100.0.1. This is all sent to the service in one command, using the -setmanual option with networksetup. When you use this option, you use the -setmanual option followed by the name of the service to configure, then the IP address that will be given to the service, then the subnet and finally the router (default gateway). In our case, the command would be:

networksetup -setmanual WiredNetwork 10.100.1.11 255.255.0.0 10.100.0.1

The wireless network is a bit more persnickety. As is typical, we will use DHCP but we will also need to configure a number of proxy services. Use the following command to set the adapter to DHCP:

networksetup -setdhcp WirelessNetwork

To set the proxies, use a combination of two of the following proxy options per service:

  • Setftpproxystate: Enables the FTP proxy.

  • setftpproxy: Sets up a proxy for FTP.

  • setwebproxystate: Enables the web proxy.

  • setwebproxy: Sets up a proxy for web traffic.

  • setsecurewebproxystate: Enables the SSL proxy.

  • setsecurewebproxy: Sets a proxy for SSL traffic.

  • setstreamingproxystate: Enables the streaming proxy.

  • setstreamingproxy: Sets a proxy for streaming traffic.

  • setgopherproxystate: Enables the gopher proxy (if you are using gopher, please stay after class for a parent-teacher conference).

  • setgopherproxy: Sets the gopher proxy.

  • setsocksfirewallproxystate: Enables a socks firewall.

  • setsocksfirewallproxy: Sets up the socks firewall.

  • setproxybypassdomains: Defines the domains that the proxy will not be used for.

To deploy a proxy setting, we'll use two commands, one to enable the option and the other to set it. For each proxy option that can be set, you will add the network service, a host name (or IP address), and a port number that the proxy will run on. Optionally you can then specify (still on the same line of the command) an authentication option (as either on or off) along with a username and password for each proxy service. For example, to set a web proxy for proxy.318.com that runs on port 8080 and requires authentication as username proxyserv with a password of Asimov you would use the following commands:

networksetup -setwebproxystate on
networksetup -setwebproxy WirelessNetwork proxy.318.com 8080 on proxyserv Asimov

Now that we have the services configured, we need to assign name servers. In order to set up DNS, we will use the -setdnsservers option with networksetup. In this case, our DNS servers are 10.100.0.2 and 10.100.0.3. When using the -setdnsservers option, you simply list the primary name server, followed by the secondary name server and any tertiary name servers. DNS is used on WiredNetwork as WirelessNetwork picks up DNS from DHCP:

networksetup -setdnsservers WiredNetwork 10.100.0.2 10.100.0.3

At this point you're probably thinking to yourself that you could have done all of this in the Network System Preference pane in about two minutes. Now however, we're going to take all of the commands we used in this example and put them into a shell script, replacing the actual IP addresses with positional parameters for the WiredNetwork and WirelessNetwork IP addresses, so that we can send the script along with the IP address that it will receive to each workstation. The script would look something like this:

#!/bin/bash
networksetup -renamenetworkservice Ethernet WiredNetwork
networksetup -renamenetworkservice Ethernet2 WirelessNetwork
networksetup -ordernetworkservices WiredNetwork WirelessNetwork FireWire
networksetup -setnetworkserviceenabled FireWire off
networksetup -setv6off WiredNetwork
networksetup -setv6off WirelessNetwork
networksetup -setmanual WiredNetwork $1 255.255.0.0 10.100.0.1
networksetup -setdnsservers WiredNetwork 10.100.0.2 10.100.0.3
networksetup -setmanual WirelessNetwork $2 255.255.255.0
networksetup -setwebproxystate on
networksetup -setwebproxy WirelessNetwork proxy.318.com 8080 on proxyserv Asimov

Now the script can be sent to each workstation. For this example, we're going to call the script setnetworkservices.sh. In order to send an IP address for the WiredNetwork of 10.100.1.12 and an IP for the WirelessNetwork of 192.168.1.12, you would simply send the following command (including the path of course):

setnetworkservices.sh 10.100.1.12 192.168.1.12

Then, to set up the next host using the same convention you would use:

setnetworkservices.sh 10.100.1.13 192.168.1.13

If you want to get a bit more complicated with the script, you could add some logic. For example, you might query for en0 and convert a service name to be used with en0 based on the interface, to keep the script from failing due to someone having renamed the service in the past. Because a common issue during setup is to patch the wrong interfaces into the networks (in the case that there are two wired interfaces), you could also use the ping command to test each network to verify it is live and if not (else) go ahead and swap the IP settings and names. You might also go ahead and turn every single setting into a variable to make it much more portable.

Finally, as you are updating this information, you are actually augmenting the /Library/Preferences/SystemConfiguration/com.apple.network.identification.plist file. While there are a variety of ways to edit this file directly, I wouldn't really suggest it because most adapters are referenced by MAC and have generated ServiceIDs (for example F8166C7E-CCFC-438C-98C6-CB05C7FA13E7). It is far easier to simply use the networksetup tool than it is to actually use a file drop of the plist or augment this file directly.

In Mac OS X 10.6 there are three major additions to networksetup. The first is that you can now use networksetup to import and export 802.1x profiles (and link them to certificates that you import from pkcs12 into Keychain), which will hopefully ease implementation burdens for environments with supported 802.1x setups. The second is that networksetup can now be used to manage a Baseboard Management Controller (BMC), which is the chip that enables ipmi/Lights-Out Management. The third new option is the addition of network locations control from within networksetup. This means that networksetup can now be used to configure basically the entire network stack.

First let's look at the options that have been added to ease the burden of integrating 802.1x. In the Network System Preference pane, if you've enabled 802.1x on a Mac host, you may have noticed that you have user profiles, login window profiles, and a system profile. The options in networksetup correspond to these, with -listalluserprofiles and -listloginprofiles showing available user and login profiles respectively (you can only have one system profile, so there's no need for listing all one of them). Additionally, any profiles that you generate will need to be enabled. You will use the -enablesystemprofile to enable the system profile for a given service. And if you are integrating 802.1x with the loginwindow you'll need to enable one of the profiles that you listed earlier, using the -enableloginprofile option to networksetup along with the service, followed by the profile, followed by an on or off switch. For example, if we wanted to enable a profile called mycompany for the login window and use the service that we'd set up called PrimaryEthernet, then we could use the following command:

networksetup -enableloginprofile PrimaryEthernet mycompany on

But, where are these profiles coming from? Well, the easiest way to get them on your system is to use the -export8021xProfiles to export all profiles for a given service on an imaging station and then the -import8021xProfiles followed by the service to import the profiles into, followed by the path to the export file. You can also export just the user profiles using the -export8021xLoginProfiles or the -export 8021xSystemProfiles options to export just the login profiles and system profiles respectively. TLS will be a bit trickier. Apple includes the -settlsidentityonsystemprofile and -settlsidentityonuserprofile to assist with pkcs12 integration (currently the only supported format).

In addition to 802.1x options, in 10.6 you can also now programmatically configure and control preferred wireless network settings from the command line. Arguments associated with this functionality are -listpreferredwirelessnetworks, -addpreferredwirelessnetworkatindex, -removepreferredwirelessnetwork, and -removeallpreferredwirelessnetworks. For instance, to add a preferred wireless network "Ansible" with WPA2 personal security, I would use the following command:

sudo networksetup -addpreferredwirelessnetworkatindex Airport Ansible 1 WPA2 Secretp4$$

10.6 also brings location management to networksetup. Locations have always been pretty straight forward in the Network System Preference pane, and with 10.6 you can now create and change locations programmatically (previously this was provided via the scselect utility). Simply use the -getcurrentlocation option to show you which location is active (if you haven't ever customized network locations this should be "Automatic"). You can see all available locations (not just the active one) by using the -listlocations option. New locations can be created with the -createlocation argument followed by the name to be assigned to the location. By default, the default services will not be included in this location, so use the populate option to add them. As an alternative you can add individual services manually via the -createnetworkservice option. If we were creating a new location called "MyCo Location," with all network services populated, then our command would look something like the following:

networksetup -createlocation "MyCo Location" populate

To then make that location our active location, use the -switchtolocation option. For example, we could use the following to activate that location we just created:

networksetup -switchtolocation "MyCo Location"

And to delete it if we did something wrong, use -deletelocation (to continue on with our previous example):

networksetup -deletelocation "MyCo Location"

NOTE

Mac OS X 10.6 also includes support for automating the deployment of 802.1x profiles. One of the authors of this book did a write-up on doing so that is available at: http://www.afp548.com/article.php?story=20090901010221742.

8.5.6. Power Management

Power management can most easily be managed via MCX, as discussed in Chapter 7. However, there may be instances where you need to resort to scripting to deploy your power management settings, and you can use the pmsetcommand line utility to accomplish this. For starters, let's look at enabling the wake on magic packet:

pmset -a womp 1

The -a indicates that the setting will apply to all settings modes for a computer: it will apply to the system when on battery, when we're plugged in, or when we are running on UPS backup power. You can change settings for only a specific state with the following flags, which fall into the first positional parameter:

  • -a: all

  • -b: battery

  • -c: wall power

  • -u: UPS

The next parameter you'll pass to the command is the option (argument) for that power setting that you would like to send. Here you can set the number of minutes before the display goes to sleep, the brightness at various power settings, and other options that have a direct effect on power behavior. These include the following:

  • acwake: Wake when the system is connected to power; it's a 0 or 1.

  • autorestart: Automatically restart when there's been a power loss (when the system is plugged in); use 0 or 1.

  • disksleep: Number of minutes before the disk spins down.

  • displaysleep: Number of minutes before the computers monitor (signal to the monitor) goes to sleep.

  • dps: Allows the CPU speed to dynamically change with power; 0 or 1.

  • halfdim: Controls whether the display goes to half- brightness for the power setting in question; 0 or 1

  • lessbright: Same as above, just not as much

  • lidwake: Automatically wake the system when the lid is opened; 0 or 1

  • powerbutton: Allows the box to go to sleep if someone hits the power button. If it's disabled, the system will not go to sleep if someone hits the power button. This doesn't disable powering down by holding down that same power button; 0 or 1.

  • reduce: Allows reduction of the CPU speed; 0 or 1.

  • ring: Wakes if someone calls the modem (but since the modern laptops don't have modems, likely not something you'll be using). It's an integer, 0 or 1.

  • sleep: Number of minutes before the computer goes to sleep (but doesn't spin down the disk).

  • sms: Controls whether you're using the Sudden Motion Sensor to stop the disk heads from locking down when the system gets jarred (G force math is kewl). It's a Boolean thing, either on or off.

  • womp: explained previously.

In addition to these, you can also use pmset to get information with the -g flag. Using -g alone will net you all of the available information and while there are other options to limit what it outputs, I normally just use grep for that.

There are also a number of options for managing SafeSleep (maintaining the system state in memory, using the argument hibernatemode) or UPS options (haltvalue for how much battery to trigger a shutdown and halfafterfor when to spin the CPU to 50% of full). If you're trying to manage the system and you have a battery (such as a laptop plugged into a UPS), the settings will not be respected.

Just as in the System Preference pane, you can also control scheduling for when the system sleeps, wakes, powers on, or shuts down as well. These events can be scheduled by using the schedule or repeat arguments, which can be used to set one time power events, or repeated events, respectively. Options for each are sleep, wake, poweron, and shutdown in conjunction with using date, time, weekdays. You can optionally provide a string name of the person setting the schedule for documentation purposes:

pmset schedule poweron "09/09/09 9:09:09"
pmset repeat shutdown MTWRF 21:00:00

There are also a few other options that you don't have in the GUI. These include force, which doesn't write settings to disk, touch, which reads currently enforced settings from the disk, noidle, which prevents idle sleep (and just spins the disk down when it's ready) and sleepnow, which puts the system to sleep right then. sleepnow is useful when you're troubleshooting why a system won't go to sleep.

For the Xserve specifically, there is also Lights-Out Management (discussed later in the chapter) in the form of the IPMI toolkit from Intel. You can use that to power systems on, power them off, and perform a few other tasks. This must be secured with a password, using Server Monitor. You can then control state through Server Monitor, or through Apple Remote Desktop. Find out more about IPMI on this page over at Intel.com.

8.5.7. ServerAdmin Backups and Change Monitoring

At its most basic, change control can be used in Mac OS X Server by leveraging the serveradmin command. You can use the serveradmin command with the settings option as we've done extensively in Chapter 5 to obtain information about settings and augment those settings in Mac OS X Server on a per-server basis. However, you can also use the serveradmin command to report all of the settings for all of its services. To do so, you use the following command:

serveradmin settings all

You can then pipe this information into a file. For example, the following command would copy the information from serveradmin into a text file in the /scripts directory of a system called dailyservercheck:

serveradmin settings all > /scripts/dailyservercheck

It is important to note that any changes made directly to a particular software package's configuration files will likely not be detected through this method; for instance, if a user modified the postfix service's configuration at /etc/postfix/main.cf. To monitor Unix utilities such as these, Tripwire, a change monitoring solution both with open-source and enterprise solutions available (www.tripwire.org and www.tripwire.com), is a better option. That being said, serveradmin is a great way to track changes made through standard Apple tools and therefore certainly does have a purpose.

To fully automate this task, we can use the code listed below, also available for digital download (file 8_sabackuplocal.sh). This code creates a folder specified by variable SABACKUPDIR, and then creates a disk image in the form of a sparsebundle named by the variable SAARCHIVEDMG. Once these assets are created, they will be utilized for the backup. This script will automatically mount the disk image, perform a serveradmin backup, and then check that against the last run to determine if any changes were made. A symbolic link named "Latest.txt" will always be linked at the latest serveradmin output. A more robust version of this script can be found for digital download as well (file 8_sabackup.sh).

#!/bin/bash

##########  Server Admin Backup Script #####################
##
##      Written by Beau Hunter, Zack Smith  7/03/09
##  [email protected] [email protected]
##  Server Admin backup script, equivalent to serveradmin settings all
##  backs up only when config changes, generates diffs with each change.
##
###############################################################################

## User configuration
SABACKUPDIR=/Auto Server Setup

## Serveradmin archive disk image
SAARCHIVEDMG="serveradmin_archives.sparsebundle"
SAARCHIVE_MOUNTPOINT="/Volumes/${SAARCHIVEDMG%.sparsebundle}"

## bin vars
declare -x grep="/usr/bin/grep"
declare -x serveradmin="/usr/sbin/serveradmin"
declare -x defaults="/usr/bin/defaults"
declare -x hdiutil="/usr/bin/hdiutil"
declare -x diskutil="/usr/sbin/diskutil"
declare -x mkdir="/bin/mkdir"
declare -x du="/usr/bin/du"
declare -x date="/bin/date"
declare -x diff="/usr/bin/diff"
declare -x awk="/usr/bin/awk"
declare -x mv="/bin/mv"
declare -x ln="/bin/ln"
declare -x mktemp="/usr/bin/mktemp"
declare -x umount="/sbin/umount"
declare -x sleep="/bin/sleep"

##  Runtime variables
DATE=$("$date" +'%Y%m%d.%H%M_%S')
declare -x REQCMDS="$awk,$ntpdate,$perl,$scutil"
declare -x SCRIPT="${0##*/}" ; SCRIPTNAME="${SCRIPT%%.*}"
declare -x SCRIPTPATH="$0" RUNDIRECTORY="${0%/*}"
declare -x SCRIPTLOG="/Library/Logs/${SCRIPT%%.*}.log"

## test for root
[ "$EUID" != 0 ] && printf "%s
" "This script requires root access ($EUID)!" && exit 1


exec 2>>"${SCRIPTLOG:?}" # Redirect standard error to log file


########## MAIN ##########

## check for the backup dir
if [ ! -d "$SABACKUPDIR" ]; then
       echo "A local directory was not found at path: $SABACKUPDIR, attempting to create"

       "$mkdir" "$SABACKUPDIR" &> /dev/null
       if [ $? != 0 ]; then
               echo "Failed to mount $NFSPATH to $SABACKUPDIR, exiting!"
               exit 1
       fi
fi

## Check for directory mounted where our DMG should be
if [ -d "$SAARCHIVE_MOUNTPOINT" ]; then
       echo "Directory mounted at ServerAdmin Backup DMG mountpath: $SAARCHIVE_MOUNTPOINT"
       "$umount" "$SAARCHIVE_MOUNTPOINT"

       ## attempt to remove the local directory
       rm "$SAARCHIVE_MOUNTPOINT"/.DS_Store &> /dev/null
       rmdir "$SAARCHIVE_MOUNTPOINT" &> /dev/null
       if [ -d "$SAARCHIVE_MOUNTPOINT" ]; then
               echo "Could not resolve the issue, please remove: $SAARCHIVE_MOUNTPOINT"
               exit 4
       fi
fi

## Check for an archive disk image
if [ -d "$SABACKUPDIR"/"$SAARCHIVEDMG" ]; then
       ## mount it if it exists
       "$hdiutil" mount -nobrowse "$SABACKUPDIR"/"$SAARCHIVEDMG" >> "$SCRIPTLOG"
       echo "ServerAdmin Backup DMG found, mounting!"
else
       ## here if we need to create our DMG
       echo "ServerAdmin Backup DMG: $SAARCHIVEDMG could not be found! creating..."
       TEMPPATH="$("$mktemp" -d /tmp/XXXXXX)"
       "$hdiutil" create -type SPARSEBUNDLE -size 1g -fs HFS+ -volname  
"${SAARCHIVEDMG%.sparsebundle}" "$TEMPPATH"/"$SAARCHIVEDMG" >> "$SCRIPTLOG" "$mv" "$TEMPPATH"/"$SAARCHIVEDMG" "$SABACKUPDIR"/"$SAARCHIVEDMG" if [ $? != 0 ]; then

echo "Could not move from $TEMPPATH/$SAARCHIVEDMG"
               exit 3
       fi
       "$hdiutil" mount -nobrowse "$SABACKUPDIR"/"$SAARCHIVEDMG" >> "$SCRIPTLOG"
       echo "Mounting ServerAdmin Backup DMG"
fi


## One last sanity check
if [ ! -d "$SAARCHIVE_MOUNTPOINT" ]; then
       echo "Disk image did not seem to mount! Exiting!"
       exit 5
fi

## and last but not least, dump our settings
echo "Checking for changes..."
"$serveradmin" settings all | "$grep" -v "info:currentTime" >  
"$TEMPPATH"/sa_export_"$DATE".txt "$diff" "$SAARCHIVE_MOUNTPOINT"/latest.txt "$TEMPPATH"/"sa_export_$DATE.txt" &> /dev/null if [ $? == 0 ]; then echo "No changes were detected, not saving export" else echo "Changes found, saving output and creating diff file." "$diff" "$SAARCHIVE_MOUNTPOINT"/latest.txt "$TEMPPATH"/"sa_export_$DATE.txt" >>
"$SAARCHIVE_MOUNTPOINT"/"sa_export_${DATE}-diff.txt" "$mv" -f "$TEMPPATH"/sa_export_"$DATE".txt
"$SAARCHIVE_MOUNTPOINT"/"sa_export_$DATE.txt" cd "$SAARCHIVE_MOUNTPOINT" "$ln" -s "sa_export_$DATE.txt" "latest.txt" cd "$OLDPWD" fi if [ -d "$TEMPPATH" ]; then "$rm" -rf "$TEMPPATH" &> /dev/null fi ## if we're still here, then force the unmount (there is no messing around!) while [ -d "$SAARCHIVE_MOUNTPOINT" ]; do let COUNT++ if [ $COUNT -le 10 ]; then echo "Unmounting ServerAdmin Backup DMG: $SAARCHIVE_MOUNTPOINT" "$hdiutil" eject "$SAARCHIVE_MOUNTPOINT" >> "$SCRIPTLOG" elif [ $COUNT -eq 11 ]; then echo "ServerAdmin Backup DMG failed to unmount, forcing!" "$diskutil" unmount force "$SAARCHIVE_MOUNTPOINT" >> "$SCRIPTLOG" else echo "ServerAdmin Backup DMG failed to unmount!" break fi "$sleep" 1 done exit 0

8.5.8. Xserve Lights-Out Management

Snow Leopard also comes with the ability to manage that Lights-Out Management (LOM) port via the previously discussed networksetup command. To see the LOM settings, you would use networksetup along with the -showBMCSettings option. To set up LOM, use the -setupBMC option, along with the port on which to use it, followed by whether it will be static or DHCP (yes, I said DHCP, but I don't think I'd do that: this is a management interface and should be persistent), the IP, subnet mask, gateway, and finally the admin user name and password (keep in mind those passwords need 8 characters). So let's say that I wanted to configure my LOM interface to use Ethernet 1, using 10.1.1.29 with a subnet mask of 255.255.255.0 and a gateway of 10.1.1.1, with a LOM username of admin and a password of mysecretpassword1. I would then use the following command:

networksetup -setupBMC 1 static 10.1.1.29 255.255.252.0 10.1.1.1 admin mysecretpassword1

For 10.5 or earlier, or to access additional functionality, you can use the ipmitool command to accomplish the task. To set the IP address on a LOM interface, use the following command:

sudo ipmitool lan set 1 ipaddr 10.1.1.29 netmask 255.255.255.0 defgw 10.1.1.1

This sets interface one; replace set 1 with set 2 to set it on the second interface. There is some pretty cool functionality here, such as specifying VLAN tagging, backup gateways, hard-coding gateway MAC addresses to protect against arp poisoning, and so on.

If you want to edit the default user account created by Server Monitor, which resides at userid 2, you can do so using these commands:

sudo ipmitool user set name 2 myadmin
sudo ipmitool user set password 2 'mypass'

Passwords are provided via stdin and are thereby recorded in your .history file.


If you want to create a new user account altogether (on top of an already configured apple user), use the following commands:

## get a list of current users to determine the nextuser ID.
## This will output a list of all user's on the system, as well as their user
## id. When creating a new user, we need to make sure we use the next available user id:
sudo ipmitool user list 1

## OS X by default has 2 users, one is a built in system user, the other an admin user
## which is setup typically in Server Monitor. Id 3 is the next unused id
sudo ipmitool user set name 3 mynewadmin
sudo ipmitool user set password 3 'mynewpass'
sudo ipmitool enable 3

## turn on serial over lan for the user on both interfaces (not sure if this 
is needed, but it's used by the default admin) sudo ipmitool user sol 3 enable 1

sudo ipmitool user sol 3 enable 2

## set the user as admin for the first LOM port
sudo ipmitool channel setaccess 1 3 callin=on ipmi=on link=on privilege=4

## and then the second
sudo ipmitool channel setaccess 2 3 callin=on ipmi=on link=on privilege=4

After running these commands, you should be able to verify LOM management via ARD or Server Monitor by using the newly created user.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.237.255