]> arthur.barton.de Git - backup-script.git/blob - bin/backup-script
Fix usage: --dry-run equals -n (not -d)
[backup-script.git] / bin / backup-script
1 #!/bin/bash
2 #
3 # backup-script system for cloning systems using rsync
4 # Copyright (c)2008-2013 Alexander Barton, alex@barton.de
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 # Please read the file COPYING, README and AUTHORS for more information.
11 #
12
13 NAME=`basename $0`
14 CONF_D="/etc/backup-script.d"
15 PIDFILE="/var/run/$NAME.pid"
16
17 DRYRUN=0
18 VERBOSE=0
19
20 export LC_ALL=C
21
22 declare -i count_all=0
23 declare -i count_started=0
24 declare -i count_ok=0
25 declare -i count_ok_vanished=0
26
27 destinations=""
28
29 # Default settings, can be overwritten in backup-script.conf:
30 pre_exec=""
31 post_exec=""
32 default_target=""
33 default_user="root"
34 default_ssh_args_add=""
35 default_rsync_args_add=""
36 default_exclude_args_add=""
37 default_compress=1
38 default_ping=1
39 default_local=0
40 default_generations=0
41
42 Usage() {
43         echo "Usage: $NAME [<options>] [<system> [<system> [...]]]"
44         echo
45         echo "  -p, --progress    Show progress, see rsync(1)."
46         echo "  -n, --dry-run     Test run only, don't copy any data."
47         echo
48         echo "When no <system> is given, all defined systems are used."
49         echo
50         exit 1
51 }
52
53 CleanUp() {
54         if [ -n "$post_exec" ]; then
55                 echo "Executing \"$post_exec\" ..."
56                 sh -c $post_exec
57                 if [ $? -ne 0 ]; then
58                         echo "Warning: post-exec command failed!"
59                 fi
60                 echo
61         fi
62         rm -f "$PIDFILE"
63 }
64
65 GotSignal() {
66         echo
67         echo "--> Got break signal, cleaning up & aborting ..."
68         echo
69         CleanUp
70         echo -n "Aborted: "; date
71         echo
72         exit 9
73 }
74
75 while [ $# -gt 0 ]; do
76         case "$1" in
77           "-n"|"--dry-run")
78                 DRYRUN=1; shift
79                 ;;
80           "-p"|"--progress")
81                 VERBOSE=1; shift
82                 ;;
83           "-"*)
84                 Usage
85                 ;;
86           *)
87                 break
88         esac
89 done
90
91 if [ $# -ge 1 ]; then
92         for s in $@; do
93                 if [ ! -r "${CONF_D}/$s" ]; then
94                         echo "$NAME: Can' read \"${CONF_D}/$s\"!"
95                         exit 1
96                 fi
97                 sys="$sys ${CONF_D}/$s"
98         done
99 else
100         sys=${CONF_D}/*
101 fi
102
103 trap GotSignal SIGINT
104
105 echo -n "Started: "; date
106 echo
107
108 [ -r "${CONF_D}/backup-script.conf" ] && source "${CONF_D}/backup-script.conf"
109
110 # check and create PID file
111 if [ -e "$PIDFILE" ]; then
112         echo "Lockfile \"$PIDFILE\" already exists."
113         echo "Is an other instance still running?"
114         echo
115         echo -n "Aborted: "; date
116         echo
117         exit 3
118 fi
119 touch "$PIDFILE" 2>/dev/null
120 if [ $? -ne 0 ]; then
121         echo "Warning: can't create PID file \"$PIDFILE\"!"
122         echo
123 else
124         echo "$$" >>"$PIDFILE"
125 fi
126
127 if [ -n "$pre_exec" ]; then
128         echo "Executing \"$pre_exec\" ..."
129         sh -c $pre_exec
130         if [ $? -ne 0 ]; then
131                 echo "Error: pre-exec command failed!"; echo
132                 CleanUp
133                 echo "Aborting backup."; echo
134                 exit 2
135         fi
136         sleep 2
137         echo
138 fi
139
140 for f in $sys; do
141         [ -r "$f" -a -f "$f" ] || continue
142
143         fname=`basename $f`
144         case "$fname" in
145                 "backup-script.conf"|*.sh)
146                         continue
147                         ;;
148         esac
149
150         # Set global defaults
151         system="$fname"
152         user="$default_user"
153         target="$default_target"
154         ssh_args_add="$default_ssh_args_add"
155         rsync_args_add="$default_rsync_args_add"
156         exclude_args_add="$default_exclude_args_add"
157         compress="$default_compress"
158         ping="$default_ping"
159         local="$default_local"
160         generations="$default_generations"
161
162         # Read in system configuration file
163         source "$f"
164
165         # Validate configuration
166         [ "$system" = "localhost" -o "$system" = "127.0.0.1" ] && local=1
167
168         [ "$system" = "$fname" ] \
169                 && systxt="\"$system\"" \
170                 || systxt="\"$fname\" [\"$system\"]"
171         [ "$local" -eq 0 ] \
172                 && echo "Working on $systxt ..." \
173                 || echo "Working on $sytxts (local system) ..."
174
175         count_all=$count_all+1
176
177         # Check target directory
178         if [ -z "$target" ]; then
179                 echo "No target directory specified for \"$system\"!? Skipped!"
180                 echo; continue
181         fi
182         if [ ! -d "$target" ]; then
183                 echo "Target \"$target\" is not a directory!? \"$system\" skipped!"
184                 echo; continue
185         fi
186
187         sys_target="$target/$fname"
188         if [ "$DRYRUN" -eq 0 ]; then
189                 mkdir -p "$sys_target" >/dev/null 2>&1
190                 if [ $? -ne 0 ]; then
191                         echo "Can't create \"$sys_target\"!? \"$system\" skipped!"
192                         echo continue
193                 fi
194         fi
195
196         if [ "$local" -eq 0 -a "$ping" -ne 0 ]; then
197                 # Check if system is alive
198                 ping -c 1 "$system" >/dev/null 2>&1
199                 if [ $? -ne 0 ]; then
200                         echo "Host \"$system\" seems not to be alive!? Skipped."
201                         echo; continue
202                 fi
203                 echo "OK, host \"$system\" seems to be alive."
204         fi
205
206         if [ $generations -gt 0 ]; then
207                 # Make sure no old backup is stored in system directory
208                 if [ -e "$sys_target/.stamp" ]; then
209                         # There seems to be a genearation-less backup in the
210                         # target directory!
211                         echo "Target directory \"$sys_target\" seems to be unclean!? \"$system\" skipped!"
212                         echo; continue
213                 fi
214
215                 # Search directory of last generation, if any
216                 last="`ls -1 "$sys_target" 2>/dev/null | sort -r | head -n1`"
217                 if [ -n "$last" ]; then
218                         last="$sys_target/$last"
219                         if [ ! -d "$last" ]; then
220                                 echo "Last snapshot \"$last\" seems not to be a directory!? \"$system\" skipped!"
221                                 echo; continue
222                         fi
223                 fi
224                 sys_target="$sys_target/`date +%Y%m%d-%H%M%S`"
225
226                 if [ -n "$last" -a ! -e "$last/.stamp" ]; then
227                         # Old backup directory without "stamp file", continue
228                         echo "Found incomplete snapshot in \"$last\", reusing and renaming it ..."
229                         mv "$last" "$sys_target" >/dev/null 2>&1
230                         if [ $? -ne 0 ]; then
231                                 echo "Failed to rename last snapshot \"$last\" to \"$sys_target\"!? \"$system\" skipped!"
232                                 echo; continue
233                         fi
234                 elif [ -n "$last" ]; then
235                         # Old backup directory found, create new snapshot
236                         echo "Found last snapshot in \"$last\"."
237                         if [ "$DRYRUN" -eq 0 ]; then
238                                 btrfs subvolume snapshot \
239                                   "$last" "$sys_target" >/dev/null 2>&1; r=$?
240                                 if [ $r -ne 0 ]; then
241                                         echo "Can't create btrfs snapshot \"$sys_target\" of \"$last\", code $r!? \"$system\" skipped!"
242                                         echo; continue
243                                 fi
244                                 echo "Created new snapshot in \"$sys_target\"."
245                         else
246                                 echo " *** Trial run, not creating new snapshot in \"$sys_target\"!"
247                         fi
248                 else
249                         # No old backup found, create new subvolume
250                         if [ "$DRYRUN" -eq 0 ]; then
251                                 btrfs subvolume create \
252                                   "$sys_target" >/dev/null 2>&1; r=$?
253                                 if [ $r -ne 0 ]; then
254                                         echo "Can't create btrfs subvolume \"$sys_target\", code $r!? \"$system\" skipped!"
255                                         echo; continue
256                                 fi
257                                 echo "Created new subvolume in \"$sys_target\"."
258                         else
259                                 echo " *** Trial run, not creating new subvolume \"$sys_target\"!"
260                         fi
261                 fi
262         fi
263
264         ssh_cmd="ssh"
265         [ -n "$ssh_args_add" ] && ssh_cmd="$ssh_cmd $ssh_args_add"
266
267         cmd="rsync --archive"
268         [ "$compress" -ne 0 ] && cmd="$cmd --compress"
269         cmd="$cmd --rsh=\"$ssh_cmd\" --delete --delete-excluded --sparse"
270         [ "$VERBOSE" -gt 0 ] && cmd="$cmd --progress"
271         cmd="$cmd --exclude=/dev --exclude=/proc --exclude=/sys"
272         cmd="$cmd --exclude=/run --exclude=/tmp --exclude=/var/tmp"
273         cmd="$cmd --exclude=/media --exclude=/mnt --exclude=/net"
274         cmd="$cmd --exclude=/var/cache/apt --exclude=/var/log"
275         [ -n "$exclude_args_add" ] && cmd="$cmd $exclude_args_add"
276         [ -n "$rsync_args_add" ] && cmd="$cmd $rsync_args_add"
277
278         [ "$local" -eq 0 ] \
279                 && cmd="$cmd ${user}@${system}:/ $sys_target/" \
280                 || cmd="$cmd / $sys_target/"
281
282         echo "Backing up to \"$sys_target\" ..."
283         echo -n "Start date: "; date
284         echo "$cmd"
285         count_started=$count_started+1
286         
287         if [ "$DRYRUN" -eq 0 ]; then
288                 rm -f "$sys_target/.stamp"
289                 $SHELL -c "$cmd"; ret=$?
290                 echo "code=$ret" >"$sys_target/.stamp"
291         else
292                 echo " *** Trial run, not executing synchronization command!"
293                 ret=0
294         fi
295
296         if [ $ret -eq 20 ]; then
297                 echo "Backup of \"$system\" interrupted. Aborting ..."
298                 CleanUp
299                 exit 1
300         fi
301
302         echo -n "End date: "; date
303         if [ $ret -eq 0 -o $ret -eq 24 ]; then
304                 [ $ret -eq 24 ] && count_ok_vanished=$count_ok_vanished+1
305
306                 echo "System \"$system\" completed with status $ret, OK."
307                 [ "$DRYRUN" -gt 0 ] || count_ok=$count_ok+1
308         else
309                 echo "System \"$system\" completed with ERRORS, code $ret!"
310         fi
311
312         destinations="$destinations $target"
313         echo
314 done
315
316 sync
317
318 paths=$( echo $destinations | sed -e 's/ /\n/g' | sort | uniq )
319 if [ "$DRYRUN" -eq 0 -a -n "$paths" ]; then
320         df -h $paths
321         echo
322 fi
323
324 CleanUp
325
326 echo -n "Done: "; date
327 echo
328 [ $count_all -eq 1 ] && s="" || s="s"
329 echo " - $count_all job$s defined,"
330 [ $count_started -eq 1 ] && s="" || s="s"
331 echo " - $count_started job$s started,"
332 echo " - $count_ok done without errors."
333 echo
334
335 if [ $count_started -ne $count_ok ]; then
336         echo "----->  THERE HAVE BEEN ERRORS!  <-----"
337         echo
338 fi
339
340 # -eof-