#!/bin/bash ##################################################################### # # # Stupidly simple backup script for own projects # # # # Author: Anthony Axenov (Антон Аксенов) # # Version: 1.2 # # License: WTFPLv2 # # More info (RU): https://axenov.dev/?p=1272 # # # ##################################################################### # use remote storages =============================================== USE_SSH=1 USE_S3=1 # database credentials ============================================== DBUSER= DBPASS= DBNAME= DBCHARSET="utf8" # dates for file structure ========================================== TODAY_DIR="$(date +%Y.%m.%d)" TODAY_FILE="$(date +%H.%M)" # local storage ===================================================== LOCAL_BAK_DIR="/backup" LOCAL_BAK_PATH="$LOCAL_BAK_DIR/$TODAY_DIR" # database backup file LOCAL_SQL_FILE="$TODAY_FILE-db.sql.gz" LOCAL_SQL_PATH="$LOCAL_BAK_PATH/$LOCAL_SQL_FILE" # project path and backup file LOCAL_SRC_DIR="/var/www/html" LOCAL_SRC_FILE="$TODAY_FILE-src.tar.gz" LOCAL_SRC_PATH="$LOCAL_BAK_PATH/$LOCAL_SRC_FILE" # log file LOG_FILE="$TODAY_FILE.log" LOG_PATH="$LOCAL_BAK_PATH/$LOG_FILE" # remote storages =================================================== SSH_HOST="user@example.com" SSH_BAK_DIR="/backup" SSH_BAK_PATH="$SSH_BAK_DIR/$TODAY_DIR" SSH_SQL_FILE="$SSH_BAK_PATH/$LOCAL_SQL_FILE" SSH_SRC_FILE="$SSH_BAK_PATH/$LOCAL_SRC_FILE" SSH_LOG_FILE="$SSH_BAK_PATH/$LOG_FILE" S3_BUCKET="s3://my.bucket" S3_DIR="$S3_BUCKET/$TODAY_DIR" S3_SQL_FILE="$S3_DIR/$LOCAL_SQL_FILE" S3_SRC_FILE="$S3_DIR/$LOCAL_SRC_FILE" S3_LOG_FILE="$S3_DIR/$LOG_FILE" # autoremove ======================================================== # time to live on different storages TTL_LOCAL=3 TTL_SSH=7 TTL_S3=60 # autoremove flags CLEAR_SSH=1 CLEAR_S3=1 # notifications ===================================================== USE_NTFY=1 NTFY_TITLE="Backup script" NTFY_CHANNEL= #==================================================================== # # Functions used for the whole backup flow # #==================================================================== # prints arguments to stdout and into log file log() { echo -e "[$(date +%H:%M:%S)] $*" | tee -a "$LOG_PATH" } # sends notification with information ntfy_info() { [ $USE_NTFY == 1 ] && ntfy send \ --title "$NTFY_TITLE" \ --message "$1" \ --priority 1 \ "$NTFY_CHANNEL" } # sends notification with warning ntfy_warn() { [ $USE_NTFY == 1 ] && ntfy send \ --title "$NTFY_TITLE" \ --tags "warning" \ --message "$1" \ --priority 5 \ "$NTFY_CHANNEL" } # prints initialized parameters show_params() { log "Initialized parameters:" log "├ [ Remotes ]" log "│\t├ USE_SSH = $USE_SSH" [ $USE_SSH == 1 ] && log "│\t├ SSH_HOST = $SSH_HOST" log "│\t├ USE_S3 = $USE_S3" [ $USE_S3 == 1 ] && log "│\t├ S3_BUCKET = $S3_BUCKET" log "├ [ Database ]" log "│\t├ DBUSER = $DBUSER" log "│\t├ DBNAME = $DBNAME" log "│\t├ DBCHARSET = $DBCHARSET" log "│\t├ LOCAL_SQL_PATH = $LOCAL_SQL_PATH" [ $USE_SSH == 1 ] && log "│\t├ SSH_SQL_FILE = $SSH_SQL_FILE" [ $USE_S3 == 1 ] && log "│\t├ S3_SQL_FILE = $S3_SQL_FILE" log "├ [ Sources ]" log "│\t├ LOCAL_SRC_DIR = $LOCAL_SRC_DIR" log "│\t├ LOCAL_SRC_PATH = $LOCAL_SRC_PATH" [ $USE_SSH == 1 ] && log "│\t├ SSH_SRC_FILE = $SSH_SRC_FILE" [ $USE_S3 == 1 ] && log "│\t├ S3_SRC_FILE = $S3_SRC_FILE" log "├ [ Log ]" log "│\t├ LOG_PATH = $LOG_PATH" [ $USE_SSH == 1 ] && log "│\t├ SSH_LOG_FILE = $SSH_LOG_FILE" [ $USE_S3 == 1 ] && log "│\t├ S3_LOG_FILE = $S3_LOG_FILE" log "├ [ Autoclear ]" log "│\t├ TTL_LOCAL = $TTL_LOCAL" [ $USE_SSH == 1 ] && { log "│\t├ CLEAR_SSH = $CLEAR_SSH" log "│\t├ TTL_SSH = $TTL_SSH" } [ $USE_S3 == 1 ] && { log "│\t├ CLEAR_S3 = $CLEAR_S3" log "│\t├ TTL_S3 = $TTL_S3" } log "└ [ ntfy ]" log "\t├ USE_NTFY = $USE_NTFY" [ $USE_NTFY == 1 ] && log "\t├ NTFY_TITLE = $NTFY_TITLE" [ $USE_NTFY == 1 ] && log "\t└ NTFY_CHANNEL = $NTFY_CHANNEL" } # initializes directories for backup init_dirs() { if [ ! -d "$LOCAL_BAK_PATH" ]; then mkdir -p $LOCAL_BAK_PATH fi [ $USE_SSH == 1 ] && ssh $SSH_HOST "mkdir -p $SSH_BAK_PATH" } # clears old local backups clear_local_backups() { log "\tLocal:" log $(find "$LOCAL_BAK_DIR" -type d -mtime +"$TTL_LOCAL" | sort) find "$LOCAL_BAK_DIR" -type d -mtime +"$TTL_LOCAL" | xargs rm -rf } # clears old backups on remote ssh storage clear_ssh_backups() { if [ $USE_SSH == 1 ] && [ $CLEAR_SSH == 1 ]; then log "\tSSH:" log $(ssh "$SSH_HOST" "find $SSH_BAK_DIR -type d -mtime +$TTL_SSH" | sort) ssh "$SSH_HOST" "find $SSH_BAK_DIR -type d -mtime +$TTL_SSH | xargs rm -rf" else log "\tSSH: disabled (\$USE_SSH, \$CLEAR_SSH)" fi } # clears backups on remote s3 storage clear_s3_backups() { # https://gist.github.com/JProffitt71/9044744?permalink_comment_id=3539681#gistcomment-3539681 if [ $USE_S3 == 1 ] && [ $CLEAR_S3 == 1 ]; then log "\tS3:" OLDER_THAN=$(date -d "$TTL_S3 days ago" "+%s") s3cmd ls -r $S3_DIR | while read -r line; do FILETIME=$(echo "$line" | awk {'print $1" "$2'}) FILETIME=$(date -d "$FILETIME" "+%s") if [[ $FILETIME -le $OLDER_THAN ]]; then FILEPATH=$(echo "$line" | awk {'print $4'}) if [ $FILEPATH != "" ]; then log "$line" s3cmd del $FILEPATH fi fi done else log "\tS3: disabled (\$USE_S3 + \$CLEAR_S3)" fi } # clears old backups clear_backups() { echo log "1/7 Removing old backups..." clear_local_backups clear_ssh_backups clear_s3_backups } # makes archive with database dump backup_db() { echo log "2/7 Dumping DB: $DBNAME..." mysqldump \ --user=$DBUSER \ --password=$DBPASS \ --opt \ --default-character-set=$DBCHARSET \ --quick \ $DBNAME | gzip > $LOCAL_SQL_PATH if [ $? == 0 ]; then log "\t- OK" send_db_ssh send_db_s3 else log "\t- ERROR: failed to create dump. Exit-code: $?" ntfy_warn "ERROR: failed to create dump" log "3/7 Sending database backup to $SSH_HOST... skipped" log "4/7 Sending database backup to $S3_DIR... skipped" fi } # sends database archive into ssh remote storage send_db_ssh() { echo log "3/7 Sending database backup to $SSH_HOST..." if [ $USE_SSH == 1 ]; then rsync --progress "$LOCAL_SQL_PATH" "$SSH_HOST:$SSH_SQL_FILE" if [ $? == 0 ]; then log "\t- OK" else log "\t- ERROR: failed to send DB backup to $SSH_HOST. Exit-code: $?" ntfy_warn "ERROR: failed to send DB backup to $SSH_HOST" fi else log "\t- disabled (\$USE_SSH)" fi } # sends database archive into s3 remote storage send_db_s3() { echo log "4/7 Sending database backup to $S3_DIR..." if [ $USE_S3 == 1 ]; then s3cmd put "$LOCAL_SQL_PATH" "$S3_SQL_FILE" if [ $? == 0 ]; then log "\t- OK" else log "\t- ERROR: failed to send DB backup to $S3_DIR. Exit-code: $?" ntfy_warn "ERROR: failed to send DB backup to $S3_DIR" fi else log "\t- disabled (\$USE_SSH)" fi } # makes archive with project sources backup_src() { echo log "5/7 Compressing project dir: $LOCAL_SRC_DIR..." tar -zcf "$LOCAL_SRC_PATH" "$LOCAL_SRC_DIR" if [ $? == 0 ]; then log "\t- OK" send_src_ssh send_src_s3 else log "\t- ERROR: failed to compress project. Exit-code: $?" ntfy_warn "ERROR: failed to compress project" log "6/7 Sending project backup to $SSH_HOST... skipped" log "7/7 Sending project backup to $S3_DIR... skipped" fi } # sends sources archive into ssh remote storage send_src_ssh() { echo log "6/7 Sending project backup to $SSH_HOST..." if [ $USE_SSH == 1 ]; then rsync --progress "$LOCAL_SRC_PATH" "$SSH_HOST:$SSH_SRC_FILE" if [ $? == 0 ]; then log "\t- OK" else log "\t- ERROR: failed to send project backup to $SSH_HOST. Exit-code: $?" ntfy_warn "ERROR: failed to send project backup to $SSH_HOST" fi else log "\t- disabled" fi } # sends sources archive into s3 remote storage send_src_s3() { echo log "7/7 Sending project backup to $S3_DIR..." s3cmd put "$LOCAL_SRC_PATH" "$S3_SRC_FILE" if [ $? == 0 ]; then log "\t- OK" else log "\t- ERROR: failed to send database backup to $S3_DIR. Exit-code: $?" ntfy_warn "ERROR: failed to send project backup to $S3_DIR" fi } # prints used/free space on local storage show_finish() { echo log "Finish!" log "Used space: $(du -h "$LOCAL_BAK_PATH" | tail -n1)" # вывод размера папки с бэкапами за текущий день log "Free space: $(df -h "$LOCAL_BAK_PATH" | tail -n1 | awk '{print $4}')" # вывод свободного места на локальном диске echo } # sends log file into both remote storage send_log() { [ $USE_SSH == 1 ] && rsync --progress "$LOG_PATH" "$SSH_HOST:$SSH_LOG_FILE" [ $USE_S3 == 1 ] && s3cmd put "$LOG_PATH" "$S3_LOG_FILE" } # main flow ========================================================= log "Start ----------------------------------------------------------" show_params init_dirs clear_backups backup_db backup_src show_finish send_log ntfy_info "Finish!"