Permalink
Find file
executable file 703 lines (613 sloc) 24.2 KB
#!/bin/bash
set -eo pipefail
#
# MongoDB Backup Script
# VER. 0.20
# More Info: http://github.com/micahwedemeyer/automongobackup
# Note, this is a lobotomized port of AutoMySQLBackup
# (http://sourceforge.net/projects/automysqlbackup/) for use with
# MongoDB.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
#=====================================================================
#=====================================================================
# Set the following variables to your system needs
# (Detailed instructions below variables)
#=====================================================================
# Database name to specify a specific database only e.g. myawesomeapp
# Unnecessary if backup all databases
# DBNAME=""
# Collections name list to include e.g. system.profile users
# DBNAME is required
# Unecessary if backup all collections
# COLLECTIONS=""
# Collections to exclude e.g. system.profile users
# DBNAME is required
# Unecessary if backup all collections
# EXCLUDE_COLLECTIONS=""
# Username to access the mongo server e.g. dbuser
# Unnecessary if authentication is off
# DBUSERNAME=""
# Password to access the mongo server e.g. password
# Unnecessary if authentication is off
# DBPASSWORD=""
# Database for authentication to the mongo server e.g. admin
# Unnecessary if authentication is off
# DBAUTHDB=""
# Host name (or IP address) of mongo server e.g localhost
DBHOST="127.0.0.1"
# Port that mongo is listening on
DBPORT="27017"
# Backup directory location e.g /backups
BACKUPDIR="/var/backups/mongodb"
# Mail setup
# What would you like to be mailed to you?
# - log : send only log file
# - files : send log file and sql files as attachments (see docs)
# - stdout : will simply output the log to the screen if run manually.
# - quiet : Only send logs if an error occurs to the MAILADDR.
MAILCONTENT="stdout"
# Set the maximum allowed email size in k. (4000 = approx 5MB email [see docs])
export MAXATTSIZE="4000"
# Email Address to send mail to? (user@domain.com)
# MAILADDR=""
# ============================================================================
# === SCHEDULING AND RETENTION OPTIONS ( Read the doc's below for details )===
#=============================================================================
# Do you want to do hourly backups? How long do you want to keep them?
DOHOURLY="no"
HOURLYRETENTION=24
# Do you want to do daily backups? How long do you want to keep them?
DODAILY="yes"
DAILYRETENTION=0
# Which day do you want weekly backups? (1 to 7 where 1 is Monday)
DOWEEKLY="yes"
WEEKLYDAY=6
WEEKLYRETENTION=4
# Do you want monthly backups? How long do you want to keep them?
DOMONTHLY="yes"
MONTHLYRETENTION=4
# ============================================================
# === ADVANCED OPTIONS ( Read the doc's below for details )===
#=============================================================
# Choose Compression type. (gzip or bzip2)
COMP="gzip"
# Choose if the uncompressed folder should be deleted after compression has completed
CLEANUP="yes"
# Additionally keep a copy of the most recent backup in a seperate directory.
LATEST="yes"
# Make Hardlink not a copy
LATESTLINK="yes"
# Use oplog for point-in-time snapshotting.
OPLOG="yes"
# Choose other Server if is Replica-Set Master
REPLICAONSLAVE="yes"
# Allow DBUSERNAME without DBAUTHDB
REQUIREDBAUTHDB="yes"
# Maximum files of a single backup used by split - leave empty if no split required
# MAXFILESIZE=""
# Command to run before backups (uncomment to use)
# PREBACKUP=""
# Command run after backups (uncomment to use)
# POSTBACKUP=""
#=====================================================================
# Options documentation
#=====================================================================
# Set DBUSERNAME and DBPASSWORD of a user that has at least SELECT permission
# to ALL databases.
#
# Set the DBHOST option to the server you wish to backup, leave the
# default to backup "this server".(to backup multiple servers make
# copies of this file and set the options for that server)
#
# You can change the backup storage location from /backups to anything
# you like by using the BACKUPDIR setting..
#
# The MAILCONTENT and MAILADDR options and pretty self explanatory, use
# these to have the backup log mailed to you at any email address or multiple
# email addresses in a space seperated list.
#
# (If you set mail content to "log" you will require access to the "mail" program
# on your server. If you set this to "files" you will have to have mutt installed
# on your server. If you set it to "stdout" it will log to the screen if run from
# the console or to the cron job owner if run through cron. If you set it to "quiet"
# logs will only be mailed if there are errors reported. )
#
#
# Finally copy automongobackup.sh to anywhere on your server and make sure
# to set executable permission. You can also copy the script to
# /etc/cron.daily to have it execute automatically every night or simply
# place a symlink in /etc/cron.daily to the file if you wish to keep it
# somwhere else.
#
# NOTE: On Debian copy the file with no extention for it to be run
# by cron e.g just name the file "automongobackup"
#
# Thats it..
#
#
# === Advanced options ===
#
# To set the day of the week that you would like the weekly backup to happen
# set the WEEKLYDAY setting, this can be a value from 1 to 7 where 1 is Monday,
# The default is 6 which means that weekly backups are done on a Saturday.
#
# Use PREBACKUP and POSTBACKUP to specify Pre and Post backup commands
# or scripts to perform tasks either before or after the backup process.
#
#
#=====================================================================
# Backup Rotation..
#=====================================================================
#
# Hourly backups are executed if DOHOURLY is set to "yes".
# The number of hours backup copies to keep for each day (i.e. 'Monday', 'Tuesday', etc.) is set with DHOURLYRETENTION.
# DHOURLYRETENTION=0 rotates hourly backups every day (i.e. only the most recent hourly copy is kept). -1 disables rotation.
#
# Daily backups are executed if DODAILY is set to "yes".
# The number of daily backup copies to keep for each day (i.e. 'Monday', 'Tuesday', etc.) is set with DAILYRETENTION.
# DAILYRETENTION=0 rotates daily backups every week (i.e. only the most recent daily copy is kept). -1 disables rotation.
#
# Weekly backups are executed if DOWEEKLY is set to "yes".
# WEEKLYDAY [1-7] sets which day a weekly backup occurs when cron.daily scripts are run.
# Rotate weekly copies after the number of weeks set by WEEKLYRETENTION.
# WEEKLYRETENTION=0 rotates weekly backups every week. -1 disables rotation.
#
# Monthly backups are executed if DOMONTHLY is set to "yes".
# Monthy backups occur on the first day of each month when cron.daily scripts are run.
# Rotate monthly backups after the number of months set by MONTHLYRETENTION.
# MONTHLYRETENTION=0 rotates monthly backups upon each execution. -1 disables rotation.
#
#=====================================================================
# Please Note!!
#=====================================================================
#
# I take no resposibility for any data loss or corruption when using
# this script.
#
# This script will not help in the event of a hard drive crash. You
# should copy your backups offline or to another PC for best protection.
#
# Happy backing up!
#
#=====================================================================
# Restoring
#=====================================================================
# ???
#
#=====================================================================
# Change Log
#=====================================================================
# VER 0.11 - (2016-05-04) (author: Claudio Prato)
# - Fixed bugs in select_secondary_member() with authdb enabled
# - Fixed bugs in Compression function by removing the * symbol
# - Added incremental backup feature
# - Added option to select the collections to backup
#
# VER 0.10 - (2015-06-22) (author: Markus Graf)
# - Added option to backup only one specific database
#
# VER 0.9 - (2011-10-28) (author: Joshua Keroes)
# - Fixed bugs and improved logic in select_secondary_member()
# - Fixed minor grammar issues and formatting in docs
#
# VER 0.8 - (2011-10-02) (author: Krzysztof Wilczynski)
# - Added better support for selecting Secondary member in the
# Replica Sets that can be used to take backups without bothering
# busy Primary member too much.
#
# VER 0.7 - (2011-09-23) (author: Krzysztof Wilczynski)
# - Added support for --journal dring taking backup
# to enable journaling.
#
# VER 0.6 - (2011-09-15) (author: Krzysztof Wilczynski)
# - Added support for --oplog during taking backup for
# point-in-time snapshotting.
# - Added filter for "mongodump" writing "connected to:"
# on the standard error, which is not desirable.
#
# VER 0.5 - (2011-02-04) (author: Jan Doberstein)
# - Added replicaset support (don't Backup on Master)
# - Added Hard Support for 'latest' Copy
#
# VER 0.4 - (2010-10-26)
# - Cleaned up warning message to make it clear that it can
# usually be safely ignored
#
# VER 0.3 - (2010-06-11)
# - Added the DBPORT parameter
# - Changed USERNAME and PASSWORD to DBUSERNAME and DBPASSWORD
# - Fixed some bugs with compression
#
# VER 0.2 - (2010-05-27) (author: Gregory Barchard)
# - Added back the compression option for automatically creating
# tgz or bz2 archives
# - Added a cleanup option to optionally remove the database dump
# after creating the archives
# - Removed unnecessary path additions
#
# VER 0.1 - (2010-05-11)
# - Initial Release
#
# VER 0.2 - (2015-09-10)
# - Added configurable backup rentention options, even for
# monthly backups.
#
#=====================================================================
#=====================================================================
#=====================================================================
#
# Should not need to be modified from here down!!
#
#=====================================================================
#=====================================================================
#=====================================================================
shellout () {
if [ -n "$1" ]; then
echo "$1"
exit 1
fi
exit 0
}
# External config - override default values set above
for x in default sysconfig; do
if [ -f "/etc/$x/automongobackup" ]; then
# shellcheck source=/dev/null
source /etc/$x/automongobackup
fi
done
# Include extra config file if specified on commandline, e.g. for backuping several remote dbs from central server
# shellcheck source=/dev/null
[ ! -z "$1" ] && [ -f "$1" ] && source ${1}
#=====================================================================
PATH=/usr/local/bin:/usr/bin:/bin
DATE=$(date +%Y-%m-%d_%Hh%Mm) # Datestamp e.g 2002-09-21
HOD=$(date +%s) # Current timestamp for PITR backup
DOW=$(date +%A) # Day of the week e.g. Monday
DNOW=$(date +%u) # Day number of the week 1 to 7 where 1 represents Monday
DOM=$(date +%d) # Date of the Month e.g. 27
M=$(date +%B) # Month e.g January
W=$(date +%V) # Week Number e.g 37
VER=0.11 # Version Number
LOGFILE=$BACKUPDIR/$DBHOST-$(date +%H%M).log # Logfile Name
LOGERR=$BACKUPDIR/ERRORS_$DBHOST-$(date +%H%M).log # Logfile Name
OPT="" # OPT string for use with mongodump
OPTSEC="" # OPT string for use with mongodump in select_secondary_member function
QUERY="" # QUERY string for use with mongodump
HOURLYQUERY="" # HOURLYQUERY string for use with mongodump
# Do we need to use a username/password?
if [ "$DBUSERNAME" ]; then
OPT="$OPT --username=$DBUSERNAME --password=$DBPASSWORD"
if [ "$REQUIREDBAUTHDB" = "yes" ]; then
OPT="$OPT --authenticationDatabase=$DBAUTHDB"
fi
fi
# Do we need to use a username/password for ReplicaSet Secondary Members Selection?
if [ "$DBUSERNAME" ]; then
OPTSEC="$OPTSEC --username=$DBUSERNAME --password=$DBPASSWORD"
if [ "$REQUIREDBAUTHDB" = "yes" ]; then
OPTSEC="$OPTSEC --authenticationDatabase=$DBAUTHDB"
fi
fi
# Do we use oplog for point-in-time snapshotting?
if [ "$OPLOG" = "yes" ] && [ "$DBNAME" != "yes" ]; then
OPT="$OPT --oplog"
fi
# Do we need to backup only a specific database?
if [ "$DBNAME" ]; then
OPT="$OPT -d $DBNAME"
fi
# Do we need to backup only a specific collections?
if [ "$COLLECTIONS" ]; then
for x in $COLLECTIONS; do
OPT="$OPT --collection $x"
done
fi
# Do we need to exclude collections?
if [ "$EXCLUDE_COLLECTIONS" ]; then
for x in $EXCLUDE_COLLECTIONS; do
OPT="$OPT --excludeCollection $x"
done
fi
# Do we use a filter for hourly point-in-time snapshotting?
if [ "$DOHOURLY" == "yes" ]; then
# getting PITR START timestamp
# shellcheck disable=SC2012
[ "$COMP" = "gzip" ] && HOURLYQUERY=$(ls -t $BACKUPDIR/hourly | head -n 1 | cut -d '.' -f3)
# setting the start timestamp to NOW for the first execution
if [ -z "$HOURLYQUERY" ]; then
QUERY=""
else
# limit the documents included in the output of mongodump
# shellcheck disable=SC2016
QUERY='{ "ts" : { $gt : Timestamp('$HOURLYQUERY', 1) } }'
fi
fi
# Create required directories
mkdir -p $BACKUPDIR/{hourly,daily,weekly,monthly} || shellout 'failed to create directories'
if [ "$LATEST" = "yes" ]; then
rm -rf "$BACKUPDIR/latest"
mkdir -p "$BACKUPDIR/latest" || shellout 'failed to create directory'
fi
# Do we use a filter for hourly point-in-time snapshotting?
if [ "$DOHOURLY" == "yes" ]; then
# getting PITR START timestamp
# shellcheck disable=SC2012
[ "$COMP" = "gzip" ] && HOURLYQUERY=$(ls -t $BACKUPDIR/hourly | head -n 1 | cut -d '.' -f3)
# setting the start timestamp to NOW for the first execution
if [ -z "$HOURLYQUERY" ]; then
QUERY=""
else
# limit the documents included in the output of mongodump
# shellcheck disable=SC2016
QUERY='{ "ts" : { $gt : Timestamp('$HOURLYQUERY', 1) } }'
fi
fi
# Check for correct sed usage
if [ "$(uname -s)" = 'Darwin' ] || [ "$(uname -s)" = 'FreeBSD' ]; then
SED="sed -i ''"
else
SED="sed -i"
fi
# IO redirection for logging.
touch "$LOGFILE"
exec 6>&1 # Link file descriptor #6 with stdout.
# Saves stdout.
exec > "$LOGFILE" # stdout replaced with file $LOGFILE.
touch "$LOGERR"
exec 7>&2 # Link file descriptor #7 with stderr.
# Saves stderr.
exec 2> "$LOGERR" # stderr replaced with file $LOGERR.
# When a desire is to receive log via e-mail then we close stdout and stderr.
[ "x$MAILCONTENT" == "xlog" ] && exec 6>&- 7>&-
# Functions
# Database dump function
dbdump () {
if [ -n "$QUERY" ]; then
# filter for point-in-time snapshotting and if DOHOURLY=yes
# shellcheck disable=SC2086
mongodump --quiet --host=$DBHOST:$DBPORT --out="$1" $OPT -q "$QUERY"
else
# all others backups type
# shellcheck disable=SC2086
mongodump --quiet --host=$DBHOST:$DBPORT --out="$1" $OPT
fi
[ -e "$1" ] && return 0
echo "ERROR: mongodump failed to create dumpfile: $1" >&2
return 1
}
#
# Select first available Secondary member in the Replica Sets and show its
# host name and port.
#
function select_secondary_member {
# We will use indirect-reference hack to return variable from this function.
local __return=$1
# Return list of with all replica set members
# shellcheck disable=SC2086
members=( $(mongo --quiet --host $DBHOST:$DBPORT --eval 'rs.conf().members.forEach(function(x){ print(x.host) })' $OPTSEC ) )
# Check each replset member to see if it's a secondary and return it.
if [ ${#members[@]} -gt 1 ]; then
for member in "${members[@]}"; do
is_secondary=$(mongo --quiet --host "$member" --eval 'rs.isMaster().secondary' $OPTSEC )
case "$is_secondary" in
'true') # First secondary wins ...
secondary=$member
break
;;
'false') # Skip particular member if it is a Primary.
continue
;;
*) # Skip irrelevant entries. Should not be any anyway ...
continue
;;
esac
done
fi
if [ -n "$secondary" ]; then
# Ugly hack to return value from a Bash function ...
# shellcheck disable=SC2086
eval $__return="'$secondary'"
fi
}
if [ -n "$MAXFILESIZE" ]; then
write_file() {
split --bytes "$MAXFILESIZE" --numeric-suffixes - "${1}-"
}
else
write_file() {
cat > "$1"
}
fi
# Compression function plus latest copy
compression () {
SUFFIX=""
dir=$(dirname "$1")
file=$(basename "$1")
if [ -n "$COMP" ]; then
[ "$COMP" = "gzip" ] && SUFFIX=".tgz"
[ "$COMP" = "bzip2" ] && SUFFIX=".tar.bz2"
echo Tar and $COMP to "$file$SUFFIX"
cd "$dir" || return 1
tar -cf - "$file" | $COMP --stdout | write_file "${file}${SUFFIX}"
cd - >/dev/null || return 1
else
echo "No compression option set, check advanced settings"
fi
if [ "$LATEST" = "yes" ]; then
if [ "$LATESTLINK" = "yes" ];then
COPY="ln"
else
COPY="cp"
fi
$COPY "$1$SUFFIX" "$BACKUPDIR/latest/"
fi
if [ "$CLEANUP" = "yes" ]; then
echo Cleaning up folder at "$1"
rm -rf "$1"
fi
return 0
}
# Run command before we begin
if [ "$PREBACKUP" ]; then
echo ======================================================================
echo "Prebackup command output."
echo
eval "$PREBACKUP"
echo
echo ======================================================================
echo
fi
# Hostname for LOG information
if [ "$DBHOST" = "localhost" ] || [ "$DBHOST" = "127.0.0.1" ]; then
HOST=$(hostname)
if [ "$SOCKET" ]; then
OPT="$OPT --socket=$SOCKET"
fi
else
HOST=$DBHOST
fi
# Try to select an available secondary for the backup or fallback to DBHOST.
if [ "x${REPLICAONSLAVE}" == "xyes" ]; then
# Return value via indirect-reference hack ...
select_secondary_member secondary
if [ -n "$secondary" ]; then
DBHOST=${secondary%%:*}
DBPORT=${secondary##*:}
else
SECONDARY_WARNING="WARNING: No suitable Secondary found in the Replica Sets. Falling back to ${DBHOST}."
fi
fi
echo ======================================================================
echo AutoMongoBackup VER $VER
if [ ! -z "$SECONDARY_WARNING" ]; then
echo
echo "$SECONDARY_WARNING"
fi
echo
echo "Backup of Database Server - $HOST on $DBHOST"
echo ======================================================================
echo "Backup Start $(date)"
echo ======================================================================
# Monthly Full Backup of all Databases
if [[ $DOM = "01" ]] && [[ $DOMONTHLY = "yes" ]]; then
echo Monthly Full Backup
echo
# Delete old monthly backups while respecting the set rentention policy.
if [[ $MONTHLYRETENTION -ge 0 ]] ; then
NUM_OLD_FILES=$(find $BACKUPDIR/monthly -depth -not -newermt "$MONTHLYRETENTION month ago" -type f | wc -l)
if [[ $NUM_OLD_FILES -gt 0 ]] ; then
echo Deleting "$NUM_OLD_FILES" global setting backup file\(s\) older than "$MONTHLYRETENTION" month\(s\) old.
find $BACKUPDIR/monthly -not -newermt "$MONTHLYRETENTION month ago" -type f -delete
fi
fi
FILE="$BACKUPDIR/monthly/$DATE.$M"
# Weekly Backup
elif [[ "$DNOW" = "$WEEKLYDAY" ]] && [[ "$DOWEEKLY" = "yes" ]] ; then
echo Weekly Backup
echo
if [[ $WEEKLYRETENTION -ge 0 ]] ; then
# Delete old weekly backups while respecting the set rentention policy.
NUM_OLD_FILES=$(find $BACKUPDIR/weekly -depth -not -newermt "$WEEKLYRETENTION week ago" -type f | wc -l)
if [[ $NUM_OLD_FILES -gt 0 ]] ; then
echo Deleting "$NUM_OLD_FILES" global setting backup file\(s\) older than "$WEEKLYRETENTION" week\(s\) old.
find $BACKUPDIR/weekly -not -newermt "$WEEKLYRETENTION week ago" -type f -delete
fi
fi
FILE="$BACKUPDIR/weekly/week.$W.$DATE"
# Daily Backup
elif [[ $DODAILY = "yes" ]] ; then
echo Daily Backup of Databases
echo
# Delete old daily backups while respecting the set rentention policy.
if [[ $DAILYRETENTION -ge 0 ]] ; then
NUM_OLD_FILES=$(find $BACKUPDIR/daily -depth -not -newermt "$DAILYRETENTION days ago" -type f | wc -l)
if [[ $NUM_OLD_FILES -gt 0 ]] ; then
echo Deleting "$NUM_OLD_FILES" global setting backup file\(s\) made in previous weeks.
find "$BACKUPDIR/daily" -not -newermt "$DAILYRETENTION days ago" -type f -delete
fi
fi
FILE="$BACKUPDIR/daily/$DATE.$DOW"
# Hourly Backup
elif [[ $DOHOURLY = "yes" ]] ; then
echo Hourly Backup of Databases
echo
# Delete old hourly backups while respecting the set rentention policy.
if [[ $HOURLYRETENTION -ge 0 ]] ; then
NUM_OLD_FILES=$(find $BACKUPDIR/hourly -depth -not -newermt "$HOURLYRETENTION hour ago" -type f | wc -l)
if [[ $NUM_OLD_FILES -gt 0 ]] ; then
echo "Deleting $NUM_OLD_FILES global setting backup file\(s\) made in previous weeks."
find $BACKUPDIR/hourly -not -newermt "$HOURLYRETENTION hour ago" -type f -delete
fi
fi
FILE="$BACKUPDIR/hourly/$DATE.$DOW.$HOD"
# convert timestamp to date: echo $TIMESTAMP | gawk '{print strftime("%c", $0)}'
fi
# FILE will not be set if no frequency is selected.
if [[ -z "$FILE" ]] ; then
echo "ERROR: No backup frequency was chosen."
echo "Please set one of DOHOURLY,DODAILY,DOWEEKLY,DOMONTHLY to \"yes\""
exit 1
fi
dbdump "$FILE" && compression "$FILE"
echo ----------------------------------------------------------------------
echo "Backup End Time $(date)"
echo ======================================================================
echo Total disk space used for backup storage..
echo Size - Location
du -hs "$BACKUPDIR"
echo
echo ======================================================================
# Run command when we're done
if [ "$POSTBACKUP" ]; then
echo ======================================================================
echo "Postbackup command output."
echo
eval "$POSTBACKUP"
echo
echo ======================================================================
fi
# Clean up IO redirection if we plan not to deliver log via e-mail.
[ ! "x$MAILCONTENT" == "xlog" ] && exec 1>&6 2>&7 6>&- 7>&-
if [ -s "$LOGERR" ]; then
eval "$SED" "/^connected/d" "$LOGERR"
fi
if [ "$MAILCONTENT" = "log" ]; then
mail -s "Mongo Backup Log for $HOST - $DATE" "$MAILADDR" < "$LOGFILE"
if [ -s "$LOGERR" ]; then
cat "$LOGERR"
mail -s "ERRORS REPORTED: Mongo Backup error Log for $HOST - $DATE" "$MAILADDR" < "$LOGERR"
fi
else
if [ -s "$LOGERR" ]; then
cat "$LOGFILE"
echo
echo "###### WARNING ######"
echo "STDERR written to during mongodump execution."
echo "The backup probably succeeded, as mongodump sometimes writes to STDERR, but you may wish to scan the error log below:"
cat "$LOGERR"
else
cat "$LOGFILE"
fi
fi
# TODO: Would be nice to know if there were any *actual* errors in the $LOGERR
STATUS=0
if [ -s "$LOGERR" ]; then
STATUS=1
fi
# Clean up Logfile
rm -f "$LOGFILE" "$LOGERR"
exit $STATUS