Last active
January 7, 2022 12:46
-
-
Save ProBackup-nl/82fb02c67dc05ca0310d0f9f8e8be14c to your computer and use it in GitHub Desktop.
/opt and /etc/storage backup utility for Padavan firmware using rsync
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/sh | |
| if [ $# = 0 ] || [ $1 = -h ]; then cat <<EOF | |
| -------------------------------------------------------------------------- | |
| Pro Backup's handy rotating-filesystem-backup utility for Padavan firmware | |
| -------------------------------------------------------------------------- | |
| It makes rotating backup-snapshots of /opt and /etc/storage when called | |
| to a disk that must be prepared with label "Backup". (mkfs.ext4 -L Backup) | |
| Installation: # curl -O https://gist.githubusercontent.com/ProBackup-nl/82fb02c67dc05ca0310d0f9f8e8be14c/raw/68bc28dbdcd7815feb6b5c2715ff121c44e03575/backup.sh && chmod 755 backup.sh && mkdir /opt/usr/sbin && mv backup.sh /opt/usr/sbin/backup | |
| Goals: 1. many redundant copies to defeat (flash) bit rot -> no hard links (yet) | |
| 2. reduce i/o -> no full copies on every run -> rsync | |
| By default there are 5 rotation groups: 7 years [-y --yearly], 6 months [-m --monthly], 4 weeks [-w --weekly], 7 days [-d --daily], 2 once/manual [-o --once] | |
| Auto run **only** once a day, start individual jobs each 1 minute apart. For example starting 03:01 : | |
| EOF | |
| readonly ZERO="$( cd "`/usr/bin/dirname "$0"`" ; pwd -P )/`/usr/bin/basename $0`" | |
| cat <<EOF | |
| 1 3 $(( `date +%d` + 1)) `date +%m` * $ZERO --yearly | |
| 2 3 $(( `date +%d` + 1)) * * $ZERO --monthly | |
| 3 3 * * $(( `date +%w` + 1 )) $ZERO --weekly | |
| 4 3 * * * $ZERO --daily | |
| Bi-monthly instead of monthly: | |
| 1 3 $(( `date +%d` + 1)) `date +%m` * $ZERO --yearly | |
| 2 3 $(( `date +%d` + 1)) */2 * $ZERO --monthly | |
| 3 3 * * $(( `date +%w` + 1 )) $ZERO --weekly | |
| 4 3 * * * $ZERO --daily | |
| -y assumes -m, -w, -d; will run for at least 4 minutes to prevent 1 minute later started duplicates | |
| -m assumes -w, -d; will run for at least 3 minutes | |
| -w assumes -d; will run for at least 2 minutes | |
| -o once/manual with its own rotation set | |
| -l lists disk space consumption | |
| ------------------------------------------------------------------------ | |
| Forked from <http://www.mikerubel.org/computers/rsync%5Fsnapshots/contributed/elio_pizzottelli> | |
| GNU GPL v.2 or later | |
| EOF | |
| exit | |
| fi | |
| # backups are stored on the partitions with this name | |
| readonly PART_LABEL='Backup'; | |
| readonly BACKUP_NAME='Snapshot'; | |
| # number of backups to store per rotation group | |
| readonly MAX_DAY=7; | |
| readonly MAX_WEEK=4; | |
| readonly MAX_MONTH=6; | |
| readonly MAX_YEAR=7; | |
| readonly MAX_ONCE=2; | |
| # --- better not change below unless you know what you are doing --------- | |
| unset PATH # suggestion from H. Milz: avoid accidental use of $PATH | |
| # define dependencies / commands used | |
| checkfor () { | |
| [ ! -x $1 ] && { echo >&2 "$1 required; exiting"; exit 1; } | |
| } | |
| readonly AWK=/usr/bin/awk; checkfor $AWK; | |
| readonly BASENAME=/usr/bin/basename; checkfor $BASENAME; | |
| readonly BLKID=/sbin/blkid; checkfor $BLKID; | |
| readonly DATE=/bin/date; checkfor $DATE; | |
| readonly DF=/bin/df; checkfor $DF; | |
| readonly DU=/usr/bin/du; checkfor $DU; | |
| readonly ECHO=/bin/echo; checkfor $ECHO; | |
| readonly FLOCK=/usr/bin/flock; checkfor $FLOCK; | |
| readonly GREP=/bin/grep; checkfor $GREP; | |
| readonly IONICE=/opt/bin/ionice; checkfor $IONICE; | |
| readonly NICE=/bin/nice; checkfor $NICE; | |
| readonly MKDIR=/bin/mkdir; checkfor $MKDIR; | |
| readonly MOUNT=/bin/mount; checkfor $MOUNT; | |
| readonly MV=/bin/mv; checkfor $MV; | |
| readonly PS=/bin/ps; checkfor $PS; | |
| readonly RSYNC=/opt/bin/rsync; checkfor $RSYNC; | |
| readonly SEQ=/usr/bin/seq; checkfor $SEQ; | |
| readonly SLEEP=/bin/sleep; checkfor $SLEEP; | |
| readonly TOUCH=/bin/touch; checkfor $TOUCH; | |
| readonly MOUNT_DEVICE=`$BLKID -L $PART_LABEL`; | |
| readonly MOUNT_POINT_RW='/media/Backup'; | |
| RSYNC_OPTIONS='--recursive | |
| --links | |
| --perms | |
| --times | |
| --group | |
| --owner | |
| --devices | |
| --specials | |
| --inplace | |
| --hard-links | |
| --human-readable | |
| --numeric-ids | |
| --no-acls | |
| --no-xattrs | |
| --no-whole-file | |
| --delete | |
| --delete-excluded' ; | |
| #shortened and without make defaults explicit | |
| readonly RSYNC_OPTIONS='-aHh --inplace --numeric-ids --delete --delete-excluded' ; | |
| #readonly RSYNC_OPTIONS='-a --delete --delete-excluded' ; | |
| # padavan kernel.config | |
| # CONFIG_EXT4_FS_POSIX_ACL is not set | |
| # CONFIG_TMPFS_POSIX_ACL is not set | |
| # --acls --> hang at '[sender] make_file(.,*,0)' | |
| # Sanitize environment | |
| export LC_CTYPE='C' LC_ALL='C' LANG='C' #only english | |
| # make sure we're running as root | |
| # ToDo: workaround for missing /id command in Padavan Busybox 1.24 | |
| #if (( `$ID -u` != 0 )); then { $ECHO 'Sorry, must be root. Exiting...'; exit; } fi | |
| # check if arguments are given | |
| #if [ $# = 0 ] ; then | |
| # $ECHO Error: no arguments. Type $0 -h for help. ; | |
| # exit 10; | |
| #fi | |
| # make sure all MAX_... values are at least 2 or higher | |
| [ $MAX_DAY -ge 2 ] || { $ECHO "Error: This is a rotating backup: MAX_DAY=$MAX_DAY and should be set at least to 2. Exiting" ; exit 11; } | |
| [ $MAX_WEEK -ge 2 ] || { $ECHO "Error: This is a rotating backup: MAX_WEEK=$MAX_WEEK and should be set at least to 2. Exiting" ; exit 12; } | |
| [ $MAX_MONTH -ge 2 ] || { $ECHO "Error: This is a rotating backup: MAX_MONTH=$MAX_MONTH and should be set at least to 2. Exiting" ; exit 13; } | |
| [ $MAX_YEAR -ge 2 ] || { $ECHO "Error: This is a rotating backup: MAX_YEAR=$MAX_YEAR and should be set at least to 2. Exiting" ; exit 14; } | |
| [ $MAX_ONCE -ge 2 ] || { $ECHO "Error: This is a rotating backup: MAX_ONCE=$MAX_ONCE and should be set at least to 2. Exiting" ; exit 15; } | |
| # make sure not already running | |
| readonly SCRIPT_NAME=`$BASENAME $0` | |
| readonly LOCK_FILE="/var/run/$SCRIPT_NAME.pid" | |
| #set -e | |
| exec 200>$LOCK_FILE | |
| $FLOCK -n 200 || { $ECHO "Error: Another copy of $0 is running. Exiting" ; exit 2 ; } | |
| readonly PID=$$ | |
| $ECHO $PID 1>&200 | |
| # make sure read-write mount location exists | |
| if [ ! -d $MOUNT_POINT_RW ] ; then | |
| if [ -e $MOUNT_POINT_RW ] ; then | |
| $ECHO "Error: $MOUNT_POINT_RW isn't a directory. Exiting" ; | |
| exit 16; | |
| fi | |
| $ECHO "Notice: $MOUNT_POINT_RW doesn't exist. Creating..." ; | |
| $MKDIR -p -m 755 $MOUNT_POINT_RW ; | |
| fi | |
| # rotate daily dirs | |
| #set -x | |
| case "$#" in | |
| 1) | |
| case "$1" in | |
| -y|--yearly) readonly TODO='y m w d' ; readonly END_TIME=$(( `$DATE +%s` + 240 )) ;; | |
| -m|--monthly) readonly TODO='m w d' ; readonly END_TIME=$(( `$DATE +%s` + 180 )) ;; | |
| -w|--weekly) readonly TODO='w d' ; readonly END_TIME=$(( `$DATE +%s` + 120 )) ;; | |
| -d|--daily) readonly TODO='d' ;; | |
| -o|--once) readonly TODO='o' ;; | |
| -l|--list) | |
| cd $MOUNT_POINT_RW | |
| $DU -c -h -d 1 -x * | |
| cd - > /dev/null | |
| $DF -h $MOUNT_POINT_RW | $AWK '{printf "%-20s %-20s %-1s\n", $2, $3, $5}' | |
| exit | |
| ;; | |
| *) $ECHO "Error: '$1' is an unknown option. Exiting" ; exit 17 ;; | |
| esac | |
| ;; | |
| *) $ECHO 'Error: unknown number of options. Exiting'; exit 18 ;; | |
| esac | |
| [ -z "$TODO" ] && { $ECHO "Error: nothing to do. Exiting" ; exit 19 ; } | |
| # attempt to remount the mount point as RW; else abort | |
| $MOUNT -o remount,rw,errors=remount-ro $MOUNT_DEVICE $MOUNT_POINT_RW 2>/dev/null ; | |
| if [ $? -ne 0 ]; then | |
| $MOUNT -o mount,rw,errors=remount-ro $MOUNT_DEVICE $MOUNT_POINT_RW 2>/dev/null ; | |
| fi | |
| if [ $? -ne 0 ]; then | |
| $ECHO "Error: could not (re)mount $MOUNT_POINT_RW read-write" ; | |
| exit 20; | |
| fi | |
| for strRound in $TODO; do | |
| # prepare this round | |
| case $strRound in | |
| y) DESTINATION_DIR='Yearly' ; MAX_NUM=$MAX_YEAR ;; | |
| m) DESTINATION_DIR='Monthy' ; MAX_NUM=$MAX_MONTH ;; | |
| w) DESTINATION_DIR='Weekly' ; MAX_NUM=$MAX_WEEK ;; | |
| d) DESTINATION_DIR='Daily' ; MAX_NUM=$MAX_DAY ;; | |
| o) DESTINATION_DIR='Manual' ; MAX_NUM=$MAX_ONCE ;; | |
| esac | |
| # rotate backup destination folders | |
| if [ -d $MOUNT_POINT_RW/$DESTINATION_DIR/$BACKUP_NAME.$MAX_NUM ] ; then | |
| $IONICE -c 3 $NICE -n 19 $MV $MOUNT_POINT_RW/$DESTINATION_DIR/$BACKUP_NAME.$MAX_NUM $MOUNT_POINT_RW/$DESTINATION_DIR/$BACKUP_NAME.tmp ; | |
| fi | |
| for i in `$SEQ $MAX_NUM -1 2`; do | |
| iminus=$(($i-1)) ; | |
| if [ -d $MOUNT_POINT_RW/$DESTINATION_DIR/$BACKUP_NAME.$iminus ] ; then | |
| $IONICE -c 3 $NICE -n 19 $MV $MOUNT_POINT_RW/$DESTINATION_DIR/$BACKUP_NAME.$iminus \ | |
| $MOUNT_POINT_RW/$DESTINATION_DIR/$BACKUP_NAME.$i ; | |
| fi | |
| done | |
| if [ -d $MOUNT_POINT_RW/$DESTINATION_DIR/$BACKUP_NAME.tmp ] ; then | |
| $IONICE -c 3 $NICE -n 19 $MV $MOUNT_POINT_RW/$DESTINATION_DIR/$BACKUP_NAME.tmp $MOUNT_POINT_RW/$DESTINATION_DIR/$BACKUP_NAME.1 ; | |
| fi | |
| if [ ! -d $MOUNT_POINT_RW/$DESTINATION_DIR/$BACKUP_NAME.1 ] ; then | |
| $MKDIR -p -m 755 $MOUNT_POINT_RW/$DESTINATION_DIR/$BACKUP_NAME.1 ; | |
| fi | |
| #unset debug output | |
| #{ set +x ;} 2> /dev/null | |
| # make backups | |
| # Thanks to Gilles @SA for the pattern help: <https://askubuntu.com/questions/56401/backing-up-files-from-specific-folders-in-rsync/56415#answer-56415> | |
| #set -x ; | |
| $IONICE -c 3 $NICE -n 19 $RSYNC -v $RSYNC_OPTIONS \ | |
| --exclude='/etc/storage/system_time' --include='/etc' --include='/etc/storage/***' \ | |
| --exclude='/opt/var/run/*' --include='/opt/***' --exclude='*' --\ | |
| / $MOUNT_POINT_RW/$DESTINATION_DIR/$BACKUP_NAME.1 ; | |
| # update the mtime of $BACKUP_NAME.1 to reflect the snapshot time | |
| $TOUCH $MOUNT_POINT_RW/$DESTINATION_DIR/$BACKUP_NAME.1 ; | |
| done | |
| # remount read-only | |
| # our RT-N56U rev.A + Intenso Micro Line USB flash drive takes 8-33 seconds to remount | |
| # be conservative: retry up to 300 seconds | |
| #set -x ; | |
| timeout=$((`$DATE +%s` + 300)) ; | |
| until $MOUNT -o remount,ro,noload,nodiscard $MOUNT_DEVICE $MOUNT_POINT_RW 2>/dev/null || [ `$DATE +%s` -gt $timeout ] ; | |
| do | |
| $SLEEP 0.2 | |
| done | |
| ## make sure complete script runs for at least levels * 60 seconds when not running daily job | |
| if [ -n "$END_TIME" ] ; then | |
| readonly DURATION=$(( $END_TIME - `$DATE +%s` )) ; | |
| [ $DURATION -gt 0 ] && { $ECHO "Waiting $DURATION seconds." ; $SLEEP $DURATION ; } | |
| fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment