# [HOWTO] Time-Machine en Linux (a.k.a. RIBS)

## galidor

HowTo Time-Machine en Linux

Hola de nuevo a todos, tras estar haciendo muchas pruebas ya tengo un script para las copias de seguridad en mis servidores. Se trata de un time-machine algo menos vistoso que el de MacOS X pero igual de funcional. Yo lo he llamado cariñosamente COSTILLAS (RIBS - Rsync Incremental Bash Script). Este script junto con un par de volúmenes SAMBA exportados da a los usuarios una autonomía completa para recuperar datos de versiones anteriores. Hace uso de enlaces duros para optimizar al máximo el espacio en disco utilizado sin duplicar datos innecesarios.

Ahí va el código:

```

#!/bin/bash

#

# Programed by Sergio Aleixandre Somoza

#

# 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 3 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, see <http://www.gnu.org/licenses/>.

#

## RIBS (rsync Incremental Backup System)

VER="0.6.3"

echo -e   "\e[0;35m"

echo -e   "\t ____  ___ ____ ____  "

echo -e   "\t|  _ \|_ _| __ ) ___| "

echo -e   "\t| |_) || ||  _ \___ \ "

echo -e   "\t|  _ < | || |_) |__) |"

echo -e   "\t|_| \_\___|____/____/ "

echo

echo -e   "\e[1;37m RSYNC Incremental Bash Script \e[0;37mv$VER"

echo -e "\e[0;37m"

## Define some colors.

red='\e[0;31m'

RED='\e[1;31m'

green='\e[0;32m'

GREEN='\e[1;32m'

white='\e[0;37m'

WHITE='\e[1;37m'

## Vars definition.

DATE=`date +'%y-%m-%d.%H-%M'`

MAX_SIZE="419430400"    # In KiB 1024 (not KB 1000)

SOURCE_DIR="/var/samba/shared"

BACKUP_DIR="/mnt/ribs"

DEST_DIR="$BACKUP_DIR/$DATE.BACKUP"

LASTBACKUP="$BACKUP_DIR/last-backup"

LOG_DIR="/var/log/ribs"

LOG_FILE="ribs.log"

if [ $UID -ne 0 ] #The script must be run under root privileges.

then

   echo -e "${WHITE}[ ${red}ERROR ${WHITE}] ${white}You must be root"

   exit 2

fi

if [ $# != 1 ]

then 

   echo -e "${WHITE}[ ${red}ERROR ${WHITE}] ${white}Syntax Error"

   exit 2

fi

if [ ! -d $LOG_DIR ]

then

   mkdir -p $LOG_DIR

fi

## Remount Read-Only and Read-Write Functions

remount_ro() {

   mount -o remount,ro $BACKUP_DIR || return 1

}

remount_rw() {

   mount -o remount,rw $BACKUP_DIR || return 1

}

log() {

   echo `date` :: $1 >> $LOG_DIR/$LOG_FILE

}

space_test() {

   echo -e "${green}*${white} Making space test..."

   TRANS_SIZE=`rsync -rLtcn --stats --delete --link-dest=$LASTBACKUP $SOURCE_DIR/ $DEST_DIR 2> /dev/null | grep "Total transferred file size:" | sed "s/  */#/g" | cut -d# -f5`

   TRANS_SIZE=`expr $TRANS_SIZE / 1024` # Pass data to K

   ACT_SIZE=`df | grep $BACKUP_DIR | sed "s/  */#/g" | cut -d# -f3`

   TOTAL_SIZE=`expr $TRANS_SIZE + $ACT_SIZE`

   FREE_SPACE=`expr $MAX_SIZE - $TOTAL_SIZE`

   if [ $FREE_SPACE -le 0 ]

   then

      echo -e " ${WHITE}[ ${red}ERROR ${WHITE}] ${white}Not enough free space on $BACKUP_DIR or MAX_SIZE exceeded."      

      echo -e " ${green}* ${white}Deleting older backups..."

   fi

   while [ $FREE_SPACE -le 0 ]

   do

      delete_older

      ACT_SIZE=`df | grep $BACKUP_DIR | sed "s/  */#/g" | cut -d# -f3`

      TOTAL_SIZE=`expr $TRANS_SIZE + $ACT_SIZE`

      FREE_SPACE=`expr $MAX_SIZE - $TOTAL_SIZE`

   done

   if [ $FREE_SPACE -gt 0 ]

   then

      log "Free RIBS Space: $FREE_SPACE K"

      echo -e " ${WHITE}[ ${green}DONE ${WHITE}] ${white}Space test passed."

      return 0

   fi

}

delete_older() {

   local OLDER_BACKUP=`ls -1r $BACKUP_DIR | tail -n1`

   rm -rf $BACKUP_DIR/$OLDER_BACKUP && echo -e " ${WHITE}[ ${green}DONE ${WHITE}] ${white}Deleted $OLDER_BACKUP"

   log "Deleted $OLDER_BACKUP"

}

## Snapshot Function (Run Once, It creates the base for incremental system)

snapshot() {

   DEST_DIR="$DEST_DIR-S"

   remount_rw || echo -e "[ ${red}ERROR ${white}] \t Impossible to mount $BACKUP_DIR in RW mode."

   if [ ! -d $DEST_DIR ]

   then

      mkdir $DEST_DIR

   fi

   space_test

   if [ $? ]

   then

      rsync -rLtc --stats --progress $SOURCE_DIR/ $DEST_DIR && echo -e "${WHITE}[ ${green}DONE ${WHITE}] ${white}Snapshot Backup."

      log "Snapshot"

   fi

   ln -s $DEST_DIR $LASTBACKUP

   remount_ro

}

## Incremental Function (It must be run periodicaly)

incremental() {

   DEST_DIR="$DEST_DIR-I"

   remount_rw || echo -e "[ ${red}ERROR ${white}] \t Impossible to mount $BACKUP_DIR in RW mode."

   if [ ! -d $DEST_DIR ]

   then

      mkdir $DEST_DIR

   fi

   space_test

   if [ $? ]

   then

      echo -e "${green}*${white} Making incremental copy..."

      rsync -rLtc --stats --delete --link-dest=$LASTBACKUP $SOURCE_DIR/ $DEST_DIR && echo -e " ${WHITE}[ ${green}DONE ${WHITE}] ${white}Incremental Backup."

      log "Incremental"

   fi

   rm -rf $LASTBACKUP

   ln -s $DEST_DIR $LASTBACKUP

   remount_ro

}

case $1 in

snapshot) snapshot;;

incremental) incremental;;

esac

exit 0

```

NOTAS:

Es altamente recomendable almacenar las copias incrementales en un disco separado que por supuesto debe ser de un tamaño sensiblemente superior al que esperemos que vayan a tener los datos de los que deseamos hacer las incrementales. Un 20% más del tamaño estimado puede ser suficiente.

Ya que los enlaces duros tan sólo son útiles dentro de la misma partición deberemos lanzar un primer snapshot del que partir para hacer el resto de incrementales. Éste será marcado en la lista de directorios con una "-S" al final de la ruta y se generará un enlace simbólico llamado "last-backup" que apuntará a él, y en un futuro, a la última incremental realizada.

Antes de hacer la transferencia de datos a la unidad de backup el script hace una verificación de espacio libre y en caso de no quedar suficiente borrará tantas incrementales como sea necesario empezando por las más antiguas.

Un fallo que no he conseguido solucionar es que para calcular el tamaño de los archivos a transferir hace una simulación con el cálculo bit a bit de cada uno de los archivos y a la hora de hacer la transferencia si se detecta que queda suficiente espacio en disco vuelve a hacer el mismo cálculo, así que se hace la misma tarea dos veces y cuando hay un nivel elevado de datos tarda una barbaridad.

Con archivos grandes que se modifican con frecuencia como los PST de Outlook tenemos un problema pues cada vez que se descarga un correo el HASH del archivo cambia y se hace una copia nueva.

La partición destino de las incrementales debe montarse en sólo lectura y el script se encarga de remontarla en modo escritura justo antes de hacer la transferencia de datos para luego dejarla de nuevo en modo sólo lectura.

El script se ejecuta pasando como parámetro la acción que se desea realizar (snapshot o incremental). Snapshot tan sólo la primera vez y luego programamos la función incremental en el CRON.

VARIABLES:

MAX_SIZE debe contener el tamaño máximo que queremos que ocupen las incrementales.

DATE contiene la fecha en formato AÑO-MES-DIA-HORA para que las copias se ordenen de forma temporal al listar los directorios por orden alfanumérico.

SOURCE_DIR es la ruta de origen de la que se van a hacer las copias de seguridad.

BACKUP_DIR es la ruta de destino donde se generarán las incrementales.

DEST_DIR es la ruta de destino de la incremental que se va a generar.

LASTBACKUP es un enlace simbólico a la ruta de la última incremental.

LOG_DIR es la ruta donde se guardarán los logs de script.

LOG_FILE es el nombre del archivo de logs dentro de la ruta LOG_DIR.

Espero que a alguien le resulte tan útil como a mi y que aporteis mejoras.

P.D. Todo bajo licencia GPL.

Un saludo.

----------

## galidor

Añado esta respuesta para subir el post al principio del foro ya que es una modificación del anterior y sino quedará perdido en el olvido.

Un saludo.

----------

## Theasker

gracias por compartilo, yo buscaba algo más sencillo, pero me puede dar ideas.

Gracias de nuevo.

----------

## Theasker

Aprovecho para hacer una pregunta sobre los enlaces duros, que nunca he entendido muy bien, se supone que al ser un enlace a una dirección física del disco duro ocupa mucho menos pero ... si esa copia de seguridad hay que restaurarla en otro disco físico diferente, ... servirían de algo los enlaces?

Imagino que estoy bastante equivocado, pero como he dicho, nunca me ha entrado en la cabeza bien, lo que son los enlaces duros y lo prácticos que pueden llegar a ser.

----------

## galidor

El tema de los enlaces duros no es realmente así. A ver si sé explicarlo.

Cuando creas un archivo lo que haces es ocupar un espacio en disco y lo referencias mediante un inodo. En ese mismo instante el nombre del archivo ya es un anlace duro a esos datos, el único enlace duro. Cuando generas un segundo enlace duro a esos datos en el disco no ocupas más espacio, lo que haces es referenciar a esos datos desde otra ruta distinta. Los datos del disco duro tan sólo quedan disponibles (aparentemente borrados) cuando borras todos los inodos o enlaces duros que apuntan a ellos. De esta manera, cuando haces una incremental, todos los datos que no han sido modificados desde la incremental anterior no necesitan ser escritos de nuevo en el disco duro. Tan sólo referenciados desde la ruta de la última incremental y sólo se escribiran nuevos los datos que difieran entre el origen de datos y la última incremental.

No sé si me he explicado debidamente. El resultado es que en cada directorio de las incrementales ves los datos tal cual estaban en el momento en que se realizó la copia, una imagen exacta de la ruta de origen. Con la peculiaridad de que un mismo archivo no está fisicamente en el disco tantas veces como incrementales hagas sino que sólo está una vez referenciado desde muchos sitios.

Dejo un enlace que me sirvió en su día para hacer el script y donde viene una explicación acerca de los enlaces duros (hard links).

http://www.mikerubel.org/computers/rsync_snapshots/

----------

## Theasker

A ver si lo he entendido bien, entonces pongamos que tu ordenador sufre un mal accidente y queda inservible por lo que sólo te queda la copia de seguridad q tienes en tu disco duro externo que guardas para un caso como este. Pero si no lo he entendido mal, la copia de seguridad está con los enlaces duros que no han cambiado apuntando estos inodos al espacio de disco donde están (estaban ya que se ha destruido el disco) los datos físicos y la incremental de los que han cambiado. Entonces, ¿cómo puedes restaurar la copia si el disco duro al que apuntan ya no existe?. 

Imagino que esto es imposible y el que estoy equivocado soy yo, xq sino no serviría de nada, creo que no estoy pillando el concepto claramente, me parece que soy un poco corto :/.

----------

## Coghan

@Theasker, los enlaces duros los realiza solamente en las copias incrementales y enlazan a los datos que previamente ya se han copiados desde una copia total, al utilizar los hard-link de esta manera consigue que cada incremental sea una copia completa sin el peso de la misma, pero siempre tendrá que ser necesario una copia completa como mínimo. A la hora de restaurar eliges la última incremental solamente y no tendrás que hacer el proceso de restaurar la completa y luego las sucesivas incrementales.

----------

## Theasker

vale, ahora ya lo he entendido, creo, osea que en el caso que yo proponía de un desastre en el ordenador, lo que habría q restaurar sería la incremental q apuntarían los enlaces duros a la última copia integra.

----------

## galidor

Como comento en la intro y bien aclara Coghan los hard-link no pueden hacerse en particiones distintas. Tienen que estar dentro de la misma partición. De ahí que haya implementado la función Snapshot que hace una primera copia de los datos a las bravas, creando datos nuevos en el disco o partición de destino sobre los que en el fututo ya se trabajará con enlaces duros.

La utilidad real de este script no es poder restaurar la última incremental en caso de desastre, que también. Sin embargo está más pensado para tener versiones de los archivos de forma que si borras uno, lo modificas y guardas indebidamente o pillas un mal virus... puedes retroceder hasta la fecha en que recuerdas que lo conservabas en buen estado para recuperarlo. De ahí el que MacOS lo haya llamado time-machine y yo por evitar conflictos RIBS  :Wink: 

----------

