#!/bin/bash
# Make Backup Demon
# Make backups of selected files by count of block device insertion.
#
# Written by Noam Alum
#
# Documentation at https://alum.sh/assets/Documents/Make%20backup.html
# GitHub page at https://github.com/Noam-Alum/make_backup
#
# © Noam Alum. All rights reserved
# Visit alum.sh for more scripts like this :)

### FUNCTIONS
## LOG
function AddLog {
    # VARS
    local Tag="$1"
    shift
    local Message="$*"

    # get binaries
    # search for binary
    if [ ! -z "$(command -v logger)" ]; then
        # set command to full path to binary
        export logger="$(command -v logger)"

    # try running the command if so use it as is
    elif [ ! -z "$(logger --help 2>/dev/null)" ]; then
        export logger="logger"

    # binary dependency missing exiting
    else
        # EXIT
        exit 1
    fi

    # LOG
    $logger -p local0.info -t "[$Tag]" "$Message"
}

## handle errors
function HandleError {
    local ExitCode=$?
    local ErrorMessage="$BASH_COMMAND exited with status $ExitCode"
    AddLog "ERROR" "$ErrorMessage"
}
trap 'HandleError' ERR

## Get binary
function get_binary {
    trap 'HandleError' ERR

    # list all binaries
    mbn_commands=(
        "rmdir"
        "find"
        "cat"
        "date"
        "sed"
        "awk"
        "tr"
        "sort"
        "tail"
        "rm"
        "ls"
        "mkdir"
        "head"
        "rsync"
        "echo"
    )

    # get binaries
    for binary in ${mbn_commands[@]}
    do
        # search for binary
        if [ ! -z "$(command -v $binary)" ]; then
            # set command to full path to binary
            export $binary="$(command -v $binary)"

        # try running the command if so use it as is
        elif [ ! -z "$($binary --help 2>/dev/null)" ];then
            export $binary="$binary"

        # binary dependency missing exiting
        else
            # LOG
            AddLog "ERROR" "binary dependency missing \"$binary\" exiting."

            # EXIT
            exit 1
        fi
    done
}

## Read config
function read_config {

    #### GET BINARY
    get_binary

    trap 'HandleError' ERR

    ## GET LIST ALL NEEDED ITEMS
    export Items="$($cat /etc/make_backup/make_backup.conf | $sed -n '/> start items to backup <$/,/> end items to backup <$/{//!p}')"
    if [ -z "$Items" ]; then
        AddLog "ERROR" error while reading items to backup, exiting.
        exit 1
    fi

    ## GET CONF VARS
    conf_vars=("count_location" "fallback_directory" "bd_count" "backup_in_c_month" "backup_in_month" "month_in_c_year" "month_in_year" "rm_old_backups" "BACKUP_dir")
    for c_var in ${conf_vars[@]}
    do
        export $c_var="$($awk -v cvar="$c_var" -F '"' '$1 ~ cvar {print $2}' /etc/make_backup/make_backup.conf)"
        if [ -z "$c_var" ]; then
            AddLog "ERROR" error while allocating \"$c_var\", exiting.
            exit 1
        fi
    done

    ## CHECK
    # COUNT FILE
    if [ ! -f "$count_location" ]; then
        AddLog "ERROR" count file "$count_location" could not be used, exiting.
        exit 1
    fi

    ## BACKUP DIR
    if [ -z "$BACKUP_dir" ] || [ ! -e "$BACKUP_dir" ]; then
        if [ ! -z "$fallback_directory" ] && [ -e "$fallback_directory" ]; then
            # SET BACKUP_dir to fallback_directory to avoid crashing
            export BACKUP_dir=$fallback_directory

            # set old backup removal to no
            export rm_old_backups="no"
        else
            AddLog "ERROR" No useable backup location, check /etc/make_backup/make_backup.conf for more information. exiting.
            exit 1
        fi
    fi
}

## keep backups
function keep_backups {
    trap 'HandleError' ERR

    #### GET BINARY
    get_binary

    #### READ CONFIG ####
    read_config

    # CHECK IF THERE ARE BACKUPS IN THE Laptop_backups DIR
    if [ ! -z "$($ls $BACKUP_dir/ 2> /dev/null)" ] && [ "$rm_old_backups" == "yes" ]; then
        # GET BIGGEST YEAR
        biggest_year="$BACKUP_dir/$($ls $BACKUP_dir 2> /dev/null | $sort -n | $tail -1)"

        # GEt ALL YEARS DIRECTORIES
        for year_dir in $BACKUP_dir/*
        do
            # CHECK IF THIS IS THE BIGGEST YEAR
            if [ "$year_dir" == "$biggest_year" ]; then

                # COUNT MONTHS
                month_count=0

                # GET BIGGEST MONTH
                biggest_month="$year_dir/$($ls $year_dir 2> /dev/null| $sort -n | $tail -1)"

                # GEt ALL MONTH DIRECTORIES
                for month_dir in $year_dir/*
                do
                    month_count=$(( $month_count + 1 ))

                    # CHECK IF COUNT IS OVER month_in_year
                    if [ $month_count -gt $month_in_c_year ]; then
                        $rm -rf $year_dir/$($ls -t --time=atime -1 $year_dir 2> /dev/null | $tail -1)
                        AddLog "CLEARED SPACE" removed old month directory \"$year_dir/$($ls -t --time=atime -1 $year_dir 2> /dev/null | $tail -1)\"
                    fi

                    # CHECK IF MONTH_DIR IS THE BIGGEST MONTH
                    if [ "$month_dir" == "$biggest_month" ]; then
                        # COUNT BACKUPS
                        backup_count=0

                        # GEt ALL BACKUPS DIRECTORIES
                        for backup in $month_dir/*
                        do
                            # ADD ONE TO COUNT
                            backup_count=$(( $backup_count + 1 ))

                            # CHECK IF COUNT IS OVER backup_in_c_month
                            if [ $backup_count -gt $backup_in_c_month ]; then
                                # REAMOVE OLDEST BACKUP IN MONTH DIR
                                $rm -rf $month_dir/$($ls -t --time=atime -1 $month_dir 2> /dev/null | $tail -1)
                                AddLog "CLEARED SPACE" removed old backup directory \"$month_dir/$($ls -t --time=atime -1 $month_dir 2> /dev/null | $tail -1)\"
                            fi
                        done
                    else
                        # COUNT BACKUPS
                        backup_count=0

                        # GEt ALL BACKUPS DIRECTORIES
                        for backup in $month_dir/*
                        do
                            # ADD ONE TO COUNT
                            backup_count=$(( $backup_count + 1 ))

                            # CHECK IF COUNT IS OVER backup_in_c_month
                            if [ $backup_count -gt $backup_in_month ]; then
                                # REAMOVE OLDEST BACKUP IN MONTH DIR
                                $rm -rf $month_dir/$($ls -t --time=atime -1 $month_dir 2> /dev/null | $tail -1)
                                AddLog "CLEARED SPACE" removed old backup directory \"$month_dir/$($ls -t --time=atime -1 $month_dir 2> /dev/null | $tail -1)\"
                            fi
                        done
                    fi
                done
            else
                # COUNT MONTHS
                month_count=0

                # GEt ALL MONTH DIRECTORIES
                for month_dir in $year_dir/*
                do
                    month_count=$(( $month_count + 1 ))
                    # CHECK IF COUNT IS OVER month_in_year
                    if [ $month_count -gt $month_in_year ]; then
                        $rm -rf $year_dir/$($ls -t --time=atime -1 $year_dir 2> /dev/null | $tail -1)
                        AddLog "CLEARED SPACE" removed old month directory \"$year_dir/$($ls -t --time=atime -1 $year_dir 2> /dev/null | $tail -1)\"
                    fi


                    # COUNT BACKUPS
                    backup_count=0

                    # GEt ALL BACKUPS DIRECTORIES
                    for backup in $month_dir/*
                    do
                        # ADD ONE TO COUNT
                        backup_count=$(( $backup_count + 1 ))

                        # CHECK IF COUNT IS OVER backup_in_c_month
                        if [ $backup_count -gt $backup_in_month ]; then
                            # REAMOVE OLDEST BACKUP IN MONTH DIR
                            $rm -rf $month_dir/$($ls -t --time=atime -1 $month_dir 2> /dev/null | $tail -1)
                            AddLog "CLEARED SPACE" removed old backup directory \"$month_dir/$($ls -t --time=atime -1 $month_dir 2> /dev/null | $tail -1)\"
                        fi
                    done
                done
            fi
        done
    fi
}

while true; do
    #### READ CONFIG ####
    read_config

    #### GET BINARY
    get_binary

    #### CATCH UDEV ####
    if [ "$1" == "UDEV" ]; then
        if [ ! -s "$count_location" ] || [ $(cat "$count_location") -gt $bd_count ]; then
            # START COUNT
            echo '0' > $count_location

            # LOG
            AddLog "STARTING-COUNT" "set count to \"0\" count file \"$count_location\" was empty or above \"$bd_count\"."

            # EXIT
            exit 0
        elif [[ "$(cat "$count_location")" =~ ^[0-9]+$ ]]; then
            # LOG
            AddLog "CHANGING-COUNT" "changing count to \"$(( $(cat $count_location) + 1 ))\"."

            # ADD COUNT
            echo $(( $(cat $count_location) + 1 )) > $count_location

            # EXIT
            exit 0
        else
            # LOG
            AddLog "ERROR" "count file $count_location is not numeric"

            # EXIT
            exit 1
        fi
    fi

    #### KEEP BACKUPS NEEDED ####
    keep_backups

    #### CHECK IF COUNT IS BIGGER THAN 10 ####
    if [ $($cat $count_location) -eq $bd_count ]; then
            sleep 10
            ### START SCRIPT

            #### READ CONFIG ####
            read_config

            ## IF NOT EXIST CREATE YEAR AND MONTH DIRS
            # YEAR
            year_dir="$($date '+%Y')"

            # MONTH
            month_dir="$($date '+%m')"

            $mkdir -p $BACKUP_dir/$year_dir/$month_dir

            # CHECK FOR BACKUPS IN fallback_directory
            if [ "$BACKUP_dir" != "$fallback_directory" ] && [ -e $fallback_directory ] && [ `ls $fallback_directory | wc -l` -ne 0 ]; then
                AddLog "MOVING BACKUPS" found old backups in fallback directory, moving them to main backup directory.
                $rsync -av --remove-source-files --prune-empty-dirs $fallback_directory/* $BACKUP_dir &> /dev/null
                $rm -rf $fallback_directory/*
            fi

            ## CREATE BACKUP DIRECTORY
            BACKUP_dir="$BACKUP_dir/$year_dir/$month_dir/$($tr -dc 'a-zA-Z0-9' < /dev/random | $head -c 6)_$($date '+%d_%H_%M')/"
            $mkdir "$BACKUP_dir"

            ## BACKUP FILES
            # LOG
            AddLog "BACKUP STARTED" backup started at $BACKUP_dir.
            AddLog "BACKUPING FILES" started backuping files to $BACKUP_dir :

            for Item in $Items
            do
                # RSYNC
                AddLog "BACKUPING ITEM" "$Item"
                $rsync -av --relative "$Item" "$BACKUP_dir" &> /dev/null
            done

            # LOG
            AddLog "FINISHED BACKUP" backup at $BACKUP_dir

            $echo "0" > $count_location
            # LOG
            AddLog "RESTORING COUNT" setting activation count to \"0\".

            exit 0
    fi

    sleep 2
done
Bash