mongodb_backup2s3.erb 5.04 KB
#!/bin/bash
#
# This file was generated by CHEF. Changes will be overwritten!

# Backup script for MongoDB in AWS EC2 instances.
#
# If this mongod instance is primary, then backup the
# designated database/s and upload it to S3. Encrypt first if needed.
#
# Depends on:
#   MongoDB
#   AWSCLI
#   OpenSSL

set -e

suffix=.mongodb_backup2s3
if [ -f /tmp/*"$suffix" ] ; then
    ( >&2 echo "[ERROR] Another operation might still be in progress" )
    exit 200
fi
tmp_file=$( mktemp --suffix "$suffix" )

bin_aws=<%= @bin_aws %>
bin_mongo=<%= @bin_mongo %>
bin_mongodump=<%= @bin_mongodump %>
bin_openssl=<%= @bin_openssl %>

db_host=<%= @db_host %>
db_port=<%= @db_port %>
db_user='<%= @backup_user %>'
db_pass='<%= @backup_pass %>'
db_auth=<%= @backup_auth %>

s3_bucket=<%= @s3_bucket %>
s3_region=<%= @s3_region %>

pub_key=<%= @pub_key %>
<% bak_dir = "#{Chef::Config[:file_cache_path]}/mongodb_backup2s3" -%>
bak_dir=<%= bak_dir %>

# Perform the actual mongodump
# Args:
#   $1 = db name
#   $2 = backup dump filename, e.g. 'mydb'
export_db() {
    echo "$(date) : Export database ${1} to ${bak_dir}."
    rm -f "${bak_dir}/${2}.gz"

    "$bin_mongodump" --host="${db_host}:${db_port}" \
        -u "$db_user" -p "$db_pass" --authenticationDatabase="${db_auth}" \
        --dumpDbUsersAndRoles --gzip -d "${1}" --archive="${bak_dir}/${1}.gz"
}

# Encrypt the backup file with OpenSSL
# Args:
#   $1 = compressed dump filename, e.g. 'mydb.gz'
encrypt_backup() {
    echo "$(date) : Encrypt file ${1}."
    rm -f "${bak_dir}/${1}.enc"

    "$bin_openssl" smime -encrypt -binary -text -aes256 \
        -in "${bak_dir}/${1}" -out "${bak_dir}/${1}.enc" \
        -outform DER "$pub_key"
    rm "${bak_dir}/${1}"
}

# Rotate the current backups in S3
# Args:
#   $1 = backup dump filename, e.g. 'mydb'
#   $2 = max number of backup files to store at a time
rotate_backup() {
    folder=$1
    max=$2

    # Backups will stored inside subfolders (prefixes)
    # in S3, so only look at 'PRE' objects.
    baks=$( "$bin_aws" --output text --region "$s3_region" \
            s3 ls "s3://${s3_bucket}/" | grep '^\s*PRE' | \
            sed -e 's/^ *PRE //' -e 's/\/$//' | \
            grep "^${folder}" || echo "" )

    echo "$(date) : Backup rotation for ${folder}."
    start=$((max - 1))

    for (( x=start ; x > 0 ; x-- )) ; do
        if echo "$baks" | grep "^${folder}\\.${x}\$" ; then
            newx=$((x + 1))
            if [[ $newx -lt $max ]] ; then
                "$bin_aws" --region "$s3_region" \
                    s3 mv --recursive "s3://${s3_bucket}/${folder}.${x}" \
                    "s3://${s3_bucket}/${folder}.${newx}"
            else
                "$bin_aws" --region "$s3_region" \
                    s3 rm --recursive "s3://${s3_bucket}/${folder}.${x}"
            fi
        fi
    done

    if echo "$baks" | grep "^${folder}\$" ; then
        if [[ $max -gt 1 ]] ; then
            "$bin_aws" --region "$s3_region" \
                s3 mv --recursive "s3://${s3_bucket}/${folder}" \
                "s3://${s3_bucket}/${folder}.1"
        else
            "$bin_aws" --region "$s3_region" \
                s3 rm --recursive "s3://${s3_bucket}/${folder}"
        fi
    fi
}

# Upload the compressed db backup file. It will be uploaded
# to this example location: ${s3_bucket}/dump_filename/dump_filename.gz.enc
#
# A timestamp file will also be uploaded to this location:
# ${s3_bucket}/dump_filename/YYYY-MM-DDThh:mm:ss.txt
# Args:
#   $1 = backup dump filename, e.g. 'mydb'
#   $2 = if file is encrypted or not (boolean)
upload_to_s3() {
    keyname=$1
    if [ "$2" = true ] ; then
        fname="${keyname}.gz.enc"
    else
        fname="${keyname}.gz"
    fi

    echo "$(date) : Upload ${fname} to S3 bucket ${s3_bucket}."

    stamp=$( date +"%FT%T" )
    echo "Uploaded: ${stamp}" > "${bak_dir}/${stamp}.txt"

    "$bin_aws" --region "$s3_region" \
        s3 mv "${bak_dir}/${fname}" \
        "s3://${s3_bucket}/${keyname}/${fname}"
    "$bin_aws" --region "$s3_region" \
        s3 mv "${bak_dir}/${stamp}.txt" \
        "s3://${s3_bucket}/${keyname}/${stamp}.txt"
}

## Do the backup only if this node is the primary:
if "$bin_mongo" --host="${db_host}:${db_port}" -u "$db_user" \
                -p "$db_pass" --authenticationDatabase="${db_auth}" \
                --eval 'db.isMaster()["ismaster"]' | grep -q true ; then

    if [[ ! -d "$bak_dir" ]] ; then
        mkdir -p "$bak_dir"
    fi

<% @db_map.each do |x| -%>
<%
    if x.is_a?(Array)
      db_name = x[0]
      x = x[1]
    else
      db_name = x[:db_name]
    end

    do_backup = x.has_key?(:backup) ? x[:backup] : true
    is_enc = x.has_key?(:bak_encrypted) ? x[:bak_encrypted] : false
    bak_filename = x[:bak_filename] || db_name
    bak_maxcopies = x[:bak_maxcopies] || 30
-%>
<%   if do_backup -%>
    # Database: <%= db_name %>
    export_db <%= db_name %> <%= bak_filename %>
<%     if is_enc -%>
    encrypt_backup <%= bak_filename %>.gz
<%     end -%>
    rotate_backup <%= bak_filename %> <%= bak_maxcopies %>
    upload_to_s3 <%= bak_filename %> <%= is_enc %>

<%   end -%>
<% end -%>
    echo "$(date) : Done."
fi

rm "$tmp_file"