1500 lines
38 KiB
Bash
Executable File
1500 lines
38 KiB
Bash
Executable File
#!/bin/bash
|
|
VALIDCOMMANDS="go ls init repos stats diff forget get check"
|
|
|
|
RCFILE=${HOME}/.backup/config
|
|
DEF_AUTHFILE=${HOME}/.backup/auth
|
|
REPOFILE=${HOME}/.backup/repos
|
|
STATFILE=${HOME}/.backup/stats
|
|
LOGFILE=/var/log/backup.log
|
|
FULLLOGFILE_BASE=/tmp/full_backup_log
|
|
|
|
RESTIC=/usr/local/bin/restic
|
|
RCLONE=/usr/local/bin/rclone
|
|
RSYNC=/usr/local/bin/rsync
|
|
USMB=/usr/local/bin/usmb
|
|
|
|
#RCLONEOPTS="--cache-chunk-no-memory --buffer-size=10M --progress"
|
|
RCLONEOPTS="--progress --buffer-size 10M --cache-chunk-no-memory"
|
|
|
|
SPEED=""
|
|
RESTOREDIRBASE="/mnt/restore"
|
|
CRONMODE=0
|
|
CONNECTIONS=""
|
|
LOG=/dev/stdout
|
|
DATE=/bin/date
|
|
DOALL=0
|
|
REPO_LIST=""
|
|
TESTMODE=0
|
|
TELEGRAM=0
|
|
VERBOSE=0
|
|
|
|
function cecho() {
|
|
local col
|
|
col="$1"
|
|
shift 1
|
|
echo -e "${col}$*${PLAIN}"
|
|
}
|
|
function error() {
|
|
cecho "$RED" "Error: $*"
|
|
}
|
|
function action() {
|
|
cecho "$CYAN" "$*"
|
|
}
|
|
|
|
function log() {
|
|
local now col lowrer
|
|
now=`${DATE} +%Y/%m/%d-%H:%M:%S`
|
|
lower=$(echo "$*" | tr '[:upper:]' '[:lower:]')
|
|
if [[ $lower == *error* ]]; then
|
|
col="$RED"
|
|
elif [[ $lower == *fail* ]]; then
|
|
col="$RED"
|
|
elif [[ $lower == *success* ]]; then
|
|
col="$GREEN"
|
|
elif [[ $lower == *starting* ]]; then
|
|
col="$BOLD"
|
|
elif [[ $lower == *created* ]]; then
|
|
col="$BOLD"
|
|
elif [[ $lower == *creating* ]]; then
|
|
col="$BOLD"
|
|
elif [[ $lower == *finished* ]]; then
|
|
col="$BOLD"
|
|
else
|
|
col="$CYAN"
|
|
fi
|
|
if [[ $TELEGRAM -eq 1 ]]; then
|
|
echo -e "__${now}__ **<${mode}>** \`$*\`" >>${LOG}
|
|
else
|
|
echo -e "$CYAN${now} <${mode}> $col$*$PLAIN" >>${LOG}
|
|
fi
|
|
}
|
|
|
|
function updatestats() {
|
|
local repo cmd stamp code
|
|
repo=$1
|
|
cmd=$2
|
|
code=$3
|
|
stamp=`date +%s`
|
|
if [[ -f $STATFILE ]]; then
|
|
sed -i "/${repo}:${cmd}/d" $STATFILE
|
|
fi
|
|
echo "${repo}:${cmd}:${code}:${stamp}" >> $STATFILE
|
|
}
|
|
|
|
function usage-repo() {
|
|
echo " reponame1;/path/to/files1/;tag1,tag2,...,tagX;repo_url;repo_passfile;repo_excludefile"
|
|
echo " reponame2;/path/to/files2/;tag1,tag2,...,tagX;repo_url;repo_passfile;repo_excludefile"
|
|
echo " ..."
|
|
echo " reponameX;/path/to/filesX/;tag1,tag2,...,tagX;repo_url;repo_passfile;repo_excludefile"
|
|
echo ""
|
|
echo " Default password file is: $DEF_AUTHFILE"
|
|
echo ""
|
|
echo "Backup method tags:"
|
|
echo " restic Backup using restic (default repo is Backblaze B2 bucket \$B2_BUCKET_PREFIX-<reponame>)"
|
|
echo " rclone Backup using rclone (default repo is Backblaze B2 bucket \$B2_BUCKET_PREFIX-<reponame>)"
|
|
echo " rsync Backup using rsync (default repo is \$DEF_RSYNC_USER@\$DEF_RSYNC_SERVER:\$DEF_RSYNC_DIR/<reponame>)"
|
|
echo " postgres Dump postgres databases. Instead of local file path, specify database name to backup. If empty, all dbs are backed up."
|
|
echo " mysql Dump mysql databases. Instead of local file path, specify database name to backup. If empty, all dbs are backed up."
|
|
echo ""
|
|
echo "Pre-backup commands to access local files:"
|
|
echo " nfs Mount \$DEF_NFS_SERVER:\$DEF_NFS_SERVER_BASE/<sharename> to <repopath>"
|
|
echo " before running. Sharename determined by stripping \$NFS_PREFIX"
|
|
echo " from start of repo path."
|
|
echo " usmb Run 'usmb reponame' to mount \$USMB_PREFIX/<sharename> before running."
|
|
echo " Sharename determined by stripping \$NFS_PREFIX from start of <repopath>."
|
|
echo " localmount Run 'mount reponame' to mount <path_to_files> before running."
|
|
echo
|
|
echo "Pre-backup commands to access repo:"
|
|
echo " premount=x Run 'mount <x>' before command and 'umount x' afterwards."
|
|
}
|
|
|
|
function usage-rc() {
|
|
echo " export B2_ACCOUNT_ID=xxx # Backblaze B2 credentials"
|
|
echo " export B2_ACCOUNT_KEY=xxx # Backblaze B2 credentials"
|
|
echo " export B2_APP_ID=xxx # Backblaze B2 bucket details"
|
|
echo " export B2_APP_KEY=xxx # Backblaze B2 bucket auth"
|
|
echo " export B2_BUCKET_PREFIX=xxx # Prefix to append to create unique B2 bucket name"
|
|
echo ""
|
|
|
|
|
|
echo " # Binary paths (optional, below are defualts):"
|
|
echo " #export RESTIC=/usr/local/bin/restic"
|
|
echo " #export RCLONE=/usr/local/bin/rclone"
|
|
echo " #export RSYNC=/usr/local/bin/rsync"
|
|
echo " #export USMB=/usr/local/bin/usmb"
|
|
echo ""
|
|
echo " export RESTIC_KEEPDAYS=31 # Days to keep restic backups for"
|
|
echo " export RESTICOPTS=\"--one-file-system\" # Extra args to pass to restic"
|
|
echo ""
|
|
echo " export PGUSER=postgres # Postgresql username"
|
|
echo ""
|
|
echo " export DEF_RSYNC_SERVER=xxx # Server used for repos with 'rsync' tag"
|
|
echo " export DEF_RSYNC_USER=backups # Username for rsync server"
|
|
echo " export DEF_RSYNC_DIR=/home/backups/backups # Remote directory on rsync server"
|
|
echo " export RSYNC_OPTIONS=-Pavz # Options to pass to rsync"
|
|
echo ""
|
|
echo " export USMB_PREFIX=/DataVolume/shares # Strip this from repo path to get USMB share name"
|
|
echo ""
|
|
echo " export DEF_NFS_SERVER=nfs.yourdomain.com # NFS server where local files for 'nfs' repos are found"
|
|
echo " export DEF_NFS_SERVER_BASE=/remote/nfs/share/base # Path on local NFS server to prefix to repo names"
|
|
echo " export NFS_PREFIX=/local/nfs/mountpoint # Where to mount NFS exports locally before backing them up"
|
|
}
|
|
|
|
function usage() {
|
|
echo "usage: $0 command reponame"
|
|
echo ""
|
|
echo " -a Run on all repos defined as 'auto'"
|
|
echo " -d dir Restore mode: specify where to put restored files (default: $RESTOREDIRBASE)"
|
|
echo " -h Show this usage text"
|
|
echo " -s num Limit speed to 'num' Mbps (default: unlimited)"
|
|
echo " -x num Use 'num' simultaneous connections (default: 20)"
|
|
echo " -c Cron mode - log to ${LOGFILE}"
|
|
echo " -t Test mode - dump what would be done then exit."
|
|
echo " -T Telegram formatting mode"
|
|
echo ""
|
|
echo "Valid commands are:"
|
|
echo " $VALIDCOMMANDS"
|
|
echo ""
|
|
echo "${RCFILE} should look like this:"
|
|
usage-rc
|
|
echo ""
|
|
echo "${REPOFILE} should look like this:"
|
|
usage-repo
|
|
echo ""
|
|
}
|
|
function getdatapath() {
|
|
local DATAPATH REPO x
|
|
REPO="$1"
|
|
|
|
DATAPATH=""
|
|
for x in ${REPODEFS[@]}; do
|
|
match="^${REPO}\;"
|
|
if [[ $x =~ $match ]]; then
|
|
DATAPATH=`echo $x | sed -e s/${match}//`
|
|
fi
|
|
done
|
|
DATAPATH=`echo $DATAPATH | sed -e s/\;.*//`
|
|
echo "$DATAPATH"
|
|
}
|
|
function getmode() {
|
|
local mode
|
|
checktag "$1" rclone
|
|
if [[ $? -eq 0 ]]; then
|
|
mode=rclone
|
|
else
|
|
checktag "$1" rsync
|
|
if [[ $? -eq 0 ]]; then
|
|
mode=rsync
|
|
else
|
|
mode=restic
|
|
fi
|
|
fi
|
|
echo "$mode"
|
|
}
|
|
|
|
function getrepoexcludefile() { # output path
|
|
local res rv
|
|
res=`getrepotok $1 6`
|
|
rv=$?
|
|
echo "$res"
|
|
return $rv
|
|
}
|
|
|
|
function getrepopath() { # output path
|
|
local res rv mode def repobase
|
|
res=`getrepotok $1 4`
|
|
rv=$?
|
|
|
|
if [[ -z $res ]]; then
|
|
mode=`getmode $1`
|
|
repobase=`getreponame $1`
|
|
if [[ $mode == "restic" ]]; then
|
|
def="b2:${B2_BUCKET_PREFIX}-${repobase}"
|
|
elif [[ $mode == "rclone" ]]; then
|
|
def="remote:${B2_BUCKET_PREFIX}-${repobase}"
|
|
elif [[ $mode == "rsync" ]]; then
|
|
def="${DEF_RSYNC_USER}@${DEF_RSYNC_SERVER}:${DEF_RSYNC_DIR}/$repobase"
|
|
else
|
|
def="(no default repo for mode $mode)"
|
|
fi
|
|
res="$def"
|
|
fi
|
|
|
|
echo "$res"
|
|
return $rv
|
|
}
|
|
|
|
function getrepopassfile() { # output path
|
|
local res rv
|
|
res=`getrepotok $1 5`
|
|
rv=$?
|
|
echo "$res"
|
|
return $rv
|
|
}
|
|
|
|
function getrepotags() { # output path
|
|
local res rv
|
|
res=`getrepotok $1 3`
|
|
rv=$?
|
|
echo "$res"
|
|
return $rv
|
|
}
|
|
|
|
function getrepotok() { # output given token from repo def
|
|
local def res rv code idx
|
|
def=`getrepodef "$1"`
|
|
idx=$2
|
|
code="{ print \$${idx} }"
|
|
res=`echo "$def" | awk -F';' "$code"`
|
|
echo "$res"
|
|
if [[ -z $res ]]; then
|
|
rv=1
|
|
else
|
|
rv=0
|
|
fi
|
|
return $rv
|
|
}
|
|
|
|
function getreponame() {
|
|
local reponame
|
|
|
|
reponame="$1"
|
|
|
|
if [[ $reponame =~ ^.*\;.*$ ]]; then
|
|
# if we were given a repo def, return just the name
|
|
reponame=`echo "$reponame" | sed -e 's/\;.*//g'`
|
|
fi
|
|
echo "$reponame"
|
|
}
|
|
|
|
function getrepodef() {
|
|
local reponame match repodef x
|
|
|
|
reponame="$1"
|
|
|
|
if [[ $reponame =~ ^.*\;.*$ ]]; then
|
|
repodef="$reponame"
|
|
else
|
|
# if we were just given a repo name, look up the def
|
|
repodef=""
|
|
for x in ${REPODEFS[@]}; do
|
|
match="^${reponame};"
|
|
if [[ $x =~ $match ]]; then
|
|
repodef="$x"
|
|
fi
|
|
done
|
|
fi
|
|
echo "$repodef"
|
|
}
|
|
|
|
function gettagval() { # return 0 if tag matches
|
|
local reponame lookfor justtags x t val
|
|
|
|
reponame="$1"
|
|
lookfor="$2"
|
|
|
|
justtags=`getrepotags "$reponame"`
|
|
|
|
val=""
|
|
IFS=',' read -ra t <<< "$justtags"
|
|
for x in ${t[@]}; do
|
|
if [[ $x == ${lookfor}=* ]]; then
|
|
val=`echo "$x" | cut -d= -f2`
|
|
break
|
|
fi
|
|
done
|
|
|
|
if [[ ! -z ${val} ]]; then
|
|
echo "$val"
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
|
|
function checktag() { # return 0 if tag matches
|
|
local reponame lookfor justtags
|
|
|
|
reponame="$1"
|
|
lookfor="$2"
|
|
|
|
justtags=`getrepotags "$reponame"`
|
|
|
|
if [[ $justtags =~ ^.*${lookfor}.*$ ]]; then
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
|
|
function do_mount() {
|
|
local msg
|
|
if [[ $USEUSMB -eq 1 ]]; then
|
|
[[ $VERBOSE -eq 1 ]] && log "debug: mounting $DATAPATH via smb"
|
|
mount | grep -q ${DATAPATH}
|
|
if [[ $? -ne 0 ]]; then
|
|
# try mounting it.
|
|
${USMB} ${USMBNAME} >/dev/null
|
|
if [[ $? -eq 0 ]]; then
|
|
log "Mount usmb volume '$USMBNAME': success"
|
|
else
|
|
log "Mount usmb volume '$USMBNAME': FAILED"
|
|
return 1
|
|
fi
|
|
fi
|
|
elif [[ $USENFS -eq 1 ]]; then
|
|
[[ $VERBOSE -eq 1 ]] && log "debug: mounting $DATAPATH via nfs"
|
|
mount | grep -q ${DATAPATH}
|
|
if [[ $? -ne 0 ]]; then
|
|
# try mounting it.
|
|
mkdir -p $DATAPATH
|
|
mount_nfs -R 1 ${NFSPATH} ${DATAPATH} >/dev/null
|
|
if [[ $? -eq 0 ]]; then
|
|
log "Mount nfs volume '$NFSPATH' to $DATAPATH: success"
|
|
else
|
|
log "Mount nfs volume '$NFSPATH' to $DATAPATH: FAILED"
|
|
return 1
|
|
fi
|
|
fi
|
|
elif [[ $USELOCALMOUNT -eq 1 ]]; then
|
|
[[ $VERBOSE -eq 1 ]] && log "debug: mounting $DATAPATH via fstab"
|
|
mount | grep -q ${DATAPATH}
|
|
if [[ $? -ne 0 ]]; then
|
|
# try mounting it.
|
|
mkdir -p $DATAPATH
|
|
mount ${DATAPATH} >/dev/null
|
|
if [[ $? -eq 0 ]]; then
|
|
log "Mount $DATAPATH: success"
|
|
else
|
|
log "Mount $DATAPATH: FAILED"
|
|
return 1
|
|
fi
|
|
fi
|
|
fi
|
|
# datapath is used for sql filename in pgsql/mysql backups
|
|
if checktag "$f" postgres; then
|
|
# Make sure the db exists
|
|
if ! [[ -z ${DATAPATH} ]]; then
|
|
psql -lqt | cut -d \| -f 1 | grep -qw ${DATAPATH}
|
|
if [[ $? -ne 0 ]]; then
|
|
log "Error: PostgreSQL database '${DATAPATH}' doesn't exist"
|
|
return 1
|
|
fi
|
|
fi
|
|
elif checktag "$f" mysql; then
|
|
# Make sure the db exists
|
|
if ! [[ -z ${DATAPATH} ]]; then
|
|
mysql -e 'show databases' | grep -v "^Database$" | grep -qw ${DATAPATH}
|
|
if [[ $? -ne 0 ]]; then
|
|
log "Error: MySQL database '${DATAPATH}' doesn't exist"
|
|
return 1
|
|
fi
|
|
fi
|
|
else
|
|
if ! [[ -e ${DATAPATH} ]]; then
|
|
log "Error: ${DATAPATH} doesn't exist"
|
|
return 1
|
|
fi
|
|
|
|
count=`ls ${DATAPATH} | wc -l`
|
|
if [ $count -le 2 ]; then
|
|
log "Error: ${DATAPATH} exists but appears to be empty"
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
function do_umount() {
|
|
if is_mounted $DATAPATH; then
|
|
if [[ $USEUSMB -eq 1 ]]; then
|
|
log "Unmounting usmb volume '$USMBNAME'"
|
|
usmb -u $USMBNAME
|
|
elif [[ $USENFS -eq 1 ]]; then
|
|
log "Unmounting nfs volume '$NFSPATH' from $DATAPATH"
|
|
umount $DATAPATH
|
|
elif [[ $USELOCALMOUNT -eq 1 ]]; then
|
|
log "Unmounting dir $DATAPATH"
|
|
umount $DATAPATH
|
|
fi
|
|
fi
|
|
}
|
|
|
|
function updatedir() {
|
|
local temp r tempwd
|
|
tempwd=$(echo "$wd" | sed -e 's-/\{1,\}-/-g')
|
|
temp=$( ${RESTIC} $AUTHOPTS $CONNECTIONSOPTS -q ls -l $RESTORESNAPID $tempwd 2>/dev/null )
|
|
r=$?
|
|
if [[ $r -ne 0 || -z $temp ]]; then
|
|
return 1
|
|
fi
|
|
wd="$tempwd"
|
|
contents=$( echo "$temp" | awk -v wd=$wd '{ sub(wd,""); sub(wd "/", ""); print $0; }')
|
|
subdirs=$( echo "${contents}" | grep "^d" | awk '{ print $NF}' | sed -e 's,^/,,')
|
|
subfiles=$( echo "${contents}" | awk '{ print $NF}' | sed -e 's,^/,,')
|
|
return 0
|
|
}
|
|
|
|
function showrestoreinfo() {
|
|
cecho "$YELLOW$BOLD" "Repo to restore from:"
|
|
cecho "$YELLOW" " $f (snapshot $RESTORESNAPID)"
|
|
echo
|
|
cecho "$YELLOW$BOLD" "Files to restore:"
|
|
if [[ -z $RESTORELIST ]]; then
|
|
cecho "$YELLOW" " (none)"
|
|
else
|
|
cecho "$YELLOW" "$RESTORELIST" | sed -e 's/^ //' | tr ' ' '\n' | sed -e 's/^/ /g'
|
|
fi
|
|
echo
|
|
}
|
|
|
|
function restorecmd_help() {
|
|
echo
|
|
echo "x Abort"
|
|
echo "q Abort"
|
|
echo "lcd dir Change local restore dir"
|
|
echo "ls Show files in current repo dir"
|
|
echo "cd Change current repo dir"
|
|
echo "dirs Start subdirectories of current repo dir"
|
|
echo "go Start restore of marked file"
|
|
echo "w List files marked for restore"
|
|
echo "a file Mark file to be restored"
|
|
echo "d file Un-mark file from restore list"
|
|
echo
|
|
}
|
|
|
|
function is_mounted() {
|
|
local dir dev1 dev2 rv formatarg
|
|
dir="$1"
|
|
|
|
if [[ $(uname -s) == "Linux" ]]; then
|
|
formatarg="-c"
|
|
else
|
|
formatarg="-f"
|
|
fi
|
|
|
|
dev1=$(stat $formatarg %d $dir 2>/dev/null)
|
|
dev2=$(stat $formatarg %d $dir/.. 2>/dev/null)
|
|
if [[ $dev1 == $dev2 ]]; then
|
|
rv=1
|
|
else
|
|
rv=0
|
|
fi
|
|
return $rv
|
|
|
|
}
|
|
|
|
function check_repomount_needed() {
|
|
local path
|
|
path=`gettagval "$f" premount`
|
|
if [[ $? -eq 0 ]]; then
|
|
grep -q $path /etc/fstab
|
|
if [[ $? -ne 0 ]]; then
|
|
log "Error: repo '$REPO' has premount tag but $path not in fstab."
|
|
return 1
|
|
fi
|
|
|
|
MOUNTREPO=1
|
|
else
|
|
MOUNTREPO=0
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
function check_datamount_needed() {
|
|
if [[ $CMD == "go" ]]; then
|
|
NEEDMOUNT=1
|
|
elif [[ $CMD == "get" ]]; then
|
|
NEEDMOUNT=1
|
|
elif [[ $CMD == "diff" ]]; then
|
|
NEEDMOUNT=1
|
|
else
|
|
NEEDMOUNT=0
|
|
fi
|
|
}
|
|
|
|
# Handle args
|
|
ARGS="acd:hs:tTvx:"
|
|
|
|
while getopts "$ARGS" i; do
|
|
case "$i" in
|
|
a)
|
|
DOALL=1
|
|
;;
|
|
d)
|
|
RESTOREDIRBASE="$OPTARG";
|
|
;;
|
|
h)
|
|
usage;
|
|
exit 1
|
|
;;
|
|
s)
|
|
SPEED="$OPTARG";
|
|
;;
|
|
x)
|
|
CONNECTIONS="$OPTARG";
|
|
;;
|
|
c)
|
|
CRONMODE=1;
|
|
LOG=${LOGFILE}
|
|
;;
|
|
t)
|
|
TESTMODE=1
|
|
;;
|
|
T)
|
|
TELEGRAM=1
|
|
;;
|
|
v)
|
|
VERBOSE=1
|
|
;;
|
|
*)
|
|
echo "ERROR: invalid argument: $i";
|
|
usage;
|
|
;;
|
|
esac
|
|
done
|
|
shift $((OPTIND - 1))
|
|
|
|
if [[ $CRONMODE -eq 0 ]]; then
|
|
BOLD="\033[1m"
|
|
PLAIN="\033[0m"
|
|
UNDERLINE="\033[4m"
|
|
RED="\033[31m"
|
|
GREEN="\033[32m"
|
|
YELLOW="\033[33m"
|
|
BLUE="\033[34m"
|
|
CYAN="\033[36m"
|
|
LINK="$BLUE$UNDERLINE"
|
|
fi
|
|
|
|
if ! [ -e ${RCFILE} ]; then
|
|
error "can't find ${RCFILE}"
|
|
echo ""
|
|
usage-rc
|
|
exit 1
|
|
fi
|
|
if ! [ -e ${REPOFILE} ]; then
|
|
error "can't find ${REPOFILE}"
|
|
echo ""
|
|
usage-repo
|
|
exit 1
|
|
fi
|
|
which bc >/dev/null 2>&1
|
|
if [ $? -ne 0 ]; then
|
|
error "can't find bc in path"
|
|
echo ""
|
|
exit 1
|
|
fi
|
|
|
|
. ${RCFILE}
|
|
|
|
if [[ -z $RESTIC_KEEPDAYS ]]; then
|
|
error "\$RESTIC_KEEPDAYS not set."
|
|
exit 1
|
|
fi
|
|
|
|
# Convert speed from Mbps to KBps
|
|
if ! [[ -z $SPEED ]]; then
|
|
KSPEED=$(echo "scale=2; $SPEED * 125" | bc)
|
|
else
|
|
KSPEED=""
|
|
fi
|
|
|
|
CMD="$1"
|
|
shift 1
|
|
|
|
# command synonyms
|
|
if [[ $CMD == "repolist" ]]; then
|
|
CMD="repos"
|
|
elif [[ $CMD == "restore" ]]; then
|
|
CMD="get"
|
|
elif [[ $CMD == "snapshots" ]]; then
|
|
CMD="ls"
|
|
fi
|
|
|
|
|
|
if (echo "$VALIDCOMMANDS" | grep -wq $CMD) ; then
|
|
true
|
|
else
|
|
error "invalid command $CMD. Should one of: $VALIDCOMMANDS"
|
|
exit 1
|
|
fi
|
|
|
|
# Get list of defined repos
|
|
idx=0
|
|
for f in `cat $REPOFILE | grep ";" | grep -v "^#"`; do
|
|
REPODEFS[$idx]="$f"
|
|
|
|
mode=`getmode "$f"`
|
|
if [[ $mode == "restic" ]]; then
|
|
thisrpath=`getrepopath $f`
|
|
if [[ ${thisrpath,,} == *b2* ]]; then
|
|
if [[ -z $B2_APP_ID ]]; then
|
|
error "\$B2_APP_ID not set."
|
|
exit 1
|
|
fi
|
|
if [[ -z $B2_APP_KEY ]]; then
|
|
error "\$B2_APP_KEY not set."
|
|
exit 1
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
if [[ $DOALL -eq 1 ]]; then
|
|
checktag "$f" auto
|
|
if [[ $? -eq 0 ]]; then
|
|
REPO_LIST="$REPO_LIST `echo $f | sed -e 's/\;.*//'`"
|
|
fi
|
|
fi
|
|
idx=$((idx + 1))
|
|
done
|
|
|
|
|
|
if [[ $CMD == "get" ]]; then
|
|
# one repo + snapshotid/date
|
|
err=0
|
|
if [[ -z $REPO_LIST ]]; then
|
|
if [[ $# -ge 1 ]]; then
|
|
REPO_LIST=$1 ;shift 1
|
|
else
|
|
error "no reponame provided."
|
|
exit 1
|
|
fi
|
|
if [[ $# -ge 1 ]]; then
|
|
RESTORESNAPID=$1 ; shift 1
|
|
fi
|
|
RESTORELIST="$*"
|
|
else
|
|
echo "restore usage: $0 get repo_name (snapshotid|latest)"
|
|
echo " or"
|
|
echo " $0 get repo_name (snapshotid|latest) file1 file2 etc"
|
|
echo
|
|
echo "Files will be restored to $RESTOREDIRBASE/repo_name/"
|
|
echo
|
|
exit 1
|
|
fi
|
|
else
|
|
# one or more repos
|
|
if [[ -z $REPO_LIST ]]; then
|
|
REPO_LIST="$*"
|
|
fi
|
|
fi
|
|
|
|
if [[ $CMD == "repos" ]]; then
|
|
format="%-20s%-20s%-30s%s\n"
|
|
cecho "${CYAN}" "Repos defined in $BOLD${RCFILE}$PLAIN$CYAN:"
|
|
echo ""
|
|
head=$(printf "$format" "Repo" "Path/DB" "Repo path" "Tags")
|
|
cecho "$BOLD$YELLOW" "$head"
|
|
for x in ${REPODEFS[@]}; do
|
|
IFS=';' read -ra tok <<< "$x"
|
|
thisrepo=${tok[0]}
|
|
thispath=${tok[1]}
|
|
if [[ -z ${tok[2]} ]]; then
|
|
tags="n/a"
|
|
else
|
|
tags=`echo "${tok[2]}" | sed -e 's/,/ /g'`
|
|
fi
|
|
thisrpath=`getrepopath $thisrepo`
|
|
[[ -z $thisrpath ]] && thisrpath="(default)"
|
|
|
|
if [[ $tags == *postgres* && -z $thispath ]]; then
|
|
thispath="(all dbs)"
|
|
elif [[ $tags == *mysql* && -z $thispath ]]; then
|
|
thispath="(all dbs)"
|
|
fi
|
|
|
|
line=$(printf "$format" "$thisrepo" "$thispath" "$thisrpath" "$tags")
|
|
cecho "$YELLOW" "$line"
|
|
done
|
|
exit 0
|
|
fi
|
|
|
|
if [[ -z $REPO_LIST ]]; then
|
|
usage
|
|
exit 1
|
|
fi
|
|
|
|
# Validate repos
|
|
GOODREPOS=""
|
|
for f in $REPO_LIST; do
|
|
REPO=${f}
|
|
DATAPATH=$(getdatapath $f)
|
|
REPOPATH=$(getrepopath $f)
|
|
mode=`getmode "$REPO"`
|
|
|
|
checktag "$f" usmb
|
|
if [[ $? -eq 0 ]]; then
|
|
if [[ -z $USMB_PREFIX ]]; then
|
|
USMBNAME=$f
|
|
else
|
|
if ! [[ $DATAPATH == *${USMB_PREFIX}* ]]; then
|
|
log "Error: path '$DATAPATH' of repo '$REPO' does not contain smb prefix '$USMB_PREFIX'."
|
|
continue
|
|
fi
|
|
USMBNAME=`echo "${DATAPATH}" | sed -e "s,${USMB_PREFIX}/\(.*\).*,\1,"`
|
|
fi
|
|
USEUSMB=1
|
|
else
|
|
USMBNAME=""
|
|
USEUSMB=0
|
|
fi
|
|
|
|
checktag "$f" nfs
|
|
if [[ $? -eq 0 ]]; then
|
|
if [[ -z $DEF_NFS_SERVER ]]; then
|
|
log "Error: repo '$REPO' has nfs tag but \$DEF_NFS_SERVER not defined."
|
|
continue
|
|
fi
|
|
if [[ -z $DEF_NFS_SERVER_BASE ]]; then
|
|
log "Error: repo '$REPO' has nfs tag but \$DEF_NFS_SERVER_BASE not defined."
|
|
continue
|
|
fi
|
|
if ! [[ $DATAPATH == *${NFS_PREFIX}* ]]; then
|
|
log "Error: path '$DATAPATH' of repo '$REPO' does not contain nfs prefix '$NFS_PREFIX'."
|
|
continue
|
|
fi
|
|
NFSNAME=`echo "${DATAPATH}" | sed -e "s,${NFS_PREFIX}/\(.*\).*,\1,"`
|
|
NFSPATH="${DEF_NFS_SERVER}:${DEF_NFS_SERVER_BASE}/${NFSNAME}"
|
|
USENFS=1
|
|
else
|
|
NFSNAME=""
|
|
USENFS=0
|
|
fi
|
|
|
|
check_repomount_needed "$f" # sets $MOUNTREPO
|
|
[[ $? -ne 0 ]] && continue
|
|
|
|
checktag "$f" localmount
|
|
if [[ $? -eq 0 ]]; then
|
|
dir=`echo "$DATAPATH" | sed -e 's,/$,,'`
|
|
grep -q $dir /etc/fstab
|
|
if [[ $? -ne 0 ]]; then
|
|
log "Error: repo '$REPO' has localmount tag but $DATAPATH not in fstab."
|
|
continue
|
|
fi
|
|
USELOCALMOUNT=1
|
|
else
|
|
USELOCALMOUNT=0
|
|
fi
|
|
|
|
if [[ -z $DATAPATH ]]; then
|
|
checktag "$f" postgres
|
|
pgrv=$?
|
|
checktag "$f" mysql
|
|
myrv=$?
|
|
if [[ $pgrv -ne 0 && $myrv -ne 0 ]]; then
|
|
log "error - can't find matching repo for $f - make sure it is listed in ${REPOFILE}"
|
|
continue
|
|
fi
|
|
fi
|
|
|
|
check_datamount_needed "$f" # sets $NEEDMOUNT
|
|
if [[ $CMD == "go" ]]; then
|
|
NEEDMOUNT=1
|
|
elif [[ $CMD == "get" ]]; then
|
|
NEEDMOUNT=1
|
|
elif [[ $CMD == "diff" ]]; then
|
|
NEEDMOUNT=1
|
|
else
|
|
NEEDMOUNT=0
|
|
fi
|
|
|
|
if [[ $NEEDMOUNT -eq 1 ]]; then
|
|
do_mount $DATAPATH
|
|
if [[ $? -ne 0 ]]; then
|
|
continue
|
|
fi
|
|
fi
|
|
|
|
|
|
if [[ -z $GOODREPOS ]]; then
|
|
GOODREPOS="$f"
|
|
else
|
|
GOODREPOS="$GOODREPOS $f"
|
|
fi
|
|
done
|
|
|
|
REPO_LIST="$GOODREPOS"
|
|
if [[ -z $REPO_LIST ]]; then
|
|
log "Error: errors found with all repos. Aborting."
|
|
exit 1
|
|
fi
|
|
|
|
[[ $VERBOSE -eq 1 ]] && log "Processing these repos: $REPO_LIST"
|
|
|
|
|
|
if [[ $TESTMODE -eq 1 ]]; then
|
|
action "Would have run '$CMD' on these repos:"
|
|
for f in $REPO_LIST; do
|
|
DATAPATH=$(getdatapath $f)
|
|
echo " $f -> $DATAPATH"
|
|
if [[ $NEEDMOUNT -eq 1 ]]; then
|
|
do_umount
|
|
fi
|
|
done
|
|
exit 0
|
|
fi
|
|
|
|
errcount=0 # used for 'check' mode
|
|
warncount=0 # used for 'check' mode
|
|
errtext=""
|
|
for f in $REPO_LIST; do
|
|
[[ $VERBOSE -eq 1 ]] && log "debug: starting repo $f"
|
|
REPO=${f}
|
|
DATAPATH=$(getdatapath $REPO)
|
|
FULLLOGFILE="${FULLLOGFILE_BASE}_${REPO}.txt"
|
|
checktag "$f" usmb
|
|
if [[ $? -eq 0 ]]; then
|
|
if [[ -z $USMB_PREFIX ]]; then
|
|
USMBNAME=$f
|
|
else
|
|
USMBNAME=`echo "${DATAPATH}" | sed -e "s,${USMB_PREFIX}/\(.*\).*,\1,"`
|
|
fi
|
|
USEUSMB=1
|
|
else
|
|
USMBNAME=""
|
|
USEUSMB=0
|
|
fi
|
|
|
|
checktag "$f" nfs
|
|
if [[ $? -eq 0 ]]; then
|
|
NFSNAME=`echo "${DATAPATH}" | sed -e "s,${NFS_PREFIX}/\(.*\).*,\1,"`
|
|
NFSPATH="${DEF_NFS_SERVER}:${DEF_NFS_SERVER_BASE}/${NFSNAME}"
|
|
USENFS=1
|
|
else
|
|
NFSNAME=""
|
|
USENFS=0
|
|
fi
|
|
|
|
checktag "$f" localmount
|
|
if [[ $? -eq 0 ]]; then
|
|
USELOCALMOUNT=1
|
|
else
|
|
USELOCALMOUNT=0
|
|
fi
|
|
|
|
|
|
export REPO_PATH=`getrepopath "$f"`
|
|
if [[ $? -eq 0 ]]; then
|
|
USINGDEFAULT=0
|
|
else
|
|
USINGDEFAULT=1
|
|
fi
|
|
check_datamount_needed "$f" # sets $NEEDMOUNT
|
|
|
|
check_repomount_needed "$f" # sets $MOUNTREPO
|
|
if [[ $MOUNTREPO -eq 1 ]]; then
|
|
path=`gettagval "$f" premount`
|
|
[[ $VERBOSE -eq 1 ]] && log "debug: doing repo premount at $path via fstab"
|
|
if is_mounted $path; then
|
|
[[ ! $CMD == "check" ]] && log "Pre-mount of '$path': already mounted"
|
|
else
|
|
mount $path
|
|
if [[ $? -eq 0 ]]; then
|
|
[[ ! $CMD == "check" ]] && log "Pre-mount of '$path': success"
|
|
else
|
|
[[ ! $CMD == "check" ]] && log "Pre-mount of '$path': FAILED"
|
|
continue
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
mode=`getmode "$f"`
|
|
|
|
SPEEDOPTS=""
|
|
AUTHOPTS=""
|
|
OTHEROPTS=""
|
|
CONNECTIONOPTS=""
|
|
if [[ $mode == "restic" ]]; then
|
|
MYREPO="restic: $REPO_PATH"
|
|
export RESTIC_REPOSITORY="$REPO_PATH"
|
|
export RESTIC_PASSWORD_FILE=`getrepopassfile "$f"`
|
|
if [[ ! -z $RESTIC_PASSWORD_FILE ]]; then
|
|
unset RESTIC_PASSWORD
|
|
AUTHOPTS="-p ${RESTIC_PASSWORD_FILE}"
|
|
else
|
|
AUTHOPTS="-p ${DEF_AUTHFILE}"
|
|
fi
|
|
if [[ -z $KSPEED ]]; then
|
|
SPEEDOPTS=""
|
|
else
|
|
SPEEDOPTS="--limit-upload $KSPEED"
|
|
fi
|
|
|
|
if [[ -z $CONNECTIONS ]]; then
|
|
CONNECTIONSOPTS=""
|
|
else
|
|
CONNECTIONSOPTS="-o b2.connections=${CONNECTIONS}"
|
|
fi
|
|
|
|
checktag "$f" postgres
|
|
if [[ $? -eq 0 ]]; then
|
|
CONNECTIONSOPTS="$OTHEROPTS --tag postgres"
|
|
fi
|
|
checktag "$f" mysql
|
|
if [[ $? -eq 0 ]]; then
|
|
CONNECTIONSOPTS="$OTHEROPTS --tag mysql"
|
|
fi
|
|
|
|
if [[ $VERBOSE -eq 1 ]]; then
|
|
OTHEROPTS="$OTHEROPTS -v"
|
|
fi
|
|
|
|
DBOPTS=""
|
|
DBDUMPCMD=""
|
|
checktag "$f" postgres
|
|
if [[ $? -eq 0 ]]; then
|
|
DBOPTS="$DBOPTS --clean"
|
|
OTHEROPTS="$OTHEROPTS --stdin"
|
|
if [[ -z ${DATAPATH} ]]; then
|
|
DBDUMPCMD="pg_dumpall"
|
|
DBFILENAME="alldbs.sql"
|
|
else
|
|
DBDUMPCMD="pg_dump"
|
|
DBFILENAME="${DATAPATH}.sql"
|
|
DBOPTS="$DBOPTS ${DATAPATH}" # Last dbdump option is database name
|
|
fi
|
|
fi
|
|
checktag "$f" mysql
|
|
if [[ $? -eq 0 ]]; then
|
|
DBOPTS="$DBOPTS --all-databases --master-data --single-transaction"
|
|
OTHEROPTS="$OTHEROPTS --stdin"
|
|
if [[ -z ${DATAPATH} ]]; then
|
|
DBDUMPCMD="mysqldump"
|
|
DBFILENAME="mysql_alldbs.sql"
|
|
else
|
|
DBDUMPCMD="mysqldump"
|
|
DBFILENAME="mysql_${DATAPATH}.sql"
|
|
DBOPTS="$DBOPTS ${DATAPATH}" # Last dbdump option is database name
|
|
fi
|
|
fi
|
|
|
|
export RESTIC_EXCLUDEFILE=`getrepoexcludefile "$f"`
|
|
if [[ ! -z $RESTIC_EXCLUDEFILE ]]; then
|
|
OTHEROPTS="$OTHEROPTS --exclude-file=${RESTIC_EXCLUDEFILE}"
|
|
fi
|
|
OTHEROPTS="$OTHEROPTS $RESTICOPTS"
|
|
elif [[ $mode == "rsync" ]]; then
|
|
# rsync
|
|
MYREPO="rsync: $REPO_PATH"
|
|
export RSYNC_REPOSITORY="$REPO_PATH"
|
|
export RSYNC_FULLDIR="${DEF_RSYNC_DIR}/${REPO}"
|
|
if [[ $USINGDEFAULT -eq 1 ]]; then
|
|
list="DEF_RSYNC_SERVER DEF_RSYNC_USER DEF_RSYNC_DIR RSYNC_OPTIONS"
|
|
else
|
|
list="RSYNC_OPTIONS"
|
|
fi
|
|
|
|
for xx in $list; do
|
|
eval val='$'$xx
|
|
if [[ -z $val ]]; then
|
|
error "\$$xx not set. Please update ${RCFILE}."
|
|
echo ""
|
|
usage-rc
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
if [[ -z $KSPEED ]]; then
|
|
SPEEDOPTS=""
|
|
else
|
|
SPEEDOPTS="--bwlimit=${KSPEED}k"
|
|
fi
|
|
CONNECTIONSOPTS=""
|
|
else
|
|
# rclone
|
|
MYREPO="rclone: $REPO_PATH"
|
|
export RCLONE_REPOSITORY="$REPO_PATH"
|
|
if [[ -z $KSPEED ]]; then
|
|
SPEEDOPTS=""
|
|
else
|
|
SPEEDOPTS="--bwlimit=${KSPEED}k"
|
|
fi
|
|
|
|
if [[ -z $CONNECTIONS ]]; then
|
|
CONNECTIONSOPTS=""
|
|
else
|
|
CONNECTIONSOPTS="--transfers=${CONNECTIONS}"
|
|
fi
|
|
fi
|
|
|
|
|
|
if [[ ! $CMD == "check" ]]; then
|
|
text="Starting '$CMD' on repo '$REPO' [$DATAPATH <-> $MYREPO]"
|
|
log "$text"
|
|
fi
|
|
if [[ $CMD == "check" ]]; then
|
|
line=`egrep "^$REPO:go:" $STATFILE 2>/dev/null`
|
|
if [[ $? -eq 0 ]]; then
|
|
# Get result
|
|
code=`echo "$line" | cut -d: -f3`
|
|
stamp=`echo "$line" | cut -d: -f4`
|
|
date --version 2>&1 | grep -q GNU
|
|
if [[ $? -eq 0 ]]; then
|
|
humanstamp=`date --date="@$stamp"`
|
|
else
|
|
humanstamp=`date -r $stamp`
|
|
fi
|
|
now=`date +%s`
|
|
age=$( echo "$now - $stamp" | bc )
|
|
oneday=$( echo "24 * 60 * 60" | bc)
|
|
if [[ $code -eq 0 ]]; then
|
|
if [[ $age -le $oneday ]]; then
|
|
if [[ $TELEGRAM -eq 1 ]]; then
|
|
echo "**OK**: Last backup for __${REPO}__ succeeded on $humanstamp."
|
|
else
|
|
cecho "$GREEN" "OK: Last backup for $BOLD${REPO}$PLAIN$GREEN succeeded on $humanstamp."
|
|
fi
|
|
rv=0
|
|
else
|
|
if [[ $TELEGRAM -eq 1 ]]; then
|
|
echo "**CRITICAL**: Last backup for __${REPO}__ succeeded on $humanstamp (age $age not in last 24 hours)."
|
|
else
|
|
cecho "$RED" "CRITICAL: Last backup for $BOLD${REPO}${PLAIN}${RED} succeeded on $humanstamp (age $age not in last 24 hours)."
|
|
fi
|
|
errcount=$(( $errcount + 1))
|
|
rv=2
|
|
fi
|
|
else
|
|
if [[ $TELEGRAM -eq 1 ]]; then
|
|
echo "**CRITICAL**: Last backup for __${REPO}__ failed on $humanstamp."
|
|
else
|
|
cecho "$RED" "CRITICAL: Last backup for $BOLD${REPO}$PLAIN$RED failed on $humanstamp."
|
|
fi
|
|
rv=2
|
|
errcount=$(( $errcount + 1))
|
|
fi
|
|
else
|
|
if [[ $TELEGRAM -eq 1 ]]; then
|
|
echo "**WARNING**: No history found for repo __${REPO}__"
|
|
else
|
|
cecho "$YELLOW" "WARNING: No history found for repo $BOLD$REPO$PLAIN$YELLOW'"
|
|
fi
|
|
rv=1
|
|
warncount=$(( $warncount + 1))
|
|
fi
|
|
elif [[ $mode == "restic" ]]; then
|
|
if [[ $CMD == "go" ]]; then
|
|
if checktag "$f" postgres; then
|
|
btype=postgres
|
|
elif checktag "$f" mysql; then
|
|
btype=mysql
|
|
else
|
|
btype=normal
|
|
fi
|
|
if [[ $CRONMODE -eq 1 ]]; then
|
|
if [[ $VERBOSE -eq 1 ]]; then
|
|
echo "debug: running: [${RESTIC} backup $AUTHOPTS $CONNECTIONSOPTS $SPEEDOPTS $OTHEROPTS $DATAPATH" >>${LOG}
|
|
echo "debug: full log is in ${FULLLOGFILE}" >>${LOG}
|
|
if [[ $btype == "postgres" || $btype == "mysql" ]] ; then
|
|
$DBDUMPCMD $DBOPTS | ${RESTIC} backup $AUTHOPTS $CONNECTIONSOPTS $SPEEDOPTS $OTHEROPTS --stdin-filename $DBFILENAME 2>&1 >> ${FULLLOGFILE}
|
|
rv=$?
|
|
else
|
|
${RESTIC} backup $AUTHOPTS $CONNECTIONSOPTS $SPEEDOPTS $OTHEROPTS $DATAPATH 2>&1 >> ${FULLLOGFILE}
|
|
rv=$?
|
|
fi
|
|
egrep "^(Added|processed|snapshot)" ${FULLLOGFILE} >>${LOG}
|
|
else
|
|
if [[ $btype == "postgres" || $btype == "mysql" ]] ; then
|
|
$DBDUMPCMD $DBOPTS | ${RESTIC} backup $AUTHOPTS $CONNECTIONSOPTS $SPEEDOPTS $OTHEROPTS --stdin-filename $DBFILENAME 2>&1 | egrep "^(Added|processed|snapshot)" >> ${LOG}
|
|
rv=${PIPESTATUS[0]}
|
|
else
|
|
${RESTIC} backup $AUTHOPTS $CONNECTIONSOPTS $SPEEDOPTS $OTHEROPTS $DATAPATH 2>&1 | egrep "^(Added|processed|snapshot)" >> ${LOG}
|
|
rv=${PIPESTATUS[0]}
|
|
fi
|
|
fi
|
|
else
|
|
if [[ $btype == "postgres" || $btype == "mysql" ]] ; then
|
|
[[ $VERBOSE -eq 1 ]] && log "debug: running: [$DBDUMPCMD $DBOPTS | ${RESTIC} backup $AUTHOPTS $CONNECTIONSOPTS $SPEEDOPTS $OTHEROPTS --stdin-filename $DBFILENAME"
|
|
$DBDUMPCMD $DBOPTS | ${RESTIC} backup $AUTHOPTS $CONNECTIONSOPTS $SPEEDOPTS $OTHEROPTS --stdin-filename $DBFILENAME 2>&1 >> ${LOG}
|
|
rv=$?
|
|
else
|
|
[[ $VERBOSE -eq 1 ]] && log "debug: running: [${RESTIC} backup $AUTHOPTS $CONNECTIONSOPTS $SPEEDOPTS $OTHEROPTS $DATAPATH"
|
|
${RESTIC} backup $AUTHOPTS $CONNECTIONSOPTS $SPEEDOPTS $OTHEROPTS $DATAPATH 2>&1 >> ${LOG}
|
|
rv=$?
|
|
fi
|
|
fi
|
|
elif [[ $CMD == "get" ]]; then
|
|
RESTOREDIR="$RESTOREDIRBASE/$f/"
|
|
snaps=$(${RESTIC} snapshots $AUTHOPTS $CONNECTIONSOPTS 2>&1 | grep -v ID | awk '(NF >= 4) { print }')
|
|
if checktag "$f" postgres; then
|
|
snaps=$(echo "$snaps" | grep -w postgres)
|
|
elif checktag "$f" mysql; then
|
|
snaps=$(echo "$snaps" | grep -w mysql)
|
|
else
|
|
snaps=$(echo "$snaps" | egrep -vw "postgres|mysql")
|
|
fi
|
|
snapids=$(echo "$snaps" | awk '{ print $1 }')
|
|
defsid=$(echo "$snapids" | tail -1)
|
|
|
|
if [[ -z $RESTORESNAPID ]]; then
|
|
cecho "$BOLD$CYAN" "Found these snapshots:"
|
|
echo "$snaps"
|
|
RESTORESNAPID="xxxxxx"
|
|
while [[ ! $snaps == *$RESTORESNAPID* ]]; do
|
|
read -p "snapshot [$defsid]: " RESTORESNAPID
|
|
if [[ $RESTORESNAPID == "latest" || -z $RESTORESNAPID ]]; then
|
|
RESTORESNAPID=$defsid
|
|
fi
|
|
done
|
|
elif [[ $RESTORESNAPID == "latest" ]]; then
|
|
RESTORESNAPID=$defsid
|
|
elif [[ ! $snaps == *$RESTORESNAPID* ]]; then
|
|
error "snapshot '$RESTORESNAPID' not found."
|
|
echo
|
|
cecho "$RED" "Valid snapshots for repo $f:"
|
|
cecho "$RED" "$snaps"
|
|
exit 1
|
|
|
|
fi
|
|
if [[ -z $RESTORELIST ]]; then
|
|
action "Opening repo for snapshot $BOLD$RESTORESNAPID$PLAIN$CYAN..."
|
|
wd=/
|
|
updatedir
|
|
c="ls"
|
|
doit=0
|
|
while [[ ! $c == "x" && ! c == "q" ]]; do
|
|
if [[ $c == "ls" ]]; then
|
|
echo "$contents"
|
|
elif [[ $c == "go" ]]; then
|
|
if [[ -z $RESTORELIST ]]; then
|
|
cecho "$RED" "Nothing to restore!"
|
|
else
|
|
doit=1
|
|
fi
|
|
break
|
|
elif [[ $c == "x" || $c == "q" ]]; then
|
|
doit=0
|
|
break
|
|
elif [[ $c == "?" || $c == "h" ]]; then
|
|
restorecmd_help
|
|
elif [[ -z $c ]]; then
|
|
true
|
|
elif [[ $c == "dirs" ]]; then
|
|
echo "subdirs:"
|
|
echo "$subdirs" | sed -e 's/^/ /'
|
|
elif [[ $c == w* ]]; then
|
|
showrestoreinfo
|
|
elif [[ $c == a* || $c == mark* ]]; then
|
|
file=$(echo "$c" | awk '{ print $2 }')
|
|
# remove leading /
|
|
file=$(echo $file | sed -e 's,^/,,')
|
|
if [[ -z $file ]]; then
|
|
error "no file provided"
|
|
else
|
|
echo "$RESTORELIST" | tr ' ' '\n' | egrep -q "^$wd$file$"
|
|
thisrv=$?
|
|
if [[ $thisrv -eq 0 ]]; then
|
|
error "'$file' already in restore list"
|
|
else
|
|
if [[ $file == */* ]]; then
|
|
# add without checking
|
|
if [[ -z $RESTORELIST ]]; then
|
|
RESTORELIST="$wd$file"
|
|
else
|
|
RESTORELIST="$RESTORELIST $wd$file"
|
|
fi
|
|
action "Added to restore list: $BOLD$file$PLAIN$CYAN (no verify)"
|
|
else
|
|
echo "$subfiles" | grep -qw "$file"
|
|
rv=$?
|
|
if [[ $rv -eq 0 ]]; then
|
|
RESTORELIST="$RESTORELIST $wd$file"
|
|
action "Added to restore list: $BOLD$file"
|
|
else
|
|
error "'$file' doesn't exist"
|
|
fi
|
|
fi
|
|
fi
|
|
fi
|
|
elif [[ $c == d* || $c == unmark* ]]; then
|
|
file=$(echo "$c" | awk '{ print $2 }')
|
|
file="$wd$file"
|
|
echo "$RESTORELIST" | tr ' ' '\n' | egrep -q "^$file$"
|
|
if [[ $? -eq 0 ]]; then
|
|
#RESTORELIST=$(echo "$RESTORELIST" | sed -e 's/^ //' | tr ' ' '\n' | grep -wv $file | tr '\n' ' ' | egrep -v "^$" | sed -e 's/^/ /')
|
|
RESTORELIST=$(echo "$RESTORELIST" | sed -e "s, $file,,")
|
|
action "Removed from restore list: $BOLD$file"
|
|
else
|
|
error "Restore list doesn't contain '$file'"
|
|
fi
|
|
elif [[ $c == cd* ]]; then
|
|
newdir=$(echo "$c" | awk '{ print $2 }')
|
|
olddir=$wd
|
|
needupdate=1
|
|
if [[ $newdir == "/" ]]; then
|
|
wd=$newdir
|
|
rv=0
|
|
elif [[ $newdir == /* ]]; then
|
|
wd=$newdir/
|
|
updatedir
|
|
rv=$?
|
|
needupdate=0
|
|
elif [[ $newdir == */* ]]; then
|
|
wd=$wd$newdir/
|
|
updatedir
|
|
rv=$?
|
|
needupdate=0
|
|
elif [[ $newdir == ".." ]]; then
|
|
wd=$(dirname $wd)
|
|
rv=0
|
|
else
|
|
echo "$subdirs" | grep -qw "$newdir"
|
|
rv=$?
|
|
if [[ $rv -eq 0 ]]; then
|
|
wd=$wd$newdir/
|
|
fi
|
|
fi
|
|
if [[ $rv -eq 0 ]]; then
|
|
[[ $needupdate -eq 1 ]] && updatedir
|
|
else
|
|
error "no such directory '$newdir'"
|
|
wd=$olddir
|
|
fi
|
|
elif [[ $c == lcd* ]]; then
|
|
newlocaldir=$(echo "$c" | awk '{ print $2 }')
|
|
if [[ -d $newlocaldir ]]; then
|
|
RESTOREDIR="$newlocaldir"
|
|
elif [[ -e $newlocaldir ]]; then
|
|
error "'$newlocaldir' is not a directory"
|
|
else
|
|
error "'$newlocaldir' does not exist"
|
|
fi
|
|
else
|
|
error "bad command '$c' (? for help)"
|
|
fi
|
|
echo
|
|
action "Restore path: $RESTOREDIR"
|
|
echo -en "$CYAN$wd> $PLAIN"
|
|
read c
|
|
done
|
|
else
|
|
# restorelist not empty
|
|
doit=1
|
|
fi
|
|
if [[ $doit -eq 1 ]]; then
|
|
showrestoreinfo
|
|
action "Performing restore..."
|
|
echo
|
|
RESTOREOPTS=""
|
|
for r in $RESTORELIST; do
|
|
RESTOREOPTS="$RESTOREOPTS -i $r"
|
|
done
|
|
[[ $VERBOSE -eq 1 ]] && log "debug: running: ${RESTIC} restore $AUTHOPTS $CONNECTIONSOPTS -t "$RESTOREDIR" $RESTOREOPTS $RESTORESNAPID"
|
|
${RESTIC} restore $AUTHOPTS $CONNECTIONSOPTS -t "$RESTOREDIR" $RESTOREOPTS $RESTORESNAPID 2>&1 >> ${LOG}
|
|
rv=$?
|
|
|
|
if [[ $rv -eq 0 ]]; then
|
|
# check that we got them all
|
|
tried=0
|
|
success=0
|
|
failed=0
|
|
for r in $RESTORELIST; do
|
|
tried=$((tried + 1))
|
|
if [[ -e $RESTOREDIR/$r ]]; then
|
|
success=$((success + 1))
|
|
else
|
|
failed=$((failed + 1))
|
|
fi
|
|
done
|
|
echo
|
|
echo -en "${CYAN}${BOLD}Restored $success / $tried items$PLAIN"
|
|
[[ $failed -gt 0 ]] && cecho "$RED" " ($failed failed)" || echo
|
|
echo
|
|
if [[ $success -eq $tried ]]; then
|
|
rv=0
|
|
else
|
|
rv=1
|
|
fi
|
|
fi
|
|
else
|
|
action "${BOLD}Aborting..."
|
|
rv=1
|
|
fi
|
|
elif [[ $CMD == "ls" ]]; then
|
|
|
|
[[ $VERBOSE -eq 1 ]] && log "debug: running: ${RESTIC} snapshots $AUTHOPTS $CONNECTIONSOPTS $OTHEROPTS "
|
|
${RESTIC} snapshots $AUTHOPTS $CONNECTIONSOPTS 2>&1 >> ${LOG}
|
|
rv=$?
|
|
elif [[ $CMD == "stats" ]]; then
|
|
${RESTIC} stats $AUTHOPTS $CONNECTIONSOPTS 2>&1 >> ${LOG}
|
|
rv=$?
|
|
elif [[ $CMD == "diff" ]]; then
|
|
log "Error: diff not supported in restic."
|
|
rv=1
|
|
elif [[ $CMD == "init" ]]; then
|
|
# check whether it already exists first!
|
|
${RESTIC} stats $AUTHOPTS $CONNECTIONSOPTS >/dev/null 2>&1
|
|
if [ $? -eq 0 ]; then
|
|
log "ERROR: Repo ${REPO} already exists. Aborting."
|
|
rv=1
|
|
else
|
|
cmdtorun="${RESTIC} init $AUTHOPTS $CONNECTIONSOPTS"
|
|
if [[ $VERBOSE -eq 1 ]]; then
|
|
log "export RESTIC_REPOSITORY=\"$REPO_PATH\""
|
|
log "export RESTIC_PASSWORD_FILE=`getrepopassfile \"$f\"`"
|
|
log "will run: [${cmdtorun}]"
|
|
fi
|
|
errtext=`${RESTIC} init $AUTHOPTS $CONNECTIONSOPTS 2>&1`
|
|
rv=$?
|
|
fi
|
|
elif [[ $CMD == "forget" ]]; then
|
|
# age out stuff after RESTIC_KEEPDAYS days
|
|
FORGETOPTS="-d ${RESTIC_KEEPDAYS}"
|
|
${RESTIC} forget $AUTHOPTS $CONNECTIONSOPTS $FORGETOPTS 2>&1 | awk '($1 == "Applying") { print } ($2 == "snapshots") { show=0 } (show == 1 && $1 != "ID" && NF > 1) { print "Removing: " $0 } ($1 == "remove") { show=1 }' >> ${LOG}
|
|
rv=$?
|
|
${RESTIC} prune $AUTHOPTS 2>&1 | egrep "^(repository contain|found|will|remove)" >> ${LOG}
|
|
else
|
|
log "Error: invalid command $CMD"
|
|
rv=1
|
|
fi
|
|
elif [[ $mode == "rsync" ]]; then
|
|
REMOTE=""
|
|
if checktag "$f" postgres; then
|
|
log "Error: postgres backups not supported for rsync mode."
|
|
rv=1
|
|
elif checktag "$f" mysql; then
|
|
log "Error: mysql backups not supported for rsync mode."
|
|
rv=1
|
|
elif [[ $CMD == "go" ]]; then
|
|
${RSYNC} ${RSYNC_OPTIONS} $DATAPATH/ ${RSYNC_REPOSITORY} 2>&1 >> ${LOG}
|
|
rv=$?
|
|
elif [[ $CMD == "ls" ]]; then
|
|
ssh ${DEF_RSYNC_USER}@${DEF_RSYNC_SERVER} ls ${RSYNC_FULLDIR} 2>&1 >> ${LOG}
|
|
rv=$?
|
|
elif [[ $CMD == "stats" ]]; then
|
|
action "Size of remote data for ${REPO}:"
|
|
ssh ${DEF_RSYNC_USER}@${DEF_RSYNC_SERVER} du -hs ${RSYNC_FULLDIR} 2>&1 >> ${LOG}
|
|
rv=$?
|
|
elif [[ $CMD == "diff" ]]; then
|
|
${RSYNC} ${RSYNC_OPTIONS} -n $DATAPATH/ ${RSYNC_REPOSITORY} 2>&1 >> ${LOG}
|
|
rv=$?
|
|
elif [[ $CMD == "init" ]]; then
|
|
# check whether it already exists first!
|
|
ssh ${DEF_RSYNC_USER}@${DEF_RSYNC_SERVER} ls -d ${RSYNC_FULLDIR} 2>&1 >> ${LOG}
|
|
if [ $? -eq 0 ]; then
|
|
log "ERROR: Repo ${REPO} already exists. Aborting."
|
|
else
|
|
ssh ${DEF_RSYNC_USER}@${DEF_RSYNC_SERVER} mkdir -p ${RSYNC_FULLDIR} 2>&1 >>${LOG}
|
|
if [ $? -eq 0 ]; then
|
|
log "Created directory ${RSYNC_FULLDIR}..."
|
|
else
|
|
log "Creation of ${RSYNC_FULLDIR} failed."
|
|
fi
|
|
fi
|
|
elif [[ $CMD == "forget" ]]; then
|
|
log "Error: forget not supported for $mode mode."
|
|
rv=1
|
|
else
|
|
log "Error: invalid command $CMD"
|
|
rv=1
|
|
fi
|
|
else
|
|
# rclone
|
|
REMOTE=""
|
|
if checktag "$f" postgres; then
|
|
log "Error: postgres backups not supported for $mode mode."
|
|
rv=1
|
|
elif checktag "$f" mysql; then
|
|
log "Error: mysql backups not supported for $mode mode."
|
|
rv=1
|
|
elif [[ $CMD == "go" ]]; then
|
|
${RCLONE} sync $RCLONEOPTS $CONNECTIONSOPTS $SPEEDOPTS $OTHEROPTS $DATAPATH $RCLONE_REPOSITORY 2>&1 >> ${LOG}
|
|
rv=$?
|
|
elif [[ $CMD == "ls" ]]; then
|
|
${RCLONE} ls $RCLONEOPTS $CONNECTIONSOPTS $SPEEDOPTS $OTHEROPTS $RCLONE_REPOSITORY 2>&1 >> ${LOG}
|
|
rv=$?
|
|
elif [[ $CMD == "stats" ]]; then
|
|
${RCLONE} size $RCLONEOPTS $CONNECTIONSOPTS $SPEEDOPTS $OTHEROPTS $RCLONE_REPOSITORY 2>&1 >> ${LOG}
|
|
rv=$?
|
|
elif [[ $CMD == "diff" ]]; then
|
|
${RCLONE} check $RCLONEOPTS $CONNECTIONSOPTS $SPEEDOPTS $OTHEROPTS $RCLONE_REPOSITORY 2>&1 >> ${LOG}
|
|
rv=$?
|
|
elif [[ $CMD == "init" ]]; then
|
|
# check whether it already exists first!
|
|
${RCLONE} size $RCLONEOPTS $RCLONE_REPOSITORY >/dev/null 2>&1
|
|
if [ $? -eq 0 ]; then
|
|
log "ERROR: Repo ${REPO} already exists. Aborting."
|
|
else
|
|
# does 'remote' exist?
|
|
${RCLONE} listremotes | grep -q remote: >/dev/null 2>&1
|
|
rv=$?
|
|
if [ $rv -ne 0 ]; then
|
|
log "Rclone remote doesn't exist - creating it..."
|
|
${RCLONE} $RCLONEOPTS config create remote b2 account $B2_APP_ID key $B2_APP_KEY >>${LOG}
|
|
rv=$?
|
|
|
|
if [ $rv -ne 0 ]; then
|
|
log "Rclone remote init failed."
|
|
exit 1
|
|
fi
|
|
|
|
fi
|
|
|
|
if [ $rv -eq 0 ]; then
|
|
log "Creating B2 bucket for $RCLONE_REPOSITORY..."
|
|
# create the bucket
|
|
${RCLONE} $RCLONEOPTS mkdir $RCLONE_REPOSITORY >>${LOG}
|
|
else
|
|
log "Rclone bucket creation of $RCLONE_REPOSITORY failed."
|
|
fi
|
|
fi
|
|
elif [[ $CMD == "forget" ]]; then
|
|
log "Error: forget not supported for rclone mode."
|
|
rv=1
|
|
else
|
|
log "Error: invalid command $CMD"
|
|
rv=1
|
|
fi
|
|
fi
|
|
|
|
if [[ ! $CMD == "check" ]]; then
|
|
# Record result
|
|
updatestats $f $CMD $rv
|
|
|
|
# did our command succeed?
|
|
if [[ $rv -ne 0 ]]; then
|
|
log "Error: '$CMD' on repo '$REPO' failed"
|
|
if [[ ! -z $errtext ]]; then
|
|
log "$errtext"
|
|
fi
|
|
fi
|
|
log "Finished '$CMD' on repo '$REPO'"
|
|
fi
|
|
done
|
|
|
|
# Clean up
|
|
for f in $REPO_LIST; do
|
|
check_datamount_needed "$f" # sets $NEEDMOUNT
|
|
if [[ $NEEDMOUNT -eq 1 ]]; then
|
|
do_umount
|
|
fi
|
|
|
|
check_repomount_needed "$f" # sets $MOUNTREPO
|
|
if [[ $MOUNTREPO -eq 1 ]]; then
|
|
path=`gettagval "$f" premount`
|
|
if is_mounted $path; then
|
|
umount $path
|
|
if [[ $? -eq 0 ]]; then
|
|
[[ ! $CMD == "check" ]] && log "Un-mount of '$path': success"
|
|
else
|
|
[[ ! $CMD == "check" ]] && log "Un-mount of '$path': FAILED"
|
|
fi
|
|
fi
|
|
fi
|
|
done
|
|
|
|
if [[ $CMD == "check" ]]; then
|
|
if [[ $errcount -ge 1 ]]; then
|
|
rv=2
|
|
elif [[ $warncount -ge 1 ]]; then
|
|
rv=1
|
|
else
|
|
rv=0
|
|
fi
|
|
exit $rv
|
|
fi
|
|
|