Skip to content

Instantly share code, notes, and snippets.

@ProBackup-nl
Last active January 7, 2022 12:46
Show Gist options
  • Select an option

  • Save ProBackup-nl/82fb02c67dc05ca0310d0f9f8e8be14c to your computer and use it in GitHub Desktop.

Select an option

Save ProBackup-nl/82fb02c67dc05ca0310d0f9f8e8be14c to your computer and use it in GitHub Desktop.
/opt and /etc/storage backup utility for Padavan firmware using rsync
#!/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