diff --git a/mssql-backup.sh b/mssql-backup.sh index 8e93caf..acc046f 100644 --- a/mssql-backup.sh +++ b/mssql-backup.sh @@ -2,174 +2,274 @@ # Script per il backup di database MS SQL Server # Autore: Lorenzo Paciotti & Mattia Tadini # Nome del file: mssql-backup.sh -# Revisione: 1.10 +# Revisione: 1.12 -# Configuration -BACKUP_DIR="/zucchetti/backupdb/bkfiles" -SQLCMD="/opt/mssql-tools18/bin/sqlcmd" -USER="sa" -PASSWORD="DBP0l01nf0.." -HOST="localhost" -RETENTION=1 # Retention per i backup locali -REMOTE_RETENTION=2 # Retention per i backup remoti -DATE=$(LC_TIME=C date +%Y%m%d%H%M%S) # Forza il formato della data -SERVER_NAME="DBSQL01" # Identificativo del server fisico -LOG_FILE="/zucchetti/backupdb/backup_log_$DATE.txt" -ERROR_LOG_FILE="/zucchetti/backupdb/backup_err_$DATE.txt" -FTP_SERVER="terni.poloinformatico.it" -FTP_USER="backupmssqldb" -FTP_PASSWORD="!Li'D/i%2&*QkvK!" -REMOTE_DIR="/BackupDB" -SMTP_SERVER="smtp://relay.poloinformatico.it:587" -SMTP_USER="brass@relay.poloinformatico.it" -SMTP_PASS="DMKqP9vUYn8s" - -# Check if a database name is provided as an argument -if [ -z "$1" ]; then - echo "Usage: $0 " +# Load configuration from external file +CONFIG_FILE="/zucchetti/backupdb/config.env" +if [ -f "$CONFIG_FILE" ]; then + source "$CONFIG_FILE" +else + echo "Configuration file not found: $CONFIG_FILE" exit 1 fi -DB_NAME=$1 -LOCAL_DB_DIR="$BACKUP_DIR/$DB_NAME" -REMOTE_DB_DIR="$REMOTE_DIR/$DB_NAME" +# Default values for configuration (will be overridden by config.env if present) +: ${BACKUP_DIR:="/zucchetti/backupdb/bkfiles"} +: ${SQLCMD:="/opt/mssql-tools18/bin/sqlcmd"} +: ${HOST:="localhost"} +: ${RETENTION:=1} # Retention per i backup locali +: ${REMOTE_RETENTION:=2} # Retention per i backup remoti +: ${SERVER_NAME:="DBSQL01"} # Identificativo del server fisico +: ${LOG_FILE:="/zucchetti/backupdb/backup_log.txt"} +: ${ERROR_LOG_FILE:="/zucchetti/backupdb/backup_err.txt"} +: ${REMOTE_DIR:="/BackupDB"} -# Create local backup directory if it doesn't exist -mkdir -p "$LOCAL_DB_DIR" -chown mssql:mssql "$LOCAL_DB_DIR" +# Required variables check +required_vars=( + "USER" "PASSWORD" "FTP_SERVER" "FTP_USER" "FTP_PASSWORD" + "SMTP_SERVER" "SMTP_USER" "SMTP_PASS" +) -# Create remote directory on FTP server if it doesn't exist -ftp -inv $FTP_SERVER <> $LOG_FILE 2>> $ERROR_LOG_FILE +for var in "${required_vars[@]}"; do + if [ -z "${!var}" ]; then + echo "Error: Required variable $var is not set in config file" + exit 1 + fi +done + +# Set date format +DATE=$(LC_TIME=C date +%Y%m%d%H%M%S) + +# Function to get all user databases +get_all_databases() { + $SQLCMD -S "$HOST" -U "$USER" -P "$PASSWORD" -h-1 \ + -Q "SELECT name FROM sys.databases WHERE database_id > 4 AND state = 0 AND is_read_only = 0" \ + -C -b | tr -d ' ' +} + +# Function to check FTP connection +check_ftp_connection() { + local temp_file=$(mktemp) + ftp -inv $FTP_SERVER > $temp_file 2>&1 < $temp_file 2>&1 <> $LOG_FILE + sleep 5 + done + + cat $temp_file >> $LOG_FILE 2>> $ERROR_LOG_FILE + rm -f $temp_file + return $upload_status +} # Function to send email with msmtp send_email() { - SUBJECT="Backup [${EMAIL_STATUS}] for $DB_NAME on $SERVER_NAME - $(LC_TIME=C date)" - MESSAGE="Backup process completed. Check the details below:\n\n$(cat $LOG_FILE)" + local db_name="$1" + local status_message="" + + # Costruisci il messaggio di stato dettagliato + if [ $LOCAL_STATUS -eq 0 ]; then + status_message="Local backup: SUCCESS\n" + else + status_message="Local backup: FAILED\n" + fi + + if [ $FTP_STATUS -eq 0 ]; then + status_message+="FTP upload: SUCCESS\n" + else + status_message+="FTP upload: FAILED\n" + fi - echo -e "Subject: $SUBJECT\nFrom: $SMTP_USER\nTo: $SMTP_USER\n\n$MESSAGE" | msmtp --debug --from="$SMTP_USER" -t + SUBJECT="Backup [${EMAIL_STATUS}] for $db_name on $SERVER_NAME - $(LC_TIME=C date)" + MESSAGE="Backup process completed with following status:\n\n${status_message}\nDetailed log:\n\n$(cat $LOG_FILE)" + + if [ -f "$ERROR_LOG_FILE" ] && [ -s "$ERROR_LOG_FILE" ]; then + MESSAGE+="\n\nErrors encountered:\n\n$(cat $ERROR_LOG_FILE)" + fi + + echo -e "Subject: $SUBJECT\nFrom: $SMTP_USER\nTo: $SMTP_USER\n\n$MESSAGE" | \ + msmtp --debug --from="$SMTP_USER" -t } -# Start time for the backup -START_TIME=$(date +%s) -echo "$(LC_TIME=C date): Starting backup for database: $DB_NAME on server $SERVER_NAME" > $LOG_FILE +# Function to perform backup for a single database +perform_backup() { + local db_name="$1" + local LOCAL_STATUS=1 + local FTP_STATUS=1 + local EMAIL_STATUS="" + + echo "Starting backup process for database: $db_name" + + LOCAL_DB_DIR="$BACKUP_DIR/$db_name" + REMOTE_DB_DIR="$REMOTE_DIR/$db_name" + BACKUP_FILE="$LOCAL_DB_DIR/${db_name}_backup_$DATE.bak" + REMOTE_BACKUP_FILE="${db_name}_backup_$DATE.bak" -# Perform database backup with CHECK option -$SQLCMD -S $HOST -U $USER -P $PASSWORD -Q "BACKUP DATABASE [$DB_NAME] TO DISK = N'$BACKUP_FILE' WITH CHECKSUM, COMPRESSION, INIT" -C >> $LOG_FILE 2>> $ERROR_LOG_FILE + # Create local backup directory if it doesn't exist + mkdir -p "$LOCAL_DB_DIR" + chown mssql:mssql "$LOCAL_DB_DIR" -# Check if the local backup was successful -if [ $? -eq 0 ]; then - LOCAL_STATUS=0 - echo "$(LC_TIME=C date): Local backup completed successfully for $DB_NAME." >> $LOG_FILE -else - LOCAL_STATUS=1 - echo "$(LC_TIME=C date): Error during local backup of $DB_NAME. Check error log." >> $LOG_FILE -fi - -# End time and duration -END_TIME=$(date +%s) -DURATION=$((END_TIME - START_TIME)) -echo "$(LC_TIME=C date): Backup ended for $DB_NAME." >> $LOG_FILE -echo "Start time: $(LC_TIME=C date -d @$START_TIME)" >> $LOG_FILE -echo "End time: $(LC_TIME=C date -d @$END_TIME)" >> $LOG_FILE -echo "Duration: $((DURATION / 60)) minutes and $((DURATION % 60)) seconds" >> $LOG_FILE - -# Upload backup to remote FTP server -echo "$(LC_TIME=C date): Starting FTP upload for $DB_NAME." >> $LOG_FILE -ftp -inv $FTP_SERVER >> $LOG_FILE 2>> $ERROR_LOG_FILE <> $LOG_FILE 2>> $ERROR_LOG_FILE user $FTP_USER $FTP_PASSWORD -cd $REMOTE_DB_DIR -put $BACKUP_FILE $REMOTE_BACKUP_FILE +mkdir "$REMOTE_DB_DIR" bye EOF + fi -# Check FTP status -if [ $? -eq 0 ]; then - FTP_STATUS=0 - echo "$(LC_TIME=C date): FTP upload completed successfully for $DB_NAME." >> $LOG_FILE -else - FTP_STATUS=1 - echo "$(LC_TIME=C date): Error during FTP upload for $DB_NAME. Check error log." >> $LOG_FILE -fi + # Start time for the backup + START_TIME=$(date +%s) + echo "$(LC_TIME=C date): Starting backup for database: $db_name on server $SERVER_NAME" > $LOG_FILE -# Local retention policy -ls -1tr $LOCAL_DB_DIR/${DB_NAME}_backup_*.bak | head -n -$RETENTION | xargs -d '\n' rm -f >> $LOG_FILE 2>> $ERROR_LOG_FILE + # Perform database backup with CHECK option (fixed SQLCMD syntax) + $SQLCMD -S "$HOST" -U "$USER" -P "$PASSWORD" \ + -Q "BACKUP DATABASE [$db_name] TO DISK = N'$BACKUP_FILE' WITH CHECKSUM, COMPRESSION, INIT" \ + -C -b >> $LOG_FILE 2>> $ERROR_LOG_FILE -# Remote retention policy -echo "$(LC_TIME=C date): Performing remote retention check." >> $LOG_FILE + # Check if the local backup was successful + if [ $? -eq 0 ] && [ -f "$BACKUP_FILE" ]; then + LOCAL_STATUS=0 + echo "$(LC_TIME=C date): Local backup completed successfully for $db_name." >> $LOG_FILE + else + LOCAL_STATUS=1 + echo "$(LC_TIME=C date): Error during local backup of $db_name. Check error log." >> $LOG_FILE + fi -# Otteniamo la lista dei file remoti (escludiamo informazioni non rilevanti come dati FTP) -ftp -inv $FTP_SERVER < /tmp/ftp_file_list.txt 2>> $ERROR_LOG_FILE + # End time and duration + END_TIME=$(date +%s) + DURATION=$((END_TIME - START_TIME)) + echo "$(LC_TIME=C date): Backup ended for $db_name." >> $LOG_FILE + echo "Start time: $(LC_TIME=C date -d @$START_TIME)" >> $LOG_FILE + echo "End time: $(LC_TIME=C date -d @$END_TIME)" >> $LOG_FILE + echo "Duration: $((DURATION / 60)) minutes and $((DURATION % 60)) seconds" >> $LOG_FILE + + # Upload backup to remote FTP server only if local backup was successful + if [ $LOCAL_STATUS -eq 0 ]; then + echo "$(LC_TIME=C date): Starting FTP upload for $db_name." >> $LOG_FILE + + if ! check_ftp_connection; then + FTP_STATUS=1 + echo "$(LC_TIME=C date): Cannot establish FTP connection. Upload failed." >> $LOG_FILE + echo "FTP connection failed - Cannot connect to server" >> $ERROR_LOG_FILE + else + if perform_ftp_upload "$BACKUP_FILE" "$REMOTE_BACKUP_FILE"; then + FTP_STATUS=0 + echo "$(LC_TIME=C date): FTP upload completed successfully for $db_name." >> $LOG_FILE + else + FTP_STATUS=1 + echo "$(LC_TIME=C date): Error during FTP upload for $db_name. Check error log." >> $LOG_FILE + fi + fi + else + echo "$(LC_TIME=C date): Skipping FTP upload due to local backup failure." >> $LOG_FILE + FTP_STATUS=1 + fi + + # Local retention policy + if [ $LOCAL_STATUS -eq 0 ]; then + find "$LOCAL_DB_DIR" -name "${db_name}_backup_*.bak" -type f -mtime +$RETENTION -delete >> $LOG_FILE 2>> $ERROR_LOG_FILE + fi + + # Remote retention policy + if [ $FTP_STATUS -eq 0 ]; then + echo "$(LC_TIME=C date): Performing remote retention check." >> $LOG_FILE + + temp_list_file=$(mktemp) + ftp -inv $FTP_SERVER > $temp_list_file 2>> $ERROR_LOG_FILE <> $LOG_FILE + if [ -n "$file_date" ]; then + echo "$(LC_TIME=C date): Data estratta dal file remoto $file: $file_date" >> $LOG_FILE + retention_date=$(LC_TIME=C date -d "-$REMOTE_RETENTION days" +%Y%m%d%H%M%S) - # Confrontiamo direttamente la data come numeri (formato YYYYMMDDHHMMSS) - retention_date=$(LC_TIME=C date -d "-$REMOTE_RETENTION days" +%Y%m%d%H%M%S) - - # Verifica se la data estratta è più vecchia della retention - if [ "$file_date" -lt "$retention_date" ]; then - # Elimina il file remoto se è troppo vecchio - ftp -inv $FTP_SERVER <> $LOG_FILE 2>> $ERROR_LOG_FILE - user $FTP_USER $FTP_PASSWORD - cd $REMOTE_DB_DIR - delete "$file" - bye + if [ "$file_date" -lt "$retention_date" ]; then + ftp -inv $FTP_SERVER <> $LOG_FILE 2>> $ERROR_LOG_FILE +user $FTP_USER $FTP_PASSWORD +cd $REMOTE_DB_DIR +delete "$file" +bye EOF - echo "$(LC_TIME=C date): File remoto $file eliminato perché più vecchio di $REMOTE_RETENTION giorni." >> $LOG_FILE + echo "$(LC_TIME=C date): File remoto $file eliminato perché più vecchio di $REMOTE_RETENTION giorni." >> $LOG_FILE + fi + fi fi - else - echo "$(LC_TIME=C date): Data non estratta correttamente dal nome del file remoto $file. Skipping file." >> $LOG_FILE - fi - else - echo "$(LC_TIME=C date): Il file remoto $file non appartiene al database $DB_NAME. Skipping file." >> $LOG_FILE + done + rm -f $temp_list_file fi -done -# Determine email status -if [ $LOCAL_STATUS -eq 0 ] && [ $FTP_STATUS -eq 0 ]; then - EMAIL_STATUS="SUCCESS" + # Determine email status + if [ $LOCAL_STATUS -eq 0 ] && [ $FTP_STATUS -eq 0 ]; then + EMAIL_STATUS="SUCCESS" + elif [ $LOCAL_STATUS -eq 1 ] && [ $FTP_STATUS -eq 1 ]; then + EMAIL_STATUS="FAILED" + else + EMAIL_STATUS="WARNING" + fi + + # Send email with log + send_email "$db_name" + + # Cleanup + rm -f $LOG_FILE + [ -f "$ERROR_LOG_FILE" ] && [ ! -s "$ERROR_LOG_FILE" ] && rm -f "$ERROR_LOG_FILE" + + echo "Backup process complete for $db_name with status: $EMAIL_STATUS (Local: $LOCAL_STATUS, FTP: $FTP_STATUS)" + return $(( LOCAL_STATUS | FTP_STATUS )) +} + +# Main script logic +if [ -z "$1" ]; then + echo "No database specified, backing up all databases..." + OVERALL_STATUS=0 + + for db in $(get_all_databases); do + perform_backup "$db" + BACKUP_STATUS=$? + [ $BACKUP_STATUS -ne 0 ] && OVERALL_STATUS=1 + done + + exit $OVERALL_STATUS else - EMAIL_STATUS="FAILED" + perform_backup "$1" + exit $? fi - -# Send the email with the log -send_email - -# Remove the log file after sending the email -rm -f $LOG_FILE - -echo "Backup process complete, local status: $LOCAL_STATUS, FTP status: $FTP_STATUS, and email sent." - -# Send the email with the log -send_email - -# Remove the log file after sending the email -rm -f $LOG_FILE - -echo "Backup process complete, local status: $LOCAL_STATUS, FTP status: $FTP_STATUS, and email sent." \ No newline at end of file