4 # Full and Incremental backup script with tar and LVM snapshots.
6 # Copyright (C) 2008, 2009 Amand Tihon <amand.tihon@alrj.org>
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 3 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.
18 # You should have received a copy of the GNU General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
21 # Note: This script relies on GNU tar specific options,
22 # do not attempt to use it as-is with other tar implementations.
25 # Example of use, from crontab
26 # # Daily incremental backup
27 # 30 3 * * 1-6 /usr/local/bin/brioche > /var/log/backup.`date "+%a"`.log 2>&1
28 # # Weekly full backup on Sunday
29 # 30 3 * * 0 /usr/local/bin/brioche -f > /var/log/backup.`date "+%a"`.log 2>&1
33 CONFIG_FILE="/etc/brioche.conf"
36 #######################################################################
37 # Default config values. Do not change, edit $CONFIG_FILE
38 #######################################################################
40 BACKUPTAB="/etc/briochetab"
43 TAR_OPTS="--one-file-system -S "
46 SNAPSHOT_MOUNTPOINT="/mnt/backup-snapshot"
47 SNAPSHOT_NAME="backup-snap"
51 # TODO: Implement the storage on a distant FTP
53 #FTP_HOST="ftpback.example.com"
58 # Ensure that we have a minimal PATH
59 PATH=/sbin:/bin:/usr/sbin:/usr/bin
60 FINAL_STATUS="SUCCESS"
61 SUMMARY="/tmp/backup.sumary"
64 #######################################################################
66 #######################################################################
68 trap 'interrupted ctrl-C' INT
69 trap 'interrupted KILL' TERM
73 echo -n `date "+%Y-%m-%d %H:%M:%S"`
76 # Log a line with timestamp.
82 # The summary that will be sent by email
92 local usage=`df -hP "${REPODIR}" | tail -n 1 | tr -s [:space:] | cut -d" " -f5 | tr -d '%'`
93 log "${REPODIR} usage after backup: ${usage}%."
95 if [ "$usage" -ge "$USAGE_WARN" ]; then
96 if [ "$FINAL_STATUS" = "SUCCESS" ]; then
97 FINAL_STATUS="WARNING"
99 summary "Warning : Filesystem ${REPODIR} is ${usage}% full."
102 summary "Backup procedure ended on `NOW`"
104 SUBJECT="Backup report for `hostname` (${FINAL_STATUS})."
105 mail -s "${SUBJECT}" "${MAILTO}" < "$SUMMARY"
113 FINAL_STATUS="INTERRUPTED"
114 summary "Backup procedure interrupted by user ($1)."
115 summary "Take care, an LVM snapshot may still be present !"
119 #######################################################################
121 #######################################################################
123 # Make a snapshot of a logical volume.
124 # Usage: make_snapshot vg lv
127 log "Creating a snapshot volume of /dev/$1/$2"
128 lvcreate --snapshot -L ${SNAPSHOT_SIZE} -n ${SNAPSHOT_NAME} /dev/$1/$2
130 if [ "$RETVAL" != "0" ]; then
131 log "Error ${RETVAL}: Unable to create a snapshot of /dev/$1/$2"
136 # Mount the snapshot volume.
137 # On error, tries to remove the snapshot.
138 # Usage: mount_snapshot vg
141 log "Mounting the snapshot volume ${SNAPSHOT_NAME}."
142 if [ ! -d "${SNAPSHOT_MOUNTPOINT}" ]; then
143 log "Creating mountpoint ${SNAPSHOT_MOUNTPOINT}."
144 mkdir -p "${SNAPSHOT_MOUNTPOINT}"
147 mount /dev/$1/${SNAPSHOT_NAME} ${SNAPSHOT_MOUNTPOINT}
149 if [ "$RETVAL" != "0" ]; then
150 log "Error ${RETVAL}: Unable to mount /dev/$1/${SNAPSHOT_NAME} on ${SNAPSHOT_MOUNTPOINT}"
151 remove_snapshot $1 || return 100
156 # Remove a previously created snapshot. It must be unmounted.
157 # Usage: remove_snapshot vg
160 log "Removing the snapshot volume /dev/$1/${SNAPSHOT_NAME}."
161 lvremove -f /dev/$1/${SNAPSHOT_NAME}
163 if [ "$RETVAL" != "0" ]; then
164 log "Error ${RETVAL}: Unable to remove the snapshot volume /dev/$1/${SNAPSHOT_NAME}"
169 # Unmount the previously mounted snapshot.
170 # Usage: unmount_snapshot vg
173 log "Unmounting the snapshot volume /dev/$1/${SNAPSHOT_NAME}."
174 umount /dev/$1/${SNAPSHOT_NAME}
176 if [ "$RETVAL" != "0" ]; then
177 log "Error ${RETVAL}: Unable to unmount the snapshot volume /dev/$1/${SNAPSHOT_NAME}"
182 #######################################################################
183 # Main backup functions
184 #######################################################################
186 # Make a full backup of a directory, with snar file for subsequent incremental
187 # Usage: make_full_backup source_dir hostname volumename
188 # Returns 0 on success, 1 on error
191 log "Making full backup of $2 - ${3}."
193 local destdir="${REPODIR}/$2"
194 local today=`date "+%Y%m%d"`
195 local destfile="${destdir}/${3}.full.${today}.tar.${COMPRESS}"
196 local destsnar="${destdir}/${3}.full.snar"
198 # Move previous run to the "undo" directory
199 if [ ! -d "${destdir}/undo" ]; then
200 log "Creating undo/ directory."
201 mkdir -p "${destdir}/undo"
203 log "Moving old run into undo/ directory."
204 mv ${destdir}/${3}.* ${destdir}/undo
206 # Do the actual backup. Destination file name and snar are like
207 # /backup/valeron/root.full.20090105.tar.bz2
208 # /backup/valeron/root.full.snar
210 tar -cf ${destfile} ${TAR_OPTS} ${COMPRESS_OPT} -g ${destsnar} $1
212 if [ "$?" = "0" ]; then
213 log "Removing undo/ directory."
214 rm -rf "${destdir}/undo"
216 log "Error $?: Could not archive $2 - $3"
223 # Make an incremental backup of a directory, from full's snar file
224 # Usage: make_incr_backup source_dir hostname volumename
225 # Returns 0 on success, 1 on error, 2 if no previous full is found.
228 log "Making incremental backup of $2 - ${3}."
230 local destdir="${REPODIR}/$2"
231 local today=`date "+%Y%m%d"`
232 local destfile="${destdir}/${3}.incr.${today}.tar.${COMPRESS}"
233 local destsnar="${destdir}/${3}.incr.${today}.snar"
234 local fullsnar="${destdir}/${3}.full.snar"
236 # Test existence of full backup
237 if [ ! -e $fullsnar ]; then
238 log "Could not find catalog ${fullsnar}."
242 # Prepare the copy of the snar file
243 cp $fullsnar $destsnar
245 # Do the actual backup. Destination file name and snar are like
246 # /backup/valeron/root.incr.20090105.tar.bz2
247 # /backup/valeron/root.incr.20090105.snar
249 tar -caf ${destfile} ${TAR_OPTS} ${COMPRESS_OPT} -g ${destsnar} $1
251 if [ ! "$?" = "0" ]; then
252 log "Error $?: Could not archive $hostname - $volumename"
260 #######################################################################
262 #######################################################################
264 # Truncate summary file, start header blurb
266 summary "Backup procedure started on `NOW`"
268 if [ -r "${CONFIG_FILE}" ]
270 source "${CONFIG_FILE}"
272 summary "Error: Unable to read configuration file ${CONFIG_FILE}. Aborting."
273 FINAL_STATUS="CRITICAL"
277 if [ ! -r "${BACKUPTAB}" ]
279 summary "Error: Unable to read ${BACKUPTAB}. Aborting."
280 FINAL_STATUS="CRITICAL"
284 # TODO: Something cleaner, here...
291 echo "Usage: ${0} [-f]"
296 # Discover which COMPRESS_OPT to use, from COMPRESS
299 COMPRESS_OPT="--gzip"
302 COMPRESS_OPT="--bzip2"
305 COMPRESS_OPT="--lzma"
308 summary "Unknown compression method: ${COMPRESS}. Falling back to gzip."
310 COMPRESS_OPT="--gzip"
314 #######################################################################
315 # Parse backuptab file, call backup functions for each line
316 #######################################################################
318 # Ignore empty and commented lines
319 grep -v -E '^[[:space:]]*(#.*)?$' $BACKUPTAB | tr -s [:space:]| while read line
321 # split line in fields
322 device=`echo $line|cut -d" " -f1`
323 dosnap=`echo $line|cut -d" " -f2`
324 group=`echo $line|cut -d" " -f3`
325 volume=`echo $line|cut -d" " -f4`
327 # Make and mount snapshot if needed.
328 if [ "$dosnap" = "yes" ]; then
329 # Split the device to find the VG and LV
330 vg=`echo $device|cut -d"/" -f3`
331 lv=`echo $device|cut -d"/" -f4`
332 make_snapshot $vg $lv
333 if [ "$?" != "0" ]; then
334 summary "Could not take a snapshot of $device"
342 if [ "$RETVAL" != "0" ]; then
343 summary "Could not mount the snapshot of $device"
345 if [ "$RETVAL" = "100" ]; then
346 summary "Could not remove the snapshot !"
347 FINAL_STATUS="CRITICAL"
354 BACKUP_SOURCE="${SNAPSHOT_MOUNTPOINT}"
356 BACKUP_SOURCE="$device"
360 if [ "$DO_FULL_BACKUP" = "no" ]; then
361 make_incr_backup ${BACKUP_SOURCE} $group $volume
363 if [ "$RETVAL" = "0" ]; then
364 summary "INCREMENTAL backup of $device done on `NOW`."
365 elif [ "$RETVAL" = "1" ]; then
366 summary "Error during incremental backup of $device"
368 elif [ "$RETVAL" = "2" ]; then
369 summary "Can't do an incremental backup without a full one being present."
370 summary "Switching to full backup for $device"
371 make_full_backup ${BACKUP_SOURCE} $group $volume
372 if [ "$?" = "0" ]; then
373 summary "FULL backup of $device done on `NOW`."
375 summary "Error during full backup of $device"
379 summary "Unknown error during incremental backup of $device"
382 else # Do a full backup
383 make_full_backup ${BACKUP_SOURCE} $group $volume
384 if [ "$?" = "0" ]; then
385 summary "FULL backup of $device done on `NOW`."
387 summary "Error during full backup of $device"
392 # Time to unmount and destroy the snapshot, if needed.
393 # Any error here is critical, since the snapshot and the mountpoint are
394 # the same for each device...
395 if [ "$dosnap" = "yes" ]; then
397 if [ "$?" != "0" ]; then
398 summary "Could not unmount snapshot !"
399 FINAL_STATUS="CRITICAL"
404 if [ "$?" != "0" ]; then
405 summary "Could not destroy the snapshot !"
406 FINAL_STATUS="CRITICAL"
414 ## Example Backup description table
416 ## Can handle logical volumes, with snapshots, or plain mountpoints.
417 ## Priority is not used yet.
419 ## Partition or LV Snapshot Host name Volume name Priority
420 ## ----------------------------------------------------------------------------
422 #/dev/vg00/kadarin-root yes kadarin root 1
423 #/dev/vg00/valeron-root yes valeron root 1
424 #/dev/vg00/syrtis-root yes syrtis root 1
425 #/dev/vg00/syrtis-home yes syrtis home 1
426 #/dev/vg00/syrtis-usr yes syrtis usr 1
427 #/dev/vg00/syrtis-var yes syrtis var 1
428 #/dev/vg00/syrtis-srv yes syrtis srv 1