4 # Full and Incremental backup script with tar and LVM snapshots.
6 # Copyright Amand Tihon, 2008, 2009 <amand.tihon@alrj.org>
7 # Licensed under GNU GPL version 2.0.
9 # http://blog.kagesenshi.org/2008/01/automated-tar-and-dump-incremental.html
11 # Note: This script relies on GNU tar specific options,
12 # do not attempt to use it as-is with other tar implementations.
15 # Example of use, from crontab
16 # # Daily incremental backup
17 # 30 3 * * 1-6 /usr/local/bin/brioche > /var/log/backup.`date "+%a"`.log 2>&1
18 # # Weekly full backup on Sunday
19 # 30 3 * * 0 /usr/local/bin/brioche -f > /var/log/backup.`date "+%a"`.log 2>&1
23 CONFIG_FILE="/etc/brioche.conf"
26 #######################################################################
27 # Default config values. Do not change, edit $CONFIG_FILE
28 #######################################################################
30 BACKUPTAB="/etc/briochetab"
33 TAR_OPTS="--one-file-system -S "
36 SNAPSHOT_MOUNTPOINT="/mnt/backup-snapshot"
37 SNAPSHOT_NAME="backup-snap"
41 # TODO: Implement the storage on a distant FTP
43 #FTP_HOST="ftpback.example.com"
48 # Ensure that we have a minimal PATH
49 PATH=/sbin:/bin:/usr/sbin:/usr/bin
50 FINAL_STATUS="SUCCESS"
51 SUMMARY="/tmp/backup.sumary"
54 #######################################################################
56 #######################################################################
58 trap 'interrupted ctrl-C' INT
59 trap 'interrupted KILL' TERM
63 echo -n `date "+%Y-%m-%d %H:%M:%S"`
66 # Log a line with timestamp.
72 # The summary that will be sent by email
82 local usage=`df -hP "${REPODIR}" | tail -n 1 | tr -s [:space:] | cut -d" " -f5 | tr -d '%'`
83 log "${REPODIR} usage after backup: ${usage}%."
85 if [ "$usage" -ge "$USAGE_WARN" ]; then
86 if [ "$FINAL_STATUS" = "SUCCESS" ]; then
87 FINAL_STATUS="WARNING"
89 summary "Warning : Filesystem ${REPODIR} is ${usage}% full."
92 summary "Backup procedure ended on `NOW`"
94 SUBJECT="Backup report for `hostname` (${FINAL_STATUS})."
95 mail -s "${SUBJECT}" "${MAILTO}" < "$SUMMARY"
103 FINAL_STATUS="INTERRUPTED"
104 summary "Backup procedure interrupted by user ($1)."
105 summary "Take care, an LVM snapshot may still be present !"
109 #######################################################################
111 #######################################################################
113 # Make a snapshot of a logical volume.
114 # Usage: make_snapshot vg lv
117 log "Creating a snapshot volume of /dev/$1/$2"
118 lvcreate --snapshot -L ${SNAPSHOT_SIZE} -n ${SNAPSHOT_NAME} /dev/$1/$2
120 if [ "$RETVAL" != "0" ]; then
121 log "Error ${RETVAL}: Unable to create a snapshot of /dev/$1/$2"
126 # Mount the snapshot volume.
127 # On error, tries to remove the snapshot.
128 # Usage: mount_snapshot vg
131 log "Mounting the snapshot volume ${SNAPSHOT_NAME}."
132 if [ ! -d "${SNAPSHOT_MOUNTPOINT}" ]; then
133 log "Creating mountpoint ${SNAPSHOT_MOUNTPOINT}."
134 mkdir -p "${SNAPSHOT_MOUNTPOINT}"
137 mount /dev/$1/${SNAPSHOT_NAME} ${SNAPSHOT_MOUNTPOINT}
139 if [ "$RETVAL" != "0" ]; then
140 log "Error ${RETVAL}: Unable to mount /dev/$1/${SNAPSHOT_NAME} on ${SNAPSHOT_MOUNTPOINT}"
141 remove_snapshot $1 || return 100
146 # Remove a previously created snapshot. It must be unmounted.
147 # Usage: remove_snapshot vg
150 log "Removing the snapshot volume /dev/$1/${SNAPSHOT_NAME}."
151 lvremove -f /dev/$1/${SNAPSHOT_NAME}
153 if [ "$RETVAL" != "0" ]; then
154 log "Error ${RETVAL}: Unable to remove the snapshot volume /dev/$1/${SNAPSHOT_NAME}"
159 # Unmount the previously mounted snapshot.
160 # Usage: unmount_snapshot vg
163 log "Unmounting the snapshot volume /dev/$1/${SNAPSHOT_NAME}."
164 umount /dev/$1/${SNAPSHOT_NAME}
166 if [ "$RETVAL" != "0" ]; then
167 log "Error ${RETVAL}: Unable to unmount the snapshot volume /dev/$1/${SNAPSHOT_NAME}"
172 #######################################################################
173 # Main backup functions
174 #######################################################################
176 # Make a full backup of a directory, with snar file for subsequent incremental
177 # Usage: make_full_backup source_dir hostname volumename
178 # Returns 0 on success, 1 on error
181 log "Making full backup of $2 - ${3}."
183 local destdir="${REPODIR}/$2"
184 local today=`date "+%Y%m%d"`
185 local destfile="${destdir}/${3}.full.${today}.tar.${COMPRESS}"
186 local destsnar="${destdir}/${3}.full.snar"
188 # Move previous run to the "undo" directory
189 if [ ! -d "${destdir}/undo" ]; then
190 log "Creating undo/ directory."
191 mkdir -p "${destdir}/undo"
193 log "Moving old run into undo/ directory."
194 mv ${destdir}/${3}.* ${destdir}/undo
196 # Do the actual backup. Destination file name and snar are like
197 # /backup/valeron/root.full.20090105.tar.bz2
198 # /backup/valeron/root.full.snar
200 tar -cf ${destfile} ${TAR_OPTS} ${COMPRESS_OPT} -g ${destsnar} $1
202 if [ "$?" = "0" ]; then
203 log "Removing undo/ directory."
204 rm -rf "${destdir}/undo"
206 log "Error $?: Could not archive $2 - $3"
213 # Make an incremental backup of a directory, from full's snar file
214 # Usage: make_incr_backup source_dir hostname volumename
215 # Returns 0 on success, 1 on error, 2 if no previous full is found.
218 log "Making incremental backup of $2 - ${3}."
220 local destdir="${REPODIR}/$2"
221 local today=`date "+%Y%m%d"`
222 local destfile="${destdir}/${3}.incr.${today}.tar.${COMPRESS}"
223 local destsnar="${destdir}/${3}.incr.${today}.snar"
224 local fullsnar="${destdir}/${3}.full.snar"
226 # Test existence of full backup
227 if [ ! -e $fullsnar ]; then
228 log "Could not find catalog ${fullsnar}."
232 # Prepare the copy of the snar file
233 cp $fullsnar $destsnar
235 # Do the actual backup. Destination file name and snar are like
236 # /backup/valeron/root.incr.20090105.tar.bz2
237 # /backup/valeron/root.incr.20090105.snar
239 tar -caf ${destfile} ${TAR_OPTS} ${COMPRESS_OPT} -g ${destsnar} $1
241 if [ ! "$?" = "0" ]; then
242 log "Error $?: Could not archive $hostname - $volumename"
250 #######################################################################
252 #######################################################################
254 # Truncate summary file, start header blurb
256 summary "Backup procedure started on `NOW`"
258 if [ -r "${CONFIG_FILE}" ]
260 source "${CONFIG_FILE}"
262 summary "Error: Unable to read configuration file ${CONFIG_FILE}. Aborting."
263 FINAL_STATUS="CRITICAL"
267 if [ ! -r "${BACKUPTAB}" ]
269 summary "Error: Unable to read ${BACKUPTAB}. Aborting."
270 FINAL_STATUS="CRITICAL"
274 # TODO: Something cleaner, here...
281 echo "Usage: ${0} [-f]"
286 # Discover which COMPRESS_OPT to use, from COMPRESS
289 COMPRESS_OPT="--gzip"
292 COMPRESS_OPT="--bzip2"
295 COMPRESS_OPT="--lzma"
298 summary "Unknown compression method: ${COMPRESS}. Falling back to gzip."
300 COMPRESS_OPT="--gzip"
304 #######################################################################
305 # Parse backuptab file, call backup functions for each line
306 #######################################################################
308 # Ignore empty and commented lines
309 grep -v -E '^[[:space:]]*(#.*)?$' $BACKUPTAB | tr -s [:space:]| while read line
311 # split line in fields
312 device=`echo $line|cut -d" " -f1`
313 dosnap=`echo $line|cut -d" " -f2`
314 group=`echo $line|cut -d" " -f3`
315 volume=`echo $line|cut -d" " -f4`
317 # Make and mount snapshot if needed.
318 if [ "$dosnap" = "yes" ]; then
319 # Split the device to find the VG and LV
320 vg=`echo $device|cut -d"/" -f3`
321 lv=`echo $device|cut -d"/" -f4`
322 make_snapshot $vg $lv
323 if [ "$?" != "0" ]; then
324 summary "Could not take a snapshot of $device"
332 if [ "$RETVAL" != "0" ]; then
333 summary "Could not mount the snapshot of $device"
335 if [ "$RETVAL" = "100" ]; then
336 summary "Could not remove the snapshot !"
337 FINAL_STATUS="CRITICAL"
344 BACKUP_SOURCE="${SNAPSHOT_MOUNTPOINT}"
346 BACKUP_SOURCE="$device"
350 if [ "$DO_FULL_BACKUP" = "no" ]; then
351 make_incr_backup ${BACKUP_SOURCE} $group $volume
353 if [ "$RETVAL" = "0" ]; then
354 summary "INCREMENTAL backup of $device done on `NOW`."
355 elif [ "$RETVAL" = "1" ]; then
356 summary "Error during incremental backup of $device"
358 elif [ "$RETVAL" = "2" ]; then
359 summary "Can't do an incremental backup without a full one being present."
360 summary "Switching to full backup for $device"
361 make_full_backup ${BACKUP_SOURCE} $group $volume
362 if [ "$?" = "0" ]; then
363 summary "FULL backup of $device done on `NOW`."
365 summary "Error during full backup of $device"
369 summary "Unknown error during incremental backup of $device"
372 else # Do a full backup
373 make_full_backup ${BACKUP_SOURCE} $group $volume
374 if [ "$?" = "0" ]; then
375 summary "FULL backup of $device done on `NOW`."
377 summary "Error during full backup of $device"
382 # Time to unmount and destroy the snapshot, if needed.
383 # Any error here is critical, since the snapshot and the mountpoint are
384 # the same for each device...
385 if [ "$dosnap" = "yes" ]; then
387 if [ "$?" != "0" ]; then
388 summary "Could not unmount snapshot !"
389 FINAL_STATUS="CRITICAL"
394 if [ "$?" != "0" ]; then
395 summary "Could not destroy the snapshot !"
396 FINAL_STATUS="CRITICAL"
404 ## Example Backup description table
406 ## Can handle logical volumes, with snapshots, or plain mountpoints.
407 ## Priority is not used yet.
409 ## Partition or LV Snapshot Host name Volume name Priority
410 ## ----------------------------------------------------------------------------
412 #/dev/vg00/kadarin-root yes kadarin root 1
413 #/dev/vg00/valeron-root yes valeron root 1
414 #/dev/vg00/syrtis-root yes syrtis root 1
415 #/dev/vg00/syrtis-home yes syrtis home 1
416 #/dev/vg00/syrtis-usr yes syrtis usr 1
417 #/dev/vg00/syrtis-var yes syrtis var 1
418 #/dev/vg00/syrtis-srv yes syrtis srv 1