#4sysadmins

Inicio » GNU/Linux » 4backup : Programa para crear copias de seguridad (full, diferenciales e incrementales)

4backup : Programa para crear copias de seguridad (full, diferenciales e incrementales)

Follow #4sysadmins on WordPress.com

4backup : Shell Script

AVISO: Se ha modificado el script. Anteriormente a menos que tuviésemos un servidor de correos configurado en local o haciendo relay a una cuenta por ejemplo de gmail, era imposible ver si el programa fallaba por algún motivo (directorios inaccesibles, unidades externas no montadas, etc…). Ahora comprueba si el directorio $BASE permite la escritura y crea la jerarquía de directorios. En caso de fallo podremos acudir al log.

Este script pertenece a la segunda parte de la entrada Backups con tar: fullbackups, incrementales y diferenciales. Existe una tercera y última parte Usuario para crear respaldos con 4backup en la que crearemos un usuario restringido propietario y creador de los backups.

El script está bastante comentado ya que se creó como ejercicio de práctica del Capítulo -9 El entorno de Consola, Shell Scripts, el Correo Electrónico y uso básico de SQL en el que además aprenderemos a configurar el servidor de correos postfix para que haga relay a gmail y así recibir los email.


#!/bin/bash
##################################################################################

#                   | || | | |__   __ _  ___| | ___   _ _ _                      #
#                   | || |_| '_ \ / _` |/ __| |/ / | | | '_ \                    #
#                   |__   _| |_) | (_| | (__|   <| |_| | |_) |                   #
#                      |_| |_.__/ \__,_|\___|_|\_\\__,_| .__/                    #
#                                                      |_|                       #

##################################################################################

# 4backup crea copias de seguridad en función de los días pasado como parámetros.
# El primer parámetro determina que día se realiza fullbackup y el segundo y último
# parámetro indica el día para la copia diferencial.
# Para el resto de los días de la semana se crearán backups incrementales.
# Después de realizarse los fullbackups, estos serán comprobados y si todo es OK
# todas las copias anteriores a esos fullbackup serán eliminadas. Puede  adaptarse
# a necesidades, como por ejemplo en vez de eliminar las copias moverlas a otro disco.

##################################################################################
#                                                                                #
#                       autor: nebul4ck                                          #
#                       fecha: 28-3-2015                                         #
#                       última modificación: 27-7-2015                           #
#                       web: https://nebul4ck.wordpress.com                      #
#                                                                                #
##################################################################################

# Como se va a lanzar desde cron lo primero es ejecutar mediante source el archivo
# de configuración del entorno del usuario: ~.profile (Debian) o ~/.bash_profile
# (Red Hat)
. ~/.profile

# // VARIABLES A MODIFICAR POR EL USUARIO //

# Directorio BASE del programa 4backup (la raíz de los archivos y dir creados por 4backup).
BASE="/home/-USUARIO-/4backup"

# ¿ Donde se encuentra montado el disco externo en el que guardaremos las copias ?
MOUNTPOINT="/media/USB"

# ¿ En que directorio quieres que se guarden las copias de seguridad dentro del disco externo ?
DESTINO="$MOUNTPOINT/Mis Backups"

# Lista de directorios a respaldar
LISTA_DIR=(/home/jjgomez/Imágenes /opt/apps /usr/local/share)

# Si tenemos un servidor de correos local configurado o que haga relay a un correo de gmail por
# ejemplo, podremos recibir las alertas y el log por mail
EMAIL="usuario@hostname"

# // VARIABLES GLOBALES PARA 4BACKUP //

# Fecha usada para la extensión de los archivos tar
EXT="`date +%d%b%y.tar`"

# Día de la semana. Útil par cuando el programa comprueba que día de la semana es.
DIA=`date | cut -d" " -f1`

# Directorio donde serán guardados los snapshot de los backups con tar
SNAP="$BASE/4history"

# Directorio para archivos de registros (logs)
LOGS="$BASE/log"

# Archivo de registro
LOG="$BASE/log/4backup.log"

# Archivos de registros antiguos
OLDLOGS="$BASE/log/old"

# Archivo de emergencia. Usado cuando no ha sido posible iniciar el programa y ni
# enviar el correo al destino.
ARCHEMERG="$BASE/emerg.4backup"

# FUNCIONES

# Mostrar ayuda
help4backup() {
        echo -e "Especificar el día para copias full y el día para copias diferenciales (respectivamente).\nEl resto de días se realizarán copias incrementales\n"
        echo -e "\tEjemplo: ./4backup lun jue\n"
        echo -e "Nota: Los días en formato corto y Español"
        exit 0
}

# Función que comprueba si existen los directorios a respaldar
compruebaDir() {
if [ ! -d "$DIR" ]
then
                echo -e "ERROR: No es posible respaldar $DIR. El directorio no existe.\n"
                continue
fi
}

# Comprueba los Backups
checkBackup() {
        ESTADO="OK"
        if [ "$1" == "error" ]
        then
                ESTADO="ERROR"
                echo -e "\nEMERG: No se pudo verificar el archivador $TAR. Se recomienda revisar la última copia\n"
        elif [ "$1" == "$TAR".gz ]
        then
                echo -e "\nINFO: Comprobando backup...\n"
                if [ "$2" == "incremental" ]
                then
                        CAMBIOS=`tar -tf "$TAR".gz | wc -l`
                        echo -e "\nSe han realizado $CAMBIOS cambios - $(date "+[%x %X]")\n"
                fi
                gzip -vt "$TAR".gz
                if [ "$?" -ne "0" ]
                then
                        ESTADO="ERROR"
                else
                        echo -e "\nLa copia $TAR.gz ha finalizado correctamente - $(date "+[%x %X]")\n"
                fi
        fi
}

# Envía email
enviaCorreo() {
        exec &> /dev/null
        mail -s "$1" tudirección@gmail.com << EOF
Correo de notificación $(date "+[%x %X]")

$2



Aviso: Mensaje generado automáticamente.
EOF

if [ "$?" -ne "0" ]
then
        if [ "$RETORNO" == "error" ]
        then
                echo -e "Mail no enviado. Revise /var/log/mail.* o maillog\n\nEl error por el que la aplicación no ha iniciado es:\n$2" > "$ARCHEMERG"
        else
                echo -e "Mail no enviado. Revise /var/log/mail.* o maillog\n" >> "$LOG"
        fi
else
        echo -e "Mail enviado!\n"
fi
exit 0
}

# COMPROBACIONES PREVIAS

# Comprobar el número de parámetros y si estos coinciden
DIAS=(lun mar mié jue vie sáb dom)

if [ "$#" -lt "2" ] || [ "$#" -ge "3" ]
then
        help4backup
else
# Dos maneras diferentes de desplegar el contenido completo de un array @ y *
        if ! echo "${DIAS[@]}" | grep -qw "$1" && ! echo "${DIAS[*]}" | grep -qw "$2"; then help4backup; fi
fi

# Creamos el directorio base y log para registrar posibles errores durante la ejecución
if [ ! -d "$LOGS" ]
then
        mkdir -p "$LOGS" &>> /dev/null

        if [ "$?" -ne "0" ]
        then
                echo -e "No es posible escribir en el directorio base ($BASE). \nEl directorio $LOGS no puede ser creado.\nInterrumpiendo ejecución...\n"
                exit 1
        fi
fi

# Abrimos log
exec &> "$LOG"

# Comprueba que el dispositivo este montado, mediante mtab y su directorio (punto de montaje)
if ! cat /etc/mtab | grep -qw "$MOUNTPOINT"; then RETORNO="error" enviaCorreo "Dispositivo no montado" "El dispositivo ($MOUNTPOINT) no se encuentra montado. Abortado"; fi

for dir in "$DESTINO" "$BASE" "$SNAP" "$OLDLOGS"
do
    test -d "$dir" || mkdir -p "$dir" &>> /dev/null
    if [ "$?" -ne "0" ]
    then
        case "$dir" in
            "$DESTINO") MSG="No se pudo crear el directorio $DESTINO. Abortado.";;
            "$BASE") MSG="No se pudo crear el directorio $BASE. Abortado.";;
            "$SNAP") MSG="No se pudo crear el directorio $SNAP. Abortado.";;
            "$OLDLOGS") MSG="No se pudo crear el directorio $OLDLOGS. Abortado.";;
        esac
    enviaCorreo "Error al iniciar 4Backup" "$MSG"
    fi
done

# Abrimos log de ejecución
exec &> "$LOG"

# Inicia programa
for ((n=0;n<"${#LISTA_DIR[@]}";n++))
    {
# Seteamos variables para cada directorio a respaldar:
        DIR="${LISTA_DIR[$n]}"
# Esta es una forma de obtener el último campo de una línea (con egrep) en las copias diferenciales
# se muestra otra forma de obtener el último campo de línea con awk
        NOMBRE=`echo "$DIR" | egrep -o "[^/]+$"`
        HIST="$SNAP"/"$NOMBRE".snap

# Comprobamos si existe copia full de ese directorio:
        if [ ! -e "$HIST" ] || [ "$1" == "$DIA" ]
        then
            echo -e "----------------------------------------------------------------------------------------------"
            echo -e "La copia para $DIR será full.\n"
            TIPO="full"
        else
            if [ "$DIA" == "$2" ]
            then
                TIPO="dif"
            else
                TIPO="inc"
            fi
        fi

        case "$TIPO" in
        full)
            PREFIJO="full"
            TAR="$DESTINO"/"$PREFIJO"_"$NOMBRE"_"$EXT"
# Función que comprueba si existe el directorio a respaldar.
                        compruebaDir "$DIR"
                        if [ -f "$HIST" ]
                        then
# Se elimina el snapshot de tar para que la nueva copia sea full
                                rm "$HIST"
                                echo -e "Archivo histórico $HIST eliminado\n"
                        fi

                        echo -e "Iniciando FullBackup para $DIR\t $(date "+[%x %X]")\n"

# Nos movemos al directorio que queremos respaldar. De esta forma evitaremos agregar la ruta completa al archivo tar.
                        cd "$DIR"
# Podríamos haber hecho esto de otras maneras pero está es en la que mejor veremos lo que se hace. Primero se crea el tar full
# si el tar se ha finalizado correctamente se comprime con grado 8 y se llama a la función checkBackup para comprobar la compresión.
                        if tar -cpvWf "$TAR" -g "$HIST" *
                        then
                                 gzip -8f "$TAR"
# Función que comprueba el estado del backup e informamos
                                checkBackup "$TAR".gz
                        else
                                checkBackup "error"
                        fi

# Si se ha devuelto un valor positivo para la verificación de la copia entonces eliminamos la anterior. Si
# el valor devuelto es erróneo entonces no se elimina la anterior y se sigue con la siguiente copia
                        if [ "$ESTADO" == "OK" ]
                        then
                                echo -e "Buscando backups de $DIR anteriores a este último full..."
                                BACKOLD=`find "$DESTINO" -maxdepth 1 -type f -mmin +600 -name *_"$NOMBRE"_*.tar.gz |wc -l`

                                if [ "$BACKOLD" -eq "0" ]
                                then
                                        echo -e "No se han encontrado backups antiguos de $DIR para eliminar.\n"
                                else
                                        find "$DESTINO" -maxdepth 1 -type f -mmin +600 -name *_"$NOMBRE"_* -exec rm -f {} \; && echo -e "Backups anteriores encontrados y eliminados.\n"
                                fi
                        else
                                echo -e "El FullBackup anterior no será eliminado. Esta copia ha finalizado con errores\n"
                                FIN="ERROR"
                        fi
            ;;
        inc)
            PREFIJO="inc"
            TAR="$DESTINO"/"$PREFIJO"_"$NOMBRE"_"$EXT"
                        compruebaDir "$DIR"
                        echo -e "----------------------------------------------------------------------------------------------"
                        echo -e "Iniciando backup INCREMENTAL para $DIR $(date "+[%x %X]")\n"
                        cd "$DIR"
                        if tar -cpvWf "$TAR" -g "$HIST" *
                        then
                                gzip -8f "$TAR"
                                checkBackup "$TAR".gz "incremental"
                        else
                                checkBackup "error"
                                FIN="ERROR"
                        fi
        ;;
        dif)
            PREFIJO="dif"
            TAR="$DESTINO"/"$PREFIJO"_"$NOMBRE"_"$EXT"
# Con este método también podemos recuperar el último campo (awk)
                        TARFULL=`ls -l "$DESTINO" | grep full_"$NOMBRE" | awk '{printf $NF}'`
# Sacamos la fecha para que respaldemos solo desde la última full hasta el momento
                        FECHA=`date -r "$DESTINO"/"$TARFULL" "+%F %H:%M"`
                        compruebaDir "$DIR"
                        echo -e "----------------------------------------------------------------------------------------------"
                        echo -e "Iniciando backup DIFERENCIAL para $DIR $(date "+[%x %X]")\n"
                        cd "$DIR"
                        if tar -cpvWf "$TAR" * -N "$FECHA"
                        then
                                gzip -8f "$TAR"
                                checkBackup "$TAR".gz
                        else
                                checkBackup "error"
                                FIN="ERROR"
                        fi
        ;;
        esac
    }

# Enviamos email con los resultados. Comprobamos el día para setear el primer parámetro y si hubieron errores
# Con esta estructura condicional aprendemos a anidar if... elif.... else fi
if [ "$DIA" == "$1" ]
then
    BACKUP="FULLBACKUPS"
elif [ "$DIA" == "$2" ]
then
    BACKUP="DIFBACKUPS"
else
    BACKUP="INCBACKUPS"
fi

if [ -z "$FIN" ]
then
    CHECK="OK"
else
    CHECK="ERROR"
fi

#Para terminar hacemos una copia del log en el directorio de logs antiguos
cp "$LOG" "$OLDLOGS"/backup_"$(date +%d%b%y)".log

enviaCorreo  "$BACKUP - $CHECK" "$(cat $LOG)"

Descargarlo limpio de comentarios:

4backup

ATENCIÓN: El script del enlace está en formato doc. Una vez descargado eliminar la extensión o modificarla por .sh

Licencia Creative Commons
4backup por nebul4ck se distribuye bajo una Licencia Creative Commons Atribución-NoComercial-CompartirIgual 4.0 Internacional.


22 comentarios

  1. rocesvinto dice:

    Una pregunta:
    ¿Cómo restaurar los datos?

    Una consideración:
    buen artículo

    Me gusta

  2. Genial. Muchas gracias.
    Como aporte, he modificado las líneas 239, 273 y 293 para añadir patrones de exclusión -> if tar -cpvWf «$TAR» -g «$HIST» -X «${DESTINO}/exclude.txt» *

    Me gusta

  3. Hola,
    Ante todo muchas gracias por el aporte,
    Soy un poquito nuevo en linux, y hay ciertas cosas que no entiendo, he generado el scriptbackup.sh he copiado el script, y modificado donde quiero que me haga las copias, cuando ejecuto ./scriptbackup vie dom me indica todo el rato; (Especificar el dia para copias full y el dia para copias diferenciales (respectivamente).
    El resto de dias se realizaran copias incrementales»)
    No se si tengo que modificar algo más.
    Quedo atento y gracias.

    Me gusta

  4. alemanco dice:

    Sigo sin poder hacer gz en los .tar .

    Tuve que cambiar el idioma a español para que cogiera los dias, pense que ahi estaba el error y no..sigue sin comprimirlos y me colapsa el server..el log dice poco:

    «tar: Exiting with failure status due to previous errors

    EMERG: No se pudo verificar el archivador /media/dades/Backups/full_etc_06abr17.tar. Se recomienda revisar la última copia

    El FullBackup anterior no será eliminado. Esta copia ha finalizado con errores
    »

    Las pruebas las estoy realizando desde 0, sin snapshots ni ningun backup anterior… Cuando ejecuto se crean bien los full de los dos path que indico, el log muestra todo compreso y la verificacion..el unico error este que te pongo.

    Imagino que el script se esta quedando aqui…No entiendo ese if…$1 no es el primer dia de la semana que le pones al ejecutarlo? ./4backup.sh lun jue …lun=$1 , jue=$2¿?¿

    ESTADO=»OK»
    if [ «$1» == «error» ]
    then
    ESTADO=»ERROR»
    echo -e «\nEMERG: No se pudo verificar el archivador $TAR. Se recomienda revisar la última copia\n»
    elif [ «$1» == «$TAR».gz ]

    Me gusta

    • nebul4ck dice:

      Has depurado ya 4backups?, no es complicado y te hará entender todo mejor y hacerte a los comandos. Es un script básico, si sigues la trazas y lo vas depurando, encuentras la solución.

      Ánimo!!! Saludos

      Me gusta

  5. Jaime Oliva dice:

    Buenos días,

    Lo primero felicitarte y darte las gracias por el script, es muy bueno, pero no entiendo muy bien como funciona, como puedo configurar los días de la copias full, diferencial e incremental?. Luego también quería saber como lo pongo en el crontab. no se si me he explicado bien , cuando yo lanzo el comando pongo sudo ./4backup ./backup lun y funciona pero cuando pongo ./4backup ./backup lun jue no funciona con lo cual si quiero que se haga la copias full dos dias no se hacerlo. hay algun sitio para configurarlo.?

    gracias de antemano

    Me gusta

    • nebul4ck dice:

      Hola Jaime, el programa se invoca tan solo con dos parámetros, el primero el día para las copias full y el segundo para las diferenciales e incrementales, según toque. Tu problema es que estás pasando dos parámetros ya y no te deja meter el tercero que es el de las copias diferenciales e incrementales. Debes de ejecutar el programa tal que así:

      # ./4backups dia_full dia_diferencial

      Por ejemplo: # ./4backups lun jue

      Un saludo!

      Le gusta a 1 persona

  6. excelente script amigo una pregunta podrías ayudarme a modificarlo un poco para que cuando haga los backup no haga compresión alguna?

    Me gusta

    • nebul4ck dice:

      Hola Carlos!, a ver.. te puedo ayudar en algo pero es importante que lo hagas tu, ya te lleve 1 día, si lo que quieres es aprender, además de tener un «programa» que sabe lo que hace, algo que ayuda a la hora de detectar posibles errores.

      Cosas que tienes que tener en cuenta:

      Cada vez que aparece un comando gzip, deberías de eliminar esas líneas, por ejemplo:

      gzip -8f «$TAR» -> Aquí se comprime el tar con un grado de 8. Como no quieres comprimir pues la eliminas.

      gzip -vt «$TAR».gz -> Esta línea de la función «checkbackup» comprueba el archivo comprimido, como ya no se ha comprimido deberías de eliminarla. Aquí debes de tener en cuenta que tras la comprobación, existe la siguiente estructura lógica:

      if [ «$?» -ne «0» ]
      then
      ESTADO=»ERROR»
      else

      Que lo que hace es comprobar que el comando anterior se ha ejecutado correctamente, como se ha eliminado y no se va a ejecutar, pues se elimina también estas líneas.

      Otra cosa a tener en cuenta una vez eliminado todas las líneas donde se procede a comprimir o comprobar una compresión, es el nombre de los archivos, por ejemplo líneas como la siguiente deberían de ser corregidas, ya que ningún archivo terminará en gz:

      elif [ «$1» == «$TAR».gz ]

      ó

      BACKOLD=`find «$DESTINO» -maxdepth 1 -type f -mmin +600 -name *_»$NOMBRE»_*.tar.gz

      En las líneas anteriores deberías de quitar el .gz

      Bueno amigo estos son algunos puntos a tener en cuenta, no es complicado, pero deberás de prestar atención al código, saber lo que hace y ejecutarlo con directorios de pruebas, hasta que te de buenos resultados.

      Un saludo!!

      Me gusta

      • alemanco dice:

        A mi me ocurre que no me los comprime, los deja solo en tar. Alguna idea?

        El script es grandioso :D

        Me gusta

      • nebul4ck dice:

        En el log no dice nada?, mira el log y dime que copias son las que no te comprimen, si las full, diferenciales… para echarle un vistazo cuando tenga un ratillo.

        Un saludo!!

        Me gusta

  7. Julian Andres dice:

    hola, estoy intentado ejecutar este script en una Raspberri Pi con Raspbian instalado, pero al intentar ejecutarlo tanto en la linea LISTA_DIR como DIAS me salta el error «Syntax error: «(» unexpected».

    Asi que cambio la linea

    DIAS=(lun mar mié jue vie sáb dom)

    por

    DIAS=»lun mar mié jue vie sáb dom»

    lo mismo para LISTA DIR.

    luego intento ejecutar con

    sudo sh copia.sh mié sab

    y me salta es la ayuda y no funciona. ahora no se si es debido a esas comillas o estoy intentando ejecutar mal el script.

    Ahora nose que mas hacer para saber si esto funciona o no.

    Me gusta

    • nebul4ck dice:

      Hola Julian, lo que ocurre es que el shell de Raspbian no te está interpretando correctamente el script y esto se debe a que el script está desarrollado para ser ejecutado por Bash. En muchos sistemas Linux, Bash es un simple enlace simbólico a sh y aquí, igualmente podríamos encontrar errores gramaticales. No obstante esto va mas allá. Si vas a tu Raspbian (yo uso openelec, el cual está montado sobre Raspbian) y haces un «ls -l /bin |grep bash» verás que tu bash es un enlace simbólico a busybox. Busybox es un «todoenuno» el cual contiene compiladas numerosas herramientas de un sistema GNU/Linux y que se suele instalar en los sistemas Linux embebidos, es decir los que usa por ejemplo Android. Busybox no contiene todas las funcionalidades de las herramientas compiladas. Lo mas seguro es que bash en este caso se encuentre compilado dentro de BusyBox y no sea un bash como tal por lo que en resumidas cuentas este script no va a funcionar a menos que vayas adaptándolo según los inconvenientes que te vayas encontrando. Seguramente haciendo algo mucho mas básico, te llegue a funcionar y puedas respaldar tus archivos automáticamente. Un saludo!

      Le gusta a 1 persona

      • Julian Andres dice:

        Ok, gracias por responder, ire rehaciendo el script, lo mas seguro que es tenga que dividirlo en varias partes y ejecutarlo a punta de cron para que me haga segun que dia el tipo de compresion.

        Gracias por todo excelente blog.

        Un saludo.

        Me gusta

      • nebul4ck dice:

        Gracias a ti! Saludos

        Me gusta

    • Julian Andres dice:

      ACTUALIZO, Si que funciona en una Raspberry Pi con Raspbian, lo he hecho de la siguiente forma por si en un futuro alguien lo necesita.

      pi@raspberry ~ $ nano 4Backup
      //pego el script que anteriormente he configurado con mis rutas de archivo, de resto todo funciona correctamente.

      //le doy permisos de ejecucion
      pi@raspberry ~ $ sudo chmod 777 4Backup

      //ahora ejecutare el script para que haga las copias full los Lunes y las diferenciales los miércoles
      // tanto el miercoles como el sabado lleban tilde mié y sáb
      pi@raspberry ~ $ sudo ./script lun mié

      y listo, ya esto lo meto a que se ejecute todos los dias con un crontab -e.

      Muchas gracias Nebul por el script. eres un crack.

      Me gusta

      • nebul4ck dice:

        Julian Andres, muchas gracias por tu aportación!! esperemos que a otros compañeros les sea útil. ¿ Puedes por favor ejecutar los siguientes comandos y pegar aquí la info?

        $ cat /etc/release
        $ uname -a

        Gracias!!

        Me gusta

  8. […] 4backup : Programa para crear copias de seguridad (full, diferenciales e incrementales) marzo 20, 2015 […]

    Me gusta

  9. […] 4backup : Programa para crear copias de seguridad (full, diferenciales e incrementales) marzo 20, 2015 […]

    Me gusta

Deja un comentario, Gracias!