Backup system

Imported both the server- and client-side backup scripts.
This commit is contained in:
Emmanuel BENOîT 2012-07-28 16:27:29 +02:00
commit d9f75447a6
24 changed files with 1043 additions and 0 deletions

5
README Normal file
View file

@ -0,0 +1,5 @@
This repo contains various scripts which I figure could be useful to someone.
backup/ A set of backup scripts
License: WTFPL (http://sam.zoy.org/wtfpl/)

8
backup/README Normal file
View file

@ -0,0 +1,8 @@
This set of scripts can be used to implement a rather primitive backup system.
server/
The scripts and example configuration files for the backup
server.
ssh-client/
Scripts that can be used on remote systems.

107
backup/server/README Normal file
View file

@ -0,0 +1,107 @@
Backup server scripts
======================
The scripts in this directory implement a backup "server". While quite
primitive, it supports a rather flexible configuration and can be customised in
various ways (e.g. support for new types of data fetching).
Installation
-------------
1/ Copy the backup script to /usr/local/sbin
2/ Copy the share/ directory to /usr/local/share/backup (omit the
"postprocess" script if you don't need it - see below for more info)
3/ Copy backup.conf and backup.conf.d/ to /etc
4/ Configure the server
5/ Add cron jobs to execute backups (see crontab.example)
Configuration
--------------
The main configuration file, backup.conf, defines a few variables. You need to
modify some of these values for the system to function properly (e.g. title
for backup reports, main archive storage location, and possibly the log
directory). It also allows you to modify the location of the data fetching and
postprocessing scripts, as well as the location of the rest of the
configuration, should you need / want to do that.
The main configuration files (backup.conf.d/*.conf) include some documentation.
They are mostly self-explanatory, with the exception of the "fetch modes" thing.
Fetch modes
------------
A "fetch mode" associates a data acquisition script (share/fetch-*) and a
specific configuration for this script. Fetch modes must be listed in the
backup.conf.d/fetch-modes.conf file. Hosts are then associated to a fetch mode.
When the backup archives from a host need to be generated, the mode's
configuration will be read from backup.conf.d/fetch/<name of the mode>.conf if
it exists (otherwise defaults will be assumed). After that, the backup script
also attempts to load backup.conf.d/<name of the mode>/<name of the host>.conf
if it exists.
Two fetch scripts are provided:
fetch-local Fetch data from a mounted filesystem
BASE Base directory for all "hosts"
Default: /
ROOT Root directory of a host relative to the base
Default: /
fetch-ssh Fetch data through SSH (see ../ssh-client/)
SSH_HOST Host to connect to
SSH_KEY Private key to use
SSH_PORT Port to connect to
Default: 22
SSH_USER User to log in as
Default: user running the server script
Default postprocessing script
------------------------------
The default postprocessing script will encrypt all archives using a fixed key,
and send them to some remote server using FTP.
To enable the script, simply make sure the "share/postprocess" script is
present and executable.
The script's configuration includes the server's name or address, the
credentials needed to log in, and the amount of remote rotations; in addition,
a second file contains the encryption key.
Customisation - Fetch scripts
------------------------------
Fetch scripts must be written in Bash and must define a function named "FETCH".
The function will output the archive's data on its standard output; any error
should be written to the standard error stream.
When the function is called, all variables loaded from the fetch mode or host
configuration files will be present, as well as the following variables:
backup_directory The directory to backup, from types.conf
backup_exclude An array of directories to exclude from the
resulting archive, as defined in
exclude.conf
Customisation - Postprocessing
-------------------------------
The postprocessing script is launched by the main script when it starts. It
will be passed the name of a temporary directory in which its data resides.
The first thing a postprocessing script ought to do is create a file named "pid"
in that directory, writing its ... PID ... into it (thank you, Captain Obvious!)
The main script will then write a host name and backup type identifier whenever
it finishes fetching an archive.
Anything written to the standard output or standard error stream will end up in
the main report.

441
backup/server/backup Executable file
View file

@ -0,0 +1,441 @@
#!/bin/bash
MODE="$1"
[ "x$MODE" = "x" ] && exit 1
source /etc/backup.conf
if ! [ -d "$BACKUP_TARGET" ]; then
echo "missing target directory $BACKUP_TARGET"
exit 1
fi
function checkMode
{
local tocheck="$1"
local mode=
for mode in `grep -v '^#' "${BACKUP_CONFS}/modes.conf" | awk '{ print $1 }'`; do
[ "x$mode" = "x$tocheck" ] && return
done
exit 1
}
function checkType
{
local tocheck="$1"
local btype=
for btype in `grep -v '^#' "${BACKUP_CONFS}/types.conf" | awk '{ print $1 }'`; do
[ "x$btype" = "x$tocheck" ] && return 0
done
return 1
}
function getHosts
{
grep -v '^#' "${BACKUP_CONFS}/hosts.conf" | awk '{ print $1 }'
}
function printHostHeader
{
local host="$1"
local id=
local name=
grep '^'"$host" "${BACKUP_CONFS}/hosts.conf" | while read id name; do
if [ "x$id" = "x$host" ]; then
echo "$name (identifier: $id)";
break;
fi
done
}
function getBackupTypes
{
local mode="$1"
local host="$2"
local cmode=
local chost=
local ctypes=
local ctype=
grep -v '^#' "${BACKUP_CONFS}/modes.conf" | while read cmode chost ctypes; do
if [ "x$cmode" != "x$mode" ] && [ "x$cmode" != "x*" ]; then
continue;
fi
if [ "x$chost" != "x$host" ] && [ "x$chost" != "x*" ]; then
continue;
fi
echo "$ctypes" | sed -e 's/,/ /g'
done
}
function getTypeName
{
local btype="$1"
local ctype=
local crot=
local cdir=
local cdesc=
grep '^'"$btype" "${BACKUP_CONFS}/types.conf" | while read ctype crot cdir cdesc; do
if [ "x$ctype" != "x$btype" ]; then
continue;
fi
echo "$cdesc"
break
done
}
function getTypeRotation
{
local btype="$1"
local ctype=
local crot=
local cdir=
local cdesc=
grep '^'"$btype" "${BACKUP_CONFS}/types.conf" | while read ctype crot cdir cdesc; do
if [ "x$ctype" != "x$btype" ]; then
continue;
fi
echo "$crot"
break
done
}
function getTypeDirectory
{
local btype="$1"
local ctype=
local cdir=
local crot=
local cdesc=
grep '^'"$btype" "${BACKUP_CONFS}/types.conf" | while read ctype crot cdir cdesc; do
if [ "x$ctype" != "x$btype" ]; then
continue;
fi
echo "$cdir"
break
done
}
function getTypeExcludes
{
local btype="$1"
local host="$2"
local etype=
local ehost=
local edir=
grep -v '^#' "${BACKUP_CONFS}/exclude.conf" | while read etype ehost edir; do
if [ "x$etype" != "x$btype" ]; then
continue;
fi
if [ "x$ehost" != "x$host" ] && [ "x$ehost" != "x*" ]; then
continue;
fi
echo "$edir"
done
}
function getFetchMode
{
local host="$1"
local fhost=
local fmode=
local fparams=
grep '^'"$host" "${BACKUP_CONFS}/fetch-hosts.conf" | while read fhost fmode fparams; do
if [ "x$fhost" = "x$host" ]; then
echo $fmode;
break;
fi
done
}
function getFetchScript
{
local mode="$1"
local fmode=
local fscript=
local fparams=
grep '^'"$mode" "${BACKUP_CONFS}/fetch-modes.conf" | while read fmode fscript fparams; do
if [ "x$mode" = "x$fmode" ]; then
echo "$fscript";
break;
fi
done
}
function fetchData
{
local fetchmode="$1"
local fetchscript="$2"
local host="$3"
local btype="$4"
local fetchconf="$5"
if ! [ -d "$BACKUP_TARGET/$host" ]; then
mkdir "$BACKUP_TARGET/$host"
fi
local logfile="`mktemp`"
chmod 600 "$logfile"
local tempfile="`mktemp`"
chmod 600 "$tempfile"
(
if [ -f "${BACKUP_CONFS}/fetch/$fetchmode.conf" ]; then
source "${BACKUP_CONFS}/fetch/$fetchmode.conf"
fi
if [ -f "${BACKUP_CONFS}/fetch/$fetchmode/$host.conf" ]; then
source "${BACKUP_CONFS}/fetch/$fetchmode/$host.conf"
fi
source "$fetchconf"
source "${BACKUP_SCRIPTS}/fetch-$fetchscript"
FETCH || exit 1
) 2>$logfile | gzip -5 > "$tempfile"
cat "$logfile"
if grep -q 'ERROR' $logfile; then
echo -e "\t\t\tBackup files will not be rotated"
echo "$host" >>"$ERROR_FILE"
rm -f "$tempfile"
else
if [ $btrot -gt 1 ]; then
echo -e "\t\t\tRotating files ..."
for index in $( seq $btrot -1 2 ); do
local previous=$(( $index - 1 ))
if ! [ -f "$BACKUP_TARGET/$host/$btype-$previous.tar.gz" ]; then
continue;
fi
/bin/mv -f "$BACKUP_TARGET/$host/$btype-$previous.tar.gz" \
"$BACKUP_TARGET/$host/$btype-$index.tar.gz"
done
fi
/bin/mv -f "$tempfile" "$BACKUP_TARGET/$host/$btype-1.tar.gz"
echo -e "\t\t\tBackup completed"
echo "$host $btype" >&3
fi
rm -f "$logfile"
}
function executeBackupType
{
local btype="$1"
local host="$2"
if ! checkType "$btype"; then
echo -e "\tCONFIGURATION ERROR: unknown type '$btype'";
echo "$host" >>"$ERROR_FILE"
return 1
fi
local btname="`getTypeName "$btype"`"
local btrot="`getTypeRotation "$btype"`"
echo -e "\t$btname ($btype)"
local btdir="`getTypeDirectory "$btype"`"
local btexclude=( `getTypeExcludes "$btype" "$host"` )
local fetchmode="`getFetchMode "$host"`"
local fetchscript="`getFetchScript "$fetchmode"`"
local index=
echo -e "\t\tDirectory:\t\t$btdir"
for index in $( seq 0 $(( ${#btexclude[@]} - 1 )) ); do
echo -e "\t\tExcluded directory:\t${btexclude[$index]}"
done
echo -e "\t\tFetching:\t\tmode $fetchmode, script $fetchscript"
if [ -f "${BACKUP_SCRIPTS}/fetch-$fetchscript" ]; then
echo -e "\t\tStarting backup..."
local fetchconf="`mktemp`"
chmod 600 "$fetchconf"
{
echo "backup_directory=\"$btdir\""
echo 'backup_exclude=()'
for index in $( seq 0 $(( ${#btexclude[@]} - 1 )) ); do
echo 'backup_exclude['$index']="'"${btexclude[$index]}"'"'
done
} > "$fetchconf"
fetchData "$fetchmode" "$fetchscript" "$host" "$btype" "$fetchconf"
rm -f "$fetchconf"
else
echo -e "\t\tCONFIGURATION ERROR: unknown fetch script '$fetchscript'"
echo
echo "$host" >>"$ERROR_FILE"
return 1
fi
echo
}
function backupHost
{
local mode="$1"
local host="$2"
local types=( `getBackupTypes "$mode" "$host"` )
if [ ${#types[*]} -eq 0 ]; then
return
fi
echo "======================================================"
echo
printHostHeader "$host"
echo
local index=
for index in $( seq 0 $(( ${#types[@]} - 1 )) ); do
local btype="${types[$index]}"
executeBackupType "$btype" "$host"
done
echo
echo
}
function backupHosts
{
local mode="$1"
local hosts=( `getHosts` )
local index=
for index in $( seq 0 $((${#hosts[@]} - 1)));
do
local host="${hosts[$index]}"
backupHost "$mode" "$host"
done
}
function computeTime
{
local total=$(( $2 - $1 ))
date -u -d "@$total" +'%d %H %M %S' | sed -e 's/ 0/ /g' | (
read day hour minutes seconds;
echo $(( $day - 1 ))' day(s), '$hour' hour(s), '$minutes' minute(s) and '$seconds' second(s)'
)
}
function getNextLogFile
{
local base="$BACKUP_LOG/`date +'%Y-%m-%d'`"
local counter=1
while [ -f "$base-$counter.log" ]; do
counter=$(( $counter + 1 ))
done
echo "$base-$counter.log"
}
checkMode "$MODE"
if [ -f "${BACKUP_SCRIPTS}/postprocess" ]; then
PP_DIR=`mktemp -d`
chmod 700 $PP_DIR
mkfifo -m 600 "$PP_DIR/pp_fifo"
bash "${BACKUP_SCRIPTS}/postprocess" "$PP_DIR" <"$PP_DIR/pp_fifo" >"$PP_DIR/log" 2>&1 &
exec 3>"$PP_DIR/pp_fifo"
else
exec 3>/dev/null
fi
ERROR_FILE="`mktemp`"
chmod 600 "$ERROR_FILE"
LOG_FILE="`mktemp`"
chmod 600 "$LOG_FILE"
START_FULL="`date +'%Y-%m-%d %H:%M:%S'`"
START_TS="`date +'%s'`"
backupHosts "$MODE" >$LOG_FILE 2>&1
END_FULL="`date +'%Y-%m-%d %H:%M:%S'`"
END_TS="`date +'%s'`"
if [ -f "${BACKUP_SCRIPTS}/postprocess" ] && [ -f "$PP_DIR/pid" ]; then
PP_PID=`cat $PP_DIR/pid`
exec 3>&-
while [ -e "/proc/$PP_PID" ]; do
sleep 1
done
PP_FULL="`date +'%Y-%m-%d %H:%M:%S'`"
PP_TS="`date +'%s'`"
fi
FINAL_LOG=`getNextLogFile`
exec 7>&1 >$FINAL_LOG
if [ -z "`cat $ERROR_FILE`" ]; then
echo "Successful backup."
TAG="BACKUP"
else
echo "BACKUP FAILURE!"
echo
echo "The following hosts encountered errors:"
for host in `cat "$ERROR_FILE" | sort | uniq`; do
echo -e "\t* $host"
done
TAG="BACKUP-FAILURE"
fi
if [ -f "${BACKUP_SCRIPTS}/postprocess" ] && [ -f "$PP_DIR/log" ] && grep -q ERROR "$PP_DIR/log"; then
echo "There were errors during post-processing."
if [ "$TAG" = "BACKUP" ]; then
TAG="BACKUP-WARNING"
fi
fi
spent="`computeTime "$START_TS" "$END_TS"`"
totsize="`du -sh ${BACKUP_TARGET} | awk '{ print $1 }'`"
latestsize="`du -sch ${BACKUP_TARGET}/*/*-1.tar.gz | tail -n 1 | awk '{ print $1 }'`"
freesize="`df -Ph ${BACKUP_TARGET} | tail -n 1 | awk '{ print $4 }'`"
echo "Full log below."
echo
echo "======================================================"
echo "STATISTICS"
echo -e "\tStarted:\t\t$START_FULL"
echo -e "\tEnded:\t\t\t$END_FULL"
if [ -f "${BACKUP_SCRIPTS}/postprocess" ] && [ -f "$PP_DIR/log" ]; then
echo -e "\tBackup time:\t\t$spent"
echo -e "\tPost-processing ended:\t$PP_FULL"
spent="`computeTime "$START_TS" "$PP_TS"`"
fi
echo -e "\tTotal time:\t\t$spent"
echo -e "\tTotal size:\t\t$totsize"
echo -e "\tLatest:\t\t\t$latestsize"
echo -e "\tFree space:\t\t$freesize"
echo
cat "$LOG_FILE"
if [ -f "${BACKUP_SCRIPTS}/postprocess" ] && [ -f "$PP_DIR/log" ]; then
echo
cat "$PP_DIR/log"
rm -rf "$PP_DIR"
fi
exec 1>&7 7>&-
cat "$FINAL_LOG" | mail -s "[$TAG] - $BACKUP_NAME - `date +'%Y-%m-%d'` - $MODE mode" $BACKUP_EMAIL
rm -f "$ERROR_FILE" "$LOG_FILE"

19
backup/server/backup.conf Normal file
View file

@ -0,0 +1,19 @@
# Main backup server configuration
# "Title" for the backup, used in e-mail reports
BACKUP_NAME="Change me"
# Directory that contains the various configuration files
BACKUP_CONFS=/etc/backup.conf.d
# Directory that contains the various scripts (fetching, postprocessing)
BACKUP_SCRIPTS=/usr/local/share/backup
# Target directory for the archives
BACKUP_TARGET=/change/this
# Logs directory
BACKUP_LOG=/var/log/backup
# E-mail address to send reports to
BACKUP_MAIL=root

View file

@ -0,0 +1,7 @@
# Locations to exclude from backups, based on backup types and optionally host
# names. If a location is to be excluded for all hosts, "*" may be used.
#
# Backup type Host name Directory to exclude
full * /var/tmp
full * /var/cache
full * /tmp

View file

@ -0,0 +1,7 @@
# For each host to backup, define the fetch mode to use. Fetch modes are
# defined in fetch-modes.conf
#
# Host Mode
backup-server self
ve-host ve
remote-system remote

View file

@ -0,0 +1,7 @@
# List each "fetch mode", associating an (arbitrary) name with the name of the
# script to use.
#
# Mode Fetch script
self local
ve local
remote ssh

View file

@ -0,0 +1,12 @@
# Configuration for remote fetches
# SSH port to connect to
SSH_PORT="22"
# Backup user on the remote system
SSH_USER="rbackup"
# SSH private key
SSH_KEY="/path/to/private.key"
# Host to connect to. Do not change this unless you know what you're doing.
SSH_HOST="$host"

View file

@ -0,0 +1,7 @@
# This is an example for "local" fetching on a set of OpenVZ VE's whose roots
# are mounted under /mnt/virtual-envs
BASE=/mnt/virtual-envs
# Then, for each host, there's a file in the ve/ subdirectory which defines the
# root.

View file

@ -0,0 +1,2 @@
# Define the root for a VE host
ROOT=1000

View file

@ -0,0 +1,6 @@
# List all hosts and the title they will be given in the report.
#
# Host Title
backup-server Backup server
ve-host Some OpenVZ virtual environment
remote-system A remote system

View file

@ -0,0 +1,11 @@
# Define backup modes. A backup mode corresponds to a set of locations to
# backup; these locations (defined in types.conf and exclude.conf) can be
# listed for all hosts (using "*" as the host name) or for some specific hosts.
#
# When the main backup script is called, its first (and only) parameter should
# be the name of the mode.
#
# Mode Host name Backup types
daily * config,logs,varlib
daily remote-system home
weekly * full

View file

@ -0,0 +1 @@
key to use when encrypting files during post-processing goes here

View file

@ -0,0 +1,13 @@
# Configuration file for the example post-processing script
# The FTP server
ftp_host="remote-ftp.example.org"
# User name on the FTP server
ftp_user="backup"
# Password on the FTP server
ftp_pass="password"
# Amount of rotations on the server
ftp_rotate=4

View file

@ -0,0 +1,12 @@
# Backup "types" correspond to locations to backup, along with a description
# used in the report, and the amount of copies to keep.
#
# The exclude.conf file is used in conjunction with this file to determine
# locations to exclude from each specific backup type.
#
# Backup type Rotation Directory Description
config 7 /etc System configuration
logs 7 /var/log Logs
varlib 7 /var/lib Various runtime information
home 7 /home Home directory
full 2 / Full backup

View file

@ -0,0 +1,6 @@
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
# m h dom mon dow user command
12 1 * * 0 root /usr/local/sbin/backup weekly
12 1 * * 1-6 root /usr/local/sbin/backup daily

View file

@ -0,0 +1,44 @@
#!/bin/bash
function FETCH
{
local fetchroot="/"
if [ "x$BASE" != "x" ]; then
fetchroot="$fetchroot/$BASE"
fi
if [ "x$ROOT" != "x" ]; then
fetchroot="$fetchroot/$ROOT"
fi
fetchroot="`echo "$fetchroot" | sed -e 's/\/\+/\//g'`"
if ! [ -d "$fetchroot" ]; then
echo -e "\t\t\tCONFIGURATION ERROR: missing root directory '$fetchroot'" >&2
exit 1
fi
echo -e "\t\t\tRoot directory:\t$fetchroot" >&2
local tarerrors="`mktemp`"
chmod 600 "$tarerrors"
local command='tar --numeric-owner --one-file-system --ignore-failed-read --warning=none -c'
local index=
for index in $( seq 0 $(( ${#backup_exclude[@]} - 1 )) ); do
command="$command"' "--exclude='"`echo "./${backup_exclude[$index]}" | sed -e 's/\/\+/\//g' -e 's/\/$//'`"'"'
done
command="$command"' ".'"$backup_directory"'"'
cd "$fetchroot"
eval $command 2>"$tarerrors"
if ! [ -z "`cat $tarerrors`" ]; then
echo -e "\t\t\tFETCH ERROR: something went wrong while creating the archive:" >&2
echo -e "-----------------------------------------------------" >&2
cat "$tarerrors" >&2
echo -e "-----------------------------------------------------" >&2
rm -f "$tarerrors"
return 1
fi
rm -f "$tarerrors"
return 0
}

View file

@ -0,0 +1,55 @@
#!/bin/bash
function FETCH
{
if [ -z "$SSH_KEY" ]; then
echo -e "\t\t\tCONFIGURATION ERROR: no SSH key" >&2
exit 1
elif [ -z "$SSH_HOST" ]; then
echo -e "\t\t\tCONFIGURATION ERROR: no destination SSH host" >&2
exit 1
fi
local command="ssh -T"
if [ "x$SSH_USER" != "x" ]; then
command="$command -l $SSH_USER"
fi
if [ "x$SSH_PORT" != "x" ]; then
command="$command -p $SSH_PORT"
fi
command="$command -i $SSH_KEY $SSH_HOST echo"
local errorfile="`mktemp`"
chmod 600 "$errorfile"
{
echo "$backup_directory"
local index=
for index in $( seq 0 $(( ${#backup_exclude[@]} - 1 )) ); do
echo "${backup_exclude[$index]}"
done
} | eval $command 2>"$errorfile"
local nerrfile=`mktemp`
echo 0 > $nerrfile
cat $errorfile | while read line; do
if [[ "$line" =~ ^\.\.\.SRC\.\.\..*$ ]]; then
local text="`echo "$line" | sed -e 's/^.........//'`"
if [[ "$text" =~ ERROR ]]; then
echo 1 > $nerrfile;
fi
printf "\t\t\t%s\n" "$text" >&2
else
echo -e "\t\t\tCONNECTION ERROR: SSH or the remote script caused errors:" >&2
printf "\t\t\t\t%s\n" "$line" >&2
echo 1 > $nerrfile
fi
done
local rv="`cat $nerrfile`"
rm -f "$nerrfile" "$errorfile"
return $rv
}

179
backup/server/share/postprocess Executable file
View file

@ -0,0 +1,179 @@
#!/bin/bash
#
# An example post-processing script
#
# This script runs in parallel to the main backup script (once the actual
# data fetching is completed, the main script will wait for the post-processing
# script to complete).
#
# It uses openssl to encrypt backup archives, then sends them to a remote FTP
# server using kermit; backups of type "full" (i.e. root filesystems) will not
# be processed.
#
[ -z "$1" ] && exit 1
echo $$ >"$1/pid"
source /etc/backup.conf
source "${BACKUP_CONFS}/post/ftp-access.conf"
if ! [ -f "${BACKUP_CONFS}/post/crypto.key" ]; then
echo "ERROR: no cryptographic key"
exit 1
fi
function crypt
{
openssl enc -kfile "${BACKUP_CONFS}/post/crypto.key" -aes-256-cbc -e
}
function makeFTPScript
{
local script=`mktemp`
chmod 600 $script
{
echo "set ftp passive-mode off"
echo "ftp open $ftp_host /user:$ftp_user /password:$ftp_pass"
echo "if fail exit 1 Connection failed"
echo 'if not \v(ftp_loggedin) exit 1 Login failed'
for cmd in "$@"; do
if [[ "$cmd" =~ ^lcd\ ]]; then
echo "$cmd"
else
echo "ftp $cmd"
echo "if fail exit 1 ftp $cmd: \\v(ftp_message)"
fi
done
echo "ftp bye"
echo "exit 0"
} > $script
echo $script
}
function executeKermitScript
{
local script="$1"
local dest="$2"
wermit + < $script > $dest 2>/dev/null
local result=$?
rm -f "$script"
return $result
}
function fileExists
{
local file="$1"
local script=`makeFTPScript "check $file"`
local output=`mktemp`
if ! executeKermitScript "$script" $output; then
if grep -q '^ftp check ' $output; then
echo "no"
else
echo "error"
fi
else
echo "yes"
fi
rm -f "$output"
}
function rotateRemoteFilesFor
{
local host="$1"
local btype="$2"
local commands=()
local fnum=
for fnum in $( seq $ftp_rotate -1 1 ); do
local fname="/encrypted-${host}-${btype}-$fnum.tar.gz"
local fe=`fileExists "$fname"`
if [ "x$fe" = "xerror" ]; then
echo "FTP check error for $fname"
exit 1;
elif [ "x$fe" = "xyes" ]; then
local ncommand=
if [ $fnum -eq $ftp_rotate ]; then
ncommand="delete $fname"
else
ncommand="rename $fname /encrypted-${host}-${btype}-$(( $fnum + 1 )).tar.gz"
fi
commands=( "${commands[@]}" "$ncommand" )
fi
done
[ ${#commands[@]} -eq 0 ] && return 0
local temp=`mktemp`
executeKermitScript `makeFTPScript "${commands[@]}"` "$temp"
local rv=$?
rm -f "$temp"
return $rv
}
function putRemoteFileFor
{
local host="$1"
local btype="$2"
local tempdir="$3"
if ! rotateRemoteFilesFor "$host" "$btype"; then
return 1;
fi
local temp=`mktemp`
executeKermitScript `makeFTPScript "lcd $tempdir" "put encrypted-${host}-${btype}-1.tar.gz"` $temp
local rv=$?
rm -f "$temp"
return $rv
}
function handleFile
{
local host="$1"
local btype="$2"
if [ "x$btype" = "xfull" ]; then
return
fi
echo -e "\tCopying data for $host / $btype to FTP server" >&2
local tempdir="`mktemp -d`"
local src="${BACKUP_TARGET}/${host}/${btype}-1.tar.gz"
local dest="$tempdir/encrypted-${host}-${btype}-1.tar.gz"
cat "$src" | crypt > $dest
putRemoteFileFor $host $btype $tempdir
rm -rf "$tempdir"
}
function initPost
{
echo "======================================================"
echo "POST-PROCESSING BACKUPS"
echo
}
function finishPost
{
echo
}
initPost
while read host btype; do
handleFile $host $btype 2>&1
done
finishPost

31
backup/ssh-client/README Normal file
View file

@ -0,0 +1,31 @@
Client-side scripts for SSH backup
===================================
The scripts in this directory are meant to be used with the backup server's SSH
fetch script.
Installation
-------------
1/ Create an user that uses the backup-user-shell as its shell and
/var/lib/rbackup as its home directory.
2/ Authorize the server's SSH key (limiting the key to the backup server's
address is a good idea) to log in as that specific user
3/ Authorize the backup user to run the main script as root (see sudo.example)
If you want the archive sent to the backup server to be encrypted locally,
write the encryption key in the /etc/rbackup-encryption-key file (mode 0600 for
root). Otherwise, make sure the file does not exist.
Notes
------
1/ If the backup server is compromised, then so is the system being backed up.
2/ If you use local encryption (which would mitigate the problem described
above), make sure you have a copy of the key somewhere.
3/ If you want to use something other than /var/lib/rbackup as the user's home
directory, you'll have to change the backup-user-shell script.

56
backup/ssh-client/backup-client Executable file
View file

@ -0,0 +1,56 @@
#!/bin/bash
function printToServer
{
echo "...SRC...$*" >&2
}
function catToServer
{
sed -e 's/^/...SRC.../' < "$1" >&2
}
read backup_directory
if [ -z "$backup_directory" ]; then
printToServer "ERROR: no directory to backup"
exit 1
else
backup_directory="`echo "/$backup_directory" | sed -e 's/\/\+/\//g' -e 's/\/$//'`"
if ! [ -d "$backup_directory" ]; then
printToServer "ERROR: missing directory $backup_directory"
exit 1
fi
fi
backup_exclude=( )
while read backup_edir; do
backup_exclude=( ${backup_exclude[@]} $backup_edir )
done
command='ionice -c2 -n7 tar --numeric-owner --one-file-system --ignore-failed-read --warning=none -c'
index=
for index in $( seq 0 $(( ${#backup_exclude[@]} - 1 )) ); do
command="$command"' "--exclude='"`echo "./${backup_exclude[$index]}" | sed -e 's/\/\+/\//g' -e 's/\/$//'`"'"'
done
command="$command"' ".'"$backup_directory"'"'
if [ -f "/etc/rbackup-encryption-key" ]; then
command="$command | nice -n20 openssl enc -kfile /etc/rbackup-encryption-key -aes-256-cbc -e"
fi
printToServer "Remote host ready"
tarerrors="`mktemp`"
chmod 600 "$tarerrors"
cd /
eval $command 2>"$tarerrors"
if ! [ -z "`cat $tarerrors`" ]; then
printToServer "FETCH ERROR: something went wrong while creating the archive:" >&2
printToServer "-----------------------------------------------------" >&2
catToServer "$tarerrors"
printToServer "-----------------------------------------------------" >&2
rm -f "$tarerrors"
exit 1
fi
rm -f "$tarerrors"

View file

@ -0,0 +1,3 @@
#!/bin/sh
exec sudo /bin/bash /var/lib/rbackup/backup-client

View file

@ -0,0 +1,4 @@
# Remote backup system, assuming the remote backup user is "rbackup" and
# the script has been installed in /var/lib/rbackup
#
rbackup ALL= (root) NOPASSWD: /bin/bash /var/lib/rbackup/backup-client