Take the 2-minute tour ×
Server Fault is a question and answer site for system and network administrators. It's 100% free, no registration required.

So I wrote up a short script that I am planning to run via a cron job daily to package up my site files and send them to a remote location. I also plan to incorporate DB dumps, but I have not gotten that far yet.

My issue today however is that Im am uncertain how to log the output of each command for errors, warnings, or other pertinent information the command may output. I would also like to install sometype of fail safe so if something goes horribly wrong the script will stop dead in its tracks and notify me via email or something. Ok the email thing is not as critical, but would be nice.

Does anybody have any ideas for that? Here is what I have so far.

By the way, both servers are CentOS 6.2 running standard LAMP.

#!/bin/sh

#################################
### Set Vars
#################################
THEDATE=`date +%m%d%y%H%M`

#################################
### Create Archives
#################################
tar -cf /root/backups/files/server_BAK_${THEDATE}.tar -C / var/www/vhosts
gzip /root/backups/files/server_BAK_${THEDATE}.tar

#################################
### Send Data to Remote Server
#################################
scp /root/backups/files/server_BAK_${THEDATE}.tar.gz user@host:/home/bak1/ftp/backups/

#################################
### Remove Data from this Server
#################################
rm -rf /root/backups/files/server_BAK_${THEDATE}.tar.gz
share|improve this question
1  
You are deleting only one file for security remove 'r' option from rm –  B14D3 Nov 2 '12 at 6:35
    
Thanks for the tip. I took it out. Security is important –  Brian Dainis Nov 2 '12 at 21:28

3 Answers 3

up vote 0 down vote accepted

A little disorganized answer, but just read through it, please.

To make your script stop in its tracks when there is any kind of error:

set -e

This will make sure that if any of your commands fail, the script will fail right there. Other good ideas:

function error_handler() {
   # This will get run when an error is detected
}
trap error_handler ERR

This will run the error_handler when a command fails. You can also have an EXIT handler to run code every time your script exits.

To catch all output of your script to a file:

exec &> some_log_file

To also be able to see the commands that your script executes in the log file (great for troubleshooting):

set -x

To make your script throw warnings about undefined variables:

set -u

To sum it up, start your script with:

trap error_handler ERR
exec &> some_log_file
set -eux

If you are running the script from cron, it will send you an email if there is any output from your script. To only get the email when there is a problem, put a cat some_log_file in the error handler and you are set.

Update: To answer your other questions.

  • To prevent an error from breaking the whole script you can run the command in an if statement. Like if ! <some command>; then <what do you do if it fails, or possibly nothing>; fi
  • If you have trap error_handler you also need to define a function error_handler.
  • Shuffling set -eu and set -eux is not a bad idea, but...
  • ...if you are worried about security you should not put the mysql password on the command line (anyone who runs pgrep -lf mysql will see it). Use an option file instead. And then you don't need to worry about passwords in the log files.

    option_file=`mktemp`
    cat > $option_file <<EOF
    [client]
    password = $DB_PASSWD
    user     = $DB_USER
    EOF
    

    And then run mysqldump with --default-extra-path=$option_file. And don't forget to delete $option_file when you don't need it anymore.

  • Use rsync instead of scp. It works much better for copying relatively large files.
share|improve this answer
    
It looks like this will overwrite the log file everytime. Is there a way to use cat or something to have it add on to the log file everytime. I took out the -x since I dont need the commands. Just errors. Also, anyway to get time stamps on that too. I modified the script. Ill add it below. –  Brian Dainis Nov 2 '12 at 21:06
    
I got another question about this. It seems if a file changes such as an error_log or access_log while the file is being archived, it throws an error and stops the entire script from completing. Any idea how I can stop that from happening? –  Brian Dainis Nov 3 '12 at 20:56
    
@BrianDainis, I updated my answer. –  chutz Dec 22 '12 at 3:53

You can redirect error for each command to specific file.

grep da * 2> grep-errors.txt

This redirects errors from to grep to file grep-errors.txt.

grep da * 1>&2

This will cause the stderr ouput of a program to be written to the same filedescriptor than stdout.

rm -rf * 2>&1 This will cause the stderr ouput of a program to be written to the same filedescriptor than stdout

rm -f $(find / -name core) &> /dev/null

This will place every output of a program to a file. This is suitable sometimes for cron entries, if you want a command to pass in absolute silence. Ofcourse you can replace /dev/null with some file and write all output to it.

Thats for logging errors. I know that it's simple backup script but you are trying to reinvent wheel once again. And in my opinion you should check Bacula. It's quite large solution for backuping only one server but it will do all you request in question. Buckup, notify etc and many more. You will have one of the best backup solution. Setting it up for only one or few servers is quite simple and you will do it in few hours.

If you still want to use your script then you should redirect all errors to stdout after adding this script to cron it will email you every output from that script. You should check only /etc/aliases for root entry and write your mail there like this :

root: your@mail.com

share|improve this answer

I made some updates to the script. Here is the new one. Still working on it though,

EDIT: So I made even more adjustments to the script to incorporate an iterated backup of the databases. Below is the most recent version. I am in the process of testing it now.

My new questions is this: I dont want to show passwords for the database on the log file, so I set the first "set" to "set -eu". But I decided I do want to see what commands are running in the script on the log file, so after the DB iteration and dump is complete I reset "set" to "set -eux". Is this good practice what I did? Also, how can I show timestamps on the log file?

#/bin/sh

#################################
### Set Vars
#################################
LOCALBAKTMP='/root/backups/files'                            ###~~~PLACE WHERE TMP FILES GO
LOCALBAKLOC='var/www/vhosts'                                 ###~~~DIRECTORY TO BACKUP (NO SLASH IN FRONT)
REMOTEUSR='user'                                             ###~~~USER AT REMOTE SERVER
REMOTEHOST='host'                                            ###~~~REMOTE HOST ADDRESS
REMOTEDEST='/home/bak1/ftp/backups/'                         ###~~~FILE DESTINATION AT REMOTE
LOGDIR='/root/backups/logs/'                                 ###~~~LOCAL BACKUP LOG DIRECTORY
DELLOGSDAYS='30'                                             ###~~~DAYS TO STORE LOG FILES
THEDATE=`date +%m%d%y%H%M`                                   ###~~~DATE VARIABLE
DB_BACKUP="/root/backups/files/dbs"                          ###~~~DB BACKUP LOCATION
LOCALDBLOC="root/backups/files/dbs"                          ###~~~DB FILES TO BAK
DB_USER="dbuser"                                             ###~~~DB ROOT USER
DB_PASSWD="dbpassword"                                       ###~~~DB ROOT PASS
HN=`hostname | awk -F. '{print $1}'`                         ###~~~DO NOT CHANGE

#################################
### Set Logging
#################################
trap error_handler ERR
exec &> ${LOGDIR}bak_${THEDATE}.log
set -eu

#################################
### Dump MySQL
#################################
for db in $(mysql --user=$DB_USER --password=$DB_PASSWD -e 'show databases' -s --skip-column-names|grep -vi information_schema);
do mysqldump --user=$DB_USER --password=$DB_PASSWD --opt $db | gzip > "$DB_BACKUP/mysqldump-$HN-$db-$(date +%Y-%m-%d).sql.gz";
done
#Log commands for the rest of the script
set -eux

#################################
### Create Archives
#################################
tar -cf ${LOCALBAKTMP}/server_BAK_${THEDATE}.tar -C / ${LOCALBAKLOC}
gzip ${LOCALBAKTMP}/server_BAK_${THEDATE}.tar
tar -cf ${LOCALBAKTMP}/db_BAK_${THEDATE}.tar -C / ${LOCALDBLOC}
gzip ${LOCALBAKTMP}/db_BAK_${THEDATE}.tar
tar -cfz full_BAK_${THEDATE}.tar.gz *_BAK_${THEDATE}.tar.gz

#################################
### Send Data to Remote Server
#################################
scp ${LOCALBAKTMP}/full_BAK_${THEDATE}.tar.gz ${REMOTEUSR}@${REMOTEHOST}:${REMOTEDEST}

#################################
### Remove Data from this Server
#################################
rm -f ${LOCALBAKTMP}/*_BAK_${THEDATE}.tar.gz
rm -rf ${DB_BACKUP}/*.sql.gz
find ${LOGDIR}* -mtime +${DELLOGSDAYS} -exec rm {} \;
share|improve this answer
    
If you have more questions you may want to update your question instead of adding them as an answer. Honestly, comments are not very comfortable for answering questions. –  chutz Dec 22 '12 at 3:37

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.