Coalesce rsync options, plus misc cleanups
[wizbackup.git] / wizbackup
1 #!/bin/bash
2 #
3 # wizbackup 2.0 - Simple rsync backup
4 # Based on incremental-backup 0.1 by Matteo Mattei
5 #
6 # Copyright 2006 Matteo Mattei <matteo.mattei@gmail.com>
7 # Copyright 2007, 2008, 2009, 2010, 2011 Bernie Innocenti <bernie@codewiz.org>
8 #
9 # This program is free software: you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation, either version 3 of the License,
12 # or (at your option) any later version.
13
14
15 if [ $# -lt 2 ]; then
16         echo "Usage: $0 SOURCE DEST [RSYNC_OPTS]"
17         exit 1
18 fi
19
20 # Fail on any error; Treat undefined variables as errors
21 set -e -u
22
23 #####################################################################
24 # CONFIGURATION
25 #####################################################################
26
27 # Source path
28 SRC=$1; shift
29
30 # DESTINATION DIRECTORY (must exists)
31 DEST=$1; shift
32
33 # NOTE: --timeout needs to be large enough: if a large dir tree don't change a lot of time can pass without I/O
34 # NOTE: --inplace will clobber linked files in older snapshots. DON'T USE IT!
35 RSYNC_OPTS="-HAXa --stats --timeout 1800 --numeric-ids --delete --delete-excluded --ignore-errors $@"
36
37 # Number of saved snapshots
38 SNAPSHOTS=45
39
40 RESULT=500
41 DATE=`date +"%Y%m%d"`
42 DEST="`echo $DEST | sed -e 's/\/$//'`"
43
44
45 # Use "backup" ssh key with ssh protocol, or password file for rsync protocol
46 if [ "${SRC%:*}" == "rsync" ]; then
47         RSYNC_OPTS="$RSYNC_OPTS --password-file=/etc/wizbackup/rsync_password --contimeout 10"
48 else
49         export RSYNC_RSH="ssh -i /etc/wizbackup/ssh_id -c arcfour -x -o VerifyHostKeyDNS=yes -o StrictHostKeyChecking=no"
50 fi
51
52 # Error tolerant grep
53 tgrep()
54 {
55         grep "$@"
56         return 0
57 }
58
59 do_backup()
60 {
61         set -o pipefail
62         echo "$(date): rsync $RSYNC_OPTS $SRC $DEST/tmp/"
63         rsync $RSYNC_OPTS "$SRC" "$DEST/tmp/" 2>&1 | tgrep -v -E 'vanished|some files'
64         RESULT=$?
65         case "$RESULT" in
66         0|24)
67                 RESULT=0 # Ignore error 24 (Partial transfer due to vanished source files)
68                 # Cleanup old incomplete backups
69                 if [ -d "$DEST/$DATE" ]; then
70                         echo "$(date): Removing existing backup $DEST/$DATE"
71                         rm -rf "$DEST/$DATE"
72                 fi
73                 mv "$DEST/tmp" "$DEST/$DATE"
74                 ;;
75         *)
76                 echo "$(date): rsync failed: $RESULT"
77                 echo "$(date): Leaving incomplete backup in $DEST/tmp"
78                 ;;
79         esac
80         set +o pipefail
81 }
82
83
84 do_init()
85 {
86         # Safety net (4 slashes just in case)
87         case "$DEST" in
88                 /|//|///|////) exit 666 ;;
89         esac
90
91         if [ ! -d "$DEST" ]; then
92                 echo "$(date): Creating missing destination directory $DEST."
93                 mkdir -p "$DEST" || exit 667
94         fi
95
96         cd "$DEST" || exit 668
97 }
98
99 do_prune()
100 {
101         local old="`ls | grep -v tmp | head -n -$SNAPSHOTS`"
102         if [ ! -z "$old" ]; then
103                 echo "$(date): Removing oldest snapshot(s): $old..."
104                 rm -rf "$old" || exit 669
105         fi
106 }
107
108 do_link()
109 {
110         local newest=`ls | grep  -v tmp | tail -n 1`
111         if [ -d "$DEST/tmp" ]; then
112                 echo "$(date): Continuing with pre-existing snapshot $DEST/tmp"
113         elif [ -z "$newest" ]; then
114                 echo "$(date): No previous snapshot found, performing a full backup!"
115         else
116                 echo "$(date): Linking snapshot $DEST/$newest to $DEST/tmp"
117                 # TODO: Creating the hardlinks takes a lot of time.
118                 # Perhaps we could save time by recycling the oldest snapshot
119                 cp -lR "$DEST/$newest" "$DEST/tmp" || exit 670
120         fi
121 }
122
123 do_test()
124 {
125         # Avoid clobbering the latest snapshot if the remote host does
126         # not allow us to connect
127         # --contimeout: sometimes hangs on connection...
128         rsync $RSYNC_OPTS --dry-run --no-recursive "$SRC" >/dev/null
129         RESULT=$?
130         if [ $RESULT -ne 0 ]; then
131                 echo "$(date): rsync test failed: $RESULT.  Aborting."
132                 exit $RESULT
133         fi
134 }
135
136 ######################
137 # MAIN
138 ######################
139
140 # make sure to be root
141 if (( `id -u` != 0 )); then { echo "Sorry, must be root.  Exiting..."; exit; } fi
142
143 echo "$(date): START backup: $SRC -> $DEST"
144 do_init
145 do_prune
146 do_test
147 do_link
148 do_backup
149 echo "$(date): END backup: $SRC -> $DEST"
150
151 exit $RESULT