236fb67b1aa0bee5d572e0c3fcb0d6b2a3cbb21e
[matthijs/upstream/backupninja.git] / backupninja
1 #!/bin/bash
2 #                          |\_
3 # B A C K U P N I N J A   /()/
4 #                         `\|
5 #
6 # Copyright (C) 2004 riseup.net -- property is theft.
7 #
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.
12 #
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.
17 #
18
19 #####################################################
20 ## DEFAULTS
21
22 DEBUG=${DEBUG:=0}
23 CONFFILE="/etc/backupninja.conf"
24 USECOLOURS=1
25
26 #####################################################
27 ## FUNCTIONS
28
29 function setupcolors() {
30         if [  "$USECOLOURS" == 1 ]
31         then
32                 BLUE="\033[34;01m"
33                 GREEN="\033[32;01m"
34                 YELLOW="\033[33;01m"
35                 PURPLE="\033[35;01m"
36                 RED="\033[31;01m"
37                 OFF="\033[0m"
38                 CYAN="\033[36;01m"
39         fi
40 }
41
42 function run() {
43         RUNERROR=0
44         debug 0 "$@"
45         returnstring=`$@ 2>&1`
46         RUNERROR=$?
47         RUNERRORS=$[RUNERRORS+RUNERROR]
48         if [ "$RUNERROR" != 0 ]; then
49                 debug 3 "Exitcode $RUNERROR returned when running: $@"
50                 debug 3 "$returnstring"
51         else
52                 debug 0 "$returnstring"
53         fi
54         return $RUNERROR
55 }
56
57 # We have the following debug levels:
58 # 0 - debug - blue
59 # 1 - normal messages - green
60 # 2 - warnings - yellow
61 # 3 - errors - orange
62 # 4 - fatal - red
63 # First variable passed is the error level, all others are printed
64
65 # if 1, echo out all warnings, errors, or fatal
66 # used to capture output from handlers
67 echo_debug_msg=0
68
69 function debug() {
70
71         [ ${#@} -gt 1 ] || return
72          
73         TYPES=(Debug Info Warning Error Fatal)
74         COLOURS=($BLUE $GREEN $YELLOW $RED $PURPLE)
75         type=$1
76         colour=${COLOURS[$type]}
77         shift
78         print=$[4-type]
79         if [ "$print" -lt "$loglevel" -o "$DEBUG" == 1 ]; then
80                 if [ -z "$logfile" ]; then
81                         echo -e "${colour}${TYPES[$type]}: $@${OFF}" >&2
82                 else
83                         if [ "$DEBUG" == 1 -o "$type" == 4 ]; then
84                                 echo -e "${colour}${TYPES[$type]}: $@${OFF}" >&2
85                         fi
86                         echo -e "${colour}${TYPES[$type]}: $@${OFF}" >> $logfile
87                 fi
88         fi
89         if [ "$echo_debug_msg" != "0" -a "$type" -gt "1" ]; then
90                 echo -e "${TYPES[$type]}: $@"
91         fi
92 }
93
94 function fatal() {
95         debug 4 "$@"
96         exit 2
97 }
98
99 msgcount=0
100 function msg {
101         messages[$msgcount]=$1
102         let "msgcount += 1"
103 }
104
105 function setfile() {
106         CURRENT_CONF_FILE=$1
107 }
108
109 function setsection() {
110         CURRENT_SECTION=$1
111 }
112
113 #
114 # sets a global var with name equal to $1
115 # to the value of the configuration parameter $1
116 # $2 is the default.
117
118
119 function getconf() {
120         CURRENT_PARAM=$1
121         ret=`awk -f $scriptdir/parseini S=$CURRENT_SECTION P=$CURRENT_PARAM $CURRENT_CONF_FILE`
122         # if nothing is returned, set the default
123         if [ "$ret" == "" -a "$2" != "" ]; then
124                 ret="$2"
125         fi
126
127         # replace * with %, so that it is not globbed.
128         ret="${ret//\\*/__star__}"
129
130         # this is weird, but single quotes are needed to 
131         # allow for returned values with spaces. $ret is still expanded
132         # because it is in an 'eval' statement.
133         eval $1='$ret'
134 }
135
136
137 #####################################################
138 ## MAIN
139
140 ## process command line options
141
142 if [ "$1" == "--help" ]; then
143         HELP=1;DEBUG=1;loglevel=4
144 else
145         while getopts h,f:,d,t option
146         do
147                 case "$option" in
148                         h) HELP=1;DEBUG=1;loglevel=4;;
149                         d) DEBUG=1;loglevel=4;;
150                         f) CONFFILE="$OPTARG";;
151                         t) test=1;DEBUG=1;;
152                 esac
153         done
154 fi
155         
156 setupcolors
157
158 ## Print help
159
160 if [ "$HELP" == 1 ]; then
161 cat << EOF
162 $0 usage:
163 This script allows you to coordinate system backup by dropping a few
164 simple configuration files into /etc/backup.d/. In general, this script
165 is run from a cron job late at night. 
166
167 The following options are available:
168 -h         This help message
169 -d         Run in debug mode, where all log messages are output to the current shell.
170 -f <file>  Use <file> for the main configuration instead of /etc/backupninja.conf
171 -t         Run in test mode, no actions are actually taken.
172
173 When using colored output, there are:
174 EOF
175 debug 0 "Debugging info (when run with -d)"
176 debug 1 "Informational messages (verbosity level 4)"
177 debug 2 "Warnings (verbosity level 3 and up)"
178 debug 3 "Errors (verbosity level 2 and up)"
179 debug 4 "Fatal, halting errors (always shown)"
180 exit 0
181 fi
182
183 ## Load and confirm basic configuration values
184
185 # bootstrap
186 [ -r "$CONFFILE" ] || fatal "Configuration file $CONFFILE not found."
187 scriptdir=`grep scriptdirectory $CONFFILE | awk '{print $3}'`
188 [ -n "$scriptdir" ] || fatal "Cound not find entry 'scriptdirectory' in $CONFFILE."
189 [ -d "$scriptdir" ] || fatal "Script directory $scriptdir not found."
190 setfile $CONFFILE
191
192 # get global config options (second param is the default)
193 getconf configdirectory /etc/backup.d
194 getconf reportemail
195 getconf loglevel 3
196 getconf logfile /var/log/backupninja.log
197 getconf SLAPCAT /usr/sbin/slapcat
198 getconf RDIFFBACKUP /usr/bin/rdiff-backup
199 getconf MYSQL /usr/bin/mysql
200 getconf MYSQLHOTCOPY /usr/bin/mysqlhotcopy
201 getconf MYSQLDUMP /usr/bin/mysqldump
202 getconf GZIP /bin/gzip
203
204 [ -d "$configdirectory" ] || fatal "Configuration directory '$configdirectory' not found."
205 [ `id -u` == "0" ] || fatal "Can only be run as root"
206
207 ## Process each configuration file
208
209 debug 1 "====== starting at "`date`" ======"
210
211 # by default, don't make files which are world or group readable.
212 umask 077
213
214 for file in $configdirectory/*; do      
215         perms=`ls -ld $file`
216         perms=${perms:4:6}
217         if [ "$perms" != "------" ]; then
218                 fatal "Configuration files must not be group or world readable! Dying on file $file"
219         fi
220         if [ `ls -ld $file | awk '{print $3}'` != "root" ]; then
221                 fatal "Configuration files must be owned by root! Dying on file $file"
222         fi
223         suffix="${file##*.}"
224         base=`basename $file`
225         if [ "${base:0:1}" == "0" ]; then
226                 debug 1 "Skipping $file"
227                 continue
228         else
229                 debug 1 "Processing $file"
230         fi
231
232         if [ -e "$scriptdir/$suffix" ]; then
233                 setfile $file
234                 echo_debug_msg=1
235                 ret=`( . $scriptdir/$suffix $file )`
236                 retcode="$?"
237                 warnings=`echo $ret | grep -e "^Warning: " | wc -l`
238                 errors=`echo $ret | grep -e "^Error: \|^Fatal: " | wc -l`
239                 if [ $errors != 0 ]; then
240                         msg "*failed* -- $file"
241                         error="$error\n== errors from $file ==\n\n$ret\n"
242                 elif [ $warnings != 0 ]; then
243                         msg "*warning* -- $file"
244                         error="$error\n== warnings from $file ==\n\n$ret\n"
245                 elif [ $retcode == 0 ]; then
246                         msg "success -- $file"
247                 else
248                         msg "unknown -- $file"
249                 fi
250                 echo_debug_msg=0
251         else
252                 debug 3 "Can't process file '$file': no handler script for suffix '$suffix'"
253                 msg "*missing handler* -- $file"
254         fi
255 done
256
257 ## mail the messages to the report address
258
259 if [ "$reportemail" != "" ]; then
260         hostname=`hostname`
261         {
262                 for ((i=0; i < ${#messages[@]} ; i++)); do
263                         echo ${messages[$i]}
264                 done
265                 echo -e "$error"
266         } | mail $reportemail -s "backupninja: $hostname"
267 fi
268
269 debug 1 "====== finished at "`date`" ======"
270
271 ############################################################