#!/bin/sh - # vi:ts=3:ai ####################################################################### # This script backs up all critical configuration and user files as you # specify in a separate file (see BACKUP_LIST_FILE below). This script # can optionally utilize samba to send the backup archives off-host to # a Microsoft Windows share. Several other options can be set and are # identified in the OPTIONS section below. # # USAGE NOTE: Copy this file to /etc/cron.daily/ZZbackup.cron in order # to automate your nightly backups. # # QUICK START GUIDE: Set the following values and create your # /etc/backuplist file with a list of files to back up: # SERVICE_LIST (list services actually on your machine that need # to be stopped and restarted for the duration of the backup) # LOCAL_ARCHIVE_PATH (set to a path that is a mount to a different # machine on your network) # # Script Version: # 1.4 # Original Author: # William Kimball, Jr. # History: # 10 FEB 2006 : # Originated this source file. # 11 FEB 2006 : # Added new stop_services(), start_services(), and reverse_list() # functions and incorporated into the main() body the ability to # stop/start services automatically to avoid backup archive file # corruption. Also added a new option, SERVICE_PATH to accomodate. # 15 FEB 2006 : # Added new TRY_ATTEMPTS and RETRY_DELAY user options to give the # user an option to control the number of times a service is called # to start or stop if it fails to comply, and a number of seconds # to hesitate between each attempt. This affected start_services() # and stop_services(). # 17 FEB 2006 : # Added a new IGNORE_SERVICE_FAIL user option to allow the user to # decide whether or not to continue with the backup operation in # case a service fails to yield to control directives. # Incorporated more robust error handling in main() with improved # user feedback per input from CLUE members. # Added RUN_BEFORE_BACKUP and RUN_AFTER_BACKUP user options as # external script hooks to give users the ability to shut down and # start up services on machines that do not have a services # command facility. This is an added benefit for all users in that # these options can also be used to automate any kind of pre- and # post-backup tasks. IGNORE_RUN_FAIL, also new, has the same # effect as IGNORE_SERVICE_FAIL. # 28 JUL 2006 : # Added new options for Windows 2003 DC style SMB shares. This # fixes a bug wherein smbmount doesn't work, but mount -t cifs # may. ####################################################################### ####################################################################### # OPTIONS #---------------------------------------------------------------------# # This set of _PATH options specify the absolute paths to some programs # which this script relies on. The recommended values come from # RHEL4/CentOS4 systems and may not fit your site's configuration. # While is is possible, and generally okay, to specify only a program # name without any pathing information, these options help in cases # where this script runs in an automated context (cron, at, etc.) # without an appropriate $PATH environment variable. # Power User Note: If you put these values in quotes, then you can # also specify additional options to these commands, which will precede # the arguments passed by this script. Doing this isn't recommended as # you will alter the expected behavior of this script beyond the intent # of its author, but the possibility is available to you. # # Recommended values: # HOSTNAME_PATH=/bin/hostname # MV_PATH=/bin/mv # RM_PATH=/bin/rm # MKDIR_PATH=/bin/mkdir # RMDIR_PATH=/bin/rmdir # ECHO_PATH=/bin/echo # PRINTF_PATH=/usr/bin/printf # TR_PATH=/usr/bin/tr # GREP_PATH=/bin/grep # CHMOD_PATH=/bin/chmod # PWD_PATH=/bin/pwd # TAR_PATH=/bin/tar # GZIP_PATH=/bin/gzip # BZIP2_PATH=/usr/bin/bzip2 # SMBMOUNT_PATH=/usr/bin/smbmount # SMBUMOUNT_PATH=/usr/bin/smbumount # UMOUNT_PATH=/bin/umount # SERVICE_PATH=/sbin/service # SLEEP_PATH=/bin/sleep #---------------------------------------------------------------------# HOSTNAME_PATH=/bin/hostname MV_PATH=/bin/mv RM_PATH=/bin/rm MKDIR_PATH=/bin/mkdir RMDIR_PATH=/bin/rmdir ECHO_PATH=/bin/echo PRINTF_PATH=/usr/bin/printf TR_PATH=/usr/bin/tr GREP_PATH=/bin/grep CHMOD_PATH=/bin/chmod PWD_PATH=/bin/pwd TAR_PATH=/bin/tar GZIP_PATH=/bin/gzip BZIP2_PATH=/usr/bin/bzip2 SMBMOUNT_PATH=/usr/bin/smbmount SMBUMOUNT_PATH=/usr/bin/smbumount MOUNT_PATH=/bin/mount UMOUNT_PATH=/bin/umount SERVICE_PATH=/sbin/service SLEEP_PATH=/bin/sleep #---------------------------------------------------------------------# # SHORT_HOST_NAME is the local host name of this machine, in short form # (not FQDN). Set this value manually only if `hostname -s` is unable # to read your hostname correctly. # # The recommended value is: # $(hostname -s) #---------------------------------------------------------------------# SHORT_HOST_NAME=$($HOSTNAME_PATH -s) #---------------------------------------------------------------------# # BACKUP_LIST_FILE is a local file which lists all files and # directories that are to be copied to the backup archive. The # content format of the external BACKUP_LIST_FILE is particular. Put # one and only one file or directory, by absolute path, per each line. # Directory entries MUST end with a / and CANNOT end with /* else tar # will fail. When a directory is listed, that directory and ALL its # subdirectories will be included in the archive. Avoid using # skeleton masks such as ? or *. # # The recommended value is: # /etc/backuplist #---------------------------------------------------------------------# BACKUP_LIST_FILE=/etc/backuplist #---------------------------------------------------------------------# # This set of options enables you to specify a list of system services # (see "/sbin/chkconfig --list") which should be temporarilly disabled # while files are archived. This is necessary because file corruption # can occur if files are open (especially when being written to) when # they are copied into the archive. # # Note: If your installation does not have the service facility, then # use RUN_BEFORE_BACKUP and RUN_AFTER_BACKUP, instead. # # SERVICE_LIST is a quoted, space-delimited list of services (which can # be controlled using $SERVICE_PATH) which must be stopped prior to # making the backup archive, then started immediately after. You are # strongly advised to pick your services carefully so that files being # copied by the backup will not be in use at the time of the backup. # Note that the order of this list is significant. The services are # stopped in left-to-right order, then started in reverse order. # This enables you to order your services specifically to resolve # inter-service dependencies. # TRY_ATTEMPTS is an integer number which indicates the number of times # to attempt to stop and start services in the event of start/stop # failure. # RETRY_DELAY is a number of seconds to wait between attempts to start # or stop individual services that fail to respond. # IGNORE_SERVICE_FAIL is a boolean value (true or false) which # indicates whether or not to archive files regardless of whether any # service fails to yield. For example, setting true permits the backup # operation to proceed even if one or more services fail to stop. # # The recommended values are machine dependant. Set to your own site # needs. Typical (theoretical) values for a web+ftp+database server: # SERVICE_LIST="httpd vsftpd mysqld" # TRY_ATTEMPTS=5 # RETRY_DELAY=5 # IGNORE_SERVICE_FAIL=true #---------------------------------------------------------------------# SERVICE_LIST="" TRY_ATTEMPTS=5 RETRY_DELAY=5 IGNORE_SERVICE_FAIL=true #---------------------------------------------------------------------# # This set of options enable you to run any code you want before and # after the backup operation. This can be any single command (in # double-quotes) or a call to another shell script. Whatever is called # should return a success/fail indicator (integer values 0/1). # # RUN_BEFORE_BACKUP will be called after the services in SERVICE_LIST # are stopped (if any) and before the files and directories in # BACKUP_LIST_FILE are archived. This call will be ignored if empty. # RUN_AFTER_BACKUP will be called before the services in SERVICE_LIST # are restarted (if any) and after the files and directories in # BACKUP_LIST_FILE are archived and optionally compressed. This call # will be ignored if empty. # IGNORE_RUN_FAIL is a boolean (true or false) which indicates whether # to ignore a fail response from the command/script specified by # RUN_BEFORE_BACKUP. If IGNORE_RUN_FAIL is true, the backup operation # will proceed regardless of the exit status from calling # RUN_BEFORE_BACKUP. Otherwise, the script will terminate on failure. # # The recommended values are machine dependant. Set to your own site # needs. Theoretical values might include: # RUN_BEFORE_BACKUP="/usr/local/sbin/service-control all stop" # RUN_AFTER_BACKUP="/usr/local/sbin/service-control all start" # IGNORE_RUN_FAIL=true #---------------------------------------------------------------------# RUN_BEFORE_BACKUP="" RUN_AFTER_BACKUP="" IGNORE_RUN_FAIL=true #---------------------------------------------------------------------# # COMPRESSION is a supported tarball compression format; one of: # none, gz, bz2 # # The recommended value is: # bz2 #---------------------------------------------------------------------# COMPRESSION=bz2 #---------------------------------------------------------------------# # WORK_PATH is a workspace directory where temorary files may be # created for the purpose of building the backup archive. # # The recommended value is: # #---------------------------------------------------------------------# WORK_PATH=/tmp/backup #---------------------------------------------------------------------# # BACKUP_COUNT is the number of backup images to keep in rotation. It # is your responsibility to ensure that the destination drive for your # backup archives has enough drive space to hold all iterations # (rotations) of your backup archive. # # The recommended value is: # 14 #---------------------------------------------------------------------# BACKUP_COUNT=14 #---------------------------------------------------------------------# # INDEX_PAD indicates how many digits to require for the numeric index # portion of your rotating backup archive names. The index portion # of the filenames will be zero-buffered to this many digits. 0 or 1 # disables this feature, which exists to facilitate file sorting. # # The recommended value is: # 2 #---------------------------------------------------------------------# INDEX_PAD=2 #---------------------------------------------------------------------# # LOCAL_ARCHIVE_PATH is any local path to store the backup archives. # It is is better to use off-host shares than any truly local path, # otherwise you are defeating the purpose of performing a backup, at # all (which, by nature, is to help protect you against drive # failures). This value is used only if the following SMB_ options # are not set. # # The recommended value is machine dependent, but use something like: # /mnt/some-off-site-mounted-host/path #---------------------------------------------------------------------# LOCAL_ARCHIVE_PATH=~/backup #---------------------------------------------------------------------# # The following SMB_ options enable this script to open and close a # remote file share automatically for the purpose of copying the # backup archive files off-host. You can leave these SMB_ options # blank and set only LOCAL_ARCHIVE_PATH to store your backup archives # locally (not a good idea) or to a reliable off-host mount point (be # it samba, nfs, or otherwise) which is created and maintained outside # this script. # # SMB_MODE is a manual mode switch to toggle between using SMB's native # smbfs (smbmount) or the cifs alternative (mount). Options are: # smbfs: Use smbmount # cifs: Use mount # NOTE: When cifs is used, SMB_SERVER_IP must be set. # SMB_SERVER is a remote samba server onto which to store backup # images (by name). # SMB_SERVER_IP is optional (mandatory only when SMB_MODE=cifs) and is # the IP address of the SMB_SERVER. # SMB_SHARE is an appropraite samba share on the specified samba server # onto which to store backup images. # SMB_USER is your samba user name which has read+write access to the # specified samba share. # SMB_PASSWORD is your samba password for the specified samba user # name. # SMB_PATH_PREFIX is the directory on the samba share into which the # backup images will be stored. # # The recommended values are network-specific. Set to your own site # needs. #---------------------------------------------------------------------# SMB_MODE=smbfs SMB_SERVER= SMB_SERVER_IP= SMB_SHARE= SMB_USER= SMB_PASSWORD= SMB_PATH_PREFIX="/$SHORT_HOST_NAME/" # END OPTIONS ####################################################################### # Lock the user preferences as global constants. readonly MV_PATH RM_PATH MKDIR_PATH RMDIR_PATH PWD_PATH readonly ECHO_PATH PRINTF_PATH TR_PATH GREP_PATH CHMOD_PATH readonly TAR_PATH GZIP_PATH BZIP2_PATH readonly HOSTNAME_PATH SMBMOUNT_PATH SMBUMOUNT_PATH MOUNT_PATH UMOUNT_PATH SERVICE_PATH readonly SHORT_HOST_NAME WORK_PATH BACKUP_LIST_FILE SERVICE_LIST TRY_ATTEMPTS readonly COMPRESSION BACKUP_COUNT INDEX_PAD readonly LOCAL_ARCHIVE_PATH readonly SMB_MODE SMB_SERVER SMB_SERVER_IP SMB_SHARE SMB_PATH_PREFIX SMB_USER SMB_PASSWORD ####################################################################### # Function: showerror # Purpose: Sends an error message to the stderr output. # Receives: # $1: The quoted error message. # Returns: # Sends text to the stderr output pipe. # Example: # showerror "Some error!" # Function Version: # 1.0 # History: # 10 FEB 2006 : # Originated this function. ####################################################################### showerror() { $ECHO_PATH Error from $0: $1 1>&2 } ####################################################################### # end showerror() ####################################################################### ####################################################################### # Function: stroccurs # Purpose: Returns the number of times a given string (the needle) # occurs within a source string (the haystack). # Receives: # $1: The needle. # $2: The haystack. # Returns: # Output is on stdout (you must capture it). # Example: # slash_count=$(stroccurs '/' $PATH) # Function Version: # 1.0 # History: # 10 FEB 2006 : # Originated this function. ####################################################################### stroccurs() { $ECHO_PATH "$2" | $TR_PATH -c "$1" "\n" | $GREP_PATH -c "$1" } ####################################################################### # end stroccurs() ####################################################################### ####################################################################### # Function: strleft # Purpose: Returns a specified number of characters from the left end # of a given string. # Receives: # $1: The string. # $2: The number of characters to return. # Returns: # Output is on stdout (you must capture it). # Example: # lead_char=$(strleft $1 1) # Function Version: # 1.0 # History: # 10 FEB 2006 : # Originated this function. ####################################################################### strleft() { $PRINTF_PATH "%.$2s" $1 } ####################################################################### # end strleft() ####################################################################### ####################################################################### # Function: add_last_slash # Purpose: Returns a path string with a trailing slash. # Receives: # $1: The source path string. If empty, $HOME will be provided. The # path does not have to exist, but must be legally composed. # Returns: # Output is on stdout (you must capture it). # Example: # fqpath=$(add_last_slash $source_path) # Function Version: # 1.0 # History: # 10 FEB 2006 : # Originated this function. ####################################################################### add_last_slash() { # Initialize local variables. source_path=${1:-~} lead_char=$(strleft $source_path 1) build_path= # Determine the root of the source path. case "$lead_char" in "/") build_path=/ ;; "~") build_path=~/ ;; *) build_path="$($PWD_PATH)/" ;; esac # Reconstruct the source path with proper trailing slashes. for path_part in $($ECHO_PATH "$source_path" | $TR_PATH -d "." | $TR_PATH "/" "\n"); do build_path="$build_path$path_part/" done $ECHO_PATH $build_path } ####################################################################### # end add_last_slash() ####################################################################### ####################################################################### # Function: remove_last_slash # Purpose: Returns a path string without any trailing slash (unless # the source path is the root /). # Receives: # $1: The source path string. If empty, $HOME will be provided. The # path does not have to exist, but must be legally composed. # Returns: # Output is on stdout (you must capture it). Note this function will # return the empty string if you pass the root path (/) as the # argument. # Example: # partial_path=$(remove_last_slash $source_path) # Function Version: # 1.0 # History: # 10 FEB 2006 : # Originated this function. ####################################################################### remove_last_slash() { # Initialize local variables. source_path=${1:-~} lead_char=$(strleft $source_path 1) build_path= # Determine the root of the source path. case "$lead_char" in "/") build_path= ;; "~") build_path=~ ;; *) build_path=$($PWD_PATH) ;; esac # Reconstruct the source path with proper leading slashes. for path_part in $($ECHO_PATH "$source_path" | $TR_PATH -d "." | $TR_PATH "/" "\n"); do build_path="$build_path/$path_part" done $ECHO_PATH $build_path } ####################################################################### # end remove_last_slash() ####################################################################### ####################################################################### # Function: reverse_list # Purpose: Reverses a delimited list. # Receives: # $1: The delimited list to reverse. # Returns: # Output is on stdout (you must capture it). # Example: # reversed=$(reverse_list "item1 item2 item3") # Function Version: # 1.0 # History: # 11 FEB 2006 : # Originated this function. ####################################################################### reverse_list() { # Initalize local variables. reverse_list= # Do nothing if the list is empty. if [ 0 -lt ${#1} ]; then for list_item in $1; do reverse_list="$list_item $reverse_list" done fi $ECHO_PATH $reverse_list } ####################################################################### # end reverse_list() ####################################################################### ####################################################################### # Function: makedirs # Purpose: Receives a fully-qualified path and ensures that path # exists -- will create each element of the path as necessary. # Receives: # $1: Fully-qualified path to validate. # $2: (optional) Permission mask to apply to any path elements that # are created. This mask is NOT applied to directories which # already exist! # Returns: # Standard Unix error codes (0 = success, else failure) # Example: # makedirs /var/shares/free4all/ 777 # Function Version: # 1.1 # History: # 10 FEB 2006 : # Originated this function. # 17 FEB 2006 : # Added a tag to error messages to indicate the message source per # requests for more informative and traceable error messages. ####################################################################### makedirs() { # Initialize local variables. full_path=$1 sec_mask=${2:-770} lead_char=$(strleft "$full_path" 1) build_path= # Test for an empty string. if [ "" = "$full_path" ]; then showerror "makedirs(): You must specify a path to makedirs!" return 1 fi # Determine the root of the path to be created. case "$lead_char" in "/") build_path=/ ;; "~") build_path=~/ ;; *) build_path="$($PWD_PATH)/" ;; esac # Do nothing if the path already exists. if [ ! -d $full_path ]; then for path_part in $($ECHO_PATH "$full_path" | $TR_PATH "/" "\n"); do build_path="$build_path$path_part/" # Create a portion of the path if it does not already exist. if [ ! -d $build_path ]; then if $MKDIR_PATH $build_path; then $CHMOD_PATH $sec_mask $build_path else showerror "makedirs(): Path, $build_path, could not be created!" return 1 fi fi done fi # Exit with success. return 0 } ####################################################################### # end makedirs() ####################################################################### ####################################################################### # Function: rotatefiles # Purpose: Rotates all iterations of a particular, numerically # sequenced, file up to a given iteration. Any instances above # the iteration limit are destroyed, but only if they would be ceated # by this function call. Pre-existing files in the sequence beyond # the iteration limit you specify are ignored (so be careful). # Receives: # $1: Fully-qualified file name to rotate. Do not specify a name # which already includes a numeric index -- yours will not be used. # $2: Dot-seperated filename component to use as the numeric index. # This defaults to 2 so that filenames like somefile.tar create # cyles like somefile.01.tar while filenames like somefile.tar.bz2 # create rotations like somefile.01.tar.bz2. As you can see, the # index is inserted as the second dot-seperated filename component. # $3: Maximum iterations to preserve. The default is 7. # $4: Zero-buffered size of the numeric index. The default is 1, # which means that zeros are not appended to the front (0 has the # same effect). Set to a value greater than 1 to force zeros to # appear. # Returns: # Standard Unix error codes (0 = success, else failure) # Example: # rotatefiles /var/share/backups/my.backup.tar.bz2 3 4 2 # Function Version: # 1.1 # History: # 10 FEB 2006 : # Originated this function. # 17 FEB 2006 : # Added a tag to error messages to indicate the message source per # requests for more informative and traceable error messages. ####################################################################### rotatefiles() { # Initialize local variables. file_path=${1%/*} file_name=${1##*/} index_at=${2:-2} max_limit=${3:-7} zero_buffer=${4:-1} dot_count=$(stroccurs '.' $file_name) part_index=1 build_prefix= build_suffix= build_from= build_to= # Test for an empty string. if [ "" = "$file_name" ]; then showerror "rotatefiles(): You must specify a file name to rotatefiles!" return 1 fi # The specified file must exist. if [ ! -e "$1" ]; then showerror "rotatefiles(): The specified file, $1, does not exist!" return 1 fi # Compose the filename around the numeric index. if [ 0 == $dot_count ]; then # Not a dot-separated filename. build_prefix="$file_name." build_suffix= index_at=2 else part_index=1 for name_part in $($ECHO_PATH "$file_name" | $TR_PATH "." "\n"); do # Add the filename components to the appropriate build parts. if [ "$index_at" -gt "$part_index" ]; then build_prefix="$build_prefix$name_part." else build_suffix="$build_suffix.$name_part" fi # Bump the part index position. part_index=$((++part_index)) done fi # Rotate the files. part_index=$max_limit until [ "$part_index" -le 0 ]; do # Construct the rotation filenames. build_from="$file_path/$build_prefix$($PRINTF_PATH "%.${zero_buffer}i" $part_index)$build_suffix" build_to="$file_path/$build_prefix$($PRINTF_PATH "%.${zero_buffer}i" $((part_index+1)))$build_suffix" # Destory the destination file if it already exists. if [ -e "$build_to" ]; then $RM_PATH -f $build_to fi # Move the source file if it exists. if [ -e "$build_from" ]; then $MV_PATH -f $build_from $build_to fi # Bump the part index position. part_index=$((--part_index)) done # Move the source file to the first position in the new or refreshed rotation. build_from="$1" build_to="$file_path/$build_prefix$($PRINTF_PATH "%.${zero_buffer}i" 1)$build_suffix" $MV_PATH -f $build_from $build_to # Destroy any file that would exceed the iteration limit. build_from="$file_path/$build_prefix$($PRINTF_PATH "%.${zero_buffer}i" $((max_limit+1)))$build_suffix" $RM_PATH -f $build_from # Exit with success. return 0 } ####################################################################### # end rotatefiles() ####################################################################### ####################################################################### # Function: close_smb_share # Purpose: Closes the off-host samba share to which the backups are # sent for archival. # Receives: # $1: Fully-qualified local mount point. # Returns: # Standard Unix error codes (0 = success, else failure) # Example: # close_smb_share /mnt/server/share/ # Function Version: # 1.1 # History: # 10 FEB 2006 : # Originated this function. # 17 FEB 2006 : # Added a tag to error messages to indicate the message source per # requests for more informative and traceable error messages. ####################################################################### close_smb_share() { # Initialize local variables. local_smb_mount=${1:-$($ECHO_PATH "/mnt/$SMB_SERVER/$SMB_SHARE/" | $TR_PATH A-Z a-z)} local_smb_host=$($ECHO_PATH "/mnt/$SMB_SERVER" | $TR_PATH A-Z a-z) # Using CIFS? if [ "cifs" = $SMB_MODE ]; then $UMOUNT_PATH $local_smb_mount 2>/dev/null else # Attempt to let samba unmount the share. # Use umount as a backup since $SMBUMOUNT_PATH is known to randomly fail. if ! $SMBUMOUNT_PATH $local_smb_mount; then $UMOUNT_PATH $local_smb_mount 2>/dev/null fi fi # Attempt to remove the local mount point. if ! $RMDIR_PATH $local_smb_mount; then showerror "close_smb_share(): Unable to unmount the samba backup share, ${local_smb_mount}!" return 1 fi # Also attempt to remove the machine-name mount point (quietly fail # if other mounts are present). $RMDIR_PATH "$local_smb_host" 2>/dev/null # Exit with success. return 0 } ####################################################################### # end close_smb_share() ####################################################################### ####################################################################### # Function: open_smb_share # Purpose: Opens the off-host samba share to which the backup files # are sent for archival. Backup files are sent (effectively) to: # \\$SMB_SERVER\$SMB_SHARE\$SHORT_HOST_NAME\ # Receives: # $1: Samba server name; defaults to $SMB_SERVER. # $2: Samba share name on the indicated samba server; defaults to # $SMB_SHARE. # $3: Samba access username; defaults to $SMB_USER. # $4: Samba access password; defaults to $SMB_PASSWORD. # Returns: # Standard Unix error codes (0 = success, else failure) # Example: # open_smb_share Win2K3 Backups user password # Function Version: # 1.2 # History: # 10 FEB 2006 : # Originated this function. # 17 FEB 2006 : # Added a tag to error messages to indicate the message source per # requests for more informative and traceable error messages. # 28 JUL 2006 : # The current version of SMB isn't compatible with Windows 2003 # server domain controllers. An alternative, cifs, is optionally # used as a work-around. This new option is user-managed. ####################################################################### open_smb_share() { # Initialize local variables. samba_server=${1:-$SMB_SERVER} samba_share=${2:-$SMB_SHARE} samba_user=${3:-$SMB_USER} samba_pass=${4:-$SMB_PASSWORD} local_smb_mount=$($ECHO_PATH "/mnt/$samba_server/$samba_share" | $TR_PATH A-Z a-z) remote_smb_mount="//$samba_server/$samba_share" remote_cifs_mount="//$SMB_SERVER_IP/$samba_share" # Create the local mount point, then attempt to mount it. if makedirs "$local_smb_mount" "755"; then if [ "cifs" = $SMB_MODE ]; then if ! $MOUNT_PATH -t cifs "$remote_cifs_mount" "$local_smb_mount" -o "rw,username=${samba_user},password=${samba_pass}"; then showerror "open_smb_share(): Unable to mount the remote cifs backup share, ${remote_cifs_mount}!" return 1 fi else if ! $SMBMOUNT_PATH "$remote_smb_mount" "$local_smb_mount" -o "username=${samba_user}%${samba_pass},TTL=10000"; then showerror "open_smb_share(): Unable to mount the remote smbfs backup share, ${remote_smb_mount}!" return 1 fi fi else showerror "open_smb_share(): Unable to create the local mount point, ${local_smb_mount}!" return 1 fi # Exit with success. return 0 } ####################################################################### # end open_smb_share() ####################################################################### ####################################################################### # Function: stop_services # Purpose: Stops a set of system services as listed. # Receives: # $1: The delimited list of system services to stop, in order. # Returns: # Standard Unix error codes (0 = success, else failure) # Example: # stop_services "vsftpd httpd" # Function Version: # 1.2 # History: # 11 FEB 2006 : # Originated this function. # 15 FEB 2006 : # Enhanced for multiple attempts on failure and to report overall # status to the caller along with per-service failure notices to # the user. # 17 FEB 2006 : # Added a tag to error messages to indicate the message source per # requests for more informative and traceable error messages. ####################################################################### stop_services() { # Initialize local variables. result=0 stopped=false attempt=0 retries=$TRY_ATTEMPTS statusmsg= # There must be at least 1 attempt. if [ $retries -le 0 ]; then retries=1 fi # Do nothing if the service list is empty. if [ 0 -lt ${#1} ]; then # Stop each service in order, one at a time. for service_name in $1; do stopped=false attempt=$retries until [ $stopped == true ] || [ $attempt -eq 0 ]; do # Because service's exit code does NOT always correctly report # whether the service responds, it cannot be reliably wrapped # in an if. Look for actual effect instead of service exit code. $SERVICE_PATH $service_name stop >/dev/null statusmsg=$($SERVICE_PATH $service_name status | $TR_PATH -d "\a\b\f\n\r\t\v") # Check the service status message for known stopped reports. if [ 0 != $(stroccurs ' is stopped' "$statusmsg") ] \ || [ 0 != $(stroccurs 'FAILED' "$statusmsg") ] then stopped=true else showerror "stop_services(): $statusmsg" $SLEEP_PATH $RETRY_DELAY fi attempt=$((attempt-1)) done # If the service still failed after all that, notify the user. if [ ! $stopped ]; then showerror "stop_services(): $service_name failed to stop!" result=1 fi done fi # Report overall success status. return $result } ####################################################################### # end stop_services() ####################################################################### ####################################################################### # Function: start_services # Purpose: Starts a set of system services as listed. # Receives: # $1: The delimited list of system services to start, in order. # Returns: # Standard Unix error codes (0 = success, else failure) # Example: # start_services "httpd vsftpd" # Function Version: # 1.2 # History: # 11 FEB 2006 : # Originated this function. # 15 FEB 2006 : # Enhanced for multiple attempts on failure and to report overall # status to the caller along with per-service failure notices to # the user. # 17 FEB 2006 : # Added a tag to error messages to indicate the message source per # requests for more informative and traceable error messages. ####################################################################### start_services() { # Initialize local variables. result=0 started=false attempt=0 retries=$TRY_ATTEMPTS statusmsg= # There must be at least 1 attempt. if [ $retries -le 0 ]; then retries=1 fi # Do nothing if the service list is empty. if [ 0 -lt ${#1} ]; then # Start each service in order, one at a time. for service_name in $1; do started=false attempt=$retries until [ $started == true ] || [ $attempt -eq 0 ]; do # Because service's exit code does NOT always correctly # report whether the service responds, it cannot be reliably # wrapped in an if. Look for actual effect instead of service # exit code. $SERVICE_PATH $service_name start >/dev/null statusmsg=$($SERVICE_PATH $service_name status | $TR_PATH -d "\a\b\f\n\r\t\v") # Check the service status message for known running reports. if [ 0 != $(stroccurs ' is running' "$statusmsg") ] \ || [ 0 != $(stroccurs 'OK' "$statusmsg") ] then started=true else showerror "start_services(): $statusmsg" $SLEEP_PATH $RETRY_DELAY fi attempt=$((attempt-1)) done # If the service still failed after all that, notify the user. if [ ! $started ]; then showerror "start_services(): $service_name failed to start!" result=1 fi done fi # Report overall success status. return $result } ####################################################################### # end start_services() ####################################################################### ####################################################################### # Function: main (implied) # Purpose: This serves as the main insertion point for script # execution. # Receives: N/A # Returns: # Standard Unix error codes (0 = success, else failure) # History: # 10 FEB 2006 : # Originated this function. # 11 FEB 2006 : # Incorporated stop_services() and start_services(). # 17 FEB 2006 : # Applied the IGNORE_SERVICE_FAIL user option. # Incorporated more robust error handling and reporting with # improved user feedback per input from CLUE members. # Applied RUN_BEFORE_BACKUP, RUN_AFTER_BACKUP, and IGNORE_RUN_FAIL # to provide user-defined script hooks to manually handle service # control that is beyond the reach of RHEL/CentOS4's /service # mechanism. ####################################################################### # Initialize local variables. local_mount=$($ECHO_PATH "/mnt/$SMB_SERVER/$SMB_SHARE/" | $TR_PATH A-Z a-z) compress_type=$($ECHO_PATH "$COMPRESSION" | $TR_PATH A-Z a-z) tarball_name=$($ECHO_PATH "$SHORT_HOST_NAME.tar" | $TR_PATH A-Z a-z) shrunk_name="$tarball_name.$compress_type" workspace_path=$(add_last_slash $WORK_PATH) workspace_tar_fqn="$workspace_path$tarball_name" archive_path=$(add_last_slash $LOCAL_ARCHIVE_PATH) serviceflip=$(reverse_list "$SERVICE_LIST") workspace_zip_fqn= archive_file_fqn= smb_mount_point= use_samba=false tar_formed=false services_stopped=false exit_state=0 # Ensure that the backup file list exists. if [ ! -e $BACKUP_LIST_FILE ]; then showerror "main(): The backup list file, $BACKUP_LIST_FILE, is missing!" exit 1 fi # Prepare the temporary workspace (and clean up after any previous # failed attempts). makedirs $WORK_PATH 700 if [ -e "$workspace_tar_fqn" ]; then $RM_PATH -f "${workspace_tar_fqn}"* fi # Prepare the backup target directory based on whether samba will be used. if [ 0 != ${#SMB_SERVER} ] \ && [ 0 != ${#SMB_SHARE} ] \ && [ 0 != ${#SMB_USER} ] \ && [ 0 != ${#SMB_PASSWORD} ] then # Using samba; open the samba share. use_samba=true smb_mount_point=$($ECHO_PATH "/mnt/$SMB_SERVER/$SMB_SHARE/" | $TR_PATH A-Z a-z) # Close (quietly and for good measure), then reopen the samba share. close_smb_share "$local_mount" 2>/dev/null if ! open_smb_share $SMB_SERVER "$SMB_SHARE" $SMB_USER $SMB_PASSWORD; then # Unable to open the samba share. Report failure and bail; there # is little point in forming a backup archive that is going to # stay on the local machine. Terminate because the system services # have not yet been stopped and no mounts were created. showerror "main(): Samba failed to open mount $SMB_SHARE on $SMB_SERVER!" exit 1 fi # Set and prepare the backup path. archive_path="$(add_last_slash $(add_last_slash $smb_mount_point)$SMB_PATH_PREFIX)" makedirs $archive_path 755 else # Storing the backups locally; prepare the local path. use_samba=false archive_path=$(add_last_slash $LOCAL_ARCHIVE_PATH) makedirs $archive_path 700 fi # Stop system services to close and unlock files which may be read for # the backup. Try to use the service list first, then a user-defined # script. if stop_services "$SERVICE_LIST" || $IGNORE_SERVICE_FAIL; then # Either services yielded to the stop request, there are no services # listed, or the user has chosen to ignore service stop failure. # Run a user-specified command/script, if specified. if [ 0 != ${#RUN_BEFORE_BACKUP} ]; then if $RUN_BEFORE_BACKUP >/dev/null || $IGNORE_RUN_FAIL; then # Services have stopped by either or both means. services_stopped=true else # Services failed to yield to the user-defined script, the # script failed to execute, or the script failed to return # a success indicator. services_stopped=false fi else # There is no user-defined script and all services yielded to # service stop calls (or there were not services listed). services_stopped=true fi else # Services are specified and one or more failed to yield to service # stop calls. services_stopped=false fi # Run the backup only if services have stopped or the user has # indicated that it's okay to proceed in case of service failure. if $services_stopped; then # Perform the backup (form a tarball based on files in the external # BACKUP_LIST_FILE). Don't care about any exit-status from tar -- will # accept _anything_ that comes out because a perfect operation assumes # everything on the server is healthy, which may not always be the case. # If the system is unhealthy and breaks tar, but _something_ gets # preserved, then it is best to take whatever is possible. $TAR_PATH -cf "$workspace_tar_fqn" -T "$BACKUP_LIST_FILE" 2>/dev/null # Regardless of failure, look for _anything_ that may have been # preserved in the tarball. if [ -e "$workspace_tar_fqn" ]; then tar_formed=true else # tar produced no output file. Report the failure, but do not # exit -- must restore the system to operational status. tar_formed=false showerror "main(): The backup catalog, $workspace_tar_fqn, failed to copy files in $BACKUP_LIST_FILE!" exit_state=1 fi else # Error: One or more services failed to yield; do not exit -- must # restore the system to operational status. showerror "main(): Unable to form the backup archive because one or more services failed to yield to a stop request and IGNORE_SERVICE_FAIL is not set to true!" tar_formed=false exit_state=1 fi # Run a user-defined script before running the service restart list, # if there is one. if [ 0 != ${#RUN_AFTER_BACKUP} ]; then $RUN_AFTER_BACKUP >/dev/null fi # Restart system services in reverse order immediately following the tar # operation to bring the server back up ASAP (waiting until after # compressions adds too much delay). if ! start_services "$serviceflip"; then # One or more services failed to restart. Report, but continue to # form and preserve the backup archive. showerror "main(): One or more services failed to restart! The backup operation is proceeding anyway." exit_state=1 fi # Attempt to compress the backup archive, if it exists, per user preferences. if $tar_formed; then # Compress the backup tarball based on user preferences. case $compress_type in bz2) $BZIP2_PATH -z9 "$workspace_tar_fqn" 2>/dev/null ;; gz) $GZIP_PATH -9 "$workspace_tar_fqn" 2>/dev/null ;; *) shrunk_name="$tarball_name" esac # Given the user-selected compression type, establish fully-qualified # archive paths. workspace_zip_fqn="$workspace_path$shrunk_name" archive_file_fqn="$archive_path$shrunk_name" # Check that the working file exists. if [ -e "$workspace_zip_fqn" ]; then # Rotate the backup archive, if it already exists. if [ -e "$archive_file_fqn" ]; then rotatefiles "$archive_file_fqn" 2 $BACKUP_COUNT $INDEX_PAD fi # Attempt to move the backup archive off-host and clean up any leftovers. if [ -e "$archive_file_fqn" ]; then # The target off-host archive file still exists after rotation. showerror "main(): The archive file, $workspace_zip_fqn, failed to rotate! The new archive file, $workspace_zip_fqn, could not be moved off-host." exit_state=1 elif [ -e "$workspace_zip_fqn" ]; then # The compressed archive exists and the target is available. $MV_PATH "$workspace_zip_fqn" "$archive_file_fqn" $RM_PATH -f "${workspace_tar_fqn}"* else # The compressed archive file does not exist. This check is # deliberately redundant for thorough style. showerror "main(): The compressed backup archive, $workspace_zip_fqn, was not found and has not been moved off-host!" exit_state=1 fi else # The compressed archive file does not exist (the tarball failed # to compress). showerror "main(): The backup catalog, $workspace_tar_fqn, failed compression to $workspace_zip_fqn!" exit_state=1 fi fi # Close the samba share, if it was used. if $use_samba; then # Attempt to close the samba mount. if ! close_smb_share "$local_mount"; then # Samba failed to release the share. Report to the user. showerror "main(): Unable to close the samba mount at $local_mount!" exit_state=1 fi fi # Exit, reporting success/fail. exit $exit_state ####################################################################### # end main() (implied) #######################################################################