david/sshbackup
david
/
sshbackup
Archived
1
0
Fork 0
This repository has been archived on 2023-12-23. You can view files and clone it, but cannot push or open issues or pull requests.
sshbackup/sshbackup

492 lines
14 KiB
Bash
Executable File

#!/bin/bash
# ************************************* #
# #
# sshbackup #
# #
# ************************************* #
# **** function definitions ****
bashtrap()
{
echo
echo "CTRL+C detected."
echo "commiting suicide."
exit 1
}
usage()
{
echo
echo "usage: sshbackup <options> [[user@]server:]/source/path /destination/path [versions]"
echo
echo "OPTIONS:"
echo " -h, --help show this message"
echo " -v, --version version information"
echo
echo " -l, --list <file> list of sources and destinations"
echo " -c, --config <file> alternate config file [~/.sshbackup]"
echo " -s, --sshkey <file> alternate sshkey [~/.ssh/id_rsa]"
echo " -b, --bandwidth <kbps> bandwidth limit in kbit/s"
echo
echo " -d, --deploy deploy settings to remote host"
echo " -n, --no-root run without root privileges"
echo
}
version()
{
echo
echo -e "vesion: \033[1;37m$version\033[0m"
echo "author: $author"
echo
}
interactive()
{
tty -s
return $?
}
amiroot()
{
if [ $(id -u) -eq 0 ]; then
return 0
else
return 1
fi
}
findhome()
{
#get executing user id
local userid=$(id -u)
#find out where executing users $HOME is
while read line; do
line=$(echo $line | sed 's/ //g')
line=$(echo $line | sed 's/:/ /g')
if [ $(echo $line | awk '{print $3}') -eq $userid ]; then
local home=$(echo $line | awk '{print $6}')
fi
done < /etc/passwd
#return home directory
echo $home
}
#this function is needed to pass on the
#password to the remote sudo command
pipewrap()
{
echo $1
local lockfile=$2;
while true; do
if [ ! -e $lockfile ]; then
return 0
fi
read -t 1 line
if [ $? -eq 0 ]; then
echo $line
fi
done
}
deploy()
{
local machine=$1 #user@machine.example.com
local user=${machine%@*} #user
if [ -z $user ]; then
user=$USER #set $USER if none is specified
fi
local machine=${machine#*@} #machine.example.com
local username=""
local password=""
local script="/tmp/sshbackup_deploy`date +%s`"
local pubkey=$(cat $2)
#create script which is executed on remote host (as root)
#TODO: check dependencies on remote system as well
echo '#!/bin/bash
user="'$user'"
pubkeyfile="'$pubkeyfile'"
machine="'$machine'"
cat /etc/passwd | grep -e ^'$user' >> /dev/null
if [ $? -eq 0 ]; then
echo; echo "aborting mission. user '$user' already exists on '$machine'."
exit 1
else
echo; echo "attempting to create user: '$user'"
useradd -m -d /home/'$user' -s /bin/bash '$user'
mkdir /home/'$user'/.ssh
echo "'$pubkey'" > /home/'$user'/.ssh/authorized_keys
chown -R '$user':'$user' /home/'$user'/.ssh
chmod 600 /home/'$user'/.ssh/authorized_keys
sshgroups=$(cat /etc/ssh/sshd_config | grep AllowGroups)
sshgroups=${sshgroups#AllowGroups }
usermod -a -G ${sshgroups// /,} '$user'
echo "'$user' ALL=(root)NOPASSWD: /usr/bin/rsync" >> /etc/sudoers
echo "remote settings deployed. hopefully :)"
fi' > $script
echo "i will now attempt to create the user $user and apply all needed"
echo "settings on following remote host: $machine"
echo "in order to do so i need a sudo enabled username and"
echo "password on the remote host. (hit Ctrl-C to abort)"
echo -e "please enter your [sudo] username: \c"
read username
echo -e "please enter your [sudo] password: \c"
read -s password
sshpass -p "$password" scp -q "$script" "$username@$machine:'$script'"
sshpass -p "$password" ssh -q "$username@$machine" "chmod +x '$script'"
sshpass -p "$password" ssh -q "$username@$machine" "sudo -K"
local lockfile=`mktemp`
eval pipewrap '$password' '$lockfile' | (sshpass -p "$password" ssh -q "$username@$machine" "sudo -S '$script'"; rm "$lockfile")
sshpass -p $password ssh -q "$username@$machine" rm $script
rm $script
#we will assume everything went fine
return 0
}
preflight()
{
#source and destination path?
if [ -z $sourcepath ]; then
echo "aborting mission. no source path given."
return 1
elif [ -z $destpath ]; then
echo "aborting mission. no destination path given."
return 1
fi
#amiroot?
if ( ! amiroot ) && [ $noroot -eq 0 ]; then
echo "aborting mission. you need be root or use the --no-root option."
return 1
fi
#check for dependencies
#TODO: and install them if necessary
local deps="rsync cat ssh scp sshpass rm mv grep awk date tty id"
local missingdeps=""
local depscount="0"
for dep in $deps; do
if ! ( command -v $dep >> /dev/null ); then
if [ $depscount -eq "0" ]; then
missingdeps="$dep"
else
missingdeps="$missingdeps $dep"
fi
let depscount++
fi
done
if [ $depscount -ne "0" ]; then
echo "aborting mission. missing dependencies. [$missingdeps]"
return 1
fi
#if there is a remote source or destination check for ssh key and config
if [[ $sourcepath =~ .*@.* ]]; then
#deactivate StrictHostKeyChecking for ssh client
if [ -r $HOME/.ssh/config ]; then
cat $HOME/.ssh/config | grep "StrictHostKeyChecking" >> /dev/null
if [ $? -ne 0 ]; then
echo "StrictHostKeyChecking no" >> $HOME/.ssh/config
fi
else
echo "StrictHostKeyChecking no" > $HOME/.ssh/config
fi
if [ -r $privkeyfile ]; then
#ssh key found
if ( interactive ) && [ $deploy -eq 1 ]; then
deploy ${sourcepath%:*} $pubkeyfile
fi
else
if ( interactive ); then
echo -e "no ssh key found. do you want to create a new key pair? [y/n] \c"
read choice
if [ -z $choice ]; then
echo "aborting mission. no ssh key found."
return 1
elif [ $choice == "y" ] || [ $choice == "Y" ]; then
#creating ssh key pair with default values
ssh-keygen -q -N "" -f $privkeyfile
if [ $? -ne 0 ]; then
echo "aborting mission. error occured while creating ssh key pair"
return 1
fi
echo -e "do you want to create a user and deploy this key now as well \c"
read choice
if [ -z $choice ]; then
echo "aborting mission. remote host must accept ssh key."
return 1
elif [ $choice == "y" ] || [ $choice == "Y" ]; then
#deploy key to remote system
deploy ${sourcepath%:*} $pubkeyfile
fi
else
echo "aborting mission. no ssh key found."
return 1
fi
else
echo "aborting mission. no ssh key found."
return 1
fi
fi
fi
return 0
}
sshbackup()
{
#creating local rsync options var
local cmdopt="$rsyncoptions"
#move existing versions
local num=$versions
while [ -d "$destpath/0" ]; do
if [ -d "$destpath/$num" ]; then
mv $destpath/$num $destpath/$((num+1))
fi
let num--
done
#create destpath if not existing
mkdir -p $destpath/0
#add link-destination option if existing
if [ -d $destpath/1 ]; then
cmdopt="$cmdopt --link-dest=$destpath/1"
fi
#bandwidth limit
if [ $limit -gt 0 ]; then
limit=$((limit/8))
cmdopt="$cmdopt --bwlimit=$limit"
fi
#run rsync
$localcmd $cmdopt -e "ssh -q -i $privkeyfile" --rsync-path="$remotecmd" $sourcepath $destpath/0
if [ $? -ne "0" ]; then
echo "an error occured while running backup for $sourcepath"
return 1
fi
#removing obsolet version(s)
local i=1
while [ -d $destpath/$((versions+i)) ]; do
rm -rf $destpath/$((versions+i))
let i++
done
return 0
}
# **** config section ****
version="0.4.0"
author="david@socialnerds.org"
HOME=$(findhome)
configfile="$HOME/.sshbackup"
privkeyfile="$HOME/.ssh/id_rsa"
pubkeyfile="$HOME/.ssh/id_rsa.pub"
#rsync options.
rsyncoptions="-qpogEthrzl --numeric-ids --no-motd"
#dotglob option removes bug while rsyncing folder with no visible files in it.
#TODO: only works if bash is the remote default shell
remotecmd="shopt -s dotglob; /usr/bin/sudo /usr/bin/rsync"
localcmd="rsync"
versions=999
config=0
sshkey=0
bandwidth=0
limit=0
list=0
noroot=0
deploy=0
options=$*
# **** start of script ****
#initialize bashtrap
trap bashtrap INT
# **** option handler ****
#end script if no options are given
if [ -z $1 ]; then
usage
exit 0
fi
for option in $options; do
case "$option" in
-h|--help)
usage
exit 0
;;
-v|--version)
version
exit 0
;;
-c|--config)
config=1
;;
-l|--list)
list=1
;;
-s|--sshkey)
sshkey=1
;;
-b|--bandwidth)
bandwidth=1
;;
-n|--no-root)
noroot=1
;;
-d|--deploy)
deploy=1
;;
*)
if [ $config -eq 1 ]; then
if [ -r "$option" ]; then
configfile=$option
config=0
else
echo "aborting mission. cannot read configfile. [$option]"
exit 1
fi
elif [ $sshkey -eq 1 ]; then
if [ -r $option ]; then
privkeyfile=$option
pubkeyfile=$option".pub"
sshkey=0
else
echo "aborting mission. cannot read privkeyfile. [$option]"
exit 1
fi
elif [ $bandwidth -eq 1 ]; then
if [ -z "${option//[0-9]/}" ]; then
limit=$option
else
echo "aborting mission. unknown bandwidth limit given. [$option]"
exit 1
fi
elif [ $list -eq 1 ]; then
if [ -r $option ]; then
listfile=$option
list=0
else
echo "aborting mission. cannot read listfile. [$option]"
exit 1
fi
else
if [[ $option =~ ^-.* ]]; then
echo "aborting mission. unknown option given. [$option]"
usage
exit 1
#TODO: what if source or destination is a number?
elif [ -z "${option//[0-9]/}" ]; then
versions="$option"
else
if [ -z "$sourcepath" ]; then
if [[ $option =~ .*@.* ]]; then
sourcepath="${option%/}/*"
elif [[ $option =~ ^/.* ]]; then
sourcepath="${option%/}/*"
elif [[ $option =~ .*:.* ]]; then
sourcepath="$USER@${option%/}/*"
else
sourcepath="$(pwd)/${option%/}/*"
fi
else
if [ -z "$destpath" ]; then
if [[ $option =~ ^/.* ]]; then
destpath="${option%/}"
elif [[ $option =~ .*@.* ]] || [[ $option =~ .*:.* ]]; then
echo "aborting mission. invalid destination path. [$option]"
exit 1
else
destpath="$(pwd)/${option%/}"
fi
fi
fi
fi
fi
;;
esac
done
#read config if there is one
if [ -r "$configfile" ]; then
source "$configfile"
fi
#read list if listfile is given
if [ -r "$listfile" ]; then
while read line; do
#find first letter
fletter=${line:0:1}
if [ -z $fletter ]; then
#skip line it's empty
:
elif [ $fletter = "#" ]; then
#skip line it's a comment
:
else
sourcepath=""
destpath=""
for option in $line; do
if [ -z "${option//[0-9]/}" ]; then
versions="$option"
else
if [ -z "$sourcepath" ]; then
if [[ $option =~ .*@.* ]]; then
sourcepath="${option%/}/*"
elif [[ $option =~ ^/.* ]]; then
sourcepath="${option%/}/*"
elif [[ $option =~ .*:.* ]]; then
sourcepath="$USER@${option%/}/*"
else
sourcepath="$(pwd)/${option%/}/*"
fi
else
if [ -z "$destpath" ]; then
if [[ $option =~ ^/.* ]]; then
destpath="${option%/}"
elif [[ $option =~ .*@.* ]] || [[ $option =~ .*:.* ]]; then
echo "aborting mission. invalid destination path. [$option]"
exit 1
else
destpath="$(pwd)/${option%/}"
fi
fi
fi
fi
done
if ( preflight ); then
sshbackup
fi
fi
done < "$listfile"
else
if ( preflight ); then
sshbackup
fi
fi
exit 0
# **** end of script ****