Skip to content

Instantly share code, notes, and snippets.

@2xsaiko
Created April 20, 2018 18:24
Show Gist options
  • Save 2xsaiko/f3164c0930e38c1b37588f562b2417ae to your computer and use it in GitHub Desktop.
Save 2xsaiko/f3164c0930e38c1b37588f562b2417ae to your computer and use it in GitHub Desktop.
my backup system
# library
# sanitizes paths and interprets them as relative to / (adds / at the beginning, removes it at the end, deduplicates multiple /, removes .. and .)
SED_DIRSANIT='s`(^|$)`/`g;s`/+`/`g;:b;s`/\./`/`;tb;:a;s`(^|/[^/]*?)/\.\./`/`g;ta;s`^(.+)/$`\1`g'
# escapes strings for use in a sed command
SED_ESCAPE='s/[]\/$*.^[]/\\&/g'
die() { # vararg errmsg
(tput bold
tput setaf 1
echo "$@"
tput sgr0)>&2
exit 1
}
warn() { # vararg msg
(tput bold
tput setaf 11
echo "$@"
tput sgr0)>&2
}
sed_escape() {
sed -e "$SED_ESCAPE"
}
is_dir_mbu() { # dir
test -f "$1/mbu.conf"
}
cfbd() { # dir
if ! is_dir_mbu "$1"; then
die "'$1' is not a mbu backup directory."
fi
}
create_bd() { # dir, srcdir
test -d "$2" || die "Source directory '$2' does not exist"
mkdir -p "$1" || die "Couldn't create backup directory '$1'."
touch "$1/mbu.conf"
setsrcdir "$1" "$2"
cfg_write "$1/mbu.conf" 'bdirformat' 'backup-%Y%m%d-%H%M%S'
touch "$1/excludes.conf"
}
setsrcdir() { # dir, srcdir
cfbd "$1"
test -d "$2" || die "Source directory '$2' does not exist"
cfg_write "$1/mbu.conf" 'srcdir' "$(cd "$2"; pwd)"
}
setdirformat() { # dir, dirformat
cfbd "$1"
date "+$2" > /dev/null 2> /dev/null || die "Invalid format. Look at \`man date\` for more info."
printf 'Preview: %s\n' "$(date "+$2")"
cfg_write "$1/mbu.conf" 'bdirformat' "$2"
}
get_new_backup_dir() { # dir
cfbd "$1"
cfg_haskey "$1/mbu.conf" 'bdirformat' || cfg_write "$1/mbu.conf" 'bdirformat' 'backup-%Y%m%d-%H%M%S'
local format="$(cfg_read "$1/mbu.conf" 'bdirformat')"
date "+$format"
}
execbackup() { # dir
cfbd "$1"
local backupdir="$(get_new_backup_dir "$1")"
local bdpath="$1/$backupdir"
printf "Creating backup '%s'\n" "$backupdir"
mkdir -p "$bdpath/files" || die "Could not create backup directory."
touch "$bdpath/backup.conf"
local prevb_args=""
if cfg_haskey "$1/mbu.conf" 'latest'; then
local prevbackup="$(cfg_read "$1/mbu.conf" 'latest')"
if [ -d "$1/$prevbackup/files" ]; then
cfg_write "$bdpath/backup.conf" 'prev' "$prevbackup"
prevb_args="--link-dest=../../$prevbackup/files "
else
warn "Nonexistent previous backup: '$prevbackup'"
fi
fi
local sourcedir="$(cfg_read "$1/mbu.conf" 'srcdir')/"
test -d "$sourcedir" || die "Source directory '$sourcedir' does not exist."
local excludesfile="$1/excludes.conf"
# local excludesfile="/tmp/excludes_$RANDOM.tmp"
# do the thing
rsync -aAXSh --info=progress2 --delete --exclude-from="$excludesfile" ${prevb_args} "$sourcedir" "$bdpath/files/" || die "Error while backing up files."
# rm "$excludesfile"
cfg_write "$1/mbu.conf" 'latest' "$backupdir"
echo 'Backup finished!'
}
exclude_add() { # dir, excludedir
cfbd "$1"
test "x$2" = "x" && die "Empty exclude directory"
echo "$2" >> "$1/excludes.conf"
exclude_cleanup "$1"
}
exclude_remove() { # dir, excludedir
cfbd "$1"
sed -i "/^$(echo $2 | sed_escape)$/d" "$1/excludes.conf"
exclude_cleanup "$1"
}
exclude_list() { # dir
cfbd "$1"
cat "$1/excludes.conf" | sed "s|^|($(cfg_read "$1/mbu.conf" srcdir))|g"
}
exclude_cleanup() { # dir
cfbd "$1"
local tmpfile=/tmp/.excludes.$RANDOM.conf
cp "$1/excludes.conf" "$tmpfile"
sed -Ei "$SED_DIRSANIT" "$tmpfile"
sort -uo "$tmpfile" "$tmpfile"
mv "$tmpfile" "$1/excludes.conf"
}
purge() { # dir, keep_no
cfbd "$1"
die stub
}
getprev() { # path, backup_instance
cfbd "$1"
test -f "$1/$2/backup.conf" || die "'$2' is not a valid backup"
cfg_haskey "$1/$2/backup.conf" 'prev' && cfg_read "$1/$2/backup.conf" 'prev'
}
cfg_write() { # path, key, value
cfg_delete "$1" "$2"
echo "$2=$3" >> "$1"
}
cfg_read() { # path, key -> value
test -f "$1" && grep "^$(echo "$2" | sed_escape)=" "$1" | sed "s/^$(echo "$2" | sed_escape)=//" | tail -1
}
cfg_delete() { # path, key
test -f "$1" && sed -i "/^$(echo $2 | sed_escape).*$/d" "$1"
}
cfg_haskey() { # path, key
test -f "$1" && grep "^$(echo "$2" | sed_escape)=" "$1" > /dev/null
}
#!/bin/bash
source $(dirname "$(readlink -f "$0")")/lib/libmbu.sh || exit 5
print_help() {
cat <<EOF
usage: mbu <backup dir> <operation> [...]
operations:
mbu <backup dir> create <source dir>
mbu <backup dir> backup
mbu <backup dir> excla <subdir>
mbu <backup dir> exclr <subdir>
mbu <backup dir> excll
mbu <backup dir> setbf <format>
mbu <backup dir> setsrc <source dir>
mbu <backup dir> purge
create: Creates a new backup directory.
backup: Creates a new incremental backup.
excla: Adds a new excluded file/directory.
exclr: Removes a file/directory from the excludes list.
excll: Lists the currently excluded files/directories.
setbf: Sets the format for the backup folders.
See \`man date\` for more information about the format.
setsrc: Changes the source directory.
purge: Purges everything but the last 4 backups.
EOF
exit 2
}
case "$2" in
create)
create_bd "$1" "$3"
;;
backup)
execbackup "$1"
;;
excla)
exclude_add "$1" "$3"
;;
exclr)
exclude_remove "$1" "$3"
;;
excll)
exclude_list "$1"
;;
setbf)
setdirformat "$1" "$3"
;;
setsrc)
setsrcdir "$1" "$3"
;;
purge)
purge "$1" 4
;;
*)
print_help
;;
esac
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment