Backup system
Imported both the server- and client-side backup scripts.
This commit is contained in:
commit
d9f75447a6
24 changed files with 1043 additions and 0 deletions
5
README
Normal file
5
README
Normal 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
8
backup/README
Normal 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
107
backup/server/README
Normal 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
441
backup/server/backup
Executable 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
19
backup/server/backup.conf
Normal 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
|
7
backup/server/backup.conf.d/exclude.conf
Normal file
7
backup/server/backup.conf.d/exclude.conf
Normal 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
|
7
backup/server/backup.conf.d/fetch-hosts.conf
Normal file
7
backup/server/backup.conf.d/fetch-hosts.conf
Normal 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
|
7
backup/server/backup.conf.d/fetch-modes.conf
Normal file
7
backup/server/backup.conf.d/fetch-modes.conf
Normal 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
|
12
backup/server/backup.conf.d/fetch/remote.conf
Normal file
12
backup/server/backup.conf.d/fetch/remote.conf
Normal 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"
|
7
backup/server/backup.conf.d/fetch/ve.conf
Normal file
7
backup/server/backup.conf.d/fetch/ve.conf
Normal 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.
|
2
backup/server/backup.conf.d/fetch/ve/ve-host.conf
Normal file
2
backup/server/backup.conf.d/fetch/ve/ve-host.conf
Normal file
|
@ -0,0 +1,2 @@
|
|||
# Define the root for a VE host
|
||||
ROOT=1000
|
6
backup/server/backup.conf.d/hosts.conf
Normal file
6
backup/server/backup.conf.d/hosts.conf
Normal 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
|
11
backup/server/backup.conf.d/modes.conf
Normal file
11
backup/server/backup.conf.d/modes.conf
Normal 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
|
1
backup/server/backup.conf.d/post/crypto.key
Normal file
1
backup/server/backup.conf.d/post/crypto.key
Normal file
|
@ -0,0 +1 @@
|
|||
key to use when encrypting files during post-processing goes here
|
13
backup/server/backup.conf.d/post/ftp-access.conf
Normal file
13
backup/server/backup.conf.d/post/ftp-access.conf
Normal 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
|
12
backup/server/backup.conf.d/types.conf
Normal file
12
backup/server/backup.conf.d/types.conf
Normal 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
|
6
backup/server/crontab.example
Normal file
6
backup/server/crontab.example
Normal 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
|
44
backup/server/share/fetch-local
Normal file
44
backup/server/share/fetch-local
Normal 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
|
||||
}
|
55
backup/server/share/fetch-ssh
Normal file
55
backup/server/share/fetch-ssh
Normal 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
179
backup/server/share/postprocess
Executable 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
31
backup/ssh-client/README
Normal 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
56
backup/ssh-client/backup-client
Executable 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"
|
3
backup/ssh-client/backup-user-shell
Executable file
3
backup/ssh-client/backup-user-shell
Executable file
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh
|
||||
|
||||
exec sudo /bin/bash /var/lib/rbackup/backup-client
|
4
backup/ssh-client/sudo.example
Normal file
4
backup/ssh-client/sudo.example
Normal 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
|
Loading…
Reference in a new issue