📄 nightly-backup.sh
字号:
#!/bin/bash# nightly-backup.sh# http://www.richardneill.org/source.php#nightly-backup-rsync# Copyright (c) 2005 Richard Neill <backup@richardneill.org>.# This is Free Software licensed under the GNU GPL.# ==> Included in ABS Guide with script author's kind permission.# ==> (Thanks!)# This does a backup from the host computer to a locally connected#+ firewire HDD using rsync and ssh.# It then rotates the backups.# Run it via cron every night at 5am.# This only backs up the home directory.# If ownerships (other than the user's) should be preserved,#+ then run the rsync process as root (and re-instate the -o).# We save every day for 7 days, then every week for 4 weeks,#+ then every month for 3 months.# See: http://www.mikerubel.org/computers/rsync_snapshots/#+ for more explanation of the theory.# Save as: $HOME/bin/nightly-backup_firewire-hdd.sh# Known bugs:# ----------# i) Ideally, we want to exclude ~/.tmp and the browser caches.# ii) If the user is sitting at the computer at 5am,#+ and files are modified while the rsync is occurring,#+ then the BACKUP_JUSTINCASE branch gets triggered.# To some extent, this is a #+ feature, but it also causes a "disk-space leak".##### BEGIN CONFIGURATION SECTION ############################################LOCAL_USER=rjn # User whose home directory should be backed up.MOUNT_POINT=/backup # Mountpoint of backup drive. # NO trailing slash! # This must be unique (eg using a udev symlink)SOURCE_DIR=/home/$LOCAL_USER # NO trailing slash - it DOES matter to rsync.BACKUP_DEST_DIR=$MOUNT_POINT/backup/`hostname -s`.${LOCAL_USER}.nightly_backupDRY_RUN=false #If true, invoke rsync with -n, to do a dry run. # Comment out or set to false for normal use.VERBOSE=false # If true, make rsync verbose. # Comment out or set to false otherwise.COMPRESS=false # If true, compress. # Good for internet, bad on LAN. # Comment out or set to false otherwise.### Exit Codes ###E_VARS_NOT_SET=64E_COMMANDLINE=65E_MOUNT_FAIL=70E_NOSOURCEDIR=71E_UNMOUNTED=72E_BACKUP=73##### END CONFIGURATION SECTION ############################################### Check that all the important variables have been set:if [ -z "$LOCAL_USER" ] || [ -z "$SOURCE_DIR" ] || [ -z "$MOUNT_POINT" ] || [ -z "$BACKUP_DEST_DIR" ]then echo 'One of the variables is not set! Edit the file: $0. BACKUP FAILED.' exit $E_VARS_NOT_SETfiif [ "$#" != 0 ] # If command-line param(s) . . .then # Here document(ation). cat <<-ENDOFTEXT Automatic Nightly backup run from cron. Read the source for more details: $0 The backup directory is $BACKUP_DEST_DIR . It will be created if necessary; initialisation is no longer required. WARNING: Contents of $BACKUP_DEST_DIR are rotated. Directories named 'backup.\$i' will eventually be DELETED. We keep backups from every day for 7 days (1-8), then every week for 4 weeks (9-12), then every month for 3 months (13-15). You may wish to add this to your crontab using 'crontab -e' # Back up files: $SOURCE_DIR to $BACKUP_DEST_DIR #+ every night at 3:15 am 15 03 * * * /home/$LOCAL_USER/bin/nightly-backup_firewire-hdd.sh Don't forget to verify the backups are working, especially if you don't read cron's mail!" ENDOFTEXT exit $E_COMMANDLINEfi# Parse the options.# ==================if [ "$DRY_RUN" == "true" ]; then DRY_RUN="-n" echo "WARNING:" echo "THIS IS A 'DRY RUN'!" echo "No data will actually be transferred!"else DRY_RUN=""fiif [ "$VERBOSE" == "true" ]; then VERBOSE="-v"else VERBOSE=""fiif [ "$COMPRESS" == "true" ]; then COMPRESS="-z"else COMPRESS=""fi# Every week (actually of 8 days) and every month,#+ extra backups are preserved.DAY_OF_MONTH=`date +%d` # Day of month (01..31).if [ $DAY_OF_MONTH = 01 ]; then # First of month. MONTHSTART=trueelif [ $DAY_OF_MONTH = 08 \ -o $DAY_OF_MONTH = 16 \ -o $DAY_OF_MONTH = 24 ]; then # Day 8,16,24 (use 8, not 7 to better handle 31-day months) WEEKSTART=truefi# Check that the HDD is mounted.# At least, check that *something* is mounted here!# We can use something unique to the device, rather than just guessing#+ the scsi-id by having an appropriate udev rule in#+ /etc/udev/rules.d/10-rules.local#+ and by putting a relevant entry in /etc/fstab.# Eg: this udev rule:# BUS="scsi", KERNEL="sd*", SYSFS{vendor}="WDC WD16",# SYSFS{model}="00JB-00GVA0 ", NAME="%k", SYMLINK="lacie_1394d%n"if mount | grep $MOUNT_POINT >/dev/null; then echo "Mount point $MOUNT_POINT is indeed mounted. OK"else echo -n "Attempting to mount $MOUNT_POINT..." # If it isn't mounted, try to mount it. sudo mount $MOUNT_POINT 2>/dev/null if mount | grep $MOUNT_POINT >/dev/null; then UNMOUNT_LATER=TRUE echo "OK" # Note: Ensure that this is also unmounted #+ if we exit prematurely with failure. else echo "FAILED" echo -e "Nothing is mounted at $MOUNT_POINT. BACKUP FAILED!" exit $E_MOUNT_FAIL fifi# Check that source dir exists and is readable.if [ ! -r $SOURCE_DIR ] ; then echo "$SOURCE_DIR does not exist, or cannot be read. BACKUP FAILED." exit $E_NOSOURCEDIRfi# Check that the backup directory structure is as it should be.# If not, create it.# Create the subdirectories.# Note that backup.0 will be created as needed by rsync.for ((i=1;i<=15;i++)); do if [ ! -d $BACKUP_DEST_DIR/backup.$i ]; then if /bin/mkdir -p $BACKUP_DEST_DIR/backup.$i ; then # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ No [ ] test brackets. Why? echo "Warning: directory $BACKUP_DEST_DIR/backup.$i is missing," echo "or was not initialised. (Re-)creating it." else echo "ERROR: directory $BACKUP_DEST_DIR/backup.$i" echo "is missing and could not be created." if [ "$UNMOUNT_LATER" == "TRUE" ]; then # Before we exit, unmount the mount point if necessary. cd sudo umount $MOUNT_POINT && echo "Unmounted $MOUNT_POINT again. Giving up." fi exit $E_UNMOUNTED fifidone# Set the permission to 700 for security#+ on an otherwise permissive multi-user system.if ! /bin/chmod 700 $BACKUP_DEST_DIR ; then echo "ERROR: Could not set permissions on $BACKUP_DEST_DIR to 700." if [ "$UNMOUNT_LATER" == "TRUE" ]; then # Before we exit, unmount the mount point if necessary. cd ; sudo umount $MOUNT_POINT \ && echo "Unmounted $MOUNT_POINT again. Giving up." fi exit $E_UNMOUNTEDfi# Create the symlink: current -> backup.1 if required.# A failure here is not critical.cd $BACKUP_DEST_DIRif [ ! -h current ] ; then if ! /bin/ln -s backup.1 current ; then echo "WARNING: could not create symlink current -> backup.1" fifi# Now, do the rsync.echo "Now doing backup with rsync..."echo "Source dir: $SOURCE_DIR"echo -e "Backup destination dir: $BACKUP_DEST_DIR\n"/usr/bin/rsync $DRY_RUN $VERBOSE -a -S --delete --modify-window=60 \--link-dest=../backup.1 $SOURCE_DIR $BACKUP_DEST_DIR/backup.0/# Only warn, rather than exit if the rsync failed,#+ since it may only be a minor problem.# E.g., if one file is not readable, rsync will fail.# This shouldn't prevent the rotation.# Not using, e.g., `date +%a` since these directories#+ are just full of links and don't consume *that much* space.if [ $? != 0 ]; then BACKUP_JUSTINCASE=backup.`date +%F_%T`.justincase echo "WARNING: the rsync process did not entirely succeed." echo "Something might be wrong." echo "Saving an extra copy at: $BACKUP_JUSTINCASE" echo "WARNING: if this occurs regularly, a LOT of space will be consumed," echo "even though these are just hard-links!"fi# Save a readme in the backup parent directory.# Save another one in the recent subdirectory.echo "Backup of $SOURCE_DIR on `hostname` was last run on \`date`" > $BACKUP_DEST_DIR/README.txtecho "This backup of $SOURCE_DIR on `hostname` was created on \`date`" > $BACKUP_DEST_DIR/backup.0/README.txt# If we are not in a dry run, rotate the backups.[ -z "$DRY_RUN" ] && # Check how full the backup disk is. # Warn if 90%. if 98% or more, we'll probably fail, so give up. # (Note: df can output to more than one line.) # We test this here, rather than before #+ so that rsync may possibly have a chance. DISK_FULL_PERCENT=`/bin/df $BACKUP_DEST_DIR | tr "\n" ' ' | awk '{print $12}' | grep -oE [0-9]+ ` echo "Disk space check on backup partition \ $MOUNT_POINT $DISK_FULL_PERCENT% full." if [ $DISK_FULL_PERCENT -gt 90 ]; then echo "Warning: Disk is greater than 90% full." fi if [ $DISK_FULL_PERCENT -gt 98 ]; then echo "Error: Disk is full! Giving up." if [ "$UNMOUNT_LATER" == "TRUE" ]; then # Before we exit, unmount the mount point if necessary. cd; sudo umount $MOUNT_POINT && echo "Unmounted $MOUNT_POINT again. Giving up." fi exit $E_UNMOUNTED fi # Create an extra backup. # If this copy fails, give up. if [ -n "$BACKUP_JUSTINCASE" ]; then if ! /bin/cp -al $BACKUP_DEST_DIR/backup.0 \ $BACKUP_DEST_DIR/$BACKUP_JUSTINCASE then echo "ERROR: Failed to create extra copy \ $BACKUP_DEST_DIR/$BACKUP_JUSTINCASE" if [ "$UNMOUNT_LATER" == "TRUE" ]; then # Before we exit, unmount the mount point if necessary. cd ;sudo umount $MOUNT_POINT && echo "Unmounted $MOUNT_POINT again. Giving up." fi exit $E_UNMOUNTED fi fi # At start of month, rotate the oldest 8. if [ "$MONTHSTART" == "true" ]; then echo -e "\nStart of month. \ Removing oldest backup: $BACKUP_DEST_DIR/backup.15" && /bin/rm -rf $BACKUP_DEST_DIR/backup.15 && echo "Rotating monthly,weekly backups: \ $BACKUP_DEST_DIR/backup.[8-14] -> $BACKUP_DEST_DIR/backup.[9-15]" && /bin/mv $BACKUP_DEST_DIR/backup.14 $BACKUP_DEST_DIR/backup.15 && /bin/mv $BACKUP_DEST_DIR/backup.13 $BACKUP_DEST_DIR/backup.14 && /bin/mv $BACKUP_DEST_DIR/backup.12 $BACKUP_DEST_DIR/backup.13 && /bin/mv $BACKUP_DEST_DIR/backup.11 $BACKUP_DEST_DIR/backup.12 && /bin/mv $BACKUP_DEST_DIR/backup.10 $BACKUP_DEST_DIR/backup.11 && /bin/mv $BACKUP_DEST_DIR/backup.9 $BACKUP_DEST_DIR/backup.10 && /bin/mv $BACKUP_DEST_DIR/backup.8 $BACKUP_DEST_DIR/backup.9 # At start of week, rotate the second-oldest 4. elif [ "$WEEKSTART" == "true" ]; then echo -e "\nStart of week. \ Removing oldest weekly backup: $BACKUP_DEST_DIR/backup.12" && /bin/rm -rf $BACKUP_DEST_DIR/backup.12 && echo "Rotating weekly backups: \ $BACKUP_DEST_DIR/backup.[8-11] -> $BACKUP_DEST_DIR/backup.[9-12]" && /bin/mv $BACKUP_DEST_DIR/backup.11 $BACKUP_DEST_DIR/backup.12 && /bin/mv $BACKUP_DEST_DIR/backup.10 $BACKUP_DEST_DIR/backup.11 && /bin/mv $BACKUP_DEST_DIR/backup.9 $BACKUP_DEST_DIR/backup.10 && /bin/mv $BACKUP_DEST_DIR/backup.8 $BACKUP_DEST_DIR/backup.9 else echo -e "\nRemoving oldest daily backup: $BACKUP_DEST_DIR/backup.8" && /bin/rm -rf $BACKUP_DEST_DIR/backup.8 fi && # Every day, rotate the newest 8. echo "Rotating daily backups: \ $BACKUP_DEST_DIR/backup.[1-7] -> $BACKUP_DEST_DIR/backup.[2-8]" && /bin/mv $BACKUP_DEST_DIR/backup.7 $BACKUP_DEST_DIR/backup.8 && /bin/mv $BACKUP_DEST_DIR/backup.6 $BACKUP_DEST_DIR/backup.7 && /bin/mv $BACKUP_DEST_DIR/backup.5 $BACKUP_DEST_DIR/backup.6 && /bin/mv $BACKUP_DEST_DIR/backup.4 $BACKUP_DEST_DIR/backup.5 && /bin/mv $BACKUP_DEST_DIR/backup.3 $BACKUP_DEST_DIR/backup.4 && /bin/mv $BACKUP_DEST_DIR/backup.2 $BACKUP_DEST_DIR/backup.3 && /bin/mv $BACKUP_DEST_DIR/backup.1 $BACKUP_DEST_DIR/backup.2 && /bin/mv $BACKUP_DEST_DIR/backup.0 $BACKUP_DEST_DIR/backup.1 && SUCCESS=trueif [ "$UNMOUNT_LATER" == "TRUE" ]; then # Unmount the mount point if it wasn't mounted to begin with. cd ; sudo umount $MOUNT_POINT && echo "Unmounted $MOUNT_POINT again."fiif [ "$SUCCESS" == "true" ]; then echo 'SUCCESS!' exit 0fi# Should have already exited if backup worked.echo 'BACKUP FAILED! Is this just a dry run? Is the disk full?) 'exit $E_BACKUP
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -