#!/bin/bash
# pe (Perform Everywhere) - perform the same tasks on many computers
# Copyright (C) 2018 Sebastian Krause
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
# usage info
#
usage() {
cat < file
--import FILE imports a given config file (and overwrites the old one).
-h | --help help
-v | --version shows the version
EOF
}
helptext() {
cat << EOF
This is pe (Perform Everywhere).
EOF
usage
echo
options
cat << EOF
Performs COMMAND on all computers given in the script - after asking
for the ssh password for each computer.
If you want to leave out a machine, simply press Enter when being asked
for the password.
If "sudo" is used in COMMAND, the script uses the given ssh password.
The remote computers are stored in ~/.pe/config.
This program comes with ABSOLUTELY NO WARRANTY. This program is free software:
you can redistribute it and/or modify it under the terms of the GNU General
Public License as published by the Free Software Foundation, either version 3
of the License, or (at your option) any later version.
You are welcome to redistribute this program under certain conditions.
S. Krause 2018, bashscripts@sebkrause.de
EOF
}
#
# config file
#
configpath="$HOME/.pe"
configfile="$configpath""/config"
writeconfig() {
i=0
err=0
mv "$configfile" "$configfile"".old"
[[ 0 -ne $? ]] && err=1
[[ 0 -eq $err ]] && cat > "$configfile" <> "$configfile"
[[ 0 -ne $? ]] && err=1
i=$((i+1))
done
if [ 0 -ne $err ]
then
echo "Error writing config file! Exit."
rm -f "$configfile" 2&>1 1>/dev/null
mv "$configfile"".old" "$configfile"
exit 1
else
rm "$configfile"".old"
fi
}
createconfig() {
if [ ! -f "$configfile" ]
then
echo "$(basename $0) seems to run the first time. Creating config file first."
if [ ! -d "$configpath" ]
then
echo -n "Creating directory $configpath... "
mkdir "$configpath"
if [ 0 -ne $? ]
then
echo
echo "Could not create directory $configpath - maybe permission denied? Exit."
exit 1
else
echo "Done."
fi
fi
echo -n "Creating file $configfile... "
touch "$configfile"
if [ 0 -ne $? ]
then
echo
echo "Could not create file $configfile - maybe permission denied? Exit."
exit 1
else
echo "Done."
fi
fi
}
readconfig() {
# check for config file and create if neccessary
if [ ! -a "$configfile" ]
then
createconfig
fi
# config file exists, so let's read it
n=0
comp=0
while read -r "line"
do
n=$((n+1))
line=(${line%%#*}) # remove comments
if [ -z "${line[0]}" ] # ignore empty lines
then
continue
fi
if [ 3 -ne ${#line[*]} -a 4 -ne ${#line[*]} ] # 3 or 4 words needed: nickname computername user [port]
then
echo "Line $n in $configfile: Parse error ($((${#line[*]}+1)) entries found instead of 2 or3). Ignoring..."
continue
fi
# read values
nick[$comp]="${line[0]}"
pcname[$comp]="${line[1]}"
user[$comp]="${line[2]}"
if [ -z "${line[3]}" ]
then
port[$comp]=22 # standard ssh port
else
port[$comp]="${line[3]}"
fi
comp=$((comp+1))
done < "$configfile"
}
exportconfig() {
cat "$configfile"
if [ 0 -ne $? ]
then
echo "Error printing config file! Exit."
exit 1
fi
}
importconfig() {
echo "$1"
if [ -z "$1" -o ! -f "$1" ]
then
echo "Can't read file parameter! Exit."
exit 1
fi
cp "$1" "$configfile"
if [ 0 -eq $? ]
then
echo "Config file '$1' imported. Try $(basename $0) --list..."
exit 0
else
echo "Could not import file $1! Exit."
exit 1
fi
}
#
# computers
#
listcomputer() {
n=${#pcname[*]}
i=0
if [ $n -eq 0 ]
then
echo "No computers in list. Try $(basename $0) --add. Exit."
exit 0
else
echo " Nickname | Computername | Username | Port | # "
echo "-------------+--------------------------+----------------+--------+-----"
fi
while [ $i -lt $n ]
do
printf " %-12s| %-25s| %-15s| %-6s | %-3s\n" "${nick[$i]}" "${pcname[$i]}" "${user[$i]}" "${port[$i]}" "$i"
i=$((i+1))
done
}
addcomputer() {
if [ -z "$2" ]
then # interactive mode
read -p "Nickname for the new computer: " -r 'input[0]'
if [ -z "${input[0]}" ]
then
echo "Empty nickname. Exit."
exit 1
fi
read -p "Computername (as used by ssh): " -r 'input[1]'
if [ -z "${input[1]}" ]
then
echo "Empty computername. Exit."
exit 1
fi
read -p "Username (as used by ssh): " -r 'input[2]'
if [ -z "${input[2]}" ]
then
echo "Empty username. Exit."
exit 1
fi
read -p "ssh port (empty input: it will be set to 22): " -r 'input[3]'
if [ -z "${input[3]}" ]
then
input[3]=22
fi
else # read from command line
for i in {0..3}
do
input[$i]=$2
shift
if [ -z "${input[$i]}" ]
then
if [ $i -le 2 ]
then
echo "At least 3 parameters needed: nickname computername username [port]. Exit."
exit 1
else
input[3]=22
fi
fi
done
if [ ! -z "$2" ]
then
echo "Too much parameters, only needed: nickname computername username [port]. Exit."
exit 1
fi
fi
# copy input[] into the arrays
i=${#pcname[*]}
nick[$i]="${input[0]}"
pcname[$i]="${input[1]}"
user[$i]="${input[2]}"
port[$i]="${input[3]}"
# write into config
writeconfig
echo "Computer "${input[0]}" added."
}
removecomputer() {
n=${#pcname[*]}
if [ $n -eq 0 ]
then
echo "No computer in list, so nothing can be deleted. Exit."
exit 0
fi
# let the user choose
listcomputer
read -r -p "Which computer shall be deleted? Value # from table: " del
if [ -z "$del" ]
then
echo "Invalid input. Exit."
exit 1
fi
if [ "$del" -lt 0 ]
then
echo "Invalid input. Exit."
exit 1
fi
if [ -z "${pcname[$del]}" ]
then
echo "Invalid input. Exit."
exit 1
fi
# remove: del #n by mv #n+1 -> #n, #n+2 -> #n+1 ... #m -> #m-1, #m=""
i=$((del+1))
name=${nick[$del]} # we need the nickname for echoing at the end
while [ ! -z "${pcname[$i]}" ]
do
nick[$((i-1))]="${nick[$i]}"
pcname[$((i-1))]="${pcname[$i]}"
user[$((i-1))]="${user[$i]}"
port[$((i-1))]="${port[$i]}"
i=$((i+1))
done
i=$((i-1))
nick[$i]=""
pcname[$i]=""
user[$i]=""
port[$i]=""
# save and exit
writeconfig
echo "Computer $name deleted from list."
}
editcomputer() {
n=${#pcname[*]}
if [ $n -eq 0 ]
then
echo "No computer in list, so nothing can be edited. Exit."
exit 0
fi
if [ $n -eq 1 ] # only one computer in list -> we don't ask the user for his choice
then
edit=0
else # choose an entry
listcomputer
read -r -p "Which computer shall be edited? Value # from table: " edit
if [ -z "$edit" ]
then
echo "Invalid input. Exit."
exit 1
fi
if [ "$edit" -lt 0 ]
then
echo "Invalid input. Exit."
exit 1
fi
if [ -z "${pcname[$edit]}" ]
then
echo "Invalid input. Exit."
exit 1
fi
fi
# ask for changes
prompt=" (Empty -> no change)"
echo "Old nickname: ${nick[$edit]}. New nickname? $prompt"
read -r input
if [ ! -z "$input" ]
then
nick[$edit]="$input"
fi
echo "Old computername: ${pcname[$edit]}. New computername? $prompt"
read -r input
if [ ! -z "$input" ]
then
pcname[$edit]="$input"
fi
echo "Old username: ${user[$edit]}. New username? $prompt"
read -r input
if [ ! -z "$input" ]
then
user[$edit]="$input"
fi
echo "Old ssh port: ${port[$edit]}. New port? $prompt"
read -r input
if [ ! -z "$input" ]
then
port[$edit]="$input"
fi
# save and exit
writeconfig
echo "The changes are saved."
}
#
# start
#
readconfig
# check userinput
silent=0
while [ -n "$1" ]
do
case "$1" in
"-h"|"--help")
helptext
exit 0
;;
"-s"|"--silent")
silent=1
;;
"--add")
addcomputer $*
exit 0
;;
"--list")
listcomputer
exit 0
;;
"--delete")
removecomputer
exit 0
;;
"--edit")
editcomputer
exit 0
;;
"--export")
exportconfig
exit 0
;;
"--import")
importconfig "$2"
exit 0
;;
"-v"|"--version")
version
exit 0
;;
*)
break
;;
esac
shift
done
# collect all non-option parameters (these are the commands to be performed)
i=0
while [ -n "$1" ]
do
usercommand[$i]=$1
shift
i=$((i+1))
done
command=${usercommand[@]}
# is there a command?
if [ -z "$command" ]
then
usage
echo "No command found!"
exit 1
fi
# check passwords
i=0
for pc in ${pcname[*]}
do
echo
echo "Password for ${user[$i]} on $pc: "
stty -echo
read pass[$i]
stty echo
if [ -z "${pass[$i]}" ]
then
echo "Computer $pc will be ignored."
fi
i=$((i+1))
done
# perform actions
i=0
for pc in ${pcname[*]}
do
# only do something if a password is given
if [ -n "${pass[$i]}" ]
then
if [ 0 -eq $silent ]
then
echo
echo "*** ***"
echo "Performing actions as ${user[$i]} on $pc..."
echo "*** ***"
echo
fi
# substitute "sudo" with "echo $password | sudo -S"
test=$(echo "$command" | grep "sudo ")
if [ -n "$test" ]
then
sedstring="s/sudo\ /echo\ ${pass[$i]}\ |\ sudo\ -S\ /g"
command=$(echo "$command" | sed "$sedstring")
fi
# check for computer
if [ ! -z "${pass[$i]}" -a 0 -eq $(ping -c 1 ${pcname[$i]} >/dev/null; echo $?) ]
then
# perform command
sshpass -p ${pass[$i]} ssh -t -p ${port[$i]} ${user[$i]}@$pc "$command"
else
[[ 0 -eq $silent ]] && echo -n "$pc not reachable. "
fi
[[ 0 -eq $silent ]] && echo "Working on $pc finished."
fi
i=$((i+1))
done
[[ 0 -eq $silent ]] && echo "Script done."
exit 0