3 # B A C K U P N I N J A /()/
6 # Copyright (C) 2004-05 riseup.net -- property is theft.
8 # This program is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 2 of the License, or
11 # (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
19 #####################################################
33 if [ "$usecolors" == "yes" ]; then
34 local typestr=`echo "$@" | sed 's/\(^[^:]*\).*$/\1/'`
35 [ "$typestr" == "Debug" ] && COLOR=$BLUE
36 [ "$typestr" == "Info" ] && COLOR=$GREEN
37 [ "$typestr" == "Warning" ] && COLOR=$YELLOW
38 [ "$typestr" == "Error" ] && COLOR=$RED
39 [ "$typestr" == "Fatal" ] && COLOR=$PURPLE
41 echo -e "$COLOR$@$endcolor"
47 # We have the following message levels:
49 # 1 - normal messages - green
50 # 2 - warnings - yellow
53 # First variable passed is the error level, all others are printed
55 # if 1, echo out all warnings, errors, or fatal
56 # used to capture output from handlers
62 [ ${#@} -gt 1 ] || return
66 if [ $type == 100 ]; then
67 typestr=`echo "$@" | sed 's/\(^[^:]*\).*$/\1/'`
68 [ "$typestr" == "Debug" ] && type=0
69 [ "$typestr" == "Info" ] && type=1
70 [ "$typestr" == "Warning" ] && type=2
71 [ "$typestr" == "Error" ] && type=3
72 [ "$typestr" == "Fatal" ] && type=4
75 types=(Debug Info Warning Error Fatal)
76 typestr="${types[$type]}: "
81 if [ $echo_debug_msg == 1 ]; then
82 echo -e "$typestr$@" >&2
84 colorize "$typestr$@" >&2
87 if [ $print -lt $loglevel ]; then
93 if [ -w "$logfile" ]; then
94 echo -e `date "+%h %d %H:%M:%S"` "$@" >> $logfile
120 messages[$msgcount]=$1
125 # enforces very strict permissions on configuration file $file.
128 function check_perms() {
130 local perms=`ls -ld $file`
132 if [ "$perms" != "------" ]; then
133 echo "Configuration files must not be group or world writable/readable! Dying on file $file"
134 fatal "Configuration files must not be group or world writable/readable! Dying on file $file"
136 if [ `ls -ld $file | awk '{print $3}'` != "root" ]; then
137 echo "Configuration files must be owned by root! Dying on file $file"
138 fatal "Configuration files must be owned by root! Dying on file $file"
142 # simple lowercase function
144 echo "$1" | tr [:upper:] [:lower:]
147 # simple to integer function
149 echo "$1" | tr [:alpha:] -d
153 # function isnow(): returns 1 if the time/day passed as $1 matches
154 # the current time/day.
156 # format is <day> at <time>:
162 # we grab the current time once, since processing
163 # all the configs might take more than an hour.
166 nowdayofweek=`date +%A`
167 nowdayofweek=`tolower "$nowdayofweek"`
172 whendayofweek=$1; at=$2; whentime=$3;
173 whenday=`toint "$whendayofweek"`
174 whendayofweek=`tolower "$whendayofweek"`
175 whentime=`echo "$whentime" | sed 's/:[0-9][0-9]$//' | sed -r 's/^([0-9])$/0\1/'`
177 if [ "$whendayofweek" == "everyday" -o "$whendayofweek" == "daily" ]; then
178 whendayofweek=$nowdayofweek
181 if [ "$whenday" == "" ]; then
182 if [ "$whendayofweek" != "$nowdayofweek" ]; then
183 whendayofweek=${whendayofweek%s}
184 if [ "$whendayofweek" != "$nowdayofweek" ]; then
188 elif [ "$whenday" != "$nowday" ]; then
192 [ "$at" == "at" ] || return 0
193 [ "$whentime" == "$nowtime" ] || return 0
201 This script allows you to coordinate system backup by dropping a few
202 simple configuration files into @CFGDIR@/backup.d/. Typically, this
203 script is run hourly from cron.
205 The following options are available:
206 -h, --help This usage message
207 -d, --debug Run in debug mode, where all log messages are
208 output to the current shell.
209 -f, --conffile FILE Use FILE for the main configuration instead
210 of @CFGDIR@/backupninja.conf
211 -t, --test Test run mode. This will test if the backup
212 could run, without actually preforming any
213 backups. For example, it will attempt to authenticate
214 or test that ssh keys are set correctly.
215 -n, --now Perform actions now, instead of when they might
216 be scheduled. No output will be created unless also
218 --run FILE Execute the specified action file and then exit.
219 Also puts backupninja in debug mode.
221 When in debug mode, output to the console will be colored:
224 debug "Debugging info (when run with -d)"
225 info "Informational messages (verbosity level 4)"
226 warning "Warnings (verbosity level 3 and up)"
227 error "Errors (verbosity level 2 and up)"
228 fatal "Fatal, halting errors (always shown)"
232 ## this function handles the running of a backup action
234 ## these globals are modified:
235 ## fatals, errors, warnings, actions_run, errormsg
238 function process_action() {
244 # skip over this config if "when" option
245 # is not set to the current time.
246 getconf when "$defaultwhen"
247 if [ "$processnow" == 1 ]; then
248 info ">>>> starting action $file (because of --now)"
250 elif [ "$when" == "hourly" ]; then
251 info ">>>> starting action $file (because 'when = hourly')"
260 if [ $ret == 0 ]; then
261 debug "skipping $file because it is not $w"
263 info ">>>> starting action $file (because it is $w)"
270 [ "$run" == "no" ] && return
272 let "actions_run += 1"
275 local bufferfile=`maketemp backupninja.buffer`
276 echo "" > $bufferfile
279 . $scriptdirectory/$suffix $file
282 echo $a >> $bufferfile
283 [ $debug ] && colorize "$a"
287 # ^^^^^^^^ we have a problem! we can't grab the return code "$?". grrr.
290 _warnings=`cat $bufferfile | grep "^Warning: " | wc -l`
291 _errors=`cat $bufferfile | grep "^Error: " | wc -l`
292 _fatals=`cat $bufferfile | grep "^Fatal: " | wc -l`
294 ret=`grep "\(^Warning: \|^Error: \|^Fatal: \)" $bufferfile`
296 if [ $_fatals != 0 ]; then
297 msg "*failed* -- $file"
298 errormsg="$errormsg\n== fatal errors from $file ==\n\n$ret\n"
299 passthru "Fatal: <<<< finished action $file: FAILED"
300 elif [ $_errors != 0 ]; then
301 msg "*error* -- $file"
302 errormsg="$errormsg\n== errors from $file ==\n\n$ret\n"
303 error "<<<< finished action $file: ERROR"
304 elif [ $_warnings != 0 ]; then
305 msg "*warning* -- $file"
306 errormsg="$errormsg\n== warnings from $file ==\n\n$ret\n"
307 warning "<<<< finished action $file: WARNING"
309 msg "success -- $file"
310 info "<<<< finished action $file: SUCCESS"
313 let "fatals += _fatals"
314 let "errors += _errors"
315 let "warnings += _warnings"
318 #####################################################
322 conffile="@CFGDIR@/backupninja.conf"
325 ## process command line options
327 while [ $# -ge 1 ]; do
330 -d|--debug) debug=1;;
331 -t|--test) test=1;debug=1;;
332 -n|--now) processnow=1;;
337 echo "-f|--conffile option must be followed by an existing filename"
338 fatal "-f|--conffile option must be followed by an existing filename"
341 # we shift here to avoid processing the file path
350 echo "--run option must be fallowed by a backupninja action file"
351 fatal "--run option must be fallowed by a backupninja action file"
358 echo "Unknown option $1"
359 fatal "Unknown option $1"
371 ## Load and confirm basic configuration values
374 if [ ! -r "$conffile" ]; then
375 echo "Configuration file $conffile not found."
376 fatal "Configuration file $conffile not found."
380 libdirectory=`grep '^libdirectory' $conffile | awk '{print $3}'`
381 if [ -z "$libdirectory" ]; then
382 if [ -d "@libdir@" ]; then
383 libdirectory="@libdir@"
385 echo "Could not find entry 'libdirectory' in $conffile."
386 fatal "Could not find entry 'libdirectory' in $conffile."
389 if [ ! -d "$libdirectory" ]; then
390 echo "Lib directory $libdirectory not found."
391 fatal "Lib directory $libdirectory not found."
395 # include shared functions
396 . $libdirectory/tools
400 # get global config options (second param is the default)
401 getconf configdirectory @CFGDIR@/backup.d
402 getconf scriptdirectory @datadir@
404 getconf reportsuccess yes
405 getconf reportwarning yes
407 getconf when "Everyday at 01:00"
409 getconf logfile @localstatedir@/log/backupninja.log
410 getconf usecolors "yes"
411 getconf SLAPCAT /usr/sbin/slapcat
412 getconf LDAPSEARCH /usr/bin/ldapsearch
413 getconf RDIFFBACKUP /usr/bin/rdiff-backup
414 getconf MYSQL /usr/bin/mysql
415 getconf MYSQLHOTCOPY /usr/bin/mysqlhotcopy
416 getconf MYSQLDUMP /usr/bin/mysqldump
417 getconf PGSQLDUMP /usr/bin/pg_dump
418 getconf PGSQLDUMPALL /usr/bin/pg_dumpall
419 getconf GZIP /bin/gzip
420 getconf RSYNC /usr/bin/rsync
422 getconf VSERVERINFO /usr/sbin/vserver-info
423 getconf VSERVER /usr/sbin/vserver
424 getconf VROOTDIR `if [ -f "$VSERVERINFO" ]; then $VSERVERINFO info SYSINFO |grep vserver-Rootdir | awk '{print $2}'; fi`
426 if [ ! -d "$configdirectory" ]; then
427 echo "Configuration directory '$configdirectory' not found."
428 fatal "Configuration directory '$configdirectory' not found."
431 [ -f "$logfile" ] || touch $logfile
433 if [ "$UID" != "0" ]; then
434 echo "$0 can only be run as root"
438 if [ "$vservers" == "yes" -a ! -d "$VROOTDIR" ]; then
439 echo "vservers option set in config, but $VROOTDIR is not a directory!"
440 fatal "vservers option set in config, but $VROOTDIR is not a directory!"
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."