2 # -*- mode: sh; sh-basic-offset: 3; indent-tabs-mode: nil; -*-
5 # B A C K U P N I N J A /()/
8 # Copyright (C) 2004-05 riseup.net -- property is theft.
10 # This program is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 2 of the License, or
13 # (at your option) any later version.
15 # This program is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
21 #####################################################
24 function setupcolors () {
32 COLORS=($BLUE $GREEN $YELLOW $RED $PURPLE)
35 function colorize () {
36 if [ "$usecolors" == "yes" ]; then
37 local typestr=`echo "$@" | sed 's/\(^[^:]*\).*$/\1/'`
38 [ "$typestr" == "Debug" ] && type=0
39 [ "$typestr" == "Info" ] && type=1
40 [ "$typestr" == "Warning" ] && type=2
41 [ "$typestr" == "Error" ] && type=3
42 [ "$typestr" == "Fatal" ] && type=4
43 color=${COLORS[$type]}
45 echo -e "$color$@$endcolor"
51 # We have the following message levels:
53 # 1 - normal messages - green
54 # 2 - warnings - yellow
57 # First variable passed is the error level, all others are printed
59 # if 1, echo out all warnings, errors, or fatal
60 # used to capture output from handlers
66 [ ${#@} -gt 1 ] || return
70 if [ $type == 100 ]; then
71 typestr=`echo "$@" | sed 's/\(^[^:]*\).*$/\1/'`
72 [ "$typestr" == "Debug" ] && type=0
73 [ "$typestr" == "Info" ] && type=1
74 [ "$typestr" == "Warning" ] && type=2
75 [ "$typestr" == "Error" ] && type=3
76 [ "$typestr" == "Fatal" ] && type=4
79 types=(Debug Info Warning Error Fatal)
80 typestr="${types[$type]}: "
85 if [ $echo_debug_msg == 1 ]; then
86 echo -e "$typestr$@" >&2
88 colorize "$typestr$@" >&2
91 if [ $print -lt $loglevel ]; then
97 if [ -w "$logfile" ]; then
98 echo -e `date "+%h %d %H:%M:%S"` "$@" >> $logfile
102 function passthru() {
124 messages[$msgcount]=$1
129 # enforces very strict permissions on configuration file $file.
132 function check_perms() {
134 local perms=`ls -ld $file`
136 if [ "$perms" != "------" ]; then
137 echo "Configuration files must not be group or world writable/readable! Dying on file $file"
138 fatal "Configuration files must not be group or world writable/readable! Dying on file $file"
140 if [ `ls -ld $file | awk '{print $3}'` != "root" ]; then
141 echo "Configuration files must be owned by root! Dying on file $file"
142 fatal "Configuration files must be owned by root! Dying on file $file"
146 # simple lowercase function
148 echo "$1" | tr [:upper:] [:lower:]
151 # simple to integer function
153 echo "$1" | tr -d [:alpha:]
157 # function isnow(): returns 1 if the time/day passed as $1 matches
158 # the current time/day.
160 # format is <day> at <time>:
166 # we grab the current time once, since processing
167 # all the configs might take more than an hour.
170 nowdayofweek=`date +%A`
171 nowdayofweek=`tolower "$nowdayofweek"`
176 whendayofweek=$1; at=$2; whentime=$3;
177 whenday=`toint "$whendayofweek"`
178 whendayofweek=`tolower "$whendayofweek"`
179 whentime=`echo "$whentime" | sed 's/:[0-9][0-9]$//' | sed -r 's/^([0-9])$/0\1/'`
181 if [ "$whendayofweek" == "everyday" -o "$whendayofweek" == "daily" ]; then
182 whendayofweek=$nowdayofweek
185 if [ "$whenday" == "" ]; then
186 if [ "$whendayofweek" != "$nowdayofweek" ]; then
187 whendayofweek=${whendayofweek%s}
188 if [ "$whendayofweek" != "$nowdayofweek" ]; then
192 elif [ "$whenday" != "$nowday" ]; then
196 [ "$at" == "at" ] || return 0
197 [ "$whentime" == "$nowtime" ] || return 0
205 This script allows you to coordinate system backup by dropping a few
206 simple configuration files into @CFGDIR@/backup.d/. Typically, this
207 script is run hourly from cron.
209 The following options are available:
210 -h, --help This usage message
211 -d, --debug Run in debug mode, where all log messages are
212 output to the current shell.
213 -f, --conffile FILE Use FILE for the main configuration instead
214 of @CFGDIR@/backupninja.conf
215 -t, --test Test run mode. This will test if the backup
216 could run, without actually preforming any
217 backups. For example, it will attempt to authenticate
218 or test that ssh keys are set correctly.
219 -n, --now Perform actions now, instead of when they might
220 be scheduled. No output will be created unless also
222 --run FILE Execute the specified action file and then exit.
223 Also puts backupninja in debug mode.
225 When in debug mode, output to the console will be colored:
228 debug "Debugging info (when run with -d)"
229 info "Informational messages (verbosity level 4)"
230 warning "Warnings (verbosity level 3 and up)"
231 error "Errors (verbosity level 2 and up)"
232 fatal "Fatal, halting errors (always shown)"
236 ## this function handles the running of a backup action
238 ## these globals are modified:
239 ## fatals, errors, warnings, actions_run, errormsg
242 function process_action() {
248 # skip over this config if "when" option
249 # is not set to the current time.
250 getconf when "$defaultwhen"
251 if [ "$processnow" == 1 ]; then
252 info ">>>> starting action $file (because of --now)"
254 elif [ "$when" == "hourly" ]; then
255 info ">>>> starting action $file (because 'when = hourly')"
264 if [ $ret == 0 ]; then
265 debug "skipping $file because it is not $w"
267 info ">>>> starting action $file (because it is $w)"
274 [ "$run" == "no" ] && return
276 let "actions_run += 1"
279 local bufferfile=`maketemp backupninja.buffer`
280 echo "" > $bufferfile
283 . $scriptdirectory/$suffix $file
286 echo $a >> $bufferfile
287 [ $debug ] && colorize "$a"
291 # ^^^^^^^^ we have a problem! we can't grab the return code "$?". grrr.
294 _warnings=`cat $bufferfile | grep "^Warning: " | wc -l`
295 _errors=`cat $bufferfile | grep "^Error: " | wc -l`
296 _fatals=`cat $bufferfile | grep "^Fatal: " | wc -l`
298 ret=`grep "\(^Warning: \|^Error: \|^Fatal: \)" $bufferfile`
300 if [ $_fatals != 0 ]; then
301 msg "*failed* -- $file"
302 errormsg="$errormsg\n== fatal errors from $file ==\n\n$ret\n"
303 passthru "Fatal: <<<< finished action $file: FAILED"
304 elif [ $_errors != 0 ]; then
305 msg "*error* -- $file"
306 errormsg="$errormsg\n== errors from $file ==\n\n$ret\n"
307 error "<<<< finished action $file: ERROR"
308 elif [ $_warnings != 0 ]; then
309 msg "*warning* -- $file"
310 errormsg="$errormsg\n== warnings from $file ==\n\n$ret\n"
311 warning "<<<< finished action $file: WARNING"
313 msg "success -- $file"
314 info "<<<< finished action $file: SUCCESS"
317 let "fatals += _fatals"
318 let "errors += _errors"
319 let "warnings += _warnings"
322 #####################################################
326 conffile="@CFGDIR@/backupninja.conf"
329 ## process command line options
331 while [ $# -ge 1 ]; do
334 -d|--debug) debug=1;;
335 -t|--test) test=1;debug=1;;
336 -n|--now) processnow=1;;
341 echo "-f|--conffile option must be followed by an existing filename"
342 fatal "-f|--conffile option must be followed by an existing filename"
345 # we shift here to avoid processing the file path
354 echo "--run option must be fallowed by a backupninja action file"
355 fatal "--run option must be fallowed by a backupninja action file"
362 echo "Unknown option $1"
363 fatal "Unknown option $1"
375 ## Load and confirm basic configuration values
378 if [ ! -r "$conffile" ]; then
379 echo "Configuration file $conffile not found."
380 fatal "Configuration file $conffile not found."
384 libdirectory=`grep '^libdirectory' $conffile | awk '{print $3}'`
385 if [ -z "$libdirectory" ]; then
386 if [ -d "@libdir@" ]; then
387 libdirectory="@libdir@"
389 echo "Could not find entry 'libdirectory' in $conffile."
390 fatal "Could not find entry 'libdirectory' in $conffile."
393 if [ ! -d "$libdirectory" ]; then
394 echo "Lib directory $libdirectory not found."
395 fatal "Lib directory $libdirectory not found."
399 # include shared functions
400 . $libdirectory/tools
401 . $libdirectory/vserver
405 # get global config options (second param is the default)
406 getconf configdirectory @CFGDIR@/backup.d
407 getconf scriptdirectory @datadir@
409 getconf reportsuccess yes
410 getconf reportwarning yes
412 getconf when "Everyday at 01:00"
414 getconf logfile @localstatedir@/log/backupninja.log
415 getconf usecolors "yes"
416 getconf SLAPCAT /usr/sbin/slapcat
417 getconf LDAPSEARCH /usr/bin/ldapsearch
418 getconf RDIFFBACKUP /usr/bin/rdiff-backup
419 getconf MYSQL /usr/bin/mysql
420 getconf MYSQLHOTCOPY /usr/bin/mysqlhotcopy
421 getconf MYSQLDUMP /usr/bin/mysqldump
422 getconf PGSQLDUMP /usr/bin/pg_dump
423 getconf PGSQLDUMPALL /usr/bin/pg_dumpall
424 getconf GZIP /bin/gzip
425 getconf RSYNC /usr/bin/rsync
427 # initialize vservers support
428 # (get config variables and check real vservers availability)
429 init_vservers nodialog
431 if [ ! -d "$configdirectory" ]; then
432 echo "Configuration directory '$configdirectory' not found."
433 fatal "Configuration directory '$configdirectory' not found."
436 [ -f "$logfile" ] || touch $logfile
438 if [ "$UID" != "0" ]; then
439 echo "`basename $0` can only be run as root"
443 ## Process each configuration file
445 # by default, don't make files which are world or group readable.
448 # these globals are set by process_action()
455 if [ "$singlerun" ]; then
458 files=`find $configdirectory -mindepth 1 ! -name '.*.swp' | sort -n`
461 for file in $files; do
462 [ -f "$file" ] || continue
466 base=`basename $file`
467 if [ "${base:0:1}" == "0" -o "$suffix" == "disabled" ]; then
468 info "Skipping $file"
472 if [ -e "$scriptdirectory/$suffix" ]; then
473 process_action $file $suffix
475 error "Can't process file '$file': no handler script for suffix '$suffix'"
476 msg "*missing handler* -- $file"
480 ## mail the messages to the report address
482 if [ $actions_run == 0 ]; then doit=0
483 elif [ "$reportemail" == "" ]; then doit=0
484 elif [ $fatals != 0 ]; then doit=1
485 elif [ $errors != 0 ]; then doit=1
486 elif [ "$reportsuccess" == "yes" ]; then doit=1
487 elif [ "$reportwarning" == "yes" -a $warnings != 0 ]; then doit=1
491 if [ $doit == 1 ]; then
492 debug "send report to $reportemail"
494 [ $warnings == 0 ] || subject="WARNING"
495 [ $errors == 0 ] || subject="ERROR"
496 [ $fatals == 0 ] || subject="FAILED"
499 for ((i=0; i < ${#messages[@]} ; i++)); do
503 } | mail $reportemail -s "backupninja: $hostname $subject"
506 if [ $actions_run != 0 ]; then
507 info "FINISHED: $actions_run actions run. $fatals fatal. $errors error. $warnings warning."