~ubuntu-branches/ubuntu/jaunty/backupninja/jaunty

« back to all changes in this revision

Viewing changes to src/backupninja

  • Committer: Bazaar Package Importer
  • Author(s): Chuck Short
  • Date: 2007-09-21 12:33:27 UTC
  • Revision ID: james.westby@ubuntu.com-20070921123327-f4fq9lxkfnxn7vck
Tags: 0.9.4-6ubuntu4
Add rsnap and rub handlers. (LP: #141485)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/bin/bash
 
2
# -*- mode: sh; sh-basic-offset: 3; indent-tabs-mode: nil; -*-
 
3
#
 
4
#                          |\_
 
5
# B A C K U P N I N J A   /()/
 
6
#                         `\|
 
7
#
 
8
# Copyright (C) 2004-05 riseup.net -- property is theft.
 
9
#
 
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.
 
14
#
 
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.
 
19
#
 
20
 
 
21
#####################################################
 
22
## FUNCTIONS
 
23
 
 
24
function setupcolors () {
 
25
        BLUE="\033[34;01m"
 
26
        GREEN="\033[32;01m"
 
27
        YELLOW="\033[33;01m"
 
28
        PURPLE="\033[35;01m"
 
29
        RED="\033[31;01m"
 
30
        OFF="\033[0m"
 
31
        CYAN="\033[36;01m"
 
32
        COLORS=($BLUE $GREEN $YELLOW $RED $PURPLE)
 
33
}
 
34
 
 
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]}
 
44
                endcolor=$OFF
 
45
                echo -e "$color$@$endcolor"
 
46
        else
 
47
                echo -e "$@"
 
48
        fi
 
49
}
 
50
 
 
51
# We have the following message levels:
 
52
# 0 - debug - blue
 
53
# 1 - normal messages - green
 
54
# 2 - warnings - yellow
 
55
# 3 - errors - red
 
56
# 4 - fatal - purple
 
57
# First variable passed is the error level, all others are printed
 
58
 
 
59
# if 1, echo out all warnings, errors, or fatal
 
60
# used to capture output from handlers
 
61
echo_debug_msg=0
 
62
 
 
63
usecolors=yes
 
64
 
 
65
function printmsg() {
 
66
        [ ${#@} -gt 1 ] || return
 
67
 
 
68
        type=$1
 
69
        shift
 
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
 
77
                typestr=""
 
78
        else
 
79
                types=(Debug Info Warning Error Fatal)
 
80
                typestr="${types[$type]}: "
 
81
        fi
 
82
        
 
83
        print=$[4-type]
 
84
        
 
85
        if [ $echo_debug_msg == 1 ]; then
 
86
                echo -e "$typestr$@" >&2
 
87
        elif [ $debug ]; then
 
88
                colorize "$typestr$@" >&2
 
89
        fi
 
90
        
 
91
        if [ $print -lt $loglevel ]; then
 
92
                logmsg "$typestr$@"
 
93
        fi
 
94
}
 
95
 
 
96
function logmsg() {
 
97
        if [ -w "$logfile" ]; then
 
98
                echo -e `date "+%h %d %H:%M:%S"` "$@" >> $logfile
 
99
        fi
 
100
}
 
101
 
 
102
function passthru() {
 
103
        printmsg 100 "$@"
 
104
}
 
105
function debug() {
 
106
        printmsg 0 "$@"
 
107
}
 
108
function info() {
 
109
        printmsg 1 "$@"
 
110
}
 
111
function warning() {
 
112
        printmsg 2 "$@"
 
113
}
 
114
function error() {
 
115
        printmsg 3 "$@" 
 
116
}
 
117
function fatal() {
 
118
        printmsg 4 "$@"
 
119
        exit 2
 
120
}
 
121
 
 
122
msgcount=0
 
123
function msg {
 
124
        messages[$msgcount]=$1
 
125
        let "msgcount += 1"
 
126
}
 
127
 
 
128
#
 
129
# enforces very strict permissions on configuration file $file.
 
130
#
 
131
 
 
132
function check_perms() {
 
133
   local file=$1
 
134
   debug "check_perms $file"
 
135
   local perms
 
136
   local owners
 
137
 
 
138
   perms=($(stat -L --format='%A' $file))
 
139
   debug "perms: $perms"
 
140
   local gperm=${perms:4:3}
 
141
   debug "gperm: $gperm"
 
142
   local wperm=${perms:7:3}
 
143
   debug "wperm: $wperm"
 
144
 
 
145
   owners=($(stat -L --format='%g %G %u %U' $file))
 
146
   local gid=${owners[0]}
 
147
   local group=${owners[1]}
 
148
   local owner=${owners[2]}
 
149
 
 
150
   if [ "$owner" != 0 ]; then
 
151
      echo "Configuration files must be owned by root! Dying on file $file"
 
152
      fatal "Configuration files must be owned by root! Dying on file $file"
 
153
   fi
 
154
   
 
155
   if [ "$wperm" != '---' ]; then
 
156
      echo "Configuration files must not be world writable/readable! Dying on file $file"
 
157
      fatal "Configuration files must not be world writable/readable! Dying on file $file"
 
158
   fi
 
159
 
 
160
   if [ "$gperm" != '---' ]; then
 
161
      case "$admingroup" in
 
162
         $gid|$group) :;;
 
163
 
 
164
         *)
 
165
           if [ "$gid" != 0 ]; then
 
166
              echo "Configuration files must not be writable/readable by group $group! Use the admingroup option in backupninja.conf. Dying on file $file"
 
167
              fatal "Configuration files must not be writable/readable by group $group! Use the admingroup option in backupninja.conf. Dying on file $file"
 
168
           fi
 
169
         ;;
 
170
         esac
 
171
   fi
 
172
}
 
173
 
 
174
# simple lowercase function
 
175
function tolower() {
 
176
        echo "$1" | tr '[:upper:]' '[:lower:]'
 
177
}
 
178
 
 
179
# simple to integer function
 
180
function toint() {
 
181
        echo "$1" | tr -d '[:alpha:]'
 
182
}
 
183
 
 
184
#
 
185
# function isnow(): returns 1 if the time/day passed as $1 matches
 
186
# the current time/day.
 
187
#
 
188
# format is <day> at <time>:
 
189
#   sunday at 16
 
190
#   8th at 01
 
191
#   everyday at 22
 
192
#
 
193
 
 
194
# we grab the current time once, since processing
 
195
# all the configs might take more than an hour.
 
196
nowtime=`date +%H`
 
197
nowday=`date +%d`
 
198
nowdayofweek=`date +%A`
 
199
nowdayofweek=`tolower "$nowdayofweek"`
 
200
 
 
201
function isnow() {
 
202
        local when="$1"
 
203
        set -- $when
 
204
        whendayofweek=$1; at=$2; whentime=$3;
 
205
        whenday=`toint "$whendayofweek"`
 
206
        whendayofweek=`tolower "$whendayofweek"`
 
207
        whentime=`echo "$whentime" | sed 's/:[0-9][0-9]$//' | sed -r 's/^([0-9])$/0\1/'`
 
208
 
 
209
        if [ "$whendayofweek" == "everyday" -o "$whendayofweek" == "daily" ]; then
 
210
                whendayofweek=$nowdayofweek
 
211
        fi
 
212
 
 
213
        if [ "$whenday" == "" ]; then
 
214
                if [ "$whendayofweek" != "$nowdayofweek" ]; then
 
215
                        whendayofweek=${whendayofweek%s}
 
216
                        if [ "$whendayofweek" != "$nowdayofweek" ]; then
 
217
                                return 0
 
218
                        fi
 
219
                fi
 
220
        elif [ "$whenday" != "$nowday" ]; then
 
221
                return 0
 
222
        fi
 
223
 
 
224
        [ "$at" == "at" ] || return 0
 
225
        [ "$whentime" == "$nowtime" ] || return 0
 
226
 
 
227
        return 1
 
228
}
 
229
 
 
230
function usage() {
 
231
        cat << EOF
 
232
$0 usage:
 
233
This script allows you to coordinate system backup by dropping a few
 
234
simple configuration files into /etc/backup.d/. Typically, this
 
235
script is run hourly from cron.
 
236
 
 
237
The following options are available:
 
238
-h, --help           This usage message
 
239
-d, --debug          Run in debug mode, where all log messages are
 
240
                     output to the current shell.
 
241
-f, --conffile FILE  Use FILE for the main configuration instead
 
242
                     of /etc/backupninja.conf
 
243
-t, --test           Test run mode. This will test if the backup
 
244
                     could run, without actually preforming any
 
245
                     backups. For example, it will attempt to authenticate
 
246
                     or test that ssh keys are set correctly.
 
247
-n, --now            Perform actions now, instead of when they might
 
248
                     be scheduled. No output will be created unless also
 
249
                     run with -d.
 
250
    --run FILE       Execute the specified action file and then exit.    
 
251
                     Also puts backupninja in debug mode.
 
252
                     
 
253
When in debug mode, output to the console will be colored:
 
254
EOF
 
255
        debug=1
 
256
        debug   "Debugging info (when run with -d)"
 
257
        info    "Informational messages (verbosity level 4)"
 
258
        warning "Warnings (verbosity level 3 and up)"
 
259
        error   "Errors (verbosity level 2 and up)"
 
260
        fatal   "Fatal, halting errors (always shown)"
 
261
}
 
262
 
 
263
##
 
264
## this function handles the running of a backup action
 
265
##
 
266
## these globals are modified:
 
267
## fatals, errors, warnings, actions_run, errormsg
 
268
##
 
269
 
 
270
function process_action() {
 
271
        local file="$1"
 
272
        local suffix="$2"
 
273
        local run="no"
 
274
        setfile $file
 
275
 
 
276
        # skip over this config if "when" option
 
277
        # is not set to the current time.
 
278
        getconf when "$defaultwhen"
 
279
        if [ "$processnow" == 1 ]; then
 
280
                info ">>>> starting action $file (because of --now)"
 
281
                run="yes"
 
282
        elif [ "$when" == "hourly" ]; then
 
283
                info ">>>> starting action $file (because 'when = hourly')"
 
284
                run="yes"
 
285
        else
 
286
                IFS=$'\t\n'
 
287
                for w in $when; do
 
288
                        IFS=$' \t\n'
 
289
                        isnow "$w"
 
290
                        ret=$?
 
291
                        IFS=$'\t\n'
 
292
                        if [ $ret == 0 ]; then
 
293
                                debug "skipping $file because it is not $w"
 
294
                        else
 
295
                                info ">>>> starting action $file (because it is $w)"
 
296
                                run="yes"
 
297
                        fi
 
298
                done
 
299
                IFS=$' \t\n'
 
300
        fi
 
301
        debug $run
 
302
        [ "$run" == "no" ] && return
 
303
        
 
304
        let "actions_run += 1"
 
305
 
 
306
        # call the handler:
 
307
        local bufferfile=`maketemp backupninja.buffer`
 
308
        echo "" > $bufferfile
 
309
        echo_debug_msg=1
 
310
        (
 
311
                . $scriptdirectory/$suffix $file
 
312
        ) 2>&1 | (
 
313
                while read a; do
 
314
                        echo $a >> $bufferfile
 
315
                        [ $debug ] && colorize "$a"
 
316
                done
 
317
        )
 
318
        retcode=$?
 
319
        # ^^^^^^^^ we have a problem! we can't grab the return code "$?". grrr.
 
320
        echo_debug_msg=0
 
321
 
 
322
        _warnings=`cat $bufferfile | grep "^Warning: " | wc -l`
 
323
        _errors=`cat $bufferfile | grep "^Error: " | wc -l`
 
324
        _fatals=`cat $bufferfile | grep "^Fatal: " | wc -l`
 
325
        
 
326
        ret=`grep "\(^Warning: \|^Error: \|^Fatal: \)" $bufferfile`
 
327
        rm $bufferfile
 
328
        if [ $_fatals != 0 ]; then
 
329
                msg "*failed* -- $file"
 
330
                errormsg="$errormsg\n== fatal errors from $file ==\n\n$ret\n"
 
331
                passthru "Fatal: <<<< finished action $file: FAILED"
 
332
        elif [ $_errors != 0 ]; then
 
333
                msg "*error* -- $file"
 
334
                errormsg="$errormsg\n== errors from $file ==\n\n$ret\n"
 
335
                error "<<<< finished action $file: ERROR"
 
336
        elif [ $_warnings != 0 ]; then
 
337
                msg "*warning* -- $file"
 
338
                errormsg="$errormsg\n== warnings from $file ==\n\n$ret\n"
 
339
                warning "<<<< finished action $file: WARNING"
 
340
        else
 
341
                msg "success -- $file"
 
342
                info "<<<< finished action $file: SUCCESS"
 
343
        fi
 
344
 
 
345
        let "fatals += _fatals"
 
346
        let "errors += _errors"
 
347
        let "warnings += _warnings"     
 
348
}
 
349
 
 
350
#####################################################
 
351
## MAIN
 
352
 
 
353
setupcolors
 
354
conffile="/etc/backupninja.conf"
 
355
loglevel=3
 
356
 
 
357
## process command line options
 
358
 
 
359
while [ $# -ge 1 ]; do
 
360
        case $1 in
 
361
                -h|--help) usage;;
 
362
                -d|--debug) debug=1;;
 
363
                -t|--test) test=1;debug=1;;
 
364
                -n|--now) processnow=1;;
 
365
                -f|--conffile)
 
366
                        if [ -f $2 ]; then
 
367
                                conffile=$2
 
368
                        else
 
369
                                echo "-f|--conffile option must be followed by an existing filename"
 
370
                                fatal "-f|--conffile option must be followed by an existing filename"
 
371
                                usage
 
372
                        fi
 
373
                        # we shift here to avoid processing the file path 
 
374
                        shift
 
375
                        ;;
 
376
                --run)
 
377
                        debug=1
 
378
                        if [ -f $2 ]; then
 
379
                                singlerun=$2
 
380
                                processnow=1
 
381
                        else
 
382
                                echo "--run option must be fallowed by a backupninja action file"
 
383
                                fatal "--run option must be fallowed by a backupninja action file"
 
384
                                usage
 
385
                        fi
 
386
                        shift
 
387
                        ;;
 
388
                *)
 
389
                        debug=1
 
390
                        echo "Unknown option $1"
 
391
                        fatal "Unknown option $1"
 
392
                        usage
 
393
                        exit
 
394
                        ;;
 
395
        esac
 
396
        shift
 
397
done                                                                                                                                                                                                            
 
398
 
 
399
#if [ $debug ]; then
 
400
#       usercolors=yes
 
401
#fi
 
402
 
 
403
## Load and confirm basic configuration values
 
404
 
 
405
# bootstrap
 
406
if [ ! -r "$conffile" ]; then
 
407
        echo "Configuration file $conffile not found." 
 
408
        fatal "Configuration file $conffile not found."
 
409
fi
 
410
 
 
411
# find $libdirectory
 
412
libdirectory=`grep '^libdirectory' $conffile | awk '{print $3}'`
 
413
if [ -z "$libdirectory" ]; then
 
414
        if [ -d "/usr/lib/backupninja" ]; then
 
415
           libdirectory="/usr/lib/backupninja"
 
416
        else
 
417
           echo "Could not find entry 'libdirectory' in $conffile." 
 
418
           fatal "Could not find entry 'libdirectory' in $conffile." 
 
419
        fi
 
420
else
 
421
        if [ ! -d "$libdirectory" ]; then
 
422
           echo "Lib directory $libdirectory not found." 
 
423
           fatal "Lib directory $libdirectory not found." 
 
424
        fi
 
425
fi
 
426
 
 
427
# include shared functions
 
428
. $libdirectory/tools
 
429
. $libdirectory/vserver
 
430
 
 
431
setfile $conffile
 
432
 
 
433
# get global config options (second param is the default)
 
434
getconf configdirectory /etc/backup.d
 
435
getconf scriptdirectory /usr/share/backupninja
 
436
getconf reportemail
 
437
getconf reportspace
 
438
getconf reportsuccess yes
 
439
getconf reportwarning yes
 
440
getconf loglevel 3
 
441
getconf when "Everyday at 01:00"
 
442
defaultwhen=$when
 
443
getconf logfile /var/log/backupninja.log
 
444
getconf usecolors "yes"
 
445
getconf SLAPCAT /usr/sbin/slapcat
 
446
getconf LDAPSEARCH /usr/bin/ldapsearch
 
447
getconf RDIFFBACKUP /usr/bin/rdiff-backup
 
448
getconf MYSQLADMIN /usr/bin/mysqladmin
 
449
getconf MYSQL /usr/bin/mysql
 
450
getconf MYSQLHOTCOPY /usr/bin/mysqlhotcopy
 
451
getconf MYSQLDUMP /usr/bin/mysqldump
 
452
getconf PGSQLDUMP /usr/bin/pg_dump
 
453
getconf PGSQLDUMPALL /usr/bin/pg_dumpall
 
454
getconf PGSQLUSER postgres
 
455
getconf GZIP /bin/gzip
 
456
getconf RSYNC /usr/bin/rsync
 
457
getconf admingroup root
 
458
 
 
459
# initialize vservers support
 
460
# (get config variables and check real vservers availability)
 
461
init_vservers nodialog
 
462
 
 
463
if [ ! -d "$configdirectory" ]; then
 
464
        echo "Configuration directory '$configdirectory' not found."
 
465
        fatal "Configuration directory '$configdirectory' not found."
 
466
fi
 
467
 
 
468
[ -f "$logfile" ] || touch $logfile
 
469
 
 
470
if [ "$UID" != "0" ]; then
 
471
        echo "`basename $0` can only be run as root"
 
472
        exit 1
 
473
fi
 
474
 
 
475
## Process each configuration file
 
476
 
 
477
# by default, don't make files which are world or group readable.
 
478
umask 077
 
479
 
 
480
# these globals are set by process_action()
 
481
fatals=0
 
482
errors=0
 
483
warnings=0
 
484
actions_run=0
 
485
errormsg=""
 
486
 
 
487
if [ "$singlerun" ]; then
 
488
        files=$singlerun
 
489
else
 
490
        files=`find $configdirectory -follow -mindepth 1 -maxdepth 1 -type f ! -name '.*.swp' | sort -n`
 
491
 
 
492
        if [ -z "$files" ]; then
 
493
                fatal "No backup actions configured in '$configdirectory', run ninjahelper!"
 
494
        fi
 
495
fi
 
496
 
 
497
for file in $files; do
 
498
        [ -f "$file" ] || continue
 
499
 
 
500
        check_perms ${file%/*} # check containing dir
 
501
        check_perms $file
 
502
        suffix="${file##*.}"
 
503
        base=`basename $file`
 
504
        if [ "${base:0:1}" == "0" -o "$suffix" == "disabled" ]; then
 
505
                info "Skipping $file"
 
506
                continue
 
507
        fi
 
508
 
 
509
        if [ -e "$scriptdirectory/$suffix" ]; then
 
510
                process_action $file $suffix
 
511
        else
 
512
                error "Can't process file '$file': no handler script for suffix '$suffix'"
 
513
                msg "*missing handler* -- $file"
 
514
        fi
 
515
done
 
516
 
 
517
## mail the messages to the report address
 
518
 
 
519
if [ $actions_run == 0 ]; then doit=0
 
520
elif [ "$reportemail" == "" ]; then doit=0
 
521
elif [ $fatals != 0 ]; then doit=1
 
522
elif [ $errors != 0 ]; then doit=1
 
523
elif [ "$reportsuccess" == "yes" ]; then doit=1
 
524
elif [ "$reportwarning" == "yes" -a $warnings != 0 ]; then doit=1
 
525
else doit=0
 
526
fi
 
527
 
 
528
if [ $doit == 1 ]; then
 
529
        debug "send report to $reportemail"
 
530
        hostname=`hostname`
 
531
        [ $warnings == 0 ] || subject="WARNING"
 
532
        [ $errors == 0 ] || subject="ERROR"
 
533
        [ $fatals == 0 ] || subject="FAILED"
 
534
        
 
535
        {
 
536
                for ((i=0; i < ${#messages[@]} ; i++)); do
 
537
                        echo ${messages[$i]}
 
538
                done
 
539
                echo -e "$errormsg"
 
540
                if [ "$reportspace" == "yes" ]; then
 
541
                        previous=""
 
542
                        for i in $(ls "$configdirectory"); do
 
543
                        backuploc=$(grep ^directory "$configdirectory"/"$i" | awk '{print $3}')
 
544
                        if [ "$backuploc" != "$previous" ]; then
 
545
                                mountdev=$(mount | grep "$backuploc" | awk '{print $1}')
 
546
                                df -h "$mountdev"
 
547
                                previous="$backuploc"
 
548
                                fi
 
549
                        done
 
550
                fi
 
551
        } | mail -s "backupninja: $hostname $subject" $reportemail
 
552
fi
 
553
 
 
554
if [ $actions_run != 0 ]; then
 
555
        info "FINISHED: $actions_run actions run. $fatals fatal. $errors error. $warnings warning."
 
556
fi