bkp.sh

Especificação do trabalho:

TRABALHO DE SHELL SCRIPTS
GRUPO DE TRABALHO
Trabalho grupo de três alunos.

       DESCRIÇÃO
       =========

Implementar um sistema de gerenciamento de backup.
O sistema consiste em um arquivo de configuraço (/etc/bkp.conf) e um
arquivo de shell script (/usr/local/bin/bkp.sh).
O arquivo de shell script deverá ser dividido em funções que executam
atividades especificas, esta divisão ficará sob a responsabilidade da
equipe.
Não será aceito scripts que sejam escritos em um único módulo (lingüiça).
O script deverá ler o arquivo de configuração configuração e iniciar a
execução do backup.
O script deverá ser agendado via crontab (o horário de execução será
agendado no momento da defesa).
Somente arquivos que foram modificados a menos de 24 horas ser
selecionados pela operação de backup (além de obedecerem a extensão
definido pelo usuário).
O shell deverá executar as seguintes funções:
* ler arquivo de configuração
* validar cada usuario
* realizar o backup
* limpar arquivos de backup que estão além do período de retenção

Cada sessão de backup deverá gerar um arquivo no formato:

   usuario.DDMMAA.HHMM.tar.gz

       ARQUIVO DE CONFIGURAÇÃO
       =======================

 usuário:frequencia:retenço:extenses:caminho

* usuário - usuário cadastrado no arquivo /etc/passwd
* frequencia - frequencia com que o backup é realizado.Os valores estão
 padronizados em
 1 (domingo), 2 (segunda), 3(terça), 4(quarta), 5(quinta), 6(sexta),
 7(sábado), *(todos os dias).
 Se o usuário quiser backup todos os dias ento será informado (*).
 Um intervalo pode ser defindo através de um traço, 24, corresponde aos
 dias segunda,terça e quarta. * retenço ­ define quantos dias o backup será
 retido, ultrapassado o número de dias o backup deverá ser excluído.
 O valor padronizado é de 1-15.
* extensão - Indica o padrão dos arquivos que devem ser copiados.
* caminho ­ indica o caminho onde o backup deve ser armazenado.
 Exemplo: /var/bkp.

       EXEMPLOS
       ========

Exemplo 1 - O usuário airton que fazer backup com a seguinte frequencia
segunda, quarta e sexta. Todos arquivos que pertencem a ele deverão ser
copiados na operaço de backup. É necessitar que os backups fiquem
armazenados na pasta /var/backup/airton com 4 dias de retenção.

       airton:2,4,6:4:*:/var/backup/airton

Exemplo 2 -Idem acima, porém a frequencia do backup deve ser todos os dias.

       Airton:*:4:*:/var/backup/airton

Exemplo 3 - O usuário airton que fazer backup com a seguinte frequencia
segunda,terça,quarta,quinta. Somente os arquivos que possuam o padro *.c,
*.doc e *.txt deverão ser copiados na operação de backup. É necessitar que os
backups fiquem armazenados na pasta /var/backup/airton com 4 dias de
retenção.

       Airton:2-5:4:*.c,*.doc,*.txt:/var/backup/airton

Se o usuário quiser backup segunda, quarta e sexta então será informado
2,4,6. Se o usuário quiser backup segunda,terça,quarta e quinta então
podemos utilizar 2-5.

       PREMISSAS DO TRABALHO
       =====================

* O arquivo de configuração deve ser editado através de um editor de texto
* O arquivo de configuração deve estar permanentemente ordenado pela
 primeira coluna, isto é, o nome do usuário.
* O caracter separador de campo é (:)
* Utilizar as aplicações tar e find para realizar o backup
* Utilizar o para cron disparar o backup

Script implementado:

#!/bin/bash
# vim:ts=8
#=====================================================================
#       ARQUIVO:  bkp.sh
#           USO:  bkp.sh
#     DESCRIÇÃO:  Script para gerenciamento de backup
#        OPÇÕES:  ---
#    REQUISITOS:  /etc/bkp.conf
#                 bash >= 2.04
#                 tar
#                 gzip
#                 mktemp
#          BUGS:  ---
#         NOTAS:
#       AUTORIA:  Marcelo Beckmann,
#                 Rubens Hilcko, 
#                 Fabrício Carrico
#        VERSÃO:  1.0
#       REVISÃO:  6
#=====================================================================

#=====================================================================
# 			Variáveis globais
#=====================================================================
# Arquivo de configuração
BKPCONF="/etc/bkp.conf"

# Arquivo de log do backup
LOGFILE="/tmp/logbkp.log"

# Variável para controlar log de mensagens de debug
# Setar para 1 para gerar mensagens de debug
DEBUG=0

#=====================================================================
# 			Declaração de funções
#=====================================================================
# Função para logar mensagens no arquivo de log
# Mensagem a ser logada é passada como parâmetro para a função
loga() {
	# Variável local para gerar padrão de data-hora no log
	local DATALOGPATTERN=`date +"%Y%m%d-%H:%M:%S"`
	echo "${DATALOGPATTERN} ${@}" >>${LOGFILE}
}

# Função para manter ordenado o arquivo de configuração (BKPCONF)
# Além de ordenar, também limpa linhas em branco e com comentários
ordena_conf() {
	# Testa se o arquivo de configuração BKPCONF existe e se é
	# possível abri-lo. Se não conseguir, aborta e loga o erro
	if [ ! -w ${BKPCONF} ]
	then
		loga "  ==> ERRO: Arquivo de configuração não pode ser lido"
		return 1
	fi
	# Cria arquivo temporário para reordenação
	local TMPFILE=`mktemp /tmp/bkp.sh-XXXXXX`
	# Remove linhas em branco ou com comentários (egrep -v)
	# Ordena arquivo pelo primeiro campo (sort -k1)
	# Remove espaços em branco (sed)
	cat ${BKPCONF} | egrep -v "(^$|^#)" | sort -k 1 -t : | sed -e 's, ,,g' >${TMPFILE}
	# Reescreve arquivo de configuração
	cat ${TMPFILE} >${BKPCONF}
	# Apaga arquivo temporário
	rm ${TMPFILE}
	return 0
}

# Função para logar mensagens em modo debug
# Mensagem a ser logada é passada como parâmetro
ifdebug() {
	# Testa se variável DEBUG está definida
        if [ ! -z "${DEBUG}" ]
	then
		# Testa se debug está ativo (DEBUG=1)
		if [ ${DEBUG} -eq 1 ]
		then
			loga "  DEBUG: ${@}"
		fi
	fi
}

# Obtem código numérico do dia de hoje pelo função date
# Código deve ser:
# 1 (domingo), 2 (segunda), 3(terça), 4(quarta), 5(quinta), 6(sexta),
# 7 (sábado)
dia_sem_hoje() {
	# Utilizada opção %w do date:
	# %w     day of week (0..6); 0 is Sunday
	local DIASEM=`date +"%w"`
	# O date considera Domingo = 0
	# Precisamos somar 1 para ajustar isso a nossa codificação
	BKPDIAHOJE=$[${DIASEM}+1]
}

#=====================================================================
# 	Funções de validação do arquivo de configuração
#
# * Validam cada campo do arquivo de configuração
# * Obtém os valores para as variáveis necessárias para o backup
# * Variável line corresponde a linha atual lida do arquvio de config
# * Valores de retorno:
#	0 ==> OK
#	1 ==> ERRO
#=====================================================================

# Conta quantos separadores de campo (:) há na linha
# Usa o sed para trocar o ":"  por quebras de linha, e usa o wc -l para
# contar quantas linhas resultam. Assim, obtém-se o número de ":"
# Se não tiver exatamente 4 ":", rejeita a linha (linha inválida)
valida_ncampos() {
	if [ `echo -n "${line}" | sed -e 's,:,\n,g' | wc -l` -eq 4 ]
	then
		# total de campos ok
		return 0
	else
		loga "  ==> ERRO: Quantidade de campos (:) inválida"
		return 1
	fi
}

# Testa se o usuário existe no sistema
# Obtém as variáveis:
# BKPHOME: o diretório HOME do usuário
# BKPUSUARIO: nome do usuário
valida_usuario() {
	# Obtém nome do usuário da linha do arquivo de configuração bkp.conf
	BKPUSUARIO=`echo "${line}" | cut -d: -f1`
	# Testa se o usuário existe no /etc/passwd
	if cat /etc/passwd | cut -d: -f1 | grep -w -q ${BKPUSUARIO}
	then
		# Ok, usuário existe no sistema
		# Obtém diretório home do usuário
		BKPHOME="`cut -d: -f1,6 /etc/passwd | egrep -w "^${BKPUSUARIO}" | cut -d: -f2`"
		return 0
	else
		# Erro, usuário não existe no /etc/passwd
		loga "  ==> ERRO: Usuário ${BKPUSUARIO} não existe no sistema"
		return 1
	fi
}

# Valida se o campo de frequencia está bem formatado
# Obtém a variável string BKPFREQ com os dias de backup
# Exemplo: se tivermos a programação 2-5, então BKPFREQ="2345"
valida_frequencia() {
	# Obtém o campo de frequencia (campo 2 da linha de config)
	local TMPFREQ="`echo "${line}" | cut -d: -f2`"
	# Por segurança, apaga valores anteriores de BKPFREQ
	unset BKPFREQ

	# Utiliza expressões regulares para validar o campo
	# usa echo e egrep para verificar casamento com a expressão
	# O campo pode ser:
	# Apenas um *
	if echo "${TMPFREQ}" | egrep -q "^\*$"
	then
		# Seta string de frequencia para todos os dias
		BKPFREQ="1234567"
		ifdebug "valida_frequencia - casou asterisco"
		ifdebug "BKPFREQ=${BKPFREQ}"
		return 0
	fi
	# O campo pode ser:
	# Apenas um número, no intervalo de 1 a 7
	if echo "${TMPFREQ}" | egrep -q "^[1-7]$"
	then
		# Seta string de frequencia apenas para o dia
		BKPFREQ="${TMPFREQ}"
		ifdebug "valida_frequencia - casou um único número"
		ifdebug "BKPFREQ=${BKPFREQ}"
		return 0
	fi
	# O campo pode ser:
	# Um número [1-7], seguido de um - e um número [1-7]
	# ex.: 2-5
	if echo "${TMPFREQ}" | egrep -q "^[1-7]-[1-7]$"
	then
		# Campo é no formato DIAINICIAL-DIAFINAL
		# Variáveis temporárias para obter dia inicio e dia fim
		local TMPFREQ1=`echo "${TMPFREQ}" | cut -d- -f1`
		local TMPFREQ2=`echo "${TMPFREQ}" | cut -d- -f2`
		# condição inválida, dia inicial maior ou igual ao dia final
		if [ ${TMPFREQ1} -ge ${TMPFREQ2} ]
		then
			loga "  ==> ERRO: Dia inicial ${TMPFREQ1} maior ou igual a dia final ${TMPFREQ2}"
			return 1
		fi
		# Preenche string de frequencia com os dias do intervalo
		for local1 in `seq ${TMPFREQ1} ${TMPFREQ2}`
		do
			BKPFREQ="${BKPFREQ}${local1}"
		done
		ifdebug "valida_frequencia - casou inicio-fim"
		ifdebug "BKPFREQ=${BKPFREQ}"
		return 0
	fi
	# O campo pode ser:
	# Um número [1-7], seguido uma ou mais vezes de
	# uma , e um número [1-7]
	# ex.: 2,4,5
	if echo "${TMPFREQ}" | egrep -q "^[1-7](,[1-7])+$"
	then
		BKPFREQ="`echo "${TMPFREQ}" | sed -e 's/,//g'`"
		ifdebug "valida_frequencia - casou numero[,numero]+"
		ifdebug "BKPFREQ=${BKPFREQ}"
		return 0
	fi
	# Se chegou até aqui e não casou com nenhum dos casos possíveis
	# Então há alguma inconsistência na configuração desta opção
	# retorna erro
	loga "  ==> ERRO: campo frequencia mal especificado"
	return 1
}

# Valida se o campo retenção está entre 1 e 15
# Obtém a variável BKPRET: número de dias para retenção
valida_retencao() {
	# Obtém o campo de retenção (campo 3 da linha de config)
	local TMPRET="`echo "${line}" | cut -d: -f3`"
	# Utiliza expressão regular para validar se é
	# um número [1-9] seguido 0 ou 1 vez de
	# um número [0-5]
	if echo "${TMPRET}" | egrep -q "^[1-9][0-5]?$"
	then
		# Ok, casou com a expressão regular, portanto é um número
		# Teste para ver se está entre 1 e 15
		if [ ${TMPRET} -ge 1 -a ${TMPRET} -le 15 ]
		then
			# Valor de retenção válido!
			BKPRET=${TMPRET}
			ifdebug "BKPRET=${BKPRET}"
			return 0
		else
			loga "  ==> ERRO: Valor de retenção (${BKPRET}) fora da faixa permitida (1-15)"
			return 1
		fi
	else
		# Se não casou com a regex, então campo retenção
		# está mal especificado
		loga "  ==> ERRO: campo retenção mal especificado"
		return 1
	fi
}

# Função para processar a lista de extensões
# Obtem a string BKPEXT com a lista de extensões separadas por espaços
valida_extensoes() {
	# este campo pode ter apenas uma única extensão (ou *)
	# ou pode ter mais de uma extensão separadas por ,
	# podemos simplesmente aplicar um sed para substituir as
	# possíveis virgulas por espaços e jogar numa variável
	# a qual será usada depois em um loop com tar + find

	# Obtém campo 4 da linha de config, e troca "," por " "
	BKPEXT="`echo "${line}" | cut -d: -f4 | sed -e 's/,/ /g'`"
	ifdebug "BKPEXT=${BKPEXT}"
	return 0
}

# Valida o caminho no qual será salvo o backup
valida_caminho() {
	# Obtém o campo do caminho (campo 5 da linha de config)
	BKPPATH="`echo "${line}" | cut -d: -f5`"
	ifdebug "BKPPATH=${BKPPATH}"
	# Testa se o caminho já existe e se é um diretório
	if [ -d ${BKPPATH} ]
	then
		return 0
	else
		# Tenta criar o diretório
		mkdir -p ${BKPPATH}
		if [ $? -eq 0 ]
		then
			# OK, conseguiu criar
			return 0
		else
			# ERRO, não conseguiu criar
			loga "  ==> ERRO: Impossível criar caminho ${BKPPATH}"
			return 1
		fi
	fi
}

# Função para validar a linha lida do arquivo de configuração
# Usa várias outras funções para fazer testes específicos
# Formato padrão da linha do arquivo de configuração:
# usuário:frequencia:retenção:extensões:caminho
# Valores de retorno:
# 	0 ==> Ok, arquivo validado e campos necessários obtidos
# 	1 ==> Alguma verficação de validade falhou
valida_linha() {
	# Valida quantidade de separadores de campo (:) da linha
	valida_ncampos || return 1
	# Valida se o usuário existe no sistema
	valida_usuario || return 1
	# Valida campo de frequencia
	valida_frequencia || return 1
	# Valida campo de retenção
	valida_retencao || return 1
	# Valida campo de extensões
	valida_extensoes || return 1
	# Valida campo do caminho
	valida_caminho || return 1
	# Se chegou até aqui, então a linha de configuração é válida
	# gera log com os parâmetros encontrados
	ifdebug "Linha de configuração validada!"
	loga "  Parâmetros encontrados para o backup:"
	loga "   Usuário (BKPUSUARIO): ${BKPUSUARIO}"
	loga "    Dir Home do usuário: ${BKPHOME}"
	loga "   Frequencia (BKPFREQ): ${BKPFREQ}"
	loga "      Retenção (BKPRET): ${BKPRET}"
	loga "     Extensões (BKPEXT): ${BKPEXT}"
	loga "      Caminho (BKPPATH): ${BKPPATH}"
	return 0
}

# Testa se no dia de hoje deve ser feito backup
# Verifica se o código do dia atual consta na string com os dias agendados
# Valores de retorno:
#	0 ==> OK, deve fazer backup hoje
#	1 ==> não deve fazer backup hoje
valida_bkp_hoje() {
	if echo "${BKPFREQ}" | grep -q ${BKPDIAHOJE}
	then
		return 0
	else
		return 1
	fi
}

# Gera nome de arquivo no formato padronizado para o backup:
# usuario.DDMMAA.HHMM.tar.gz
gera_nomes() {
	# Gera um padrão de data e hora com o comando date
	local DATATARPATTERN="`date +"%d%m%y.%H%M"`"
	# Nomeia arquivo para o tar.gz
	BKPTARGZ="${BKPPATH}/${BKPUSUARIO}.${DATATARPATTERN}.tar.gz"
	# Nomeia arquivo que conterá a lista de arquivos para backup
	BKPLIST="${BKPPATH}/${BKPUSUARIO}.${DATATARPATTERN}.lst"
	# Se já existir os arquivos, apaga
	[ -f ${BKPTARGZ} ] && rm ${BKPTARGZ}
	[ -f ${BKPLIST} ] && rm ${BKPLIST}
}

#=====================================================================
# 		Início do processamento
#=====================================================================
loga "=========================="
loga "Iniciando sessão de backup"

# Obtém codigo numérico do dia da semana
dia_sem_hoje
loga "Dia da semana atual: ${BKPDIAHOJE}"

# Cria diretório temporário de trabalho
# Para prevenir expansão de *.ext caso haja algum *.ext no diretório atual
WORKDIR=`mktemp -d /tmp/bkp.sh-dir-XXXXXX`
cd ${WORKDIR}

# Ordena arquivo de configuração
loga "Ordenando arquivo de configuração"
if ! ordena_conf
then
	# Arquivo de configuração não pode ser lido ou gravado, aborta
	loga "Abortando backup"
	loga "--------------------------------"
	exit 1
fi

# Loop para processar linha a linha do arquivo de configuração
loga "Iniciando processamento do arquivo de configuração"

# Inicializa contador de linhas processadas
NLINHA="0"
for line in `cat ${BKPCONF}`
do
	# Leu uma linha, então incrementa contador de linhas
	NLINHA=$[${NLINHA}+1]
	loga "Iniciando processamento linha ${NLINHA}:"
	loga "${line}"
	# Valida linha lida do arquivo de configuração
	# Se a linha for inválida por algum motivo, pula o processamento
	# para a próxima linha
	if ! valida_linha
	then
		loga "--------------------------------"
		continue
	fi

	# Ok, arquivo validado
	#
	# Processa retenções
	# Apaga arquivos mais antigos que BKPRET dias
	loga "  ==> Removendo retenções mais antigas que ${BKPRET} dias"
	find ${BKPPATH} -type f -mtime +${BKPRET} -name ${BKPUSUARIO}.* -exec rm -v {} \; >> ${LOGFILE} 2>/dev/null
	if [ $? -ne 0 ]
	then
		loga "  ==> ERRO"
	fi

	# Testa se deve executar backup hoje
	if ! valida_bkp_hoje
	then
		loga "  ==> Dia de hoje (${BKPDIAHOJE}) não coincide com agendamento (${BKPFREQ})"
		loga "--------------------------------"
		continue
	fi

	# Ok, fará backup hoje
	# Gera nomes de arquivos para efetuar o backup
	gera_nomes
	loga "  ====> gerando ${BKPLIST}"

	# Processa extensões para montar arquivo de listagem
	for exten in ${BKPEXT}
	do
		loga "  ======> ${exten}"
		# Somente arquivos que foram modificados a menos de 24 horas
		# ser selecionados pela operação de backup (além de obedecerem
		# a extensão definido pelo usuário).
		# Para pegar apenas os modificados a menos de 24h, usamos
		# a opção "-mtime -1" do find
		find ${BKPHOME} -type f -mtime -1 -name ${exten} >> ${BKPLIST}
		# Gera log com quantidade de arquivos encontrados
		loga "  ======> [`wc -l ${BKPLIST} | cut -d" " -f1`] arquivos selecionados"
	done

	# Verifica se foi selecionado algum arquivo para backup
	# checando o número de linhas de BKPLIST
	if [ `wc -l ${BKPLIST} | cut -d" " -f1` -eq 0 ]
	then
		rm ${BKPLIST}
		loga "  ====> Nenhum arquivo selecionado para backup"
		loga "--------------------------------"
		continue
	fi

	# Se passou pela verificação anterior, então existem arquivos
	# selecionados para backup
	# Executa o backup em tar.gz
	loga "  ====> gerando ${BKPTARGZ}"
	# Faz o tar lendo a lista de arquivos gerada previamente
	tar -T ${BKPLIST} -czf ${BKPTARGZ} >/dev/null 2>&1
	if [ $? -eq 0 ]
	then
		# Sucesso na execução do tar
		# Apaga lista
		rm ${BKPLIST}
		loga "  ==> Backup executado com sucesso!"
	else
		# Houve algum problema
		loga "  ==> ERRO na geração de ${BKPTARGZ}"
	fi
	# Gera linha no log para sinalizar fim do processamento da linha
	loga "--------------------------------"
done

# Apaga diretório temporário
cd /tmp
rm -Rf ${WORKDIR}

loga "Terminando processamento do arquivo de configuração"
loga "Terminando sessão de backup"
loga "==========================="
Anúncios

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s

%d blogueiros gostam disto: