Rolling Database Backups

Danny Wahl's picture Danny Wahl  •   •  DevOps

A few weeks back I decided to try to frustrate myself beyond belief by trying to write a bash script that would (among other things) dump a database every hour.  That idea quickly evolved to keeping the older backups, which turned into setting a cap on the maximum amount of old backups to keep, which then added needing to know which backup was which.  If you’re confused reading this, welcome to my world trying to write it.

I was eventually able to write it (for PostgreSQL) and then port it to MySQL – acutally fairly easily – and in the process find out about some more limitations of MySQL.  So for anyone looking to do something similar to what I wanted, here’s the scripts, followed by some notes.  I’m planning on doing a writeup of my entire script sometime – as the DB backup is just a small (albeit the most complex) part of it.

PostgreSQL

#!/bin/bash
dbcount=24  
dbnames=( "database1" "database2" "database3" )  
dbusers=( "dbuser1" "dbuser2" "dbuser3" )  
domains=( "domain1" "domain2" "domain3" )  
domain_path="/var/www/"  
backup_path="/backup/"  
short_date=`date +"%y%m%d-%H"`  
# Dumping Databases
for (( i = 0 ; i <= `expr ${#domains[@]} \- 1`; i++ ))  
do  
  cd $domain_path${domains[$i]}$backup_path
  for (( c = `expr $dbcount \- 1` ; c >=0; c-- ))
  do
    n=`expr $c \+ 1`
    while test "${#n}" -lt 2
    do
      n="0$n"
    done
    d=$c
    while test "${#d}" -lt 2
    do
      d="0$d"
    done
    file=`ls | grep -E ".($d).(tar)$"`
    if [[ $file =~ $date_regex ]]
    then
      if [ -f "$file" ]
      then
        mv $file ${dbnames[$i]}.${BASH_REMATCH[0]}.$n.tar
      fi
    fi
  done
  pg_dump -U ${dbusers[$i]} -Ft ${dbnames[$i]} > ${dbnames[$i]}.$short_date.00.tar
  old_file=`ls | grep -E ".($dbcount).(tar)$"`
  if [[ $old_file =~ $date_regex ]]
  then
    if [ -f "$old_file" ]
    then
      rm $old_file
    fi
  fi
done  

You’ll notice by looking at this script that there aren’t any passwords involved, and that’s because it relies on reading the passwords from .pgpass.  You’ll also notice if you try to run the script that it complains you need root.  The reason for that is that I use root’s .pgpass file to store the passwords rather than the normal user just for a layer of security – as somebody that gets access to my standard account can’t just go grab my DB passwords and walk away – it is clear text after all.  You could drop the root check and move the pgpass file to ~ if you’re feeling lucky.

MySQL

#!/bin/bash
dbcount=24  
dbnames=( "database1" "database2" "database3" )  
domains=( "domain1" "domain2" "domain3" )  
domain_path="/var/www/"  
backup_path="/backup/"  
short_date=`date +"%y%m%d-%H"`  
date_regex="[0-9]{6}\-[0-9]{2}"  
# Dumping Databases
for (( i = 0 ; i <= `expr ${#domains[@]} \- 1`; i++ ))  
do  
  cd $domain_path${domains[$i]}$backup_path
  for (( c = `expr $dbcount \- 1` ; c >=0; c-- ))
  do
    n=`expr $c \+ 1`
    while test "${#n}" -lt 2
    do
      n="0$n"
    done
    d=$c
    while test "${#d}" -lt 2
    do
      d="0$d"
    done
    file=`ls | grep -E ".($d).(sql)$"`
    if [[ $file =~ $date_regex ]]
    then
      if [ -f "$file" ]
      then
        mv $file ${dbnames[$i]}.${BASH_REMATCH[0]}.$n.sql
      fi
    fi
  done
  mysqldump -B ${dbnames[$i]} > ${dbnames[$i]}.$short_date.00.sql
  old_file=`ls | grep -E ".($dbcount).(sql)$"`
  if [[ $old_file =~ $date_regex ]]
  then
    if [ -f "$old_file" ]
    then
      rm $old_file
    fi
  fi
done  

This script is very very similar to the Postgres one – and that’s because it was ported from the Postgres script.  The main difference is that the database owner array is gone, and that’s because my.cnf only supports one account – so you need a credential set that has access to all of your databases you want to dump, e.g. Root.

Notes

Well those are the scripts, and they should look pretty straight-forward, it loops through your databases that are associated with a domain, and dumps them to a folder you specify for that domain.  E.g. domain_1_db would be dumped to /path/to/domain_1/path/to/backup, or example_db would be backed up to /var/www/example/backup if that was where it was really stored. Your databases are also named with the regex you define for $shore_date which by default is YYMMDD-HH.##.ext the ## is 00-$dbcount (default is 23) 00 is the newest dump and 23 is the oldest. Anything older than that gets deleted.