Tag Archives: BASH

Backups con tar + rsync + rotación

En estos días tuve que configurar unos backups para un cliente. Algo bastante sencillo. Tenía dos servidores y necesitaba hacer backups de distintas cosas en cada equipo y copiarlos al otro. Por supuesto, la herramienta clave para esto es rsync. Googleando un poco vi que existe esta página donde se puede ver cómo generar snapshots incrementales automáticos usando hard links y rsync. Realmente es una solución muy interesante, pero a mí no me terminaba de convencer porque yo quería tener archivos comprimidos que fueran rotando.

Por lo tanto lo que hice fue, seguramente, reinventar la rueda una vez más. Hice una pequeña librería en BASH que luego reutilicé en los distintos scripts de backup que hice. La clave aquí es la función “create_archive” que toma un path, un nombre de archivo base y una lista de archivos, y se ocupa de ir al path, ver todos los archivos que haya con ese patrón de nombre básico, rotarlos y generar un nuevo tar.gz. Después agregué un par de funciones más que me venían bien, “log” para registrar todo lo que pasaba en un archivo, “mail_notification” para mandarme mails y “sync_backup_servers” para ejecutar rsync.

El código:

#!/bin/bash
CONF_FILE="/home/sysbackup/scripts/backup.conf"
if [ -f $CONF_FILE ]; then
    . $CONF_FILE
else
    echo "Configuration file $CONF_FILE not found"
    exit 1
fi

RM_BIN="/bin/rm"
TAR_BIN="/bin/tar"
MAIL_BIN="/bin/mail"
RSYNC_BIN="/usr/bin/rsync"

function log
{
    echo "`date`: $*" >> $LOG_FILE
}

function mail_notification
{
    subject="$1"
    message="$2"

    if [ -z "$subject" ]; then
        subject="Backup Notification"
    fi

    if [ -z "$NOTIFICATIONS_MAILS" ]; then
        log "mail_notification: empty recipients list"
        return 1
    fi

    if [ -z "$message" ]; then
        log "mail_notification: empty message"
        return 1
    fi

    sysdata="Date: `date`\nHost: `hostname`"

    echo -e "$message\n\n$sysdata" | $MAIL_BIN -s "$subject" "$NOTIFICATIONS_MAILS"
}

function sync_backup_servers
{
    log "Running rsync from $LOCAL_DIR to $REMOTE_DIR as $RSYNC_USER"
    su -c "$RSYNC_BIN -aq –delete -e ssh $LOCAL_DIR/ $REMOTE_DIR/ 2>> $LOG_FILE" $RSYNC_USER
    if [ $? -eq 0 ]; then
        log "Syncronization complete successfuly"
    else
        log "Syncronization finished with errors"
    fi
}

function create_archive
{
    file_path="$1"
    file_name="$2"
    files="${*:3}"

    if [ -z $file_path ]; then
        echo "Missing file path"
        return 1
    fi

    if [ -z $file_name ]; then
        echo "Missing file name"
        return 1
    fi

    if [ -z $files ]; then
        echo "Missing file list"
        return 1
    fi

    if [ -z $MAX_BKP_FILES ]; then
        MAX_BKP_FILES=4
    fi

    archive_name="${file_name}_`date +%Y%m%d`.tar.gz"
    log "Preparing to create $archive_name archive"

    # Begin to Rotate files
    # Sort files by date and keep the latest ones
    # MAX_BKP_FILES will determine how many to store
    dir=`ls -t $file_path/${file_name}_*.tar.gz 2> /dev/null`
    if [ $? -eq 0 ]; then
        N=0
        for file in $dir; do
            if [ $N -lt $MAX_BKP_FILES ]; then
                let N++
            else
                log "Removing old file $file"
                $RM_BIN -f $file
            fi
        done
    else
        log "No other archive found on $file_path"
    fi

    log "Creating tar archive $file_path/$archive_name"
    $TAR_BIN zcf "$file_path/$archive_name" $files 2>> $LOG_FILE
}
 

Algunas consideraciones importantes.

Configuración

Al principio del script lo que hago es incluir un archivo de configuración con algunas variables básicas que voy a utilizar. Este archivo debería ser algo similar a esto:

#!/bin/bash
LOCAL_DIR="/home/sysbackup/backup/server1.domain.com"
REMOTE_DIR="sysbackup@server1.domain.com:/home/sysbackup/backup/server1.domain.com"
LOG_FILE="/var/log/sysbackup.log"
MAX_BKP_FILES=4
NOTIFICATIONS_MAILS="admin@email.com"
RSYNC_USER="sysbackup"
 

LOCAL_DIR es el directorio local base donde van a estar mis backups. REMOTE_DIR es el directorio equivalente en el otro servidor. Lo que hice fue crear un usuario “sysbackup” (un poco menos obvio que “backup”, pero también self-explanatory) en los dos servidores y generar llaves para que se puedan conectar por SSH entre los dos servidores. Luego en la /home de cada usuario creé una carpeta “backup” y adentro de ella una carpeta para cada host que iba a ser backupeado “server1.domain.com” y “server2.domain.com”.

LOG_FILE indica el archivo donde se van a guardar los logs del backup y MAX_BKP_FILES determina la cantidad máxima de archivos de backup que va a haber por cada elemento.

Rotación

Como dije, la función más importante es create_archive. La pensé para logs que se crean una vez por día (o cada más tiempo), por lo que si la quieren usar para rotar logs generados en intervalos de tiempo más cortos van a tener que modificarla. Lo que hace es generar un nombre de archivo con una raiz elegida por nosotros (digamos “db”) y la fecha actual (la función pone la fecha, pero si necesitan intervalos más cortos van a tener que poner fecha y hora). Luego hace un `ls -t` del directorio donde hay que generar el backup para ver si hay otros archivos que coincidan con el patrón del nombre correspondiente (por ejemplo, ‘db_*.tar.gz’). Si hay archivos, va a conservar los últimos 4 (en realidad la cantidad la determina MAX_BKP_FILES) y va a borrar los más viejos. Por último, va a generar el tar.gz con la lista de archivos que le hayamos pasado.

Ejemplo:

create_archive "$LOCAL_DIR/web" "web" "/var/www/html"
 

Va a generar con un archivo de nombre como “web_20100902.tar.gz” en la carpeta $LOCAL_DIR/web con todos los contenidos de /var/www/html.

Rsync

La función sync_backup_servers hace la sincronización entre los dos servidores usando rsync por SSH. Una cosa a tener en cuenta es que en mi caso, yo necesitaba que el script de backup corriera como root (para poder acceder a todos los archivos que había que backupear) pero quería que el rsync se ejecutara con el usuario sysbackup (para mayor seguridad). Por lo tanto la función usa “su -c sysbackup”. Si a uds. este enfoque no les sirve pueden poner en la función directamente:

$RSYNC_BIN -aq –delete -e ssh $LOCAL_DIR/ $REMOTE_DIR/ 2>> $LOG_FILE
 

Notificaciones por mail

Es muy útil poder tener notificaciones por mail, sobre todo cuando las cosas andan mal. El comando “mail” de Linux/Unix es muy útil para esto. La funcion mail_notification usa ese comando para mandar un mail con el mensaje que queramos y le agrega dos datos útiles: la fecha y el hostname desde el que se manda. En realidad son datos que siempre viajan en los headers del mail, pero a mí me parecía útil que estuvieran en el body.

Espero que les sirvan estos recursos. No son una solución completa y cerrada, sino herramientas que quizás les sirvan para implementar o al menos pensar cómo hacer sus propios sistemas de backup. Les recomiendo enfáticamente que lean este artículo sobre cómo generar backups con rsync porque explica muchas cosas fundamentales.

Obtener listado de link rotos con wget

Esas cosas por las que uno ama Linux. Encontré este post en DiarioLinux que me pareció genial.

$  wget --spider  --no-parent -r -o log.txt http://tuweb.com


Parámetros:

–spider : recorrer la web que le digas, pero SIN descargar nada. Sólo recorrerla.
- r : recursivo, como si fuera el robotito de Google :-)
- o fichero : la salida de ejecutar el comando que salga por pantalla
–no-parent : si le pasamos como parámetro un nombre de directorio, no queremos que suba hacia los directorios padre.

La lista de enlaces rotos estará en log.txt (parte final)

Tomado de DiarioLinux.

Y agrego otro muy útil. Cómo descargar todos los contenidos de un sitio FTP con wget.

wget -r -q -b -P /home/myuser/destination ftp://mydomain.com --ftp-user=myuser --ftp-password=mypass

Parámetros:

-r: recursivo
-q: quiet/silencioso, no imprime mensajes en la consola
-b: background, para que se ejecute en background y nos devuelva el prompt.
-P: directorio donde queremos que se guarde todo lo que bajamos
–ftp-user=: usuario FTP
–ftp-password=: password FTP

Ver dominios de Apache que más transferencia consumen

Frecuentemente me encuentro ante la necesidad de saber qué dominios de los que tengo alojados en mis servidores consumen más tráfico HTTP. Como éste es el tipo de tráfico más importante y que más incide en el desempeño general de mis servidores, saber qué dominios transfieren más datos por HTTP me orienta en la búsqueda de heavy users. Para ello hice un script hace algún tiempo, y ahora lo estuve revisando, retocando y simplificando, y decidí compartirlo.

En el entorno de Directadmin, los logs de Apache se encuentran en /var/log/httpd. Allí hay una carpeta “domains” que guarda los logs de cada dominio. Por cada dominio hay tres logs: dominio.com.log, dominio.com.error.log y dominio.com.bytes. El primero es el access_log, el segundo el error_log y el tercero solamente guarda la cantidad de bytes de cada request. La opción más sencilla es trabajar con este último tipo de logs, pero el problema que tienen es que no indican la fecha. Si bien se supone que los logs de Directadmin rotan una vez por día, alguna vez me ha pasado que por razones que nunca pude descubrir, algunos logs no rotaban y generaban ciertas confusiones. Por lo tanto, el script que dejo usa los access_logs. Además esta opción es reutilizable en otras implementaciones de Apache que no tengan archivos de bytes.

El código:

# Access Log Parser
#
# Parses all the files in a directory
# treating them as access_log files
# and outputs the list of files sorted
# by transfered megabytes. Useful for
# identifying heavy users.
#
# Usage:
# ./access_log_parser.sh <base_dir>
# base_dir = directory where the access_log files are

if [ -z $1 ]; then
    echo "Usage: $0 "
    exit 1
fi

BASE_DIR=$1
T1=`date +%s`
for file in $BASE_DIR/*;
do
    size=`stat -c %s $file`
    if [ $size -gt 0 ]; then
        from=`head -1 $file | awk ‘{print($4)}’ |sed ‘s/^\[//’`
        to=`tail -1 $file | awk ‘{print($4)}’ |sed ‘s/^\[//’`
        bytes=`cat $file | awk ‘{a+=$10}END{print a}’`
        mbytes=`echo $bytes | awk ‘{print $1 / 1048576}’`
        echo  "$mbytes MB ($bytes bytes) | From: $from | To: $to | ${file:${#BASE_DIR}+1}"
    fi
done \
| sort -nb
T=`date +%s`

Un detalle importante: el script levanta todos los archivos de un directorio que se especifica por parámetro. Esto es así porque yo lo invoco desde otro script:

#!/bin/bash
LOG_DIR=/var/log/httpd/domains/
TMP=/tmp/domain_logs
if [ ! -d $TMP ]; then
    mkdir -p $TMP
fi

rm -f $TMP/*

cd $LOG_DIR
for i in `ls *.log |grep -v error`; do
    ln $i $TMP/$i
done
/root/access_log_parser.sh $TMP

Este último script lo que hace es crear un directorio temporal (si no existe). Vaciar su contenido. Y luego generar hardlinks a todos los access_logs de /var/log/httpd/domains. De esta forma, no me tengo que preocupar por si los logs son modificados mientras yo estoy ejecutando el script. Por último, ejecuta mi primer script (access_log_parser.sh) y le pasa como parámetro el directorio temporal donde están los access_logs.

Por último, la salida del script sería algo así:

...
186.995 MB (196078184 bytes) | From: 10/Mar/2010:00:27:47 | To: 10/Mar/2010:16:51:09 | dominio1.com.ar.log
187.096 MB (196184596 bytes) | From: 10/Mar/2010:03:24:55 | To: 10/Mar/2010:16:51:24 | dominio2.com.ar.log
245.692 MB (257626221 bytes) | From: 10/Mar/2010:02:53:50 | To: 10/Mar/2010:16:39:54 | dominio3.com.log
273.46 MB (286743390 bytes) | From: 10/Mar/2010:02:48:33 | To: 10/Mar/2010:14:59:29 | dominio4.com.ar.log
306.344 MB (321224473 bytes) | From: 10/Mar/2010:00:23:09 | To: 10/Mar/2010:16:51:20 | dominio5.com.ar.log
444.097 MB (465669066 bytes) | From: 10/Mar/2010:00:49:35 | To: 10/Mar/2010:16:51:20 | dominio6.com.ar.log
Generated in 6 seconds

Les dejo, de yapa, la opción del script utilizando los logs de bytes. Como dije, esta opción es un poco más sencilla y veloz, pero no nos dice las fechas de los logs.

#!/bin/bash

if [ -z $1 ]; then
    echo "Usage: $0 "
    exit 1
fi

BASE_DIR=$1
T1=`date +%s`
for file in $BASE_DIR/*.bytes;
do
    size=`stat -c %s $file`
    if [ $size -gt 0 ]; then
        bytes=`cat $file | awk ‘{a+=$1}END{print a}’`
        mbytes=`echo $bytes | awk ‘{print $1 / 1048576}’`
        echo  "$mbytes MB ($bytes bytes) ${file:${#BASE_DIR}+1}"
    fi
done \
| sort -nb
T=`date +%s`
echo "Generated in $(($T-$T1)) seconds"

Y la salida sería así:

# ./parser.sh /var/log/httpd/domains
...
181.716 MB (190543470 bytes) dominio1.com.bytes
187.115 MB (196203973 bytes) dominio2.com.ar.bytes
189.093 MB (198278283 bytes) dominio3.com.ar.bytes
245.692 MB (257626221 bytes) dominio4.com.bytes
273.472 MB (286756234 bytes) dominio5.com.ar.bytes
314.32 MB (329588717 bytes) dominio6.com.ar.bytes
444.516 MB (466108759 bytes) dominio7.com.ar.bytes
Generated in 2 seconds

Sacar una foto con la webcam y enviarla por mail desde BASH

WEBCAM480K_1Esta noche tenía un ratito y me puse a jugar con la Eee PC que tanto me costó configurar. Lo que hice fue un simple script en BASH que toma una foto con la webcam, la guarda en un archivo temporal y la envía por mail a un destinatario.

El script utiliza ffmpeg y mutt. Pueden instalarlos poniendo

sudo apt-get install ffmpeg mutt

Para que Mutt funcione se tiene que usar con un MTA, por default postfix. Se puede instalar con Exim o con SSMTP. Este último es una especie de wrapper que nos permite configurar un MTA externo para enviar los mails. Esta guía explica bien cómo hacerlo.

#!/bin/bash
IMG_SIZE="320x240"
CAM_DEV="/dev/video0"

IMG_NAME="`mktemp -u /tmp/img.XXXXXX`.jpg"

mutt=/usr/bin/mutt
ffmpeg=/usr/bin/ffmpeg
date=`date`
hostname=`hostname`

echo "Taking picture and saving it on $IMG_NAME"

$ffmpeg -f video4linux2 \
	-s $IMG_SIZE \
	-r 5 \
	-vframes 1 \
	-i $CAM_DEV \
	-f mjpeg \
	$IMG_NAME > /dev/null 2>&1

if [ ! -z $1 ]; then
	echo "Attempting to email the picture to $1"
	echo "Picture taken on $hostname at $date" | \
		$mutt -s "A picture was sent to you" -a $IMG_NAME -- $1
fi

Para ejecutarlo, copiamos el contenido en un archivo, por ejemplo “cam.sh”. Luego le damos permisos de escritura y lo ejecutamos.

# chmod +x cam.sh
# ./cam.sh "myaddress@domain.com"

Entre las utilidades de este script podríamos pensar en sacar una foto de la persona en frente de la máquina cuando se produce un login incorrecto o en algún otro evento que pudiera darnos la pauta de un uso diferente al usual. Ideal para paranóicos y nerds obsesivos.

Netstat con colorcitos

GNU/Linux

GNU/Linux

Luego del post de ayer, pensando en que vengo posteando poco, me puse a buscar a ver si tenía algún otro script útil que pudiera compartir por este medio… pero no encontré nada. Algo debo tener por ahí, pero nada a mano. Lo que encontré es este script (medio feo) que hice hace bastante tiempo, que muestra un netstat (concretamente netstat -natp) con distintos colores según el estado de cada conexión (established, listen, syn_sent, fyn_wait, etc.). Claramente no es un script muy útil (ni muy bien hecho), pero lo publico. Quizás a alguno le sirve de disparador para hacer algo verdaderamente útil.

#!/bin/bash
cyan="\E[1;36m\E[1m";
normal="\E[m";
blue="\E[34m\E[1m";
violet="\E[35m\E[1m";
red="\E[31m\E[1m";
yellow="\E[33m\E[1m";
green="\E[37m\E[32m\E[1m";
text="\E[1;37m\E[1m";

if [ "$UID" != "0" ]; then
	echo -e "$red$0: You will get more information if you have root privileges. Try sudo $0$normal"
fi

netstat -natp | \
while read line; do

	if [ `echo $line | awk '{print($1)}'` = "Proto" ]; then
		echo -e "$yellow=====================================================================================================$normal"
		echo -e "$text$line$normal"
		echo -e "$yellow=====================================================================================================$normal"
	else

		state=`echo $line | awk '{print($6)}'`
		color=$normal
		case $state in
			"ESTABLISHED")
				color=$green;;
			"SYN_SENT" | "SYN_RECV")
				color=$yellow;;
			"FIN_WAIT1" | "FIN_WAIT2" |"TIME_WAIT")
				color=$violet;;
			"CLOSE" | "CLOSE_WAIT" | "LAST_ACK" | "CLOSING" )
				color=$blue;;
			"LISTEN")
				color=$cyan;;
			"UNKNOWN")
				color=$red;;
			*)
		esac
		echo -e "$color$line$normal"

	fi
done;

Nota: el script utiliza el comando netstat con los parámetros “natp”. Estos parámetros de esta forma solamente funcionan en GNU/Linux. Para otros sistemas Unix (Solaris, FreeBSD, etc.) será necesario adaptar los parámetros y ver si la salida del netstat es igual.