diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2e78a75 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +backupld.conf.local +backupld.log \ No newline at end of file diff --git a/backupld b/backupld index faa4a61..977dd6e 100644 --- a/backupld +++ b/backupld @@ -3,11 +3,12 @@ # Start Date: 12/03/14 # Dependencies / Setup +set -o pipefail . cpfb TODAY_DATE="$(date +%Y%m%d)" FAIL_COUNT=0 LOG="$(mktemp /tmp/backupld_XXXXX)" -parseConfig "$1" >> "$LOG" || let FAIL_COUNT+=1 +parseConfig "$1" || let FAIL_COUNT+=1 # Check backup_name specials if [[ "$BACKUP_NAME" == "date" ]]; then @@ -16,98 +17,124 @@ fi # Logic createBackup() { - echo "--- [Create] Backup for $1::$2" >> "$LOG" - if $(attic create --stats "$BACKUP_HOST:$1.attic::$BACKUP_NAME" "$2" --exclude .forever --exclude tmp >> "$LOG" 2>&1); then - echo "--- [Create] Completed successfully" >> "$LOG" + echo "--- [Create] Backup for $1::$2" | printMessage + attic create -v --stats "$BACKUP_HOST:$1.attic::$BACKUP_NAME" "$2" --exclude .forever --exclude tmp 2>&1 | printMessage + exitCode=$? + if [[ $exitCode -eq 0 ]]; then + echo "--- [Create] Completed successfully" | printMessage return 0 else - echo "--- [Create] Completed unsuccessfully" >> "$LOG" + echo "--- [Create] Completed unsuccessfully" | printMessage let FAIL_COUNT+=1 return 1 fi } pruneBackup() { - echo "--- [Prune] Backup for $1::$2" >> "$LOG" + echo "--- [Prune] Backup for $1::$2" | printMessage local keeps= local hourly= local daily= local weekly= local monthly= - [[ ${PRUNE_HOURLY[default]} ]] && hourly="-H ${PRUNE_HOURLY[default]}" - [[ ${PRUNE_HOURLY[$1]} ]] && hourly="-H ${PRUNE_HOURLY[$1]}" - [[ ${hourly: -1} -eq 0 ]] && hourly= + [[ "${PRUNE_HOURLY[default]}" ]] && hourly="-H ${PRUNE_HOURLY[default]}" + [[ "${PRUNE_HOURLY[$1]}" ]] && hourly="-H ${PRUNE_HOURLY[$1]}" + [[ "${hourly: -1}" -eq 0 ]] && hourly= keeps="$hourly" - [[ ${PRUNE_DAILY[default]} ]] && daily="-d ${PRUNE_DAILY[default]}" - [[ ${PRUNE_DAILY[$1]} ]] && daily="-d ${PRUNE_DAILY[$1]}" - [[ ${daily: -1} -eq 0 ]] && daily= + [[ "${PRUNE_DAILY[default]}" ]] && daily="-d ${PRUNE_DAILY[default]}" + [[ "${PRUNE_DAILY[$1]}" ]] && daily="-d ${PRUNE_DAILY[$1]}" + [[ "${daily: -1}" -eq 0 ]] && daily= keeps="$keeps $daily" - [[ ${PRUNE_WEEKLY[default]} ]] && weekly="-w ${PRUNE_WEEKLY[default]}" - [[ ${PRUNE_WEEKLY[$1]} ]] && weekly="-w ${PRUNE_WEEKLY[$1]}" - [[ ${weekly: -1} -eq 0 ]] && weekly= + [[ "${PRUNE_WEEKLY[default]}" ]] && weekly="-w ${PRUNE_WEEKLY[default]}" + [[ "${PRUNE_WEEKLY[$1]}" ]] && weekly="-w ${PRUNE_WEEKLY[$1]}" + [[ "${weekly: -1}" -eq 0 ]] && weekly= keeps="$keeps $weekly" - [[ ${PRUNE_MONTHLY[default]} ]] && monthly="-m ${PRUNE_MONTHLY[default]}" - [[ ${PRUNE_MONTHLY[$1]} ]] && monthly="-m ${PRUNE_MONTHLY[$1]}" - [[ ${monthly: -1} -eq 0 ]] && monthly= + [[ "${PRUNE_MONTHLY[default]}" ]] && monthly="-m ${PRUNE_MONTHLY[default]}" + [[ "${PRUNE_MONTHLY[$1]}" ]] && monthly="-m ${PRUNE_MONTHLY[$1]}" + [[ "${monthly: -1}" -eq 0 ]] && monthly= keeps="$keeps $monthly" - [[ ${PRUNE_YEARLY[default]} ]] && yearly="-y ${PRUNE_YEARLY[default]}" - [[ ${PRUNE_YEARLY[$1]} ]] && yearly="-y ${PRUNE_YEARLY[$1]}" - [[ ${yearly: -1} -eq 0 ]] && yearly= + [[ "${PRUNE_YEARLY[default]}" ]] && yearly="-y ${PRUNE_YEARLY[default]}" + [[ "${PRUNE_YEARLY[$1]}" ]] && yearly="-y ${PRUNE_YEARLY[$1]}" + [[ "${yearly: -1}" -eq 0 ]] && yearly= keeps="$keeps $yearly" - if $(attic prune -v "$BACKUP_HOST:$1.attic" $keeps >> "$LOG" 2>&1); then - echo "--- [Prune] Completed successfully" >> "$LOG" + attic prune --stats "$BACKUP_HOST:$1.attic" $keeps 2>&1 | printMessage + exitCode=$? + if [[ $exitCode -eq 0 ]]; then + echo "--- [Prune] Completed successfully" | printMessage return 0 else - echo "--- [Prune] Completed unsuccessfully" >> "$LOG" + echo "--- [Prune] Completed unsuccessfully" | printMessage let FAIL_COUNT+=1 return 1 fi } checkBackup() { - echo "--- [Check] Backup for for $1::$2" >> "$LOG" - if $(attic check -v "$BACKUP_HOST:$1.attic" >> "$LOG" 2>&1); then - echo "--- [Check] Completed successfully" >> "$LOG" + echo "--- [Check $(date +%A)] Backup for $1::$2" | printMessage + attic check -v "$BACKUP_HOST:$1.attic" 2>&1 | printMessage + exitCode=$? + if [[ $exitCode -eq 0 ]]; then + echo "--- [Check] Completed successfully" | printMessage return 0 else - echo "--- [Check] Completed unsuccessfully" >> "$LOG" + echo "--- [Check] Completed unsuccessfully" | printMessage let FAIL_COUNT+=1 return 1 fi } +checkEncryption() { + if [[ -n "$1" ]]; then + export ATTIC_PASSPHRASE="$1" + return 0 + fi + return 1 +} + runBackups() { - local failed=0 - echo "#### backupld - Starting ####" >> "$LOG" + echo "#### backupld - Starting ####" | printMessage for dir in "${!LOCAL_BACKUP_DIRS[@]}"; do - if createBackup "$dir" "${LOCAL_BACKUP_DIRS[$dir]}"; then - pruneBackup "$dir" "${LOCAL_BACKUP_DIRS[$dir]}" + local dirPath="$(echo ${LOCAL_BACKUP_DIRS[$dir]} | awk -F';' '{print $1}')" + if checkEncryption "$(echo ${LOCAL_BACKUP_DIRS[$dir]} | awk -F';' '{print $2}')"; then + echo "--- ...enabled encryption..." | printMessage + elif [[ -n "$ATTIC_PASSPHRASE" ]]; then + export ATTIC_PASSPHRASE= + echo "--- ...disabled encryption..." | printMessage + fi + if createBackup "$dir" "$dirPath"; then + pruneBackup "$dir" "$dirPath" + if [[ $(date +%u) -eq $BACKUP_CHECK_DAY ]]; then + checkBackup "$dir" "$dirPath" + fi fi done - if [[ $(date +%u) -eq $BACKUP_CHECK_DAY ]]; then - echo "--- [Check] It's $(date +%A)! Verifying consistency..." >> "$LOG" - for dir in "${!LOCAL_BACKUP_DIRS[@]}"; do - checkBackup "$dir" "${LOCAL_BACKUP_DIRS[$dir]}" - done - fi if [[ "$FAIL_COUNT" -gt 0 ]]; then - echo "#### backupld - Unuccessful ####" >> "$LOG" + echo "#### backupld - Unuccessful ####" | printMessage else - echo "#### backupld - Successful ####" >> "$LOG" + echo "#### backupld - Successful ####" | printMessage fi - for user in "${NOTIFY_USERS[@]}"; do - notifyUser "$(echo $user | cut -d';' -f1)" "$(echo $user | cut -d';' -f2)" - done - cp -f "$LOG" "/var/log/backupld-$TODAY_DATE-$FAIL_COUNT.log" - rm -rf "$LOG" + + notifyUsers + + # Cleanup return $FAIL_COUNT } -notifyUser() { +notifyUsers() { + local user if [[ "$NOTIFY_TYPE" == "email" ]]; then - notifyByEmail "$1" "$2" + for user in "${NOTIFY_USERS[@]}"; do + notifyByEmail "$(echo $user | cut -d';' -f1)" "$(echo $user | cut -d';' -f2)" + [[ $? -ne 0 ]] && let FAIL_COUNT+=1 && return 1 + done + elif [[ "$NOTIFY_TYPE" == "pushover" ]]; then + notifyByPushover + [[ $? -ne 0 ]] && let FAIL_COUNT+=1 && return 1 elif [[ "$NOTIFY_TYPE" == "file" ]]; then - notifyByFile "$1" "$2" + for user in "${NOTIFY_USERS[@]}"; do + notifyByFile "$(echo $user | cut -d';' -f1)" "$(echo $user | cut -d';' -f2)" + [[ $? -ne 0 ]] && let FAIL_COUNT+=1 && return 1 + done fi } @@ -137,8 +164,8 @@ notifyByEmail() { 'https://mandrillapp.com/api/1.0/messages/send.json' ) if [[ ! $output =~ \"status\"\:\"queued\" ]]; then - echo "ERROR: Mandrill notifcation failed for: $1 <$2>." >> $LOG - echo "ERROR: $output" >> $LOG + echo "ERROR: Mandrill notifcation failed for: $1 <$2>." | printMessage + echo "ERROR: $output" | printMessage fi elif [[ "$NOTIFY_MAILGUN" ]]; then local output=$(curl -s --user "api:$NOTIFY_MAILGUN" \ @@ -151,8 +178,8 @@ notifyByEmail() { -F o:tag="backupld" ) if [[ ! $output =~ \"message\"\:\ \"Queued\.\ Thank\ you\.\" ]]; then - echo "ERROR: Mailgun notifcation failed for: $1 <$2>." >> $LOG - echo "ERROR: $output" >> $LOG + echo "ERROR: Mailgun notifcation failed for: $1 <$2>." | printMessage + echo "ERROR: $output" | printMessage let FAIL_COUNT+=1 fi else @@ -161,6 +188,52 @@ notifyByEmail() { fi } +notifyByPushover() { + local message="$FAIL_COUNT errors occured" + [[ "$NOTIFY_MESSAGE" ]] && message="$(messageMacrofier $NOTIFY_MESSAGE)" + if [[ "$NOTIFY_PUSHOVER_API_KEY" ]]; then + local apiKey="$NOTIFY_PUSHOVER_API_KEY" + if [[ "$NOTIFY_PUSHOVER_USER_KEY" ]]; then + local userKey="$NOTIFY_PUSHOVER_USER_KEY" + local output=$(curl -s \ + https://api.pushover.net/1/messages.json \ + -F "token=$apiKey" \ + -F "user=$userKey" \ + -F "title=[backupld] Complete" \ + -F "message=$message" \ + ) + if [[ ! $output =~ \"status\"\:\ ?1 ]]; then + echo "ERROR: Pushover notifcation failed." | printMessage + echo "ERROR: $output" | printMessage + let FAIL_COUNT+=1 + fi + else + echo "ERROR: No Pushover user key" | printMessage + let FAIL_COUNT+=1 + fi + else + echo "ERROR: No Pushover API key" | printMessage + let FAIL_COUNT+=1 + fi +} + +notifyByFile() { + if [[ "$2" ]]; then + cp -f "$LOG" "$2" + rm -rf "$LOG" + fi +} + +printMessage() { + local message + while read message; do + if [[ "$NOTIFY_ECHO" == "true" ]]; then + echo "$message" + fi + echo "$message" >> "$LOG" + done +} + messageMacrofier() { echo "$(echo $@ | m4 \ -D name="$BACKUP_NAME" \ diff --git a/backupld.conf b/backupld.conf index d7b5af5..3731327 100644 --- a/backupld.conf +++ b/backupld.conf @@ -10,14 +10,17 @@ # The day of the week to run validity checks # Uses $(date +%u) so numbers are (1..7); 1 is Monday CHECK_DAY = 7 - + # What do we care about? # In the format: repoName = /directory/to/backup # +# Support for encrypted repos is available, just append the passphrase: +# repoName = /directory/to/backup;passphrase123 +# ==LOCAL_BACKUP_DIRS vhosts = /var/www/vhosts git = /home/git - cron = /var/spool/cron + cron = /var/spool/cron;CronSaf3ty # How often do we want to prune? # (How many to keep on each timescale) @@ -30,26 +33,45 @@ # ==PRUNE_DAILY default=7 - + ==PRUNE_WEEKLY default=4 - + ==PRUNE_MONTHLY default=6 # How do we want to send notifications? +# Supported methods are 'email', 'pushover' or 'file' # -# 'email' is the only supported so far -# This uses Mandrill or Mailgun, set your keys as so: +# Email --- +# Set type: TYPE = email +# We use Mandrill or Mailgun, set your keys as so: # MANDIRLL = or # MAILGUN = # with Mailgun, you also need to set the name of the api domain: # MAILGUN_API_DOMAIN = example.com -# +# # FROM_DOMAIN is the domain of the sender: example.com # MESSAGE is the body of the email in text (no HTML) # EMAIL_SUBJECT is the subject of the email (amazing!) # +# Pushover --- +# Set type: TYPE = pushover +# Using Pushover.net, set your keys as so: +# PUSHOVER_API_KEY = +# PUSHOVER_USER_KEY = +# +# Pushover supports the MESSAGE option too +# MESSAGE = +# +# File --- +# Set type: TYPE = file +# The log file will be copied per user, +# specify location for each one with NOTIFY_USERS. +# To echo to the console as well, set +# ECHO = true +# +# Extra --- # Both the message and subject support some macros: # 'name' will be changed to the name of the archive # 'date' is changed to the date from above: YYYYMMDD @@ -57,12 +79,13 @@ # ::NOTIFY_ TYPE = email - MANDRILL = sajnDCSsSDCnkiaScERPO< + MANDRILL = sajnDCSsSDCnkiaScERPOJ FROM_DOMAIN = example.com MESSAGE = Completed with fail_count errors. EMAIL_SUBJECT = [Daily name] Failure Count fail_count # Who should we tell? # In the format: Recipient Name;recipient@email.address +# Or: Recipient Name;/path/to/file --NOTIFY_USERS Joe Eaves;joe@example.com