| {%- from "xtrabackup/map.jinja" import server with context %} |
| {%- from "xtrabackup/map.jinja" import client with context %} |
| |
| #!/bin/bash |
| # |
| # Script to create full and incremental backups (for all databases on server) using innobackupex from Percona. |
| # http://www.percona.com/doc/percona-xtrabackup/innobackupex/innobackupex_script.html |
| # |
| # Every time it runs will generate an incremental backup except for the first time (full backup). |
| # FULLBACKUPLIFE variable will define your full backups schedule. |
| set -eo pipefail |
| |
| usage () { |
| echo "" |
| echo "USAGE: " |
| echo " innobackupex-runner.sh [-s -f -h -c]" |
| echo " -s makes the script to skip the cleanup" |
| echo " -f forces the script to run the full backup instead of an incremental one" |
| echo " -h shows this help" |
| echo " -c run only cleanup (no backup will be done)" |
| } |
| |
| SKIPCLEANUP="false" |
| FORCEFULL="false" |
| USEROPTIONS="--user={{ client.database.user }} --password={{ client.database.password }}{%- if client.database.host is defined %} --host {{ client.database.host }} --port {{ client.database.get('port', '3306') }}{%- else %} --socket=/var/run/mysqld/mysqld.sock{%- endif %}" |
| #TMPFILE="/var/log/backups/innobackupex-runner.$$.tmp" |
| LOGDIR="/var/log/backups" |
| TMPFILE="/var/log/backups/innobackupex-runner.log" |
| MYCNF="/etc/mysql/my.cnf" |
| MYSQL="/usr/bin/mysql" |
| MYSQLADMIN="/usr/bin/mysqladmin" |
| BACKUPDIR="{{ client.backup_dir }}" # Client side backups base directory |
| SERVERBACKUPDIR="{{ server.backup_dir }}" # Server side backups base directory |
| SERVERBACKUPHOST="{{ client.target.host }}" |
| FULLBACKUPDIR="$BACKUPDIR/full" # Full backups directory |
| INCRBACKUPDIR="$BACKUPDIR/incr" # Incremental backups directory |
| KEEP="{{ client.full_backups_to_keep }}" # Number of full backups (and its incrementals) to keep |
| {%- if client.backup_times is defined %} |
| INCRBEFOREFULL="{{ client.incr_before_full }}" |
| {%- else %} |
| HOURSFULLBACKUPLIFE="{{ client.hours_before_full }}" # Lifetime of the latest full backup in hours |
| FULLBACKUPLIFE="$(( $HOURSFULLBACKUPLIFE * 60 * 60 ))" |
| {%- endif %} |
| rsyncLog="/var/log/backups/innobackupex-rsync.log" |
| {%- if client.compression is defined %} |
| compression="{{ client.compression }}" |
| {%- else %} |
| compression="false" |
| {%- endif %} |
| {%- if client.rsync_tables is defined %} |
| rsyncTables="--rsync" |
| {%- else %} |
| rsyncTables="" |
| {%- endif %} |
| {%- if client.compression_threads is defined %} |
| compression_threads="{{ client.compression_threads }}" |
| {%- else %} |
| compression_threads="1" |
| {%- endif %} |
| {%- if client.throttle is defined and client.throttle|int > 0 %} |
| throttle="--throttle {{ client.throttle }}" |
| {%- else %} |
| throttle="" |
| {%- endif %} |
| {%- if client.backup_times is defined %} |
| BACKUPTIME="true" |
| {%- endif %} |
| |
| # Grab start time |
| STARTED_AT="$(date +%s)" |
| |
| ############################################################################# |
| # Display error message and exit |
| ############################################################################# |
| error() |
| { |
| echo "$1" 1>&2 |
| exit 1 |
| } |
| |
| function prerequisites() |
| { |
| # Check prerequisites before proceeding |
| if [ ! -d $BACKUPDIR ]; then |
| error "Backup destination directory: $BACKUPDIR does not exist." |
| fi |
| |
| if [ -z "$($MYSQLADMIN $USEROPTIONS status | grep 'Uptime')" ] ; then |
| error "HALTED: MySQL does not appear to be running." |
| fi |
| |
| if ! $(echo 'exit' | $MYSQL -s $USEROPTIONS) ; then |
| error "HALTED: Supplied mysql username or password appears to be incorrect (not copied here for security, see script)." |
| fi |
| |
| # Some info output |
| echo "----------------------------" |
| echo "$0: MySQL backup script" |
| echo "started: $(date)" |
| echo |
| |
| if [ ! -d $LOGDIR ]; then |
| echo "Creating destination directory $LOGDIR." |
| mkdir -p $LOGDIR |
| fi |
| if [ ! -d $FULLBACKUPDIR ]; then |
| echo "Creating destination directory $FULLBACKUPDIR". |
| mkdir -p $FULLBACKUPDIR |
| fi |
| if [ ! -d $INCRBACKUPDIR ]; then |
| echo "Creating destination directory $INCRBACKUPDIR". |
| mkdir -p $INCRBACKUPDIR |
| fi |
| |
| # Space check |
| if [ $(df $BACKUPDIR | awk '{ print $5 }' | tail -n1 | cut -d '%' -f1) -gt 95 ]; then |
| error "There is not enough space on disk." |
| fi |
| |
| } |
| |
| function backup() |
| { |
| # Find latest full backup |
| LATEST_FULL="$(find $FULLBACKUPDIR -mindepth 1 -maxdepth 1 -type d -printf "%P\n" | sort -nr | head -1)" |
| |
| # Get latest backup last modification time |
| LATEST_FULL_CREATED_AT="$(stat -c %Y $FULLBACKUPDIR/$LATEST_FULL)" |
| |
| # If compression is enabled, pass it on to the backup command |
| if [ "$compression" = True ]; then |
| compress="--compress" |
| compression_threads="--compress-threads=$compression_threads" |
| echo "Setting compression to True" |
| echo |
| else |
| compress= |
| compression_threads= |
| fi |
| |
| if [ "$BACKUPTIME" != true ]; then |
| # Run an incremental backup if latest full is still valid. Otherwise, run a new full one. |
| if [ "$LATEST_FULL" -a $(expr $LATEST_FULL_CREATED_AT + $FULLBACKUPLIFE + 5) -ge $STARTED_AT ] && [ "$FORCEFULL" != true ] ; then |
| # Create incremental backups dir if not exists. |
| TMPINCRDIR="$INCRBACKUPDIR/$LATEST_FULL" |
| mkdir -p $TMPINCRDIR |
| |
| # Find latest incremental backup. |
| LATEST_INCR=$(find $TMPINCRDIR -mindepth 1 -maxdepth 1 -type d | sort -nr | head -1) |
| |
| # If this is the first incremental, use the full as base. Otherwise, use the latest incremental as base. |
| if [ ! $LATEST_INCR ] ; then |
| INCRBASEDIR=$FULLBACKUPDIR/$LATEST_FULL |
| else |
| INCRBASEDIR=$LATEST_INCR |
| fi |
| |
| echo "Running new incremental backup using $INCRBASEDIR as base." |
| innobackupex --defaults-file=$MYCNF $USEROPTIONS $rsyncTables $throttle $compress $compression_threads --incremental $TMPINCRDIR --incremental-basedir $INCRBASEDIR 2>&1 | tee $TMPFILE |
| else |
| echo "Running new full backup." |
| innobackupex --defaults-file=$MYCNF $USEROPTIONS $rsyncTables $throttle $compress $compression_threads $FULLBACKUPDIR 2>&1 | tee $TMPFILE |
| fi |
| else |
| # Get number of full and incremental backups |
| NUMBER_OF_FULL="$(find $FULLBACKUPDIR -maxdepth 1 -mindepth 1 -type d -print| wc -l)" |
| NUMBER_OF_INCR="$(find $INCRBACKUPDIR -maxdepth 2 -mindepth 2 -type d -print| wc -l)" |
| echo "Number of Full backups stored: " $NUMBER_OF_FULL |
| echo "Number of Incremental backups stored: " $NUMBER_OF_INCR |
| echo "----------------------------" |
| #If number of incremental mod number of full backups to keep equals 1, run full backup, otherwise run incremental |
| if [ $(( ($NUMBER_OF_INCR + $NUMBER_OF_FULL) % ($INCRBEFOREFULL + 1) )) -eq 0 ] || [ "$FORCEFULL" == true ] ; then |
| echo "Running new full backup." |
| innobackupex --defaults-file=$MYCNF $USEROPTIONS $rsyncTables $compress $compression_threads $FULLBACKUPDIR 2>&1 | tee $TMPFILE |
| else |
| # Create incremental backups dir if not exists. |
| TMPINCRDIR=$INCRBACKUPDIR/$LATEST_FULL |
| mkdir -p $TMPINCRDIR |
| |
| # Find latest incremental backup. |
| LATEST_INCR=$(find $TMPINCRDIR -mindepth 1 -maxdepth 1 -type d | sort -nr | head -1) |
| |
| # If this is the first incremental, use the full as base. Otherwise, use the latest incremental as base. |
| if [ ! $LATEST_INCR ] ; then |
| INCRBASEDIR=$FULLBACKUPDIR/$LATEST_FULL |
| else |
| INCRBASEDIR=$LATEST_INCR |
| fi |
| |
| echo "Running new incremental backup using $INCRBASEDIR as base." |
| innobackupex --defaults-file=$MYCNF $USEROPTIONS $rsyncTables $compress $compression_threads --incremental $TMPINCRDIR --incremental-basedir $INCRBASEDIR 2>&1 | tee $TMPFILE |
| fi |
| fi |
| if [ -z "$(tail -1 $TMPFILE | grep 'completed OK!')" ] ; then |
| echo "$INNOBACKUPEX failed:"; echo |
| echo "---------- ERROR OUTPUT from $INNOBACKUPEX ----------" |
| cat $TMPFILE |
| exit 1 |
| fi |
| THISBACKUP="$(awk -- "/Backup created in directory/ { split( \$0, p, \"'\" ) ; print p[2] }" $TMPFILE)" |
| |
| echo "Databases backed up successfully to: $THISBACKUP" |
| echo |
| |
| # rsync just the new or modified backup files |
| if [ ! -z "$SERVERBACKUPHOST" ]; then |
| echo "Adding ssh-key of remote host to known_hosts" |
| if [ -f ~/.ssh/known_hosts ]; then |
| ssh-keygen -R $SERVERBACKUPHOST 2>&1 | tee $rsyncLog |
| fi |
| ssh-keyscan $SERVERBACKUPHOST >> ~/.ssh/known_hosts 2>&1 | tee $rsyncLog |
| echo "Rsyncing files to remote host" |
| /usr/bin/rsync -rhtPpv --rsync-path=rsync --progress $BACKUPDIR/* -e ssh xtrabackup@$SERVERBACKUPHOST:$SERVERBACKUPDIR 2>&1 | tee $rsyncLog |
| |
| # Check if the rsync succeeded or failed |
| if ! grep -q "rsync error: " $rsyncLog; then |
| echo "Rsync to remote host completed OK" |
| else |
| echo "Rsync to remote host FAILED" |
| exit 1 |
| fi |
| fi |
| } |
| function cleanup_backup() |
| { |
| # Cleanup |
| if [ "$SKIPCLEANUP" == false ] ; then |
| if [ "$BACKUPTIME" != true ]; then |
| echo "----------------------------" |
| echo "Cleanup. Keeping only $KEEP full backups and its incrementals." |
| AGE=$(($FULLBACKUPLIFE * $KEEP / 60)) |
| find $FULLBACKUPDIR -maxdepth 1 -type d -mmin +$AGE -execdir echo "removing: "$FULLBACKUPDIR/{} \; -execdir rm -rf $FULLBACKUPDIR/{} \; -execdir echo "removing: "$INCRBACKUPDIR/{} \; -execdir rm -rf $INCRBACKUPDIR/{} \; |
| |
| echo |
| echo "completed: $(date)" |
| exit 0 |
| else |
| echo "----------------------------" |
| echo "Cleanup. Keeping only $KEEP full backups and its incrementals." |
| |
| NUMBER_OF_FULL="$(( $(find $FULLBACKUPDIR -maxdepth 1 -type d -print| wc -l) - 1))" |
| FULL_TO_DELETE="$(( $NUMBER_OF_FULL - $KEEP ))" |
| echo "Found $NUMBER_OF_FULL full backups and $KEEP should be kept. This $FULL_TO_DELETE will be deleted." |
| if [ $FULL_TO_DELETE -gt 0 ] ; then |
| cd $INCRBACKUPDIR |
| ls -t $FULLBACKUPDIR | tail -n -$FULL_TO_DELETE | xargs -d '\n' rm -rf |
| cd $FULLBACKUPDIR |
| ls -t | tail -n -$FULL_TO_DELETE | xargs -d '\n' rm -rf |
| else |
| echo "There are less full backups than required, not deleting anything." |
| fi |
| fi |
| else |
| echo "----------------------------" |
| echo "-s parameter passed. Cleanup was not triggered" |
| fi |
| } |
| |
| while getopts ":sfhc" opt; do |
| case $opt in |
| s) |
| echo "Cleanup will be skipped" >&2 |
| SKIPCLEANUP="true" |
| ;; |
| c) |
| echo "Cleanup backups" >&2 |
| cleanup_backup |
| exit 0 |
| ;; |
| f) |
| echo "Full backup will be force triggered" |
| FORCEFULL="true" |
| ;; |
| h) |
| usage |
| exit 0 |
| ;; |
| \?) |
| echo "Invalid option: -$OPTARG" >&2 |
| usage |
| exit 1 |
| ;; |
| esac |
| done |
| |
| prerequisites |
| backup |
| cleanup_backup |