#!/bin/csh -f # backup_laptop # --------------------------------------------------------------------------- # Shell script to backup all modified files to a new named or timestamped # directory that is initially empty, as well as into a fully populated # backup tree, optionally creating/updating a timestamp file to record when # this was done, optionally stopping the MySQL, Apache, MongoDB and Tomcat # servers while files are being copied, and finally copying the backed up # files as compressed tarballs to the backup server. # --------------------------------------------------------------------------- # Usage: See Usage section below or run with --help to see usage. # Assumptions: # Effects: # - Does backup of files to admin2 server # - Optionally updates time/date of timestamp file # - Optionally stops and restarts MySQL, Apache, MongoDB and Tomcat servers # Notes: # Implementation Notes: # - See note in cpmod about race condition. Files added or changed while # this script is running may be missed by the sparse backups. The full # backup will handle them properly. # - Advantages over Mac "Time Machine": # - Backs up to Web not USB drive. No external drive to plug in. # - Keeps local copy of backup files for easy compare/recover. # - Keeps backup files as normal files so you can compare, copy, etc. # No special recovery software needed. # Portability Issues: # Revision History: # $Log$ # --------------------------------------------------------------------------- # Add the folder containing this script to the path so the scripts it calls # can be found. This is necessary when run via sudo because sudo starts # with a "safe" path of only: # /usr/bin /bin # Note: Add it as an absolute path, not relative, so that any cd command # below doesn't change its meaning. # If the name doesn't start with a slash or a tilde, it is a relative # name, so prefix it with the current working directory to make it # absolute. # Note: Don't use any other scripts (like normalize_filename) to make the # path absolute. Until the path is updated, they may not be found. set absolute_folder_name = "$0:h" set firstchar = "`echo ${absolute_folder_name:q} | cut -c 1`" if ("$firstchar" != "/" && "$firstchar" != "~") then set absolute_folder_name = "$cwd/$absolute_folder_name" endif set path = ($path $absolute_folder_name) echo "Adding to end of PATH: '$absolute_folder_name'" # ##################### # Get and check options # ##################### set username = "`whoami`" set default_source_root = "${HOME}/${username}" if ("$1" == "-h" || "$1" == "--help") then echo "Usage: $0:t [options] [server_username] [source_root] [backup_root]" echo "Options: (must occur in this order, if multiple options present):" echo " -h = Show this help text" echo " --help = Show this help text" echo " --no_update = Do not update timestamp file after copying" echo " --no_restart = Do not stop and restart Tomcat/Apache/MySQL servers" echo " This is currently the default anyhow" echo " --no_tar_full = Do not create a tar archive of the full backup" echo " Instead, copy individual files to server" echo " This is currently the default anyhow" echo "server_username = Username to login to backup server." echo " Default: Username on your laptop" echo "source_root = Root of tree to backup files from." echo " Default: ${default_source_root}" echo "backup_root = Root of tree to backup files to." echo " = Default: /Backup{source_root}" echo "Note: Do NOT use values with embedded spaces. I haven't yet added" echo " :q to all variable references to quote values with spaces." echo "Note: The default value for source_root" echo " is: ${default_source_root}" echo " not: ${HOME}" echo " Files in ${HOME} are not backed up; only files in" echo " ${default_source_root}. If necessary, move additional files" echo " and directories that are required to exist in your home" echo " directory into this subdirectory, and create symbolic links" echo " to them from your home directory. For example:" echo " % cp ~${username}/.cshrc ~${username}/${username}/.cshrc" echo " % rm ~${username}/.cshrc" echo " % ln -s ${username}/.cshrc ~${username}/.cshrc" echo " Note: Use cp and rm, not mv, so that the file's timestamp is" echo " updated which causes the file to be found by the next" echo " backup into a timestamped directory." echo " If you prefer, you can specify a different source_root, even" echo " your entire home directory, but beware of using huge amounts of" echo " disk space. Try to limit yourself to 5-10 GB. Use the du" echo " command to measure your disk usage: % du {source_root}" echo "Note: The default value for backup_root is:" echo " /Backup{source_root}" echo " so you'll end up with backup trees like:" echo " /Backup${default_source_root}/2010_08_03__19_24_43/..." echo " /Backup${default_source_root}/2010_08_04__20_32_56/..." echo " /Backup${default_source_root}/full/..." echo " If you prefer, you can use a different backup_root, but don't" echo " put the backup_root inside the source_root." exit 1 endif set timestamp_file_relative = ".$0:t.timestamp" set option_update = "-u ${timestamp_file_relative}" if ("$1" == "--no_update") then set option_update = "-n ${timestamp_file_relative}" shift endif #set option_restart = "yes" set option_restart = "no" # This option controls whether the servers are shut down to avoid locked # files or partially written files. # Note: Doesn't seem to be necessary yet. MySQL database is copied fine # when there is no activity. It is not locked against reading. # Doing a backup in the middle of the night is probably safe for now. # Once we have a production database that might be hit around the # clock, we may want to shut down the MySQL server (and other servers # that hold connections to it), to avoid getting a partially written # file. On the other hand, can we afford to take a production # server down? Really, what we'll have to do is find a way to backup # the MySQL server w/o taking it down. Or export data from it and # back that up, skipping the live DB files during the backup. # For now, just assume the --no_restart option. if ("$1" == "--no_restart") then set option_restart = "no" shift endif #set option_tar_full = "yes" set option_tar_full = "no" # This option controls whether the the full backup is tarred and copied to # the server or just rsync'ed to the server as individual files. # Note: For now, it makes more sense to rsync the files. Much less I/O # than copying the tarred version of lots of stuff that hasn't changed. # For now, just assume the --no_tar_full option. if ("$1" == "--no_tar_full") then set option_tar_full = "no" shift endif set server_username = "$1" if ("${server_username}" == "") then set server_username = "${username}" endif set source_root = "$2" if ("${source_root}" == "") then set source_root = "${default_source_root}" endif set backup_root = "$3" if ("${backup_root}" == "") then set backup_root = "/Backup${source_root}" endif set full_root_relative = "full" set full_root_absolute = "${backup_root}/${full_root_relative}" set logs_root_relative = "logs" set logs_root_absolute = "${backup_root}/${logs_root_relative}" set server_name = "admin2.visibiz.com" set server_backup_root = "./LaptopBackup/${source_root}" set server_full_root = "${server_backup_root}/${full_root_relative}" set server_logs_root = "${server_backup_root}/${logs_root_relative}" set timestamp = `date "+%Y_%m_%d__%H_%M_%S"` set sparse_root = "${backup_root}/${timestamp}" # ##################### # Done checking options # ##################### if (-e "${sparse_root}") then beep echo -n "${sparse_root} already exists. Overwrite (y/n)?" set overwrite = $< if ("$overwrite" != "y") exit 1 endif if (! -d ${backup_root}) then echo "*******************************************************************" echo "Creating ${backup_root}. Sudo will prompt for your Mac password..." echo "sudo mkdir -p -v ${backup_root}" sudo mkdir -p -v ${backup_root} set rc = $status if ($rc != 0) then beep "Error during sudo mkdir." exit $rc endif echo "sudo chown `whoami` ${backup_root}" sudo chown `whoami` ${backup_root} set rc = $status if ($rc != 0) then beep "Error during sudo chown." exit $rc endif echo "...Creating ${backup_root}" echo "*******************************************************************" endif # Shut down the servers to avoid locked files or partially written files. if ("${option_restart}" == "yes") then echo "*******************************************************************" echo "Stopping servers..." echo "??This section should be updated to stop whatever servers you " echo "??have running on your Mac. For now, it is skipped by default." echo "/etc/init.d/httpd stop" /etc/init.d/httpd stop echo "/etc/init.d/tomcat5 stop" /etc/init.d/tomcat5 stop echo "/etc/init.d/mysqld stop" /etc/init.d/mysqld stop sleep 10 echo "...Stopping servers" echo "*******************************************************************" echo "" echo "" endif # Copy other files to back up that don't already reside in ${source_root} #?? Not needed usually. Commented out for now. Left as comments in case #?? you have such a need on your Mac. #??echo "*******************************************************************" #??echo "Copying to ${copies_root}..." #??mkdir -p -v ${copies_root}/etc #??# --del = Delete files from destination if missing from source #??rsyncupdate --del /etc/ ${copies_root}/etc #??set rc = $status #??if ($rc != 0) then #?? beep "Error copying /etc to ${copies_root}" #?? exit $rc #??endif #??mkdir -p -v ${copies_root}/var/log/httpd #??# Note: Do not use --del option here. We want the old logs to accumulate. #??rsyncupdate /var/log/httpd/ ${copies_root}/var/log/httpd #??set rc = $status #??if ($rc != 0) then #?? beep "Error copying /var/log/httpd to ${copies_root}" #?? exit $rc #??endif #??echo "...Copying to ${copies_root}" #??echo "*******************************************************************" #??echo "" #??echo "" #??echo "" #??echo "" #??echo "" #??echo "" #??echo "" #??echo "" #??echo "" #??echo "" # Backup to sparsely populated tree with timestamp for name, as a daily # incremental backup, so we can recover from different recent days. echo "*******************************************************************" echo "Copying to ${sparse_root}..." echo "mkdir -p -v ${sparse_root}" mkdir -p -v ${sparse_root} set rc = $status if ($rc != 0) then beep "Error creating directory ${sparse_root}" exit $rc endif echo "cpmod ${option_update} ${source_root} ${sparse_root}" cpmod ${option_update} ${source_root} ${sparse_root} set rc = $status if ($rc != 0) then beep "Error copying ${source_root} to ${sparse_root}" exit $rc endif ## This is the old way, necessary when backups were being done into the /ebs ## tree and we needed to avoid trying to copy /ebs into /ebs/backup: ## # Backup all ebs files/folders except: ## # . ## # ./backup ## # ./lost+found ## pushd /ebs ## foreach i (`find . -maxdepth 1 | grep -v -E '^(.|./backup|./lost\+found)$'`) ## mkdir -p -v "${sparse_root}/$i" ## cpmod ${option_update} "$i" "${sparse_root}/$i" ## set rc = $status ## if ($rc != 0) then ## beep "Error copying $i to ${sparse_root}" ## exit $rc ## endif ##end echo "...Copying to ${sparse_root}" echo "*******************************************************************" echo "" echo "" echo "" echo "" echo "" echo "" echo "" echo "" echo "" echo "" # Backup into fully populated tree of all of the latest files, overwriting # previous day's backups in this tree. echo "*******************************************************************" echo "Copying to ${full_root_absolute}..." if (! -d ${full_root_absolute}) then echo "mkdir -p -v ${full_root_absolute}" mkdir -p -v ${full_root_absolute} set rc = $status if ($rc != 0) then beep "Error creating directory ${full_root_absolute}" exit $rc endif endif echo "rsyncupdate --del ${source_root}/ ${full_root_absolute}" rsyncupdate --del ${source_root}/ ${full_root_absolute} # --del = Delete files from destination if missing from source ## This is the old way, necessary when backups were being done into the /ebs ## tree and we needed to avoid trying to copy /ebs into /ebs/backup: ## rsyncupdate --del --exclude /ebs/backup /ebs ${full_root_absolute} ##?? May need to change to /backup instead of /ebs/backup -- relative to ##?? the source of the copy? set rc = $status if ($rc != 0) then beep "Error copying ${source_root} to ${full_root_absolute}" exit $rc endif echo "...Copying to ${full_root_absolute}" echo "*******************************************************************" echo "" echo "" echo "" echo "" echo "" echo "" echo "" echo "" echo "" echo "" if (! -d ${logs_root_absolute}) then echo "*******************************************************************" echo "Creating ${logs_root_absolute}..." echo "mkdir -p -v ${logs_root_absolute}" mkdir -p -v ${logs_root_absolute} set rc = $status if ($rc != 0) then beep "Error during mkdir." exit $rc endif echo "...Creating ${logs_root_absolute}" echo "*******************************************************************" endif set du_log = ${logs_root_absolute}/disk_usage_${timestamp}.log echo "*******************************************************************" echo "Creating ${du_log}..." echo "du -xk ${source_root} > ${du_log}" echo "du -xk ${source_root}" > "${du_log}" du -xk "${source_root}" >> "${du_log}" # -x = Skip subdirectories that are mounted filesytems like /ebs and /ebs2 # -k = Units of kBytes set rc = $status if ($rc != 0) then beep "Error during du. Check file ${du_log}." exit $rc endif echo "...Creating ${du_log}" echo "*******************************************************************" echo "" echo "" set df_log = ${logs_root_absolute}/free_space_${timestamp}.log echo "*******************************************************************" echo "Creating ${df_log}..." echo "df -h | tee ${df_log}" echo "df -h" > "${df_log}" df -h | tee -a "${df_log}" set rc = $status if ($rc != 0) then beep "Error during df." exit $rc endif echo "...Creating ${df_log}" echo "*******************************************************************" echo "" echo "" set find_log = ${logs_root_absolute}/file_list_${timestamp}.log echo "*******************************************************************" echo "Creating ${find_log}..." echo "find ${source_root} -ls | sort -f -k 11 > ${find_log}" echo "find ${source_root} -ls | sort -f -k 11" > "${find_log}" find "${source_root}" -ls | sort -f -k 11 >> "${find_log}" # -ls = Show ls -l format where filename is fields 11+ # -f = Ignore case during sort # -k 11 = Sort by fields 11+ set rc = $status if ($rc != 0) then beep "Error during find. Check file ${find_log}." exit $rc endif echo "...Creating ${find_log}" echo "*******************************************************************" echo "" echo "" # Re-start the servers if we stopped them. # From here on, we are working only from backup copies of the files not # from the originals, so it's OK if the originals are changing. if ("${option_restart}" == "yes") then echo "*******************************************************************" echo "Re-starting servers..." echo "??This section should be updated to start whatever servers you " echo "??have running on your Mac. For now, it is skipped by default." echo "/etc/init.d/mysqld start" /etc/init.d/mysqld start sleep 10 echo "/etc/init.d/httpd start" /etc/init.d/httpd start sleep 10 echo "/etc/init.d/tomcat5 start" /etc/init.d/tomcat5 start echo "...Re-starting servers" echo "*******************************************************************" echo "" echo "" endif # Backup into compressed tarball for copying to a remote backup site. # Note: The current directory is changed to the parent of the directory to # be archived so that the archive will contain names relative to the # parent. cd ${backup_root} set child = ${timestamp} set archive = ${child}.tar.gz echo "*******************************************************************" echo "Creating archive ${cwd}/${archive}" echo "from directory tree ${cwd}/${child}..." echo "tar cp -f ${archive} -z ${child}" tar cp -f ${archive} -z ${child} set rc = $status if ($rc != 0) then beep "Error during tar. Check file ${cwd}/${archive}." exit $rc endif echo "...Creating archive ${cwd}/${archive}" echo " from directory tree ${cwd}/${child}" echo "*******************************************************************" echo "" echo "" if ("${option_tar_full}" == "yes") then # Note: The current directory is changed to the parent of the directory to # be archived so that the archive will contain names relative to the # parent. cd ${backup_root} set child = "${full_root_relative}" set archive = ${child}.tar.gz echo "*******************************************************************" echo "Creating archive ${cwd}/${archive}" echo "from directory tree ${cwd}/${child}..." echo "tar cp -f ${archive} -z ${child}" tar cp -f ${archive} -z ${child} set rc = $status if ($rc != 0) then beep "Error during tar. Check file ${cwd}/${archive}." exit $rc endif echo "...Creating archive ${cwd}/${archive}" echo " from directory tree ${cwd}/${child}" echo "*******************************************************************" echo "" echo "" endif echo "*******************************************************************" echo "Copying tarball and logs to ${server_name}..." echo "ssh ${server_username}@${server_name} mkdir -p -v ${server_backup_root}" ssh ${server_username}@${server_name} mkdir -p -v ${server_backup_root} set rc = $status if ($rc != 0) then beep "Error during ssh to create directory ${server_backup_root} on server." exit $rc endif echo "ssh ${server_username}@${server_name} mkdir -p -v ${server_logs_root}" ssh ${server_username}@${server_name} mkdir -p -v ${server_logs_root} set rc = $status if ($rc != 0) then beep "Error during ssh to create directory ${server_logs_root} on server." exit $rc endif echo "scp ${backup_root}/*.tar.gz ${server_username}@${server_name}:${server_backup_root}/" scp ${backup_root}/*.tar.gz ${server_username}@${server_name}:${server_backup_root}/ set rc = $status if ($rc != 0) then beep "Error during scp of archives to server." exit $rc endif echo "scp ${logs_root_absolute}/*.log ${server_username}@${server_name}:${server_logs_root}/" scp ${logs_root_absolute}/*.log ${server_username}@${server_name}:${server_logs_root}/ set rc = $status if ($rc != 0) then beep "Error during scp of logs to server." exit $rc endif echo "...Copying tarball and logs to ${server_name}" echo "*******************************************************************" echo "" echo "" echo "*******************************************************************" echo "Deleting tarballs and logs locally..." echo "rm -v ${backup_root}/*.tar.gz ${logs_root_absolute}/*.log" rm -v ${backup_root}/*.tar.gz ${logs_root_absolute}/*.log set rc = $status if ($rc != 0) then beep "Error during rm." exit $rc endif echo "...Deleting tarballs and logs locally" echo "*******************************************************************" echo "" echo "" if ("${option_tar_full}" != "yes") then # Rsync the full backup tree directly to the server, overwriting previous # day's backups on the server. echo "*******************************************************************" echo "Copying from ${full_root_absolute}" echo "to ${server_name}:${server_full_root}..." echo "ssh ${server_username}@${server_name} mkdir -p -v ${server_full_root}" ssh ${server_username}@${server_name} mkdir -p -v ${server_full_root} set rc = $status if ($rc != 0) then beep "Error during ssh to create directory ${server_full_root} on server." exit $rc endif echo "rsyncupdate --del ${full_root_absolute}/ ${server_username}@${server_name}:${server_full_root}" rsyncupdate --del ${full_root_absolute}/ ${server_username}@${server_name}:${server_full_root} # --del = Delete files from destination if missing from source set rc = $status if ($rc != 0) then beep "Error copying ${full_root_absolute} to ${server_name}:${server_full_root}" exit $rc endif echo "...Copying from ${full_root_absolute}" echo " to ${server_name}:${server_full_root}" echo "*******************************************************************" echo "" echo "" endif echo "*******************************************************************" echo "Successful completion of $0:t." echo "*******************************************************************" exit $rc