#!/bin/bash
set -o pipefail -o errexit

# Copyright (C) 2020 Pedro Luis Lopez <pedroluis.lopezsanchez@gmail.com>
# Copyright (C) 2020 Innobyte Bechea Leonardo <https://www.innobyte.com/>
# Copyright (C) 2020 Innobyte Alin Alexandru <https://www.innobyte.com/>
# Copyright (C) 2020 DebOps <https://debops.org/>
# SPDX-License-Identifier: GPL-3.0-or-later

# This file is managed remotely, all changes will be lost

#
# InfluxDB Backup Script
# VER. 0.1

# Note, this is a lobotomized port of AutoMySQLBackup
# (https://sourceforge.net/projects/automysqlbackup/) for use with
# InfluxDB.
#
# 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=""

# Host name (or IP address) of influxdb server e.g localhost
DBHOST="127.0.0.1"

# Port that influxdb is listening on
DBPORT="8088"

# Backup directory location e.g /backups
BACKUPDIR="/var/backups/influxdb"

# 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"

# 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 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 separated 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 autoinfluxdbbackup 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
# somewhere else.
#
# NOTE: On Debian copy the file with no extension for it to be run
# by cron e.g just name the file "autoinfluxdbbackup"
#
# That's 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".
# Monthly 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 responsibility 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.1 - (2020-03-01)
#       - Initial Release
#
#=====================================================================
#=====================================================================
#=====================================================================
#
# 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/autoinfluxdbbackup" ]; then
      # shellcheck source=/dev/null
      source /etc/$x/autoinfluxdbbackup
  fi
done

# Include extra config file if specified on commandline, e.g. for backuping several remote dbs from central server
# shellcheck source=/dev/null
[ -n "$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.1                                           # 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 influxd backup

# Do we need to backup only a specific database?
if [ "$DBNAME" ]; then
  OPT="$OPT -database $DBNAME"
fi

# Create required directories
mkdir -p $BACKUPDIR/{hourly,daily,weekly,monthly,tmp} || shellout 'failed to create directories'

if [ "$LATEST" = "yes" ]; then
    rm -rf "$BACKUPDIR/latest"
    mkdir -p "$BACKUPDIR/latest" || shellout 'failed to create directory'
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 () {
    COMMAND="influxd backup -portable -host $DBHOST:$DBPORT $OPT $BACKUPDIR/tmp"
    $COMMAND
    INFLUXDBBACKUPSTATUS=$?
    if [ $INFLUXDBBACKUPSTATUS -ne 0 ]; then
        echo "ERROR: influxd backup failed: $1" >&2
        return 1
    fi
    echo Tar backup to "$1"
    cd "$BACKUPDIR/tmp" || return 1
    tar cf "$1" -- *
    cd - >/dev/null || return 1
    echo Cleaning up folder at "$BACKUPDIR/tmp"
    rm "$BACKUPDIR/tmp/"*
    [ -e "$1" ] && return 0
    echo "ERROR: influxd backup failed to create backup: $1" >&2
    return 1
}

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

echo ======================================================================
echo AutoInfluxDBBackup VER $VER

echo
echo "Backup of Database Server - $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"
    OPT="$OPT -since $(date -u --date='7 days ago' +'%Y-%m-%dT%H:00:00Z')"

# 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"
    OPT="$OPT -since $(date -u --date='1 day ago' +'%Y-%m-%dT%H:00:00Z')"

# 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 files 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)}'
    OPT="$OPT -since $(date -u --date='1 hour ago' +'%Y-%m-%dT%H:00:00Z')"

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 [ "$MAILCONTENT" = "log" ]; then
    mail -s "InfluxDB Backup Log for $DBHOST - $DATE" "$MAILADDR" < "$LOGFILE"

    if [ -s "$LOGERR" ]; then
        cat "$LOGERR"
        mail -s "ERRORS REPORTED: InfluxDB Backup error Log for $DBHOST - $DATE" "$MAILADDR" < "$LOGERR"
    fi
else
    if [ -s "$LOGERR" ]; then
        cat "$LOGFILE"
        echo
        echo "###### WARNING ######"
        echo "STDERR written to during influxd backup execution."
        echo "The backup probably succeeded, as influxd backup 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
